Compare commits
15 Commits
oribi-sync
...
oribi-sync
| Author | SHA1 | Date | |
|---|---|---|---|
| 086994a481 | |||
|
|
7e0c216e1c | ||
|
|
d68c2c1d31 | ||
|
|
4591578cb2 | ||
|
|
954c418556 | ||
|
|
b37bcfb72b | ||
|
|
0ec0e71d38 | ||
|
|
e1d9b1a402 | ||
|
|
a33a6d62d2 | ||
|
|
38d585e071 | ||
|
|
f8321568ce | ||
|
|
be30e4d59f | ||
|
|
3e211c376f | ||
|
|
618ba6ded4 | ||
| 3100a93d9a |
@@ -1,21 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* Title: Player Devices
|
||||
* Slug: ots-signs/page-devices
|
||||
* Categories: oribi-pages
|
||||
* Keywords: devices, hardware, player, screens, HDMI, commercial display
|
||||
* Post Types: page
|
||||
/*
|
||||
* Title: Devices
|
||||
* Slug: devices
|
||||
* Post Type: page
|
||||
*/
|
||||
?>
|
||||
|
||||
return <<<'ORIBI_SYNC_CONTENT'
|
||||
<!-- wp:oribi/page-hero-animated {"label":"Hardware","title":"Signage Players Engineered for the Real World","description":"Compact, silent, and built for 24/7 operation. Plug into any HDMI screen, connect to your network, and your content is live in minutes."} /-->
|
||||
|
||||
<!-- wp:oribi/platform-section {"label":"Our Devices","heading":"Commercial-Grade Hardware, Consumer-Level Simplicity","lead":"No IT degree required. Our players are designed to be set up in minutes and forgotten about for years."} -->
|
||||
<!-- wp:oribi/platform-row {"heading":"Plug Into Any Screen","description":"Our players work with any display that has an HDMI port — TVs, commercial panels, monitors, even projectors. Keep the screens you already own, or let us supply commercial-grade displays rated for continuous use. Either way, you\u0027re up and running fast.","btnText":"Get a Quote","btnUrl":"/contact"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Works With Your Existing Screens","description":"Our player devices connect to any screen with an HDMI port — no proprietary hardware required. Already have displays? Plug in and go. Need a full setup? We offer bundled player-and-display packages too.","btnText":"Get a Quote","btnUrl":"/contact","tvStick":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Never Goes Dark","description":"Every player caches content locally. If your internet connection drops, your displays continue running seamlessly with the latest synced content. When connectivity returns, new content pulls down automatically — no manual steps, no reboots.","btnText":"See Features","btnUrl":"/features","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Locked Down by Default","description":"Secure boot, encrypted storage, and encrypted communications come standard on every device. Remote management lets you monitor, update, and troubleshoot from anywhere. Firmware updates roll out over the air with zero downtime.","btnText":"Learn More","btnUrl":"/about"} /-->
|
||||
<!-- /wp:oribi/platform-section -->
|
||||
|
||||
<!-- wp:oribi/feature-section {"variant":"alt","label":"Device Specifications","heading":"What\u0027s Inside Every Player","lead":"Purpose-built components selected for reliability, performance, and silent operation in any environment.","columns":3} -->
|
||||
<!-- wp:oribi/feature-section {"variant":"alt","label":"Device Specifications","heading":"Whatu0027s Inside Every Player","lead":"Purpose-built components selected for reliability, performance, and silent operation in any environment.","columns":3} -->
|
||||
<!-- wp:oribi/feature-card {"iconType":"fontawesome","faIcon":"fas fa-display","title":"4K Output","description":"Crisp 4K resolution over HDMI for stunning visuals on any display size, from 32-inch panels to 75-inch video walls."} /-->
|
||||
<!-- wp:oribi/feature-card {"iconType":"fontawesome","faIcon":"fas fa-wifi","title":"Dual Connectivity","description":"Wi-Fi and Ethernet built in. Choose the connection that suits your environment, or use both for redundancy."} /-->
|
||||
<!-- wp:oribi/feature-card {"iconType":"fontawesome","faIcon":"fas fa-hard-drive","title":"Local Storage","description":"On-device storage caches your full content library for instant startup and uninterrupted offline playback."} /-->
|
||||
@@ -24,6 +23,7 @@
|
||||
<!-- wp:oribi/feature-card {"iconType":"fontawesome","faIcon":"fas fa-temperature-low","title":"Silent & Fanless","description":"Passive cooling means zero noise. Ideal for quiet spaces like hotel lobbies, meeting rooms, and restaurants."} /-->
|
||||
<!-- /wp:oribi/feature-section -->
|
||||
|
||||
<!-- wp:oribi/intro-section {"variant":"normal","label":"Bundles Available","heading":"Player + Display Packages","description":"Don\u0027t have screens yet? We offer turnkey bundles pairing our player device with a commercial-grade display rated for 24/7 operation — brighter, tougher, and longer-lasting than consumer TVs. Available from 32\" to 75\". Contact us for volume pricing and custom configurations.","visual":""} /-->
|
||||
<!-- wp:oribi/intro-section {"variant":"normal","label":"Bundles Available","heading":"Player + Display Packages","description":"Donu0027t have screens yet? We offer turnkey bundles pairing our player device with a commercial-grade display rated for 24/7 operation — brighter, tougher, and longer-lasting than consumer TVs. Available from 32" to 75". Contact us for volume pricing and custom configurations.","visual":""} /-->
|
||||
|
||||
<!-- wp:oribi/cta-banner {"heading":"Not Sure What You Need?","text":"Tell us about your space and we'll recommend the right player, display, and mounting solution for your environment.","btnText":"Request a Quote","btnUrl":"/contact"} /-->
|
||||
ORIBI_SYNC_CONTENT;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<!-- wp:oribi/platform-section {"label":"Core Features","heading":"Everything You Need, Nothing You Don\u0027t","lead":"Create, schedule, and manage digital signage content from a single dashboard — whether you have one screen or one thousand."} -->
|
||||
<!-- wp:oribi/platform-row {"heading":"One Dashboard for Every Display","description":"Manage your entire signage network from a single cloud-based console. Organise screens by location, group, or purpose. Push content updates across your whole estate in one click — no matter how many sites you operate.","btnText":"Get Started","btnUrl":"/contact"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Scheduling That Runs Itself","description":"Set content to appear at the right time, in the right place, automatically. Day-parting, date ranges, and event-triggered playback let you plan weeks ahead while the platform handles the execution.","btnText":"See Pricing","btnUrl":"/pricing","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Works With Your Existing Screens","description":"Our player devices connect to any screen with an HDMI port — no proprietary hardware required. Already have displays? Plug in and go. Need a full setup? We offer bundled player-and-display packages too.","btnText":"View Devices","btnUrl":"/devices"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Live Data, Straight to Screen","description":"Pull in web dashboards, social feeds, KPIs, and real-time APIs directly to your displays. Content updates automatically — no manual refreshing, no extra steps. Turn any screen into a live information hub.","btnText":"Learn More","btnUrl":"/solutions","reversed":true} /-->
|
||||
<!-- /wp:oribi/platform-section -->
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ return <<<'ORIBI_SYNC_CONTENT'
|
||||
<!-- wp:oribi/platform-section {"label":"The Complete Package","heading":"Everything You Need for Engaging Digital Signage","lead":"High-quality visuals, real-time data, and reliable playback — all managed from one powerful platform."} -->
|
||||
<!-- wp:oribi/platform-row {"heading":"Professional Content Creation","description":"Our in-house photography and video production services showcase your products, services, and environment with polished, engaging visuals. From digital menu boards to branded promotions, we create content that captures attention.","btnText":"See Features","btnUrl":"/features"} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Live Data \u0026amp; Web Dashboards","description":"Integrate your existing web dashboards, social feeds, and real-time data sources directly to your displays. Bring your most important information to life on screen, automatically and effortlessly.","btnUrl":"/features","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Live Data \u0026amp; Web Dashboards","description":"Integrate your existing web dashboards, social feeds, and real-time data sources directly to your displays. Bring your most important information to life on screen, automatically and effortlessly.","btnUrl":"/features","reversed":true,"isDashboard":true} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Reliable on Any Screen","description":"Our intelligent player devices work on any screen with HDMI, and keep your message running even when the internet goes down. Enterprise-grade hardware designed for uninterrupted, always-on signage.","btnText":"View Devices","btnUrl":"/devices"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Reliable on Any Screen","description":"Our intelligent player devices work on any screen with HDMI, and keep your message running even when the internet goes down. Enterprise-grade hardware designed for uninterrupted, always-on signage.","btnText":"View Devices","btnUrl":"/devices","deviceAnim":true} /-->
|
||||
<!-- /wp:oribi/platform-section -->
|
||||
|
||||
<!-- wp:oribi/feature-section {"variant":"alt","label":"Who It's For","heading":"Solutions for Every Industry","lead":"Modern businesses need real-time communication. Digital signage helps you connect, inform, and engage.","columns":4} -->
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
/*
|
||||
* Title: Pricing
|
||||
* Slug: ots-signs/page-pricing
|
||||
* Categories: oribi-pages
|
||||
* Keywords: pricing, plans, affordable, scalable, essentials, pro
|
||||
* Post Types: page
|
||||
* Slug: pricing
|
||||
* Post Type: page
|
||||
*/
|
||||
?>
|
||||
|
||||
return <<<'ORIBI_SYNC_CONTENT'
|
||||
<!-- wp:oribi/page-hero-animated {"label":"Pricing","title":"Straightforward Pricing, No Surprises","description":"Every plan includes the full content engine. Scale your infrastructure, integrations, and support as you grow. No hidden fees, no per-user charges."} /-->
|
||||
|
||||
<!-- wp:oribi/value-section {"variant":"normal","label":"Included on Every Plan","heading":"The Full Content Engine, From Day One","lead":"Whether you choose Essentials or Pro, your team gets the same powerful tools to create, schedule, and publish.","columns":4} -->
|
||||
<!-- wp:oribi/value-section {"label":"Included on Every Plan","heading":"The Full Content Engine, From Day One","lead":"Whether you choose Essentials or Pro, your team gets the same powerful tools to create, schedule, and publish.","columns":4} -->
|
||||
<!-- wp:oribi/value-card {"iconType":"fontawesome","faIcon":"fas fa-clock","title":"Automated Scheduling","description":"Day-parting, date ranges, and recurring schedules — your content plays at exactly the right time, automatically."} /-->
|
||||
|
||||
<!-- wp:oribi/value-card {"iconType":"fontawesome","faIcon":"fas fa-rss","title":"Live Data to Screen","description":"Pull DataSets, RSS feeds, social widgets, and embedded HTML directly to your displays — updated in real time."} /-->
|
||||
|
||||
<!-- wp:oribi/value-card {"iconType":"fontawesome","faIcon":"fas fa-users","title":"Unlimited Team Access","description":"Invite everyone who needs access. No per-user fees, no seat limits, no gatekeeping."} /-->
|
||||
|
||||
<!-- wp:oribi/value-card {"iconType":"fontawesome","faIcon":"fas fa-rocket","title":"Instant Publishing","description":"Upload content and push it live across your network in seconds — not hours."} /-->
|
||||
<!-- /wp:oribi/value-section -->
|
||||
|
||||
<!-- wp:oribi/pricing-section {"variant":"alt","label":"Choose Your Plan","heading":"Scale When You\u0027re Ready","lead":"Start with Essentials and upgrade seamlessly as your network grows. No disruption, no data loss."} -->
|
||||
<!-- wp:oribi/pricing-card {"name":"Essentials","tagline":"The full content engine for growing networks","price":"$7","pricePer":"per screen / month · or $70/screen annually","features":["Up to 50 screens","Custom subdomain","Shared CMS instance","Content scheduling & day-parting","DataSets, RSS, social & embedded widgets","Menu boards & interactive layouts","Unlimited users with standard roles","Canva integration","Offline playback","Proof of Play analytics (30-day retention)","Email support (next-business-day)"],"btnText":"Get Started","btnUrl":"/contact"} /-->
|
||||
<!-- wp:oribi/pricing-card {"name":"Pro","tagline":"Dedicated infrastructure, enterprise integrations & white-glove service","price":"Custom","pricePer":"tailored to your network size","features":["Unlimited screens","Custom domain","Dedicated CMS instance","Geo-location & weather-triggered scheduling","Dashboard Connector & custom API integrations","Video wall support","Ad campaigns & SSP monetisation","SSO (SAML/CAS) & custom user roles","Proof of Play analytics (12+ month retention)","Audience Reporting & scheduled PDF reports","In-house creative services included","Priority support (4-hour SLA) & account manager","Contractual SLA guarantee"],"btnText":"Contact Sales","btnUrl":"/contact","featured":true,"badge":"Enterprise"} /-->
|
||||
<!-- wp:oribi/pricing-section {"variant":"alt","label":"Choose Your Plan","heading":"Scale When Youu0027re Ready","lead":"Start with Essentials and upgrade seamlessly as your network grows. No disruption, no data loss."} -->
|
||||
<!-- wp:oribi/pricing-card {"name":"Essentials","tagline":"The full content engine for growing networks","price":"$7","pricePer":"per screen / month · or $70/screen annually","features":["Up to 50 screens","Custom subdomain","Shared CMS instance","Content scheduling \u0026 day-parting","DataSets, RSS, social \u0026 embedded widgets","Menu boards \u0026 interactive layouts","Unlimited users with standard roles","Canva integration","Offline playback","Proof of Play analytics (30-day retention)","Email support (next-business-day)"]} /-->
|
||||
|
||||
<!-- wp:oribi/pricing-card {"name":"Pro","tagline":"Dedicated infrastructure, enterprise integrations \u0026 white-glove service","price":"Custom","pricePer":"tailored to your network size","features":["Unlimited screens","Custom domain","Dedicated CMS instance","Geo-location \u0026 weather-triggered scheduling","Dashboard Connector \u0026 custom API integrations","Video wall support","Ad campaigns \u0026 SSP monetisation","SSO (SAML/CAS) \u0026 custom user roles","Proof of Play analytics (12+ month retention)","Audience Reporting \u0026 scheduled PDF reports","Priority support (4-hour SLA) \u0026 account manager","Contractual SLA guarantee"],"btnText":"Contact Sales","featured":true,"badge":"Enterprise"} /-->
|
||||
<!-- /wp:oribi/pricing-section -->
|
||||
|
||||
<!-- wp:oribi/comparison-table {"variant":"normal","label":"Plan Comparison","heading":"See Exactly What\u0027s Included","lead":"A full breakdown of what you get on each plan — so there are no surprises.","columns":["Essentials","Pro"],"rows":[{"group":"Scale & Infrastructure"},{"feature":"Screen limit","values":["Up to 50","Unlimited"]},{"feature":"CMS instance","values":["Shared","Dedicated"]},{"feature":"Custom subdomain","values":[true,true]},{"feature":"Custom domain","values":[false,true]},{"group":"Content & Scheduling"},{"feature":"Day-parting & date scheduling","values":[true,true]},{"feature":"Playlists & campaigns","values":[true,true]},{"feature":"Menu boards","values":[true,true]},{"feature":"Interactive touchscreen actions","values":[true,true]},{"feature":"Overlay layouts","values":[true,true]},{"feature":"Geo-location scheduling","values":[false,true]},{"feature":"Weather-triggered scheduling","values":[false,true]},{"feature":"Video wall","values":[false,true]},{"feature":"Ad campaigns & plays-per-hour control","values":[false,true]},{"group":"Data & Integrations"},{"feature":"DataSets, RSS & tickers","values":[true,true]},{"feature":"Embedded HTML & web pages","values":[true,true]},{"feature":"Social feeds","values":[true,true]},{"feature":"Canva integration","values":[true,true]},{"feature":"Dashboard Connector","values":[false,true]},{"feature":"Custom API integrations","values":[false,true]},{"feature":"SSP Connector (ad monetisation)","values":[false,true]},{"group":"Analytics & Reporting"},{"feature":"Proof of Play reporting","values":["30-day retention","12+ month retention"]},{"feature":"Scheduled PDF reports","values":[false,true]},{"feature":"Audience Reporting","values":[false,true]},{"feature":"Display health monitoring","values":[true,true]},{"group":"Users & Security"},{"feature":"Unlimited user seats","values":[true,true]},{"feature":"Predefined roles (admin/editor/viewer)","values":[true,true]},{"feature":"Custom user roles & feature access","values":[false,true]},{"feature":"Two-factor authentication","values":[true,true]},{"feature":"SSO (SAML / CAS)","values":[false,true]},{"feature":"Audit trail","values":["7-day retention","Extended retention"]},{"group":"Display Management"},{"feature":"Screen power on/off control","values":[true,true]},{"feature":"Offline playback","values":[true,true]},{"feature":"Portrait / landscape","values":[true,true]},{"feature":"Email alerts (player offline)","values":[true,true]},{"feature":"Periodic screenshots","values":[false,true]},{"feature":"Display map view","values":[false,true]},{"feature":"Shell commands & RS232","values":[false,true]},{"group":"Support & Services"},{"feature":"Email support","values":["Next-business-day",true]},{"feature":"Priority support","values":[false,"4-hour SLA"]},{"feature":"Dedicated account manager","values":[false,true]},{"feature":"In-house creative services","values":[false,"Included hours"]},{"feature":"White-glove onboarding","values":[false,true]}]} /-->
|
||||
<!-- wp:oribi/comparison-table {"label":"Plan Comparison","heading":"See Exactly Whatu0027s Included","lead":"A full breakdown of what you get on each plan — so there are no surprises.","columns":["Essentials","Pro"],"rows":[{"group":"Scale \u0026 Infrastructure"},{"feature":"Screen limit","values":["Up to 50","Unlimited"]},{"feature":"CMS instance","values":["Shared","Dedicated"]},{"feature":"Custom subdomain","values":[true,true]},{"feature":"Custom domain","values":[false,true]},{"group":"Content \u0026 Scheduling"},{"feature":"Day-parting \u0026 date scheduling","values":[true,true]},{"feature":"Playlists \u0026 campaigns","values":[true,true]},{"feature":"Menu boards","values":[true,true]},{"feature":"Interactive touchscreen actions","values":[true,true]},{"feature":"Overlay layouts","values":[true,true]},{"feature":"Geo-location scheduling","values":[false,true]},{"feature":"Weather-triggered scheduling","values":[false,true]},{"feature":"Video wall","values":[false,true]},{"feature":"Ad campaigns \u0026 plays-per-hour control","values":[false,true]},{"group":"Data \u0026 Integrations"},{"feature":"DataSets, RSS \u0026 tickers","values":[true,true]},{"feature":"Embedded HTML \u0026 web pages","values":[true,true]},{"feature":"Social feeds","values":[true,true]},{"feature":"Canva integration","values":[true,true]},{"feature":"Dashboard Connector","values":[false,true]},{"feature":"Custom API integrations","values":[false,true]},{"feature":"SSP Connector (ad monetisation)","values":[false,true]},{"group":"Analytics \u0026 Reporting"},{"feature":"Proof of Play reporting","values":["30-day retention","12+ month retention"]},{"feature":"Scheduled PDF reports","values":[false,true]},{"feature":"Audience Reporting","values":[false,true]},{"feature":"Display health monitoring","values":[true,true]},{"group":"Users \u0026 Security"},{"feature":"Unlimited user seats","values":[true,true]},{"feature":"Predefined roles (admin/editor/viewer)","values":[true,true]},{"feature":"Custom user roles \u0026 feature access","values":[false,true]},{"feature":"Two-factor authentication","values":[true,true]},{"feature":"SSO (SAML / CAS)","values":[false,true]},{"feature":"Audit trail","values":["7-day retention","Extended retention"]},{"group":"Display Management"},{"feature":"Screen power on/off control","values":[true,true]},{"feature":"Offline playback","values":[true,true]},{"feature":"Portrait / landscape","values":[true,true]},{"feature":"Email alerts (player offline)","values":[true,true]},{"feature":"Periodic screenshots","values":[false,true]},{"feature":"Display map view","values":[false,true]},{"feature":"Shell commands \u0026 RS232","values":[false,true]},{"group":"Support \u0026 Services"},{"feature":"Email support","values":["Next-business-day",true]},{"feature":"Priority support","values":[false,"4-hour SLA"]},{"feature":"Dedicated account manager","values":[false,true]},{"feature":"In-house creative services","values":[false,"Included hours"]},{"feature":"White-glove onboarding","values":[false,true]}]} /-->
|
||||
|
||||
<!-- wp:oribi/intro-section {"variant":"normal","label":"Try Before You Commit","heading":"Want to Explore the Platform First?","description":"Request access to our live demo instance and take the full CMS for a spin — create content, set up schedules, and see exactly how it works. No credit card, no obligation.","visual":""} /-->
|
||||
<!-- wp:oribi/intro-section {"label":"Try Before You Commit","heading":"Want to Explore the Platform First?","description":"Request access to our live demo instance and take the full CMS for a spin — create content, set up schedules, and see exactly how it works. No credit card, no obligation."} /-->
|
||||
|
||||
<!-- wp:oribi/cta-banner {"heading":"Questions About Pricing?","text":"We're happy to walk you through the plans, build a custom quote, or set up a demo so you can see the value firsthand.","btnText":"Get in Touch","btnUrl":"/contact"} /-->
|
||||
ORIBI_SYNC_CONTENT;
|
||||
|
||||
@@ -1984,6 +1984,648 @@ p:last-child { margin-bottom: 0; }
|
||||
.platform-row.reverse .platform-visual { order: unset; }
|
||||
}
|
||||
|
||||
/* Dashboard visual - remove framed background */
|
||||
.platform-visual.has-dashboard {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
border-radius: 0;
|
||||
aspect-ratio: unset;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* ── Dashboard TV frame ────────────────────────── */
|
||||
.dashboard-tv {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.dashboard-tv__body {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 4px solid var(--color-bg-alt);
|
||||
border-radius: 6px 6px 4px 4px;
|
||||
outline: 1px solid var(--color-border);
|
||||
padding: 3px;
|
||||
position: relative;
|
||||
box-shadow: 0 14px 48px rgba(0,0,0,0.55);
|
||||
}
|
||||
.dashboard-tv__body::after {
|
||||
content: '\25B6';
|
||||
position: absolute;
|
||||
bottom: -13px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 8px;
|
||||
color: rgba(74,222,128,0.7);
|
||||
}
|
||||
.dashboard-tv__screen {
|
||||
width: 100%;
|
||||
aspect-ratio: 16/9;
|
||||
background: #111;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.dashboard-tv__screen .dashboard-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.dashboard-tv__feet {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 60%;
|
||||
max-width: 300px;
|
||||
}
|
||||
.dashboard-tv__foot {
|
||||
width: 12px;
|
||||
height: 8px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
/* ── 10b. Device Animator ───────────────────────────────────── */
|
||||
.platform-visual.has-anim {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
border-radius: 0;
|
||||
aspect-ratio: unset;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
}
|
||||
.da-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
/* Each device panel – hidden by default, centred in stage */
|
||||
.da-device {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0.88);
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
transition: opacity 0.55s cubic-bezier(0.4,0,0.2,1),
|
||||
transform 0.55s cubic-bezier(0.4,0,0.2,1);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
.da-device.is-active {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
.da-device.is-leaving {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(1.07);
|
||||
}
|
||||
/* Screen surface */
|
||||
.da-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
180deg,
|
||||
transparent,
|
||||
transparent 3px,
|
||||
rgba(0,0,0,0.10) 3px,
|
||||
rgba(0,0,0,0.10) 4px
|
||||
),
|
||||
linear-gradient(135deg, #0c1016 0%, #151c28 60%, #0c1016 100%);
|
||||
}
|
||||
.da-screen::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: -10%;
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
background: radial-gradient(ellipse, rgba(74,222,128,0.12) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes da-scan {
|
||||
0% { top: -6%; opacity: 0; }
|
||||
5% { opacity: 1; }
|
||||
95% { opacity: 1; }
|
||||
100% { top: 106%; opacity: 0; }
|
||||
}
|
||||
.da-screen::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, rgba(74,222,128,0.28), transparent);
|
||||
animation: da-scan 3s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Device label */
|
||||
.da-label {
|
||||
display: block;
|
||||
margin-top: 11px;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
/* ── Tablet ────────────────────────────────────── */
|
||||
.da-tablet .da-body {
|
||||
width: 128px;
|
||||
height: 194px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: 14px;
|
||||
padding: 10px 8px 14px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,0.50);
|
||||
}
|
||||
.da-tablet .da-body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--color-border);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.da-tablet .da-body::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 36px;
|
||||
height: 3px;
|
||||
background: var(--color-border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* ── Small Monitor ─────────────────────────────── */
|
||||
.da-monitor-sm .da-body {
|
||||
width: 236px;
|
||||
height: 146px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 5px solid var(--color-bg-alt);
|
||||
border-radius: 6px;
|
||||
outline: 1px solid var(--color-border);
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
box-shadow: 0 10px 36px rgba(0,0,0,0.50);
|
||||
}
|
||||
.da-monitor-sm .da-body::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -9px;
|
||||
right: 8px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 5px var(--color-primary);
|
||||
}
|
||||
.da-monitor-sm .da-stand,
|
||||
.da-monitor-lg .da-stand { display: flex; flex-direction: column; align-items: center; }
|
||||
.da-monitor-sm .da-stem {
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
background: var(--color-bg-alt);
|
||||
border-left: 1px solid var(--color-border);
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
.da-monitor-sm .da-base {
|
||||
width: 68px;
|
||||
height: 5px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
/* ── Large Monitor ─────────────────────────────── */
|
||||
.da-monitor-lg .da-body {
|
||||
width: 298px;
|
||||
height: 177px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 4px solid var(--color-bg-alt);
|
||||
border-radius: 6px;
|
||||
outline: 1px solid var(--color-border);
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,0.50);
|
||||
}
|
||||
.da-monitor-lg .da-stem {
|
||||
width: 16px;
|
||||
height: 26px;
|
||||
background: var(--color-bg-alt);
|
||||
border-left: 1px solid var(--color-border);
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
.da-monitor-lg .da-base {
|
||||
width: 88px;
|
||||
height: 5px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
/* ── TV ────────────────────────────────────────── */
|
||||
.da-tv .da-body {
|
||||
width: 320px;
|
||||
height: 188px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 4px solid var(--color-bg-alt);
|
||||
border-radius: 6px 6px 4px 4px;
|
||||
outline: 1px solid var(--color-border);
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
box-shadow: 0 14px 48px rgba(0,0,0,0.55);
|
||||
}
|
||||
.da-tv .da-body::after {
|
||||
content: '\25B6';
|
||||
position: absolute;
|
||||
bottom: -13px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 8px;
|
||||
color: rgba(74,222,128,0.7);
|
||||
}
|
||||
.da-tv .da-feet { display: flex; justify-content: space-between; width: 180px; }
|
||||
.da-tv .da-foot {
|
||||
width: 12px;
|
||||
height: 8px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
/* ── Projector ─────────────────────────────────── */
|
||||
.da-projector .da-proj-layout { display: flex; flex-direction: column; align-items: center; }
|
||||
.da-projector .da-proj-body {
|
||||
width: 156px;
|
||||
height: 62px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 10px 10px 8px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
gap: 10px;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.45);
|
||||
position: relative;
|
||||
}
|
||||
.da-projector .da-proj-body::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 6px var(--color-primary);
|
||||
}
|
||||
.da-projector .da-proj-body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 8px;
|
||||
width: 28px;
|
||||
height: 8px;
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
var(--color-border) 0px,
|
||||
var(--color-border) 2px,
|
||||
transparent 2px,
|
||||
transparent 5px
|
||||
);
|
||||
border-radius: 1px;
|
||||
}
|
||||
.da-projector .da-lens {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background: #080c12;
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
box-shadow: inset 0 0 8px rgba(0,0,0,0.8);
|
||||
}
|
||||
.da-projector .da-lens::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 5px;
|
||||
background: radial-gradient(circle at 35% 35%, rgba(74,222,128,0.30) 0%, #080c12 65%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.da-projector .da-beam {
|
||||
width: 240px;
|
||||
height: 50px;
|
||||
clip-path: polygon(31% 0%, 69% 0%, 100% 100%, 0% 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(74,222,128,0.07) 0%,
|
||||
rgba(74,222,128,0.02) 100%
|
||||
);
|
||||
}
|
||||
.da-projector .da-proj-screen {
|
||||
width: 240px;
|
||||
height: 72px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
padding: 3px;
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,0.40);
|
||||
}
|
||||
/* ── Video Wall (2×2) ──────────────────────────── */
|
||||
.da-vwall .da-vwall-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 5px;
|
||||
background: #0a0d12;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 14px 48px rgba(0,0,0,0.60);
|
||||
}
|
||||
.da-vwall .da-panel {
|
||||
width: 148px;
|
||||
height: 90px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 2px solid var(--color-bg-alt);
|
||||
padding: 2px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
}
|
||||
.da-vwall .da-panel:nth-child(2) .da-screen::after { animation-delay: -0.75s; }
|
||||
.da-vwall .da-panel:nth-child(3) .da-screen::after { animation-delay: -1.5s; }
|
||||
.da-vwall .da-panel:nth-child(4) .da-screen::after { animation-delay: -2.25s; }
|
||||
/* ── Responsive scale-down ─────────────────────── */
|
||||
@media (max-width: 900px) {
|
||||
.da-device { transform: translate(-50%,-50%) scale(0.76); }
|
||||
.da-device.is-active { transform: translate(-50%,-50%) scale(0.84); }
|
||||
.da-device.is-leaving { transform: translate(-50%,-50%) scale(0.91); }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.da-device { transform: translate(-50%,-50%) scale(0.56); }
|
||||
.da-device.is-active { transform: translate(-50%,-50%) scale(0.64); }
|
||||
.da-device.is-leaving { transform: translate(-50%,-50%) scale(0.70); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.da-device { transition: none; }
|
||||
.da-screen::after { animation: none; }
|
||||
}
|
||||
|
||||
/* ── 10c. TV Stick Plug Animation ──────────────────────────── */
|
||||
.platform-visual.has-tv-stick {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
border-radius: 0;
|
||||
aspect-ratio: unset;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
}
|
||||
.ts-stage {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* ── TV ── */
|
||||
.ts-tv {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
.ts-tv__body {
|
||||
width: 320px;
|
||||
height: 188px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 4px solid var(--color-bg-alt);
|
||||
border-radius: 6px 6px 4px 4px;
|
||||
outline: 1px solid var(--color-border);
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
box-shadow: 0 14px 48px rgba(0,0,0,0.55);
|
||||
}
|
||||
.ts-tv__screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
180deg,
|
||||
transparent,
|
||||
transparent 3px,
|
||||
rgba(0,0,0,0.10) 3px,
|
||||
rgba(0,0,0,0.10) 4px
|
||||
),
|
||||
linear-gradient(135deg, #0c1016 0%, #151c28 60%, #0c1016 100%);
|
||||
}
|
||||
/* Subtle green ambient glow on screen */
|
||||
.ts-tv__screen::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: -10%;
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
background: radial-gradient(ellipse, rgba(74,222,128,0.12) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Scan line on screen */
|
||||
.ts-tv__screen::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, rgba(74,222,128,0.28), transparent);
|
||||
animation: da-scan 3s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Screen glow when stick plugs in */
|
||||
.ts-stage.is-plugged .ts-tv__screen {
|
||||
animation: ts-screen-glow 0.8s ease 0.1s both;
|
||||
}
|
||||
@keyframes ts-screen-glow {
|
||||
0% { filter: brightness(1); }
|
||||
40% { filter: brightness(1.25); }
|
||||
100% { filter: brightness(1); }
|
||||
}
|
||||
/* HDMI port on back-right of TV */
|
||||
.ts-tv__port {
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 14px;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid var(--color-border);
|
||||
border-left: none;
|
||||
border-radius: 0 2px 2px 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.ts-tv__port::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
width: 3px;
|
||||
height: 8px;
|
||||
background: #333;
|
||||
border-radius: 0 1px 1px 0;
|
||||
}
|
||||
/* Feet */
|
||||
.ts-tv__feet {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 180px;
|
||||
}
|
||||
.ts-tv__foot {
|
||||
width: 12px;
|
||||
height: 8px;
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
/* ── Stick device ── */
|
||||
.ts-stick {
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(80px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.ts-stick__body {
|
||||
width: 68px;
|
||||
height: 26px;
|
||||
background: linear-gradient(180deg, #f5f5f5, #e0e0e0);
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0,0,0,0.25),
|
||||
inset 0 1px 0 rgba(255,255,255,0.6);
|
||||
}
|
||||
/* Brand logo area subtle inset */
|
||||
.ts-stick__body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 10px;
|
||||
width: 28px;
|
||||
height: 12px;
|
||||
background: rgba(0,0,0,0.04);
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* LED indicator */
|
||||
.ts-stick__led {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #999;
|
||||
border-radius: 50%;
|
||||
transition: background 0.4s ease, box-shadow 0.4s ease;
|
||||
}
|
||||
.ts-stage.is-plugged .ts-stick__led {
|
||||
background: #4CAF50;
|
||||
box-shadow: 0 0 6px rgba(76,175,80,0.8);
|
||||
}
|
||||
/* HDMI connector */
|
||||
.ts-stick__connector {
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
background: linear-gradient(180deg, #888, #666);
|
||||
border-radius: 0 2px 2px 0;
|
||||
margin-left: -1px;
|
||||
position: relative;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
}
|
||||
.ts-stick__connector::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #555;
|
||||
border-radius: 1px;
|
||||
}
|
||||
/* ── Plug-in animation ── */
|
||||
.ts-stage.is-animating .ts-stick {
|
||||
animation: ts-slide-in 1.4s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
|
||||
}
|
||||
@keyframes ts-slide-in {
|
||||
0% { opacity: 0; transform: translateY(-50%) translateX(80px); }
|
||||
20% { opacity: 1; transform: translateY(-50%) translateX(60px); }
|
||||
70% { transform: translateY(-50%) translateX(8px); }
|
||||
85% { transform: translateY(-50%) translateX(12px); }
|
||||
100% { opacity: 1; transform: translateY(-50%) translateX(6px); }
|
||||
}
|
||||
/* Responsive scale-down */
|
||||
@media (max-width: 900px) {
|
||||
.ts-tv__body { width: 260px; height: 152px; }
|
||||
.ts-tv__feet { width: 140px; }
|
||||
.ts-stick__body { width: 56px; height: 22px; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.ts-tv__body { width: 200px; height: 118px; }
|
||||
.ts-tv__feet { width: 110px; }
|
||||
.ts-stick__body { width: 46px; height: 18px; }
|
||||
.ts-stick__connector { width: 10px; height: 8px; }
|
||||
.ts-stick__body::before { top: 4px; left: 6px; width: 20px; height: 8px; }
|
||||
.ts-stick__led { width: 3px; height: 3px; right: 5px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ts-stage .ts-stick {
|
||||
opacity: 1;
|
||||
transform: translateY(-50%) translateX(6px);
|
||||
}
|
||||
.ts-stage.is-animating .ts-stick { animation: none; }
|
||||
.ts-stage.is-plugged .ts-tv__screen { animation: none; }
|
||||
.ts-stage.is-plugged .ts-stick__led {
|
||||
background: #4CAF50;
|
||||
box-shadow: 0 0 6px rgba(76,175,80,0.8);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 11. Page Hero (inner pages) ───────────────────────────── */
|
||||
.page-hero {
|
||||
background: #111111;
|
||||
@@ -2255,6 +2897,24 @@ p:last-child { margin-bottom: 0; }
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ── Dashboard Chart ───────────────────────────────────── */
|
||||
.dashboard-chart {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
user-select: none;
|
||||
overflow: visible;
|
||||
}
|
||||
/* Reduced-motion: JS already checks the media query, belt-and-suspenders */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dashboard-chart .bar,
|
||||
.dashboard-chart .pie-segment { transition: none !important; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-chart-container { padding: 1rem; border-radius: var(--radius-md, 12px); }
|
||||
}
|
||||
|
||||
.cta-banner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@@ -2606,7 +3266,7 @@ p:last-child { margin-bottom: 0; }
|
||||
border-color: rgba(255,255,255,.25);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .platform-visual:not(.has-img) {
|
||||
[data-theme="dark"] .platform-visual:not(.has-img):not(.has-dashboard) {
|
||||
background: var(--color-bg-alt);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
157
theme/assets/js/dashboard-animator.js
Normal file
157
theme/assets/js/dashboard-animator.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Dashboard Chart Animator
|
||||
* Gently animates SVG bar charts, line graph, and pie chart.
|
||||
* Pauses off-screen via IntersectionObserver for performance.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var SPEED = 0.002; // phase increment per frame
|
||||
var BAR_H = 120; // max bar height (SVG units)
|
||||
var BAR_MIN = 0.15; // min bar ratio
|
||||
var LINE_PTS = 8;
|
||||
var LINE_W = 340; // line graph width in SVG units
|
||||
var PIE_R = 55; // pie chart radius
|
||||
|
||||
var DARK = { text: '#E0E0E0', muted: '#9E9E9E', border: '#333', center: '#222' };
|
||||
var LIGHT = { text: '#333333', muted: '#666666', border: '#E0E0E0', center: '#fff' };
|
||||
|
||||
function isDark() { return document.documentElement.getAttribute('data-theme') === 'dark'; }
|
||||
function pal() { return isDark() ? DARK : LIGHT; }
|
||||
|
||||
function wave(t, off) {
|
||||
return Math.max(0, Math.min(1,
|
||||
0.55 +
|
||||
Math.sin(t + off) * 0.25 +
|
||||
Math.sin(t * 1.8 + off * 1.3) * 0.15
|
||||
));
|
||||
}
|
||||
|
||||
function makeState(svg) {
|
||||
return {
|
||||
svg: svg,
|
||||
bars1: svg.querySelectorAll('#bars-group-1 .bar'),
|
||||
bars2: svg.querySelectorAll('#bars-group-2 .bar'),
|
||||
vals1: svg.querySelectorAll('#values-group-1 text'),
|
||||
vals2: svg.querySelectorAll('#values-group-2 text'),
|
||||
linePath: svg.querySelector('#line-path'),
|
||||
lineFill: svg.querySelector('#line-fill'),
|
||||
pieSegs: svg.querySelectorAll('.pie-segment'),
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
paused: false,
|
||||
themeN: 0
|
||||
};
|
||||
}
|
||||
|
||||
function updateBars(bars, vals, st, pct) {
|
||||
for (var i = 0; i < bars.length; i++) {
|
||||
var v = Math.max(BAR_MIN, wave(st.phase, i * 1.1));
|
||||
var h = v * BAR_H;
|
||||
bars[i].setAttribute('height', h);
|
||||
bars[i].setAttribute('y', BAR_H - h);
|
||||
}
|
||||
for (var j = 0; j < Math.min(bars.length, vals.length); j++) {
|
||||
var val = Math.max(BAR_MIN, wave(st.phase, j * 1.1));
|
||||
vals[j].textContent = pct ? Math.round(val * 100) + '%' : Math.round(val * 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function updateLine(st) {
|
||||
if (!st.linePath) return;
|
||||
var d = 'M';
|
||||
for (var i = 0; i < LINE_PTS; i++) {
|
||||
var x = (i / (LINE_PTS - 1)) * LINE_W;
|
||||
var y = 25 + (1 - wave(st.phase * 0.8, i * 0.9)) * 110;
|
||||
d += (i ? ' L' : '') + x.toFixed(1) + ',' + y.toFixed(1);
|
||||
}
|
||||
st.linePath.setAttribute('d', d);
|
||||
if (st.lineFill) st.lineFill.setAttribute('d', d + ' L' + LINE_W + ',145 L0,145 Z');
|
||||
}
|
||||
|
||||
/* Pie: each segment gently shifts its slice size, no spinning */
|
||||
function updatePie(st) {
|
||||
if (!st.pieSegs.length) return;
|
||||
var n = st.pieSegs.length;
|
||||
// Generate proportional weights that shift over time
|
||||
var weights = [], total = 0;
|
||||
for (var i = 0; i < n; i++) {
|
||||
var w = 0.5 + wave(st.phase * 0.4, i * 2.0) * 0.5;
|
||||
weights.push(w);
|
||||
total += w;
|
||||
}
|
||||
// Convert to cumulative angles
|
||||
var angle = 0;
|
||||
for (var j = 0; j < n; j++) {
|
||||
var sweep = (weights[j] / total) * 360;
|
||||
var startA = angle * Math.PI / 180;
|
||||
var endA = (angle + sweep) * Math.PI / 180;
|
||||
// Large-arc flag needed when sweep > 180
|
||||
var large = sweep > 180 ? 1 : 0;
|
||||
var r = PIE_R;
|
||||
var x1 = Math.sin(startA) * r, y1 = -Math.cos(startA) * r;
|
||||
var x2 = Math.sin(endA) * r, y2 = -Math.cos(endA) * r;
|
||||
var path = st.pieSegs[j].querySelector('path');
|
||||
if (path) {
|
||||
path.setAttribute('d',
|
||||
'M0,0 L' + x1.toFixed(2) + ',' + y1.toFixed(2) +
|
||||
' A' + r + ',' + r + ' 0 ' + large + ',1 ' +
|
||||
x2.toFixed(2) + ',' + y2.toFixed(2) + ' Z'
|
||||
);
|
||||
}
|
||||
// Remove any rotation transform — segments are positioned by path geometry
|
||||
st.pieSegs[j].removeAttribute('transform');
|
||||
angle += sweep;
|
||||
}
|
||||
}
|
||||
|
||||
function applyTheme(st) {
|
||||
var c = pal(), q = st.svg.querySelectorAll.bind(st.svg), all, k;
|
||||
all = q('.ct'); for (k = 0; k < all.length; k++) all[k].setAttribute('fill', c.text);
|
||||
all = q('.cl'); for (k = 0; k < all.length; k++) all[k].setAttribute('fill', c.muted);
|
||||
all = q('.cv'); for (k = 0; k < all.length; k++) all[k].setAttribute('fill', c.text);
|
||||
all = q('.grid-line'); for (k = 0; k < all.length; k++) all[k].setAttribute('stroke', c.border);
|
||||
var cc = st.svg.querySelector('#pie-center');
|
||||
if (cc) { cc.setAttribute('fill', c.center); cc.setAttribute('stroke', c.border); }
|
||||
var pt = st.svg.querySelector('#pie-center-text');
|
||||
if (pt) pt.setAttribute('fill', c.text);
|
||||
}
|
||||
|
||||
function tick(st) {
|
||||
if (!st.paused) {
|
||||
st.phase += SPEED * 16;
|
||||
updateBars(st.bars1, st.vals1, st, true);
|
||||
updateBars(st.bars2, st.vals2, st, false);
|
||||
updateLine(st);
|
||||
updatePie(st);
|
||||
if (++st.themeN > 30) { st.themeN = 0; applyTheme(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.svg);
|
||||
}
|
||||
|
||||
function boot() {
|
||||
var svgs = document.querySelectorAll('.dashboard-chart');
|
||||
if (!svgs.length) return;
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
for (var i = 0; i < svgs.length; i++) {
|
||||
if (svgs[i]._dbAnim) continue;
|
||||
var st = makeState(svgs[i]);
|
||||
svgs[i]._dbAnim = st;
|
||||
applyTheme(st);
|
||||
tick(st);
|
||||
observe(st);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
})();
|
||||
@@ -610,3 +610,99 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
paintBg();
|
||||
start();
|
||||
})();
|
||||
|
||||
/* ── Device Animator ──────────────────────────────────────────────────────── */
|
||||
(function () {
|
||||
const DEVICES = [
|
||||
'da-tablet', 'da-monitor-sm', 'da-monitor-lg',
|
||||
'da-tv', 'da-projector', 'da-vwall'
|
||||
];
|
||||
const DWELL = 2500; // ms each device is shown
|
||||
|
||||
document.querySelectorAll('.da-stage').forEach(function (stage) {
|
||||
let current = 0;
|
||||
let timer = null;
|
||||
|
||||
// Collect the 6 device panels
|
||||
const panels = DEVICES.map(function (cls) {
|
||||
return stage.querySelector('.' + cls);
|
||||
});
|
||||
|
||||
function show(idx) {
|
||||
panels.forEach(function (el, i) {
|
||||
if (!el) return;
|
||||
el.classList.toggle('is-active', i === idx);
|
||||
el.classList.remove('is-leaving');
|
||||
});
|
||||
}
|
||||
|
||||
function advance() {
|
||||
const leaving = current;
|
||||
current = (current + 1) % DEVICES.length;
|
||||
if (panels[leaving]) panels[leaving].classList.add('is-leaving');
|
||||
show(current);
|
||||
setTimeout(function () {
|
||||
if (panels[leaving]) panels[leaving].classList.remove('is-leaving');
|
||||
}, 600);
|
||||
}
|
||||
|
||||
function startCycle() {
|
||||
if (timer) return;
|
||||
timer = setInterval(advance, DWELL);
|
||||
}
|
||||
|
||||
function stopCycle() {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
|
||||
// Honour reduced-motion preference: show first device statically
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
show(0);
|
||||
return;
|
||||
}
|
||||
|
||||
show(0);
|
||||
startCycle();
|
||||
|
||||
// Pause when scrolled out of view to save resources
|
||||
if ('IntersectionObserver' in window) {
|
||||
new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (e) {
|
||||
e.isIntersecting ? startCycle() : stopCycle();
|
||||
});
|
||||
}, { threshold: 0.2 }).observe(stage);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
/* ── TV Stick Plug Animation ─────────────────────────────────────────────── */
|
||||
(function () {
|
||||
var stages = document.querySelectorAll('[data-tv-stick-anim]');
|
||||
if (!stages.length) return;
|
||||
|
||||
// Honour reduced-motion: show plugged-in state immediately
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
stages.forEach(function (stage) {
|
||||
stage.classList.add('is-plugged');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (e) {
|
||||
if (e.isIntersecting) {
|
||||
var stage = e.target;
|
||||
stage.classList.add('is-animating');
|
||||
// Add plugged state after slide-in completes (1.4s)
|
||||
setTimeout(function () {
|
||||
stage.classList.add('is-plugged');
|
||||
}, 1400);
|
||||
io.unobserve(stage);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
stages.forEach(function (stage) { io.observe(stage); });
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -553,6 +553,8 @@ add_action( 'init', function () {
|
||||
'imgUrl' => [ 'type' => 'string', 'default' => '' ],
|
||||
'imgAlt' => [ 'type' => 'string', 'default' => '' ],
|
||||
'imgWidth' => [ 'type' => 'number', 'default' => 300 ],
|
||||
'deviceAnim' => [ 'type' => 'boolean', 'default' => false ],
|
||||
'tvStick' => [ 'type' => 'boolean', 'default' => false ],
|
||||
],
|
||||
'supports' => $block_supports,
|
||||
'render_callback' => 'oribi_render_platform_row',
|
||||
@@ -1338,7 +1340,128 @@ function oribi_render_platform_row( $a ) {
|
||||
$img_alt = ! empty( $a['imgAlt'] ) ? $a['imgAlt'] : '';
|
||||
$img_w = ! empty( $a['imgWidth'] ) ? intval( $a['imgWidth'] ) : 300;
|
||||
|
||||
if ( $img_url ) {
|
||||
// Only render animated dashboard when explicitly flagged
|
||||
$is_dashboard = ! empty( $a['isDashboard'] );
|
||||
|
||||
if ( $is_dashboard ) {
|
||||
// Render animated dashboard chart SVG
|
||||
// Text uses class hooks: .ct = title, .cl = label, .cv = value
|
||||
// JS will dynamically set fill colours based on data-theme
|
||||
$visual_html = '<div class="dashboard-tv" data-dashboard-container="true">
|
||||
<div class="dashboard-tv__body">
|
||||
<div class="dashboard-tv__screen">
|
||||
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg" class="dashboard-chart" role="img" aria-label="Animated dashboard charts">
|
||||
<defs>
|
||||
<linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#004225" stop-opacity="1"/>
|
||||
<stop offset="100%" stop-color="#4CAF50" stop-opacity=".8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#4CAF50" stop-opacity=".3"/>
|
||||
<stop offset="100%" stop-color="#4CAF50" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- ── Top-left: Performance bars ── -->
|
||||
<g transform="translate(35,20)">
|
||||
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Performance</text>
|
||||
<g id="bars-group-1" transform="translate(0,25)">
|
||||
<rect class="bar" x="0" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="40" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="80" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="120" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="160" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
</g>
|
||||
<g transform="translate(0,152)">
|
||||
<text class="cl" x="14" y="0" font-size="10" text-anchor="middle" fill="#666">API</text>
|
||||
<text class="cl" x="54" y="0" font-size="10" text-anchor="middle" fill="#666">Cache</text>
|
||||
<text class="cl" x="94" y="0" font-size="10" text-anchor="middle" fill="#666">DB</text>
|
||||
<text class="cl" x="134" y="0" font-size="10" text-anchor="middle" fill="#666">Queue</text>
|
||||
<text class="cl" x="174" y="0" font-size="10" text-anchor="middle" fill="#666">Worker</text>
|
||||
</g>
|
||||
<g id="values-group-1" transform="translate(0,168)">
|
||||
<text class="cv" x="14" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="54" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="94" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="134" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="174" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ── Top-right: Requests/sec bars ── -->
|
||||
<g transform="translate(430,20)">
|
||||
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Requests/sec</text>
|
||||
<g id="bars-group-2" transform="translate(0,25)">
|
||||
<rect class="bar" x="0" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="40" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="80" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="120" y="120" width="28" height="0" fill="url(#barGradient)"/>
|
||||
</g>
|
||||
<g transform="translate(0,152)">
|
||||
<text class="cl" x="14" y="0" font-size="10" text-anchor="middle" fill="#666">Read</text>
|
||||
<text class="cl" x="54" y="0" font-size="10" text-anchor="middle" fill="#666">Write</text>
|
||||
<text class="cl" x="94" y="0" font-size="10" text-anchor="middle" fill="#666">Update</text>
|
||||
<text class="cl" x="134" y="0" font-size="10" text-anchor="middle" fill="#666">Delete</text>
|
||||
</g>
|
||||
<g id="values-group-2" transform="translate(0,168)">
|
||||
<text class="cv" x="14" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
<text class="cv" x="54" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
<text class="cv" x="94" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
<text class="cv" x="134" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ── Bottom-left: Traffic Trend line ── -->
|
||||
<g id="line-graph" transform="translate(35,245)">
|
||||
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Traffic Trend</text>
|
||||
<g transform="translate(0,25)">
|
||||
<line class="grid-line" x1="0" y1="0" x2="340" y2="0" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
<line class="grid-line" x1="0" y1="40" x2="340" y2="40" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
<line class="grid-line" x1="0" y1="80" x2="340" y2="80" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
<line class="grid-line" x1="0" y1="120" x2="340" y2="120" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
</g>
|
||||
<g transform="translate(0,25)">
|
||||
<path id="line-fill" d="M0,80 L340,80 L340,145 L0,145 Z" fill="url(#lineGradient)"/>
|
||||
<path id="line-path" d="M0,80 L340,80" stroke="#4CAF50" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ── Bottom-right: Distribution pie ── -->
|
||||
<g transform="translate(490,245)">
|
||||
<text class="ct" x="100" y="0" font-size="14" font-weight="600" text-anchor="middle" fill="#333">Distribution</text>
|
||||
<g transform="translate(100,90)">
|
||||
<g class="pie-segment" transform="rotate(0)">
|
||||
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#004225" opacity=".9"/>
|
||||
</g>
|
||||
<g class="pie-segment" transform="rotate(90)">
|
||||
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#4CAF50" opacity=".8"/>
|
||||
</g>
|
||||
<g class="pie-segment" transform="rotate(180)">
|
||||
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#f59e0b" opacity=".7"/>
|
||||
</g>
|
||||
<g class="pie-segment" transform="rotate(270)">
|
||||
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#ef4444" opacity=".7"/>
|
||||
</g>
|
||||
<circle id="pie-center" cx="0" cy="0" r="22" fill="#fff" stroke="#E0E0E0" stroke-width="1"/>
|
||||
<text id="pie-center-text" x="0" y="5" text-anchor="middle" font-size="13" font-weight="600" fill="#333">100%</text>
|
||||
</g>
|
||||
<g transform="translate(30,170)">
|
||||
<rect x="0" y="0" width="8" height="8" fill="#004225"/>
|
||||
<text class="cl" x="12" y="8" font-size="11" fill="#666">Service A</text>
|
||||
<rect x="0" y="15" width="8" height="8" fill="#4CAF50"/>
|
||||
<text class="cl" x="12" y="23" font-size="11" fill="#666">Service B</text>
|
||||
<rect x="100" y="0" width="8" height="8" fill="#f59e0b"/>
|
||||
<text class="cl" x="112" y="8" font-size="11" fill="#666">Service C</text>
|
||||
<rect x="100" y="15" width="8" height="8" fill="#ef4444"/>
|
||||
<text class="cl" x="112" y="23" font-size="11" fill="#666">Service D</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div></div>
|
||||
<div class="dashboard-tv__feet"><div class="dashboard-tv__foot"></div><div class="dashboard-tv__foot"></div></div>
|
||||
</div>';
|
||||
$visual_cls = 'platform-visual has-dashboard';
|
||||
} elseif ( $img_url ) {
|
||||
$img_style = 'width:' . $img_w . 'px;max-width:100%;height:auto;border-radius:var(--radius-sm);object-fit:contain;display:block;margin-inline:auto;';
|
||||
if ( $img_id ) {
|
||||
$visual_html = wp_get_attachment_image( $img_id, 'full', false, [ 'style' => $img_style, 'alt' => $img_alt ] );
|
||||
@@ -1346,6 +1469,35 @@ function oribi_render_platform_row( $a ) {
|
||||
$visual_html = '<img src="' . esc_url( $img_url ) . '" alt="' . esc_attr( $img_alt ) . '" style="' . esc_attr( $img_style ) . '">';
|
||||
}
|
||||
$visual_cls = 'platform-visual has-img';
|
||||
} elseif ( ! empty( $a['deviceAnim'] ) ) {
|
||||
$da = '<div class="da-stage" aria-hidden="true">';
|
||||
$da .= '<div class="da-device da-tablet"><div class="da-body"><div class="da-screen"></div></div><span class="da-label">Tablet</span></div>';
|
||||
$da .= '<div class="da-device da-monitor-sm"><div class="da-body"><div class="da-screen"></div></div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Small Monitor</span></div>';
|
||||
$da .= '<div class="da-device da-monitor-lg"><div class="da-body"><div class="da-screen"></div></div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Large Monitor</span></div>';
|
||||
$da .= '<div class="da-device da-tv"><div class="da-body"><div class="da-screen"></div></div><div class="da-feet"><div class="da-foot"></div><div class="da-foot"></div></div><span class="da-label">TV</span></div>';
|
||||
$da .= '<div class="da-device da-projector"><div class="da-proj-layout"><div class="da-proj-body"><div class="da-lens"></div></div><div class="da-beam"></div><div class="da-proj-screen"><div class="da-screen"></div></div></div><span class="da-label">Projector</span></div>';
|
||||
$da .= '<div class="da-device da-vwall"><div class="da-vwall-grid"><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div></div><span class="da-label">Video Wall</span></div>';
|
||||
$da .= '</div>';
|
||||
$visual_html = $da;
|
||||
$visual_cls = 'platform-visual has-anim';
|
||||
} elseif ( ! empty( $a['tvStick'] ) ) {
|
||||
$ts = '<div class="ts-stage" data-tv-stick-anim aria-hidden="true">';
|
||||
$ts .= '<div class="ts-tv">';
|
||||
$ts .= '<div class="ts-tv__body">';
|
||||
$ts .= '<div class="ts-tv__screen"></div>';
|
||||
$ts .= '<div class="ts-tv__port"></div>';
|
||||
$ts .= '</div>';
|
||||
$ts .= '<div class="ts-tv__feet"><div class="ts-tv__foot"></div><div class="ts-tv__foot"></div></div>';
|
||||
$ts .= '</div>';
|
||||
$ts .= '<div class="ts-stick">';
|
||||
$ts .= '<div class="ts-stick__body">';
|
||||
$ts .= '<div class="ts-stick__led"></div>';
|
||||
$ts .= '</div>';
|
||||
$ts .= '<div class="ts-stick__connector"></div>';
|
||||
$ts .= '</div>';
|
||||
$ts .= '</div>';
|
||||
$visual_html = $ts;
|
||||
$visual_cls = 'platform-visual has-tv-stick';
|
||||
} else {
|
||||
$visual_html = oribi_render_icon( $a['visual'] ?? '' );
|
||||
$visual_cls = 'platform-visual';
|
||||
|
||||
@@ -29,6 +29,15 @@ add_action( 'wp_enqueue_scripts', function () {
|
||||
true
|
||||
);
|
||||
|
||||
// Dashboard chart animator - smooth continuous animations for dashboard cards
|
||||
wp_enqueue_script(
|
||||
'oribi-dashboard-animator',
|
||||
ORIBI_URI . '/assets/js/dashboard-animator.js',
|
||||
[],
|
||||
ORIBI_VERSION . '.' . filemtime( ORIBI_DIR . '/assets/js/dashboard-animator.js' ),
|
||||
true
|
||||
);
|
||||
|
||||
// Localize AJAX endpoint for the contact form
|
||||
wp_localize_script( 'oribi-main', 'oribiAjax', [
|
||||
'url' => admin_url( 'admin-ajax.php' ),
|
||||
|
||||
Reference in New Issue
Block a user