diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/pages/about.php b/pages/about.php index c5e2a30..c98d5ae 100644 --- a/pages/about.php +++ b/pages/about.php @@ -8,7 +8,7 @@ return <<<'ORIBI_SYNC_CONTENT' - + @@ -38,5 +38,5 @@ return <<<'ORIBI_SYNC_CONTENT' - +ORIBI_SYNC_CONTENT; ORIBI_SYNC_CONTENT; diff --git a/pages/contact.php b/pages/contact.php index 38a8c3e..49892b5 100644 --- a/pages/contact.php +++ b/pages/contact.php @@ -14,13 +14,13 @@ return <<<'ORIBI_SYNC_CONTENT' - + - - + + diff --git a/pages/corporate.php b/pages/corporate.php index 8cf2a30..ffe1647 100644 --- a/pages/corporate.php +++ b/pages/corporate.php @@ -7,12 +7,12 @@ * Post Types: page */ ?> - + - - - - + + + + @@ -24,7 +24,7 @@ - + @@ -32,7 +32,7 @@ - + diff --git a/pages/design.php b/pages/design.php index 9e61fc7..0c7a632 100644 --- a/pages/design.php +++ b/pages/design.php @@ -6,41 +6,41 @@ */ return <<<'ORIBI_SYNC_CONTENT' - + - - + + - + - + - + - - + + - + - + - + - + - + ORIBI_SYNC_CONTENT; diff --git a/pages/devices.php b/pages/devices.php index 8a8cde4..fab8d5a 100644 --- a/pages/devices.php +++ b/pages/devices.php @@ -44,14 +44,29 @@ return <<<'ORIBI_SYNC_CONTENT'

Bring Your Own Player (BYO)

-

Already have compatible media players? We can onboard BYO hardware and connect it to your Command Center. Our team will confirm compatibility requirements and provide setup guidance before rollout.

+

Already have compatible hardware? Connect it to your Command Center at the same per-screen rate — no premium for BYO. Set it up yourself using our step-by-step guides, or pay a one-time $99 professional setup fee and our team will configure and connect your device for you.

- + + + + + + + + + + + + + + + + diff --git a/pages/education.php b/pages/education.php index 2f4b333..eeeb723 100644 --- a/pages/education.php +++ b/pages/education.php @@ -7,12 +7,12 @@ * Post Types: page */ ?> - + - - - - + + + + @@ -27,14 +27,14 @@ - + - + diff --git a/pages/faq.php b/pages/faq.php index fa063e2..5fbb489 100644 --- a/pages/faq.php +++ b/pages/faq.php @@ -8,7 +8,7 @@ return <<<'ORIBI_SYNC_CONTENT' - + @@ -30,7 +30,7 @@ return <<<'ORIBI_SYNC_CONTENT' - + diff --git a/pages/features.php b/pages/features.php index 702abe9..d4f77fa 100644 --- a/pages/features.php +++ b/pages/features.php @@ -6,51 +6,51 @@ */ return <<<'ORIBI_SYNC_CONTENT' - + - - + + - + - + - + - + - - + + - + - + - + - + - + - + @@ -72,10 +72,10 @@ return <<<'ORIBI_SYNC_CONTENT' - + - + - + ORIBI_SYNC_CONTENT; diff --git a/pages/fitness.php b/pages/fitness.php index 7114f71..4765a09 100644 --- a/pages/fitness.php +++ b/pages/fitness.php @@ -7,12 +7,12 @@ * Post Types: page */ ?> - + - - - + + + @@ -34,7 +34,7 @@ - + diff --git a/pages/healthcare.php b/pages/healthcare.php index 0a5a126..1237e13 100644 --- a/pages/healthcare.php +++ b/pages/healthcare.php @@ -7,12 +7,12 @@ * Post Types: page */ ?> - + - - - + + + @@ -24,16 +24,16 @@ - - - + + + - + diff --git a/pages/home.php b/pages/home.php index cabe221..e895fd0 100644 --- a/pages/home.php +++ b/pages/home.php @@ -36,10 +36,10 @@ return <<<'ORIBI_SYNC_CONTENT' - + - + diff --git a/pages/hospitality.php b/pages/hospitality.php index 51994f6..c7977ea 100644 --- a/pages/hospitality.php +++ b/pages/hospitality.php @@ -7,12 +7,12 @@ * Post Types: page */ ?> - + - - - + + + @@ -34,7 +34,7 @@ - + diff --git a/pages/kiosks.php b/pages/kiosks.php index 0760fe7..2049646 100644 --- a/pages/kiosks.php +++ b/pages/kiosks.php @@ -7,11 +7,11 @@ * Post Types: page */ ?> - + - - - + + + diff --git a/pages/outdoor.php b/pages/outdoor.php index 234ad69..36fcd1e 100644 --- a/pages/outdoor.php +++ b/pages/outdoor.php @@ -10,9 +10,9 @@ - - - + + + diff --git a/pages/resources.php b/pages/resources.php index 950a884..4d2d64a 100644 --- a/pages/resources.php +++ b/pages/resources.php @@ -6,10 +6,10 @@ */ return <<<'ORIBI_SYNC_CONTENT' - + - + @@ -32,8 +32,14 @@ return <<<'ORIBI_SYNC_CONTENT' - - + + + + + + + + diff --git a/pages/retail.php b/pages/retail.php index 6ec520b..7989914 100644 --- a/pages/retail.php +++ b/pages/retail.php @@ -7,19 +7,19 @@ * Post Types: page */ ?> - + - - - - + + + + - + @@ -30,7 +30,7 @@ - + diff --git a/pages/solutions.php b/pages/solutions.php index 7c75b41..3547c07 100644 --- a/pages/solutions.php +++ b/pages/solutions.php @@ -6,23 +6,23 @@ */ return <<<'ORIBI_SYNC_CONTENT' - + - + - + - + - + - + - - - - + + + + @@ -36,4 +36,4 @@ return <<<'ORIBI_SYNC_CONTENT' -ORIBI_SYNC_CONTENT; +ORIBI_SYNC_CONTENT; \ No newline at end of file diff --git a/theme/assets/css/main.css b/theme/assets/css/main.css index d87a104..d99f4d2 100644 --- a/theme/assets/css/main.css +++ b/theme/assets/css/main.css @@ -280,6 +280,7 @@ p:last-child { margin-bottom: 0; } background: var(--header-scrolled-bg); box-shadow: var(--shadow-sm); padding-block: .65rem; + -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); } @@ -364,6 +365,24 @@ p:last-child { margin-bottom: 0; } .nav-menu a:hover::after, .nav-menu .current-menu-item > a::after { width: 100%; } +/* Contact button in nav */ +.nav-contact a.btn { + display: flex !important; + align-items: center !important; + justify-content: center !important; + text-align: center; + padding: .6rem 1.8rem !important; + padding-bottom: .6rem !important; + font-size: .85rem; + border-radius: 50px; + color: #fff !important; + line-height: 1; +} +.nav-contact a.btn::after { display: none !important; } /* Remove underline animation from button */ +.site-header.scrolled .nav-contact a.btn, +.site-header.over-light-hero:not(.scrolled) .nav-contact a.btn, +.site-header.scrolled .nav-menu .nav-contact > a.btn { color: #fff !important; } + /* ── Dropdown sub-menu ─────────────────────────────────────── */ .nav-menu > li { position: relative; @@ -484,6 +503,8 @@ p:last-child { margin-bottom: 0; } gap: 5px; margin-left: auto; padding: 4px; + position: relative; + z-index: 101; } .nav-toggle span { display: block; @@ -504,7 +525,7 @@ p:last-child { margin-bottom: 0; } display: block; position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: var(--color-dark); + background: var(--color-bg); z-index: 99; overflow: hidden; } @@ -517,6 +538,10 @@ p:last-child { margin-bottom: 0; } body.menu-open { overflow: hidden; } + body.menu-open .site-header.scrolled { + -webkit-backdrop-filter: none; + backdrop-filter: none; + } .site-nav.open .nav-menu { flex-direction: column; align-items: flex-start; @@ -525,9 +550,18 @@ p:last-child { margin-bottom: 0; } .site-nav.open .nav-menu > li { width: 100%; } + .nav-toggle.open span { background: var(--color-heading); } .site-nav.open .nav-menu a { - color: #fff; + color: var(--color-heading); font-size: 1.25rem; + text-decoration: none; + } + .site-nav.open .nav-menu a::after { + display: none; + } + .site-nav.open .nav-menu a:hover, + .site-nav.open .nav-menu .current-menu-item > a { + color: var(--color-primary); } /* Mobile sub-menu */ @@ -536,7 +570,7 @@ p:last-child { margin-bottom: 0; } } .site-nav.open .nav-menu > li.menu-item-has-children > a::before { right: .25rem; - border-color: rgba(255,255,255,.7); + border-color: var(--color-text-muted); } .site-nav.open .nav-menu > li.menu-item-has-children.submenu-open > a::before { transform: translateY(-30%) rotate(225deg); @@ -547,25 +581,34 @@ p:last-child { margin-bottom: 0; } opacity: 1; visibility: visible; pointer-events: auto; - background: rgba(255,255,255,.07); + background: var(--color-bg-alt); border: none; border-radius: var(--radius-sm); box-shadow: none; padding: .5rem 0 .5rem .75rem; margin-top: .5rem; display: none; + min-width: 0; + width: 100%; + left: auto; } .site-nav.open .nav-menu .sub-menu::before { display: none; } .site-nav.open .nav-menu > li.menu-item-has-children.submenu-open > .sub-menu { display: block; } + .site-nav.open .nav-menu > li.menu-item-has-children:hover > .sub-menu, + .site-nav.open .nav-menu > li.menu-item-has-children:focus-within > .sub-menu { + transform: none; + } .site-nav.open .nav-menu .sub-menu a { font-size: 1rem; padding: .4rem .75rem; - color: rgba(255,255,255,.8); + color: var(--color-text); + white-space: normal; + display: block; } .site-nav.open .nav-menu .sub-menu a:hover { - color: #fff; + color: var(--color-primary); background: transparent; } } @@ -1643,18 +1686,31 @@ p:last-child { margin-bottom: 0; } padding-inline: 1rem; border-radius: var(--radius-sm); } +@media (prefers-color-scheme: dark) { + .oribi-card.comparison-row:hover { + background: rgba(var(--color-primary-rgb), .1); + } +} .oribi-card.comparison-row .comparison-check { - width: 28px; - height: 28px; - background: var(--color-primary-lt); - color: var(--color-primary); + width: 32px; + height: 32px; + background: var(--color-accent); + color: var(--color-bg); border-radius: 50%; display: flex; align-items: center; justify-content: center; - font-size: .85rem; - font-weight: 700; + font-size: .9rem; + font-weight: 800; flex-shrink: 0; + box-shadow: 0 2px 8px rgba(76, 175, 80, 0.2); +} +@media (prefers-color-scheme: dark) { + .oribi-card.comparison-row .comparison-check { + background: var(--color-accent-dk); + color: var(--color-accent-lt); + box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3); + } } .oribi-card.comparison-row h3 { font-size: 1rem; @@ -1919,8 +1975,6 @@ p:last-child { margin-bottom: 0; } transform: translateY(-4px); } .pricing-card.featured { - border-color: var(--color-primary); - box-shadow: 0 0 0 4px rgba(var(--color-primary-rgb),.1); } .pricing-badge { position: absolute; @@ -1946,6 +2000,7 @@ p:last-child { margin-bottom: 0; } line-height: 1; } .pricing-amount sup { font-size: 1.25rem; vertical-align: super; } +.pricing-amount .pricing-unit { font-size: 1rem; font-weight: 500; color: var(--color-text-muted); margin-left: .2em; } .pricing-per { font-size: .85rem; color: var(--color-text-muted); margin-top: .35rem; } .pricing-features { list-style: none; @@ -3217,7 +3272,8 @@ p:last-child { margin-bottom: 0; } color: #fff; } /* When an image is set, remove the gradient box styling */ -.about-intro-visual.has-img { +.about-intro-visual.has-img, +.about-intro-visual.has-cloud-anim { background: none; box-shadow: none; aspect-ratio: unset; @@ -3229,6 +3285,119 @@ p:last-child { margin-bottom: 0; } .about-intro-visual { max-width: 280px; margin-inline: auto; } } +/* ── Digital Signage Animation ────────────────────────────── */ +.ds-anim-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: 400px; + margin: 0 auto; + padding: 2rem 0; + position: relative; + color: var(--color-primary); +} + +[data-theme="dark"] .ds-anim-container { + color: #fff; +} + +.ds-tv, .ds-cloud { + position: relative; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; +} + +.ds-tv { + font-size: 5rem; + position: relative; +} + +.ds-tv-screen { + position: absolute; + top: 15%; + left: 10%; + width: 80%; + height: 60%; + background: rgba(var(--color-primary-rgb), 0.2); + border-radius: 4px; + animation: ds-screen-pulse 3s infinite alternate; +} + +[data-theme="dark"] .ds-tv-screen { + background: rgba(255, 255, 255, 0.15); +} + +@keyframes ds-screen-pulse { + 0% { opacity: 0.4; } + 100% { opacity: 1; } +} + +.ds-cloud { + font-size: 4.5rem; + animation: ds-float 4s ease-in-out infinite; +} + +@keyframes ds-float { + 0% { transform: translateY(0); } + 50% { transform: translateY(-8px); } + 100% { transform: translateY(0); } +} + +.ds-line { + flex-grow: 1; + height: 2px; + background: rgba(var(--color-primary-rgb), 0.2); + margin: 0 1.5rem; + position: relative; + display: flex; + align-items: center; + overflow: hidden; + border-radius: 2px; +} + +[data-theme="dark"] .ds-line { + background: rgba(255, 255, 255, 0.1); +} + +.ds-packet { + width: 12px; + height: 4px; + background: var(--color-primary); + border-radius: 4px; + position: absolute; + left: -20px; + box-shadow: 0 0 8px var(--color-primary); +} + +[data-theme="dark"] .ds-packet { + background: #fff; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.8); +} + +.ds-packet-1 { animation: ds-travel 2s linear infinite; } +.ds-packet-2 { animation: ds-travel 2s linear infinite 0.6s; } +.ds-packet-3 { animation: ds-travel 2s linear infinite 1.2s; } + +@keyframes ds-travel { + 0% { left: -20px; opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { left: 100%; opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + .ds-tv-screen, .ds-cloud, .ds-packet { + animation: none; + } + .ds-packet-1 { left: 20%; opacity: 1; } + .ds-packet-2 { left: 50%; opacity: 1; } + .ds-packet-3 { left: 80%; opacity: 1; } +} + + /* ── 13. Contact ────────────────────────────────────────────── */ .contact-layout { display: grid; @@ -3417,8 +3586,17 @@ p:last-child { margin-bottom: 0; } } .footer-brand .logo-text { font-size: 1.5rem; color: #fff; } +.footer-logo-section { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; } +.footer-logo-section .custom-logo-link img, +.footer-logo-section .custom-logo { + max-height: 60px !important; + width: auto !important; +} +.footer-company-name { display: flex; align-items: center; } .footer-tagline { font-size: .9rem; color: rgba(255,255,255,.5); margin-top: .75rem; margin-bottom: .5rem; } -.footer-location { font-size: .8rem; color: rgba(255,255,255,.4); } +.footer-location { font-size: .8rem; color: rgba(255,255,255,.8); } +.footer-location a { color: var(--color-primary); font-weight: 600; } +.footer-location a:hover { color: var(--color-primary-lt); } .footer-links { display: contents; } .footer-col h4 { @@ -3439,12 +3617,18 @@ p:last-child { margin-bottom: 0; } .footer-bottom { padding-block: 1.5rem; + display: flex; + align-items: center; + gap: 1rem; } .footer-bottom p { font-size: .8rem; color: rgba(255,255,255,.3); - text-align: center; margin: 0; + flex: 0 1 auto; +} +.footer-bottom .theme-toggle { + margin-left: auto; } /* ── 15a. FAQ Accordion ─────────────────────────────────────── */ @@ -3523,6 +3707,12 @@ p:last-child { margin-bottom: 0; } border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--card-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} +@media (prefers-color-scheme: dark) { + .comparison-table-wrap { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + } } .comparison-table { width: 100%; @@ -3544,6 +3734,12 @@ p:last-child { margin-bottom: 0; } top: 0; z-index: 1; } +@media (prefers-color-scheme: dark) { + .comparison-table thead th { + background: var(--color-bg-alt); + border-bottom-color: var(--color-border); + } +} .comparison-table thead th:first-child { text-align: left; min-width: 220px; @@ -3570,7 +3766,7 @@ p:last-child { margin-bottom: 0; } font-size: .82rem; text-transform: uppercase; letter-spacing: .08em; - color: #fff; + color: var(--color-bg); text-align: left !important; padding: .85rem 1.25rem; border-bottom-color: var(--color-primary) !important; @@ -3578,6 +3774,12 @@ p:last-child { margin-bottom: 0; } .comparison-group-row td:first-child { border-left: 4px solid var(--color-accent); } +@media (prefers-color-scheme: dark) { + .comparison-group-row td { + background: var(--color-primary-dk); + border-bottom-color: var(--color-primary-dk) !important; + } +} .comparison-yes { color: var(--color-accent); font-size: 1.1rem; @@ -3590,6 +3792,11 @@ p:last-child { margin-bottom: 0; } .comparison-table tbody tr:hover td { background: rgba(var(--color-primary-rgb), .03); } +@media (prefers-color-scheme: dark) { + .comparison-table tbody tr:hover td { + background: rgba(var(--color-primary-rgb), .08); + } +} @media (max-width: 640px) { .comparison-table-wrap { margin-inline: calc(var(--container-pad) * -1); border-radius: 0; border-left: none; border-right: none; } } @@ -3713,7 +3920,6 @@ p:last-child { margin-bottom: 0; } [data-theme="dark"] .site-footer { background: #0D0D0D; } -[data-theme="dark"] .site-nav.open { background: #111111; } [data-theme="dark"] .logo-text { color: #F5F5F5; } [data-theme="dark"] .logo-text strong { color: var(--color-primary); } @@ -5846,3 +6052,3718 @@ p:last-child { margin-bottom: 0; } display: block; filter: drop-shadow(0 8px 28px rgba(0, 0, 0, 0.18)); } + +/* ═══════════════════════════════════════════════════════════════ + HOSPITALITY SIGN ANIMATION (.platform-visual.has-hospitality) + Rotating display: Breakfast -> Lunch -> Dinner (9s cycle) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-hospitality { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; + font-size: inherit; +} + +.hosp-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.hosp-tv { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.hosp-tv__body { + width: 100%; + background: #111; + border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; + outline: 2px solid #000; + padding: 4px; + position: relative; + box-shadow: + 0 14px 48px rgba(0,0,0,0.6), + inset 0 1px 0 rgba(255,255,255,0.06); +} + +.hosp-tv__screen { + width: 100%; + aspect-ratio: 16/9; + background: #0a0a0a; + border-radius: 2px; + overflow: hidden; + position: relative; +} + +.hosp-tv__feet { + display: flex; + justify-content: space-between; + width: 60%; + max-width: 300px; +} + +.hosp-tv__foot { + width: 12px; + height: 8px; + background: #111; + border: 1px solid #000; + border-radius: 0 0 4px 4px; +} + +/* POS Sync Indicator */ +.hosp-pos { + position: absolute; + top: 12px; + right: 12px; + display: flex; + align-items: center; + gap: 6px; + background: rgba(0,0,0,0.5); + padding: 4px 8px; + border-radius: 40px; + z-index: 10; + border: 1px solid rgba(255,255,255,0.1); + backdrop-filter: blur(4px); +} + +.hosp-pos__dot { + width: 6px; + height: 6px; + background: #4ade80; + border-radius: 50%; + box-shadow: 0 0 8px #4ade80; + animation: hosp-pos-pulse 2s ease-in-out infinite; +} + +.hosp-pos__txt { + font-size: 9px; + color: #fff; + font-weight: 500; + letter-spacing: 0.3px; +} + +/* Menu Slides */ +.hosp-slides { + position: relative; + width: 100%; + height: 100%; +} + +.hosp-slide { + position: absolute; + inset: 0; + opacity: 0; + animation: hosp-slide-fade 9s infinite; + display: flex; + flex-direction: column; +} + +.hosp-slide--breakfast { animation-delay: 0s; } +.hosp-slide--lunch { animation-delay: 3s; } +.hosp-slide--dinner { animation-delay: 6s; } + +.hosp-menu { + padding: 24px 32px; + height: 100%; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, rgba(20,20,25,1) 0%, rgba(30,30,40,1) 100%); + color: #fff; +} + +.hosp-menu__hd { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.hosp-menu__badge { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + background: var(--color-primary); + padding: 2px 8px; + border-radius: 4px; + font-weight: 700; +} + +.hosp-menu__time { + font-size: 11px; + color: rgba(255,255,255,0.5); + font-family: monospace; +} + +.hosp-menu__title { + font-size: 24px; + font-weight: 800; + margin-bottom: 20px; + color: #fff; +} + +.hosp-menu__items { + display: flex; + flex-direction: column; + gap: 16px; +} + +.hosp-menu__item { + display: flex; + justify-content: space-between; + align-items: flex-start; + border-bottom: 1px solid rgba(255,255,255,0.05); + padding-bottom: 8px; +} + +.hosp-menu__name { + font-size: 15px; + font-weight: 600; + color: #fff; + margin-bottom: 2px; +} + +.hosp-menu__desc { + font-size: 12px; + color: rgba(255,255,255,0.4); +} + +.hosp-menu__price { + font-size: 16px; + font-weight: 700; + color: var(--color-primary); +} + +/* Keyframes */ +@keyframes hosp-pos-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.8); } +} + +@keyframes hosp-slide-fade { + 0%, 2% { opacity: 0; transform: translateY(10px); } + 5%, 30% { opacity: 1; transform: translateY(0); } + 33%, 100% { opacity: 0; transform: translateY(-10px); } +} + +@media (max-width: 640px) { + .hosp-menu { padding: 16px 20px; } + .hosp-menu__title { font-size: 18px; margin-bottom: 12px; } + .hosp-menu__item { gap: 12px; } + .hosp-menu__name { font-size: 13px; } + .hosp-menu__desc { display: none; } + .hosp-menu__price { font-size: 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + RETAIL PROMO ANIMATION (.platform-visual.has-retail) + TV cycling 3 slides: Flash Sale → New In Store → Member Rewards (9s) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-retail { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.retail-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.retail-tv { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.retail-tv__body { + width: 100%; + background: #111; + border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; + outline: 2px solid #000; + padding: 4px; + box-shadow: 0 14px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06); +} + +.retail-tv__screen { + width: 100%; + aspect-ratio: 16/9; + background: #0a0a0a; + border-radius: 2px; + overflow: hidden; + position: relative; +} + +.retail-tv__feet { + display: flex; + justify-content: space-between; + width: 60%; + max-width: 300px; +} + +.retail-tv__foot { + width: 12px; + height: 8px; + background: #111; + border: 1px solid #000; + border-radius: 0 0 4px 4px; +} + +.retail-slides { + position: relative; + width: 100%; + height: 100%; +} + +.retail-slide { + position: absolute; + inset: 0; + opacity: 0; + animation: retail-fade 9s infinite; + display: flex; + align-items: stretch; +} + +.retail-slide--sale { animation-delay: 0s; } +.retail-slide--new { animation-delay: 3s; } +.retail-slide--loyalty { animation-delay: 6s; } + +.retail-promo { + width: 100%; + padding: 20px 28px; + display: flex; + flex-direction: column; + justify-content: center; + gap: 10px; +} + +/* Sale slide */ +.retail-slide--sale .retail-promo { background: linear-gradient(135deg, #1a0a0a 0%, #2a0d0d 100%); } +/* New arrivals slide */ +.retail-slide--new .retail-promo { background: linear-gradient(135deg, #0a0f1a 0%, #0d1a2a 100%); } +/* Loyalty slide */ +.retail-slide--loyalty .retail-promo { background: linear-gradient(135deg, #0a1a0d 0%, #0d2a12 100%); } + +.retail-promo__eyebrow { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1.5px; + color: rgba(255,255,255,0.5); +} + +.retail-promo__headline { + font-size: 28px; + font-weight: 800; + color: #fff; + line-height: 1.1; +} + +.retail-promo__badge { + display: inline-block; + background: var(--color-primary); + color: #fff; + font-size: 13px; + font-weight: 700; + padding: 4px 12px; + border-radius: 4px; + width: fit-content; +} + +.retail-promo__items { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 4px; +} + +.retail-promo__item { + display: flex; + align-items: center; + gap: 8px; + color: rgba(255,255,255,0.8); + font-size: 13px; +} + +.retail-promo__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-primary); + flex-shrink: 0; +} + +.retail-promo__cta { + font-size: 11px; + color: rgba(255,255,255,0.4); + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 4px; +} + +/* New arrivals swatches */ +.retail-promo__grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + margin: 4px 0; +} + +.retail-promo__swatch { + aspect-ratio: 1; + border-radius: 4px; + animation: retail-swatch-pulse 3s ease-in-out infinite; +} +.retail-promo__swatch--a { background: #c8a882; animation-delay: 0s; } +.retail-promo__swatch--b { background: #4a7c59; animation-delay: 0.4s; } +.retail-promo__swatch--c { background: #2c4a8a; animation-delay: 0.8s; } +.retail-promo__swatch--d { background: #8a2c2c; animation-delay: 1.2s; } + +.retail-promo__sub { + font-size: 12px; + color: rgba(255,255,255,0.5); +} + +/* Loyalty points */ +.retail-promo__points { + display: flex; + align-items: baseline; + gap: 10px; +} + +.retail-promo__pts-val { + font-size: 48px; + font-weight: 900; + color: var(--color-accent); + line-height: 1; +} + +.retail-promo__pts-lbl { + font-size: 15px; + color: rgba(255,255,255,0.8); +} + +.retail-promo__bar { + height: 8px; + background: rgba(255,255,255,0.12); + border-radius: 4px; + overflow: hidden; +} + +.retail-promo__bar-fill { + height: 100%; + width: 72%; + background: var(--color-accent); + border-radius: 4px; + animation: retail-bar-grow 2s ease-out infinite alternate; +} + +@keyframes retail-fade { + 0%, 2% { opacity: 0; transform: translateY(10px); } + 5%, 30% { opacity: 1; transform: translateY(0); } + 33%, 100% { opacity: 0; transform: translateY(-10px); } +} + +@keyframes retail-swatch-pulse { + 0%, 100% { opacity: 0.7; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.04); } +} + +@keyframes retail-bar-grow { + from { width: 50%; } + to { width: 82%; } +} + +@media (prefers-reduced-motion: reduce) { + .retail-slide { animation: none; opacity: 0; } + .retail-slide--sale { opacity: 1; } + .retail-promo__swatch { animation: none; } + .retail-promo__bar-fill { animation: none; } +} + +@media (max-width: 640px) { + .retail-promo { padding: 14px 18px; gap: 7px; } + .retail-promo__headline { font-size: 20px; } + .retail-promo__pts-val { font-size: 36px; } +} + +/* ═══════════════════════════════════════════════════════════════ + CORPORATE MEETING ROOM PANEL (.platform-visual.has-corporate) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-corporate { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.corp-stage { + width: 100%; + max-width: 400px; + margin: 0 auto; +} + +.corp-panel { + background: #0f1117; + border: 1px solid #1e2435; + border-radius: 12px; + padding: 24px 28px; + display: flex; + flex-direction: column; + gap: 20px; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); + color: #fff; +} + +.corp-panel__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.corp-panel__room { + font-size: 20px; + font-weight: 700; + color: #fff; +} + +.corp-panel__status { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 10px; + border-radius: 20px; +} + +.corp-panel__status--busy { + background: rgba(239,68,68,0.15); + color: #f87171; + border: 1px solid rgba(239,68,68,0.3); +} + +.corp-panel__dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #ef4444; + animation: corp-dot-pulse 1.8s ease-in-out infinite; +} + +.corp-panel__meeting { + border-left: 3px solid var(--color-primary); + padding-left: 14px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.corp-panel__meeting-name { + font-size: 16px; + font-weight: 600; + color: #fff; +} + +.corp-panel__meeting-time { + font-size: 13px; + color: rgba(255,255,255,0.5); + font-family: monospace; +} + +.corp-panel__organiser { + font-size: 12px; + color: rgba(255,255,255,0.35); +} + +/* Timeline bar */ +.corp-panel__timeline { + display: flex; + flex-direction: column; + gap: 6px; +} + +.corp-panel__tl-track { + height: 8px; + background: rgba(255,255,255,0.08); + border-radius: 4px; + position: relative; + overflow: hidden; +} + +.corp-panel__tl-fill { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 42%; + background: linear-gradient(90deg, var(--color-primary), #ff6b35); + border-radius: 4px; + animation: corp-tl-progress 12s linear infinite; +} + +.corp-panel__tl-now { + position: absolute; + top: -2px; + bottom: -2px; + left: 42%; + width: 2px; + background: #fff; + border-radius: 2px; + animation: corp-tl-now 12s linear infinite; +} + +.corp-panel__tl-labels { + display: flex; + justify-content: space-between; + font-size: 10px; + color: rgba(255,255,255,0.3); + font-family: monospace; +} + +/* Next meeting */ +.corp-panel__next { + background: rgba(255,255,255,0.04); + border-radius: 8px; + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 3px; +} + +.corp-panel__next-lbl { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255,255,255,0.35); +} + +.corp-panel__next-name { + font-size: 14px; + font-weight: 500; + color: rgba(255,255,255,0.8); +} + +.corp-panel__next-time { + font-size: 12px; + color: rgba(255,255,255,0.4); + font-family: monospace; +} + +/* Teams indicator */ +.corp-panel__teams { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: rgba(255,255,255,0.45); +} + +.corp-panel__teams-icon { + width: 18px; + height: 18px; + background: #4a90d9; + border-radius: 4px; + flex-shrink: 0; +} + +@keyframes corp-dot-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.7); } +} + +@keyframes corp-tl-progress { + 0% { width: 35%; } + 100% { width: 55%; } +} + +@keyframes corp-tl-now { + 0% { left: 35%; } + 100% { left: 55%; } +} + +@media (prefers-reduced-motion: reduce) { + .corp-panel__dot { animation: none; } + .corp-panel__tl-fill { animation: none; } + .corp-panel__tl-now { animation: none; } +} + +@media (max-width: 640px) { + .corp-panel { padding: 18px 20px; gap: 16px; } + .corp-panel__room { font-size: 16px; } + .corp-panel__meeting-name { font-size: 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + EDUCATION SCHEDULE BOARD (.platform-visual.has-education) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-education { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.edu-stage { + width: 100%; + max-width: 480px; + margin: 0 auto; +} + +.edu-board { + background: #0d1117; + border: 1px solid #1e2435; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); + color: #fff; +} + +.edu-board__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 20px; + background: linear-gradient(135deg, #1a1f2e 0%, #141928 100%); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.edu-board__title { + font-size: 15px; + font-weight: 700; + color: #fff; +} + +.edu-board__date { + font-size: 11px; + color: rgba(255,255,255,0.4); + font-family: monospace; +} + +.edu-board__rows { + display: flex; + flex-direction: column; +} + +.edu-board__row { + display: grid; + grid-template-columns: 56px 1fr 70px auto; + align-items: center; + gap: 12px; + padding: 10px 20px; + border-bottom: 1px solid rgba(255,255,255,0.04); + transition: background 0.2s; +} + +.edu-row--done { + opacity: 0.35; +} + +.edu-row--now { + background: rgba(76,175,80,0.08); + border-left: 3px solid var(--color-accent); + padding-left: 17px; + animation: edu-row-pulse 3s ease-in-out infinite; +} + +.edu-row__time { + font-size: 12px; + color: rgba(255,255,255,0.45); + font-family: monospace; +} + +.edu-row__subject { + font-size: 13px; + font-weight: 500; + color: rgba(255,255,255,0.85); +} + +.edu-row--now .edu-row__subject { + color: #fff; + font-weight: 600; +} + +.edu-row__room { + font-size: 11px; + color: rgba(255,255,255,0.35); + text-align: right; +} + +.edu-row__badge { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + background: var(--color-accent); + color: #fff; + padding: 2px 7px; + border-radius: 4px; +} + +/* Emergency alert bar */ +.edu-board__alert { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 20px; + background: rgba(245,158,11,0.12); + border-top: 1px solid rgba(245,158,11,0.25); + animation: edu-alert-glow 2.5s ease-in-out infinite; +} + +.edu-alert__icon { + width: 20px; + height: 20px; + border-radius: 50%; + background: rgba(245,158,11,0.3); + border: 1px solid #f59e0b; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 800; + color: #f59e0b; + flex-shrink: 0; + line-height: 20px; + text-align: center; +} + +.edu-alert__txt { + font-size: 12px; + color: #fbbf24; +} + +@keyframes edu-row-pulse { + 0%, 100% { background: rgba(76,175,80,0.08); } + 50% { background: rgba(76,175,80,0.14); } +} + +@keyframes edu-alert-glow { + 0%, 100% { background: rgba(245,158,11,0.10); } + 50% { background: rgba(245,158,11,0.18); } +} + +@media (prefers-reduced-motion: reduce) { + .edu-row--now { animation: none; } + .edu-board__alert { animation: none; } +} + +@media (max-width: 640px) { + .edu-board__row { grid-template-columns: 46px 1fr auto; gap: 8px; padding: 9px 14px; } + .edu-row__room { display: none; } + .edu-row__subject { font-size: 12px; } + .edu-board__header { padding: 12px 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + OUTDOOR MARKETPLACE BOARD (.platform-visual.has-outdoor) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-outdoor { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.outdoor-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.outdoor-board { + background: #1a1a1a; + border: 6px solid #222; + border-radius: 8px; + outline: 2px solid #000; + box-shadow: 0 16px 60px rgba(0,0,0,0.65), inset 0 1px 0 rgba(255,255,255,0.06); + position: relative; +} + +.outdoor-board__screen { + aspect-ratio: 16/7; + overflow: hidden; + background: #090909; + border-radius: 2px; + position: relative; +} + +.outdoor-board__bezel { + height: 10px; + background: linear-gradient(180deg, #1a1a1a 0%, #111 100%); + border-top: 1px solid #0a0a0a; +} + +.outdoor-slides { + position: relative; + width: 100%; + height: 100%; +} + +.outdoor-slide { + position: absolute; + inset: 0; + opacity: 0; + animation: outdoor-fade 8s infinite; +} + +.outdoor-slide--info { animation-delay: 0s; } +.outdoor-slide--directory { animation-delay: 4s; } + +/* Market info slide */ +.outdoor-info { + height: 100%; + padding: 18px 24px; + background: linear-gradient(135deg, #0d1a0d 0%, #0a1a10 100%); + display: flex; + flex-direction: column; + gap: 12px; + color: #fff; +} + +.outdoor-info__header { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.outdoor-info__name { + font-size: 20px; + font-weight: 800; + color: #fff; +} + +.outdoor-info__weather { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} + +.outdoor-info__temp { + font-size: 24px; + font-weight: 700; + color: #fbbf24; +} + +.outdoor-info__cond { + font-size: 10px; + color: rgba(255,255,255,0.5); +} + +.outdoor-info__details { + display: flex; + flex-direction: column; + gap: 7px; +} + +.outdoor-info__row { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: rgba(255,255,255,0.8); +} + +.outdoor-info__icon { + width: 16px; + height: 16px; + border-radius: 3px; + background: rgba(76,175,80,0.3); + border: 1px solid rgba(76,175,80,0.5); + flex-shrink: 0; +} + +/* Directory slide */ +.outdoor-dir { + height: 100%; + padding: 18px 24px; + background: linear-gradient(135deg, #0d0d1a 0%, #0a0a1a 100%); + display: flex; + flex-direction: column; + gap: 12px; + color: #fff; +} + +.outdoor-dir__title { + font-size: 16px; + font-weight: 700; + color: #fff; + border-bottom: 1px solid rgba(255,255,255,0.08); + padding-bottom: 8px; +} + +.outdoor-dir__grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + flex: 1; + align-content: start; +} + +.outdoor-dir__cell { + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 6px; + padding: 8px 10px; + display: flex; + flex-direction: column; + gap: 3px; +} + +.outdoor-dir__zone { + font-size: 10px; + font-weight: 700; + color: var(--color-accent); + font-family: monospace; + text-transform: uppercase; +} + +.outdoor-dir__cat { + font-size: 12px; + color: rgba(255,255,255,0.7); +} + +@keyframes outdoor-fade { + 0%, 2% { opacity: 0; transform: scale(0.98); } + 8%, 45% { opacity: 1; transform: scale(1); } + 50%, 100% { opacity: 0; transform: scale(0.98); } +} + +@media (prefers-reduced-motion: reduce) { + .outdoor-slide { animation: none; opacity: 0; } + .outdoor-slide--info { opacity: 1; } +} + +@media (max-width: 640px) { + .outdoor-info { padding: 12px 16px; } + .outdoor-dir { padding: 12px 16px; } + .outdoor-info__name { font-size: 16px; } +} + +/* ═══════════════════════════════════════════════════════════════ + LIVE DATA / OPS BOARD (.platform-visual.has-live-data) + KPI counters ticked by solutions-animator.js + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-live-data { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.ld-stage { + width: 100%; + max-width: 480px; + margin: 0 auto; +} + +.ld-board { + background: #0a0d14; + border: 1px solid #1a2035; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.6); + color: #fff; +} + +.ld-board__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 18px; + background: rgba(255,255,255,0.03); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ld-board__title { + font-size: 13px; + font-weight: 600; + color: rgba(255,255,255,0.7); + text-transform: uppercase; + letter-spacing: 0.8px; + font-size: 11px; +} + +.ld-board__live { + display: flex; + align-items: center; + gap: 5px; + font-size: 10px; + font-weight: 700; + color: #f87171; + text-transform: uppercase; + letter-spacing: 1px; +} + +.ld-board__live-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #ef4444; + animation: ld-live-pulse 1.4s ease-in-out infinite; +} + +.ld-kpis { + display: grid; + grid-template-columns: repeat(4, 1fr); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ld-kpi { + padding: 14px 12px; + border-right: 1px solid rgba(255,255,255,0.06); + display: flex; + flex-direction: column; + gap: 4px; +} + +.ld-kpi:last-child { border-right: none; } + +.ld-kpi__label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.6px; + color: rgba(255,255,255,0.35); +} + +.ld-kpi__value { + font-size: 18px; + font-weight: 700; + color: #fff; + font-family: monospace; + transition: color 0.2s; +} + +.ld-kpi__trend::after { + display: block; + font-size: 10px; + margin-top: 2px; +} + +.ld-kpi--up .ld-kpi__value { color: #4ade80; } +.ld-kpi--down .ld-kpi__value { color: #f87171; } +.ld-kpi--up .ld-kpi__trend::after { content: '▲'; color: #4ade80; } +.ld-kpi--down .ld-kpi__trend::after { content: '▼'; color: #f87171; } +.ld-kpi--neutral .ld-kpi__trend::after { content: '—'; color: rgba(255,255,255,0.3); } + +/* Sparkline chart area */ +.ld-chart { + padding: 12px 18px 8px; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ld-sparkline { + width: 100%; + height: 60px; + display: block; +} + +.ld-chart__label { + font-size: 10px; + color: rgba(255,255,255,0.25); + margin-top: 4px; +} + +/* Service status row */ +.ld-status { + display: flex; + align-items: center; + gap: 16px; + padding: 10px 18px; + flex-wrap: wrap; +} + +.ld-svc { + display: flex; + align-items: center; + gap: 5px; + font-size: 11px; + color: rgba(255,255,255,0.55); +} + +.ld-svc__dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.ld-svc--ok .ld-svc__dot { background: #4ade80; box-shadow: 0 0 6px #4ade80; } +.ld-svc--warn .ld-svc__dot { background: #fbbf24; box-shadow: 0 0 6px #fbbf24; animation: ld-warn-pulse 1.5s ease-in-out infinite; } +.ld-svc--err .ld-svc__dot { background: #f87171; box-shadow: 0 0 6px #f87171; animation: ld-warn-pulse 0.8s ease-in-out infinite; } + +@keyframes ld-live-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +@keyframes ld-warn-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +@media (max-width: 640px) { + .ld-kpis { grid-template-columns: repeat(2, 1fr); } + .ld-kpi__value { font-size: 15px; } + .ld-status { gap: 10px; padding: 8px 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + HEALTHCARE QUEUE BOARD (.platform-visual.has-healthcare) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-healthcare { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.hc-stage { + width: 100%; + max-width: 460px; + margin: 0 auto; +} + +.hc-board { + background: #0a1419; + border: 1px solid #162030; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.55); + color: #fff; +} + +.hc-board__header { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 18px; + background: #0d1c2a; + border-bottom: 1px solid rgba(255,255,255,0.07); +} + +.hc-board__logo { + width: 28px; + height: 28px; + border-radius: 6px; + background: linear-gradient(135deg, #0ea5e9, #38bdf8); + flex-shrink: 0; +} + +.hc-board__title { + font-size: 15px; + font-weight: 600; + color: #fff; +} + +/* Now serving hero */ +.hc-now { + padding: 20px 18px 16px; + text-align: center; + border-bottom: 1px solid rgba(255,255,255,0.06); + background: linear-gradient(135deg, rgba(14,165,233,0.06) 0%, rgba(56,189,248,0.03) 100%); +} + +.hc-now__lbl { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1.5px; + color: rgba(255,255,255,0.4); + margin-bottom: 6px; +} + +.hc-now__number { + font-size: 40px; + font-weight: 900; + color: #38bdf8; + font-family: monospace; + letter-spacing: 4px; + animation: hc-number-flash 3.5s ease-in-out infinite; +} + +.hc-now__counter { + font-size: 12px; + color: rgba(255,255,255,0.5); + margin-top: 4px; +} + +/* Counter rows */ +.hc-counters { + display: flex; + flex-direction: column; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.hc-counter { + display: grid; + grid-template-columns: 32px 1fr auto auto; + align-items: center; + gap: 10px; + padding: 10px 18px; + border-bottom: 1px solid rgba(255,255,255,0.04); + font-size: 12px; +} + +.hc-counter:last-child { border-bottom: none; } + +.hc-counter--active { } +.hc-counter--closed { opacity: 0.3; } + +.hc-counter__id { + font-size: 11px; + font-weight: 700; + color: rgba(255,255,255,0.4); + font-family: monospace; +} + +.hc-counter__doctor { + color: rgba(255,255,255,0.75); + font-size: 12px; +} + +.hc-counter__ticket { + font-size: 13px; + font-weight: 700; + color: #38bdf8; + font-family: monospace; + min-width: 36px; + text-align: right; +} + +.hc-counter__wait { + font-size: 11px; + color: rgba(255,255,255,0.35); + min-width: 50px; + text-align: right; +} + +.hc-board__footer { + padding: 10px 18px; + font-size: 11px; + color: rgba(255,255,255,0.3); + text-align: center; + background: rgba(255,255,255,0.02); +} + +@keyframes hc-number-flash { + 0%, 100% { opacity: 1; color: #38bdf8; } + 10% { opacity: 0.5; color: #7dd3fc; } + 20% { opacity: 1; color: #38bdf8; } +} + +@media (prefers-reduced-motion: reduce) { + .hc-now__number { animation: none; } +} + +@media (max-width: 640px) { + .hc-now__number { font-size: 28px; } + .hc-counter { grid-template-columns: 28px 1fr auto; gap: 8px; } + .hc-counter__wait { display: none; } +} + +/* ═══════════════════════════════════════════════════════════════ + TRANSIT DEPARTURE BOARD (.platform-visual.has-transit) + Split-flap flip characters; rows cycled by solutions-animator.js + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-transit { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.transit-stage { + width: 100%; + max-width: 500px; + margin: 0 auto; +} + +.transit-board { + background: #111; + border: 1px solid #1a1a1a; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.7); + font-family: monospace; +} + +.transit-board__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 16px; + background: #1a1a1a; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.transit-board__title { + font-size: 14px; + font-weight: 700; + color: #f5f5a0; + letter-spacing: 1px; + text-transform: uppercase; +} + +.transit-board__clock { + font-size: 18px; + font-weight: 700; + color: #f5f5a0; + letter-spacing: 2px; +} + +.transit-board__cols { + display: grid; + grid-template-columns: 56px 1fr 64px 80px; + gap: 0; + padding: 6px 16px; + background: #161616; + border-bottom: 1px solid rgba(255,255,255,0.08); +} + +.transit-col-hd { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255,255,255,0.3); +} + +.transit-rows { + display: flex; + flex-direction: column; +} + +.transit-row { + display: grid; + grid-template-columns: 56px 1fr 64px 80px; + align-items: center; + padding: 7px 16px; + border-bottom: 1px solid rgba(255,255,255,0.04); + gap: 0; + transition: background 0.3s; +} + +.transit-row:last-child { border-bottom: none; } + +.transit-cell { + font-size: 13px; + color: #f5f5a0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.transit-cell--time { + font-weight: 700; + letter-spacing: 1px; +} + +.transit-cell--plat { + text-align: center; + color: rgba(245,245,160,0.7); +} + +.transit-cell--status { } + +.transit-status--on-time { color: #4ade80; } +.transit-status--delayed { color: #f87171; animation: transit-delayed-blink 2s step-end infinite; } +.transit-status--cancelled { color: #f87171; text-decoration: line-through; } + +/* Split-flap character flip animation */ +.transit-flap { + display: inline-block; + animation: transit-flap-flip 0s; +} + +.transit-flap.is-flipping { + animation: transit-flap-flip 0.25s ease-in-out forwards; +} + +@keyframes transit-flap-flip { + 0% { transform: scaleY(1); } + 40% { transform: scaleY(0); opacity: 0.5; } + 60% { transform: scaleY(0); opacity: 0.5; } + 100% { transform: scaleY(1); } +} + +@keyframes transit-delayed-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +@media (prefers-reduced-motion: reduce) { + .transit-flap.is-flipping { animation: none; } + .transit-status--delayed { animation: none; } +} + +@media (max-width: 640px) { + .transit-board__cols, + .transit-row { grid-template-columns: 50px 1fr 52px; } + .transit-cell--plat { display: none; } + .transit-col-hd:nth-child(3) { display: none; } + .transit-cell { font-size: 11px; } +} + +/* ═══════════════════════════════════════════════════════════════ + FITNESS CLASS SCHEDULE (.platform-visual.has-fitness) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-fitness { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.fit-stage { + width: 100%; + max-width: 460px; + margin: 0 auto; +} + +.fit-board { + background: #0d0d0d; + border: 1px solid #1a1a1a; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.6); + color: #fff; +} + +.fit-board__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 18px; + background: #111; + border-bottom: 1px solid rgba(255,255,255,0.07); +} + +.fit-board__logo { + width: 26px; + height: 26px; + border-radius: 6px; + background: linear-gradient(135deg, var(--color-primary), #ff6b35); + flex-shrink: 0; +} + +.fit-board__title { + font-size: 14px; + font-weight: 700; + color: #fff; +} + +/* Live now hero */ +.fit-now { + padding: 18px 18px 14px; + background: linear-gradient(135deg, rgba(216,51,2,0.1) 0%, rgba(255,107,53,0.06) 100%); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.fit-now__badge { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--color-primary); + color: #fff; + font-size: 10px; + font-weight: 800; + letter-spacing: 1px; + padding: 3px 10px; + border-radius: 4px; + margin-bottom: 8px; + animation: fit-badge-pulse 2s ease-in-out infinite; +} + +.fit-now__name { + font-size: 22px; + font-weight: 800; + color: #fff; + margin-bottom: 4px; +} + +.fit-now__detail { + font-size: 12px; + color: rgba(255,255,255,0.45); + margin-bottom: 12px; +} + +/* Capacity bar */ +.fit-now__capacity { + display: flex; + align-items: center; + gap: 10px; +} + +.fit-cap__lbl { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(255,255,255,0.35); + white-space: nowrap; +} + +.fit-cap__track { + flex: 1; + height: 8px; + background: rgba(255,255,255,0.1); + border-radius: 4px; + overflow: hidden; +} + +.fit-cap__fill { + height: 100%; + width: 80%; + background: linear-gradient(90deg, var(--color-primary), #ff6b35); + border-radius: 4px; + animation: fit-cap-breathe 3s ease-in-out infinite alternate; +} + +.fit-cap__val { + font-size: 11px; + font-weight: 600; + color: rgba(255,255,255,0.6); + white-space: nowrap; + font-family: monospace; +} + +/* Upcoming classes list */ +.fit-upcoming { + display: flex; + flex-direction: column; +} + +.fit-class { + display: grid; + grid-template-columns: 48px 1fr 1fr auto; + align-items: center; + gap: 10px; + padding: 10px 18px; + border-bottom: 1px solid rgba(255,255,255,0.04); + font-size: 12px; +} + +.fit-class:last-child { border-bottom: none; } + +.fit-class__time { + font-size: 12px; + font-weight: 600; + color: rgba(255,255,255,0.5); + font-family: monospace; +} + +.fit-class__name { + font-size: 13px; + font-weight: 500; + color: rgba(255,255,255,0.85); +} + +.fit-class__coach { + font-size: 11px; + color: rgba(255,255,255,0.35); +} + +.fit-class__spaces { + font-size: 11px; + color: rgba(255,255,255,0.4); + text-align: right; + white-space: nowrap; +} + +.fit-class--filling .fit-class__spaces { + color: #fbbf24; + font-weight: 600; +} + +@keyframes fit-badge-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(216,51,2,0.5); } + 50% { box-shadow: 0 0 0 6px rgba(216,51,2,0); } +} + +@keyframes fit-cap-breathe { + from { width: 76%; } + to { width: 83%; } +} + +@media (prefers-reduced-motion: reduce) { + .fit-now__badge { animation: none; } + .fit-cap__fill { animation: none; } +} + +@media (max-width: 640px) { + .fit-now { padding: 14px 14px 12px; } + .fit-now__name { font-size: 18px; } + .fit-class { grid-template-columns: 42px 1fr auto; gap: 8px; padding: 9px 14px; } + .fit-class__coach { display: none; } +} + +/* ═══════════════════════════════════════════════════════════ + LOBBY & RECEPTION ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-lobby { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.lobby-stage { width: 100%; max-width: 480px; aspect-ratio: 16 / 10; position: relative; margin: 0 auto; } +.lobby-screen { + width: 100%; height: 100%; + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); + border-radius: 10px; overflow: hidden; position: relative; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); +} +.lobby-hdr { + display: flex; justify-content: space-between; align-items: center; + padding: 10px 16px; background: rgba(255,255,255,0.08); + font-size: 11px; color: rgba(255,255,255,0.7); +} +.lobby-hdr__logo { font-weight: 700; color: #e2c076; letter-spacing: 0.5px; } +.lobby-slides { position: relative; height: calc(100% - 70px); overflow: hidden; } +.lobby-slide { + position: absolute; inset: 0; display: flex; flex-direction: column; + align-items: center; justify-content: center; padding: 16px; + opacity: 0; animation: lobby-cycle 12s infinite; +} +.lobby-slide--welcome { animation-delay: 0s; } +.lobby-slide--amenities { animation-delay: 4s; } +.lobby-slide--events { animation-delay: 8s; } + +.lobby-welcome__msg { font-size: 28px; font-weight: 700; color: #fff; } +.lobby-welcome__sub { font-size: 13px; color: rgba(255,255,255,0.6); margin-top: 6px; } + +.lobby-amen__title { font-size: 14px; font-weight: 600; color: #e2c076; margin-bottom: 12px; } +.lobby-amen__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } +.lobby-amen__item { + background: rgba(255,255,255,0.08); border-radius: 8px; padding: 10px; + font-size: 12px; color: rgba(255,255,255,0.8); text-align: center; +} + +.lobby-events__title { font-size: 14px; font-weight: 600; color: #e2c076; margin-bottom: 10px; text-align: center; } +.lobby-events__list { width: 100%; } +.lobby-events__item { display: flex; gap: 10px; padding: 6px 0; font-size: 12px; color: rgba(255,255,255,0.8); } +.lobby-events__time { color: #e2c076; font-weight: 600; min-width: 40px; } + +.lobby-wifi { + position: absolute; bottom: 0; left: 0; right: 0; + padding: 8px 16px; background: rgba(255,255,255,0.05); + font-size: 10px; color: rgba(255,255,255,0.5); text-align: center; +} + +@keyframes lobby-cycle { + 0%, 5% { opacity: 0; transform: translateY(8px); } + 8%, 28% { opacity: 1; transform: translateY(0); } + 33%, 100%{ opacity: 0; transform: translateY(-8px); } +} + +@media (prefers-reduced-motion: reduce) { + .lobby-slide { animation: none; } + .lobby-slide--welcome { opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .lobby-welcome__msg { font-size: 22px; } + .lobby-amen__grid { gap: 6px; } +} + +/* ═══════════════════════════════════════════════════════════ + CONFERENCE ROOM PANEL ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-conference { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.conf-stage { width: 100%; max-width: 400px; margin: 0 auto; } +.conf-panel { + background: #0f1117; border: 1px solid #1e2435; border-radius: 12px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); padding: 20px; +} +.conf-panel__room { font-size: 20px; font-weight: 700; color: #fff; margin-bottom: 8px; } +.conf-panel__status { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; } +.conf-status__dot { + width: 10px; height: 10px; border-radius: 50%; + background: var(--color-primary); animation: conf-dot-pulse 2s ease-in-out infinite; +} +.conf-panel__current { margin-bottom: 16px; } +.conf-current__label { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; color: rgba(255,255,255,0.4); } +.conf-current__title { font-size: 16px; font-weight: 600; color: #fff; margin-top: 4px; } +.conf-current__meta { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 4px; } +.conf-panel__timeline { margin-bottom: 16px; } +.conf-tl__bar { height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; } +.conf-tl__fill { + height: 100%; width: 0%; background: var(--color-primary); border-radius: 2px; + animation: conf-fill 8s linear infinite; +} +.conf-panel__next { + font-size: 12px; color: rgba(255,255,255,0.5); + padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.08); +} +.conf-next__label { color: rgba(255,255,255,0.4); } +.conf-next__title { color: rgba(255,255,255,0.7); } +.conf-next__time { float: right; color: rgba(255,255,255,0.4); } + +@keyframes conf-dot-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(216,51,2,0.6); } + 50% { box-shadow: 0 0 0 6px rgba(216,51,2,0); } +} +@keyframes conf-fill { + from { width: 35%; } + to { width: 90%; } +} + +@media (prefers-reduced-motion: reduce) { + .conf-status__dot { animation: none; } + .conf-tl__fill { animation: none; width: 60%; } +} + +@media (max-width: 640px) { + .conf-panel { padding: 16px; } + .conf-panel__room { font-size: 17px; } +} + +/* ═══════════════════════════════════════════════════════════ + DAY-PART RETAIL PROMO ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-daypart { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.daypart-stage { width: 100%; max-width: 520px; aspect-ratio: 16 / 10; margin: 0 auto; } +.daypart-screen { + width: 100%; height: 100%; + background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); + border-radius: 10px; overflow: hidden; position: relative; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); +} +.daypart-hdr { + display: flex; justify-content: space-between; align-items: center; + padding: 10px 16px; background: rgba(255,255,255,0.06); +} +.daypart-hdr__clock { font-size: 14px; font-weight: 700; color: #fff; font-variant-numeric: tabular-nums; } +.daypart-hdr__badge { + font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; + padding: 3px 10px; border-radius: 20px; background: rgba(216,51,2,0.2); color: #ff9068; +} +.daypart-slides { position: relative; height: calc(100% - 42px); overflow: hidden; } +.daypart-slide { + position: absolute; inset: 0; display: flex; flex-direction: column; + align-items: center; justify-content: center; padding: 20px; text-align: center; + opacity: 0; animation: daypart-cycle 12s infinite; +} +.daypart-slide--morning { animation-delay: 0s; } +.daypart-slide--afternoon { animation-delay: 4s; } +.daypart-slide--evening { animation-delay: 8s; } +.daypart-promo__tag { + font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; + color: #ff9068; margin-bottom: 8px; +} +.daypart-promo__title { font-size: 24px; font-weight: 700; color: #fff; margin-bottom: 6px; } +.daypart-promo__sub { font-size: 13px; color: rgba(255,255,255,0.6); } + +@keyframes daypart-cycle { + 0%, 5% { opacity: 0; transform: scale(0.96); } + 8%, 28% { opacity: 1; transform: scale(1); } + 33%, 100%{ opacity: 0; transform: scale(1.02); } +} + +@media (prefers-reduced-motion: reduce) { + .daypart-slide { animation: none; } + .daypart-slide--morning { opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .daypart-promo__title { font-size: 20px; } +} + +/* ═══════════════════════════════════════════════════════════ + IN-STORE WAYFINDING KIOSK ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-wayfind { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.wayfind-stage { width: 100%; max-width: 400px; margin: 0 auto; } +.wayfind-kiosk { + background: #0f1117; border: 1px solid #1e2435; border-radius: 12px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); padding: 16px; +} +.wayfind-kiosk__search { + display: flex; align-items: center; gap: 8px; + background: rgba(255,255,255,0.06); border-radius: 8px; + padding: 10px 14px; margin-bottom: 14px; +} +.wayfind-search__icon { font-size: 14px; } +.wayfind-search__text { font-size: 13px; color: rgba(255,255,255,0.35); } +.wayfind-kiosk__map { + display: grid; grid-template-columns: 1fr 1fr; gap: 6px; + position: relative; margin-bottom: 14px; +} +.wayfind-zone { + background: rgba(255,255,255,0.05); border-radius: 6px; + padding: 16px 8px; text-align: center; font-size: 11px; + font-weight: 600; color: rgba(255,255,255,0.6); + border: 1px solid rgba(255,255,255,0.06); +} +.wayfind-zone--a { animation: wayfind-glow 6s ease-in-out infinite; } +.wayfind-zone--b { animation: wayfind-glow 6s ease-in-out infinite 1.5s; } +.wayfind-zone--c { animation: wayfind-glow 6s ease-in-out infinite 3s; } +.wayfind-zone--d { animation: wayfind-glow 6s ease-in-out infinite 4.5s; } +.wayfind-pin { + position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); + display: flex; align-items: center; gap: 4px; +} +.wayfind-pin__dot { + width: 10px; height: 10px; border-radius: 50%; background: var(--color-primary); + animation: wayfind-pin-pulse 2s infinite; +} +.wayfind-pin__label { font-size: 9px; color: rgba(255,255,255,0.5); white-space: nowrap; } +.wayfind-kiosk__dir { display: flex; flex-direction: column; gap: 6px; } +.wayfind-dir__item { + font-size: 12px; color: rgba(255,255,255,0.6); padding: 8px 10px; + background: rgba(255,255,255,0.04); border-radius: 6px; +} +.wayfind-dir__arrow { color: var(--color-primary); font-weight: 700; margin-right: 6px; } + +@keyframes wayfind-glow { + 0%, 100% { border-color: rgba(255,255,255,0.06); background: rgba(255,255,255,0.05); } + 25% { border-color: rgba(216,51,2,0.4); background: rgba(216,51,2,0.08); } +} +@keyframes wayfind-pin-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(216,51,2,0.6); } + 50% { box-shadow: 0 0 0 8px rgba(216,51,2,0); } +} + +@media (prefers-reduced-motion: reduce) { + .wayfind-zone { animation: none; } + .wayfind-pin__dot { animation: none; } +} + +@media (max-width: 640px) { + .wayfind-kiosk { padding: 12px; } +} + +/* ═══════════════════════════════════════════════════════════ + STOREFRONT WINDOW DISPLAY ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-storefront { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.store-stage { width: 100%; max-width: 520px; aspect-ratio: 16 / 10; position: relative; margin: 0 auto; } +.store-window { + width: 100%; height: 100%; position: relative; + border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); +} +.store-window__sun { + position: absolute; top: -20px; right: -20px; width: 80px; height: 80px; + border-radius: 50%; background: radial-gradient(circle, rgba(255,200,50,0.4), transparent 70%); + animation: store-sun-pulse 4s ease-in-out infinite; z-index: 2; +} +.store-window__screen { + width: 100%; height: 100%; + background: linear-gradient(135deg, #1a1a2e, #2d1b4e); + position: relative; overflow: hidden; +} +.store-slides { position: relative; width: 100%; height: 100%; } +.store-slide { + position: absolute; inset: 0; display: flex; flex-direction: column; + align-items: center; justify-content: center; padding: 20px; text-align: center; + opacity: 0; animation: store-cycle 12s infinite; +} +.store-slide--new { animation-delay: 0s; } +.store-slide--sale { animation-delay: 4s; } +.store-slide--hours { animation-delay: 8s; } +.store-slide__tag { + font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; + color: #e2c076; margin-bottom: 8px; +} +.store-slide__title { font-size: 26px; font-weight: 700; color: #fff; } +.store-slide__cta { font-size: 13px; color: var(--color-primary); font-weight: 600; margin-top: 10px; } +.store-slide__sub { font-size: 13px; color: rgba(255,255,255,0.5); margin-top: 4px; } +.store-window__glare { + position: absolute; inset: 0; + background: linear-gradient(120deg, rgba(255,255,255,0.08) 0%, transparent 50%); + pointer-events: none; z-index: 1; +} + +@keyframes store-cycle { + 0%, 5% { opacity: 0; } + 8%, 28% { opacity: 1; } + 33%, 100%{ opacity: 0; } +} +@keyframes store-sun-pulse { + 0%, 100% { opacity: 0.6; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.15); } +} + +@media (prefers-reduced-motion: reduce) { + .store-slide { animation: none; } + .store-slide--new { opacity: 1; } + .store-window__sun { animation: none; } +} + +@media (max-width: 640px) { + .store-slide__title { font-size: 20px; } +} + +/* ═══════════════════════════════════════════════════════════ + CORPORATE ANNOUNCEMENT BOARD ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-announcement { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.announce-stage { width: 100%; max-width: 460px; margin: 0 auto; } +.announce-board { + background: #0d1117; border: 1px solid #1e2435; border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); +} +.announce-hdr { + display: flex; justify-content: space-between; align-items: center; + padding: 12px 16px; background: rgba(255,255,255,0.04); +} +.announce-hdr__title { font-size: 14px; font-weight: 700; color: #fff; } +.announce-hdr__live { font-size: 11px; color: #22c55e; animation: announce-live-blink 2s infinite; } +.announce-cards { padding: 12px; display: flex; flex-direction: column; gap: 8px; } +.announce-card { + border-radius: 8px; padding: 12px; opacity: 0; + animation: announce-card-in 12s infinite; +} +.announce-card--urgent { + background: rgba(220,38,38,0.1); border-left: 3px solid #dc2626; + animation-delay: 0s; +} +.announce-card--celebration { + background: rgba(34,197,94,0.1); border-left: 3px solid #22c55e; + animation-delay: 4s; +} +.announce-card--hr { + background: rgba(59,130,246,0.1); border-left: 3px solid #3b82f6; + animation-delay: 8s; +} +.announce-card__badge { + font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; + color: rgba(255,255,255,0.5); margin-bottom: 4px; +} +.announce-card--urgent .announce-card__badge { color: #dc2626; } +.announce-card--celebration .announce-card__badge { color: #22c55e; } +.announce-card--hr .announce-card__badge { color: #3b82f6; } +.announce-card__title { font-size: 14px; font-weight: 600; color: #fff; margin-bottom: 4px; } +.announce-card__body { font-size: 12px; color: rgba(255,255,255,0.5); line-height: 1.4; } + +@keyframes announce-card-in { + 0%, 5% { opacity: 0; transform: translateX(-8px); } + 8%, 28% { opacity: 1; transform: translateX(0); } + 33%, 100%{ opacity: 0; transform: translateX(8px); } +} +@keyframes announce-live-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +@media (prefers-reduced-motion: reduce) { + .announce-card { animation: none; opacity: 1; transform: none; } + .announce-hdr__live { animation: none; } +} + +@media (max-width: 640px) { + .announce-card { padding: 10px; } + .announce-card__title { font-size: 13px; } +} + +/* ═══════════════════════════════════════════════════════════ + CAMPUS WAYFINDING KIOSK ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-campus-wayfind { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.campus-stage { width: 100%; max-width: 400px; margin: 0 auto; } +.campus-kiosk { + background: #0f1117; border: 1px solid #1e2435; border-radius: 12px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); padding: 16px; +} +.campus-kiosk__hdr { font-size: 16px; font-weight: 700; color: #fff; margin-bottom: 12px; text-align: center; } +.campus-kiosk__search { + display: flex; align-items: center; gap: 8px; + background: rgba(255,255,255,0.06); border-radius: 8px; + padding: 10px 14px; margin-bottom: 14px; +} +.campus-search__icon { font-size: 14px; } +.campus-search__text { font-size: 13px; color: rgba(255,255,255,0.35); } +.campus-kiosk__map { + display: grid; grid-template-columns: 1fr 1fr; gap: 6px; + position: relative; margin-bottom: 12px; +} +.campus-bldg { + background: rgba(255,255,255,0.05); border-radius: 6px; + padding: 18px 8px; text-align: center; font-size: 11px; + font-weight: 600; color: rgba(255,255,255,0.6); + border: 1px solid rgba(255,255,255,0.06); +} +.campus-bldg--lib { animation: campus-highlight 8s ease-in-out infinite; } +.campus-bldg--sci { animation: campus-highlight 8s ease-in-out infinite 2s; } +.campus-bldg--arts { animation: campus-highlight 8s ease-in-out infinite 4s; } +.campus-bldg--sports { animation: campus-highlight 8s ease-in-out infinite 6s; } +.campus-pin { position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%); } +.campus-pin__dot { + display: block; width: 10px; height: 10px; border-radius: 50%; background: #3b82f6; + animation: campus-pin-pulse 2s infinite; +} +.campus-kiosk__legend { font-size: 10px; color: rgba(255,255,255,0.4); display: flex; align-items: center; gap: 6px; } +.campus-legend__dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; } +.campus-legend__dot--you { background: #3b82f6; } + +@keyframes campus-highlight { + 0%, 100% { border-color: rgba(255,255,255,0.06); } + 12.5%, 25% { border-color: rgba(59,130,246,0.5); background: rgba(59,130,246,0.08); } +} +@keyframes campus-pin-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(59,130,246,0.6); } + 50% { box-shadow: 0 0 0 8px rgba(59,130,246,0); } +} + +@media (prefers-reduced-motion: reduce) { + .campus-bldg { animation: none; } + .campus-pin__dot { animation: none; } +} + +@media (max-width: 640px) { + .campus-kiosk { padding: 12px; } +} + +/* ═══════════════════════════════════════════════════════════ + EMERGENCY OVERRIDE ALERT ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-emergency { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.emerg-stage { width: 100%; max-width: 480px; aspect-ratio: 16 / 10; margin: 0 auto; } +.emerg-screen { + width: 100%; height: 100%; position: relative; + border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); +} +.emerg-screen__normal { + position: absolute; inset: 0; background: #0d1117; padding: 20px; + animation: emerg-normal 8s infinite; +} +.emerg-normal__hdr { font-size: 14px; font-weight: 700; color: #fff; margin-bottom: 12px; } +.emerg-normal__item { font-size: 12px; color: rgba(255,255,255,0.5); padding: 6px 0; } +.emerg-screen__alert { + position: absolute; inset: 0; + background: #7f1d1d; display: flex; flex-direction: column; + align-items: center; justify-content: center; padding: 20px; text-align: center; + opacity: 0; animation: emerg-alert 8s infinite; +} +.emerg-alert__icon { font-size: 32px; animation: emerg-icon-flash 1s infinite; } +.emerg-alert__title { + font-size: 18px; font-weight: 800; color: #fff; letter-spacing: 2px; margin-top: 8px; +} +.emerg-alert__msg { font-size: 12px; color: rgba(255,255,255,0.8); margin-top: 8px; line-height: 1.4; } +.emerg-alert__pulse { + position: absolute; inset: 0; + border: 3px solid #dc2626; + animation: emerg-border-pulse 1.5s infinite; border-radius: 10px; +} + +@keyframes emerg-normal { + 0%, 45% { opacity: 1; } + 50%, 95% { opacity: 0; } + 100% { opacity: 1; } +} +@keyframes emerg-alert { + 0%, 45% { opacity: 0; } + 50%, 95% { opacity: 1; } + 100% { opacity: 0; } +} +@keyframes emerg-icon-flash { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.2; } +} +@keyframes emerg-border-pulse { + 0% { opacity: 1; } + 100% { opacity: 0; transform: scale(1.03); } +} + +@media (prefers-reduced-motion: reduce) { + .emerg-screen__normal { animation: none; opacity: 0; } + .emerg-screen__alert { animation: none; opacity: 1; } + .emerg-alert__icon { animation: none; } + .emerg-alert__pulse { animation: none; opacity: 0.5; } +} + +@media (max-width: 640px) { + .emerg-alert__title { font-size: 15px; } +} + +/* ═══════════════════════════════════════════════════════════ + OUTDOOR ENCLOSURE ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-enclosure { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.encl-stage { width: 100%; max-width: 400px; position: relative; padding-top: 20px; margin: 0 auto; } +.encl-rain { position: absolute; inset: 0; overflow: hidden; pointer-events: none; } +.encl-rain__drop { + position: absolute; width: 2px; background: rgba(100,160,255,0.35); + border-radius: 1px; animation: encl-drop 1.2s linear infinite; +} +.encl-rain__drop:nth-child(1) { left: 8%; height: 16px; animation-delay: 0s; } +.encl-rain__drop:nth-child(2) { left: 18%; height: 12px; animation-delay: 0.15s; } +.encl-rain__drop:nth-child(3) { left: 28%; height: 18px; animation-delay: 0.3s; } +.encl-rain__drop:nth-child(4) { left: 38%; height: 14px; animation-delay: 0.45s; } +.encl-rain__drop:nth-child(5) { left: 48%; height: 16px; animation-delay: 0.6s; } +.encl-rain__drop:nth-child(6) { left: 58%; height: 12px; animation-delay: 0.1s; } +.encl-rain__drop:nth-child(7) { left: 68%; height: 18px; animation-delay: 0.25s; } +.encl-rain__drop:nth-child(8) { left: 78%; height: 14px; animation-delay: 0.4s; } +.encl-rain__drop:nth-child(9) { left: 88%; height: 16px; animation-delay: 0.55s; } +.encl-rain__drop:nth-child(10) { left: 13%; height: 12px; animation-delay: 0.7s; } +.encl-rain__drop:nth-child(11) { left: 43%; height: 18px; animation-delay: 0.85s; } +.encl-rain__drop:nth-child(12) { left: 73%; height: 14px; animation-delay: 1s; } + +.encl-unit { position: relative; z-index: 1; } +.encl-unit__body { + background: #1f2937; border-radius: 10px; border: 3px solid #374151; + padding: 4px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); +} +.encl-unit__screen { + background: linear-gradient(135deg, #1a1a2e, #16213e); + border-radius: 6px; padding: 28px 16px; text-align: center; +} +.encl-screen__content { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.7); } +.encl-unit__badge { + position: absolute; top: -8px; right: 12px; + background: #15803d; color: #fff; font-size: 10px; font-weight: 800; + padding: 4px 10px; border-radius: 4px; letter-spacing: 1px; +} +.encl-unit__temp { + text-align: center; margin-top: 10px; + font-size: 11px; color: rgba(255,255,255,0.5); +} + +@keyframes encl-drop { + from { top: -20px; opacity: 1; } + to { top: 110%; opacity: 0.3; } +} + +@media (prefers-reduced-motion: reduce) { + .encl-rain__drop { animation: none; opacity: 0.3; } +} + +@media (max-width: 640px) { + .encl-stage { padding-top: 14px; } +} + +/* ═══════════════════════════════════════════════════════════ + HIGH-BRIGHTNESS COMPARISON ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-brightness { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.bright-stage { width: 100%; max-width: 460px; position: relative; padding-top: 10px; margin: 0 auto; } +.bright-sun { + position: absolute; top: -10px; right: 20px; width: 50px; height: 50px; + border-radius: 50%; background: radial-gradient(circle, rgba(255,200,50,0.5), transparent 70%); + animation: bright-sun-glow 3s ease-in-out infinite; +} +.bright-compare { display: flex; gap: 12px; align-items: center; } +.bright-panel { flex: 1; } +.bright-panel__screen { + border-radius: 8px; padding: 24px 12px; text-align: center; + border: 2px solid rgba(255,255,255,0.08); +} +.bright-panel__screen--dim { + background: rgba(255,255,255,0.03); opacity: 0.4; +} +.bright-panel__screen--vivid { + background: linear-gradient(135deg, #1a1a2e, #16213e); + animation: bright-vivid-pulse 3s ease-in-out infinite; +} +.bright-panel__text { font-size: 14px; font-weight: 600; color: #fff; } +.bright-panel__screen--dim .bright-panel__text { color: rgba(255,255,255,0.3); } +.bright-panel__label { + text-align: center; font-size: 11px; color: rgba(255,255,255,0.5); + margin-top: 8px; font-weight: 600; +} +.bright-vs { font-size: 12px; font-weight: 700; color: rgba(255,255,255,0.3); } + +@keyframes bright-sun-glow { + 0%, 100% { opacity: 0.7; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.2); } +} +@keyframes bright-vivid-pulse { + 0%, 100% { box-shadow: 0 0 12px rgba(216,51,2,0.15); } + 50% { box-shadow: 0 0 24px rgba(216,51,2,0.3); } +} + +@media (prefers-reduced-motion: reduce) { + .bright-sun { animation: none; } + .bright-panel__screen--vivid { animation: none; } +} + +@media (max-width: 640px) { + .bright-panel__screen { padding: 18px 10px; } +} + +/* ═══════════════════════════════════════════════════════════ + CELLULAR CONNECTIVITY ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-cellular { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.cell-stage { + width: 100%; max-width: 380px; margin: 0 auto; + display: flex; flex-direction: column; align-items: center; gap: 16px; + padding: 20px 0; +} +.cell-tower { position: relative; width: 80px; height: 100px; } +.cell-tower__mast { + position: absolute; left: 50%; bottom: 0; width: 6px; height: 60px; + background: linear-gradient(to top, #374151, #6b7280); + transform: translateX(-50%); border-radius: 3px; +} +.cell-tower__base { + position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); + width: 30px; height: 8px; background: #374151; border-radius: 2px; +} +.cell-wave { + position: absolute; top: 10px; left: 50%; transform: translateX(-50%); + border: 2px solid rgba(216,51,2,0.4); border-radius: 50%; + animation: cell-wave-expand 3s ease-out infinite; +} +.cell-wave--1 { width: 30px; height: 30px; animation-delay: 0s; } +.cell-wave--2 { width: 50px; height: 50px; animation-delay: 1s; } +.cell-wave--3 { width: 70px; height: 70px; animation-delay: 2s; } +.cell-badge { + font-size: 14px; font-weight: 800; color: #fff; + background: rgba(216,51,2,0.15); padding: 6px 16px; border-radius: 20px; + letter-spacing: 1px; +} +.cell-signal { display: flex; gap: 3px; align-items: flex-end; } +.cell-signal__bar { + width: 6px; background: #22c55e; border-radius: 2px; + animation: cell-bar-fill 3s ease-in-out infinite; +} +.cell-signal__bar--1 { height: 6px; animation-delay: 0s; } +.cell-signal__bar--2 { height: 12px; animation-delay: 0.3s; } +.cell-signal__bar--3 { height: 18px; animation-delay: 0.6s; } +.cell-signal__bar--4 { height: 24px; animation-delay: 0.9s; } +.cell-status { font-size: 12px; color: #22c55e; font-weight: 600; } + +@keyframes cell-wave-expand { + 0% { opacity: 1; transform: translateX(-50%) scale(0.5); } + 100% { opacity: 0; transform: translateX(-50%) scale(1.5); } +} +@keyframes cell-bar-fill { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +@media (prefers-reduced-motion: reduce) { + .cell-wave { animation: none; opacity: 0.3; } + .cell-signal__bar { animation: none; } +} + +@media (max-width: 640px) { + .cell-tower { transform: scale(0.85); } +} + +/* ═══════════════════════════════════════════════════════════ + DESIGNER EDITOR CANVAS ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-designer { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.designer-stage { width: 100%; max-width: 480px; margin: 0 auto; } +.designer-editor { + background: #0d1117; border: 1px solid #1e2435; border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); display: grid; + grid-template-columns: 1fr 90px; grid-template-rows: auto 1fr; +} +.designer-toolbar { + grid-column: 1 / -1; display: flex; gap: 8px; padding: 10px 14px; + background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.06); +} +.designer-tool { + width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; + background: rgba(255,255,255,0.06); border-radius: 6px; + font-size: 13px; color: rgba(255,255,255,0.5); +} +.designer-canvas { padding: 14px; display: flex; flex-direction: column; gap: 8px; } +.designer-widget { + border-radius: 6px; padding: 12px 10px; + border: 1px dashed rgba(255,255,255,0.12); + opacity: 0; animation: designer-drop 8s infinite; +} +.designer-widget--text { background: rgba(59,130,246,0.08); animation-delay: 0s; } +.designer-widget--img { background: rgba(34,197,94,0.08); animation-delay: 1s; } +.designer-widget--ticker { background: rgba(234,179,8,0.08); animation-delay: 2s; } +.designer-widget--video { background: rgba(168,85,247,0.08); animation-delay: 3s; } +.designer-widget__label { font-size: 11px; color: rgba(255,255,255,0.5); } +.designer-layers { + padding: 14px 10px; border-left: 1px solid rgba(255,255,255,0.06); +} +.designer-layers__title { font-size: 10px; font-weight: 600; color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; } +.designer-layer { + font-size: 11px; color: rgba(255,255,255,0.4); padding: 4px 6px; + border-radius: 4px; margin-bottom: 3px; +} + +@keyframes designer-drop { + 0%, 8% { opacity: 0; transform: translateY(-8px); } + 15%, 80% { opacity: 1; transform: translateY(0); } + 90%, 100%{ opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + .designer-widget { animation: none; opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .designer-editor { grid-template-columns: 1fr; } + .designer-layers { display: none; } +} + +/* ═══════════════════════════════════════════════════════════ + MEDIA LIBRARY GRID ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-media-library { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.medialib-stage { width: 100%; max-width: 460px; margin: 0 auto; } +.medialib-panel { + background: #0d1117; border: 1px solid #1e2435; border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); padding: 16px; +} +.medialib-hdr { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; } +.medialib-hdr__title { font-size: 14px; font-weight: 700; color: #fff; } +.medialib-hdr__count { font-size: 11px; color: rgba(255,255,255,0.4); } +.medialib-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 14px; } +.medialib-thumb { + aspect-ratio: 1; border-radius: 8px; display: flex; + align-items: center; justify-content: center; + opacity: 0; animation: medialib-fade 10s infinite; +} +.medialib-thumb--img { background: rgba(59,130,246,0.1); animation-delay: 0s; } +.medialib-thumb--vid { background: rgba(168,85,247,0.1); animation-delay: 0.4s; } +.medialib-thumb--pdf { background: rgba(220,38,38,0.1); animation-delay: 0.8s; } +.medialib-thumb--ppt { background: rgba(234,179,8,0.1); animation-delay: 1.2s; } +.medialib-thumb--img2 { background: rgba(34,197,94,0.1); animation-delay: 1.6s; } +.medialib-thumb--audio { background: rgba(236,72,153,0.1); animation-delay: 2s; } +.medialib-thumb__icon { font-size: 16px; color: rgba(255,255,255,0.5); font-weight: 700; } +.medialib-upload { display: flex; align-items: center; gap: 10px; } +.medialib-upload__bar { + flex: 1; height: 4px; background: rgba(255,255,255,0.08); border-radius: 2px; + overflow: hidden; position: relative; +} +.medialib-upload__bar::after { + content: ''; position: absolute; inset: 0; + background: #3b82f6; border-radius: 2px; + animation: medialib-progress 4s ease-in-out infinite; + transform-origin: left; +} +.medialib-upload__text { font-size: 11px; color: rgba(255,255,255,0.4); white-space: nowrap; } + +@keyframes medialib-fade { + 0%, 8% { opacity: 0; transform: scale(0.85); } + 16%, 75%{ opacity: 1; transform: scale(1); } + 85%, 100%{ opacity: 0; transform: scale(0.95); } +} +@keyframes medialib-progress { + 0% { transform: scaleX(0); } + 80% { transform: scaleX(0.95); } + 100% { transform: scaleX(0); } +} + +@media (prefers-reduced-motion: reduce) { + .medialib-thumb { animation: none; opacity: 1; transform: none; } + .medialib-upload__bar::after { animation: none; transform: scaleX(0.6); } +} + +@media (max-width: 640px) { + .medialib-grid { grid-template-columns: repeat(2, 1fr); } +} + +/* ═══════════════════════════════════════════════════════════ + PUBLISH WORKFLOW ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-publish { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.publish-stage { width: 100%; max-width: 460px; padding: 16px 0; margin: 0 auto; } +.publish-flow { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 20px; } +.publish-step { text-align: center; } +.publish-step__badge { + display: block; font-size: 12px; font-weight: 700; padding: 8px 16px; + border-radius: 8px; margin-bottom: 6px; +} +.publish-step--draft .publish-step__badge { background: rgba(234,179,8,0.15); color: #eab308; } +.publish-step--review .publish-step__badge { background: rgba(59,130,246,0.15); color: #3b82f6; } +.publish-step--live .publish-step__badge { background: rgba(34,197,94,0.15); color: #22c55e; } +.publish-step__label { font-size: 10px; color: rgba(255,255,255,0.4); } +.publish-arrow { font-size: 16px; color: rgba(255,255,255,0.2); } +.publish-step--draft { animation: pub-step-highlight 6s infinite 0s; } +.publish-step--review { animation: pub-step-highlight 6s infinite 2s; } +.publish-step--live { animation: pub-step-highlight 6s infinite 4s; } +.publish-bar { + height: 4px; background: rgba(255,255,255,0.08); border-radius: 2px; + overflow: hidden; margin-bottom: 10px; +} +.publish-bar__fill { + height: 100%; width: 0%; background: linear-gradient(90deg, #eab308, #3b82f6, #22c55e); + border-radius: 2px; animation: pub-bar-fill 6s ease-in-out infinite; +} +.publish-status { + font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; + animation: pub-status-fade 6s infinite; +} + +@keyframes pub-step-highlight { + 0%, 10% { opacity: 0.4; transform: scale(1); } + 16%, 28% { opacity: 1; transform: scale(1.08); } + 33%, 100% { opacity: 0.4; transform: scale(1); } +} +@keyframes pub-bar-fill { + 0% { width: 0%; } + 80% { width: 100%; } + 100% { width: 100%; opacity: 0; } +} +@keyframes pub-status-fade { + 0%, 60% { opacity: 0; } + 70%, 90% { opacity: 1; } + 100% { opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + .publish-step--draft, .publish-step--review, .publish-step--live { animation: none; opacity: 1; } + .publish-bar__fill { animation: none; width: 100%; } + .publish-status { animation: none; opacity: 1; } +} + +@media (max-width: 640px) { + .publish-step__badge { padding: 6px 12px; font-size: 11px; } +} + +/* ═══════════════════════════════════════════════════════════ + SCREEN GROUPS HIERARCHY ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-screen-groups { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.groups-stage { width: 100%; max-width: 400px; padding: 10px 0; margin: 0 auto; } +.groups-tree { margin-bottom: 16px; } +.groups-node { + display: flex; align-items: center; gap: 8px; padding: 8px 12px; + border-radius: 8px; margin-bottom: 4px; + background: rgba(255,255,255,0.04); +} +.groups-node__icon { font-size: 8px; } +.groups-node--root .groups-node__icon { color: var(--color-primary); } +.groups-node--region .groups-node__icon { color: #3b82f6; } +.groups-node--site .groups-node__icon { color: #22c55e; } +.groups-node__name { font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.8); flex: 1; } +.groups-node__count { + font-size: 10px; font-weight: 700; color: rgba(255,255,255,0.4); + background: rgba(255,255,255,0.06); padding: 2px 8px; border-radius: 10px; +} +.groups-branch { padding-left: 20px; border-left: 2px solid rgba(255,255,255,0.06); margin-left: 16px; } +.groups-branch--deep { padding-left: 16px; margin-left: 12px; } +.groups-push { + display: flex; align-items: center; justify-content: center; gap: 8px; + padding: 10px; border-radius: 8px; background: rgba(216,51,2,0.08); + position: relative; overflow: hidden; +} +.groups-push__ripple { + position: absolute; inset: 0; border-radius: 8px; + border: 2px solid rgba(216,51,2,0.3); + animation: groups-ripple 3s ease-out infinite; +} +.groups-push__label { font-size: 12px; font-weight: 600; color: var(--color-primary); } + +@keyframes groups-ripple { + 0% { opacity: 1; transform: scale(0.95); } + 100% { opacity: 0; transform: scale(1.05); } +} + +@media (prefers-reduced-motion: reduce) { + .groups-push__ripple { animation: none; opacity: 0.3; } +} + +@media (max-width: 640px) { + .groups-branch { padding-left: 14px; margin-left: 12px; } +} + +/* ═══════════════════════════════════════════════════════════ + MONITORING DASHBOARD ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-monitoring { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.monitor-stage { width: 100%; max-width: 460px; margin: 0 auto; } +.monitor-dash { + background: #0d1117; border: 1px solid #1e2435; border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); padding: 16px; +} +.monitor-hdr { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; } +.monitor-hdr__title { font-size: 14px; font-weight: 700; color: #fff; } +.monitor-hdr__count { font-size: 11px; color: #22c55e; font-weight: 600; } +.monitor-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-bottom: 14px; } +.monitor-tile { + display: flex; align-items: center; gap: 6px; + padding: 8px 10px; border-radius: 6px; + background: rgba(255,255,255,0.04); font-size: 11px; color: rgba(255,255,255,0.6); +} +.monitor-tile__dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } +.monitor-tile--ok .monitor-tile__dot { background: #22c55e; } +.monitor-tile--warn .monitor-tile__dot { background: #eab308; animation: monitor-warn-blink 1.5s infinite; } +.monitor-tile__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.monitor-screenshot { + background: rgba(255,255,255,0.04); border-radius: 8px; padding: 10px; text-align: center; +} +.monitor-screenshot__img { + width: 100%; aspect-ratio: 16 / 9; background: rgba(255,255,255,0.06); + border-radius: 4px; margin-bottom: 6px; + animation: monitor-screenshot-flash 6s infinite; +} +.monitor-screenshot__label { font-size: 10px; color: rgba(255,255,255,0.4); } + +@keyframes monitor-warn-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} +@keyframes monitor-screenshot-flash { + 0%, 90% { opacity: 1; } + 93% { opacity: 0.3; } + 96% { opacity: 1; } +} + +@media (prefers-reduced-motion: reduce) { + .monitor-tile--warn .monitor-tile__dot { animation: none; } + .monitor-screenshot__img { animation: none; } +} + +@media (max-width: 640px) { + .monitor-grid { grid-template-columns: repeat(2, 1fr); } +} + +/* ═══════════════════════════════════════════════════════════ + PATIENT WAYFINDING KIOSK ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-patient-wayfind { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.pwayfind-stage { width: 100%; max-width: 400px; margin: 0 auto; } +.pwayfind-kiosk { + background: #0f1117; border: 1px solid #1e2435; border-radius: 12px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); padding: 16px; +} +.pwayfind-hdr { + display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; +} +.pwayfind-hdr__title { font-size: 16px; font-weight: 700; color: #fff; } +.pwayfind-hdr__a11y { font-size: 20px; color: rgba(255,255,255,0.3); } +.pwayfind-search { + display: flex; align-items: center; gap: 8px; + background: rgba(255,255,255,0.06); border-radius: 8px; + padding: 10px 14px; margin-bottom: 14px; +} +.pwayfind-search__icon { font-size: 14px; } +.pwayfind-search__text { + font-size: 13px; color: rgba(255,255,255,0.6); + animation: pwayfind-typing 4s steps(13) infinite; + overflow: hidden; white-space: nowrap; width: 0; + border-right: 2px solid rgba(255,255,255,0.4); +} +.pwayfind-result { + background: rgba(59,130,246,0.06); border-radius: 8px; + padding: 12px; margin-bottom: 14px; + border-left: 3px solid #3b82f6; + opacity: 0; animation: pwayfind-result-in 4s ease infinite; + animation-delay: 2s; +} +.pwayfind-result__dept { font-size: 13px; font-weight: 600; color: #fff; margin-bottom: 8px; } +.pwayfind-result__route { display: flex; align-items: center; gap: 8px; font-size: 11px; color: rgba(255,255,255,0.5); } +.pwayfind-route__line { + flex: 1; height: 2px; background: linear-gradient(90deg, #3b82f6, #22c55e); + border-radius: 1px; +} +.pwayfind-route__start { white-space: nowrap; color: #3b82f6; } +.pwayfind-route__end { white-space: nowrap; color: #22c55e; } +.pwayfind-quick { display: flex; gap: 6px; flex-wrap: wrap; } +.pwayfind-quick__item { + font-size: 11px; padding: 6px 12px; border-radius: 20px; + background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.5); +} + +@keyframes pwayfind-typing { + 0% { width: 0; } + 50% { width: 100%; } + 80% { width: 100%; } + 100% { width: 0; } +} +@keyframes pwayfind-result-in { + 0%, 40% { opacity: 0; transform: translateY(6px); } + 50%, 80% { opacity: 1; transform: translateY(0); } + 90%, 100%{ opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + .pwayfind-search__text { animation: none; width: auto; border-right: none; } + .pwayfind-result { animation: none; opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .pwayfind-kiosk { padding: 12px; } +} + +/* ═══════════════════════════════════════════════════════════ + WAITING ROOM INFORMATION DISPLAY ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-waiting-room { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.waitroom-stage { width: 100%; max-width: 520px; margin: 0 auto; } +.waitroom-tv { display: flex; flex-direction: column; align-items: center; width: 100%; } +.waitroom-tv__body { + width: 100%; background: #111; border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; outline: 2px solid #000; padding: 4px; + box-shadow: 0 14px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06); +} +.waitroom-tv__screen { width: 100%; aspect-ratio: 16 / 9; background: #0a0a0a; border-radius: 2px; overflow: hidden; position: relative; } +.waitroom-slides { position: relative; width: 100%; height: 100%; } +.waitroom-slide { + position: absolute; inset: 0; display: flex; flex-direction: column; + align-items: center; justify-content: center; padding: 20px; text-align: center; + opacity: 0; animation: waitroom-cycle 12s infinite; +} +.waitroom-slide--tips { animation-delay: 0s; } +.waitroom-slide--wait { animation-delay: 4s; } +.waitroom-slide--info { animation-delay: 8s; } +.waitroom-tips__title { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: #22c55e; margin-bottom: 8px; } +.waitroom-tips__text { font-size: 14px; color: rgba(255,255,255,0.7); line-height: 1.5; } +.waitroom-wait__title { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: #3b82f6; margin-bottom: 8px; } +.waitroom-wait__value { font-size: 32px; font-weight: 800; color: #fff; } +.waitroom-wait__queue { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 4px; } +.waitroom-info__title { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: rgba(255,255,255,0.4); margin-bottom: 8px; } +.waitroom-info__text { font-size: 13px; color: rgba(255,255,255,0.6); line-height: 1.5; } +.waitroom-tv__feet { display: flex; justify-content: space-between; width: 60%; max-width: 300px; } +.waitroom-tv__foot { width: 12px; height: 8px; background: #111; border: 1px solid #000; border-radius: 0 0 4px 4px; } + +@keyframes waitroom-cycle { + 0%, 5% { opacity: 0; transform: translateY(8px); } + 8%, 28% { opacity: 1; transform: translateY(0); } + 33%, 100%{ opacity: 0; transform: translateY(-8px); } +} + +@media (prefers-reduced-motion: reduce) { + .waitroom-slide { animation: none; } + .waitroom-slide--tips { opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .waitroom-wait__value { font-size: 26px; } +} + +/* ═══════════════════════════════════════════════════════════ + MULTI-ZONE LAYOUT ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-multi-zone { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.mzone-stage { width: 100%; max-width: 520px; margin: 0 auto; } +.mzone-tv { display: flex; flex-direction: column; align-items: center; width: 100%; } +.mzone-tv__body { + width: 100%; background: #111; border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; outline: 2px solid #000; padding: 4px; + box-shadow: 0 14px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06); +} +.mzone-tv__screen { width: 100%; aspect-ratio: 16 / 9; background: #0a0a0a; border-radius: 2px; overflow: hidden; } +.mzone-layout { + width: 100%; height: 100%; + display: grid; grid-template-columns: 1fr 120px; grid-template-rows: 1fr 28px; +} +.mzone-main { + background: #0f0f1a; display: flex; align-items: center; justify-content: center; + border-right: 1px solid rgba(255,255,255,0.06); + border-bottom: 1px solid rgba(255,255,255,0.06); +} +.mzone-main__label { font-size: 14px; color: rgba(255,255,255,0.3); } +.mzone-side { + padding: 10px 8px; background: rgba(255,255,255,0.02); + border-bottom: 1px solid rgba(255,255,255,0.06); + overflow: hidden; +} +.mzone-side__title { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--color-primary); margin-bottom: 6px; } +.mzone-side__item { font-size: 10px; color: rgba(255,255,255,0.5); padding: 3px 0; } +.mzone-ticker { + grid-column: 1 / -1; background: rgba(216,51,2,0.08); padding: 6px 12px; + font-size: 10px; color: rgba(255,255,255,0.5); white-space: nowrap; + overflow: hidden; animation: mzone-scroll 20s linear infinite; +} +.mzone-tv__feet { display: flex; justify-content: space-between; width: 60%; max-width: 300px; } +.mzone-tv__foot { width: 12px; height: 8px; background: #111; border: 1px solid #000; border-radius: 0 0 4px 4px; } + +@keyframes mzone-scroll { + from { text-indent: 100%; } + to { text-indent: -200%; } +} + +@media (prefers-reduced-motion: reduce) { + .mzone-ticker { animation: none; text-indent: 0; } +} + +@media (max-width: 640px) { + .mzone-layout { grid-template-columns: 1fr 90px; } +} + +/* ═══════════════════════════════════════════════════════════ + MEMBERSHIP PROMO CYCLING ANIMATION + ═══════════════════════════════════════════════════════════ */ +.platform-visual.has-membership { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.member-stage { width: 100%; max-width: 520px; aspect-ratio: 16 / 10; margin: 0 auto; } +.member-screen { + width: 100%; height: 100%; + border-radius: 10px; overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); position: relative; +} +.member-slides { position: relative; width: 100%; height: 100%; } +.member-slide { + position: absolute; inset: 0; display: flex; flex-direction: column; + align-items: center; justify-content: center; padding: 20px; text-align: center; + opacity: 0; animation: member-cycle 12s infinite; +} +.member-slide--jan { + background: linear-gradient(135deg, #1e3a5f, #0f172a); + animation-delay: 0s; +} +.member-slide--summer { + background: linear-gradient(135deg, #7c2d12, #1c1917); + animation-delay: 4s; +} +.member-slide--sept { + background: linear-gradient(135deg, #14532d, #0f172a); + animation-delay: 8s; +} +.member-slide__tag { + font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; + color: var(--color-primary); margin-bottom: 6px; +} +.member-slide__title { font-size: 24px; font-weight: 700; color: #fff; margin-bottom: 6px; } +.member-slide__price { font-size: 14px; color: rgba(255,255,255,0.6); margin-bottom: 12px; } +.member-slide__cta { + font-size: 12px; font-weight: 600; color: #fff; + background: var(--color-primary); padding: 6px 18px; border-radius: 20px; + display: inline-block; +} + +@keyframes member-cycle { + 0%, 5% { opacity: 0; transform: scale(0.96); } + 8%, 28% { opacity: 1; transform: scale(1); } + 33%, 100%{ opacity: 0; transform: scale(1.02); } +} + +@media (prefers-reduced-motion: reduce) { + .member-slide { animation: none; } + .member-slide--jan { opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .member-slide__title { font-size: 20px; } +} + +/* ═══════════════════════════════════════════════════════════════ + VIDEO & MOTION GRAPHICS ANIMATION (.platform-visual.has-video-motion) + Looping reel with kinetic typography and motion preview + ═══════════════════════════════════════════════════════════════ */ +.platform-visual.has-video-motion { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.vidmo-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.vidmo-tv { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.vidmo-tv__body { + width: 100%; + background: #111; + border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; + outline: 2px solid #000; + padding: 4px; + box-shadow: 0 14px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06); +} + +.vidmo-tv__screen { + width: 100%; + aspect-ratio: 16/9; + background: #0a0a0a; + border-radius: 2px; + overflow: hidden; + position: relative; +} + +.vidmo-tv__feet { + display: flex; + justify-content: space-between; + width: 60%; + max-width: 300px; +} + +.vidmo-tv__foot { + width: 12px; + height: 8px; + background: #111; + border: 1px solid #000; + border-radius: 0 0 4px 4px; +} + +/* Slides */ +.vidmo-slides { + position: relative; + width: 100%; + height: 100%; +} + +.vidmo-slide { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; + text-align: center; + opacity: 0; + animation: vidmo-cycle 12s infinite; +} + +.vidmo-slide--kinetic { animation-delay: 0s; } +.vidmo-slide--promo { animation-delay: 4s; } +.vidmo-slide--loop { animation-delay: 8s; } + +/* Kinetic typography slide */ +.vidmo-kinetic { + display: flex; + flex-direction: column; + gap: 4px; + align-items: center; +} + +.vidmo-kinetic__word { + font-weight: 800; + text-transform: uppercase; + letter-spacing: 2px; + color: #fff; + opacity: 0; + animation: vidmo-word-in 12s infinite; +} + +.vidmo-kinetic__word--1 { font-size: 28px; animation-delay: 0.2s; } +.vidmo-kinetic__word--2 { font-size: 20px; color: var(--color-primary); animation-delay: 0.5s; } +.vidmo-kinetic__word--3 { font-size: 16px; color: rgba(255,255,255,0.5); animation-delay: 0.8s; } + +/* Promo slide */ +.vidmo-promo { + position: relative; + width: 80%; + max-width: 280px; +} + +.vidmo-promo__bg { + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(216,51,2,0.2), transparent); + border-radius: 10px; + animation: vidmo-bg-pulse 4s ease-in-out infinite; +} + +.vidmo-promo__label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1.5px; + color: var(--color-primary); + margin-bottom: 6px; + position: relative; +} + +.vidmo-promo__title { + font-size: 22px; + font-weight: 800; + color: #fff; + position: relative; +} + +.vidmo-promo__sub { + font-size: 12px; + color: rgba(255,255,255,0.5); + margin-top: 6px; + position: relative; +} + +/* Loop counter slide */ +.vidmo-loop { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.vidmo-loop__icon { + width: 40px; + height: 40px; + border-radius: 50%; + border: 2px solid var(--color-primary); + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + animation: vidmo-icon-spin 4s linear infinite; +} + +.vidmo-loop__text { + font-size: 13px; + font-weight: 600; + color: rgba(255,255,255,0.6); +} + +.vidmo-loop__formats { + display: flex; + gap: 8px; +} + +.vidmo-loop__fmt { + font-size: 10px; + font-weight: 700; + padding: 3px 8px; + border-radius: 4px; + background: rgba(255,255,255,0.06); + color: rgba(255,255,255,0.4); + letter-spacing: 0.5px; +} + +/* Progress bar */ +.vidmo-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: rgba(255,255,255,0.06); +} + +.vidmo-progress__bar { + height: 100%; + background: var(--color-primary); + animation: vidmo-progress-fill 12s linear infinite; +} + +@keyframes vidmo-cycle { + 0%, 2% { opacity: 0; transform: translateY(10px); } + 5%, 30% { opacity: 1; transform: translateY(0); } + 33%, 100%{ opacity: 0; transform: translateY(-10px); } +} + +@keyframes vidmo-word-in { + 0%, 2% { opacity: 0; transform: scale(0.8); } + 5%, 28% { opacity: 1; transform: scale(1); } + 33%, 100%{ opacity: 0; transform: scale(1.05); } +} + +@keyframes vidmo-bg-pulse { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 1; } +} + +@keyframes vidmo-icon-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes vidmo-progress-fill { + 0% { width: 0%; } + 95% { width: 100%; } + 100% { width: 0%; } +} + +@media (prefers-reduced-motion: reduce) { + .vidmo-slide { animation: none; } + .vidmo-slide--kinetic { opacity: 1; transform: none; } + .vidmo-kinetic__word { animation: none; opacity: 1; transform: none; } + .vidmo-loop__icon { animation: none; } + .vidmo-progress__bar { animation: none; width: 60%; } +} + +@media (max-width: 640px) { + .vidmo-kinetic__word--1 { font-size: 22px; } + .vidmo-promo__title { font-size: 18px; } +} + +/* ═══════════════════════════════════════════════════════════════ + BRANDED LAYOUT DESIGN ANIMATION (.platform-visual.has-brand-layout) + Template builder showing brand elements snapping into place + ═══════════════════════════════════════════════════════════════ */ +.platform-visual.has-brand-layout { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.blay-stage { + width: 100%; + max-width: 480px; + margin: 0 auto; +} + +.blay-editor { + background: #0d1117; + border: 1px solid #1e2435; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); +} + +.blay-toolbar { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + background: rgba(255,255,255,0.03); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.blay-toolbar__title { + font-size: 12px; + font-weight: 600; + color: rgba(255,255,255,0.5); + flex: 1; +} + +.blay-toolbar__dots { + display: flex; + gap: 4px; +} + +.blay-toolbar__dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.blay-toolbar__dot--r { background: #ff5f56; } +.blay-toolbar__dot--y { background: #ffbd2e; } +.blay-toolbar__dot--g { background: #27c93f; } + +.blay-body { + display: grid; + grid-template-columns: 1fr 140px; +} + +/* Canvas area */ +.blay-canvas { + padding: 16px; + border-right: 1px solid rgba(255,255,255,0.06); +} + +.blay-preview { + background: #0a0a0e; + border-radius: 6px; + overflow: hidden; + position: relative; + aspect-ratio: 16/10; +} + +/* Brand elements dropping in */ +.blay-el { + position: absolute; + opacity: 0; + animation: blay-drop 10s infinite; +} + +.blay-el--logo { + top: 12px; + left: 12px; + width: 60px; + height: 18px; + background: var(--color-primary); + border-radius: 3px; + animation-delay: 0s; +} + +.blay-el--hero { + top: 38px; + left: 12px; + right: 12px; + height: 48%; + background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.08)); + border-radius: 4px; + animation-delay: 0.5s; +} + +.blay-el--headline { + bottom: 38%; + left: 12px; + width: 65%; + height: 12px; + background: rgba(255,255,255,0.2); + border-radius: 2px; + animation-delay: 1s; +} + +.blay-el--body { + bottom: 28%; + left: 12px; + width: 80%; + height: 6px; + background: rgba(255,255,255,0.08); + border-radius: 2px; + animation-delay: 1.3s; +} + +.blay-el--body2 { + bottom: 22%; + left: 12px; + width: 70%; + height: 6px; + background: rgba(255,255,255,0.06); + border-radius: 2px; + animation-delay: 1.5s; +} + +.blay-el--cta { + bottom: 10%; + left: 12px; + width: 80px; + height: 24px; + background: var(--color-primary); + border-radius: 4px; + animation-delay: 1.8s; +} + +.blay-el--accent { + top: 0; + right: 0; + width: 4px; + height: 100%; + background: var(--color-primary); + animation-delay: 2.1s; +} + +/* Brand guide panel */ +.blay-panel { + padding: 14px 12px; +} + +.blay-panel__title { + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255,255,255,0.35); + margin-bottom: 12px; +} + +.blay-colors { + display: flex; + gap: 6px; + margin-bottom: 14px; +} + +.blay-color { + width: 22px; + height: 22px; + border-radius: 4px; + border: 1px solid rgba(255,255,255,0.1); +} + +.blay-color--1 { background: var(--color-primary); } +.blay-color--2 { background: #1a1a2e; } +.blay-color--3 { background: #fff; } +.blay-color--4 { background: #6b7280; } + +.blay-fonts { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 14px; +} + +.blay-font { + display: flex; + flex-direction: column; + gap: 2px; +} + +.blay-font__label { + font-size: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(255,255,255,0.25); +} + +.blay-font__sample { + font-size: 14px; + font-weight: 700; + color: rgba(255,255,255,0.6); +} + +.blay-font__sample--body { + font-size: 11px; + font-weight: 400; +} + +.blay-spacing { + display: flex; + flex-direction: column; + gap: 4px; +} + +.blay-spacing__label { + font-size: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(255,255,255,0.25); +} + +.blay-spacing__bars { + display: flex; + align-items: flex-end; + gap: 3px; + height: 20px; +} + +.blay-spacing__bar { + width: 12px; + border-radius: 2px; + background: rgba(255,255,255,0.1); +} + +.blay-spacing__bar--sm { height: 6px; } +.blay-spacing__bar--md { height: 12px; } +.blay-spacing__bar--lg { height: 18px; } +.blay-spacing__bar--xl { height: 20px; } + +@keyframes blay-drop { + 0%, 5% { opacity: 0; transform: translateY(-8px); } + 12%, 75% { opacity: 1; transform: translateY(0); } + 85%, 100%{ opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + .blay-el { animation: none; opacity: 1; transform: none; } +} + +@media (max-width: 640px) { + .blay-body { grid-template-columns: 1fr; } + .blay-panel { display: none; } +} + +/* ═══════════════════════════════════════════════════════════════ + DIGITAL MENU BOARD ANIMATION (.platform-visual.has-menu-board) + Rotating restaurant menu with categories and prices + ═══════════════════════════════════════════════════════════════ */ +.platform-visual.has-menu-board { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; + font-size: inherit; +} + +.menu-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.menu-tv { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.menu-tv__body { + width: 100%; + background: #111; + border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; + outline: 2px solid #000; + padding: 4px; + position: relative; + box-shadow: 0 14px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06); +} + +.menu-tv__screen { + width: 100%; + aspect-ratio: 16/9; + background: #0a0a0a; + border-radius: 2px; + overflow: hidden; + position: relative; +} + +.menu-tv__feet { + display: flex; + justify-content: space-between; + width: 60%; + max-width: 300px; +} + +.menu-tv__foot { + width: 12px; + height: 8px; + background: #111; + border: 1px solid #000; + border-radius: 0 0 4px 4px; +} + +/* Live badge */ +.menu-live { + position: absolute; + top: 10px; + right: 10px; + display: flex; + align-items: center; + gap: 5px; + background: rgba(0,0,0,0.5); + padding: 3px 8px; + border-radius: 20px; + z-index: 10; + border: 1px solid rgba(255,255,255,0.1); + backdrop-filter: blur(4px); +} + +.menu-live__dot { + width: 6px; + height: 6px; + background: #4ade80; + border-radius: 50%; + box-shadow: 0 0 8px #4ade80; + animation: menu-live-pulse 2s ease-in-out infinite; +} + +.menu-live__text { + font-size: 9px; + color: #fff; + font-weight: 500; + letter-spacing: 0.3px; +} + +/* Menu slides */ +.menu-slides { + position: relative; + width: 100%; + height: 100%; +} + +.menu-slide { + position: absolute; + inset: 0; + opacity: 0; + animation: menu-slide-fade 9s infinite; + display: flex; + flex-direction: column; +} + +.menu-slide--mains { animation-delay: 0s; } +.menu-slide--drinks { animation-delay: 3s; } +.menu-slide--dessert { animation-delay: 6s; } + +.menu-content { + padding: 20px 28px; + height: 100%; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, rgba(15,15,20,1) 0%, rgba(25,25,35,1) 100%); + color: #fff; +} + +.menu-content__hd { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +.menu-content__badge { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + background: var(--color-primary); + padding: 2px 8px; + border-radius: 4px; + font-weight: 700; +} + +.menu-content__time { + font-size: 11px; + color: rgba(255,255,255,0.5); + font-family: monospace; +} + +.menu-content__title { + font-size: 22px; + font-weight: 800; + margin-bottom: 16px; + color: #fff; +} + +.menu-content__items { + display: flex; + flex-direction: column; + gap: 12px; +} + +.menu-content__item { + display: flex; + justify-content: space-between; + align-items: flex-start; + border-bottom: 1px solid rgba(255,255,255,0.05); + padding-bottom: 6px; +} + +.menu-content__name { + font-size: 14px; + font-weight: 600; + color: #fff; + margin-bottom: 2px; +} + +.menu-content__desc { + font-size: 11px; + color: rgba(255,255,255,0.35); +} + +.menu-content__price { + font-size: 15px; + font-weight: 700; + color: var(--color-primary); + white-space: nowrap; +} + +@keyframes menu-live-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.8); } +} + +@keyframes menu-slide-fade { + 0%, 2% { opacity: 0; transform: translateY(10px); } + 5%, 30% { opacity: 1; transform: translateY(0); } + 33%, 100%{ opacity: 0; transform: translateY(-10px); } +} + +@media (prefers-reduced-motion: reduce) { + .menu-slide { animation: none; } + .menu-slide--mains { opacity: 1; transform: none; } + .menu-live__dot { animation: none; } +} + +@media (max-width: 640px) { + .menu-content { padding: 14px 18px; } + .menu-content__title { font-size: 17px; margin-bottom: 10px; } + .menu-content__name { font-size: 12px; } + .menu-content__desc { display: none; } + .menu-content__price { font-size: 13px; } +} diff --git a/theme/assets/js/solutions-animator.js b/theme/assets/js/solutions-animator.js new file mode 100644 index 0000000..c2b67dc --- /dev/null +++ b/theme/assets/js/solutions-animator.js @@ -0,0 +1,354 @@ +/** + * Solutions Page Animators + * Handles the two JS-driven animations on the Solutions page: + * 1. Live Data board — ticking KPI values + animated sparkline + * 2. Transit board — live clock, split-flap flip characters, row cycling + * + * Both respect prefers-reduced-motion and pause via IntersectionObserver. + * Mirrors the patterns and conventions of dashboard-animator.js. + */ + +/* ── 1. Live Data KPI Animator ─────────────────────────────────────────── */ +(function () { + 'use strict'; + + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + /* KPI definitions: label, base value, unit, variance range, display format */ + var KPIS = [ + { id: 'ld-orders', base: 1847, range: 120, fmt: function (v) { return v.toLocaleString(); } }, + { id: 'ld-uptime', base: 9997, range: 2, fmt: function (v) { return (v / 100).toFixed(2) + '%'; } }, + { id: 'ld-alerts', base: 3, range: 2, fmt: function (v) { return Math.max(0, v).toString(); } }, + { id: 'ld-latency', base: 42, range: 18, fmt: function (v) { return Math.max(8, v) + 'ms'; } }, + ]; + + /* Sparkline path parameters */ + var LINE_PTS = 16; + var LINE_W = 260; + var LINE_H = 60; + var SPEED = 0.0008; + + function wave(t, off) { + return Math.max(0, Math.min(1, + 0.5 + + Math.sin(t + off) * 0.28 + + Math.sin(t * 2.1 + off * 1.7) * 0.12 + )); + } + + function makeState(stage) { + var kpiEls = []; + for (var i = 0; i < KPIS.length; i++) { + kpiEls.push(stage.querySelector('#' + KPIS[i].id)); + } + return { + stage: stage, + kpiEls: kpiEls, + linePath: stage.querySelector('#ld-line-path'), + fillPath: stage.querySelector('#ld-fill-path'), + phase: Math.random() * Math.PI * 2, + ticker: 0, /* frame counter — update KPI text every N frames */ + paused: false, + }; + } + + function updateKpis(st) { + for (var i = 0; i < KPIS.length; i++) { + var el = st.kpiEls[i]; + if (!el) continue; + var k = KPIS[i]; + var raw = Math.round(k.base + wave(st.phase, i * 1.5) * k.range - k.range / 2); + el.textContent = k.fmt(raw); + } + } + + function updateSparkline(st) { + if (!st.linePath) return; + var pts = []; + for (var i = 0; i < LINE_PTS; i++) { + var x = (i / (LINE_PTS - 1)) * LINE_W; + var y = 8 + (1 - wave(st.phase * 0.7, i * 0.8)) * (LINE_H - 16); + pts.push(x.toFixed(1) + ',' + y.toFixed(1)); + } + var d = 'M' + pts.join(' L'); + st.linePath.setAttribute('d', d); + if (st.fillPath) { + st.fillPath.setAttribute('d', d + ' L' + LINE_W + ',' + LINE_H + ' L0,' + LINE_H + ' Z'); + } + } + + function tick(st) { + if (!st.paused) { + st.phase += SPEED * 16; + st.ticker++; + /* Update KPI text every 12 frames (~5/sec at 60fps) for legibility */ + if (st.ticker >= 12) { + st.ticker = 0; + updateKpis(st); + } + updateSparkline(st); + } + requestAnimationFrame(function () { tick(st); }); + } + + function observe(st) { + if (!('IntersectionObserver' in window)) return; + new IntersectionObserver(function (entries) { + for (var i = 0; i < entries.length; i++) { + st.paused = !entries[i].isIntersecting; + } + }, { rootMargin: '200px', threshold: 0.05 }).observe(st.stage); + } + + function boot() { + var stages = document.querySelectorAll('.ld-stage'); + if (!stages.length) return; + for (var i = 0; i < stages.length; i++) { + if (stages[i]._ldAnim) continue; + var st = makeState(stages[i]); + stages[i]._ldAnim = st; + observe(st); + tick(st); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); + +/* ── 2. Transit Departure Board Animator ───────────────────────────────── */ +(function () { + 'use strict'; + + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + /* Still run the clock in reduced-motion mode */ + startClocks(); + return; + } + + /* Departure data sets — cycle between these every CYCLE_MS */ + var CYCLE_MS = 8000; + + var DATA_SETS = [ + [ + { time: '10:14', dest: 'London Victoria', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '10:22', dest: 'Brighton', plat: '4', status: 'On Time', cls: 'on-time' }, + { time: '10:31', dest: 'Gatwick Airport', plat: '1', status: 'Delayed', cls: 'delayed' }, + { time: '10:45', dest: 'London Bridge', plat: '3', status: 'On Time', cls: 'on-time' }, + { time: '11:02', dest: 'East Croydon', plat: '2', status: 'On Time', cls: 'on-time' }, + ], + [ + { time: '10:22', dest: 'Brighton', plat: '4', status: 'On Time', cls: 'on-time' }, + { time: '10:31', dest: 'Gatwick Airport', plat: '1', status: 'Delayed', cls: 'delayed' }, + { time: '10:45', dest: 'London Bridge', plat: '3', status: 'On Time', cls: 'on-time' }, + { time: '11:02', dest: 'East Croydon', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '11:14', dest: 'London Victoria', plat: '2', status: 'On Time', cls: 'on-time' }, + ], + [ + { time: '10:31', dest: 'Gatwick Airport', plat: '1', status: 'Delayed', cls: 'delayed' }, + { time: '10:45', dest: 'London Bridge', plat: '3', status: 'On Time', cls: 'on-time' }, + { time: '11:02', dest: 'East Croydon', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '11:14', dest: 'London Victoria', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '11:28', dest: 'Three Bridges', plat: '4', status: 'Cancelled', cls: 'cancelled'}, + ], + ]; + + /* ── Clock ── */ + function startClocks() { + var clocks = document.querySelectorAll('#transit-clock'); + if (!clocks.length) return; + + function updateClock() { + var now = new Date(); + var hh = String(now.getHours()).padStart(2, '0'); + var mm = String(now.getMinutes()).padStart(2, '0'); + var ss = String(now.getSeconds()).padStart(2, '0'); + var str = hh + ':' + mm + ':' + ss; + for (var i = 0; i < clocks.length; i++) clocks[i].textContent = str; + } + updateClock(); + setInterval(updateClock, 1000); + } + + /* ── Flip helpers ── */ + function flipCells(rowEl, newDest) { + var flapEls = rowEl.querySelectorAll('.transit-flap'); + var chars = newDest.split(''); + + /* Extend or shrink the flap container to match new length */ + var destCell = rowEl.querySelector('.transit-cell--dest'); + if (!destCell) return; + + /* Animate existing flaps, create/remove extras */ + var i; + for (i = 0; i < chars.length; i++) { + var ch = chars[i] === ' ' ? '\u00a0' : chars[i]; + if (i < flapEls.length) { + /* Animate existing */ + (function (el, character) { + el.classList.add('is-flipping'); + setTimeout(function () { + el.textContent = character; + el.classList.remove('is-flipping'); + }, 125); + })(flapEls[i], ch); + } else { + /* Append new flap */ + var newFlap = document.createElement('span'); + newFlap.className = 'transit-flap is-flipping'; + newFlap.textContent = ch; + destCell.appendChild(newFlap); + setTimeout(function (el) { + el.classList.remove('is-flipping'); + }, 125, newFlap); + } + } + /* Remove surplus flaps */ + for (i = chars.length; i < flapEls.length; i++) { + (function (el) { + el.classList.add('is-flipping'); + setTimeout(function () { el.parentNode && el.parentNode.removeChild(el); }, 250); + })(flapEls[i]); + } + } + + function applyRow(rowEl, departure) { + var timeEl = rowEl.querySelector('.transit-cell--time'); + var platEl = rowEl.querySelector('.transit-cell--plat'); + var statusEl = rowEl.querySelector('.transit-cell--status'); + + if (timeEl) timeEl.textContent = departure.time; + if (platEl) platEl.textContent = departure.platform || departure.plat; + if (statusEl) { + statusEl.textContent = departure.status; + statusEl.className = 'transit-cell transit-cell--status transit-status--' + departure.cls; + } + flipCells(rowEl, departure.dest); + } + + function cycleBoard(stage, dataIdx) { + var rows = stage.querySelectorAll('.transit-row'); + var set = DATA_SETS[dataIdx % DATA_SETS.length]; + + for (var i = 0; i < Math.min(rows.length, set.length); i++) { + /* Stagger each row by 180ms */ + (function (row, dep) { + setTimeout(function () { applyRow(row, dep); }, i * 180); + })(rows[i], set[i]); + } + } + + function initBoard(stage) { + var state = { idx: 0, timer: null, paused: false }; + + function advance() { + if (state.paused) return; + state.idx++; + cycleBoard(stage, state.idx); + } + + function startTimer() { + if (state.timer) return; + state.timer = setInterval(advance, CYCLE_MS); + } + + function stopTimer() { + clearInterval(state.timer); + state.timer = null; + } + + if ('IntersectionObserver' in window) { + new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + state.paused = !e.isIntersecting; + e.isIntersecting ? startTimer() : stopTimer(); + }); + }, { rootMargin: '200px', threshold: 0.05 }).observe(stage); + } + + startTimer(); + } + + function boot() { + startClocks(); + var stages = document.querySelectorAll('.transit-stage'); + if (!stages.length) return; + for (var i = 0; i < stages.length; i++) { + if (stages[i]._transitAnim) continue; + stages[i]._transitAnim = true; + initBoard(stages[i]); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); + +/* ── 3. Day-Part Clock Animator ────────────────────────────────────────── */ +(function () { + 'use strict'; + + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + function initDaypart(stage) { + var clockEl = stage.querySelector('[data-daypart-clock]'); + var badgeEl = stage.querySelector('[data-daypart-badge]'); + if (!clockEl || !badgeEl) return; + + var visible = true; + var observer = new IntersectionObserver(function (entries) { + visible = entries[0].isIntersecting; + }, { threshold: 0.1 }); + observer.observe(stage); + + var simHour = 7; + var simMin = 0; + var parts = ['Morning', 'Afternoon', 'Evening']; + + function pad(n) { return n < 10 ? '0' + n : '' + n; } + + function tick() { + if (!visible) { requestAnimationFrame(tick); return; } + + simMin += 1; + if (simMin >= 60) { simMin = 0; simHour = (simHour + 1) % 24; } + + var displayHour = simHour % 12 || 12; + var ampm = simHour < 12 ? 'AM' : 'PM'; + clockEl.textContent = displayHour + ':' + pad(simMin) + ' ' + ampm; + + if (simHour >= 5 && simHour < 12) { + badgeEl.textContent = parts[0]; + } else if (simHour >= 12 && simHour < 17) { + badgeEl.textContent = parts[1]; + } else { + badgeEl.textContent = parts[2]; + } + + requestAnimationFrame(tick); + } + + requestAnimationFrame(tick); + } + + function boot() { + var stages = document.querySelectorAll('.daypart-stage'); + for (var i = 0; i < stages.length; i++) { + if (stages[i]._daypartAnim) continue; + stages[i]._daypartAnim = true; + initDaypart(stages[i]); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); diff --git a/theme/blocks/editor.js b/theme/blocks/editor.js index a8c8bdc..add7336 100644 --- a/theme/blocks/editor.js +++ b/theme/blocks/editor.js @@ -6,2050 +6,2167 @@ * Child blocks are dynamic (save -> null) with inline RichText editing. */ (function (wp) { -'use strict'; + 'use strict'; -var el = wp.element.createElement; -var Frag = wp.element.Fragment; -var reg = wp.blocks.registerBlockType; -var RT = wp.blockEditor.RichText; -var IC = wp.blockEditor.InspectorControls; -var IB = wp.blockEditor.InnerBlocks; -var PB = wp.components.PanelBody; -var TC = wp.components.TextControl; -var TA = wp.components.TextareaControl; -var SC = wp.components.SelectControl; -var TG = wp.components.ToggleControl; -var RC = wp.components.RangeControl; -var Btn = wp.components.Button; -var MUC = wp.blockEditor.MediaUploadCheck; -var MU = wp.blockEditor.MediaUpload; -var useS = wp.element.useState; + var el = wp.element.createElement; + var Frag = wp.element.Fragment; + var reg = wp.blocks.registerBlockType; + var RT = wp.blockEditor.RichText; + var IC = wp.blockEditor.InspectorControls; + var IB = wp.blockEditor.InnerBlocks; + var PB = wp.components.PanelBody; + var TC = wp.components.TextControl; + var TA = wp.components.TextareaControl; + var SC = wp.components.SelectControl; + var TG = wp.components.ToggleControl; + var RC = wp.components.RangeControl; + var Btn = wp.components.Button; + var MUC = wp.blockEditor.MediaUploadCheck; + var MU = wp.blockEditor.MediaUpload; + var useS = wp.element.useState; -/* ── Simple array helpers (for pricing-card features) ────────────────── */ -function arrSet(arr, i, val) { var c = arr.slice(); c[i] = val; return c; } -function arrAdd(arr, val) { return arr.concat([val]); } -function arrRm(arr, i) { var c = arr.slice(); c.splice(i, 1); return c; } + /* ── Simple array helpers (for pricing-card features) ────────────────── */ + function arrSet(arr, i, val) { var c = arr.slice(); c[i] = val; return c; } + function arrAdd(arr, val) { return arr.concat([val]); } + function arrRm(arr, i) { var c = arr.slice(); c.splice(i, 1); return c; } -/* ── Shared card image attributes ────────────────────────────────────── */ -var CARD_IMAGE_ATTRS = { - imgId: { type: 'number', default: 0 }, - imgUrl: { type: 'string', default: '' }, - imgAlt: { type: 'string', default: '' }, - imgWidth: { type: 'number', default: 80 }, - imgHeight: { type: 'number', default: 0 }, - imgFit: { type: 'string', default: 'contain' }, - imgPosition: { type: 'string', default: 'top' } -}; - -/* ── Shared icon attributes ──────────────────────────────────────────── */ -var CARD_ICON_ATTRS = { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' } -}; - -/** - * Build icon InspectorControls: a toggle that switches between emoji and - * Font Awesome mode, with the appropriate text input shown beneath it. - */ - -/* ── Font Awesome icon catalogue ─────────────────────────────────────── */ -// Format: ['s' = fas solid | 'b' = fab brand, 'icon-name'] -var FA_ICONS = [ - // Navigation - ['s','angle-down'],['s','angle-left'],['s','angle-right'],['s','angle-up'], - ['s','angles-down'],['s','angles-left'],['s','angles-right'],['s','angles-up'], - ['s','arrow-down'],['s','arrow-left'],['s','arrow-right'],['s','arrow-up'], - ['s','arrow-rotate-left'],['s','arrow-rotate-right'],['s','arrows-rotate'], - ['s','arrows-up-down-left-right'],['s','caret-down'],['s','caret-left'], - ['s','caret-right'],['s','caret-up'],['s','chevron-down'],['s','chevron-left'], - ['s','chevron-right'],['s','chevron-up'],['s','circle-arrow-down'], - ['s','circle-arrow-left'],['s','circle-arrow-right'],['s','circle-arrow-up'], - // Interface - ['s','asterisk'],['s','at'],['s','ban'],['s','bars'],['s','bell'],['s','bell-slash'], - ['s','bolt'],['s','check'],['s','check-double'],['s','circle-check'], - ['s','circle-exclamation'],['s','circle-info'],['s','circle-minus'], - ['s','circle-plus'],['s','circle-question'],['s','circle-xmark'], - ['s','ellipsis'],['s','ellipsis-vertical'],['s','eye'],['s','eye-slash'], - ['s','filter'],['s','grip'],['s','grip-vertical'],['s','hashtag'],['s','key'], - ['s','lock'],['s','lock-open'],['s','magnifying-glass'],['s','minus'],['s','plus'], - ['s','power-off'],['s','rotate'],['s','shield'],['s','shield-check'], - ['s','shield-halved'],['s','sliders'],['s','sort'],['s','sort-down'],['s','sort-up'], - ['s','spinner'],['s','star'],['s','star-half-stroke'],['s','toggle-off'], - ['s','toggle-on'],['s','xmark'], - // Text editing - ['s','align-center'],['s','align-justify'],['s','align-left'],['s','align-right'], - ['s','bold'],['s','code'],['s','crop'],['s','eraser'],['s','highlight'], - ['s','indent'],['s','italic'],['s','link'],['s','link-slash'],['s','list'], - ['s','list-check'],['s','list-ol'],['s','list-ul'],['s','outdent'],['s','pen'], - ['s','pen-to-square'],['s','pencil'],['s','quote-left'],['s','quote-right'], - ['s','scissors'],['s','strikethrough'],['s','subscript'],['s','superscript'], - ['s','table'],['s','table-list'],['s','underline'], - // Files and documents - ['s','book'],['s','book-open'],['s','bookmark'],['s','box'],['s','box-archive'], - ['s','boxes-stacked'],['s','clipboard'],['s','clipboard-check'], - ['s','clipboard-list'],['s','copy'],['s','database'],['s','download'], - ['s','envelope'],['s','envelope-open'],['s','file'],['s','file-arrow-down'], - ['s','file-arrow-up'],['s','file-code'],['s','file-excel'],['s','file-image'], - ['s','file-lines'],['s','file-pdf'],['s','file-powerpoint'],['s','file-word'], - ['s','file-zipper'],['s','floppy-disk'],['s','folder'],['s','folder-minus'], - ['s','folder-open'],['s','folder-plus'],['s','inbox'],['s','newspaper'], - ['s','paper-plane'],['s','paperclip'],['s','print'],['s','share'], - ['s','share-nodes'],['s','upload'], - // Technology - ['s','barcode'],['s','bug'],['s','cloud'],['s','cloud-arrow-down'], - ['s','cloud-arrow-up'],['s','code-branch'],['s','cpu'],['s','desktop'], - ['s','display'],['s','ethernet'],['s','gear'],['s','gears'],['s','hard-drive'], - ['s','keyboard'],['s','laptop'],['s','memory'],['s','microchip'],['s','mobile'], - ['s','mobile-screen'],['s','network-wired'],['s','plug'],['s','qrcode'], - ['s','robot'],['s','satellite'],['s','satellite-dish'],['s','server'], - ['s','tablet'],['s','terminal'],['s','toolbox'],['s','wifi'],['s','wrench'], - // Media and communication - ['s','backward'],['s','backward-fast'],['s','camera'],['s','camera-retro'], - ['s','clapperboard'],['s','comment'],['s','comment-dots'],['s','comments'], - ['s','film'],['s','forward'],['s','forward-fast'],['s','headphones'], - ['s','message'],['s','microphone'],['s','microphone-slash'],['s','music'], - ['s','pause'],['s','phone'],['s','phone-slash'],['s','phone-volume'], - ['s','photo-film'],['s','play'],['s','radio'],['s','repeat'],['s','rss'], - ['s','shuffle'],['s','stop'],['s','tv'],['s','video'],['s','video-slash'], - ['s','voicemail'],['s','volume-high'],['s','volume-low'],['s','volume-xmark'], - // People - ['s','address-book'],['s','address-card'],['s','baby'],['s','child'], - ['s','circle-user'],['s','people-group'],['s','person'],['s','person-running'], - ['s','person-walking'],['s','thumbs-down'],['s','thumbs-up'],['s','user'], - ['s','user-check'],['s','user-group'],['s','user-minus'],['s','user-plus'], - ['s','user-slash'],['s','user-tie'],['s','users'], - // Location and maps - ['s','building'],['s','buildings'],['s','city'],['s','compass'], - ['s','earth-americas'],['s','earth-asia'],['s','earth-europe'],['s','flag'], - ['s','globe'],['s','house'],['s','location-dot'],['s','location-pin'], - ['s','map'],['s','map-pin'],['s','mountain'],['s','road'],['s','route'], - ['s','signs-post'], - // Business and finance - ['s','award'],['s','briefcase'],['s','bullhorn'],['s','bullseye'], - ['s','calendar'],['s','calendar-check'],['s','calendar-days'], - ['s','calendar-minus'],['s','calendar-plus'],['s','certificate'], - ['s','chart-bar'],['s','chart-column'],['s','chart-line'],['s','chart-pie'], - ['s','chart-simple'],['s','clock'],['s','coins'],['s','credit-card'], - ['s','crown'],['s','gem'],['s','gift'],['s','handshake'],['s','medal'], - ['s','money-bill'],['s','money-bill-wave'],['s','piggy-bank'],['s','receipt'], - ['s','stopwatch'],['s','suitcase'],['s','tag'],['s','tags'],['s','timer'], - ['s','trophy'],['s','truck'],['s','wallet'], - // Nature and weather - ['s','fire'],['s','fire-flame-curved'],['s','leaf'],['s','moon'], - ['s','seedling'],['s','snowflake'],['s','sun'],['s','thermometer'], - ['s','tree'],['s','water'],['s','wind'], - // Health and medical - ['s','bandage'],['s','bone'],['s','brain'],['s','dna'],['s','heart'], - ['s','heart-pulse'],['s','hospital'],['s','pills'],['s','stethoscope'], - ['s','syringe'],['s','tooth'],['s','virus'],['s','wheelchair'], - // Brands - ['b','android'],['b','angular'],['b','apple'],['b','aws'],['b','bitbucket'], - ['b','bootstrap'],['b','chrome'],['b','css3'],['b','discord'],['b','docker'], - ['b','dribbble'],['b','dropbox'],['b','facebook'],['b','facebook-f'], - ['b','figma'],['b','firefox'],['b','git'],['b','github'],['b','gitlab'], - ['b','google'],['b','html5'],['b','instagram'],['b','java'],['b','linkedin'], - ['b','linux'],['b','microsoft'],['b','node-js'],['b','npm'],['b','php'], - ['b','python'],['b','react'],['b','slack'],['b','square-js'],['b','tiktok'], - ['b','vuejs'],['b','whatsapp'],['b','windows'],['b','wordpress'], - ['b','x-twitter'],['b','youtube'] -]; - -/* ── Icon picker component ───────────────────────────────────────────── */ -function IconPicker(props) { - var value = props.value || ''; - var onChange = props.onChange; - var qs = useS(''); - var query = qs[0], setQuery = qs[1]; - - var lower = query.toLowerCase().replace(/\s+/g, '-'); - var filtered = query - ? FA_ICONS.filter(function(ic){ return ic[1].indexOf(lower) !== -1; }) - : FA_ICONS; - - return el('div', { className: 'oribi-icon-picker' }, - // Current selection row - value - ? el('div', { className: 'oribi-icon-current' }, - el('i', { className: value, 'aria-hidden': 'true' }), - el('span', { className: 'oribi-icon-current-label' }, value), - el('button', { - className: 'oribi-icon-clear', - type: 'button', - onClick: function(){ onChange(''); } - }, '\u2715 Clear') - ) - : null, - // Search input - el('input', { - type: 'search', - className: 'oribi-icon-search', - placeholder: 'Search ' + FA_ICONS.length + ' icons…', - value: query, - onChange: function(e){ setQuery(e.target.value); } - }), - // Result count badge (only when filtering) - query - ? el('div', { className: 'oribi-icon-count' }, filtered.length + ' result' + (filtered.length === 1 ? '' : 's')) - : null, - // Icon grid - el('div', { className: 'oribi-icon-grid' }, - filtered.length === 0 - ? el('div', { className: 'oribi-icon-empty' }, 'No icons found.') - : filtered.map(function(ic) { - var prefix = ic[0] === 'b' ? 'fab' : 'fas'; - var cls = prefix + ' fa-' + ic[1]; - var active = cls === value; - return el('button', { - key: cls, - type: 'button', - title: ic[1], - className: 'oribi-icon-cell' + (active ? ' is-active' : ''), - onClick: function(){ onChange(cls); } - }, - el('i', { className: cls, 'aria-hidden': 'true' }), - el('span', null, ic[1]) - ); - }) - ) - ); -} - -function iconControls(a, s) { - var useFa = a.iconType === 'fontawesome'; - return el(Frag, null, - el(TG, { - label: 'Use Font Awesome icon', - checked: useFa, - onChange: function(v){ s({ iconType: v ? 'fontawesome' : 'emoji' }); } - }), - useFa - ? el(IconPicker, { value: a.faIcon || '', onChange: function(v){ s({ faIcon: v }); } }) - : el(TC, { label: 'Icon (emoji)', value: a.icon || '', onChange: function(v){ s({ icon: v }); } }) - ); -} - -/** - * Return the element to render in the card preview for the current icon state. - * cssClass - the wrapper class, e.g. 'feature-icon' or 'value-icon' - * extraStyle - optional inline style object for the wrapper - */ -function iconPreview(a, cssClass, extraStyle) { - var useFa = a.iconType === 'fontawesome'; - var hasIcon = useFa ? !!a.faIcon : !!a.icon; - if (!hasIcon) return null; - var child = useFa - ? el('i', { className: a.faIcon || '', 'aria-hidden': 'true' }) - : a.icon; - return el('div', { className: cssClass, style: extraStyle || {} }, child); -} - -/** Build shared Card Image InspectorControls panel. */ -function cardImageControls(a, s) { - var imgW = a.imgWidth || 80; - var imgH = a.imgHeight || 0; - var imgPos = a.imgPosition || 'top'; - var imgFit = a.imgFit || 'contain'; - - return el(PB, { title: 'Card Image', initialOpen: false }, - el(MUC, null, - el(MU, { - onSelect: function(media){ s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, - allowedTypes: ['image'], - value: a.imgId || 0, - render: function(ref) { - return el(Frag, null, - a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, - el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), - el(Btn, { variant: 'link', isDestructive: true, onClick: function(){ s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') - ) : null, - el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, - a.imgUrl ? 'Replace image' : 'Select from Media Library') - ); - } - }) - ), - a.imgUrl ? el(Frag, null, - el(RC, { label: 'Width (px)', value: imgW, min: 20, max: 600, step: 4, - onChange: function(v){ s({ imgWidth: v }); } }), - el(RC, { label: 'Height (px) - 0 = auto', value: imgH, min: 0, max: 600, step: 4, - onChange: function(v){ s({ imgHeight: v }); } }), - el(TG, { label: 'Scale to fill (cover)', checked: imgFit === 'cover', - onChange: function(v){ s({ imgFit: v ? 'cover' : 'contain' }); } }), - el(SC, { label: 'Position', value: imgPos, options: [ - { label: 'Above content', value: 'top' }, - { label: 'Left of content', value: 'left' }, - { label: 'Replace icon', value: 'replace-icon' }, - { label: 'Background', value: 'background' } - ], onChange: function(v){ s({ imgPosition: v }); } }) - ) : null - ); -} - -/** Build an image preview element for the editor. */ -function cardImagePreview(a) { - if (!a.imgUrl) return null; - var imgW = a.imgWidth || 80; - var imgH = a.imgHeight || 0; - var imgFit = a.imgFit || 'contain'; - var style = { - width: imgW + 'px', maxWidth: '100%', - height: imgH > 0 ? imgH + 'px' : 'auto', - borderRadius: '4px', objectFit: imgFit, display: 'block' + /* ── Shared card image attributes ────────────────────────────────────── */ + var CARD_IMAGE_ATTRS = { + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 80 }, + imgHeight: { type: 'number', default: 0 }, + imgFit: { type: 'string', default: 'contain' }, + imgPosition: { type: 'string', default: 'top' } }; - return el('div', { className: 'oribi-card-img-wrap', style: { marginBottom: '1.25rem' } }, - el('img', { src: a.imgUrl, className: 'oribi-card-img oribi-card-img--' + imgFit, style: style }) - ); -} -/** Build a shared card section edit component. */ -function createCardSectionEdit(allowedBlocks, defaultTemplate, label) { - return function(props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Section Settings' }, - el(SC, { label: 'Background', value: a.variant, options: [ - { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } - ], onChange: function(v){s({variant:v});} }), - el(RC, { label: 'Columns', value: a.columns, min: 1, max: 4, onChange: function(v){s({columns:v});} }), - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'section-header' }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Section heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }) - ), - el('div', { className: 'grid-' + a.columns }, - el(IB, { - allowedBlocks: allowedBlocks, - template: defaultTemplate, - templateLock: false - }) - ) - ) - ) - ); + /* ── Shared icon attributes ──────────────────────────────────────────── */ + var CARD_ICON_ATTRS = { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' } }; -} -/** Standard section attributes for JS. */ -var SECTION_ATTRS = { - align: { type: 'string', default: 'full' }, - variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - columns: { type: 'number', default: 3 } -}; + /** + * Build icon InspectorControls: a toggle that switches between emoji and + * Font Awesome mode, with the appropriate text input shown beneath it. + */ -/* ═══════════════════════════════════════════════════════════════════════ - STANDALONE BLOCKS (unchanged architecture) - ═══════════════════════════════════════════════════════════════════════ */ + /* ── Font Awesome icon catalogue ─────────────────────────────────────── */ + // Format: ['s' = fas solid | 'b' = fab brand, 'icon-name'] + var FA_ICONS = [ + // Navigation + ['s', 'angle-down'], ['s', 'angle-left'], ['s', 'angle-right'], ['s', 'angle-up'], + ['s', 'angles-down'], ['s', 'angles-left'], ['s', 'angles-right'], ['s', 'angles-up'], + ['s', 'arrow-down'], ['s', 'arrow-left'], ['s', 'arrow-right'], ['s', 'arrow-up'], + ['s', 'arrow-rotate-left'], ['s', 'arrow-rotate-right'], ['s', 'arrows-rotate'], + ['s', 'arrows-up-down-left-right'], ['s', 'caret-down'], ['s', 'caret-left'], + ['s', 'caret-right'], ['s', 'caret-up'], ['s', 'chevron-down'], ['s', 'chevron-left'], + ['s', 'chevron-right'], ['s', 'chevron-up'], ['s', 'circle-arrow-down'], + ['s', 'circle-arrow-left'], ['s', 'circle-arrow-right'], ['s', 'circle-arrow-up'], + // Interface + ['s', 'asterisk'], ['s', 'at'], ['s', 'ban'], ['s', 'bars'], ['s', 'bell'], ['s', 'bell-slash'], + ['s', 'bolt'], ['s', 'check'], ['s', 'check-double'], ['s', 'circle-check'], + ['s', 'circle-exclamation'], ['s', 'circle-info'], ['s', 'circle-minus'], + ['s', 'circle-plus'], ['s', 'circle-question'], ['s', 'circle-xmark'], + ['s', 'ellipsis'], ['s', 'ellipsis-vertical'], ['s', 'eye'], ['s', 'eye-slash'], + ['s', 'filter'], ['s', 'grip'], ['s', 'grip-vertical'], ['s', 'hashtag'], ['s', 'key'], + ['s', 'lock'], ['s', 'lock-open'], ['s', 'magnifying-glass'], ['s', 'minus'], ['s', 'plus'], + ['s', 'power-off'], ['s', 'rotate'], ['s', 'shield'], ['s', 'shield-check'], + ['s', 'shield-halved'], ['s', 'sliders'], ['s', 'sort'], ['s', 'sort-down'], ['s', 'sort-up'], + ['s', 'spinner'], ['s', 'star'], ['s', 'star-half-stroke'], ['s', 'toggle-off'], + ['s', 'toggle-on'], ['s', 'xmark'], + // Text editing + ['s', 'align-center'], ['s', 'align-justify'], ['s', 'align-left'], ['s', 'align-right'], + ['s', 'bold'], ['s', 'code'], ['s', 'crop'], ['s', 'eraser'], ['s', 'highlight'], + ['s', 'indent'], ['s', 'italic'], ['s', 'link'], ['s', 'link-slash'], ['s', 'list'], + ['s', 'list-check'], ['s', 'list-ol'], ['s', 'list-ul'], ['s', 'outdent'], ['s', 'pen'], + ['s', 'pen-to-square'], ['s', 'pencil'], ['s', 'quote-left'], ['s', 'quote-right'], + ['s', 'scissors'], ['s', 'strikethrough'], ['s', 'subscript'], ['s', 'superscript'], + ['s', 'table'], ['s', 'table-list'], ['s', 'underline'], + // Files and documents + ['s', 'book'], ['s', 'book-open'], ['s', 'bookmark'], ['s', 'box'], ['s', 'box-archive'], + ['s', 'boxes-stacked'], ['s', 'clipboard'], ['s', 'clipboard-check'], + ['s', 'clipboard-list'], ['s', 'copy'], ['s', 'database'], ['s', 'download'], + ['s', 'envelope'], ['s', 'envelope-open'], ['s', 'file'], ['s', 'file-arrow-down'], + ['s', 'file-arrow-up'], ['s', 'file-code'], ['s', 'file-excel'], ['s', 'file-image'], + ['s', 'file-lines'], ['s', 'file-pdf'], ['s', 'file-powerpoint'], ['s', 'file-word'], + ['s', 'file-zipper'], ['s', 'floppy-disk'], ['s', 'folder'], ['s', 'folder-minus'], + ['s', 'folder-open'], ['s', 'folder-plus'], ['s', 'inbox'], ['s', 'newspaper'], + ['s', 'paper-plane'], ['s', 'paperclip'], ['s', 'print'], ['s', 'share'], + ['s', 'share-nodes'], ['s', 'upload'], + // Technology + ['s', 'barcode'], ['s', 'bug'], ['s', 'cloud'], ['s', 'cloud-arrow-down'], + ['s', 'cloud-arrow-up'], ['s', 'code-branch'], ['s', 'cpu'], ['s', 'desktop'], + ['s', 'display'], ['s', 'ethernet'], ['s', 'gear'], ['s', 'gears'], ['s', 'hard-drive'], + ['s', 'keyboard'], ['s', 'laptop'], ['s', 'memory'], ['s', 'microchip'], ['s', 'mobile'], + ['s', 'mobile-screen'], ['s', 'network-wired'], ['s', 'plug'], ['s', 'qrcode'], + ['s', 'robot'], ['s', 'satellite'], ['s', 'satellite-dish'], ['s', 'server'], + ['s', 'tablet'], ['s', 'terminal'], ['s', 'toolbox'], ['s', 'wifi'], ['s', 'wrench'], + // Media and communication + ['s', 'backward'], ['s', 'backward-fast'], ['s', 'camera'], ['s', 'camera-retro'], + ['s', 'clapperboard'], ['s', 'comment'], ['s', 'comment-dots'], ['s', 'comments'], + ['s', 'film'], ['s', 'forward'], ['s', 'forward-fast'], ['s', 'headphones'], + ['s', 'message'], ['s', 'microphone'], ['s', 'microphone-slash'], ['s', 'music'], + ['s', 'pause'], ['s', 'phone'], ['s', 'phone-slash'], ['s', 'phone-volume'], + ['s', 'photo-film'], ['s', 'play'], ['s', 'radio'], ['s', 'repeat'], ['s', 'rss'], + ['s', 'shuffle'], ['s', 'stop'], ['s', 'tv'], ['s', 'video'], ['s', 'video-slash'], + ['s', 'voicemail'], ['s', 'volume-high'], ['s', 'volume-low'], ['s', 'volume-xmark'], + // People + ['s', 'address-book'], ['s', 'address-card'], ['s', 'baby'], ['s', 'child'], + ['s', 'circle-user'], ['s', 'people-group'], ['s', 'person'], ['s', 'person-running'], + ['s', 'person-walking'], ['s', 'thumbs-down'], ['s', 'thumbs-up'], ['s', 'user'], + ['s', 'user-check'], ['s', 'user-group'], ['s', 'user-minus'], ['s', 'user-plus'], + ['s', 'user-slash'], ['s', 'user-tie'], ['s', 'users'], + // Location and maps + ['s', 'building'], ['s', 'buildings'], ['s', 'city'], ['s', 'compass'], + ['s', 'earth-americas'], ['s', 'earth-asia'], ['s', 'earth-europe'], ['s', 'flag'], + ['s', 'globe'], ['s', 'house'], ['s', 'location-dot'], ['s', 'location-pin'], + ['s', 'map'], ['s', 'map-pin'], ['s', 'mountain'], ['s', 'road'], ['s', 'route'], + ['s', 'signs-post'], + // Business and finance + ['s', 'award'], ['s', 'briefcase'], ['s', 'bullhorn'], ['s', 'bullseye'], + ['s', 'calendar'], ['s', 'calendar-check'], ['s', 'calendar-days'], + ['s', 'calendar-minus'], ['s', 'calendar-plus'], ['s', 'certificate'], + ['s', 'chart-bar'], ['s', 'chart-column'], ['s', 'chart-line'], ['s', 'chart-pie'], + ['s', 'chart-simple'], ['s', 'clock'], ['s', 'coins'], ['s', 'credit-card'], + ['s', 'crown'], ['s', 'gem'], ['s', 'gift'], ['s', 'handshake'], ['s', 'medal'], + ['s', 'money-bill'], ['s', 'money-bill-wave'], ['s', 'piggy-bank'], ['s', 'receipt'], + ['s', 'stopwatch'], ['s', 'suitcase'], ['s', 'tag'], ['s', 'tags'], ['s', 'timer'], + ['s', 'trophy'], ['s', 'truck'], ['s', 'wallet'], + // Nature and weather + ['s', 'fire'], ['s', 'fire-flame-curved'], ['s', 'leaf'], ['s', 'moon'], + ['s', 'seedling'], ['s', 'snowflake'], ['s', 'sun'], ['s', 'thermometer'], + ['s', 'tree'], ['s', 'water'], ['s', 'wind'], + // Health and medical + ['s', 'bandage'], ['s', 'bone'], ['s', 'brain'], ['s', 'dna'], ['s', 'heart'], + ['s', 'heart-pulse'], ['s', 'hospital'], ['s', 'pills'], ['s', 'stethoscope'], + ['s', 'syringe'], ['s', 'tooth'], ['s', 'virus'], ['s', 'wheelchair'], + // Brands + ['b', 'android'], ['b', 'angular'], ['b', 'apple'], ['b', 'aws'], ['b', 'bitbucket'], + ['b', 'bootstrap'], ['b', 'chrome'], ['b', 'css3'], ['b', 'discord'], ['b', 'docker'], + ['b', 'dribbble'], ['b', 'dropbox'], ['b', 'facebook'], ['b', 'facebook-f'], + ['b', 'figma'], ['b', 'firefox'], ['b', 'git'], ['b', 'github'], ['b', 'gitlab'], + ['b', 'google'], ['b', 'html5'], ['b', 'instagram'], ['b', 'java'], ['b', 'linkedin'], + ['b', 'linux'], ['b', 'microsoft'], ['b', 'node-js'], ['b', 'npm'], ['b', 'php'], + ['b', 'python'], ['b', 'react'], ['b', 'slack'], ['b', 'square-js'], ['b', 'tiktok'], + ['b', 'vuejs'], ['b', 'whatsapp'], ['b', 'windows'], ['b', 'wordpress'], + ['b', 'x-twitter'], ['b', 'youtube'] + ]; -/* 1. HERO ─────────────────────────────────────────────────────────────── */ -reg('oribi/hero', { - title: 'Oribi Hero', - icon: 'cover-image', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - highlightWord: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - primaryBtnText: { type: 'string', default: 'Get in Touch' }, - primaryBtnUrl: { type: 'string', default: '/contact' }, - secondaryBtnText: { type: 'string', default: '' }, - secondaryBtnUrl: { type: 'string', default: '' }, - stat1Value: { type: 'string', default: '' }, - stat1Label: { type: 'string', default: '' }, - stat2Value: { type: 'string', default: '' }, - stat2Label: { type: 'string', default: '' }, - svcLaptop1: { type: 'string', default: 'Data Backup' }, - svcLaptop2: { type: 'string', default: 'Endpoint Security' }, - svcLaptop3: { type: 'string', default: 'Patch Management' }, - svcCloud1: { type: 'string', default: 'Email Protection' }, - svcCloud2: { type: 'string', default: 'License Management' }, - svcCloud3: { type: 'string', default: 'Cloud Backup' }, - svcDesktop1: { type: 'string', default: 'Network Monitoring' }, - svcDesktop2: { type: 'string', default: 'Threat Detection' }, - svcDesktop3: { type: 'string', default: 'Cloud Management' }, - svcPhone1: { type: 'string', default: 'Mobile Security' }, - svcPhone2: { type: 'string', default: 'Data Encryption' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Highlight' }, - el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function(v){s({highlightWord:v});} }) - ), - el(PB, { title: 'Primary Button' }, - el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function(v){s({primaryBtnUrl:v});} }) - ), - el(PB, { title: 'Secondary Button', initialOpen: false }, - el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function(v){s({secondaryBtnUrl:v});} }) + /* ── Icon picker component ───────────────────────────────────────────── */ + function IconPicker(props) { + var value = props.value || ''; + var onChange = props.onChange; + var qs = useS(''); + var query = qs[0], setQuery = qs[1]; + + var lower = query.toLowerCase().replace(/\s+/g, '-'); + var filtered = query + ? FA_ICONS.filter(function (ic) { return ic[1].indexOf(lower) !== -1; }) + : FA_ICONS; + + return el('div', { className: 'oribi-icon-picker' }, + // Current selection row + value + ? el('div', { className: 'oribi-icon-current' }, + el('i', { className: value, 'aria-hidden': 'true' }), + el('span', { className: 'oribi-icon-current-label' }, value), + el('button', { + className: 'oribi-icon-clear', + type: 'button', + onClick: function () { onChange(''); } + }, '\u2715 Clear') ) - ), - el('section', { className: 'hero' }, - el('div', { className: 'container hero-inner' }, - el('div', { className: 'hero-content' }, - el(RT, { tagName: 'span', className: 'hero-label', value: a.label, - onChange: function(v){s({label:v});}, placeholder: '\u25CF Label text', allowedFormats: [] }), - el(RT, { tagName: 'h1', className: 'hero-title', value: a.title, - onChange: function(v){s({title:v});}, placeholder: 'Hero title...' }), - el(RT, { tagName: 'p', className: 'hero-description', value: a.description, - onChange: function(v){s({description:v});}, placeholder: 'Description...' }), - el('div', { className: 'btn-group' }, - el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, - onChange: function(v){s({primaryBtnText:v});}, placeholder: 'Button', allowedFormats: [] }), - el(RT, { tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, - onChange: function(v){s({secondaryBtnText:v});}, placeholder: 'Secondary button', allowedFormats: [] }) - ), - el('div', { className: 'hero-stats' }, - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, - onChange: function(v){s({stat1Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, - onChange: function(v){s({stat1Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) - ), - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, - onChange: function(v){s({stat2Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, - onChange: function(v){s({stat2Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) - ) - ) - ), - el('div', { className: 'hero-visual' }, - el('div', { className: 'hero-devices' }, - el('div', { className: 'hero-device hero-device--laptop', style: { opacity: 1 } }, - el('div', { className: 'hero-device__frame' }, - el('div', { className: 'hero-device__screen' }, - el('div', { className: 'hero-device__screen-content' }, - el('div', { className: 'hero-device__app-bars' }, el('div'),el('div'),el('div'),el('div')) - ) - ), - el('div', { className: 'hero-device__base' }) - ), - el('ul', { className: 'hero-device__services' }, - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Data Backup'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Endpoint Security'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Patch Management') - ) - ), - el('div', { className: 'hero-device hero-device--cloud', style: { opacity: 1 } }, - el('div', { className: 'hero-device__frame' }, - el('div', { className: 'hero-device__cloud-icon' }, - el('span', { className: 'hero-device__cloud-label' }, '365') - ) - ), - el('ul', { className: 'hero-device__services' }, - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Email Protection'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' License Management'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Cloud Backup') - ) - ), - el('div', { className: 'hero-device hero-device--desktop', style: { opacity: 1 } }, - el('div', { className: 'hero-device__frame' }, - el('div', { className: 'hero-device__screen' }, - el('div', { className: 'hero-device__screen-content' }, - el('div', { className: 'hero-device__dash-row' }, el('div',{className:'hero-device__dash-card'}), el('div',{className:'hero-device__dash-card'})), - el('div', { className: 'hero-device__dash-bar' }), - el('div', { className: 'hero-device__dash-bar hero-device__dash-bar--short' }) - ) - ), - el('div', { className: 'hero-device__stand' }), - el('div', { className: 'hero-device__stand-base' }) - ), - el('ul', { className: 'hero-device__services' }, - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Network Monitoring'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Threat Detection'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Cloud Management') - ) - ), - el('div', { className: 'hero-device hero-device--phone', style: { opacity: 1 } }, - el('div', { className: 'hero-device__frame' }, - el('div', { className: 'hero-device__screen' }, - el('div', { className: 'hero-device__screen-content' }, - el('div', { className: 'hero-device__notif' }, - el('span', { className: 'hero-device__notif-icon' }, '\u2713'), - el('span', { className: 'hero-device__notif-text' }, 'Secure') - ) - ) - ) - ), - el('ul', { className: 'hero-device__services' }, - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Mobile Security'), - el('li', { className: 'svc', style: { opacity: 1 } }, el('span',{className:'svc__dot'}), ' Data Encryption') - ) - ) - ) - ) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* 2. PAGE HERO ────────────────────────────────────────────────────────── */ -reg('oribi/page-hero', { - title: 'Oribi Page Hero', - icon: 'flag', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Settings' }, - el(TC, { label: 'Label (optional)', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: 'page-hero' }, - el('div', { className: 'container' }, - a.label ? el('span', { className: 'hero-label' }, a.label) : null, - el(RT, { tagName: 'h1', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Page title...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* 5. CTA BANNER ───────────────────────────────────────────────────────── */ -reg('oribi/cta-banner', { - title: 'Oribi CTA Banner', - icon: 'megaphone', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - heading: { type: 'string', default: '' }, - text: { type: 'string', default: '' }, - btnText: { type: 'string', default: '' }, - btnUrl: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Button' }, - el(TC, { label: 'URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }) - ) - ), - el('section', { className: 'cta-banner' }, - el('div', { className: 'container text-center' }, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'CTA heading...' }), - el(RT, { tagName: 'p', value: a.text, onChange: function(v){s({text:v});}, placeholder: 'CTA text...' }), - el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', style: { background: '#fff', color: 'var(--color-primary)' }, value: a.btnText, onChange: function(v){s({btnText:v});}, placeholder: 'Button text...' }) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* 6. INTRO SECTION ────────────────────────────────────────────────────── */ -reg('oribi/intro-section', { - title: 'Oribi Intro Section', - icon: 'id', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - visual: { type: 'string', default: '' }, - reversed: { type: 'boolean', default: false }, - imgId: { type: 'number', default: 0 }, - imgUrl: { type: 'string', default: '' }, - imgAlt: { type: 'string', default: '' }, - imgWidth: { type: 'number', default: 280 }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgW = a.imgWidth || 280; - var visualContent = a.imgUrl - ? el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '8px', objectFit: 'contain', display: 'block' } }) - : (a.visual || '\uD83D\uDCBB'); - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Settings' }, - el(SC, { label: 'Background', value: a.variant, options: [ - { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } - ], onChange: function(v){s({variant:v});} }), - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }), - el(TC, { label: 'Visual (emoji or text)', value: a.visual, onChange: function(v){s({visual:v});} }), - el(TG, { label: 'Reversed layout', checked: a.reversed, onChange: function(v){s({reversed:v});} }) - ), - el(PB, { title: 'Visual Image', initialOpen: false }, - el(MUC, null, - el(MU, { - onSelect: function(media){ s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, - allowedTypes: ['image'], - value: a.imgId || 0, - render: function(ref) { - return el(Frag, null, - a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, - el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), - el(Btn, { variant: 'link', isDestructive: true, onClick: function(){ s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') - ) : null, - el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, - a.imgUrl ? 'Replace image' : 'Select from Media Library') - ); - } - }) - ), - a.imgUrl ? el(RC, { label: 'Width (px)', value: imgW, min: 50, max: 420, step: 4, - onChange: function(v){ s({ imgWidth: v }); } }) : null - ) - ), - el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'about-intro', style: a.reversed ? { direction: 'rtl' } : {} }, - el('div', { style: a.reversed ? { direction: 'ltr' } : {} }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', style: { marginBottom: '1.5rem' }, value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ), - el('div', { className: 'about-intro-visual' + (a.imgUrl ? ' has-img' : ''), style: a.reversed ? { direction: 'ltr' } : {} }, visualContent) - ) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* 8. CONTACT SECTION ──────────────────────────────────────────────────── */ -reg('oribi/contact-section', { - title: 'Oribi Contact', - icon: 'email', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - heading: { type: 'string', default: "Let's Talk" }, - lead: { type: 'string', default: '' }, - email: { type: 'string', default: 'solutions@oribi-tech.com' }, - supportUrl: { type: 'string', default: '' }, - portalUrl: { type: 'string', default: '' }, - location: { type: 'string', default: 'Saratoga Springs, Upstate New York' }, - formHeading: { type: 'string', default: 'Want to Learn More?' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Contact Settings' }, - el(TC, { label: 'Email', value: a.email, onChange: function(v){s({email:v});} }), - el(TC, { label: 'Support URL', value: a.supportUrl, onChange: function(v){s({supportUrl:v});} }), - el(TC, { label: 'Portal URL', value: a.portalUrl, onChange: function(v){s({portalUrl:v});} }), - el(TC, { label: 'Location', value: a.location, onChange: function(v){s({location:v});} }), - el(TC, { label: 'Form Heading', value: a.formHeading, onChange: function(v){s({formHeading:v});} }) - ) - ), - el('section', { className: 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'contact-layout' }, - el('div', { className: 'contact-info' }, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }), - el('div', { className: 'contact-method' }, - el('div',{className:'contact-method-icon'},'\uD83D\uDCE7'), - el('div',null, el('div',{className:'contact-method-label'},'Email Us'), el('div',{className:'contact-method-value'}, a.email)) - ), - el('div', { className: 'contact-method' }, - el('div',{className:'contact-method-icon'},'\uD83C\uDFAB'), - el('div',null, el('div',{className:'contact-method-label'},'Support'), el('div',{className:'contact-method-value'}, 'Open a Support Ticket')) - ), - el('div', { className: 'contact-method' }, - el('div',{className:'contact-method-icon'},'\uD83C\uDF0E'), - el('div',null, el('div',{className:'contact-method-label'},'Client Portal'), el('div',{className:'contact-method-value'}, 'portal.oribi-tech.com')) - ), - el('div', { className: 'contact-method' }, - el('div',{className:'contact-method-icon'},'\uD83D\uDCCD'), - el('div',null, el('div',{className:'contact-method-label'},'Location'), el('div',{className:'contact-method-value'}, a.location)) - ) - ), - el('div', { className: 'contact-form-wrap' }, - el('h3', { style: { marginBottom: '1.5rem' } }, a.formHeading), - el('div', { style: { padding: '2rem', background: 'var(--color-bg-alt)', borderRadius: 'var(--radius-md)', textAlign: 'center', color: 'var(--color-text-muted)' } }, - 'Contact form renders on the live site' - ) - ) - ) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* ═══════════════════════════════════════════════════════════════════════ - CHILD BLOCKS (each renders one item inside a parent) - ═══════════════════════════════════════════════════════════════════════ */ - -/* ── Feature Card ─────────────────────────────────────────────────────── */ -reg('oribi/feature-card', { - title: 'Feature Card', - icon: 'screenoptions', - category: 'oribi', - parent: ['oribi/feature-section'], - supports: { html: false, reusable: false }, - attributes: Object.assign({}, { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - url: { type: 'string', default: '' }, - centered: { type: 'boolean', default: false } - }, CARD_IMAGE_ATTRS), - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgPos = a.imgPosition || 'top'; - var imgPrev = cardImagePreview(a); - var centeredStyle = a.centered ? { marginInline: 'auto' } : {}; - - var cardPreview; - if ( a.imgUrl && imgPos === 'left' ) { - cardPreview = el('div', { className: 'oribi-card img-left' }, - imgPrev, - el('div', { className: 'oribi-card-body' }, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Card title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Card description...' }) - ) - ); - } else if ( a.imgUrl && imgPos === 'background' ) { - cardPreview = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, - el('div', { className: 'oribi-card-body' }, - iconPreview(a, 'feature-icon', centeredStyle), - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Card title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Card description...' }) - ) - ); - } else { - var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; - cardPreview = el('div', { className: 'oribi-card' + (a.centered ? ' text-center' : '') + (a.imgUrl ? ' img-' + imgPos : '') }, - a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, - showIconPrev ? iconPreview(a, 'feature-icon', centeredStyle) : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Card title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Card description...' }) - ); - } - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s), - el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function(v){s({url:v});} }), - el(TG, { label: 'Centered', checked: !!a.centered, onChange: function(v){s({centered:v});} }) - ), - cardImageControls(a, s) - ), - cardPreview - ); - }, - save: function () { return null; } -}); - -/* ── Value Card ───────────────────────────────────────────────────────── */ -reg('oribi/value-card', { - title: 'Value Card', - icon: 'heart', - category: 'oribi', - parent: ['oribi/value-section'], - supports: { html: false, reusable: false }, - attributes: Object.assign({}, { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' } - }, CARD_IMAGE_ATTRS), - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgPos = a.imgPosition || 'top'; - var imgPrev = cardImagePreview(a); - var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; - - var body; - if ( a.imgUrl && imgPos === 'left' ) { - body = el('div', { className: 'oribi-card value-card img-left' }, - imgPrev, - el('div', { className: 'oribi-card-body' }, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ); - } else if ( a.imgUrl && imgPos === 'background' ) { - body = el('div', { className: 'oribi-card value-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, - el('div', { className: 'oribi-card-body' }, - showIconPrev ? iconPreview(a, 'value-icon') : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ); - } else { - body = el('div', { className: 'oribi-card value-card' + (a.imgUrl ? ' img-' + imgPos : '') }, - a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, - showIconPrev ? iconPreview(a, 'value-icon') : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ); - } - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s) - ), - cardImageControls(a, s) - ), - body - ); - }, - save: function () { return null; } -}); - -/* ── Addon Card ───────────────────────────────────────────────────────── */ -reg('oribi/addon-card', { - title: 'Addon Card', - icon: 'plus-alt2', - category: 'oribi', - parent: ['oribi/addon-section'], - supports: { html: false, reusable: false }, - attributes: Object.assign({}, { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - tag: { type: 'string', default: '' } - }, CARD_IMAGE_ATTRS), - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgPos = a.imgPosition || 'top'; - var imgPrev = cardImagePreview(a); - var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; - - var body; - if ( a.imgUrl && imgPos === 'left' ) { - body = el('div', { className: 'oribi-card img-left' }, - imgPrev, - el('div', { className: 'oribi-card-body' }, - a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ); - } else if ( a.imgUrl && imgPos === 'background' ) { - body = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, - el('div', { className: 'oribi-card-body' }, - showIconPrev ? iconPreview(a, 'feature-icon') : null, - a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ); - } else { - body = el('div', { className: 'oribi-card' + (a.imgUrl ? ' img-' + imgPos : '') }, - a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, - showIconPrev ? iconPreview(a, 'feature-icon') : null, - a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ); - } - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s), - el(TC, { label: 'Tag / Badge (optional)', value: a.tag || '', onChange: function(v){s({tag:v});} }) - ), - cardImageControls(a, s) - ), - body - ); - }, - save: function () { return null; } -}); - -/* ── Image Card ───────────────────────────────────────────────────────── */ -reg('oribi/image-card', { - title: 'Image Card', - icon: 'format-image', - category: 'oribi', - parent: ['oribi/image-section'], - supports: { html: false, reusable: false }, - attributes: Object.assign({}, { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - url: { type: 'string', default: '' } - }, CARD_IMAGE_ATTRS), - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgPos = a.imgPosition || 'top'; - var imgPrev = cardImagePreview(a); - var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s), - el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function(v){s({url:v});} }) - ), - cardImageControls(a, s) - ), - el('div', { className: 'oribi-card image-card' + (a.imgUrl ? ' img-' + imgPos : '') }, - a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, - showIconPrev ? iconPreview(a, 'feature-icon') : null, - el('div', { className: 'oribi-card-body' }, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* ── Stat Card ────────────────────────────────────────────────────────── */ -reg('oribi/stat-card', { - title: 'Stat Card', - icon: 'chart-bar', - category: 'oribi', - parent: ['oribi/stat-section'], - supports: { html: false, reusable: false }, - attributes: Object.assign({}, { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - value: { type: 'string', default: '' }, - label: { type: 'string', default: '' }, - description: { type: 'string', default: '' } - }, CARD_IMAGE_ATTRS), - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgPrev = cardImagePreview(a); - var imgPos = a.imgPosition || 'top'; - - var body; - if ( a.imgUrl && imgPos === 'background' ) { - body = el('div', { className: 'oribi-card stat-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, - el('div', { className: 'oribi-card-body' }, - iconPreview(a, 'feature-icon'), - el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function(v){s({value:v});}, placeholder: '99.9%' }), - el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function(v){s({label:v});}, placeholder: 'Label...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description (optional)...' }) - ) - ); - } else { - body = el('div', { className: 'oribi-card stat-card' + (a.imgUrl ? ' img-' + imgPos : '') }, - (a.imgUrl && imgPos !== 'replace-icon') ? imgPrev : null, - iconPreview(a, 'feature-icon'), - el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function(v){s({value:v});}, placeholder: '99.9%' }), - el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function(v){s({label:v});}, placeholder: 'Label...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description (optional)...' }) - ); - } - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s) - ), - cardImageControls(a, s) - ), - body - ); - }, - save: function () { return null; } -}); - -/* ── Link Card ────────────────────────────────────────────────────────── */ -reg('oribi/link-card', { - title: 'Link Card', - icon: 'admin-links', - category: 'oribi', - parent: ['oribi/link-section'], - supports: { html: false, reusable: false }, - attributes: Object.assign({}, { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - linkText: { type: 'string', default: '' }, - linkUrl: { type: 'string', default: '' } - }, CARD_IMAGE_ATTRS), - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgPos = a.imgPosition || 'top'; - var imgPrev = cardImagePreview(a); - var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; - - var body; - if ( a.imgUrl && imgPos === 'left' ) { - body = el('div', { className: 'oribi-card link-card img-left' }, - imgPrev, - el('div', { className: 'oribi-card-body' }, - showIconPrev ? iconPreview(a, 'feature-icon') : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }), - el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function(v){s({linkText:v});}, placeholder: 'Link text → ...' }) - ) - ); - } else if ( a.imgUrl && imgPos === 'background' ) { - body = el('div', { className: 'oribi-card link-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, - el('div', { className: 'oribi-card-body' }, - showIconPrev ? iconPreview(a, 'feature-icon') : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }), - el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function(v){s({linkText:v});}, placeholder: 'Link text → ...' }) - ) - ); - } else { - body = el('div', { className: 'oribi-card link-card' + (a.imgUrl ? ' img-' + imgPos : '') }, - (a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon')) ? imgPrev : null, - showIconPrev ? iconPreview(a, 'feature-icon') : null, - el(RT, { tagName: 'h3', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Title...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }), - el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function(v){s({linkText:v});}, placeholder: 'Link text → ...' }) - ); - } - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s), - el(TC, { label: 'Link URL', value: a.linkUrl || '', onChange: function(v){s({linkUrl:v});} }) - ), - cardImageControls(a, s) - ), - body - ); - }, - save: function () { return null; } -}); - -/* ── Pricing Card ─────────────────────────────────────────────────────── */ -reg('oribi/pricing-card', { - title: 'Pricing Card', - icon: 'money-alt', - category: 'oribi', - parent: ['oribi/pricing-section'], - supports: { html: false, reusable: false }, - attributes: { - icon: { type: 'string', default: '' }, - iconType: { type: 'string', default: 'emoji' }, - faIcon: { type: 'string', default: '' }, - name: { type: 'string', default: '' }, - tagline: { type: 'string', default: '' }, - price: { type: 'string', default: '' }, - pricePer: { type: 'string', default: '' }, - features: { type: 'array', default: [] }, - btnText: { type: 'string', default: 'Get Started' }, - btnUrl: { type: 'string', default: '/contact' }, - featured: { type: 'boolean', default: false }, - badge: { type: 'string', default: '' }, - imgId: { type: 'number', default: 0 }, - imgUrl: { type: 'string', default: '' }, - imgAlt: { type: 'string', default: '' }, - imgWidth: { type: 'number', default: 80 }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var features = a.features || []; - var imgW = a.imgWidth || 80; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Card Settings' }, - iconControls(a, s), - el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }), - el(TG, { label: 'Featured', checked: !!a.featured, onChange: function(v){s({featured:v});} }), - a.featured ? el(TC, { label: 'Badge Text', value: a.badge, onChange: function(v){s({badge:v});} }) : null - ), - el(PB, { title: 'Card Image', initialOpen: false }, - el(MUC, null, - el(MU, { - onSelect: function(media){ s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, - allowedTypes: ['image'], - value: a.imgId || 0, - render: function(ref) { - return el(Frag, null, - a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, - el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), - el(Btn, { variant: 'link', isDestructive: true, onClick: function(){ s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') - ) : null, - el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, - a.imgUrl ? 'Replace image' : 'Select from Media Library') - ); - } - }) - ), - a.imgUrl ? el(RC, { label: 'Width (px)', value: imgW, min: 20, max: 400, step: 4, - onChange: function(v){ s({ imgWidth: v }); } }) : null - ) - ), - el('div', { className: 'pricing-card' + (a.featured ? ' featured' : '') }, - a.featured && a.badge ? el(RT, { tagName: 'span', className: 'pricing-badge', value: a.badge, - onChange: function(v){s({badge:v});}, placeholder: 'Badge...' }) : null, - a.imgUrl ? el('div', { style: { textAlign: 'center', marginBottom: '1.25rem' } }, - el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain' } }) - ) : null, - iconPreview(a, 'feature-icon', { marginInline: 'auto' }), - el(RT, { tagName: 'div', className: 'pricing-name', value: a.name, - onChange: function(v){s({name:v});}, placeholder: 'Plan name...' }), - el(RT, { tagName: 'p', className: 'pricing-tagline', value: a.tagline, - onChange: function(v){s({tagline:v});}, placeholder: 'Tagline...' }), - a.price || a.pricePer ? el('div', { className: 'pricing-price' }, - el(RT, { tagName: 'div', className: 'pricing-amount', value: a.price || '', - onChange: function(v){s({price:v});}, placeholder: '$0' }), - el(RT, { tagName: 'div', className: 'pricing-per', value: a.pricePer || '', - onChange: function(v){s({pricePer:v});}, placeholder: 'per screen / month' }) - ) : el('div', { className: 'pricing-price' }, - el(RT, { tagName: 'div', className: 'pricing-amount', value: '', - onChange: function(v){s({price:v});}, placeholder: '$0' }), - el(RT, { tagName: 'div', className: 'pricing-per', value: '', - onChange: function(v){s({pricePer:v});}, placeholder: 'per screen / month' }) - ), - el('ul', { className: 'pricing-features' }, - features.map(function (f, fi) { - return el('li', { key: fi, style: { display: 'flex', alignItems: 'center', gap: '4px' } }, - el('span', { className: 'pricing-check' }, '\u2713'), - el(RT, { tagName: 'span', style: { flex: 1, minWidth: 0 }, value: f, - onChange: function(v){ s({features: arrSet(features, fi, v)}); }, placeholder: 'Feature...' }), - el(Btn, { isSmall: true, isDestructive: true, - style: { minWidth: '20px', padding: 0, height: '20px', flexShrink: 0 }, - onClick: function(){ s({features: arrRm(features, fi)}); } }, '\u2715') + : null, + // Search input + el('input', { + type: 'search', + className: 'oribi-icon-search', + placeholder: 'Search ' + FA_ICONS.length + ' icons…', + value: query, + onChange: function (e) { setQuery(e.target.value); } + }), + // Result count badge (only when filtering) + query + ? el('div', { className: 'oribi-icon-count' }, filtered.length + ' result' + (filtered.length === 1 ? '' : 's')) + : null, + // Icon grid + el('div', { className: 'oribi-icon-grid' }, + filtered.length === 0 + ? el('div', { className: 'oribi-icon-empty' }, 'No icons found.') + : filtered.map(function (ic) { + var prefix = ic[0] === 'b' ? 'fab' : 'fas'; + var cls = prefix + ' fa-' + ic[1]; + var active = cls === value; + return el('button', { + key: cls, + type: 'button', + title: ic[1], + className: 'oribi-icon-cell' + (active ? ' is-active' : ''), + onClick: function () { onChange(cls); } + }, + el('i', { className: cls, 'aria-hidden': 'true' }), + el('span', null, ic[1]) ); - }), - el('li', { style: { listStyle: 'none', marginTop: '4px' } }, - el(Btn, { isSmall: true, variant: 'secondary', - onClick: function(){ s({features: arrAdd(features, '')}); } }, '+ Feature') - ) - ), - el(RT, { tagName: 'span', - className: 'btn ' + (a.featured ? 'btn-primary' : 'btn-outline'), - style: { width: '100%', justifyContent: 'center', cursor: 'text' }, - value: a.btnText || '', - onChange: function(v){s({btnText:v});}, placeholder: 'Button text...' }) - ) - ); - }, - save: function () { return null; } -}); - -/* ── Platform Row ─────────────────────────────────────────────────────── */ -reg('oribi/platform-row', { - title: 'Platform Row', - icon: 'slides', - category: 'oribi', - parent: ['oribi/platform-section'], - supports: { html: false, reusable: false }, - attributes: { - heading: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - btnText: { type: 'string', default: 'Learn More' }, - btnUrl: { type: 'string', default: '' }, - visual: { type: 'string', default: '' }, - reversed: { type: 'boolean', default: false }, - imgId: { type: 'number', default: 0 }, - imgUrl: { type: 'string', default: '' }, - imgAlt: { type: 'string', default: '' }, - imgWidth: { type: 'number', default: 300 }, - isDashboard: { type: 'boolean', default: false }, - deviceAnim: { type: 'boolean', default: false }, - tvStick: { type: 'boolean', default: false }, - cameraAnim: { type: 'boolean', default: false }, - neverGoesDark: { type: 'boolean', default: false }, - brandedAnim: { type: 'boolean', default: false }, - galleryIds: { type: 'array', default: [] }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var imgW = a.imgWidth || 300; - - /* ── Animation HTML strings (mirror PHP render, used via dangerouslySetInnerHTML) ── */ - var DA_SCREEN = '
'; - var DA_HTML = ''; - - var TS_MI = '
'; - var TS_COL = '
' + TS_MI + TS_MI + TS_MI + '
'; - var TS_HTML = ''; - - var NGD_ROW = '
'; - var NGD_ROWH = '
'; - var NGD_HTML = ''; - - var BD_SPLASH = '
'; - var BD_HDR = '
'; - var BD_HTML = ''; - - var DB_HTML = '
PerformanceAPICacheDBQueueWorkerRequests/secReadWriteUpdateDeleteTraffic TrendDistribution
'; - - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Row Settings' }, - el(TC, { label: 'Visual (emoji)', value: a.visual, onChange: function(v){s({visual:v});} }), - el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }), - el(TG, { label: 'Reversed', checked: !!a.reversed, onChange: function(v){s({reversed:v});} }), - el(TG, { label: 'Dashboard Animation', checked: !!a.isDashboard, onChange: function(v){s({isDashboard:v});} }), - el(TG, { label: 'Device Animation', checked: !!a.deviceAnim, onChange: function(v){s({deviceAnim:v});} }), - el(TG, { label: 'TV Stick Animation', checked: !!a.tvStick, onChange: function(v){s({tvStick:v});} }), - el(TG, { label: 'Camera Animation', checked: !!a.cameraAnim, onChange: function(v){s({cameraAnim:v});} }), - el(TG, { label: 'Never Goes Dark', checked: !!a.neverGoesDark, onChange: function(v){s({neverGoesDark:v});} }), - el(TG, { label: 'Branded Display', checked: !!a.brandedAnim, onChange: function(v){s({brandedAnim:v});} }) - ), - el(PB, { title: 'Gallery TV Slideshow', initialOpen: false }, - el(MUC, null, - el(MU, { - onSelect: function(media) { - s({ galleryIds: media.map(function(m){ return m.id; }) }); - }, - allowedTypes: ['image'], - gallery: true, - multiple: true, - value: a.galleryIds || [], - render: function(ref) { - return el(Frag, null, - a.galleryIds && a.galleryIds.length - ? el('div', { style: { marginBottom: '8px' } }, - el('p', { style: { margin: '0 0 4px' } }, a.galleryIds.length + ' image(s) selected'), - el(Btn, { variant: 'link', isDestructive: true, onClick: function(){ s({ galleryIds: [] }); } }, 'Clear gallery') - ) - : null, - el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, - a.galleryIds && a.galleryIds.length ? 'Edit gallery' : 'Select images for TV slideshow') - ); - } - }) - ) - ), - el(PB, { title: 'Visual Image', initialOpen: false }, - el(MUC, null, - el(MU, { - onSelect: function(media){ s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, - allowedTypes: ['image'], - value: a.imgId || 0, - render: function(ref) { - return el(Frag, null, - a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, - el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), - el(Btn, { variant: 'link', isDestructive: true, onClick: function(){ s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') - ) : null, - el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, - a.imgUrl ? 'Replace image' : 'Select from Media Library') - ); - } - }) - ), - a.imgUrl ? el(RC, { label: 'Width (px)', value: imgW, min: 50, max: 600, step: 4, - onChange: function(v){ s({ imgWidth: v }); } }) : null - ) - ), - el('div', { className: 'platform-row' + (a.reversed ? ' reverse' : '') }, - el('div', { className: 'platform-text' }, - el(RT, { tagName: 'h3', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Service name...' }), - el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Service description...' }), - a.btnUrl ? el(RT, { tagName: 'span', className: 'btn btn-outline mt-3', - value: a.btnText, onChange: function(v){s({btnText:v});}, placeholder: 'Button...' }) : null - ), - a.isDashboard - ? el('div', { className: 'platform-visual has-dashboard', dangerouslySetInnerHTML: { __html: DB_HTML } }) - : a.cameraAnim - ? el('div', { className: 'platform-visual has-camera' }, - el('div', { className: 'cam-stage', 'aria-hidden': 'true' }, - // Photo camera (left) - el('div', { className: 'pc-wrap' }, - el('div', { className: 'pc-body' }, - el('div', { className: 'pc-flash-unit' }), - el('div', { className: 'pc-top' }, - el('div', { className: 'pc-shutter-btn' }), - el('div', { className: 'pc-viewfinder' }) - ), - el('div', { className: 'pc-front' }, - el('div', { className: 'pc-lens-ring' }, - el('div', { className: 'pc-lens-glass' }, - el('div', { className: 'pc-lens-reflex' }) - ) - ), - el('div', { className: 'pc-grip' }) - ) - ), - el('div', { className: 'pc-prints' }, - el('div', { className: 'pc-print pc-print--1', style: { opacity: '1', transform: 'rotate(-12deg) translateY(0)' } }, - el('div', { className: 'pc-print__img' }) - ) - ) - ), - // Centre scene - el('div', { className: 'cam-scene' }, - el('div', { className: 'cam-subject cam-subject--1' }), - el('div', { className: 'cam-flash-overlay' }), - el('div', { className: 'cam-vid-overlay' }) - ), - // Video camera on tripod (right) - el('div', { className: 'vc-wrap' }, - el('div', { className: 'vc-camera' }, - el('div', { className: 'vc-handle' }), - el('div', { className: 'vc-body' }, - el('div', { className: 'vc-lens-barrel' }, - el('div', { className: 'vc-lens-tip' }, - el('div', { className: 'vc-lens-glass' }, - el('div', { className: 'vc-lens-reflex' }) - ) - ) - ), - el('div', { className: 'vc-top-rail' }), - el('div', { className: 'vc-rec-light' }), - el('div', { className: 'vc-eyepiece' }) - ) - ), - el('div', { className: 'vc-tripod' }, - el('div', { className: 'vc-stem' }), - el('div', { className: 'vc-legs' }, - el('div', { className: 'vc-leg vc-leg--l' }), - el('div', { className: 'vc-leg vc-leg--c' }), - el('div', { className: 'vc-leg vc-leg--r' }) - ) - ) - ) - ) - ) - : a.deviceAnim - ? el('div', { className: 'platform-visual has-anim', dangerouslySetInnerHTML: { __html: DA_HTML } }) - : a.tvStick - ? el('div', { className: 'platform-visual has-tv-stick', dangerouslySetInnerHTML: { __html: TS_HTML } }) - : a.neverGoesDark - ? el('div', { className: 'platform-visual has-ngd', dangerouslySetInnerHTML: { __html: NGD_HTML } }) - : a.brandedAnim - ? el('div', { className: 'platform-visual has-branded', dangerouslySetInnerHTML: { __html: BD_HTML } }) - : a.imgUrl - ? el('div', { className: 'platform-visual has-img' }, - el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain', display: 'block', marginInline: 'auto' } }) - ) - : el('div', { className: 'platform-visual' }, a.visual || '\uD83D\uDCBB') - ) - ); - }, - save: function () { return null; } -}); - -/* ── Trust Item ───────────────────────────────────────────────────────── */ -reg('oribi/trust-item', { - title: 'Trust Item', - icon: 'shield', - category: 'oribi', - parent: ['oribi/trust-section'], - supports: { html: false, reusable: false }, - attributes: { - heading: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el('div', { className: 'trust-item' }, - el(RT, { tagName: 'h3', style: { marginBottom: '1rem' }, - value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Sub-heading...' }), - el(RT, { tagName: 'p', value: a.description, - onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ); - }, - save: function () { return null; } -}); - -/* ═══════════════════════════════════════════════════════════════════════ - PARENT BLOCKS (use InnerBlocks for child items) - ═══════════════════════════════════════════════════════════════════════ */ - -/* 3. FEATURE SECTION ──────────────────────────────────────────────────── */ -reg('oribi/feature-section', { - title: 'Oribi Feature Section', - icon: 'grid-view', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: SECTION_ATTRS, - edit: createCardSectionEdit(['oribi/feature-card'], [['oribi/feature-card', {}]], 'Feature Card'), - save: function () { return el(IB.Content); } -}); - -/* VALUE SECTION ────────────────────────────────────────────────────────── */ -reg('oribi/value-section', { - title: 'Oribi Value Section', - icon: 'heart', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: SECTION_ATTRS, - edit: createCardSectionEdit(['oribi/value-card'], [['oribi/value-card', {}]], 'Value Card'), - save: function () { return el(IB.Content); } -}); - -/* ADDON SECTION ────────────────────────────────────────────────────────── */ -reg('oribi/addon-section', { - title: 'Oribi Addon Section', - icon: 'plus-alt2', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: SECTION_ATTRS, - edit: createCardSectionEdit(['oribi/addon-card'], [['oribi/addon-card', {}]], 'Addon Card'), - save: function () { return el(IB.Content); } -}); - -/* IMAGE SECTION ────────────────────────────────────────────────────────── */ -reg('oribi/image-section', { - title: 'Oribi Image Section', - icon: 'format-image', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: SECTION_ATTRS, - edit: createCardSectionEdit(['oribi/image-card'], [['oribi/image-card', {}]], 'Image Card'), - save: function () { return el(IB.Content); } -}); - -/* STAT SECTION ─────────────────────────────────────────────────────────── */ -reg('oribi/stat-section', { - title: 'Oribi Stat Section', - icon: 'chart-bar', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: SECTION_ATTRS, - edit: createCardSectionEdit(['oribi/stat-card'], [['oribi/stat-card', {}]], 'Stat Card'), - save: function () { return el(IB.Content); } -}); - -/* LINK SECTION ─────────────────────────────────────────────────────────── */ -reg('oribi/link-section', { - title: 'Oribi Link Section', - icon: 'admin-links', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: SECTION_ATTRS, - edit: createCardSectionEdit(['oribi/link-card'], [['oribi/link-card', {}]], 'Link Card'), - save: function () { return el(IB.Content); } -}); - -/* 4. PRICING SECTION ──────────────────────────────────────────────────── */ -reg('oribi/pricing-section', { - title: 'Oribi Pricing', - icon: 'money-alt', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Section Settings' }, - el(SC, { label: 'Background', value: a.variant, options: [ - { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } - ], onChange: function(v){s({variant:v});} }), - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'section-header' }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Pricing heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }) - ), - el('div', { className: 'pricing-grid' }, - el(IB, { - allowedBlocks: ['oribi/pricing-card'], - template: [ - ['oribi/pricing-card', { name: 'Essentials' }], - ['oribi/pricing-card', { name: 'Pro', featured: true, badge: 'Most Popular' }], - ['oribi/pricing-card', { name: 'Enterprise' }] - ], - templateLock: false - }) - ) - ) - ) - ); - }, - save: function () { return el(IB.Content); } -}); - -/* 7. PLATFORM SECTION ─────────────────────────────────────────────────── */ -reg('oribi/platform-section', { - title: 'Oribi Platform Section', - icon: 'slides', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Section' }, - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'section-header' }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Section heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }) - ), - el(IB, { - allowedBlocks: ['oribi/platform-row'], - template: [['oribi/platform-row', {}]], - templateLock: false }) - ) ) ); - }, - save: function () { return el(IB.Content); } -}); + } -/* 9. TRUST SECTION ────────────────────────────────────────────────────── */ -reg('oribi/trust-section', { - title: 'Oribi Trust Section', - icon: 'shield', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - btnText: { type: 'string', default: '' }, - btnUrl: { type: 'string', default: '' }, - btnSub: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; + function iconControls(a, s) { + var useFa = a.iconType === 'fontawesome'; return el(Frag, null, - el(IC, null, - el(PB, { title: 'Settings' }, - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }), - el(TC, { label: 'Button Text', value: a.btnText, onChange: function(v){s({btnText:v});} }), - el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }), - el(TC, { label: 'Button Subtext', value: a.btnSub, onChange: function(v){s({btnSub:v});} }) - ) + el(TG, { + label: 'Use Font Awesome icon', + checked: useFa, + onChange: function (v) { s({ iconType: v ? 'fontawesome' : 'emoji' }); } + }), + useFa + ? el(IconPicker, { value: a.faIcon || '', onChange: function (v) { s({ faIcon: v }); } }) + : el(TC, { label: 'Icon (emoji)', value: a.icon || '', onChange: function (v) { s({ icon: v }); } }) + ); + } + + /** + * Return the element to render in the card preview for the current icon state. + * cssClass - the wrapper class, e.g. 'feature-icon' or 'value-icon' + * extraStyle - optional inline style object for the wrapper + */ + function iconPreview(a, cssClass, extraStyle) { + var useFa = a.iconType === 'fontawesome'; + var hasIcon = useFa ? !!a.faIcon : !!a.icon; + if (!hasIcon) return null; + var child = useFa + ? el('i', { className: a.faIcon || '', 'aria-hidden': 'true' }) + : a.icon; + return el('div', { className: cssClass, style: extraStyle || {} }, child); + } + + /** Build shared Card Image InspectorControls panel. */ + function cardImageControls(a, s) { + var imgW = a.imgWidth || 80; + var imgH = a.imgHeight || 0; + var imgPos = a.imgPosition || 'top'; + var imgFit = a.imgFit || 'contain'; + + return el(PB, { title: 'Card Image', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, + allowedTypes: ['image'], + value: a.imgId || 0, + render: function (ref) { + return el(Frag, null, + a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, + el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') + ) : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.imgUrl ? 'Replace image' : 'Select from Media Library') + ); + } + }) ), - el('section', { className: 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'section-header' }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }) - ), - el('div', { className: 'grid-2', style: { alignItems: 'center' } }, - el('div', { style: { display: 'flex', flexDirection: 'column', gap: '1.5rem' } }, + a.imgUrl ? el(Frag, null, + el(RC, { + label: 'Width (px)', value: imgW, min: 20, max: 600, step: 4, + onChange: function (v) { s({ imgWidth: v }); } + }), + el(RC, { + label: 'Height (px) - 0 = auto', value: imgH, min: 0, max: 600, step: 4, + onChange: function (v) { s({ imgHeight: v }); } + }), + el(TG, { + label: 'Scale to fill (cover)', checked: imgFit === 'cover', + onChange: function (v) { s({ imgFit: v ? 'cover' : 'contain' }); } + }), + el(SC, { + label: 'Position', value: imgPos, options: [ + { label: 'Above content', value: 'top' }, + { label: 'Left of content', value: 'left' }, + { label: 'Replace icon', value: 'replace-icon' }, + { label: 'Background', value: 'background' } + ], onChange: function (v) { s({ imgPosition: v }); } + }) + ) : null + ); + } + + /** Build an image preview element for the editor. */ + function cardImagePreview(a) { + if (!a.imgUrl) return null; + var imgW = a.imgWidth || 80; + var imgH = a.imgHeight || 0; + var imgFit = a.imgFit || 'contain'; + var style = { + width: imgW + 'px', maxWidth: '100%', + height: imgH > 0 ? imgH + 'px' : 'auto', + borderRadius: '4px', objectFit: imgFit, display: 'block' + }; + return el('div', { className: 'oribi-card-img-wrap', style: { marginBottom: '1.25rem' } }, + el('img', { src: a.imgUrl, className: 'oribi-card-img oribi-card-img--' + imgFit, style: style }) + ); + } + + /** Build a shared card section edit component. */ + function createCardSectionEdit(allowedBlocks, defaultTemplate, label) { + return function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Section Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(RC, { label: 'Columns', value: a.columns, min: 1, max: 4, onChange: function (v) { s({ columns: v }); } }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Section heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'grid-' + a.columns }, el(IB, { - allowedBlocks: ['oribi/trust-item'], - template: [['oribi/trust-item', {}]], + allowedBlocks: allowedBlocks, + template: defaultTemplate, templateLock: false }) - ), - el('div', { style: { textAlign: 'center' } }, - el('span', { className: 'btn btn-primary btn-lg' }, a.btnText || 'Button'), - a.btnSub ? el('p', { className: 'lead mt-2', style: { fontSize: '.9rem' } }, a.btnSub) : null ) ) ) - ) - ); - }, - save: function () { return el(IB.Content); } -}); + ); + }; + } -/* 10. FAQ SECTION ─────────────────────────────────────────────────────── */ -reg('oribi/faq-section', { - title: 'Oribi FAQ Section', - icon: 'editor-help', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, + /** Standard section attributes for JS. */ + var SECTION_ATTRS = { + align: { type: 'string', default: 'full' }, variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, + label: { type: 'string', default: '' }, heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Section Settings' }, - el(SC, { label: 'Background', value: a.variant, options: [ - { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } - ], onChange: function(v){s({variant:v});} }), - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'section-header' }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'FAQ heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }) + lead: { type: 'string', default: '' }, + columns: { type: 'number', default: 3 } + }; + + /* ═══════════════════════════════════════════════════════════════════════ + STANDALONE BLOCKS (unchanged architecture) + ═══════════════════════════════════════════════════════════════════════ */ + + /* 1. HERO ─────────────────────────────────────────────────────────────── */ + reg('oribi/hero', { + title: 'Oribi Hero', + icon: 'cover-image', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + highlightWord: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + primaryBtnText: { type: 'string', default: 'Get in Touch' }, + primaryBtnUrl: { type: 'string', default: '/contact' }, + secondaryBtnText: { type: 'string', default: '' }, + secondaryBtnUrl: { type: 'string', default: '' }, + stat1Value: { type: 'string', default: '' }, + stat1Label: { type: 'string', default: '' }, + stat2Value: { type: 'string', default: '' }, + stat2Label: { type: 'string', default: '' }, + svcLaptop1: { type: 'string', default: 'Data Backup' }, + svcLaptop2: { type: 'string', default: 'Endpoint Security' }, + svcLaptop3: { type: 'string', default: 'Patch Management' }, + svcCloud1: { type: 'string', default: 'Email Protection' }, + svcCloud2: { type: 'string', default: 'License Management' }, + svcCloud3: { type: 'string', default: 'Cloud Backup' }, + svcDesktop1: { type: 'string', default: 'Network Monitoring' }, + svcDesktop2: { type: 'string', default: 'Threat Detection' }, + svcDesktop3: { type: 'string', default: 'Cloud Management' }, + svcPhone1: { type: 'string', default: 'Mobile Security' }, + svcPhone2: { type: 'string', default: 'Data Encryption' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Highlight' }, + el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function (v) { s({ highlightWord: v }); } }) ), - el('div', { className: 'faq-list' }, + el(PB, { title: 'Primary Button' }, + el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function (v) { s({ primaryBtnUrl: v }); } }) + ), + el(PB, { title: 'Secondary Button', initialOpen: false }, + el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function (v) { s({ secondaryBtnUrl: v }); } }) + ) + ), + el('section', { className: 'hero' }, + el('div', { className: 'container hero-inner' }, + el('div', { className: 'hero-content' }, + el(RT, { + tagName: 'span', className: 'hero-label', value: a.label, + onChange: function (v) { s({ label: v }); }, placeholder: '\u25CF Label text', allowedFormats: [] + }), + el(RT, { + tagName: 'h1', className: 'hero-title', value: a.title, + onChange: function (v) { s({ title: v }); }, placeholder: 'Hero title...' + }), + el(RT, { + tagName: 'p', className: 'hero-description', value: a.description, + onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' + }), + el('div', { className: 'btn-group' }, + el(RT, { + tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, + onChange: function (v) { s({ primaryBtnText: v }); }, placeholder: 'Button', allowedFormats: [] + }), + el(RT, { + tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, + onChange: function (v) { s({ secondaryBtnText: v }); }, placeholder: 'Secondary button', allowedFormats: [] + }) + ), + el('div', { className: 'hero-stats' }, + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, + onChange: function (v) { s({ stat1Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, + onChange: function (v) { s({ stat1Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ), + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, + onChange: function (v) { s({ stat2Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, + onChange: function (v) { s({ stat2Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ) + ) + ), + el('div', { className: 'hero-visual' }, + el('div', { className: 'hero-devices' }, + el('div', { className: 'hero-device hero-device--laptop', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__screen' }, + el('div', { className: 'hero-device__screen-content' }, + el('div', { className: 'hero-device__app-bars' }, el('div'), el('div'), el('div'), el('div')) + ) + ), + el('div', { className: 'hero-device__base' }) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Data Backup'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Endpoint Security'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Patch Management') + ) + ), + el('div', { className: 'hero-device hero-device--cloud', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__cloud-icon' }, + el('span', { className: 'hero-device__cloud-label' }, '365') + ) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Email Protection'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' License Management'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Cloud Backup') + ) + ), + el('div', { className: 'hero-device hero-device--desktop', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__screen' }, + el('div', { className: 'hero-device__screen-content' }, + el('div', { className: 'hero-device__dash-row' }, el('div', { className: 'hero-device__dash-card' }), el('div', { className: 'hero-device__dash-card' })), + el('div', { className: 'hero-device__dash-bar' }), + el('div', { className: 'hero-device__dash-bar hero-device__dash-bar--short' }) + ) + ), + el('div', { className: 'hero-device__stand' }), + el('div', { className: 'hero-device__stand-base' }) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Network Monitoring'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Threat Detection'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Cloud Management') + ) + ), + el('div', { className: 'hero-device hero-device--phone', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__screen' }, + el('div', { className: 'hero-device__screen-content' }, + el('div', { className: 'hero-device__notif' }, + el('span', { className: 'hero-device__notif-icon' }, '\u2713'), + el('span', { className: 'hero-device__notif-text' }, 'Secure') + ) + ) + ) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Mobile Security'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Data Encryption') + ) + ) + ) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 2. PAGE HERO ────────────────────────────────────────────────────────── */ + reg('oribi/page-hero', { + title: 'Oribi Page Hero', + icon: 'flag', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(TC, { label: 'Label (optional)', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: 'page-hero' }, + el('div', { className: 'container' }, + a.label ? el('span', { className: 'hero-label' }, a.label) : null, + el(RT, { tagName: 'h1', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Page title...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 5. CTA BANNER ───────────────────────────────────────────────────────── */ + reg('oribi/cta-banner', { + title: 'Oribi CTA Banner', + icon: 'megaphone', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + heading: { type: 'string', default: '' }, + text: { type: 'string', default: '' }, + btnText: { type: 'string', default: '' }, + btnUrl: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Button' }, + el(TC, { label: 'URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }) + ) + ), + el('section', { className: 'cta-banner' }, + el('div', { className: 'container text-center' }, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'CTA heading...' }), + el(RT, { tagName: 'p', value: a.text, onChange: function (v) { s({ text: v }); }, placeholder: 'CTA text...' }), + el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', style: { background: '#fff', color: 'var(--color-primary)' }, value: a.btnText, onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button text...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 6. INTRO SECTION ────────────────────────────────────────────────────── */ + reg('oribi/intro-section', { + title: 'Oribi Intro Section', + icon: 'id', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + visual: { type: 'string', default: '' }, + reversed: { type: 'boolean', default: false }, + cloudAnim: { type: 'boolean', default: false }, + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 280 }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgW = a.imgWidth || 280; + + var animHtml = null; + if (a.cloudAnim) { + animHtml = el('div', { className: 'ds-anim-container' }, + el('div', { className: 'ds-tv' }, + el('i', { className: 'fas fa-tv', 'aria-hidden': 'true' }), + el('div', { className: 'ds-tv-screen' }) + ), + el('div', { className: 'ds-line' }, + el('div', { className: 'ds-packet ds-packet-1' }), + el('div', { className: 'ds-packet ds-packet-2' }), + el('div', { className: 'ds-packet ds-packet-3' }) + ), + el('div', { className: 'ds-cloud' }, + el('i', { className: 'fas fa-cloud', 'aria-hidden': 'true' }) + ) + ); + } + + var visualContent = a.cloudAnim ? animHtml : (a.imgUrl + ? el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '8px', objectFit: 'contain', display: 'block' } }) + : (a.visual || '\uD83D\uDCBB')); + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }), + el(TC, { label: 'Visual (emoji or text)', value: a.visual, onChange: function (v) { s({ visual: v }); } }), + el(TG, { label: 'Reversed layout', checked: a.reversed, onChange: function (v) { s({ reversed: v }); } }), + el(TG, { label: 'Cloud Server Animation', checked: a.cloudAnim, onChange: function (v) { s({ cloudAnim: v }); } }) + ), + el(PB, { title: 'Visual Image', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, + allowedTypes: ['image'], + value: a.imgId || 0, + render: function (ref) { + return el(Frag, null, + a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, + el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') + ) : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.imgUrl ? 'Replace image' : 'Select from Media Library') + ); + } + }) + ), + a.imgUrl ? el(RC, { + label: 'Width (px)', value: imgW, min: 50, max: 420, step: 4, + onChange: function (v) { s({ imgWidth: v }); } + }) : null + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'about-intro', style: a.reversed ? { direction: 'rtl' } : {} }, + el('div', { style: a.reversed ? { direction: 'ltr' } : {} }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', style: { marginBottom: '1.5rem' }, value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ), + el('div', { className: 'about-intro-visual' + (a.cloudAnim ? ' has-cloud-anim' : (a.imgUrl ? ' has-img' : '')), style: a.reversed ? { direction: 'ltr' } : {} }, visualContent) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 8. CONTACT SECTION ──────────────────────────────────────────────────── */ + reg('oribi/contact-section', { + title: 'Oribi Contact', + icon: 'email', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + heading: { type: 'string', default: "Let's Talk" }, + lead: { type: 'string', default: '' }, + email: { type: 'string', default: 'solutions@oribi-tech.com' }, + supportUrl: { type: 'string', default: '' }, + portalUrl: { type: 'string', default: '' }, + location: { type: 'string', default: 'Saratoga Springs, Upstate New York' }, + formHeading: { type: 'string', default: 'Want to Learn More?' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Contact Settings' }, + el(TC, { label: 'Email', value: a.email, onChange: function (v) { s({ email: v }); } }), + el(TC, { label: 'Support URL', value: a.supportUrl, onChange: function (v) { s({ supportUrl: v }); } }), + el(TC, { label: 'Portal URL', value: a.portalUrl, onChange: function (v) { s({ portalUrl: v }); } }), + el(TC, { label: 'Location', value: a.location, onChange: function (v) { s({ location: v }); } }), + el(TC, { label: 'Form Heading', value: a.formHeading, onChange: function (v) { s({ formHeading: v }); } }) + ) + ), + el('section', { className: 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'contact-layout' }, + el('div', { className: 'contact-info' }, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83D\uDCE7'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Email Us'), el('div', { className: 'contact-method-value' }, a.email)) + ), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83C\uDFAB'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Support'), el('div', { className: 'contact-method-value' }, 'Open a Support Ticket')) + ), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83C\uDF0E'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Client Portal'), el('div', { className: 'contact-method-value' }, 'portal.oribi-tech.com')) + ), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83D\uDCCD'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Location'), el('div', { className: 'contact-method-value' }, a.location)) + ) + ), + el('div', { className: 'contact-form-wrap' }, + el('h3', { style: { marginBottom: '1.5rem' } }, a.formHeading), + el('div', { style: { padding: '2rem', background: 'var(--color-bg-alt)', borderRadius: 'var(--radius-md)', textAlign: 'center', color: 'var(--color-text-muted)' } }, + 'Contact form renders on the live site' + ) + ) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + CHILD BLOCKS (each renders one item inside a parent) + ═══════════════════════════════════════════════════════════════════════ */ + + /* ── Feature Card ─────────────────────────────────────────────────────── */ + reg('oribi/feature-card', { + title: 'Feature Card', + icon: 'screenoptions', + category: 'oribi', + parent: ['oribi/feature-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + url: { type: 'string', default: '' }, + centered: { type: 'boolean', default: false } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var centeredStyle = a.centered ? { marginInline: 'auto' } : {}; + + var cardPreview; + if (a.imgUrl && imgPos === 'left') { + cardPreview = el('div', { className: 'oribi-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + cardPreview = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + iconPreview(a, 'feature-icon', centeredStyle), + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) + ) + ); + } else { + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + cardPreview = el('div', { className: 'oribi-card' + (a.centered ? ' text-center' : '') + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon', centeredStyle) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function (v) { s({ url: v }); } }), + el(TG, { label: 'Centered', checked: !!a.centered, onChange: function (v) { s({ centered: v }); } }) + ), + cardImageControls(a, s) + ), + cardPreview + ); + }, + save: function () { return null; } + }); + + /* ── Value Card ───────────────────────────────────────────────────────── */ + reg('oribi/value-card', { + title: 'Value Card', + icon: 'heart', + category: 'oribi', + parent: ['oribi/value-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + var body; + if (a.imgUrl && imgPos === 'left') { + body = el('div', { className: 'oribi-card value-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card value-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'value-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card value-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'value-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Addon Card ───────────────────────────────────────────────────────── */ + reg('oribi/addon-card', { + title: 'Addon Card', + icon: 'plus-alt2', + category: 'oribi', + parent: ['oribi/addon-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + tag: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + var body; + if (a.imgUrl && imgPos === 'left') { + body = el('div', { className: 'oribi-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'Tag / Badge (optional)', value: a.tag || '', onChange: function (v) { s({ tag: v }); } }) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Image Card ───────────────────────────────────────────────────────── */ + reg('oribi/image-card', { + title: 'Image Card', + icon: 'format-image', + category: 'oribi', + parent: ['oribi/image-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + url: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function (v) { s({ url: v }); } }) + ), + cardImageControls(a, s) + ), + el('div', { className: 'oribi-card image-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el('div', { className: 'oribi-card-body' }, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ── Stat Card ────────────────────────────────────────────────────────── */ + reg('oribi/stat-card', { + title: 'Stat Card', + icon: 'chart-bar', + category: 'oribi', + parent: ['oribi/stat-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + value: { type: 'string', default: '' }, + label: { type: 'string', default: '' }, + description: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPrev = cardImagePreview(a); + var imgPos = a.imgPosition || 'top'; + + var body; + if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card stat-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + iconPreview(a, 'feature-icon'), + el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function (v) { s({ value: v }); }, placeholder: '99.9%' }), + el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: 'Label...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description (optional)...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card stat-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + (a.imgUrl && imgPos !== 'replace-icon') ? imgPrev : null, + iconPreview(a, 'feature-icon'), + el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function (v) { s({ value: v }); }, placeholder: '99.9%' }), + el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: 'Label...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description (optional)...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Link Card ────────────────────────────────────────────────────────── */ + reg('oribi/link-card', { + title: 'Link Card', + icon: 'admin-links', + category: 'oribi', + parent: ['oribi/link-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + linkText: { type: 'string', default: '' }, + linkUrl: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + var body; + if (a.imgUrl && imgPos === 'left') { + body = el('div', { className: 'oribi-card link-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), + el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card link-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), + el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card link-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + (a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon')) ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), + el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'Link URL', value: a.linkUrl || '', onChange: function (v) { s({ linkUrl: v }); } }) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Pricing Card ─────────────────────────────────────────────────────── */ + reg('oribi/pricing-card', { + title: 'Pricing Card', + icon: 'money-alt', + category: 'oribi', + parent: ['oribi/pricing-section'], + supports: { html: false, reusable: false }, + attributes: { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + name: { type: 'string', default: '' }, + tagline: { type: 'string', default: '' }, + price: { type: 'string', default: '' }, + pricePer: { type: 'string', default: '' }, + features: { type: 'array', default: [] }, + btnText: { type: 'string', default: 'Get Started' }, + btnUrl: { type: 'string', default: '/contact' }, + featured: { type: 'boolean', default: false }, + badge: { type: 'string', default: '' }, + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 80 }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var features = a.features || []; + var imgW = a.imgWidth || 80; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }), + el(TG, { label: 'Featured', checked: !!a.featured, onChange: function (v) { s({ featured: v }); } }), + a.featured ? el(TC, { label: 'Badge Text', value: a.badge, onChange: function (v) { s({ badge: v }); } }) : null + ), + el(PB, { title: 'Card Image', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, + allowedTypes: ['image'], + value: a.imgId || 0, + render: function (ref) { + return el(Frag, null, + a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, + el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') + ) : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.imgUrl ? 'Replace image' : 'Select from Media Library') + ); + } + }) + ), + a.imgUrl ? el(RC, { + label: 'Width (px)', value: imgW, min: 20, max: 400, step: 4, + onChange: function (v) { s({ imgWidth: v }); } + }) : null + ) + ), + el('div', { className: 'pricing-card' + (a.featured ? ' featured' : '') }, + a.featured && a.badge ? el(RT, { + tagName: 'span', className: 'pricing-badge', value: a.badge, + onChange: function (v) { s({ badge: v }); }, placeholder: 'Badge...' + }) : null, + a.imgUrl ? el('div', { style: { textAlign: 'center', marginBottom: '1.25rem' } }, + el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain' } }) + ) : null, + iconPreview(a, 'feature-icon', { marginInline: 'auto' }), + el(RT, { + tagName: 'div', className: 'pricing-name', value: a.name, + onChange: function (v) { s({ name: v }); }, placeholder: 'Plan name...' + }), + el(RT, { + tagName: 'p', className: 'pricing-tagline', value: a.tagline, + onChange: function (v) { s({ tagline: v }); }, placeholder: 'Tagline...' + }), + a.price || a.pricePer ? el('div', { className: 'pricing-price' }, + el(RT, { + tagName: 'div', className: 'pricing-amount', value: a.price || '', + onChange: function (v) { s({ price: v }); }, placeholder: '$0' + }), + el(RT, { + tagName: 'div', className: 'pricing-per', value: a.pricePer || '', + onChange: function (v) { s({ pricePer: v }); }, placeholder: 'per screen / month' + }) + ) : el('div', { className: 'pricing-price' }, + el(RT, { + tagName: 'div', className: 'pricing-amount', value: '', + onChange: function (v) { s({ price: v }); }, placeholder: '$0' + }), + el(RT, { + tagName: 'div', className: 'pricing-per', value: '', + onChange: function (v) { s({ pricePer: v }); }, placeholder: 'per screen / month' + }) + ), + el('ul', { className: 'pricing-features' }, + features.map(function (f, fi) { + return el('li', { key: fi, style: { display: 'flex', alignItems: 'center', gap: '4px' } }, + el('span', { className: 'pricing-check' }, '\u2713'), + el(RT, { + tagName: 'span', style: { flex: 1, minWidth: 0 }, value: f, + onChange: function (v) { s({ features: arrSet(features, fi, v) }); }, placeholder: 'Feature...' + }), + el(Btn, { + isSmall: true, isDestructive: true, + style: { minWidth: '20px', padding: 0, height: '20px', flexShrink: 0 }, + onClick: function () { s({ features: arrRm(features, fi) }); } + }, '\u2715') + ); + }), + el('li', { style: { listStyle: 'none', marginTop: '4px' } }, + el(Btn, { + isSmall: true, variant: 'secondary', + onClick: function () { s({ features: arrAdd(features, '') }); } + }, '+ Feature') + ) + ), + el(RT, { + tagName: 'span', + className: 'btn ' + (a.featured ? 'btn-primary' : 'btn-outline'), + style: { width: '100%', justifyContent: 'center', cursor: 'text' }, + value: a.btnText || '', + onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button text...' + }) + ) + ); + }, + save: function () { return null; } + }); + + /* ── Platform Row ─────────────────────────────────────────────────────── */ + reg('oribi/platform-row', { + title: 'Platform Row', + icon: 'slides', + category: 'oribi', + parent: ['oribi/platform-section'], + supports: { html: false, reusable: false }, + attributes: { + heading: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + btnText: { type: 'string', default: 'Learn More' }, + btnUrl: { type: 'string', default: '' }, + visual: { type: 'string', default: '' }, + reversed: { type: 'boolean', default: false }, + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 300 }, + isDashboard: { type: 'boolean', default: false }, + deviceAnim: { type: 'boolean', default: false }, + tvStick: { type: 'boolean', default: false }, + cameraAnim: { type: 'boolean', default: false }, + neverGoesDark: { type: 'boolean', default: false }, + brandedAnim: { type: 'boolean', default: false }, + galleryIds: { type: 'array', default: [] }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgW = a.imgWidth || 300; + + /* ── Animation HTML strings (mirror PHP render, used via dangerouslySetInnerHTML) ── */ + var DA_SCREEN = '
'; + var DA_HTML = ''; + + var TS_MI = '
'; + var TS_COL = '
' + TS_MI + TS_MI + TS_MI + '
'; + var TS_HTML = ''; + + var NGD_ROW = '
'; + var NGD_ROWH = '
'; + var NGD_HTML = ''; + + var BD_SPLASH = '
'; + var BD_HDR = '
'; + var BD_HTML = ''; + + var DB_HTML = '
PerformanceAPICacheDBQueueWorkerRequests/secReadWriteUpdateDeleteTraffic TrendDistribution
'; + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Row Settings' }, + el(TC, { label: 'Visual (emoji)', value: a.visual, onChange: function (v) { s({ visual: v }); } }), + el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }), + el(TG, { label: 'Reversed', checked: !!a.reversed, onChange: function (v) { s({ reversed: v }); } }), + el(TG, { label: 'Dashboard Animation', checked: !!a.isDashboard, onChange: function (v) { s({ isDashboard: v }); } }), + el(TG, { label: 'Device Animation', checked: !!a.deviceAnim, onChange: function (v) { s({ deviceAnim: v }); } }), + el(TG, { label: 'TV Stick Animation', checked: !!a.tvStick, onChange: function (v) { s({ tvStick: v }); } }), + el(TG, { label: 'Camera Animation', checked: !!a.cameraAnim, onChange: function (v) { s({ cameraAnim: v }); } }), + el(TG, { label: 'Never Goes Dark', checked: !!a.neverGoesDark, onChange: function (v) { s({ neverGoesDark: v }); } }), + el(TG, { label: 'Branded Display', checked: !!a.brandedAnim, onChange: function (v) { s({ brandedAnim: v }); } }) + ), + el(PB, { title: 'Gallery TV Slideshow', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { + s({ galleryIds: media.map(function (m) { return m.id; }) }); + }, + allowedTypes: ['image'], + gallery: true, + multiple: true, + value: a.galleryIds || [], + render: function (ref) { + return el(Frag, null, + a.galleryIds && a.galleryIds.length + ? el('div', { style: { marginBottom: '8px' } }, + el('p', { style: { margin: '0 0 4px' } }, a.galleryIds.length + ' image(s) selected'), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ galleryIds: [] }); } }, 'Clear gallery') + ) + : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.galleryIds && a.galleryIds.length ? 'Edit gallery' : 'Select images for TV slideshow') + ); + } + }) + ) + ), + el(PB, { title: 'Visual Image', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, + allowedTypes: ['image'], + value: a.imgId || 0, + render: function (ref) { + return el(Frag, null, + a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, + el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') + ) : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.imgUrl ? 'Replace image' : 'Select from Media Library') + ); + } + }) + ), + a.imgUrl ? el(RC, { + label: 'Width (px)', value: imgW, min: 50, max: 600, step: 4, + onChange: function (v) { s({ imgWidth: v }); } + }) : null + ) + ), + el('div', { className: 'platform-row' + (a.reversed ? ' reverse' : '') }, + el('div', { className: 'platform-text' }, + el(RT, { tagName: 'h3', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Service name...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Service description...' }), + a.btnUrl ? el(RT, { + tagName: 'span', className: 'btn btn-outline mt-3', + value: a.btnText, onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button...' + }) : null + ), + a.isDashboard + ? el('div', { className: 'platform-visual has-dashboard', dangerouslySetInnerHTML: { __html: DB_HTML } }) + : a.cameraAnim + ? el('div', { className: 'platform-visual has-camera' }, + el('div', { className: 'cam-stage', 'aria-hidden': 'true' }, + // Photo camera (left) + el('div', { className: 'pc-wrap' }, + el('div', { className: 'pc-body' }, + el('div', { className: 'pc-flash-unit' }), + el('div', { className: 'pc-top' }, + el('div', { className: 'pc-shutter-btn' }), + el('div', { className: 'pc-viewfinder' }) + ), + el('div', { className: 'pc-front' }, + el('div', { className: 'pc-lens-ring' }, + el('div', { className: 'pc-lens-glass' }, + el('div', { className: 'pc-lens-reflex' }) + ) + ), + el('div', { className: 'pc-grip' }) + ) + ), + el('div', { className: 'pc-prints' }, + el('div', { className: 'pc-print pc-print--1', style: { opacity: '1', transform: 'rotate(-12deg) translateY(0)' } }, + el('div', { className: 'pc-print__img' }) + ) + ) + ), + // Centre scene + el('div', { className: 'cam-scene' }, + el('div', { className: 'cam-subject cam-subject--1' }), + el('div', { className: 'cam-flash-overlay' }), + el('div', { className: 'cam-vid-overlay' }) + ), + // Video camera on tripod (right) + el('div', { className: 'vc-wrap' }, + el('div', { className: 'vc-camera' }, + el('div', { className: 'vc-handle' }), + el('div', { className: 'vc-body' }, + el('div', { className: 'vc-lens-barrel' }, + el('div', { className: 'vc-lens-tip' }, + el('div', { className: 'vc-lens-glass' }, + el('div', { className: 'vc-lens-reflex' }) + ) + ) + ), + el('div', { className: 'vc-top-rail' }), + el('div', { className: 'vc-rec-light' }), + el('div', { className: 'vc-eyepiece' }) + ) + ), + el('div', { className: 'vc-tripod' }, + el('div', { className: 'vc-stem' }), + el('div', { className: 'vc-legs' }, + el('div', { className: 'vc-leg vc-leg--l' }), + el('div', { className: 'vc-leg vc-leg--c' }), + el('div', { className: 'vc-leg vc-leg--r' }) + ) + ) + ) + ) + ) + : a.deviceAnim + ? el('div', { className: 'platform-visual has-anim', dangerouslySetInnerHTML: { __html: DA_HTML } }) + : a.tvStick + ? el('div', { className: 'platform-visual has-tv-stick', dangerouslySetInnerHTML: { __html: TS_HTML } }) + : a.neverGoesDark + ? el('div', { className: 'platform-visual has-ngd', dangerouslySetInnerHTML: { __html: NGD_HTML } }) + : a.brandedAnim + ? el('div', { className: 'platform-visual has-branded', dangerouslySetInnerHTML: { __html: BD_HTML } }) + : a.imgUrl + ? el('div', { className: 'platform-visual has-img' }, + el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain', display: 'block', marginInline: 'auto' } }) + ) + : el('div', { className: 'platform-visual' }, a.visual || '\uD83D\uDCBB') + ) + ); + }, + save: function () { return null; } + }); + + /* ── Trust Item ───────────────────────────────────────────────────────── */ + reg('oribi/trust-item', { + title: 'Trust Item', + icon: 'shield', + category: 'oribi', + parent: ['oribi/trust-section'], + supports: { html: false, reusable: false }, + attributes: { + heading: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el('div', { className: 'trust-item' }, + el(RT, { + tagName: 'h3', style: { marginBottom: '1rem' }, + value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Sub-heading...' + }), + el(RT, { + tagName: 'p', value: a.description, + onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' + }) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + PARENT BLOCKS (use InnerBlocks for child items) + ═══════════════════════════════════════════════════════════════════════ */ + + /* 3. FEATURE SECTION ──────────────────────────────────────────────────── */ + reg('oribi/feature-section', { + title: 'Oribi Feature Section', + icon: 'grid-view', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: SECTION_ATTRS, + edit: createCardSectionEdit(['oribi/feature-card'], [['oribi/feature-card', {}]], 'Feature Card'), + save: function () { return el(IB.Content); } + }); + + /* VALUE SECTION ────────────────────────────────────────────────────────── */ + reg('oribi/value-section', { + title: 'Oribi Value Section', + icon: 'heart', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: SECTION_ATTRS, + edit: createCardSectionEdit(['oribi/value-card'], [['oribi/value-card', {}]], 'Value Card'), + save: function () { return el(IB.Content); } + }); + + /* ADDON SECTION ────────────────────────────────────────────────────────── */ + reg('oribi/addon-section', { + title: 'Oribi Addon Section', + icon: 'plus-alt2', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: SECTION_ATTRS, + edit: createCardSectionEdit(['oribi/addon-card'], [['oribi/addon-card', {}]], 'Addon Card'), + save: function () { return el(IB.Content); } + }); + + /* IMAGE SECTION ────────────────────────────────────────────────────────── */ + reg('oribi/image-section', { + title: 'Oribi Image Section', + icon: 'format-image', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: SECTION_ATTRS, + edit: createCardSectionEdit(['oribi/image-card'], [['oribi/image-card', {}]], 'Image Card'), + save: function () { return el(IB.Content); } + }); + + /* STAT SECTION ─────────────────────────────────────────────────────────── */ + reg('oribi/stat-section', { + title: 'Oribi Stat Section', + icon: 'chart-bar', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: SECTION_ATTRS, + edit: createCardSectionEdit(['oribi/stat-card'], [['oribi/stat-card', {}]], 'Stat Card'), + save: function () { return el(IB.Content); } + }); + + /* LINK SECTION ─────────────────────────────────────────────────────────── */ + reg('oribi/link-section', { + title: 'Oribi Link Section', + icon: 'admin-links', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: SECTION_ATTRS, + edit: createCardSectionEdit(['oribi/link-card'], [['oribi/link-card', {}]], 'Link Card'), + save: function () { return el(IB.Content); } + }); + + /* 4. PRICING SECTION ──────────────────────────────────────────────────── */ + reg('oribi/pricing-section', { + title: 'Oribi Pricing', + icon: 'money-alt', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Section Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Pricing heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'pricing-grid' }, + el(IB, { + allowedBlocks: ['oribi/pricing-card'], + template: [ + ['oribi/pricing-card', { name: 'Essentials' }], + ['oribi/pricing-card', { name: 'Pro', featured: true, badge: 'Most Popular' }], + ['oribi/pricing-card', { name: 'Enterprise' }] + ], + templateLock: false + }) + ) + ) + ) + ); + }, + save: function () { return el(IB.Content); } + }); + + /* 7. PLATFORM SECTION ─────────────────────────────────────────────────── */ + reg('oribi/platform-section', { + title: 'Oribi Platform Section', + icon: 'slides', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Section' }, + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Section heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), el(IB, { - allowedBlocks: ['oribi/faq-item'], - template: [['oribi/faq-item', {}]], + allowedBlocks: ['oribi/platform-row'], + template: [['oribi/platform-row', {}]], templateLock: false }) ) ) - ) - ); - }, - save: function () { return el(IB.Content); } -}); + ); + }, + save: function () { return el(IB.Content); } + }); -/* FAQ ITEM (child) ─────────────────────────────────────────────────────── */ -reg('oribi/faq-item', { - title: 'Oribi FAQ Item', - icon: 'editor-help', - category: 'oribi', - parent: ['oribi/faq-section'], - supports: { html: false, reusable: false }, - attributes: { - question: { type: 'string', default: '' }, - answer: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el('details', { className: 'faq-item', open: true }, - el('summary', { className: 'faq-question' }, - el(RT, { tagName: 'span', value: a.question, - onChange: function(v){s({question:v});}, placeholder: 'Question...' }) - ), - el('div', { className: 'faq-answer' }, - el(RT, { tagName: 'p', value: a.answer, - onChange: function(v){s({answer:v});}, placeholder: 'Answer...' }) - ) - ); - }, - save: function () { return null; } -}); - -/* 11. COMPARISON TABLE (standalone) ───────────────────────────────────── */ -reg('oribi/comparison-table', { - title: 'Oribi Comparison Table', - icon: 'editor-table', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - columns: { type: 'array', default: [] }, - rows: { type: 'array', default: [] }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var cols = a.columns || []; - var rows = a.rows || []; - - /* ── Column helpers ─────────────────────────────────────── */ - function updateCol(idx, val) { - var c = cols.slice(); c[idx] = val; s({ columns: c }); - } - function addCol() { - var newRows = rows.map(function(r) { - if (r.group) return r; - return Object.assign({}, r, { values: (r.values || []).concat([false]) }); - }); - s({ columns: cols.concat(['Plan']), rows: newRows }); - } - function removeCol(idx) { - var c = cols.slice(); c.splice(idx, 1); - var newRows = rows.map(function(r) { - if (r.group) return r; - var v = (r.values || []).slice(); v.splice(idx, 1); - return Object.assign({}, r, { values: v }); - }); - s({ columns: c, rows: newRows }); - } - - /* ── Row helpers ────────────────────────────────────────── */ - function updateRow(idx, key, val) { - var r = rows.slice(); - r[idx] = Object.assign({}, r[idx]); - r[idx][key] = val; - s({ rows: r }); - } - function updateCell(ri, ci, val) { - var r = rows.slice(); - r[ri] = Object.assign({}, r[ri]); - var v = (r[ri].values || []).slice(); - v[ci] = val; - r[ri].values = v; - s({ rows: r }); - } - function toggleCell(ri, ci) { - var val = (rows[ri].values || [])[ci]; - updateCell(ri, ci, val === true ? false : val === false ? true : true); - } - function switchCellToText(ri, ci) { updateCell(ri, ci, ''); } - function addFeatureRow() { - var vals = cols.map(function() { return false; }); - s({ rows: rows.concat([{ feature: 'New feature', values: vals }]) }); - } - function addGroupRow() { - s({ rows: rows.concat([{ group: 'New Group' }]) }); - } - function removeRow(idx) { - var r = rows.slice(); r.splice(idx, 1); s({ rows: r }); - } - function moveRow(idx, dir) { - var t = idx + dir; - if (t < 0 || t >= rows.length) return; - var r = rows.slice(); - var tmp = r[idx]; r[idx] = r[t]; r[t] = tmp; - s({ rows: r }); - } - - /* ── Inline styles for editor controls ──────────────────── */ - var inputStyle = { width: '100%', padding: '4px 6px', border: '1px solid #ddd', borderRadius: '3px', fontSize: '13px', background: 'transparent', boxSizing: 'border-box' }; - var thInputStyle = Object.assign({}, inputStyle, { fontWeight: 600, textAlign: 'center' }); - var smallBtnStyle = { background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', fontSize: '11px', lineHeight: 1, opacity: 0.6, verticalAlign: 'middle' }; - var rowCtrlStyle = { whiteSpace: 'nowrap', width: '1%', padding: '4px', verticalAlign: 'middle', border: 'none', background: 'transparent' }; - var cellBtnStyle = { background: 'none', border: '1px solid #ddd', borderRadius: '3px', cursor: 'pointer', padding: '2px 6px', fontSize: '13px', lineHeight: '1.4', margin: '0 1px' }; - - /* ── Render ─────────────────────────────────────────────── */ - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Table Settings' }, - el(SC, { label: 'Background', value: a.variant, options: [ - { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } - ], onChange: function(v){s({variant:v});} }), - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }) + /* 9. TRUST SECTION ────────────────────────────────────────────────────── */ + reg('oribi/trust-section', { + title: 'Oribi Trust Section', + icon: 'shield', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + btnText: { type: 'string', default: '' }, + btnUrl: { type: 'string', default: '' }, + btnSub: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }), + el(TC, { label: 'Button Text', value: a.btnText, onChange: function (v) { s({ btnText: v }); } }), + el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }), + el(TC, { label: 'Button Subtext', value: a.btnSub, onChange: function (v) { s({ btnSub: v }); } }) + ) ), - el(PB, { title: 'Columns', initialOpen: false }, - cols.map(function(col, i) { - return el('div', { key: i, style: { display: 'flex', gap: '4px', marginBottom: '8px', alignItems: 'center' } }, - el(TC, { label: '', value: col, onChange: function(v){ updateCol(i, v); }, style: { flex: 1 } }), - el(Btn, { isDestructive: true, isSmall: true, onClick: function(){ removeCol(i); } }, '\u2715') - ); - }), - el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Add Column') - ), - el(PB, { title: 'Add Rows', initialOpen: false }, - el('div', { style: { display: 'flex', gap: '8px' } }, - el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), - el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row') + el('section', { className: 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'grid-2', style: { alignItems: 'center' } }, + el('div', { style: { display: 'flex', flexDirection: 'column', gap: '1.5rem' } }, + el(IB, { + allowedBlocks: ['oribi/trust-item'], + template: [['oribi/trust-item', {}]], + templateLock: false + }) + ), + el('div', { style: { textAlign: 'center' } }, + el('span', { className: 'btn btn-primary btn-lg' }, a.btnText || 'Button'), + a.btnSub ? el('p', { className: 'lead mt-2', style: { fontSize: '.9rem' } }, a.btnSub) : null + ) + ) ) ) - ), - el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, - el('div', { className: 'container' }, - el('div', { className: 'section-header' }, - a.label ? el('span', { className: 'section-label' }, a.label) : null, - el(RT, { tagName: 'h2', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Table heading...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' }) + ); + }, + save: function () { return el(IB.Content); } + }); + + /* 10. FAQ SECTION ─────────────────────────────────────────────────────── */ + reg('oribi/faq-section', { + title: 'Oribi FAQ Section', + icon: 'editor-help', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Section Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'FAQ heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'faq-list' }, + el(IB, { + allowedBlocks: ['oribi/faq-item'], + template: [['oribi/faq-item', {}]], + templateLock: false + }) + ) + ) + ) + ); + }, + save: function () { return el(IB.Content); } + }); + + /* FAQ ITEM (child) ─────────────────────────────────────────────────────── */ + reg('oribi/faq-item', { + title: 'Oribi FAQ Item', + icon: 'editor-help', + category: 'oribi', + parent: ['oribi/faq-section'], + supports: { html: false, reusable: false }, + attributes: { + question: { type: 'string', default: '' }, + answer: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el('details', { className: 'faq-item', open: true }, + el('summary', { className: 'faq-question' }, + el(RT, { + tagName: 'span', value: a.question, + onChange: function (v) { s({ question: v }); }, placeholder: 'Question...' + }) + ), + el('div', { className: 'faq-answer' }, + el(RT, { + tagName: 'p', value: a.answer, + onChange: function (v) { s({ answer: v }); }, placeholder: 'Answer...' + }) + ) + ); + }, + save: function () { return null; } + }); + + /* 11. COMPARISON TABLE (standalone) ───────────────────────────────────── */ + reg('oribi/comparison-table', { + title: 'Oribi Comparison Table', + icon: 'editor-table', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + columns: { type: 'array', default: [] }, + rows: { type: 'array', default: [] }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var cols = a.columns || []; + var rows = a.rows || []; + + /* ── Column helpers ─────────────────────────────────────── */ + function updateCol(idx, val) { + var c = cols.slice(); c[idx] = val; s({ columns: c }); + } + function addCol() { + var newRows = rows.map(function (r) { + if (r.group) return r; + return Object.assign({}, r, { values: (r.values || []).concat([false]) }); + }); + s({ columns: cols.concat(['Plan']), rows: newRows }); + } + function removeCol(idx) { + var c = cols.slice(); c.splice(idx, 1); + var newRows = rows.map(function (r) { + if (r.group) return r; + var v = (r.values || []).slice(); v.splice(idx, 1); + return Object.assign({}, r, { values: v }); + }); + s({ columns: c, rows: newRows }); + } + + /* ── Row helpers ────────────────────────────────────────── */ + function updateRow(idx, key, val) { + var r = rows.slice(); + r[idx] = Object.assign({}, r[idx]); + r[idx][key] = val; + s({ rows: r }); + } + function updateCell(ri, ci, val) { + var r = rows.slice(); + r[ri] = Object.assign({}, r[ri]); + var v = (r[ri].values || []).slice(); + v[ci] = val; + r[ri].values = v; + s({ rows: r }); + } + function toggleCell(ri, ci) { + var val = (rows[ri].values || [])[ci]; + updateCell(ri, ci, val === true ? false : val === false ? true : true); + } + function switchCellToText(ri, ci) { updateCell(ri, ci, ''); } + function addFeatureRow() { + var vals = cols.map(function () { return false; }); + s({ rows: rows.concat([{ feature: 'New feature', values: vals }]) }); + } + function addGroupRow() { + s({ rows: rows.concat([{ group: 'New Group' }]) }); + } + function removeRow(idx) { + var r = rows.slice(); r.splice(idx, 1); s({ rows: r }); + } + function moveRow(idx, dir) { + var t = idx + dir; + if (t < 0 || t >= rows.length) return; + var r = rows.slice(); + var tmp = r[idx]; r[idx] = r[t]; r[t] = tmp; + s({ rows: r }); + } + + /* ── Inline styles for editor controls ──────────────────── */ + var inputStyle = { width: '100%', padding: '4px 6px', border: '1px solid #ddd', borderRadius: '3px', fontSize: '13px', background: 'transparent', boxSizing: 'border-box' }; + var thInputStyle = Object.assign({}, inputStyle, { fontWeight: 600, textAlign: 'center' }); + var smallBtnStyle = { background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', fontSize: '11px', lineHeight: 1, opacity: 0.6, verticalAlign: 'middle' }; + var rowCtrlStyle = { whiteSpace: 'nowrap', width: '1%', padding: '4px', verticalAlign: 'middle', border: 'none', background: 'transparent' }; + var cellBtnStyle = { background: 'none', border: '1px solid #ddd', borderRadius: '3px', cursor: 'pointer', padding: '2px 6px', fontSize: '13px', lineHeight: '1.4', margin: '0 1px' }; + + /* ── Render ─────────────────────────────────────────────── */ + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Table Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) ), - el('div', { className: 'comparison-table-wrap' }, - el('table', { className: 'comparison-table' }, - el('thead', null, - el('tr', null, - el('th', { className: 'comparison-feature-col' }, 'Feature'), - cols.map(function(col, i) { - return el('th', { key: i }, - el('input', { type: 'text', value: col, style: thInputStyle, onChange: function(e){ updateCol(i, e.target.value); } }) - ); - }), - el('th', { style: rowCtrlStyle }) - ) - ), - el('tbody', null, - rows.map(function(row, ri) { - if (row.group) { - return el('tr', { key: ri, className: 'comparison-group-row' }, - el('td', { colSpan: cols.length + 1 }, - el('input', { type: 'text', value: row.group, style: Object.assign({}, inputStyle, { fontWeight: 700 }), onChange: function(e){ updateRow(ri, 'group', e.target.value); } }) - ), - el('td', { style: rowCtrlStyle }, - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function(){ removeRow(ri); }, title: 'Remove row' }, '\u2715') - ) - ); - } - return el('tr', { key: ri }, - el('td', { className: 'comparison-feature-name' }, - el('input', { type: 'text', value: row.feature || '', style: inputStyle, placeholder: 'Feature name\u2026', onChange: function(e){ updateRow(ri, 'feature', e.target.value); } }) - ), - (row.values || []).map(function(val, ci) { - if (typeof val === 'boolean') { - return el('td', { key: ci, className: 'comparison-cell', style: { textAlign: 'center' } }, - el('button', { style: Object.assign({}, cellBtnStyle, { color: val ? '#2e7d32' : '#c62828' }), onClick: function(){ toggleCell(ri, ci); }, title: 'Toggle \u2713/\u2717' }, - val ? '\u2713' : '\u2717' - ), - el('button', { style: Object.assign({}, smallBtnStyle, { fontSize: '10px' }), onClick: function(){ switchCellToText(ri, ci); }, title: 'Switch to text' }, 'Aa') - ); - } - return el('td', { key: ci, className: 'comparison-cell' }, - el('div', { style: { display: 'flex', alignItems: 'center', gap: '2px' } }, - el('input', { type: 'text', value: String(val), style: Object.assign({}, inputStyle, { flex: 1, minWidth: '60px' }), placeholder: 'Value\u2026', onChange: function(e){ updateCell(ri, ci, e.target.value); } }), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#2e7d32' }), onClick: function(){ updateCell(ri, ci, true); }, title: 'Set as \u2713' }, '\u2713'), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#c62828' }), onClick: function(){ updateCell(ri, ci, false); }, title: 'Set as \u2717' }, '\u2717') - ) + el(PB, { title: 'Columns', initialOpen: false }, + cols.map(function (col, i) { + return el('div', { key: i, style: { display: 'flex', gap: '4px', marginBottom: '8px', alignItems: 'center' } }, + el(TC, { label: '', value: col, onChange: function (v) { updateCol(i, v); }, style: { flex: 1 } }), + el(Btn, { isDestructive: true, isSmall: true, onClick: function () { removeCol(i); } }, '\u2715') + ); + }), + el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Add Column') + ), + el(PB, { title: 'Add Rows', initialOpen: false }, + el('div', { style: { display: 'flex', gap: '8px' } }, + el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), + el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row') + ) + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Table heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'comparison-table-wrap' }, + el('table', { className: 'comparison-table' }, + el('thead', null, + el('tr', null, + el('th', { className: 'comparison-feature-col' }, 'Feature'), + cols.map(function (col, i) { + return el('th', { key: i }, + el('input', { type: 'text', value: col, style: thInputStyle, onChange: function (e) { updateCol(i, e.target.value); } }) ); }), - el('td', { style: rowCtrlStyle }, - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function(){ removeRow(ri); }, title: 'Remove row' }, '\u2715') - ) - ); + el('th', { style: rowCtrlStyle }) + ) + ), + el('tbody', null, + rows.map(function (row, ri) { + if (row.group) { + return el('tr', { key: ri, className: 'comparison-group-row' }, + el('td', { colSpan: cols.length + 1 }, + el('input', { type: 'text', value: row.group, style: Object.assign({}, inputStyle, { fontWeight: 700 }), onChange: function (e) { updateRow(ri, 'group', e.target.value); } }) + ), + el('td', { style: rowCtrlStyle }, + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function () { removeRow(ri); }, title: 'Remove row' }, '\u2715') + ) + ); + } + return el('tr', { key: ri }, + el('td', { className: 'comparison-feature-name' }, + el('input', { type: 'text', value: row.feature || '', style: inputStyle, placeholder: 'Feature name\u2026', onChange: function (e) { updateRow(ri, 'feature', e.target.value); } }) + ), + (row.values || []).map(function (val, ci) { + if (typeof val === 'boolean') { + return el('td', { key: ci, className: 'comparison-cell', style: { textAlign: 'center' } }, + el('button', { style: Object.assign({}, cellBtnStyle, { color: val ? '#2e7d32' : '#c62828' }), onClick: function () { toggleCell(ri, ci); }, title: 'Toggle \u2713/\u2717' }, + val ? '\u2713' : '\u2717' + ), + el('button', { style: Object.assign({}, smallBtnStyle, { fontSize: '10px' }), onClick: function () { switchCellToText(ri, ci); }, title: 'Switch to text' }, 'Aa') + ); + } + return el('td', { key: ci, className: 'comparison-cell' }, + el('div', { style: { display: 'flex', alignItems: 'center', gap: '2px' } }, + el('input', { type: 'text', value: String(val), style: Object.assign({}, inputStyle, { flex: 1, minWidth: '60px' }), placeholder: 'Value\u2026', onChange: function (e) { updateCell(ri, ci, e.target.value); } }), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#2e7d32' }), onClick: function () { updateCell(ri, ci, true); }, title: 'Set as \u2713' }, '\u2713'), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#c62828' }), onClick: function () { updateCell(ri, ci, false); }, title: 'Set as \u2717' }, '\u2717') + ) + ); + }), + el('td', { style: rowCtrlStyle }, + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function () { removeRow(ri); }, title: 'Remove row' }, '\u2715') + ) + ); + }) + ) + ), + el('div', { style: { display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'center' } }, + el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), + el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row'), + el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Column') + ) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + TEMPLATE-PART HELPER BLOCKS + ═══════════════════════════════════════════════════════════════════════ */ + + /* ═══════════════════════════════════════════════════════════════════════ + ANIMATED HERO BLOCKS (OTS Signs) + ═══════════════════════════════════════════════════════════════════════ */ + + /* ANIMATED HERO ───────────────────────────────────────────────────────── */ + reg('oribi/hero-animated', { + title: 'Animated Hero', + icon: 'admin-site-alt3', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + highlightWord: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + primaryBtnText: { type: 'string', default: 'Get Started' }, + primaryBtnUrl: { type: 'string', default: '/contact' }, + secondaryBtnText: { type: 'string', default: '' }, + secondaryBtnUrl: { type: 'string', default: '' }, + stat1Value: { type: 'string', default: '' }, + stat1Label: { type: 'string', default: '' }, + stat2Value: { type: 'string', default: '' }, + stat2Label: { type: 'string', default: '' }, + stat3Value: { type: 'string', default: '' }, + stat3Label: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + // Build particle elements for editor preview + var particles = []; + for (var i = 1; i <= 12; i++) { + particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); + } + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Highlight' }, + el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function (v) { s({ highlightWord: v }); } }) + ), + el(PB, { title: 'Primary Button' }, + el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function (v) { s({ primaryBtnUrl: v }); } }) + ), + el(PB, { title: 'Secondary Button', initialOpen: false }, + el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function (v) { s({ secondaryBtnUrl: v }); } }) + ) + ), + el('section', { className: 'hero hero-animated' }, + el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), + el('div', { className: 'hero-animated__glow' }), + el('div', { className: 'container hero-animated__inner' }, + el('div', { className: 'hero-animated__content' }, + el(RT, { + tagName: 'span', className: 'hero-label', value: a.label, + onChange: function (v) { s({ label: v }); }, placeholder: '\u25CF Label text', allowedFormats: [] + }), + el(RT, { + tagName: 'h1', className: 'hero-title', value: a.title, + onChange: function (v) { s({ title: v }); }, placeholder: 'Hero title...' + }), + el(RT, { + tagName: 'p', className: 'hero-description', value: a.description, + onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' + }), + el('div', { className: 'btn-group' }, + el(RT, { + tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, + onChange: function (v) { s({ primaryBtnText: v }); }, placeholder: 'Button', allowedFormats: [] + }), + el(RT, { + tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, + onChange: function (v) { s({ secondaryBtnText: v }); }, placeholder: 'Secondary button', allowedFormats: [] }) - ) - ), - el('div', { style: { display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'center' } }, - el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), - el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row'), - el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Column') - ) - ) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* ═══════════════════════════════════════════════════════════════════════ - TEMPLATE-PART HELPER BLOCKS - ═══════════════════════════════════════════════════════════════════════ */ - -/* ═══════════════════════════════════════════════════════════════════════ - ANIMATED HERO BLOCKS (OTS Signs) - ═══════════════════════════════════════════════════════════════════════ */ - -/* ANIMATED HERO ───────────────────────────────────────────────────────── */ -reg('oribi/hero-animated', { - title: 'Animated Hero', - icon: 'admin-site-alt3', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - highlightWord: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - primaryBtnText: { type: 'string', default: 'Get Started' }, - primaryBtnUrl: { type: 'string', default: '/contact' }, - secondaryBtnText: { type: 'string', default: '' }, - secondaryBtnUrl: { type: 'string', default: '' }, - stat1Value: { type: 'string', default: '' }, - stat1Label: { type: 'string', default: '' }, - stat2Value: { type: 'string', default: '' }, - stat2Label: { type: 'string', default: '' }, - stat3Value: { type: 'string', default: '' }, - stat3Label: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - // Build particle elements for editor preview - var particles = []; - for (var i = 1; i <= 12; i++) { - particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); - } - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Highlight' }, - el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function(v){s({highlightWord:v});} }) - ), - el(PB, { title: 'Primary Button' }, - el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function(v){s({primaryBtnUrl:v});} }) - ), - el(PB, { title: 'Secondary Button', initialOpen: false }, - el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function(v){s({secondaryBtnUrl:v});} }) - ) - ), - el('section', { className: 'hero hero-animated' }, - el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), - el('div', { className: 'hero-animated__glow' }), - el('div', { className: 'container hero-animated__inner' }, - el('div', { className: 'hero-animated__content' }, - el(RT, { tagName: 'span', className: 'hero-label', value: a.label, - onChange: function(v){s({label:v});}, placeholder: '\u25CF Label text', allowedFormats: [] }), - el(RT, { tagName: 'h1', className: 'hero-title', value: a.title, - onChange: function(v){s({title:v});}, placeholder: 'Hero title...' }), - el(RT, { tagName: 'p', className: 'hero-description', value: a.description, - onChange: function(v){s({description:v});}, placeholder: 'Description...' }), - el('div', { className: 'btn-group' }, - el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, - onChange: function(v){s({primaryBtnText:v});}, placeholder: 'Button', allowedFormats: [] }), - el(RT, { tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, - onChange: function(v){s({secondaryBtnText:v});}, placeholder: 'Secondary button', allowedFormats: [] }) - ), - el('div', { className: 'hero-stats hero-stats--three' }, - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, - onChange: function(v){s({stat1Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, - onChange: function(v){s({stat1Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) ), - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, - onChange: function(v){s({stat2Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, - onChange: function(v){s({stat2Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) - ), - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat3Value, - onChange: function(v){s({stat3Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat3Label, - onChange: function(v){s({stat3Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) + el('div', { className: 'hero-stats hero-stats--three' }, + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, + onChange: function (v) { s({ stat1Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, + onChange: function (v) { s({ stat1Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ), + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, + onChange: function (v) { s({ stat2Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, + onChange: function (v) { s({ stat2Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ), + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat3Value, + onChange: function (v) { s({ stat3Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat3Label, + onChange: function (v) { s({ stat3Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ) ) ) ) ) - ) - ); - }, - save: function () { return null; } -}); - -/* ANIMATED PAGE HERO ──────────────────────────────────────────────────── */ -reg('oribi/page-hero-animated', { - title: 'Animated Page Hero', - icon: 'flag', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var particles = []; - for (var i = 1; i <= 8; i++) { - particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); - } - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Settings' }, - el(TC, { label: 'Label (optional)', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: 'page-hero page-hero-animated' }, - el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), - el('div', { className: 'hero-animated__glow' }), - el('div', { className: 'hero-overlay' }), - el('div', { className: 'container' }, - a.label ? el('span', { className: 'hero-label' }, a.label) : null, - el(RT, { tagName: 'h1', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Page title...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* ═══════════════════════════════════════════════════════════════════════ - TEMPLATE-PART HELPER BLOCKS - ═══════════════════════════════════════════════════════════════════════ */ - -reg('oribi/site-header', { - title: 'Oribi Site Header', - icon: 'admin-home', - category: 'oribi', - supports: { html: false, multiple: false, reusable: false }, - edit: function () { - return el('div', { - style: { background: '#111111', color: '#fff', padding: '20px 24px', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } + ); }, - el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, - el('strong', { style: { fontSize: '1.2rem' } }, 'Oribi'), - el('span', { style: { fontSize: '1.2rem', fontWeight: 300 } }, 'Tech') - ), - el('div', { style: { display: 'flex', gap: '1.5rem', fontSize: '.9rem', opacity: 0.7 } }, - el('span', null, 'Services'), - el('span', null, 'About'), - el('span', null, 'FAQ'), - el('span', null, 'Contact') - ) - ); - }, - save: function () { return null; } -}); + save: function () { return null; } + }); -reg('oribi/site-footer', { - title: 'Oribi Site Footer', - icon: 'admin-home', - category: 'oribi', - supports: { html: false, multiple: false, reusable: false }, - edit: function () { - return el('div', { - style: { background: '#111111', color: '#fff', padding: '24px', borderRadius: '8px', textAlign: 'center' } + /* ANIMATED PAGE HERO ──────────────────────────────────────────────────── */ + reg('oribi/page-hero-animated', { + title: 'Animated Page Hero', + icon: 'flag', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, }, - el('strong', { style: { fontSize: '1.1rem' } }, 'OTS Theme - Site Footer'), - el('p', { style: { opacity: 0.5, margin: '8px 0 0', fontSize: '.85rem' } }, 'Brand · Service Links · Company Links · Connect · Copyright') - ); - }, - save: function () { return null; } -}); + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var particles = []; + for (var i = 1; i <= 8; i++) { + particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); + } + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(TC, { label: 'Label (optional)', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: 'page-hero page-hero-animated' }, + el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), + el('div', { className: 'hero-animated__glow' }), + el('div', { className: 'hero-overlay' }), + el('div', { className: 'container' }, + a.label ? el('span', { className: 'hero-label' }, a.label) : null, + el(RT, { tagName: 'h1', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Page title...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + TEMPLATE-PART HELPER BLOCKS + ═══════════════════════════════════════════════════════════════════════ */ + + reg('oribi/site-header', { + title: 'Oribi Site Header', + icon: 'admin-home', + category: 'oribi', + supports: { html: false, multiple: false, reusable: false }, + edit: function () { + return el('div', { + style: { background: '#111111', color: '#fff', padding: '20px 24px', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } + }, + el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, + el('strong', { style: { fontSize: '1.2rem' } }, 'Oribi'), + el('span', { style: { fontSize: '1.2rem', fontWeight: 300 } }, 'Tech') + ), + el('div', { style: { display: 'flex', gap: '1.5rem', fontSize: '.9rem', opacity: 0.7 } }, + el('span', null, 'Services'), + el('span', null, 'About'), + el('span', null, 'FAQ'), + el('span', null, 'Contact') + ) + ); + }, + save: function () { return null; } + }); + + reg('oribi/site-footer', { + title: 'Oribi Site Footer', + icon: 'admin-home', + category: 'oribi', + supports: { html: false, multiple: false, reusable: false }, + edit: function () { + return el('div', { + style: { background: '#111111', color: '#fff', padding: '24px', borderRadius: '8px', textAlign: 'center' } + }, + el('strong', { style: { fontSize: '1.1rem' } }, 'OTS Theme - Site Footer'), + el('p', { style: { opacity: 0.5, margin: '8px 0 0', fontSize: '.85rem' } }, 'Brand · Service Links · Company Links · Connect · Copyright') + ); + }, + save: function () { return null; } + }); })(window.wp); diff --git a/theme/blocks/index.php b/theme/blocks/index.php index cf382cb..3c95322 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -20,25 +20,27 @@ * Child blocks are dynamic (save → null, own render_callback). */ -if ( ! defined( 'ABSPATH' ) ) exit; +if (!defined('ABSPATH')) + exit; /* ══════════════════════════════════════════════════════════════════════════════ - SHARED HELPERS - ══════════════════════════════════════════════════════════════════════════════ */ + SHARED HELPERS + ══════════════════════════════════════════════════════════════════════════════ */ /** * Shared image attributes for all card blocks. * Spread into each card's 'attributes' array. */ -function oribi_card_image_attributes() { +function oribi_card_image_attributes() +{ return [ - 'imgId' => [ 'type' => 'number', 'default' => 0 ], - 'imgUrl' => [ 'type' => 'string', 'default' => '' ], - 'imgAlt' => [ 'type' => 'string', 'default' => '' ], - 'imgWidth' => [ 'type' => 'number', 'default' => 80 ], - 'imgHeight' => [ 'type' => 'number', 'default' => 0 ], - 'imgFit' => [ 'type' => 'string', 'default' => 'contain' ], - 'imgPosition' => [ 'type' => 'string', 'default' => 'top' ], + 'imgId' => ['type' => 'number', 'default' => 0], + 'imgUrl' => ['type' => 'string', 'default' => ''], + 'imgAlt' => ['type' => 'string', 'default' => ''], + 'imgWidth' => ['type' => 'number', 'default' => 80], + 'imgHeight' => ['type' => 'number', 'default' => 0], + 'imgFit' => ['type' => 'string', 'default' => 'contain'], + 'imgPosition' => ['type' => 'string', 'default' => 'top'], ]; } @@ -48,22 +50,23 @@ function oribi_card_image_attributes() { * @param array $a Block attributes (must include imgId, imgUrl, etc.) * @return array [ 'html' => string, 'position' => string, 'card_class' => string ] */ -function oribi_card_image_html( $a ) { - $img_id = ! empty( $a['imgId'] ) ? intval( $a['imgId'] ) : 0; - $img_url = ! empty( $a['imgUrl'] ) ? $a['imgUrl'] : ''; - $img_alt = ! empty( $a['imgAlt'] ) ? $a['imgAlt'] : ''; - $img_w = ! empty( $a['imgWidth'] ) ? intval( $a['imgWidth'] ) : 80; - $img_h = ! empty( $a['imgHeight'] ) ? intval( $a['imgHeight'] ) : 0; - $img_fit = ! empty( $a['imgFit'] ) ? $a['imgFit'] : 'contain'; - $img_pos = ! empty( $a['imgPosition'] ) ? $a['imgPosition'] : 'top'; +function oribi_card_image_html($a) +{ + $img_id = !empty($a['imgId']) ? intval($a['imgId']) : 0; + $img_url = !empty($a['imgUrl']) ? $a['imgUrl'] : ''; + $img_alt = !empty($a['imgAlt']) ? $a['imgAlt'] : ''; + $img_w = !empty($a['imgWidth']) ? intval($a['imgWidth']) : 80; + $img_h = !empty($a['imgHeight']) ? intval($a['imgHeight']) : 0; + $img_fit = !empty($a['imgFit']) ? $a['imgFit'] : 'contain'; + $img_pos = !empty($a['imgPosition']) ? $a['imgPosition'] : 'top'; $result = [ - 'html' => '', - 'position' => $img_pos, + 'html' => '', + 'position' => $img_pos, 'card_class' => '', ]; - if ( ! $img_url ) { + if (!$img_url) { return $result; } @@ -71,29 +74,32 @@ function oribi_card_image_html( $a ) { // Build inline style for dimensions $styles = []; - if ( $img_pos !== 'background' ) { - if ( $img_w > 0 ) $styles[] = 'width:' . $img_w . 'px'; + if ($img_pos !== 'background') { + if ($img_w > 0) + $styles[] = 'width:' . $img_w . 'px'; $styles[] = 'max-width:100%'; - if ( $img_h > 0 ) { + if ($img_h > 0) { $styles[] = 'height:' . $img_h . 'px'; - } else { + } + else { $styles[] = 'height:auto'; } } - $style_str = implode( ';', $styles ); + $style_str = implode(';', $styles); - $fit_class = 'oribi-card-img oribi-card-img--' . ( $img_pos === 'background' ? 'cover' : esc_attr( $img_fit ) ); + $fit_class = 'oribi-card-img oribi-card-img--' . ($img_pos === 'background' ? 'cover' : esc_attr($img_fit)); - if ( $img_id ) { - $result['html'] = wp_get_attachment_image( $img_id, 'large', false, [ + if ($img_id) { + $result['html'] = wp_get_attachment_image($img_id, 'large', false, [ 'class' => $fit_class, 'style' => $style_str, - 'alt' => $img_alt, - ] ); - } else { - $result['html'] = '' . esc_attr( $img_alt )
-                        . ''; + 'alt' => $img_alt, + ]); + } + else { + $result['html'] = '' . esc_attr($img_alt)
+            . ''; } // Wrap in container @@ -105,11 +111,12 @@ function oribi_card_image_html( $a ) { /** * Shared icon attributes added to every card block that supports an icon. */ -function oribi_card_icon_attributes() { +function oribi_card_icon_attributes() +{ return [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], ]; } @@ -125,42 +132,47 @@ function oribi_card_icon_attributes() { * @param array|string $icon_or_attrs Block attributes array, or legacy icon string. * @return string Rendered HTML. */ -function oribi_render_icon( $icon_or_attrs ) { +function oribi_render_icon($icon_or_attrs) +{ // Legacy string call - if ( is_string( $icon_or_attrs ) ) { + if (is_string($icon_or_attrs)) { $icon = $icon_or_attrs; - if ( empty( $icon ) ) return ''; - if ( preg_match( '/^fa[srldb]\s+fa-/', $icon ) ) { - return ''; + if (empty($icon)) + return ''; + if (preg_match('/^fa[srldb]\s+fa-/', $icon)) { + return ''; } - return wp_kses_post( $icon ); + return wp_kses_post($icon); } // Array (block attributes) - $a = $icon_or_attrs; - $iconType = ! empty( $a['iconType'] ) ? $a['iconType'] : 'emoji'; + $a = $icon_or_attrs; + $iconType = !empty($a['iconType']) ? $a['iconType'] : 'emoji'; - if ( $iconType === 'fontawesome' ) { - $fa = ! empty( $a['faIcon'] ) ? trim( $a['faIcon'] ) : ''; - if ( empty( $fa ) ) return ''; - return ''; + if ($iconType === 'fontawesome') { + $fa = !empty($a['faIcon']) ? trim($a['faIcon']) : ''; + if (empty($fa)) + return ''; + return ''; } // emoji / text - $icon = ! empty( $a['icon'] ) ? $a['icon'] : ''; - if ( empty( $icon ) ) return ''; - return wp_kses_post( $icon ); + $icon = !empty($a['icon']) ? $a['icon'] : ''; + if (empty($icon)) + return ''; + return wp_kses_post($icon); } /** * Return true when the given block attributes specify a non-empty icon. */ -function oribi_has_icon( $a ) { - $iconType = ! empty( $a['iconType'] ) ? $a['iconType'] : 'emoji'; - if ( $iconType === 'fontawesome' ) { - return ! empty( $a['faIcon'] ); +function oribi_has_icon($a) +{ + $iconType = !empty($a['iconType']) ? $a['iconType'] : 'emoji'; + if ($iconType === 'fontawesome') { + return !empty($a['faIcon']); } - return ! empty( $a['icon'] ); + return !empty($a['icon']); } /** @@ -172,19 +184,22 @@ function oribi_has_icon( $a ) { * @param int $default_cols Default column count if not set in attributes. * @return string Rendered HTML. */ -function oribi_render_card_section( $a, $content, $grid_class = 'grid', $default_cols = 3 ) { - $cls = ( ! empty( $a['variant'] ) && $a['variant'] === 'alt' ) ? 'section section-alt' : 'section'; - $cols = ! empty( $a['columns'] ) ? intval( $a['columns'] ) : $default_cols; +function oribi_render_card_section($a, $content, $grid_class = 'grid', $default_cols = 3) +{ + $cls = (!empty($a['variant']) && $a['variant'] === 'alt') ? 'section section-alt' : 'section'; + $cols = !empty($a['columns']) ? intval($a['columns']) : $default_cols; $grid = $grid_class . '-' . $cols; ob_start(); ?> -
+
- -

-

+ +

+

-
+
@@ -195,574 +210,613 @@ function oribi_render_card_section( $a, $content, $grid_class = 'grid', $default /** * Standard section attributes shared by all card section wrappers. */ -function oribi_card_section_attributes( $default_cols = 3 ) { +function oribi_card_section_attributes($default_cols = 3) +{ return [ - 'variant' => [ 'type' => 'string', 'default' => 'normal' ], - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'lead' => [ 'type' => 'string', 'default' => '' ], - 'columns' => [ 'type' => 'number', 'default' => $default_cols ], + 'variant' => ['type' => 'string', 'default' => 'normal'], + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'lead' => ['type' => 'string', 'default' => ''], + 'columns' => ['type' => 'number', 'default' => $default_cols], ]; } /* ── Block category ────────────────────────────────────────────────────────── */ -add_filter( 'block_categories_all', function ( $cats ) { - array_unshift( $cats, [ 'slug' => 'oribi', 'title' => 'OTS Theme' ] ); +add_filter('block_categories_all', function ($cats) { + array_unshift($cats, ['slug' => 'oribi', 'title' => 'OTS Theme']); return $cats; -} ); +}); /* ── Enqueue editor assets ─────────────────────────────────────────────────── */ -add_action( 'enqueue_block_editor_assets', function () { +add_action('enqueue_block_editor_assets', function () { $dir = get_template_directory(); $uri = get_template_directory_uri(); wp_enqueue_script( 'oribi-blocks', $uri . '/blocks/editor.js', - [ 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n' ], - filemtime( $dir . '/blocks/editor.js' ), + ['wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n'], + filemtime($dir . '/blocks/editor.js'), true ); wp_enqueue_style( 'oribi-blocks-editor', $uri . '/blocks/editor.css', - [ 'wp-edit-blocks' ], - filemtime( $dir . '/blocks/editor.css' ) + ['wp-edit-blocks'], + filemtime($dir . '/blocks/editor.css') ); -} ); +}); /* ── Register all blocks ───────────────────────────────────────────────────── */ -add_action( 'init', function () { +add_action('init', function () { /* Shared supports - exposes color pickers and font-size selector in the - block inspector for every Oribi block. Individual blocks can override - these by merging their own array if needed. */ + block inspector for every Oribi block. Individual blocks can override + these by merging their own array if needed. */ $block_supports = [ - 'color' => [ - 'text' => true, + 'color' => [ + 'text' => true, 'background' => true, - 'link' => true, + 'link' => true, ], 'typography' => [ - 'fontSize' => true, + 'fontSize' => true, 'lineHeight' => true, ], - 'spacing' => [ + 'spacing' => [ 'padding' => true, - 'margin' => true, + 'margin' => true, ], ]; /* ── TEMPLATE-PART HELPER BLOCKS ──────────────────────────────────────── */ - register_block_type( 'oribi/site-header', [ + register_block_type('oribi/site-header', [ 'render_callback' => 'oribi_render_site_header', - ] ); + ]); - register_block_type( 'oribi/site-footer', [ + register_block_type('oribi/site-footer', [ 'render_callback' => 'oribi_render_site_footer', - ] ); + ]); /* ── STANDALONE BLOCKS ─────────────────────────────────────────────────── */ - register_block_type( 'oribi/hero', [ - 'attributes' => [ - 'label' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'highlightWord' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'primaryBtnText' => [ 'type' => 'string', 'default' => 'Get in Touch' ], - 'primaryBtnUrl' => [ 'type' => 'string', 'default' => '/contact' ], - 'secondaryBtnText' => [ 'type' => 'string', 'default' => '' ], - 'secondaryBtnUrl' => [ 'type' => 'string', 'default' => '' ], - 'stat1Value' => [ 'type' => 'string', 'default' => '' ], - 'stat1Label' => [ 'type' => 'string', 'default' => '' ], - 'stat2Value' => [ 'type' => 'string', 'default' => '' ], - 'stat2Label' => [ 'type' => 'string', 'default' => '' ], - 'svcLaptop1' => [ 'type' => 'string', 'default' => 'Data Backup' ], - 'svcLaptop2' => [ 'type' => 'string', 'default' => 'Endpoint Security' ], - 'svcLaptop3' => [ 'type' => 'string', 'default' => 'Patch Management' ], - 'svcCloud1' => [ 'type' => 'string', 'default' => 'Email Protection' ], - 'svcCloud2' => [ 'type' => 'string', 'default' => 'License Management' ], - 'svcCloud3' => [ 'type' => 'string', 'default' => 'Cloud Backup' ], - 'svcDesktop1' => [ 'type' => 'string', 'default' => 'Network Monitoring' ], - 'svcDesktop2' => [ 'type' => 'string', 'default' => 'Threat Detection' ], - 'svcDesktop3' => [ 'type' => 'string', 'default' => 'Cloud Management' ], - 'svcPhone1' => [ 'type' => 'string', 'default' => 'Mobile Security' ], - 'svcPhone2' => [ 'type' => 'string', 'default' => 'Data Encryption' ], + register_block_type('oribi/hero', [ + 'attributes' => [ + 'label' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'highlightWord' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'primaryBtnText' => ['type' => 'string', 'default' => 'Get in Touch'], + 'primaryBtnUrl' => ['type' => 'string', 'default' => '/contact'], + 'secondaryBtnText' => ['type' => 'string', 'default' => ''], + 'secondaryBtnUrl' => ['type' => 'string', 'default' => ''], + 'stat1Value' => ['type' => 'string', 'default' => ''], + 'stat1Label' => ['type' => 'string', 'default' => ''], + 'stat2Value' => ['type' => 'string', 'default' => ''], + 'stat2Label' => ['type' => 'string', 'default' => ''], + 'svcLaptop1' => ['type' => 'string', 'default' => 'Data Backup'], + 'svcLaptop2' => ['type' => 'string', 'default' => 'Endpoint Security'], + 'svcLaptop3' => ['type' => 'string', 'default' => 'Patch Management'], + 'svcCloud1' => ['type' => 'string', 'default' => 'Email Protection'], + 'svcCloud2' => ['type' => 'string', 'default' => 'License Management'], + 'svcCloud3' => ['type' => 'string', 'default' => 'Cloud Backup'], + 'svcDesktop1' => ['type' => 'string', 'default' => 'Network Monitoring'], + 'svcDesktop2' => ['type' => 'string', 'default' => 'Threat Detection'], + 'svcDesktop3' => ['type' => 'string', 'default' => 'Cloud Management'], + 'svcPhone1' => ['type' => 'string', 'default' => 'Mobile Security'], + 'svcPhone2' => ['type' => 'string', 'default' => 'Data Encryption'], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_hero', - ] ); + ]); - register_block_type( 'oribi/page-hero', [ - 'attributes' => [ - 'label' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/page-hero', [ + 'attributes' => [ + 'label' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_page_hero', - ] ); + ]); - register_block_type( 'oribi/cta-banner', [ - 'attributes' => [ - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'text' => [ 'type' => 'string', 'default' => '' ], - 'btnText' => [ 'type' => 'string', 'default' => '' ], - 'btnUrl' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/cta-banner', [ + 'attributes' => [ + 'heading' => ['type' => 'string', 'default' => ''], + 'text' => ['type' => 'string', 'default' => ''], + 'btnText' => ['type' => 'string', 'default' => ''], + 'btnUrl' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_cta_banner', - ] ); + ]); - register_block_type( 'oribi/intro-section', [ - 'attributes' => [ - 'variant' => [ 'type' => 'string', 'default' => 'normal' ], - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'visual' => [ 'type' => 'string', 'default' => '' ], - 'reversed' => [ 'type' => 'boolean', 'default' => false ], - 'imgId' => [ 'type' => 'number', 'default' => 0 ], - 'imgUrl' => [ 'type' => 'string', 'default' => '' ], - 'imgAlt' => [ 'type' => 'string', 'default' => '' ], - 'imgWidth' => [ 'type' => 'number', 'default' => 280 ], + register_block_type('oribi/intro-section', [ + 'attributes' => [ + 'variant' => ['type' => 'string', 'default' => 'normal'], + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'visual' => ['type' => 'string', 'default' => ''], + 'reversed' => ['type' => 'boolean', 'default' => false], + 'cloudAnim' => ['type' => 'boolean', 'default' => false], + 'imgId' => ['type' => 'number', 'default' => 0], + 'imgUrl' => ['type' => 'string', 'default' => ''], + 'imgAlt' => ['type' => 'string', 'default' => ''], + 'imgWidth' => ['type' => 'number', 'default' => 280], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_intro_section', - ] ); + ]); - register_block_type( 'oribi/contact-section', [ - 'attributes' => [ - 'heading' => [ 'type' => 'string', 'default' => "Let's Talk" ], - 'lead' => [ 'type' => 'string', 'default' => '' ], - 'email' => [ 'type' => 'string', 'default' => 'solutions@oribi-tech.com' ], - 'supportUrl' => [ 'type' => 'string', 'default' => 'https://portal.oribi-tech.com/helpdesk/technical-support-1' ], - 'portalUrl' => [ 'type' => 'string', 'default' => 'https://portal.oribi-tech.com/' ], - 'location' => [ 'type' => 'string', 'default' => 'Saratoga Springs, Upstate New York' ], - 'formHeading' => [ 'type' => 'string', 'default' => 'Want to Learn More?' ], + register_block_type('oribi/contact-section', [ + 'attributes' => [ + 'heading' => ['type' => 'string', 'default' => "Let's Talk"], + 'lead' => ['type' => 'string', 'default' => ''], + 'email' => ['type' => 'string', 'default' => 'solutions@oribi-tech.com'], + 'supportUrl' => ['type' => 'string', 'default' => 'https://portal.oribi-tech.com/helpdesk/technical-support-1'], + 'portalUrl' => ['type' => 'string', 'default' => 'https://portal.oribi-tech.com/'], + 'location' => ['type' => 'string', 'default' => 'Saratoga Springs, Upstate New York'], + 'formHeading' => ['type' => 'string', 'default' => 'Want to Learn More?'], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_contact_section', - ] ); + ]); /* ── PARENT / CHILD PAIRS ──────────────────────────────────────────────── */ /* Feature Section (parent) */ - register_block_type( 'oribi/feature-section', [ - 'attributes' => oribi_card_section_attributes( 3 ), - 'supports' => $block_supports, + register_block_type('oribi/feature-section', [ + 'attributes' => oribi_card_section_attributes(3), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_feature_section', - ] ); + ]); /* Feature Card (child) */ - register_block_type( 'oribi/feature-card', [ - 'attributes' => array_merge( - [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'url' => [ 'type' => 'string', 'default' => '' ], - 'centered' => [ 'type' => 'boolean', 'default' => false ], - ], - oribi_card_image_attributes() - ), - 'supports' => $block_supports, + register_block_type('oribi/feature-card', [ + 'attributes' => array_merge( + [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'url' => ['type' => 'string', 'default' => ''], + 'centered' => ['type' => 'boolean', 'default' => false], + ], + oribi_card_image_attributes() + ), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_feature_card', - ] ); + ]); /* Value Section (parent) */ - register_block_type( 'oribi/value-section', [ - 'attributes' => oribi_card_section_attributes( 3 ), - 'supports' => $block_supports, + register_block_type('oribi/value-section', [ + 'attributes' => oribi_card_section_attributes(3), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_value_section', - ] ); + ]); /* Value Card (child) */ - register_block_type( 'oribi/value-card', [ - 'attributes' => array_merge( - [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - ], - oribi_card_image_attributes() - ), - 'supports' => $block_supports, + register_block_type('oribi/value-card', [ + 'attributes' => array_merge( + [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + ], + oribi_card_image_attributes() + ), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_value_card', - ] ); + ]); /* Addon Section (parent) */ - register_block_type( 'oribi/addon-section', [ - 'attributes' => oribi_card_section_attributes( 3 ), - 'supports' => $block_supports, + register_block_type('oribi/addon-section', [ + 'attributes' => oribi_card_section_attributes(3), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_addon_section', - ] ); + ]); /* Addon Card (child) */ - register_block_type( 'oribi/addon-card', [ - 'attributes' => array_merge( - [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'tag' => [ 'type' => 'string', 'default' => '' ], - ], - oribi_card_image_attributes() - ), - 'supports' => $block_supports, + register_block_type('oribi/addon-card', [ + 'attributes' => array_merge( + [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'tag' => ['type' => 'string', 'default' => ''], + ], + oribi_card_image_attributes() + ), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_addon_card', - ] ); + ]); /* Image Section (parent) */ - register_block_type( 'oribi/image-section', [ - 'attributes' => oribi_card_section_attributes( 3 ), - 'supports' => $block_supports, + register_block_type('oribi/image-section', [ + 'attributes' => oribi_card_section_attributes(3), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_image_section', - ] ); + ]); /* Image Card (child) */ - register_block_type( 'oribi/image-card', [ - 'attributes' => array_merge( - [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'url' => [ 'type' => 'string', 'default' => '' ], - ], - oribi_card_image_attributes() - ), - 'supports' => $block_supports, + register_block_type('oribi/image-card', [ + 'attributes' => array_merge( + [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'url' => ['type' => 'string', 'default' => ''], + ], + oribi_card_image_attributes() + ), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_image_card', - ] ); + ]); /* Stat Section (parent) */ - register_block_type( 'oribi/stat-section', [ - 'attributes' => oribi_card_section_attributes( 3 ), - 'supports' => $block_supports, + register_block_type('oribi/stat-section', [ + 'attributes' => oribi_card_section_attributes(3), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_stat_section', - ] ); + ]); /* Stat Card (child) */ - register_block_type( 'oribi/stat-card', [ - 'attributes' => array_merge( - [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'value' => [ 'type' => 'string', 'default' => '' ], - 'label' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - ], - oribi_card_image_attributes() - ), - 'supports' => $block_supports, + register_block_type('oribi/stat-card', [ + 'attributes' => array_merge( + [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'value' => ['type' => 'string', 'default' => ''], + 'label' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + ], + oribi_card_image_attributes() + ), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_stat_card', - ] ); + ]); /* Link Section (parent) */ - register_block_type( 'oribi/link-section', [ - 'attributes' => oribi_card_section_attributes( 3 ), - 'supports' => $block_supports, + register_block_type('oribi/link-section', [ + 'attributes' => oribi_card_section_attributes(3), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_link_section', - ] ); + ]); /* Link Card (child) */ - register_block_type( 'oribi/link-card', [ - 'attributes' => array_merge( - [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'linkText' => [ 'type' => 'string', 'default' => '' ], - 'linkUrl' => [ 'type' => 'string', 'default' => '' ], - ], - oribi_card_image_attributes() - ), - 'supports' => $block_supports, + register_block_type('oribi/link-card', [ + 'attributes' => array_merge( + [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'linkText' => ['type' => 'string', 'default' => ''], + 'linkUrl' => ['type' => 'string', 'default' => ''], + ], + oribi_card_image_attributes() + ), + 'supports' => $block_supports, 'render_callback' => 'oribi_render_link_card', - ] ); + ]); /* Pricing Section (parent) */ - register_block_type( 'oribi/pricing-section', [ - 'attributes' => [ - 'variant' => [ 'type' => 'string', 'default' => 'normal' ], - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'lead' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/pricing-section', [ + 'attributes' => [ + 'variant' => ['type' => 'string', 'default' => 'normal'], + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'lead' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_pricing_section', - ] ); + ]); /* Pricing Card (child) */ - register_block_type( 'oribi/pricing-card', [ - 'attributes' => [ - 'icon' => [ 'type' => 'string', 'default' => '' ], - 'iconType' => [ 'type' => 'string', 'default' => 'emoji' ], - 'faIcon' => [ 'type' => 'string', 'default' => '' ], - 'name' => [ 'type' => 'string', 'default' => '' ], - 'tagline' => [ 'type' => 'string', 'default' => '' ], - 'price' => [ 'type' => 'string', 'default' => '' ], - 'pricePer' => [ 'type' => 'string', 'default' => '' ], - 'features' => [ 'type' => 'array', 'default' => [] ], - 'btnText' => [ 'type' => 'string', 'default' => 'Get Started' ], - 'btnUrl' => [ 'type' => 'string', 'default' => '/contact' ], - 'featured' => [ 'type' => 'boolean', 'default' => false ], - 'badge' => [ 'type' => 'string', 'default' => '' ], - 'imgId' => [ 'type' => 'number', 'default' => 0 ], - 'imgUrl' => [ 'type' => 'string', 'default' => '' ], - 'imgAlt' => [ 'type' => 'string', 'default' => '' ], - 'imgWidth' => [ 'type' => 'number', 'default' => 80 ], + register_block_type('oribi/pricing-card', [ + 'attributes' => [ + 'icon' => ['type' => 'string', 'default' => ''], + 'iconType' => ['type' => 'string', 'default' => 'emoji'], + 'faIcon' => ['type' => 'string', 'default' => ''], + 'name' => ['type' => 'string', 'default' => ''], + 'tagline' => ['type' => 'string', 'default' => ''], + 'price' => ['type' => 'string', 'default' => ''], + 'pricePer' => ['type' => 'string', 'default' => ''], + 'features' => ['type' => 'array', 'default' => []], + 'btnText' => ['type' => 'string', 'default' => 'Get Started'], + 'btnUrl' => ['type' => 'string', 'default' => '/contact'], + 'featured' => ['type' => 'boolean', 'default' => false], + 'badge' => ['type' => 'string', 'default' => ''], + 'imgId' => ['type' => 'number', 'default' => 0], + 'imgUrl' => ['type' => 'string', 'default' => ''], + 'imgAlt' => ['type' => 'string', 'default' => ''], + 'imgWidth' => ['type' => 'number', 'default' => 80], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_pricing_card', - ] ); + ]); /* Platform Section (parent) */ - register_block_type( 'oribi/platform-section', [ - 'attributes' => [ - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'lead' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/platform-section', [ + 'attributes' => [ + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'lead' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_platform_section', - ] ); + ]); /* Platform Row (child) */ - register_block_type( 'oribi/platform-row', [ - 'attributes' => [ - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'btnText' => [ 'type' => 'string', 'default' => 'Learn More' ], - 'btnUrl' => [ 'type' => 'string', 'default' => '' ], - 'visual' => [ 'type' => 'string', 'default' => '' ], - 'reversed' => [ 'type' => 'boolean', 'default' => false ], - 'imgId' => [ 'type' => 'number', 'default' => 0 ], - 'imgUrl' => [ 'type' => 'string', 'default' => '' ], - 'imgAlt' => [ 'type' => 'string', 'default' => '' ], - 'imgWidth' => [ 'type' => 'number', 'default' => 300 ], - 'isDashboard' => [ 'type' => 'boolean', 'default' => false ], - 'deviceAnim' => [ 'type' => 'boolean', 'default' => false ], - 'tvStick' => [ 'type' => 'boolean', 'default' => false ], - 'cameraAnim' => [ 'type' => 'boolean', 'default' => false ], - 'neverGoesDark'=> [ 'type' => 'boolean', 'default' => false ], - 'brandedAnim' => [ 'type' => 'boolean', 'default' => false ], - 'galleryIds' => [ 'type' => 'array', 'default' => [], 'items' => [ 'type' => 'number' ] ], + register_block_type('oribi/platform-row', [ + 'attributes' => [ + 'heading' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'btnText' => ['type' => 'string', 'default' => 'Learn More'], + 'btnUrl' => ['type' => 'string', 'default' => ''], + 'visual' => ['type' => 'string', 'default' => ''], + 'reversed' => ['type' => 'boolean', 'default' => false], + 'imgId' => ['type' => 'number', 'default' => 0], + 'imgUrl' => ['type' => 'string', 'default' => ''], + 'imgAlt' => ['type' => 'string', 'default' => ''], + 'imgWidth' => ['type' => 'number', 'default' => 300], + 'isDashboard' => ['type' => 'boolean', 'default' => false], + 'deviceAnim' => ['type' => 'boolean', 'default' => false], + 'tvStick' => ['type' => 'boolean', 'default' => false], + 'cameraAnim' => ['type' => 'boolean', 'default' => false], + 'neverGoesDark' => ['type' => 'boolean', 'default' => false], + 'brandedAnim' => ['type' => 'boolean', 'default' => false], + 'hospitalityAnim' => ['type' => 'boolean', 'default' => false], + 'retailAnim' => ['type' => 'boolean', 'default' => false], + 'corporateAnim' => ['type' => 'boolean', 'default' => false], + 'educationAnim' => ['type' => 'boolean', 'default' => false], + 'outdoorAnim' => ['type' => 'boolean', 'default' => false], + 'liveDataAnim' => ['type' => 'boolean', 'default' => false], + 'healthcareAnim' => ['type' => 'boolean', 'default' => false], + 'transitAnim' => ['type' => 'boolean', 'default' => false], + 'fitnessAnim' => ['type' => 'boolean', 'default' => false], + 'lobbyAnim' => ['type' => 'boolean', 'default' => false], + 'conferenceAnim' => ['type' => 'boolean', 'default' => false], + 'dayPartAnim' => ['type' => 'boolean', 'default' => false], + 'wayfindAnim' => ['type' => 'boolean', 'default' => false], + 'storefrontAnim' => ['type' => 'boolean', 'default' => false], + 'announcementAnim' => ['type' => 'boolean', 'default' => false], + 'campusWayfindAnim' => ['type' => 'boolean', 'default' => false], + 'emergencyAnim' => ['type' => 'boolean', 'default' => false], + 'enclosureAnim' => ['type' => 'boolean', 'default' => false], + 'brightnessAnim' => ['type' => 'boolean', 'default' => false], + 'cellularAnim' => ['type' => 'boolean', 'default' => false], + 'designerAnim' => ['type' => 'boolean', 'default' => false], + 'mediaLibraryAnim' => ['type' => 'boolean', 'default' => false], + 'publishAnim' => ['type' => 'boolean', 'default' => false], + 'screenGroupsAnim' => ['type' => 'boolean', 'default' => false], + 'monitoringAnim' => ['type' => 'boolean', 'default' => false], + 'patientWayfindAnim' => ['type' => 'boolean', 'default' => false], + 'waitingRoomAnim' => ['type' => 'boolean', 'default' => false], + 'multiZoneAnim' => ['type' => 'boolean', 'default' => false], + 'membershipAnim' => ['type' => 'boolean', 'default' => false], + 'videoMotionAnim' => ['type' => 'boolean', 'default' => false], + 'brandLayoutAnim' => ['type' => 'boolean', 'default' => false], + 'menuBoardAnim' => ['type' => 'boolean', 'default' => false], + 'galleryIds' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'number']], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_platform_row', - ] ); + ]); /* FAQ Section (parent) */ - register_block_type( 'oribi/faq-section', [ - 'attributes' => [ - 'variant' => [ 'type' => 'string', 'default' => 'normal' ], - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'lead' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/faq-section', [ + 'attributes' => [ + 'variant' => ['type' => 'string', 'default' => 'normal'], + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'lead' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_faq_section', - ] ); + ]); /* FAQ Item (child) */ - register_block_type( 'oribi/faq-item', [ - 'attributes' => [ - 'question' => [ 'type' => 'string', 'default' => '' ], - 'answer' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/faq-item', [ + 'attributes' => [ + 'question' => ['type' => 'string', 'default' => ''], + 'answer' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_faq_item', - ] ); + ]); /* Comparison Table */ - register_block_type( 'oribi/comparison-table', [ - 'attributes' => [ - 'variant' => [ 'type' => 'string', 'default' => 'normal' ], - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'lead' => [ 'type' => 'string', 'default' => '' ], - 'columns' => [ 'type' => 'array', 'default' => [] ], - 'rows' => [ 'type' => 'array', 'default' => [] ], + register_block_type('oribi/comparison-table', [ + 'attributes' => [ + 'variant' => ['type' => 'string', 'default' => 'normal'], + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'lead' => ['type' => 'string', 'default' => ''], + 'columns' => ['type' => 'array', 'default' => []], + 'rows' => ['type' => 'array', 'default' => []], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_comparison_table', - ] ); + ]); /* Trust Section (parent) */ - register_block_type( 'oribi/trust-section', [ - 'attributes' => [ - 'label' => [ 'type' => 'string', 'default' => '' ], - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'lead' => [ 'type' => 'string', 'default' => '' ], - 'btnText' => [ 'type' => 'string', 'default' => '' ], - 'btnUrl' => [ 'type' => 'string', 'default' => '' ], - 'btnSub' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/trust-section', [ + 'attributes' => [ + 'label' => ['type' => 'string', 'default' => ''], + 'heading' => ['type' => 'string', 'default' => ''], + 'lead' => ['type' => 'string', 'default' => ''], + 'btnText' => ['type' => 'string', 'default' => ''], + 'btnUrl' => ['type' => 'string', 'default' => ''], + 'btnSub' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_trust_section', - ] ); + ]); /* Trust Item (child) */ - register_block_type( 'oribi/trust-item', [ - 'attributes' => [ - 'heading' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/trust-item', [ + 'attributes' => [ + 'heading' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_trust_item', - ] ); + ]); /* ── ANIMATED HERO BLOCKS (OTS Signs) ─────────────────────────────────── */ /* Animated Hero - full homepage hero with particle background */ - register_block_type( 'oribi/hero-animated', [ - 'attributes' => [ - 'label' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'highlightWord' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], - 'primaryBtnText' => [ 'type' => 'string', 'default' => 'Get Started' ], - 'primaryBtnUrl' => [ 'type' => 'string', 'default' => '/contact' ], - 'secondaryBtnText' => [ 'type' => 'string', 'default' => '' ], - 'secondaryBtnUrl' => [ 'type' => 'string', 'default' => '' ], - 'stat1Value' => [ 'type' => 'string', 'default' => '' ], - 'stat1Label' => [ 'type' => 'string', 'default' => '' ], - 'stat2Value' => [ 'type' => 'string', 'default' => '' ], - 'stat2Label' => [ 'type' => 'string', 'default' => '' ], - 'stat3Value' => [ 'type' => 'string', 'default' => '' ], - 'stat3Label' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/hero-animated', [ + 'attributes' => [ + 'label' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'highlightWord' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], + 'primaryBtnText' => ['type' => 'string', 'default' => 'Get Started'], + 'primaryBtnUrl' => ['type' => 'string', 'default' => '/contact'], + 'secondaryBtnText' => ['type' => 'string', 'default' => ''], + 'secondaryBtnUrl' => ['type' => 'string', 'default' => ''], + 'stat1Value' => ['type' => 'string', 'default' => ''], + 'stat1Label' => ['type' => 'string', 'default' => ''], + 'stat2Value' => ['type' => 'string', 'default' => ''], + 'stat2Label' => ['type' => 'string', 'default' => ''], + 'stat3Value' => ['type' => 'string', 'default' => ''], + 'stat3Label' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_hero_animated', - ] ); + ]); /* Animated Page Hero - inner pages with particle background */ - register_block_type( 'oribi/page-hero-animated', [ - 'attributes' => [ - 'label' => [ 'type' => 'string', 'default' => '' ], - 'title' => [ 'type' => 'string', 'default' => '' ], - 'description' => [ 'type' => 'string', 'default' => '' ], + register_block_type('oribi/page-hero-animated', [ + 'attributes' => [ + 'label' => ['type' => 'string', 'default' => ''], + 'title' => ['type' => 'string', 'default' => ''], + 'description' => ['type' => 'string', 'default' => ''], ], - 'supports' => $block_supports, + 'supports' => $block_supports, 'render_callback' => 'oribi_render_page_hero_animated', - ] ); + ]); -} ); +}); /* ══════════════════════════════════════════════════════════════════════════════ - RENDER CALLBACKS - ══════════════════════════════════════════════════════════════════════════════ */ + RENDER CALLBACKS + ══════════════════════════════════════════════════════════════════════════════ */ /* ── Site Header ───────────────────────────────────────────────────────────── */ -function oribi_render_site_header() { +function oribi_render_site_header() +{ $has_logo = has_custom_logo(); ob_start(); ?> '; - echo '
  • Solutions
  • '; - echo '
  • Features
  • '; - echo '
  • Pricing
  • '; - echo '
  • Partners
  • '; - echo '
  • About
  • '; - echo '
  • Contact
  • '; + echo '
  • Solutions
  • '; + echo '
  • Features
  • '; + echo '
  • Pricing
  • '; + echo '
  • Partners
  • '; + echo '
  • About
  • '; + echo ''; echo ''; } /* ── Site Footer ───────────────────────────────────────────────────────────── */ -function oribi_render_site_footer() { - $year = gmdate( 'Y' ); +function oribi_render_site_footer() +{ + $year = gmdate('Y'); ob_start(); ?> - + ' . esc_html( $word ) . '', - wp_kses_post( $text ) + esc_html($word), + '' . esc_html($word) . '', + wp_kses_post($text) ); } /* ── Hero ──────────────────────────────────────────────────────────────────── */ -function oribi_render_hero( $a ) { +function oribi_render_hero($a) +{ ob_start(); - ?> +?>
    - -

    -

    + +

    +

    - - - - + + + +
    -
    -
    +
    +
    @@ -825,9 +887,9 @@ function oribi_render_hero( $a ) {
      -
    • -
    • -
    • +
    • +
    • +
    @@ -842,9 +904,9 @@ function oribi_render_hero( $a ) {
    @@ -865,9 +927,9 @@ function oribi_render_hero( $a ) {
    @@ -884,8 +946,8 @@ function oribi_render_hero( $a ) { @@ -897,48 +959,66 @@ function oribi_render_hero( $a ) { } /* ── Page Hero ─────────────────────────────────────────────────────────────── */ -function oribi_render_page_hero( $a ) { +function oribi_render_page_hero($a) +{ ob_start(); ?>
    - -

    -

    + +

    +

    -

    -

    - +

    +

    +
    -
    +
    > > - -

    -

    + +

    +

    +
    +
    > + '; + echo '
    '; + echo '
    '; + echo '
    '; + echo '
    '; + } + else { + echo wp_kses_post($a['visual']); + } +?>
    -
    >
    @@ -946,21 +1026,22 @@ function oribi_render_intro_section( $a ) { } /* ── Contact Section ───────────────────────────────────────────────────────── */ -function oribi_render_contact_section( $a ) { +function oribi_render_contact_section($a) +{ ob_start(); ?>
    -

    -

    -
    Email Us
    -
    Existing Customer Support
    -
    Client Portal
    -
    Location
    +

    +

    +
    Email Us
    +
    Existing Customer Support
    +
    Client Portal
    +
    Location
    -

    +

    @@ -991,289 +1072,347 @@ function oribi_render_contact_section( $a ) { } /* ── Feature Section (parent - wraps child feature-card blocks) ────────────── */ -function oribi_render_feature_section( $a, $content ) { - return oribi_render_card_section( $a, $content, 'grid', 3 ); +function oribi_render_feature_section($a, $content) +{ + return oribi_render_card_section($a, $content, 'grid', 3); } /* ── Feature Card (child - renders one card) ───────────────────────────────── */ -function oribi_render_feature_card( $a ) { - $tag = ! empty( $a['url'] ) ? 'a' : 'div'; - $href = ! empty( $a['url'] ) ? ' href="' . esc_url( $a['url'] ) . '"' : ''; - $link_cls = ! empty( $a['url'] ) ? ' feature-card-link' : ''; - $center = ! empty( $a['centered'] ); - $img = oribi_card_image_html( $a ); - $img_cls = $img['card_class'] ? ' ' . $img['card_class'] : ''; +function oribi_render_feature_card($a) +{ + $tag = !empty($a['url']) ? 'a' : 'div'; + $href = !empty($a['url']) ? ' href="' . esc_url($a['url']) . '"' : ''; + $link_cls = !empty($a['url']) ? ' feature-card-link' : ''; + $center = !empty($a['centered']); + $img = oribi_card_image_html($a); + $img_cls = $img['card_class'] ? ' ' . $img['card_class'] : ''; ob_start(); - if ( $img['html'] && $img['position'] === 'left' ) : ?> -< class="oribi-card"> + if ($img['html'] && $img['position'] === 'left'): ?> +< class="oribi-card">
    -

    -

    +

    +

    > - -< class="oribi-card"> + +< class="oribi-card">
    -
    >
    -

    -

    +
    >
    +

    +

    > - -< class="oribi-card"> - + +< class="oribi-card"> + - - -
    >
    - -

    -

    + + +
    >
    + +

    +

    > - -
    + if ($img['html'] && $img['position'] === 'background'): ?> +
    -
    -

    -

    +
    +

    +

    - -
    + +
    -

    -

    +

    +

    - -
    - - -
    - -

    -

    + +
    + + +
    + +

    +

    - -
    + if ($img['html'] && $img['position'] === 'left'): ?> +
    - -

    -

    + +

    +

    - -
    + +
    -
    - -

    -

    +
    + +

    +

    - -
    - - -
    - - -

    -

    + +
    + + +
    + + +

    +

    - -< class="oribi-card image-card"> + if ($img['html'] && $img['position'] === 'left'): ?> +< class="oribi-card image-card">
    -

    -

    +

    +

    > - -< class="oribi-card image-card"> + +< class="oribi-card image-card">
    -
    -

    -

    +
    +

    +

    > - -< class="oribi-card image-card"> - - -
    - + +< class="oribi-card image-card"> + + +
    +
    -

    -

    +

    +

    > - -
    + if ($img['html'] && $img['position'] === 'background'): ?> +
    -
    -
    -
    -

    +
    +
    +
    +

    - -
    + +
    -
    -
    -

    +
    +
    +

    - -
    - -
    -
    -
    -

    + +
    + +
    +
    +
    +

    -' - . esc_html( $a['linkText'] ?? 'Learn More' ) . ''; + if (!empty($a['linkUrl'])) { + $cta = '' + . esc_html($a['linkText'] ?? 'Learn More') . ''; } ob_start(); - if ( $img['html'] && $img['position'] === 'left' ) : ?> -