Add new animations for retail, corporate, education, outdoor, live data, healthcare, transit, and fitness sectors
- Extend block attributes to include new animation options in index.php - Implement animation rendering logic for each sector in oribi_render_platform_row function - Enqueue new JavaScript file for solutions page animations in enqueue.php - Create solutions-animator.js to handle live data KPI and transit board animations
This commit is contained in:
@@ -11,18 +11,18 @@ return <<<'ORIBI_SYNC_CONTENT'
|
||||
<!-- wp:oribi/platform-section {"label":"Industries We Serve","heading":"One Platform, Tailored to Your Sector","lead":"We've worked with businesses across six key industries. Here's how our platform fits into each one."} -->
|
||||
<!-- wp:oribi/platform-row {"heading":"Hospitality","description":"Digital menu boards that update with your POS, lobby displays that guide guests, and promotional screens that drive upsells in bars and restaurants. Create a polished guest experience from the moment they walk in - and keep it fresh without touching a single printed sign. Multi-language scheduling lets you switch content for international guests automatically.","btnText":"Learn More","btnUrl":"/hospitality","hospitalityAnim":true} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Retail","description":"Launch promotions across every store instantly, spotlight seasonal products, and guide shoppers with in-store wayfinding. Retailers using digital signage see up to 30% more sales. Use day-parting to show breakfast offers in the morning and evening deals after 5pm. Interactive touchscreens turn browsing into self-service product lookup and ordering.","btnText":"Learn More","btnUrl":"/retail","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Retail","description":"Launch promotions across every store instantly, spotlight seasonal products, and guide shoppers with in-store wayfinding. Retailers using digital signage see up to 30% more sales. Use day-parting to show breakfast offers in the morning and evening deals after 5pm. Interactive touchscreens turn browsing into self-service product lookup and ordering.","btnText":"Learn More","btnUrl":"/retail","reversed":true,"retailAnim":true} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Corporate Office","description":"Meeting room displays with native Microsoft Teams integration, company-wide announcement boards, and live KPI dashboards in common areas. Turn your office into a connected, well-informed workplace where important information is always visible.","btnText":"Learn More","btnUrl":"/corporate"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Corporate Office","description":"Meeting room displays with native Microsoft Teams integration, company-wide announcement boards, and live KPI dashboards in common areas. Turn your office into a connected, well-informed workplace where important information is always visible.","btnText":"Learn More","btnUrl":"/corporate","corporateAnim":true} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Education","description":"Timetable displays, campus wayfinding, emergency alerts, and event boards - all managed centrally. Push safety notifications to every screen instantly with emergency override. Keep students, faculty, and visitors informed across multiple buildings without the overhead of maintaining individual screens.","btnText":"Learn More","btnUrl":"/education","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Education","description":"Timetable displays, campus wayfinding, emergency alerts, and event boards - all managed centrally. Push safety notifications to every screen instantly with emergency override. Keep students, faculty, and visitors informed across multiple buildings without the overhead of maintaining individual screens.","btnText":"Learn More","btnUrl":"/education","reversed":true,"educationAnim":true} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Outdoor Marketplace","description":"From farmers' markets to seasonal fairs, digital signage adds a professional edge without losing the character of your venue. Weather-resistant display options and built-in offline playback ensure your screens perform reliably, rain or shine.","btnText":"Learn More","btnUrl":"/outdoor"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Outdoor Marketplace","description":"From farmers' markets to seasonal fairs, digital signage adds a professional edge without losing the character of your venue. Weather-resistant display options and built-in offline playback ensure your screens perform reliably, rain or shine.","btnText":"Learn More","btnUrl":"/outdoor","outdoorAnim":true} /-->
|
||||
|
||||
<!-- wp:oribi/platform-row {"heading":"Live Data Displays","description":"Bring your web dashboards, real-time KPIs, and operational data to large-format screens. Ideal for operations centres, trading floors, and management war rooms where critical information needs to be visible to the entire team at a glance.","btnText":"See Features","btnUrl":"/features","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Healthcare","description":"Queue management displays, patient wayfinding, waiting room information screens, and public health messaging. Interactive kiosks handle self-check-in and directory navigation. Centrally managed across clinics, hospitals, and multi-site health networks \u2014 with platform security and role-based access control built in.","btnText":"Learn More","btnUrl":"/healthcare"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Transport & Transit","description":"Real-time departure boards, route information, service alerts, and passenger wayfinding across stations, terminals, and transport hubs. Offline playback ensures displays stay live even when connectivity is intermittent.","btnText":"See Devices","btnUrl":"/devices","reversed":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Fitness \u0026 Leisure","description":"Class timetables, motivational content, live performance metrics, and promotional offers on screens throughout gyms, leisure centres, and sports facilities. Schedule content by time of day to match peak hours and class rotations. Use multi-zone layouts to show live TV alongside class schedules and branded promotions simultaneously.","btnText":"Learn More","btnUrl":"/fitness"} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Live Data Displays","description":"Bring your web dashboards, real-time KPIs, and operational data to large-format screens. Ideal for operations centres, trading floors, and management war rooms where critical information needs to be visible to the entire team at a glance.","btnText":"See Features","btnUrl":"/features","reversed":true,"liveDataAnim":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Healthcare","description":"Queue management displays, patient wayfinding, waiting room information screens, and public health messaging. Interactive kiosks handle self-check-in and directory navigation. Centrally managed across clinics, hospitals, and multi-site health networks — with platform security and role-based access control built in.","btnText":"Learn More","btnUrl":"/healthcare","healthcareAnim":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Transport & Transit","description":"Real-time departure boards, route information, service alerts, and passenger wayfinding across stations, terminals, and transport hubs. Offline playback ensures displays stay live even when connectivity is intermittent.","btnText":"See Devices","btnUrl":"/devices","reversed":true,"transitAnim":true} /-->
|
||||
<!-- wp:oribi/platform-row {"heading":"Fitness \u0026 Leisure","description":"Class timetables, motivational content, live performance metrics, and promotional offers on screens throughout gyms, leisure centres, and sports facilities. Schedule content by time of day to match peak hours and class rotations. Use multi-zone layouts to show live TV alongside class schedules and branded promotions simultaneously.","btnText":"Learn More","btnUrl":"/fitness","fitnessAnim":true} /-->
|
||||
<!-- /wp:oribi/platform-section -->
|
||||
|
||||
<!-- wp:oribi/stat-section {"variant":"alt","label":"By the Numbers","heading":"The Results Speak for Themselves","lead":"Businesses that invest in digital signage see measurable returns across every metric that matters.","columns":4} -->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
291
theme/assets/js/solutions-animator.js
Normal file
291
theme/assets/js/solutions-animator.js
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
})();
|
||||
@@ -576,6 +576,15 @@ add_action('init', function () {
|
||||
'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],
|
||||
'galleryIds' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'number']],
|
||||
],
|
||||
'supports' => $block_supports,
|
||||
@@ -2062,6 +2071,355 @@ function oribi_render_platform_row($a)
|
||||
$visual_html = $bd;
|
||||
$visual_cls = 'platform-visual has-branded';
|
||||
}
|
||||
elseif (!empty($a['retailAnim'])) {
|
||||
/* ── Retail Sign: TV cycling 3 promo slides ── */
|
||||
$ra = '<div class="retail-stage" aria-hidden="true">';
|
||||
$ra .= '<div class="retail-tv">';
|
||||
$ra .= '<div class="retail-tv__body">';
|
||||
$ra .= '<div class="retail-tv__screen">';
|
||||
$ra .= '<div class="retail-slides">';
|
||||
|
||||
// Slide 1: Flash Sale
|
||||
$ra .= '<div class="retail-slide retail-slide--sale">';
|
||||
$ra .= '<div class="retail-promo">';
|
||||
$ra .= '<div class="retail-promo__eyebrow">Today Only</div>';
|
||||
$ra .= '<div class="retail-promo__headline">Flash Sale</div>';
|
||||
$ra .= '<div class="retail-promo__badge">Up to 40% Off</div>';
|
||||
$ra .= '<div class="retail-promo__items">';
|
||||
$ra .= '<div class="retail-promo__item"><span class="retail-promo__dot"></span><span class="retail-promo__lbl">Select Apparel</span></div>';
|
||||
$ra .= '<div class="retail-promo__item"><span class="retail-promo__dot"></span><span class="retail-promo__lbl">Footwear Range</span></div>';
|
||||
$ra .= '<div class="retail-promo__item"><span class="retail-promo__dot"></span><span class="retail-promo__lbl">Accessories</span></div>';
|
||||
$ra .= '</div>';
|
||||
$ra .= '<div class="retail-promo__cta">In-Store Only</div>';
|
||||
$ra .= '</div>';
|
||||
$ra .= '</div>';
|
||||
|
||||
// Slide 2: New Arrivals
|
||||
$ra .= '<div class="retail-slide retail-slide--new">';
|
||||
$ra .= '<div class="retail-promo">';
|
||||
$ra .= '<div class="retail-promo__eyebrow">Just Landed</div>';
|
||||
$ra .= '<div class="retail-promo__headline">New In Store</div>';
|
||||
$ra .= '<div class="retail-promo__grid">';
|
||||
$ra .= '<div class="retail-promo__swatch retail-promo__swatch--a"></div>';
|
||||
$ra .= '<div class="retail-promo__swatch retail-promo__swatch--b"></div>';
|
||||
$ra .= '<div class="retail-promo__swatch retail-promo__swatch--c"></div>';
|
||||
$ra .= '<div class="retail-promo__swatch retail-promo__swatch--d"></div>';
|
||||
$ra .= '</div>';
|
||||
$ra .= '<div class="retail-promo__sub">Spring/Summer Collection</div>';
|
||||
$ra .= '</div>';
|
||||
$ra .= '</div>';
|
||||
|
||||
// Slide 3: Loyalty Rewards
|
||||
$ra .= '<div class="retail-slide retail-slide--loyalty">';
|
||||
$ra .= '<div class="retail-promo">';
|
||||
$ra .= '<div class="retail-promo__eyebrow">Member Exclusive</div>';
|
||||
$ra .= '<div class="retail-promo__headline">Earn Points</div>';
|
||||
$ra .= '<div class="retail-promo__points">';
|
||||
$ra .= '<div class="retail-promo__pts-val">2x</div>';
|
||||
$ra .= '<div class="retail-promo__pts-lbl">Points This Weekend</div>';
|
||||
$ra .= '</div>';
|
||||
$ra .= '<div class="retail-promo__bar"><div class="retail-promo__bar-fill"></div></div>';
|
||||
$ra .= '<div class="retail-promo__sub">Scan your card at checkout</div>';
|
||||
$ra .= '</div>';
|
||||
$ra .= '</div>';
|
||||
|
||||
$ra .= '</div>'; // retail-slides
|
||||
$ra .= '</div>'; // retail-tv__screen
|
||||
$ra .= '</div>'; // retail-tv__body
|
||||
$ra .= '<div class="retail-tv__feet"><div class="retail-tv__foot"></div><div class="retail-tv__foot"></div></div>';
|
||||
$ra .= '</div>'; // retail-tv
|
||||
$ra .= '</div>'; // retail-stage
|
||||
|
||||
$visual_html = $ra;
|
||||
$visual_cls = 'platform-visual has-retail';
|
||||
}
|
||||
elseif (!empty($a['corporateAnim'])) {
|
||||
/* ── Corporate: Meeting room door panel ── */
|
||||
$ca = '<div class="corp-stage" aria-hidden="true">';
|
||||
$ca .= '<div class="corp-panel">';
|
||||
$ca .= '<div class="corp-panel__header">';
|
||||
$ca .= '<div class="corp-panel__room">Boardroom A</div>';
|
||||
$ca .= '<div class="corp-panel__status corp-panel__status--busy"><span class="corp-panel__dot"></span>In Use</div>';
|
||||
$ca .= '</div>';
|
||||
$ca .= '<div class="corp-panel__meeting">';
|
||||
$ca .= '<div class="corp-panel__meeting-name">Q2 Strategy Review</div>';
|
||||
$ca .= '<div class="corp-panel__meeting-time">10:00 – 11:30</div>';
|
||||
$ca .= '<div class="corp-panel__organiser">Sarah Mitchell</div>';
|
||||
$ca .= '</div>';
|
||||
$ca .= '<div class="corp-panel__timeline">';
|
||||
$ca .= '<div class="corp-panel__tl-track"><div class="corp-panel__tl-fill"></div><div class="corp-panel__tl-now"></div></div>';
|
||||
$ca .= '<div class="corp-panel__tl-labels"><span>09:00</span><span>12:00</span><span>17:00</span></div>';
|
||||
$ca .= '</div>';
|
||||
$ca .= '<div class="corp-panel__next">';
|
||||
$ca .= '<div class="corp-panel__next-lbl">Next</div>';
|
||||
$ca .= '<div class="corp-panel__next-name">Design Sprint Planning</div>';
|
||||
$ca .= '<div class="corp-panel__next-time">13:00 – 14:00</div>';
|
||||
$ca .= '</div>';
|
||||
$ca .= '<div class="corp-panel__teams"><div class="corp-panel__teams-icon"></div><span>Teams Meeting Active</span></div>';
|
||||
$ca .= '</div>'; // corp-panel
|
||||
$ca .= '</div>'; // corp-stage
|
||||
|
||||
$visual_html = $ca;
|
||||
$visual_cls = 'platform-visual has-corporate';
|
||||
}
|
||||
elseif (!empty($a['educationAnim'])) {
|
||||
/* ── Education: Campus schedule board ── */
|
||||
$ea = '<div class="edu-stage" aria-hidden="true">';
|
||||
$ea .= '<div class="edu-board">';
|
||||
$ea .= '<div class="edu-board__header">';
|
||||
$ea .= '<div class="edu-board__title">Today\'s Timetable</div>';
|
||||
$ea .= '<div class="edu-board__date">Monday, 16 Mar</div>';
|
||||
$ea .= '</div>';
|
||||
$ea .= '<div class="edu-board__rows">';
|
||||
|
||||
$rows = [
|
||||
['time' => '09:00', 'subject' => 'Advanced Mathematics', 'room' => 'B204', 'state' => 'done'],
|
||||
['time' => '10:30', 'subject' => 'Physics Lab', 'room' => 'Lab 3', 'state' => 'now'],
|
||||
['time' => '13:00', 'subject' => 'English Literature', 'room' => 'A101', 'state' => ''],
|
||||
['time' => '14:30', 'subject' => 'Computer Science', 'room' => 'IT Suite', 'state' => ''],
|
||||
['time' => '16:00', 'subject' => 'Art & Design', 'room' => 'Studio 1', 'state' => ''],
|
||||
];
|
||||
foreach ($rows as $row) {
|
||||
$state_cls = $row['state'] ? ' edu-row--' . $row['state'] : '';
|
||||
$ea .= '<div class="edu-board__row' . $state_cls . '">';
|
||||
$ea .= '<span class="edu-row__time">' . esc_html($row['time']) . '</span>';
|
||||
$ea .= '<span class="edu-row__subject">' . esc_html($row['subject']) . '</span>';
|
||||
$ea .= '<span class="edu-row__room">' . esc_html($row['room']) . '</span>';
|
||||
if ($row['state'] === 'now') {
|
||||
$ea .= '<span class="edu-row__badge">Now</span>';
|
||||
}
|
||||
$ea .= '</div>';
|
||||
}
|
||||
|
||||
$ea .= '</div>'; // edu-board__rows
|
||||
$ea .= '<div class="edu-board__alert">';
|
||||
$ea .= '<span class="edu-alert__icon">!</span>';
|
||||
$ea .= '<span class="edu-alert__txt">Fire drill scheduled — 15:45 today</span>';
|
||||
$ea .= '</div>';
|
||||
$ea .= '</div>'; // edu-board
|
||||
$ea .= '</div>'; // edu-stage
|
||||
|
||||
$visual_html = $ea;
|
||||
$visual_cls = 'platform-visual has-education';
|
||||
}
|
||||
elseif (!empty($a['outdoorAnim'])) {
|
||||
/* ── Outdoor Marketplace: wide board cycling 2 slides ── */
|
||||
$oa = '<div class="outdoor-stage" aria-hidden="true">';
|
||||
$oa .= '<div class="outdoor-board">';
|
||||
$oa .= '<div class="outdoor-board__screen">';
|
||||
$oa .= '<div class="outdoor-slides">';
|
||||
|
||||
// Slide 1: Market Info
|
||||
$oa .= '<div class="outdoor-slide outdoor-slide--info">';
|
||||
$oa .= '<div class="outdoor-info">';
|
||||
$oa .= '<div class="outdoor-info__header">';
|
||||
$oa .= '<div class="outdoor-info__name">Riverside Market</div>';
|
||||
$oa .= '<div class="outdoor-info__weather"><span class="outdoor-info__temp">18°</span><span class="outdoor-info__cond">Mostly Sunny</span></div>';
|
||||
$oa .= '</div>';
|
||||
$oa .= '<div class="outdoor-info__details">';
|
||||
$oa .= '<div class="outdoor-info__row"><span class="outdoor-info__icon outdoor-info__icon--clock"></span><span>Open 8am – 3pm</span></div>';
|
||||
$oa .= '<div class="outdoor-info__row"><span class="outdoor-info__icon outdoor-info__icon--pin"></span><span>Victoria Embankment</span></div>';
|
||||
$oa .= '<div class="outdoor-info__row"><span class="outdoor-info__icon outdoor-info__icon--stall"></span><span>42 Stallholders Today</span></div>';
|
||||
$oa .= '</div>';
|
||||
$oa .= '</div>';
|
||||
$oa .= '</div>';
|
||||
|
||||
// Slide 2: Stall Directory
|
||||
$oa .= '<div class="outdoor-slide outdoor-slide--directory">';
|
||||
$oa .= '<div class="outdoor-dir">';
|
||||
$oa .= '<div class="outdoor-dir__title">Stall Directory</div>';
|
||||
$oa .= '<div class="outdoor-dir__grid">';
|
||||
$stalls = [['A1–A5', 'Produce & Veg'], ['B1–B4', 'Artisan Bakery'], ['C1–C6', 'Street Food'], ['D1–D3', 'Crafts & Gifts']];
|
||||
foreach ($stalls as $s) {
|
||||
$oa .= '<div class="outdoor-dir__cell"><span class="outdoor-dir__zone">' . esc_html($s[0]) . '</span><span class="outdoor-dir__cat">' . esc_html($s[1]) . '</span></div>';
|
||||
}
|
||||
$oa .= '</div>';
|
||||
$oa .= '</div>';
|
||||
$oa .= '</div>';
|
||||
|
||||
$oa .= '</div>'; // outdoor-slides
|
||||
$oa .= '</div>'; // outdoor-board__screen
|
||||
$oa .= '<div class="outdoor-board__bezel"></div>';
|
||||
$oa .= '</div>'; // outdoor-board
|
||||
$oa .= '</div>'; // outdoor-stage
|
||||
|
||||
$visual_html = $oa;
|
||||
$visual_cls = 'platform-visual has-outdoor';
|
||||
}
|
||||
elseif (!empty($a['liveDataAnim'])) {
|
||||
/* ── Live Data: Operations centre KPI board ── */
|
||||
$la = '<div class="ld-stage" aria-hidden="true">';
|
||||
$la .= '<div class="ld-board">';
|
||||
$la .= '<div class="ld-board__header">';
|
||||
$la .= '<div class="ld-board__title">Operations Dashboard</div>';
|
||||
$la .= '<div class="ld-board__live"><span class="ld-board__live-dot"></span>LIVE</div>';
|
||||
$la .= '</div>';
|
||||
$la .= '<div class="ld-kpis">';
|
||||
|
||||
$kpis = [
|
||||
['id' => 'ld-orders', 'label' => 'Orders / hr', 'value' => '1,847', 'trend' => 'up'],
|
||||
['id' => 'ld-uptime', 'label' => 'Uptime', 'value' => '99.97%', 'trend' => 'up'],
|
||||
['id' => 'ld-alerts', 'label' => 'Active Alerts', 'value' => '3', 'trend' => 'down'],
|
||||
['id' => 'ld-latency', 'label' => 'Avg Latency', 'value' => '42ms', 'trend' => 'neutral'],
|
||||
];
|
||||
foreach ($kpis as $k) {
|
||||
$la .= '<div class="ld-kpi ld-kpi--' . esc_attr($k['trend']) . '">';
|
||||
$la .= '<div class="ld-kpi__label">' . esc_html($k['label']) . '</div>';
|
||||
$la .= '<div class="ld-kpi__value" id="' . esc_attr($k['id']) . '">' . esc_html($k['value']) . '</div>';
|
||||
$la .= '<div class="ld-kpi__trend"></div>';
|
||||
$la .= '</div>';
|
||||
}
|
||||
|
||||
$la .= '</div>'; // ld-kpis
|
||||
$la .= '<div class="ld-chart">';
|
||||
$la .= '<svg class="ld-sparkline" viewBox="0 0 260 60" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">';
|
||||
$la .= '<defs><linearGradient id="ld-grad" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#4CAF50" stop-opacity=".35"/><stop offset="100%" stop-color="#4CAF50" stop-opacity="0"/></linearGradient></defs>';
|
||||
$la .= '<path id="ld-fill-path" d="M0,40 L0,40 L260,40 Z" fill="url(#ld-grad)"/>';
|
||||
$la .= '<path id="ld-line-path" d="M0,40 L260,40" stroke="#4CAF50" stroke-width="2" fill="none" stroke-linejoin="round"/>';
|
||||
$la .= '</svg>';
|
||||
$la .= '<div class="ld-chart__label">Orders / hr — last 60 min</div>';
|
||||
$la .= '</div>'; // ld-chart
|
||||
$la .= '<div class="ld-status">';
|
||||
|
||||
$services = [
|
||||
['API Gateway', 'ok'],
|
||||
['Payment Proc.', 'ok'],
|
||||
['Warehouse Feed', 'warn'],
|
||||
['CDN', 'ok'],
|
||||
];
|
||||
foreach ($services as $svc) {
|
||||
$la .= '<div class="ld-svc ld-svc--' . esc_attr($svc[1]) . '"><span class="ld-svc__dot"></span><span class="ld-svc__name">' . esc_html($svc[0]) . '</span></div>';
|
||||
}
|
||||
|
||||
$la .= '</div>'; // ld-status
|
||||
$la .= '</div>'; // ld-board
|
||||
$la .= '</div>'; // ld-stage
|
||||
|
||||
$visual_html = $la;
|
||||
$visual_cls = 'platform-visual has-live-data';
|
||||
}
|
||||
elseif (!empty($a['healthcareAnim'])) {
|
||||
/* ── Healthcare: Queue management display ── */
|
||||
$hca = '<div class="hc-stage" aria-hidden="true">';
|
||||
$hca .= '<div class="hc-board">';
|
||||
$hca .= '<div class="hc-board__header">';
|
||||
$hca .= '<div class="hc-board__logo"></div>';
|
||||
$hca .= '<div class="hc-board__title">Patient Queue</div>';
|
||||
$hca .= '</div>';
|
||||
$hca .= '<div class="hc-now">';
|
||||
$hca .= '<div class="hc-now__lbl">Now Serving</div>';
|
||||
$hca .= '<div class="hc-now__number hc-ticker">A042</div>';
|
||||
$hca .= '<div class="hc-now__counter">Counter 3 — Dr. Patel</div>';
|
||||
$hca .= '</div>';
|
||||
$hca .= '<div class="hc-counters">';
|
||||
|
||||
$counters = [
|
||||
['id' => 'C1', 'doctor' => 'Dr. Patel', 'ticket' => 'A042', 'wait' => '0 min', 'state' => 'active'],
|
||||
['id' => 'C2', 'doctor' => 'Dr. Okonkwo', 'ticket' => 'B018', 'wait' => '4 min', 'state' => 'active'],
|
||||
['id' => 'C3', 'doctor' => 'Dr. Williams', 'ticket' => 'A041', 'wait' => '8 min', 'state' => 'active'],
|
||||
['id' => 'C4', 'doctor' => 'Dr. Nguyen', 'ticket' => '—', 'wait' => 'Closed', 'state' => 'closed'],
|
||||
];
|
||||
foreach ($counters as $c) {
|
||||
$hca .= '<div class="hc-counter hc-counter--' . esc_attr($c['state']) . '">';
|
||||
$hca .= '<span class="hc-counter__id">' . esc_html($c['id']) . '</span>';
|
||||
$hca .= '<span class="hc-counter__doctor">' . esc_html($c['doctor']) . '</span>';
|
||||
$hca .= '<span class="hc-counter__ticket">' . esc_html($c['ticket']) . '</span>';
|
||||
$hca .= '<span class="hc-counter__wait">' . esc_html($c['wait']) . '</span>';
|
||||
$hca .= '</div>';
|
||||
}
|
||||
|
||||
$hca .= '</div>'; // hc-counters
|
||||
$hca .= '<div class="hc-board__footer">Take a seat — your number will be called</div>';
|
||||
$hca .= '</div>'; // hc-board
|
||||
$hca .= '</div>'; // hc-stage
|
||||
|
||||
$visual_html = $hca;
|
||||
$visual_cls = 'platform-visual has-healthcare';
|
||||
}
|
||||
elseif (!empty($a['transitAnim'])) {
|
||||
/* ── Transit: Split-flap departure board ── */
|
||||
$ta = '<div class="transit-stage" aria-hidden="true">';
|
||||
$ta .= '<div class="transit-board">';
|
||||
$ta .= '<div class="transit-board__header">';
|
||||
$ta .= '<div class="transit-board__title">Departures</div>';
|
||||
$ta .= '<div class="transit-board__clock" id="transit-clock">--:--</div>';
|
||||
$ta .= '</div>';
|
||||
$ta .= '<div class="transit-board__cols">';
|
||||
$ta .= '<span class="transit-col-hd">Time</span><span class="transit-col-hd">Destination</span><span class="transit-col-hd">Platform</span><span class="transit-col-hd">Status</span>';
|
||||
$ta .= '</div>';
|
||||
$ta .= '<div class="transit-rows" id="transit-rows">';
|
||||
|
||||
$departures = [
|
||||
['time' => '10:14', 'destination' => 'London Victoria', 'platform' => '2', 'status' => 'On Time', 'status_cls' => 'on-time'],
|
||||
['time' => '10:22', 'destination' => 'Brighton', 'platform' => '4', 'status' => 'On Time', 'status_cls' => 'on-time'],
|
||||
['time' => '10:31', 'destination' => 'Gatwick Airport', 'platform' => '1', 'status' => 'Delayed', 'status_cls' => 'delayed'],
|
||||
['time' => '10:45', 'destination' => 'London Bridge', 'platform' => '3', 'status' => 'On Time', 'status_cls' => 'on-time'],
|
||||
['time' => '11:02', 'destination' => 'East Croydon', 'platform' => '2', 'status' => 'On Time', 'status_cls' => 'on-time'],
|
||||
];
|
||||
foreach ($departures as $d) {
|
||||
$ta .= '<div class="transit-row">';
|
||||
$ta .= '<span class="transit-cell transit-cell--time">' . esc_html($d['time']) . '</span>';
|
||||
$ta .= '<span class="transit-cell transit-cell--dest">';
|
||||
// Each character gets a flap span for the CSS flip animation
|
||||
$dest = esc_html($d['destination']);
|
||||
foreach (str_split($dest) as $ch) {
|
||||
$ta .= '<span class="transit-flap">' . ($ch === ' ' ? ' ' : htmlspecialchars($ch)) . '</span>';
|
||||
}
|
||||
$ta .= '</span>';
|
||||
$ta .= '<span class="transit-cell transit-cell--plat">' . esc_html($d['platform']) . '</span>';
|
||||
$ta .= '<span class="transit-cell transit-cell--status transit-status--' . esc_attr($d['status_cls']) . '">' . esc_html($d['status']) . '</span>';
|
||||
$ta .= '</div>';
|
||||
}
|
||||
|
||||
$ta .= '</div>'; // transit-rows
|
||||
$ta .= '</div>'; // transit-board
|
||||
$ta .= '</div>'; // transit-stage
|
||||
|
||||
$visual_html = $ta;
|
||||
$visual_cls = 'platform-visual has-transit';
|
||||
}
|
||||
elseif (!empty($a['fitnessAnim'])) {
|
||||
/* ── Fitness: Class schedule with live capacity bar ── */
|
||||
$fa = '<div class="fit-stage" aria-hidden="true">';
|
||||
$fa .= '<div class="fit-board">';
|
||||
$fa .= '<div class="fit-board__header">';
|
||||
$fa .= '<div class="fit-board__logo"></div>';
|
||||
$fa .= '<div class="fit-board__title">Today\'s Classes</div>';
|
||||
$fa .= '</div>';
|
||||
$fa .= '<div class="fit-now">';
|
||||
$fa .= '<div class="fit-now__badge">LIVE NOW</div>';
|
||||
$fa .= '<div class="fit-now__name">HIIT Circuit</div>';
|
||||
$fa .= '<div class="fit-now__detail">Studio 1 · Coach: Emma T · Ends 10:45</div>';
|
||||
$fa .= '<div class="fit-now__capacity">';
|
||||
$fa .= '<span class="fit-cap__lbl">Capacity</span>';
|
||||
$fa .= '<div class="fit-cap__track"><div class="fit-cap__fill"></div></div>';
|
||||
$fa .= '<span class="fit-cap__val">24/30</span>';
|
||||
$fa .= '</div>';
|
||||
$fa .= '</div>';
|
||||
$fa .= '<div class="fit-upcoming">';
|
||||
|
||||
$classes = [
|
||||
['time' => '11:00', 'name' => 'Yoga Flow', 'coach' => 'Sarah K', 'spaces' => 6],
|
||||
['time' => '12:15', 'name' => 'Spin & Burn', 'coach' => 'James R', 'spaces' => 2],
|
||||
['time' => '13:30', 'name' => 'Pilates Core', 'coach' => 'Lisa M', 'spaces' => 12],
|
||||
];
|
||||
foreach ($classes as $cls) {
|
||||
$full_cls = $cls['spaces'] <= 3 ? ' fit-class--filling' : '';
|
||||
$fa .= '<div class="fit-class' . $full_cls . '">';
|
||||
$fa .= '<span class="fit-class__time">' . esc_html($cls['time']) . '</span>';
|
||||
$fa .= '<span class="fit-class__name">' . esc_html($cls['name']) . '</span>';
|
||||
$fa .= '<span class="fit-class__coach">' . esc_html($cls['coach']) . '</span>';
|
||||
$fa .= '<span class="fit-class__spaces">' . esc_html($cls['spaces']) . ' spaces</span>';
|
||||
$fa .= '</div>';
|
||||
}
|
||||
|
||||
$fa .= '</div>'; // fit-upcoming
|
||||
$fa .= '</div>'; // fit-board
|
||||
$fa .= '</div>'; // fit-stage
|
||||
|
||||
$visual_html = $fa;
|
||||
$visual_cls = 'platform-visual has-fitness';
|
||||
}
|
||||
elseif (!empty($a['hospitalityAnim'])) {
|
||||
/* ── Hospitality Sign: Rotating Menu (Breakfast, Lunch, Dinner) ── */
|
||||
$ha = '<div class="hosp-stage" aria-hidden="true">';
|
||||
|
||||
@@ -56,6 +56,15 @@ add_action( 'wp_enqueue_scripts', function () {
|
||||
true
|
||||
);
|
||||
|
||||
// Solutions page animators - live data KPI ticker and transit departure board
|
||||
wp_enqueue_script(
|
||||
'oribi-solutions-animator',
|
||||
ORIBI_URI . '/assets/js/solutions-animator.js',
|
||||
[],
|
||||
ORIBI_VERSION . '.' . filemtime( ORIBI_DIR . '/assets/js/solutions-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