Compare commits

...

3 Commits

Author SHA1 Message Date
d901050849 Sync: update home from WordPress 2026-02-21 10:04:45 -05:00
Matt Batchelder
ab6f4212bd Add camera animation support to platform rows and update rendering logic 2026-02-21 10:04:18 -05:00
Matt Batchelder
7e0c216e1c Update platform row descriptions for clarity and consistency 2026-02-21 09:57:25 -05:00
6 changed files with 510 additions and 6 deletions

View File

@@ -10,7 +10,7 @@
<!-- 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 -->

View File

@@ -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","tvStick":true} /-->
<!-- 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 -->

View File

@@ -9,9 +9,9 @@ return <<<'ORIBI_SYNC_CONTENT'
<!-- wp:oribi/hero-animated {"label":"● Digital Signage Solutions","title":"Turn any screen into a dynamic communication tool.","highlightWord":"dynamic","description":"Digital signage is the modern way to connect with your audience. From eye-catching retail displays to dynamic informational screens, we craft tailored solutions that capture attention and deliver your message.","secondaryBtnText":"Request Demo","secondaryBtnUrl":"/demo","stat1Value":"4K","stat1Label":"Resolution Supported","stat2Value":"500+","stat2Label":"Screens Supported","stat3Value":"99.9%","stat3Label":"Uptime"} /-->
<!-- 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":"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","cameraAnim":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":"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","deviceAnim":true} /-->
<!-- /wp:oribi/platform-section -->

View File

@@ -3343,3 +3343,440 @@ p:last-child { margin-bottom: 0; }
.editor-styles-wrapper .wp-block-paragraph {
margin: 0;
}
/* ══════════════════════════════════════════════════════════════════════════════
CAMERA ANIMATOR (.platform-visual.has-camera)
══════════════════════════════════════════════════════════════════════════════ */
.platform-visual.has-camera {
background: none !important;
border: none !important;
border-radius: 0;
padding: 0;
overflow: visible;
display: flex;
align-items: center;
justify-content: center;
font-size: inherit;
}
.cam-stage {
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
}
/* ── Product scene (what the camera is photographing) ── */
.cam-scene {
width: 240px;
height: 110px;
background: var(--color-bg-alt, #1a1a1a);
border: 1px solid var(--color-border, #333);
border-radius: var(--radius-sm, 6px) var(--radius-sm, 6px) 0 0;
border-bottom: none;
position: relative;
overflow: hidden;
flex-shrink: 0;
/* subtle grid lines on the backdrop */
background-image:
linear-gradient(rgba(255,255,255,.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.03) 1px, transparent 1px);
background-size: 24px 24px;
}
.cam-subject {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
.cam-subject::before {
content: '';
display: block;
border-radius: 8px;
}
/* Product 1: tall box — tech/electronics (green) */
.cam-subject--1 {
animation: cam-subject-1 9s ease-in-out infinite;
}
.cam-subject--1::before {
width: 52px; height: 76px;
background: linear-gradient(135deg, #064e3b 0%, #4ade80 100%);
box-shadow: 0 0 24px rgba(74,222,128,.35);
}
/* Product 2: wide bottle — retail (amber) */
.cam-subject--2 {
animation: cam-subject-2 9s ease-in-out infinite;
}
.cam-subject--2::before {
width: 44px; height: 88px;
background: linear-gradient(160deg, #78350f 0%, #fbbf24 100%);
border-radius: 6px 6px 12px 12px;
box-shadow: 0 0 24px rgba(251,191,36,.3);
}
/* Product 3: wide device/tablet — tech (indigo) */
.cam-subject--3 {
animation: cam-subject-3 9s ease-in-out infinite;
}
.cam-subject--3::before {
width: 92px; height: 64px;
background: linear-gradient(135deg, #1e1b4b 0%, #818cf8 100%);
box-shadow: 0 0 24px rgba(129,140,248,.3);
}
/* ── Flash overlay ── */
.cam-flash-overlay {
position: absolute;
inset: 0;
background: #fff;
opacity: 0;
pointer-events: none;
border-radius: inherit;
animation: cam-flash-pulse 9s linear infinite;
}
/* ── Camera body ── */
.cam-body {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 240px;
flex-shrink: 0;
filter: drop-shadow(0 10px 24px rgba(0,0,0,.65));
}
.cam-top {
display: flex;
align-items: flex-end;
width: 100%;
height: 36px;
background: #1e1e1e;
border-radius: 0 4px 0 0;
border: 1px solid #404040;
border-bottom: none;
border-left: none;
position: relative;
}
.cam-hump {
width: 72px;
height: 36px;
background: #1e1e1e;
border-left: 1px solid #404040;
border-top: 1px solid #404040;
border-radius: 0 4px 0 0;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.cam-dial {
width: 20px;
height: 20px;
border-radius: 50%;
background: conic-gradient(#2a2a2a 0deg, #484848 60deg, #2a2a2a 120deg, #484848 180deg, #2a2a2a 240deg, #484848 300deg, #2a2a2a 360deg);
border: 1px solid #555;
}
.cam-shutter-btn {
position: absolute;
right: 16px;
top: 8px;
width: 20px;
height: 20px;
background: #2c2c2c;
border-radius: 50%;
border: 1px solid #555;
display: flex;
align-items: center;
justify-content: center;
animation: cam-shutter-click 9s linear infinite;
transform-origin: center bottom;
}
.cam-shutter-inner {
width: 11px;
height: 11px;
background: radial-gradient(circle at 40% 40%, #555, #2a2a2a);
border-radius: 50%;
border: 1px solid #606060;
}
.cam-front {
width: 100%;
height: 90px;
background: #222;
border: 1px solid #404040;
border-top-color: #555;
border-radius: 0 0 6px 6px;
display: flex;
align-items: center;
position: relative;
padding: 0 12px 0 0;
}
/* Grip ridge on left edge */
.cam-grip-ridge {
width: 20px;
height: 100%;
background: #1a1a1a;
border-right: 1px solid #3c3c3c;
border-radius: 0 0 0 6px;
flex-shrink: 0;
}
/* Lens assembly */
.cam-lens-assembly {
width: 74px;
height: 74px;
border-radius: 50%;
background: #111;
border: 3px solid #383838;
box-shadow: 0 0 0 2px #222, inset 0 0 12px rgba(0,0,0,.9);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-left: 16px;
}
.cam-lens-barrel {
width: 58px;
height: 58px;
border-radius: 50%;
background: #18182e;
border: 2px solid #2a3a6a;
display: flex;
align-items: center;
justify-content: center;
animation: cam-lens-breathe 3s ease-in-out infinite;
}
.cam-lens-glass {
width: 44px;
height: 44px;
border-radius: 50%;
background: radial-gradient(circle at 32% 32%, #1a6ad6 0%, #0b1e5c 45%, #000d1f 100%);
border: 1px solid rgba(100,160,255,.2);
position: relative;
overflow: hidden;
}
.cam-reflection {
position: absolute;
top: 6px;
left: 6px;
width: 14px;
height: 10px;
background: rgba(255,255,255,.18);
border-radius: 50%;
transform: rotate(-25deg);
}
/* Camera controls (LED + small buttons) */
.cam-controls {
margin-left: auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 7px;
}
.cam-led {
width: 7px;
height: 7px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 6px #4ade80;
animation: cam-led-blink 9s linear infinite;
}
.cam-btn {
width: 14px;
height: 7px;
background: #2c2c2c;
border-radius: 2px;
border: 1px solid #444;
}
/* ── Polaroid prints fanning out below camera ── */
.cam-prints {
position: relative;
width: 240px;
height: 70px;
flex-shrink: 0;
}
.cam-print {
position: absolute;
width: 72px;
height: 78px;
background: #f4efdf;
border-radius: 2px;
box-shadow: 0 6px 16px rgba(0,0,0,.55);
opacity: 0;
}
.cam-print__img {
position: absolute;
top: 7px;
left: 7px;
right: 7px;
height: 52px;
border-radius: 1px;
}
/* Print 1: left-leaning (green — product 1) */
.cam-print--1 {
left: 24px;
top: 0;
transform-origin: bottom center;
animation: cam-print-1 9s linear infinite;
}
.cam-print--1 .cam-print__img {
background: linear-gradient(135deg, #064e3b, #4ade80);
}
/* Print 2: center (amber — product 2) */
.cam-print--2 {
left: 84px;
top: 0;
transform-origin: bottom center;
animation: cam-print-2 9s linear infinite;
}
.cam-print--2 .cam-print__img {
background: linear-gradient(160deg, #78350f, #fbbf24);
}
/* Print 3: right-leaning (indigo — product 3) */
.cam-print--3 {
left: 144px;
top: 0;
transform-origin: bottom center;
animation: cam-print-3 9s linear infinite;
}
.cam-print--3 .cam-print__img {
background: linear-gradient(135deg, #1e1b4b, #818cf8);
}
/* ══ Keyframes (9 s cycle — 3 captures at 0 %, 33 %, 66 %) ══════════════ */
/* Subject cycling */
@keyframes cam-subject-1 {
0%, 30% { opacity: 1; }
33%, 100% { opacity: 0; }
}
@keyframes cam-subject-2 {
0%, 30% { opacity: 0; }
33%, 63% { opacity: 1; }
66%, 100% { opacity: 0; }
}
@keyframes cam-subject-3 {
0%, 63% { opacity: 0; }
66%, 100% { opacity: 1; }
}
/* White flash on capture */
@keyframes cam-flash-pulse {
0% { opacity: 0; }
0.5% { opacity: 0.85; }
2.5% { opacity: 0; }
33% { opacity: 0; }
33.5% { opacity: 0.85; }
35.5% { opacity: 0; }
66% { opacity: 0; }
66.5% { opacity: 0.85; }
68.5% { opacity: 0; }
100% { opacity: 0; }
}
/* Physical shutter press */
@keyframes cam-shutter-click {
0% { transform: scaleY(1); }
0.4% { transform: scaleY(0.8) translateY(1px); }
2% { transform: scaleY(1); }
33% { transform: scaleY(1); }
33.4% { transform: scaleY(0.8) translateY(1px); }
35% { transform: scaleY(1); }
66% { transform: scaleY(1); }
66.4% { transform: scaleY(0.8) translateY(1px); }
68% { transform: scaleY(1); }
100% { transform: scaleY(1); }
}
/* Lens focus breathe */
@keyframes cam-lens-breathe {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.03); }
}
/* LED: red flash on capture, green otherwise */
@keyframes cam-led-blink {
0% { background: #ef4444; box-shadow: 0 0 8px #ef4444; }
1.5% { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
33% { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
33.3% { background: #ef4444; box-shadow: 0 0 8px #ef4444; }
34.5% { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
66% { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
66.3% { background: #ef4444; box-shadow: 0 0 8px #ef4444; }
67.5% { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
100% { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
}
/* Polaroid eject — print 1 at 0 % */
@keyframes cam-print-1 {
0% { opacity: 0; transform: rotate(-14deg) translateY(-24px); }
1.5% { opacity: 1; }
4% { opacity: 1; transform: rotate(-14deg) translateY(0); }
95% { opacity: 1; transform: rotate(-14deg) translateY(0); }
99% { opacity: 0; }
100% { opacity: 0; transform: rotate(-14deg) translateY(-24px); }
}
/* Polaroid eject — print 2 at 33 % */
@keyframes cam-print-2 {
0%, 33% { opacity: 0; transform: rotate(-2deg) translateY(-24px); }
34.5% { opacity: 1; }
37% { opacity: 1; transform: rotate(-2deg) translateY(0); }
95% { opacity: 1; transform: rotate(-2deg) translateY(0); }
99% { opacity: 0; }
100% { opacity: 0; transform: rotate(-2deg) translateY(-24px); }
}
/* Polaroid eject — print 3 at 66 % */
@keyframes cam-print-3 {
0%, 66% { opacity: 0; transform: rotate(12deg) translateY(-24px); }
67.5% { opacity: 1; }
70% { opacity: 1; transform: rotate(12deg) translateY(0); }
95% { opacity: 1; transform: rotate(12deg) translateY(0); }
99% { opacity: 0; }
100% { opacity: 0; transform: rotate(12deg) translateY(-24px); }
}
/* ── Reduced-motion: freeze camera in resting state ── */
@media (prefers-reduced-motion: reduce) {
.cam-subject--2, .cam-subject--3,
.cam-flash-overlay,
.cam-print--2, .cam-print--3 { animation: none !important; opacity: 0 !important; }
.cam-subject--1 { animation: none !important; opacity: 1 !important; }
.cam-shutter-btn,
.cam-lens-barrel,
.cam-led { animation: none !important; }
.cam-print--1 {
animation: none !important;
opacity: 1 !important;
transform: rotate(-14deg) translateY(0) !important;
}
}
/* ── Dark-mode ── (already dark, just ensure border tokens apply) ── */
[data-theme="dark"] .cam-scene {
background-color: var(--color-bg-alt);
border-color: var(--color-border);
}

View File

@@ -1180,6 +1180,7 @@ reg('oribi/platform-row', {
imgUrl: { type: 'string', default: '' },
imgAlt: { type: 'string', default: '' },
imgWidth: { type: 'number', default: 300 },
cameraAnim: { type: 'boolean', default: false },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
@@ -1189,7 +1190,8 @@ reg('oribi/platform-row', {
el(PB, { title: 'Row Settings' },
el(TC, { label: 'Visual (emoji)', value: a.visual, onChange: function(v){s({visual:v});} }),
el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }),
el(TG, { label: 'Reversed', checked: !!a.reversed, onChange: function(v){s({reversed:v});} })
el(TG, { label: 'Reversed', checked: !!a.reversed, onChange: function(v){s({reversed:v});} }),
el(TG, { label: 'Camera Animation', checked: !!a.cameraAnim, onChange: function(v){s({cameraAnim:v});} })
),
el(PB, { title: 'Visual Image', initialOpen: false },
el(MUC, null,
@@ -1220,7 +1222,42 @@ reg('oribi/platform-row', {
a.btnUrl ? el(RT, { tagName: 'span', className: 'btn btn-outline mt-3',
value: a.btnText, onChange: function(v){s({btnText:v});}, placeholder: 'Button...' }) : null
),
a.imgUrl
a.cameraAnim
? el('div', { className: 'platform-visual has-camera' },
el('div', { className: 'cam-stage', 'aria-hidden': 'true' },
el('div', { className: 'cam-scene' },
el('div', { className: 'cam-subject cam-subject--1' }),
el('div', { className: 'cam-flash-overlay' })
),
el('div', { className: 'cam-body' },
el('div', { className: 'cam-top' },
el('div', { className: 'cam-hump' }, el('div', { className: 'cam-dial' })),
el('div', { className: 'cam-shutter-btn' }, el('div', { className: 'cam-shutter-inner' }))
),
el('div', { className: 'cam-front' },
el('div', { className: 'cam-grip-ridge' }),
el('div', { className: 'cam-lens-assembly' },
el('div', { className: 'cam-lens-barrel' },
el('div', { className: 'cam-lens-glass' },
el('div', { className: 'cam-reflection' })
)
)
),
el('div', { className: 'cam-controls' },
el('div', { className: 'cam-led' }),
el('div', { className: 'cam-btn' }),
el('div', { className: 'cam-btn' })
)
)
),
el('div', { className: 'cam-prints' },
el('div', { className: 'cam-print cam-print--1', style: { opacity: '1', transform: 'rotate(-14deg) translateY(0)' } },
el('div', { className: 'cam-print__img' })
)
)
)
)
: a.imgUrl
? el('div', { className: 'platform-visual has-img' },
el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain', display: 'block', marginInline: 'auto' } })
)

View File

@@ -555,6 +555,7 @@ add_action( 'init', function () {
'imgWidth' => [ 'type' => 'number', 'default' => 300 ],
'deviceAnim' => [ 'type' => 'boolean', 'default' => false ],
'tvStick' => [ 'type' => 'boolean', 'default' => false ],
'cameraAnim' => [ 'type' => 'boolean', 'default' => false ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_platform_row',
@@ -1498,6 +1499,36 @@ function oribi_render_platform_row( $a ) {
$ts .= '</div>';
$visual_html = $ts;
$visual_cls = 'platform-visual has-tv-stick';
} elseif ( ! empty( $a['cameraAnim'] ) ) {
$ca = '<div class="cam-stage" aria-hidden="true">';
// Scene: product being photographed
$ca .= '<div class="cam-scene">';
$ca .= '<div class="cam-subject cam-subject--1"></div>';
$ca .= '<div class="cam-subject cam-subject--2"></div>';
$ca .= '<div class="cam-subject cam-subject--3"></div>';
$ca .= '<div class="cam-flash-overlay"></div>';
$ca .= '</div>';
// Camera body
$ca .= '<div class="cam-body">';
$ca .= '<div class="cam-top">';
$ca .= '<div class="cam-hump"><div class="cam-dial"></div></div>';
$ca .= '<div class="cam-shutter-btn"><div class="cam-shutter-inner"></div></div>';
$ca .= '</div>';
$ca .= '<div class="cam-front">';
$ca .= '<div class="cam-grip-ridge"></div>';
$ca .= '<div class="cam-lens-assembly"><div class="cam-lens-barrel"><div class="cam-lens-glass"><div class="cam-reflection"></div></div></div></div>';
$ca .= '<div class="cam-controls"><div class="cam-led"></div><div class="cam-btn"></div><div class="cam-btn"></div></div>';
$ca .= '</div>';
$ca .= '</div>';
// Polaroid prints ejecting after each capture
$ca .= '<div class="cam-prints">';
$ca .= '<div class="cam-print cam-print--1"><div class="cam-print__img"></div></div>';
$ca .= '<div class="cam-print cam-print--2"><div class="cam-print__img"></div></div>';
$ca .= '<div class="cam-print cam-print--3"><div class="cam-print__img"></div></div>';
$ca .= '</div>';
$ca .= '</div>';
$visual_html = $ca;
$visual_cls = 'platform-visual has-camera';
} else {
$visual_html = oribi_render_icon( $a['visual'] ?? '' );
$visual_cls = 'platform-visual';