Spaces:
Sleeping
Sleeping
| // @ts-nocheck | |
| // ============================================================================ | |
| // MOTION GRAPHICS · OBSIDIAN ORBIT — Scene 15 / GENERATE → PREVIEW (13s, 390f) | |
| // | |
| // "Keynote object-worship". ONE hero artifact: the GENERATION slab. A thick | |
| // glass slab whose face is a render farm — a GRID of asset tiles that fill in | |
| // one-by-one as a progress bar climbs ("GENERATING…"). When the grid completes, | |
| // a "Launch Motion Studio" button lights up and is clicked; the face transitions | |
| // into a STUDIO PREVIEW where a play head scrubs across a row of scene | |
| // thumbnails. The WORLD orbits (never the camera) in slow hold→ease arcs; spec | |
| // Callouts billboard on drawn leader lines ("GENERATING…", "LAUNCH STUDIO", | |
| // "13s PREVIEW"); the angle-tracked sheen rakes the turn. A riser builds into | |
| // the launch where the slab LOCKS straight-on with an impact + shockwave — the | |
| // payoff — then settles into the scrubbing studio, sheen tracking the final turn. | |
| // | |
| // Built ENTIRELY on the Plinth module (mirrors ObsidianOrbit.tsx's contract) + | |
| // the shared Chronixel engine. CSS-3D only, deterministic, no WebGL. | |
| // ============================================================================ | |
| import React from "react"; | |
| import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; | |
| import { | |
| Plinth, | |
| OrbitWorld, | |
| Extrude, | |
| Callout, | |
| DustMotes, | |
| } from "../../plinth/plinth"; | |
| import { C, withAlpha, ACCENT_GRAD } from "../../chronixel/theme"; | |
| import { FONT, MONO } from "../../chronixel/fonts"; | |
| import { | |
| beat, | |
| clamp01, | |
| mix, | |
| osc, | |
| enter, | |
| springAt, | |
| EASE, | |
| SPRING, | |
| } from "../../chronixel/motion"; | |
| import { Shockwave, GlowPulse, LightSweep } from "../../chronixel/fx"; | |
| /* ---------------------------------------------------------------------------- | |
| A small mono eyebrow tag pinned top-left over the void — frames the beat. | |
| -------------------------------------------------------------------------- */ | |
| const Eyebrow: React.FC<{ at: number; children: React.ReactNode }> = ({ | |
| at, | |
| children, | |
| }) => { | |
| const frame = useCurrentFrame(); | |
| const { fps } = useVideoConfig(); | |
| return ( | |
| <div | |
| style={{ | |
| position: "absolute", | |
| left: 120, | |
| top: 120, | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 14, | |
| fontFamily: MONO, | |
| fontSize: 15, | |
| letterSpacing: 5, | |
| textTransform: "uppercase", | |
| color: C.accent2, | |
| ...enter(frame, fps, at, { from: "left", dist: 32, blur: 0 }), | |
| }} | |
| > | |
| <span | |
| style={{ | |
| width: 30, | |
| height: 2, | |
| background: ACCENT_GRAD, | |
| boxShadow: `0 0 12px ${C.accentGlow}`, | |
| }} | |
| /> | |
| {children} | |
| </div> | |
| ); | |
| }; | |
| /* ============================================================================ | |
| GENERATE → PREVIEW. Hero = the generation slab. A 4x3 grid of asset tiles | |
| fills in one-by-one while a progress bar climbs; at completion a "Launch | |
| Motion Studio" button is clicked and the face flips to a studio preview where | |
| a play head scrubs across scene thumbnails. World orbits in, callouts fire, | |
| slab LOCKS front-on at the launch, then settles into the scrubbing studio. | |
| ========================================================================== */ | |
| export const MgOrbitScene15Generate: React.FC = () => { | |
| const frame = useCurrentFrame(); | |
| const { fps } = useVideoConfig(); | |
| const t = frame / fps; | |
| const LOCK = 8.4; // the launch lock-in beat (seconds) — the payoff | |
| // --- generation grid: 12 tiles fill one-by-one ------------------------ | |
| const COLS = 4; | |
| const ROWS = 3; | |
| const TILES = COLS * ROWS; | |
| const GEN_START = 1.0; // first tile begins | |
| const GEN_STEP = 0.42; // seconds between tiles igniting | |
| const GEN_FILL = 0.5; // seconds for a tile to pop in | |
| // overall generation progress 0→1 (drives the bar + the "GENERATING…" %). | |
| const genLast = GEN_START + (TILES - 1) * GEN_STEP + GEN_FILL; | |
| const genProg = clamp01((t - GEN_START) / (genLast - GEN_START)); | |
| const genPct = Math.round(100 * genProg); | |
| const genDone = genProg >= 1; | |
| // --- launch button: lights once grid is full, clicked at the lock ------ | |
| const BTN_LIVE = genLast; // button becomes live when grid completes | |
| const btnIn = beat(frame, fps, BTN_LIVE, BTN_LIVE + 0.5, EASE.backOut); | |
| const btnGlow = 0.5 + 0.5 * osc(t, 1.4); | |
| const press = beat(frame, fps, LOCK - 0.18, LOCK, EASE.out); // click depress | |
| const pressScale = 1 - 0.06 * (press - clamp01((t - LOCK) / 0.18)); | |
| // --- studio preview: opens at the launch, play head scrubs ------------- | |
| const studioOpen = beat(frame, fps, LOCK, LOCK + 0.8, EASE.expoOut); | |
| // play head sweeps left→right across the thumbnail strip, looping gently. | |
| const scrubRaw = clamp01((t - (LOCK + 0.7)) / 3.4); | |
| const scrub = scrubRaw; // 0→1 across the strip | |
| const STUDIO_TILES = 5; | |
| // --- the lock-in: a weighty spring settle on the launch ---------------- | |
| const stamp = springAt(frame, fps, LOCK, SPRING.HEAVY); // 0→1 weighty settle | |
| const stampScale = mix(1.12, 1, clamp01(stamp)); // slams down to rest | |
| const flash = clamp01(1 - (t - LOCK) / 0.45) * (t >= LOCK ? 1 : 0); | |
| const riser = beat(frame, fps, LOCK - 1.4, LOCK, EASE.out); // build into apex | |
| const ringPulse = 0.5 + 0.5 * osc(t, 3); | |
| const lockRing = clamp01(stamp); | |
| // hold→ease orbit so the slab reads the grid filling, then a final arc that | |
| // settles to centre (0°) right at the launch — the snap-to-camera payoff. | |
| const legs = [ | |
| { arc: -18, hold: 0.6, travel: 2.6 }, | |
| { arc: 26, hold: 1.2, travel: 2.8 }, | |
| { arc: -8, hold: 0.6, travel: 1.8 }, // lands ~0° near LOCK | |
| ]; | |
| // deterministic per-tile "kind" so the grid reads like varied assets. | |
| const tileKind = (i: number) => i % 3; // 0 thumb, 1 wave, 2 chip | |
| const HeroFace = ( | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| padding: 30, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 16, | |
| transform: `scale(${stampScale})`, | |
| }} | |
| > | |
| {/* the lock ring that snaps tight on the launch beat */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 16, | |
| borderRadius: 24, | |
| border: `2px solid ${withAlpha(C.accent, 0.3 + 0.45 * ringPulse)}`, | |
| opacity: lockRing, | |
| boxShadow: `0 0 30px ${withAlpha(C.accent, 0.3 * ringPulse)}`, | |
| pointerEvents: "none", | |
| zIndex: 5, | |
| }} | |
| /> | |
| {/* header row — title + GENERATING…/READY status pill */} | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "space-between", | |
| }} | |
| > | |
| <div | |
| style={{ | |
| fontFamily: MONO, | |
| fontSize: 12, | |
| letterSpacing: 3, | |
| color: C.textDim, | |
| textTransform: "uppercase", | |
| }} | |
| > | |
| render · queue | |
| </div> | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 8, | |
| padding: "5px 12px", | |
| borderRadius: 999, | |
| background: withAlpha(genDone ? C.accent2 : C.accent, 0.16), | |
| border: `1px solid ${withAlpha( | |
| genDone ? C.accent2 : C.accent, | |
| 0.5, | |
| )}`, | |
| fontFamily: MONO, | |
| fontSize: 12, | |
| letterSpacing: 2, | |
| color: genDone ? C.accent2 : C.accent, | |
| }} | |
| > | |
| <span | |
| style={{ | |
| width: 8, | |
| height: 8, | |
| borderRadius: "50%", | |
| background: genDone ? C.accent2 : C.accent, | |
| boxShadow: `0 0 10px ${C.accentGlow}`, | |
| opacity: genDone ? 1 : 0.5 + 0.5 * osc(t, 0.9), | |
| }} | |
| /> | |
| {genDone ? "READY" : `GENERATING ${genPct}%`} | |
| </div> | |
| </div> | |
| {/* the GRID of asset tiles — fills one-by-one */} | |
| <div | |
| style={{ | |
| flex: 1, | |
| display: "grid", | |
| gridTemplateColumns: `repeat(${COLS}, 1fr)`, | |
| gridTemplateRows: `repeat(${ROWS}, 1fr)`, | |
| gap: 12, | |
| position: "relative", | |
| }} | |
| > | |
| {Array.from({ length: TILES }, (_, i) => { | |
| const tAt = GEN_START + i * GEN_STEP; | |
| const fp = beat(frame, fps, tAt, tAt + GEN_FILL, EASE.backOut); | |
| const justLit = clamp01(1 - (t - (tAt + GEN_FILL)) / 0.4); | |
| const kind = tileKind(i); | |
| return ( | |
| <div | |
| key={i} | |
| style={{ | |
| borderRadius: 12, | |
| position: "relative", | |
| overflow: "hidden", | |
| background: | |
| fp > 0.02 | |
| ? `linear-gradient(135deg, ${withAlpha( | |
| C.accent2, | |
| 0.22, | |
| )}, ${withAlpha("#0a0e16", 0.9)})` | |
| : withAlpha("#0a0e16", 0.5), | |
| border: `1px solid ${withAlpha( | |
| C.accent, | |
| 0.18 + 0.4 * fp + 0.4 * justLit, | |
| )}`, | |
| opacity: mix(0.35, 1, fp), | |
| transform: `scale(${mix(0.84, 1, fp)})`, | |
| boxShadow: | |
| fp > 0.5 | |
| ? `0 6px 20px ${withAlpha("#000", 0.5)}, 0 0 ${( | |
| 18 * justLit | |
| ).toFixed(0)}px ${withAlpha(C.accent, 0.4 * justLit)}` | |
| : "none", | |
| }} | |
| > | |
| {/* tile content varies by kind to read as varied assets */} | |
| {kind === 0 && ( | |
| <> | |
| <div | |
| style={{ | |
| position: "absolute", | |
| left: 8, | |
| top: 8, | |
| width: 18, | |
| height: 18, | |
| borderRadius: 5, | |
| background: withAlpha(C.accent, 0.55 * fp), | |
| }} | |
| /> | |
| <div | |
| style={{ | |
| position: "absolute", | |
| left: 8, | |
| right: 8, | |
| bottom: 8, | |
| height: 5, | |
| borderRadius: 3, | |
| background: withAlpha(C.accent2, 0.7 * fp), | |
| }} | |
| /> | |
| </> | |
| )} | |
| {kind === 1 && ( | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| display: "flex", | |
| alignItems: "flex-end", | |
| gap: 4, | |
| padding: 10, | |
| }} | |
| > | |
| {[0.4, 0.8, 0.55, 0.95, 0.65, 0.85].map((h, b) => ( | |
| <div | |
| key={b} | |
| style={{ | |
| flex: 1, | |
| height: `${h * 100 * fp}%`, | |
| borderRadius: 2, | |
| background: withAlpha(C.accent, 0.5 * fp), | |
| }} | |
| /> | |
| ))} | |
| </div> | |
| )} | |
| {kind === 2 && ( | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| display: "flex", | |
| flexDirection: "column", | |
| justifyContent: "center", | |
| gap: 6, | |
| padding: 12, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| height: 6, | |
| width: `${70 * fp}%`, | |
| borderRadius: 3, | |
| background: withAlpha(C.accent2, 0.7 * fp), | |
| }} | |
| /> | |
| <div | |
| style={{ | |
| height: 6, | |
| width: `${45 * fp}%`, | |
| borderRadius: 3, | |
| background: withAlpha("#ffffff", 0.22 * fp), | |
| }} | |
| /> | |
| </div> | |
| )} | |
| {/* ignition flash as the tile lands */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| background: withAlpha("#ffffff", 0.5 * justLit), | |
| mixBlendMode: "screen", | |
| pointerEvents: "none", | |
| }} | |
| /> | |
| </div> | |
| ); | |
| })} | |
| {/* STUDIO PREVIEW overlay — slides up over the grid on launch */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| borderRadius: 14, | |
| background: `linear-gradient(160deg, ${withAlpha( | |
| "#0b1018", | |
| 0.96, | |
| )}, ${withAlpha("#05070c", 0.98)})`, | |
| border: `1px solid ${withAlpha(C.accent, 0.4)}`, | |
| opacity: studioOpen, | |
| transform: `translateY(${mix(34, 0, studioOpen)}px)`, | |
| display: "flex", | |
| flexDirection: "column", | |
| padding: 16, | |
| gap: 12, | |
| pointerEvents: "none", | |
| }} | |
| > | |
| {/* studio header — Motion Studio + 13s preview clock */} | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "space-between", | |
| }} | |
| > | |
| <div | |
| style={{ | |
| fontFamily: FONT, | |
| fontWeight: 800, | |
| fontSize: 18, | |
| letterSpacing: "-0.01em", | |
| color: C.text, | |
| }} | |
| > | |
| Motion Studio | |
| </div> | |
| <div | |
| style={{ | |
| fontFamily: MONO, | |
| fontSize: 12, | |
| letterSpacing: 2, | |
| color: C.accent2, | |
| }} | |
| > | |
| {`00:${String(Math.floor(scrub * 13)).padStart(2, "0")} / 00:13`} | |
| </div> | |
| </div> | |
| {/* big preview viewport — the current scene under the play head */} | |
| <div | |
| style={{ | |
| flex: 1, | |
| borderRadius: 10, | |
| position: "relative", | |
| overflow: "hidden", | |
| background: `radial-gradient(120% 120% at 50% 20%, ${withAlpha( | |
| C.accent, | |
| 0.18, | |
| )}, ${withAlpha("#06080d", 0.95)})`, | |
| border: `1px solid ${withAlpha(C.accent, 0.3)}`, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| position: "absolute", | |
| left: "50%", | |
| top: "46%", | |
| width: 64, | |
| height: 64, | |
| transform: "translate(-50%,-50%)", | |
| borderRadius: "50%", | |
| background: ACCENT_GRAD, | |
| boxShadow: `0 0 30px ${C.accentGlow}`, | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| opacity: studioOpen, | |
| }} | |
| > | |
| {/* play triangle */} | |
| <div | |
| style={{ | |
| width: 0, | |
| height: 0, | |
| borderTop: "13px solid transparent", | |
| borderBottom: "13px solid transparent", | |
| borderLeft: `20px solid #1a0d03`, | |
| marginLeft: 5, | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| {/* thumbnail strip + scrubbing play head */} | |
| <div style={{ position: "relative", height: 46 }}> | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| display: "grid", | |
| gridTemplateColumns: `repeat(${STUDIO_TILES}, 1fr)`, | |
| gap: 6, | |
| }} | |
| > | |
| {Array.from({ length: STUDIO_TILES }, (_, i) => { | |
| const active = | |
| Math.floor(scrub * STUDIO_TILES) === i ? 1 : 0; | |
| return ( | |
| <div | |
| key={i} | |
| style={{ | |
| borderRadius: 7, | |
| background: `linear-gradient(135deg, ${withAlpha( | |
| C.accent2, | |
| 0.18 + 0.25 * active, | |
| )}, ${withAlpha("#0a0e16", 0.92)})`, | |
| border: `1px solid ${withAlpha( | |
| C.accent, | |
| 0.25 + 0.5 * active, | |
| )}`, | |
| boxShadow: active | |
| ? `0 0 16px ${withAlpha(C.accent, 0.4)}` | |
| : "none", | |
| }} | |
| /> | |
| ); | |
| })} | |
| </div> | |
| {/* the play head line that scrubs across the strip */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| top: -6, | |
| bottom: -6, | |
| left: `${scrub * 100}%`, | |
| width: 2, | |
| background: C.text, | |
| boxShadow: `0 0 12px ${withAlpha("#ffffff", 0.8)}`, | |
| opacity: studioOpen, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| position: "absolute", | |
| top: -7, | |
| left: "50%", | |
| transform: "translateX(-50%) rotate(45deg)", | |
| width: 9, | |
| height: 9, | |
| background: C.text, | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* progress bar + LAUNCH MOTION STUDIO button (hidden once studio opens) */} | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 14, | |
| opacity: 1 - studioOpen, | |
| }} | |
| > | |
| {/* progress track */} | |
| <div | |
| style={{ | |
| flex: 1, | |
| height: 10, | |
| borderRadius: 6, | |
| background: withAlpha("#ffffff", 0.08), | |
| overflow: "hidden", | |
| position: "relative", | |
| }} | |
| > | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| width: `${genProg * 100}%`, | |
| borderRadius: 6, | |
| background: ACCENT_GRAD, | |
| boxShadow: `0 0 16px ${C.accentGlow}`, | |
| }} | |
| /> | |
| </div> | |
| {/* launch button — lights up once the grid is full, then is clicked */} | |
| <div | |
| style={{ | |
| padding: "11px 22px", | |
| borderRadius: 12, | |
| fontFamily: FONT, | |
| fontWeight: 800, | |
| fontSize: 17, | |
| letterSpacing: "0.01em", | |
| whiteSpace: "nowrap", | |
| color: btnIn > 0.4 ? "#1a0d03" : C.textDim, | |
| background: | |
| btnIn > 0.02 | |
| ? ACCENT_GRAD | |
| : withAlpha("#ffffff", 0.06), | |
| border: `1px solid ${withAlpha(C.accent, 0.4 + 0.4 * btnIn)}`, | |
| boxShadow: | |
| btnIn > 0.4 | |
| ? `0 0 ${(22 + 14 * btnGlow).toFixed( | |
| 0, | |
| )}px ${withAlpha(C.accent, 0.45 * btnGlow)}` | |
| : "none", | |
| opacity: mix(0.5, 1, btnIn), | |
| transform: `scale(${mix(0.94, 1, btnIn) * pressScale})`, | |
| }} | |
| > | |
| Launch Motion Studio | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| return ( | |
| <Plinth> | |
| <Eyebrow at={0.3}>Generate · Preview</Eyebrow> | |
| {/* riser glow swelling behind the hero into the launch */} | |
| <AbsoluteFill style={{ pointerEvents: "none", zIndex: 1 }}> | |
| <GlowPulse | |
| size={1000} | |
| color={withAlpha(C.accent, 0.1 * riser)} | |
| strength={riser} | |
| /> | |
| </AbsoluteFill> | |
| {/* impact flash + shockwave on the launch payoff */} | |
| <AbsoluteFill style={{ pointerEvents: "none", zIndex: 6 }}> | |
| <GlowPulse | |
| size={1200} | |
| color={withAlpha(C.accent, 0.2 * flash)} | |
| strength={flash} | |
| /> | |
| <Shockwave at={LOCK} size={820} /> | |
| <Shockwave at={LOCK + 0.08} size={1120} /> | |
| <LightSweep at={LOCK} dur={1.0} strength={0.16} /> | |
| </AbsoluteFill> | |
| <OrbitWorld legs={legs} perspective={2300} bobAmp={12}> | |
| <DustMotes layers={3} opacity={0.42} /> | |
| <Extrude w={820} h={500} depth={22} layers={16} radius={28}> | |
| {HeroFace} | |
| </Extrude> | |
| {/* billboarded spec callouts firing at orbit waypoints */} | |
| <Callout | |
| title="The Render" | |
| value="GENERATING…" | |
| at={fps * 2.2} | |
| x={0} | |
| y={-330} | |
| leader={120} | |
| side="right" | |
| /> | |
| <Callout | |
| title="12 Assets" | |
| value="FILLED IN" | |
| at={fps * 5.0} | |
| x={490} | |
| y={150} | |
| leader={150} | |
| side="right" | |
| /> | |
| <Callout | |
| title="One Click" | |
| value="LAUNCH STUDIO" | |
| at={fps * (LOCK + 0.3)} | |
| x={-500} | |
| y={130} | |
| leader={160} | |
| side="left" | |
| /> | |
| <Callout | |
| title="Live Preview" | |
| value="13s PREVIEW" | |
| at={fps * (LOCK + 1.6)} | |
| x={0} | |
| y={340} | |
| leader={120} | |
| side="right" | |
| /> | |
| </OrbitWorld> | |
| {/* flat masked mirror sibling — re-renders the hero, simplified & matte */} | |
| <Plinth.Reflection> | |
| <Extrude w={820} h={500} depth={22} layers={10} radius={28} sheen={false}> | |
| {HeroFace} | |
| </Extrude> | |
| </Plinth.Reflection> | |
| </Plinth> | |
| ); | |
| }; | |
| export default MgOrbitScene15Generate; |