Spaces:
Running
Running
| // @ts-nocheck | |
| // ============================================================================ | |
| // MOTION GRAPHICS · OBSIDIAN ORBIT — Scene 09 / DROP THE SCRIPT (11s, 330f) | |
| // | |
| // "Keynote object-worship". ONE hero artifact: the EPISODE-FOLDER slab — a thick | |
| // glass slab whose FACE is an episode workspace (a folder header + an empty | |
| // dropzone). A raw ".txt" script card DROPS onto its face from above and docks | |
| // with a pop; a scan-line then SWEEPS across the text as the UI reads it, | |
| // lighting each line in turn; a READY check pulses on once the read completes. | |
| // The WORLD orbits (never the camera) in slow hold→ease arcs; spec Callouts | |
| // billboard on drawn leader lines at waypoints ("DROP SCRIPT", "AUTO-READ"); | |
| // the angle-tracked sheen rakes the turn. A riser builds into the settle where | |
| // the slab LOCKS straight-on with an impact + shockwave as the ready-check | |
| // lands — the payoff — then idles, 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> | |
| ); | |
| }; | |
| /* ============================================================================ | |
| DROP THE SCRIPT. Hero = the EPISODE-FOLDER artifact. A raw .txt card drops | |
| onto the slab face, a scan-line reads the text line-by-line, a ready-check | |
| pulses on. The world orbits in, callouts fire, then the slab LOCKS front-on | |
| with an impact as the read completes. | |
| ========================================================================== */ | |
| export const MgOrbitScene09DropScript: React.FC = () => { | |
| const frame = useCurrentFrame(); | |
| const { fps } = useVideoConfig(); | |
| const t = frame / fps; | |
| const LOCK = 8.0; // the settle / payoff beat (seconds) | |
| // --- face choreography ------------------------------------------------- | |
| const folderP = beat(frame, fps, 0.5, 1.6, EASE.out); // workspace fades in | |
| const DROP = 2.4; // the .txt card docks onto the face (seconds) | |
| const dropSpring = springAt(frame, fps, DROP, SPRING.SNAPPY); // card settle | |
| const dropIn = clamp01((t - (DROP - 0.7)) / 0.7); // card travel/visibility | |
| const dropY = mix(-360, 0, clamp01(dropSpring)); // falls from above to dock | |
| const dropScale = mix(1.12, 1, clamp01(dropSpring)); // slight overshoot | |
| // scan-line read: sweeps top→bottom across the docked card's text. | |
| const SCAN_START = 3.3; | |
| const SCAN_END = 6.0; | |
| const scanP = beat(frame, fps, SCAN_START, SCAN_END, EASE.inOut); // 0→1 sweep | |
| // ready-check: pulses on once the read finishes, lands hard on the lock. | |
| const checkSpring = springAt(frame, fps, LOCK, SPRING.HEAVY); // weighty pop-in | |
| const checkP = clamp01(checkSpring); | |
| const checkPulse = 0.5 + 0.5 * osc(t, 2.2); | |
| // --- the lock-in: a weighty spring settle on the payoff ----------------- | |
| const stamp = springAt(frame, fps, LOCK, SPRING.HEAVY); // 0→1 weighty settle | |
| const stampScale = mix(1.1, 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); | |
| // hold→ease orbit so the slab reads its face on the way in, then a final arc | |
| // that settles to centre (0°) right at the lock — 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.5, travel: 1.8 }, // lands ~0° near LOCK | |
| ]; | |
| // the script card's text lines — each lights as the scan-line crosses it. | |
| const scriptLines = [0.9, 0.66, 0.84, 0.5, 0.74, 0.58]; | |
| const HeroFace = ( | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| padding: 36, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 18, | |
| transform: `scale(${stampScale})`, | |
| }} | |
| > | |
| {/* episode folder header — the workspace identity */} | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 14, | |
| opacity: folderP, | |
| }} | |
| > | |
| {/* folder glyph */} | |
| <div | |
| style={{ | |
| position: "relative", | |
| width: 44, | |
| height: 34, | |
| borderRadius: 6, | |
| background: `linear-gradient(135deg, ${withAlpha( | |
| C.accent2, | |
| 0.85, | |
| )}, ${C.accentDeep})`, | |
| boxShadow: `0 0 18px ${withAlpha(C.accent, 0.4)}`, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| position: "absolute", | |
| left: 4, | |
| top: -6, | |
| width: 20, | |
| height: 10, | |
| borderRadius: "5px 5px 0 0", | |
| background: withAlpha(C.accent2, 0.85), | |
| }} | |
| /> | |
| </div> | |
| <div style={{ display: "flex", flexDirection: "column", gap: 4 }}> | |
| <div | |
| style={{ | |
| fontFamily: MONO, | |
| fontSize: 12, | |
| letterSpacing: 3, | |
| textTransform: "uppercase", | |
| color: C.textDim, | |
| }} | |
| > | |
| Episode 09 | |
| </div> | |
| <div | |
| style={{ | |
| fontFamily: FONT, | |
| fontWeight: 800, | |
| fontSize: 22, | |
| lineHeight: 1, | |
| letterSpacing: "-0.01em", | |
| color: C.text, | |
| }} | |
| > | |
| Motion Graphics | |
| </div> | |
| </div> | |
| </div> | |
| {/* the dropzone — where the .txt card docks */} | |
| <div | |
| style={{ | |
| position: "relative", | |
| flex: 1, | |
| borderRadius: 16, | |
| border: `1.5px dashed ${withAlpha( | |
| C.accent, | |
| mix(0.5, 0.18, clamp01(dropSpring)), | |
| )}`, | |
| background: withAlpha("#070a11", 0.5), | |
| overflow: "hidden", | |
| opacity: folderP, | |
| }} | |
| > | |
| {/* idle dropzone hint — fades as the card arrives */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 0, | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| fontFamily: MONO, | |
| fontSize: 13, | |
| letterSpacing: 4, | |
| textTransform: "uppercase", | |
| color: withAlpha(C.accent2, 0.6), | |
| opacity: clamp01(1 - dropIn * 1.4), | |
| }} | |
| > | |
| drop .txt here | |
| </div> | |
| {/* the raw .txt script card — falls in and docks */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 16, | |
| borderRadius: 12, | |
| background: `linear-gradient(135deg, ${withAlpha( | |
| "#0e1320", | |
| 0.96, | |
| )}, ${withAlpha("#070a11", 0.96)})`, | |
| border: `1px solid ${withAlpha(C.accent, 0.4)}`, | |
| boxShadow: `0 18px 50px ${withAlpha("#000", 0.6)}, 0 0 24px ${withAlpha( | |
| C.accent, | |
| 0.2 * dropIn, | |
| )}`, | |
| padding: 18, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 13, | |
| opacity: dropIn, | |
| transform: `translateY(${dropY.toFixed(1)}px) scale(${dropScale.toFixed( | |
| 3, | |
| )})`, | |
| }} | |
| > | |
| {/* card filename */} | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 8, | |
| fontFamily: MONO, | |
| fontSize: 12, | |
| letterSpacing: 2.5, | |
| textTransform: "uppercase", | |
| color: C.accent2, | |
| }} | |
| > | |
| <span | |
| style={{ | |
| width: 18, | |
| height: 22, | |
| borderRadius: 3, | |
| border: `1px solid ${withAlpha(C.accent2, 0.6)}`, | |
| display: "inline-block", | |
| }} | |
| /> | |
| script.txt | |
| </div> | |
| {/* the text lines — each lights as the scan-line passes it */} | |
| <div | |
| style={{ | |
| position: "relative", | |
| flex: 1, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 11, | |
| justifyContent: "center", | |
| }} | |
| > | |
| {scriptLines.map((w, i) => { | |
| const lineFrac = (i + 0.5) / scriptLines.length; | |
| const read = clamp01((scanP - lineFrac) * 6 + 1); // lit once passed | |
| return ( | |
| <div | |
| key={i} | |
| style={{ | |
| height: 9, | |
| width: `${w * 100}%`, | |
| borderRadius: 5, | |
| background: | |
| read > 0.01 | |
| ? `linear-gradient(90deg, ${withAlpha( | |
| C.accent2, | |
| mix(0.2, 0.85, read), | |
| )}, ${withAlpha("#ffffff", mix(0.1, 0.35, read))})` | |
| : withAlpha("#ffffff", 0.18), | |
| boxShadow: | |
| read > 0.4 | |
| ? `0 0 12px ${withAlpha(C.accent, 0.4 * read)}` | |
| : "none", | |
| }} | |
| /> | |
| ); | |
| })} | |
| {/* the scan-line sweeping down the text as the UI reads it */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| left: -6, | |
| right: -6, | |
| top: `${(scanP * 100).toFixed(1)}%`, | |
| height: 2, | |
| transform: "translateY(-1px)", | |
| background: `linear-gradient(90deg, transparent, ${C.accent2}, transparent)`, | |
| boxShadow: `0 0 16px ${C.accentGlow}`, | |
| opacity: | |
| scanP > 0.001 && scanP < 0.999 | |
| ? 1 | |
| : clamp01((t - SCAN_START) / 0.2) * | |
| clamp01((SCAN_END + 0.15 - t) / 0.2), | |
| }} | |
| /> | |
| </div> | |
| {/* read-progress bar at the card's foot */} | |
| <div | |
| style={{ | |
| height: 4, | |
| borderRadius: 2, | |
| background: withAlpha("#ffffff", 0.08), | |
| overflow: "hidden", | |
| }} | |
| > | |
| <div | |
| style={{ | |
| height: "100%", | |
| width: `${(scanP * 100).toFixed(1)}%`, | |
| background: ACCENT_GRAD, | |
| boxShadow: `0 0 10px ${C.accentGlow}`, | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| {/* the READY check — pulses on once the read completes */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| right: 16, | |
| bottom: 16, | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 10, | |
| padding: "8px 14px 8px 10px", | |
| borderRadius: 999, | |
| background: withAlpha("#070a11", 0.85), | |
| border: `1px solid ${withAlpha( | |
| C.accent, | |
| 0.4 + 0.4 * checkPulse, | |
| )}`, | |
| boxShadow: `0 0 ${(18 + 14 * checkPulse).toFixed( | |
| 0, | |
| )}px ${withAlpha(C.accent, 0.35 * checkP * checkPulse)}`, | |
| opacity: checkP, | |
| transform: `scale(${mix(0.7, 1, checkP)})`, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| width: 26, | |
| height: 26, | |
| borderRadius: "50%", | |
| background: ACCENT_GRAD, | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| boxShadow: `inset 0 0 10px ${withAlpha("#ffffff", 0.4)}`, | |
| }} | |
| > | |
| {/* check mark */} | |
| <svg width="15" height="15" viewBox="0 0 15 15"> | |
| <path | |
| d="M3 8 L6.2 11 L12 4" | |
| fill="none" | |
| stroke="#1a0d03" | |
| strokeWidth="2.4" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| style={{ | |
| strokeDasharray: 20, | |
| strokeDashoffset: mix(20, 0, checkP), | |
| }} | |
| /> | |
| </svg> | |
| </div> | |
| <span | |
| style={{ | |
| fontFamily: MONO, | |
| fontSize: 12, | |
| letterSpacing: 3, | |
| textTransform: "uppercase", | |
| color: C.text, | |
| }} | |
| > | |
| Ready | |
| </span> | |
| </div> | |
| </div> | |
| {/* the lock ring that snaps tight on the payoff beat */} | |
| <div | |
| style={{ | |
| position: "absolute", | |
| inset: 18, | |
| borderRadius: 26, | |
| border: `2px solid ${withAlpha(C.accent, 0.28 + 0.42 * ringPulse)}`, | |
| opacity: clamp01(stamp), | |
| boxShadow: `0 0 30px ${withAlpha(C.accent, 0.3 * ringPulse)}`, | |
| pointerEvents: "none", | |
| }} | |
| /> | |
| </div> | |
| ); | |
| return ( | |
| <Plinth> | |
| <Eyebrow at={0.3}>Drop The Script</Eyebrow> | |
| {/* riser glow swelling behind the hero into the payoff */} | |
| <AbsoluteFill style={{ pointerEvents: "none", zIndex: 1 }}> | |
| <GlowPulse | |
| size={1000} | |
| color={withAlpha(C.accent, 0.1 * riser)} | |
| strength={riser} | |
| /> | |
| </AbsoluteFill> | |
| {/* impact flash + shockwave on the ready/lock 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={720} h={460} depth={22} layers={16} radius={28}> | |
| {HeroFace} | |
| </Extrude> | |
| {/* billboarded spec callouts firing at orbit waypoints */} | |
| <Callout | |
| title="The Input" | |
| value="DROP SCRIPT" | |
| at={fps * (DROP + 0.2)} | |
| x={0} | |
| y={-310} | |
| leader={120} | |
| side="right" | |
| /> | |
| <Callout | |
| title="The Engine" | |
| value="AUTO-READ" | |
| at={fps * (SCAN_START + 0.6)} | |
| x={460} | |
| y={120} | |
| leader={150} | |
| side="right" | |
| /> | |
| <Callout | |
| title="The Output" | |
| value="READY" | |
| at={fps * (LOCK + 0.3)} | |
| x={-470} | |
| y={140} | |
| leader={160} | |
| side="left" | |
| /> | |
| </OrbitWorld> | |
| {/* flat masked mirror sibling — re-renders the hero, simplified & matte */} | |
| <Plinth.Reflection> | |
| <Extrude w={720} h={460} depth={22} layers={10} radius={28} sheen={false}> | |
| {HeroFace} | |
| </Extrude> | |
| </Plinth.Reflection> | |
| </Plinth> | |
| ); | |
| }; | |
| export default MgOrbitScene09DropScript; |