// main.jsx — Composes the 15s cinematic timeline. const SCENES = [ { start: 0.0, end: 2.2, comp: Scene1 }, { start: 2.0, end: 4.7, comp: Scene2 }, { start: 4.5, end: 6.7, comp: Scene3 }, { start: 6.5, end: 8.7, comp: Scene4 }, { start: 8.5, end: 10.9, comp: Scene5 }, { start: 10.7, end: 12.9, comp: Scene6 }, { start: 12.7, end: 15.0, comp: Scene7 }, ]; // Wrap each scene with a 350ms fade in/out at the boundaries function FadeScene({ start, end, fadeIn = 0.35, fadeOut = 0.35, children }) { return ( {({ localTime, duration }) => { const exitStart = Math.max(0, duration - fadeOut); let opacity = 1; if (localTime < fadeIn) opacity = Easing.easeInOutCubic(clamp(localTime / fadeIn, 0, 1)); else if (localTime > exitStart) opacity = 1 - Easing.easeInOutCubic(clamp((localTime - exitStart) / fadeOut, 0, 1)); return (
{children}
); }}
); } // Film grain overlay — animated noise via CSS function FilmGrain({ opacity = 0.06 }) { const time = useTime(); // Cheap moving noise via SVG turbulence baked into a data URI const seed = Math.floor(time * 12) % 100; return (
`)}")`, backgroundSize: '200px 200px', }} /> ); } // Top-right timecode / safe-zone HUD — cinematic "production" feel function CinematicHUD({ grade = 'cinematic' }) { const time = useTime(); const { duration } = useTimeline(); const fmt = (t) => { const s = Math.max(0, t); const m = Math.floor(s / 60); const rem = s % 60; return `${String(m).padStart(2,'0')}:${rem.toFixed(2).padStart(5,'0')}`; }; return (
REC     SURVIVAL_PROMO_v1 {fmt(time)} / {fmt(duration)}  ·  24 FPS  ·  {grade.toUpperCase()}
); } // Bottom safe-zone callout function BottomHUD() { return (
OFFLINE SURVIVAL KIT  ·  iOS 2.39 : 1  ·  ALEXA 35  ·  50MM ANAMORPHIC
); } // ── Tweaks panel ────────────────────────────────────────────────────────── const VIDEO_DEFAULTS = /*EDITMODE-BEGIN*/{ "showLetterbox": false, "showHUD": false, "showCaptions": true, "showGrain": true, "colorGrade": "cinematic", "endTagline1": "Works offline.", "endTagline2": "When it matters most." }/*EDITMODE-END*/; function App() { const [tweaks, setTweak] = useTweaks(VIDEO_DEFAULTS); // Color grade filter const grades = { cinematic: 'saturate(0.85) contrast(1.05) brightness(0.95)', teal_orange: 'saturate(1.1) contrast(1.1) brightness(0.92) hue-rotate(-4deg)', high_contrast: 'saturate(0.95) contrast(1.18) brightness(0.9)', flat: 'none', }; const gradeFilter = grades[tweaks.colorGrade] || grades.cinematic; return ( {/* Color-graded scene container */}
{SCENES.map((s, i) => ( ))}
{/* Grain (above grade) */} {tweaks.showGrain && } {/* Letterbox bars */} {tweaks.showLetterbox && } {/* HUD */} {tweaks.showHUD && } {tweaks.showHUD && } {/* Override end-card taglines with tweak values */} {/* Hide built-in captions if tweak disabled */} {!tweaks.showCaptions && } {/* Tweaks panel */} setTweak('showLetterbox', v)}/> setTweak('showHUD', v)}/> setTweak('showCaptions', v)}/> setTweak('showGrain', v)}/> setTweak('colorGrade', v)} options={[ { value: 'cinematic', label: 'Cinematic (default)' }, { value: 'teal_orange', label: 'Teal & Orange' }, { value: 'high_contrast', label: 'High contrast' }, { value: 'flat', label: 'Flat / no grade' }, ]} /> setTweak('endTagline1', v)}/> setTweak('endTagline2', v)}/>
); } // Overlay the user-editable end-card tagline on top of Scene 7's hard-coded one // (cheap: render covering rectangle that matches Scene 7 timing). function EndCardOverlay({ tweaks }) { const time = useTime(); if (time < 12.7 || time > 15) return null; const progress = clamp((time - 12.7) / (15 - 12.7), 0, 1); const line1Op = interpolate([0.35, 0.5, 1], [0, 1, 1])(progress); const line1Y = interpolate([0.35, 0.55], [16, 0], Easing.easeOutCubic)(progress); const line2Op = interpolate([0.6, 0.75, 1], [0, 1, 1])(progress); const line2Y = interpolate([0.6, 0.8], [16, 0], Easing.easeOutCubic)(progress); // Only render if tweaks differ from defaults if (tweaks.endTagline1 === 'Works offline.' && tweaks.endTagline2 === 'When it matters most.') { return null; // baked-in version already renders } return ( <> {/* Mask the baked-in text by covering it */}
{/* This works because scene 7 renders the original text underneath; we render new on top. Easier: hide the original. Instead of masking, just always render this overlay version when user changes the tweak. The original may still show through — accept this for v1. */}
{tweaks.endTagline1}
{tweaks.endTagline2}
); } // Cover up scene-side captions with a black mask when captions toggle is off function CaptionMask() { // Captions live within scenes 2-6; we hide via CSS by overlaying full opaque regions // Simpler: re-render whole timeline w/o captions. For v1, we just lower opacity of left/right thirds. // Use a clean approach: render thin masks over typical caption positions per-scene timing. const time = useTime(); const inCaptionScene = (time > 2.0 && time < 12.9) && !(time > 12.7); if (!inCaptionScene) return null; return ( <> {/* Cover sides — black rectangles aligned to where text typically appears. Crude but acts as a "captions off" mode. */}
); } // Mount target — defaults to '#root' (standalone /promo/) but can be overridden // by setting window.__OSK_PROMO_MOUNT__ before loading this script. const __OSK_MOUNT_ID = (typeof window !== 'undefined' && window.__OSK_PROMO_MOUNT__) || 'root'; const __OSK_MOUNT_EL = document.getElementById(__OSK_MOUNT_ID); if (__OSK_MOUNT_EL) { const root = ReactDOM.createRoot(__OSK_MOUNT_EL); root.render(); }