diff --git a/pages/home.php b/pages/home.php index 762aa16..987a153 100644 --- a/pages/home.php +++ b/pages/home.php @@ -9,7 +9,7 @@ return <<<'ORIBI_SYNC_CONTENT' - + diff --git a/theme/assets/css/main.css b/theme/assets/css/main.css index e4aab86..f48348c 100644 --- a/theme/assets/css/main.css +++ b/theme/assets/css/main.css @@ -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); +} diff --git a/theme/blocks/editor.js b/theme/blocks/editor.js index ec5f6ff..e765480 100644 --- a/theme/blocks/editor.js +++ b/theme/blocks/editor.js @@ -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' } }) ) diff --git a/theme/blocks/index.php b/theme/blocks/index.php index 8fc5595..7df0f27 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -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 .= ''; $visual_html = $ts; $visual_cls = 'platform-visual has-tv-stick'; + } elseif ( ! empty( $a['cameraAnim'] ) ) { + $ca = ''; + $visual_html = $ca; + $visual_cls = 'platform-visual has-camera'; } else { $visual_html = oribi_render_icon( $a['visual'] ?? '' ); $visual_cls = 'platform-visual';