Moeeldouma's picture
Deploy Eldouma Graphics — Docker Space, bring-your-own-key demo
39d98a0 verified
Raw
History Blame Contribute Delete
15.7 kB
// @ts-nocheck
// ============================================================================
// MOTION GRAPHICS · OBSIDIAN ORBIT — Scene 08 / CREATE EPISODE (13s, 390f)
//
// "Keynote object-worship". ONE hero artifact: a DASHBOARD slab. A thick glass
// slab whose face is a project dashboard — a glowing "+ Create Episode" button
// gets clicked (a ripple fires), a name field TYPES a title, then a back-end
// directory SKELETON self-assembles row by row on the face. Instant scaffolding,
// keynote calm. The WORLD orbits (never the camera) in slow hold->ease arcs;
// spec Callouts billboard on drawn leader lines at waypoints ("+ CREATE EPISODE",
// "AUTO-SCAFFOLD"); the angle-tracked sheen rakes the turn; a gentle settle on
// the payoff as the tree finishes locking in.
//
// 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";
import { TypeOn } from "../../chronixel/viz/text";
/* ----------------------------------------------------------------------------
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>
);
};
/* ============================================================================
CREATE EPISODE. Hero = the DASHBOARD artifact. A glass slab whose face is a
project dashboard: a glowing "+ Create Episode" button is clicked (ripple),
a name field types a title, then a directory skeleton self-assembles. The
world orbits in, callouts fire, and the scaffold locks in on the payoff.
========================================================================== */
export const MgOrbitScene08CreateEpisode: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const t = frame / fps;
// --- beat timeline (seconds) -------------------------------------------
const CLICK = 2.4; // the "+ Create Episode" button is pressed (ripple)
const TYPE = 3.0; // the name field begins typing the title
const SCAFFOLD = 5.6; // the directory skeleton starts self-assembling
const LOCK = 9.0; // the scaffold finishes — a calm settle payoff
// --- face choreography -------------------------------------------------
const dashIn = beat(frame, fps, 0.8, 2.0, EASE.out); // dashboard fades up
const btnGlow = 0.5 + 0.5 * osc(t, 1.4); // the create button breathes
const pressDip = 1 - 0.06 * clamp01(1 - Math.abs(t - CLICK) / 0.18); // squash
const ripple = clamp01((t - CLICK) / 0.7); // click ripple expands
const rippleLife = clamp01(1 - (t - CLICK) / 0.7);
// the name field appears right after the click, then the title types in.
const fieldP = beat(frame, fps, CLICK + 0.15, CLICK + 0.6, EASE.backOut);
// the directory tree rows assemble one at a time after SCAFFOLD.
const tree = [
{ label: "episode-08/", depth: 0 },
{ label: "scenes/", depth: 1 },
{ label: "scene-01.tsx", depth: 2 },
{ label: "scene-02.tsx", depth: 2 },
{ label: "assets/", depth: 1 },
{ label: "render.config.ts", depth: 1 },
];
// --- the lock-in: a gentle, weighty settle on the payoff ----------------
const stamp = springAt(frame, fps, LOCK, SPRING.HEAVY); // 0->1 settle
const stampScale = mix(1.04, 1, clamp01(stamp)); // a soft, calm seat
const flash = clamp01(1 - (t - LOCK) / 0.5) * (t >= LOCK ? 1 : 0);
const riser = beat(frame, fps, LOCK - 1.4, LOCK, EASE.out); // build into apex
const lockRing = clamp01(stamp);
const ringPulse = 0.5 + 0.5 * osc(t, 3);
// hold->ease orbit: shows the button face on the way in, turns to reveal the
// scaffolding, then a final short arc that settles to centre (0°) at the lock.
const legs = [
{ arc: -18, hold: 0.7, travel: 2.4 }, // ease left to read the button
{ arc: 26, hold: 1.6, travel: 2.8 }, // swing across as it types/scaffolds
{ arc: -8, hold: 0.6, travel: 1.8 }, // lands ~0° near LOCK — settle
];
const HeroFace = (
<div
style={{
position: "absolute",
inset: 0,
padding: 30,
display: "flex",
flexDirection: "column",
gap: 16,
transform: `scale(${stampScale})`,
opacity: dashIn,
}}
>
{/* dashboard chrome — title bar */}
<div
style={{
display: "flex",
alignItems: "center",
gap: 10,
paddingBottom: 14,
borderBottom: `1px solid ${C.glassBorder}`,
}}
>
<div
style={{
width: 10,
height: 10,
borderRadius: "50%",
background: withAlpha(C.accent, 0.85),
boxShadow: `0 0 10px ${C.accentGlow}`,
}}
/>
<div
style={{
fontFamily: MONO,
fontSize: 12,
letterSpacing: 3,
textTransform: "uppercase",
color: C.textDim,
}}
>
chronixel · dashboard
</div>
<div style={{ flex: 1 }} />
<div
style={{
fontFamily: MONO,
fontSize: 11,
letterSpacing: 2,
color: withAlpha(C.accent2, 0.7),
}}
>
new project
</div>
</div>
{/* main body: left = create + name field, right = scaffold tree */}
<div
style={{
flex: 1,
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 22,
alignItems: "stretch",
}}
>
{/* LEFT — the glowing "+ Create Episode" button + the name field */}
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
gap: 18,
}}
>
{/* the create button (with click ripple) */}
<div
style={{
position: "relative",
alignSelf: "flex-start",
display: "flex",
alignItems: "center",
gap: 12,
padding: "16px 26px",
borderRadius: 16,
background: ACCENT_GRAD,
transform: `scale(${pressDip})`,
boxShadow: `0 8px 30px ${withAlpha(
C.accent,
0.4 + 0.25 * btnGlow,
)}, inset 0 1px 0 ${withAlpha("#ffffff", 0.4)}`,
overflow: "hidden",
}}
>
<span
style={{
fontFamily: FONT,
fontWeight: 800,
fontSize: 30,
color: "#1a0d03",
lineHeight: 1,
}}
>
+
</span>
<span
style={{
fontFamily: FONT,
fontWeight: 800,
fontSize: 24,
color: "#1a0d03",
letterSpacing: "-0.01em",
lineHeight: 1,
}}
>
Create Episode
</span>
{/* click ripple emanating from the press point */}
{t >= CLICK && (
<div
style={{
position: "absolute",
left: "50%",
top: "50%",
width: 40,
height: 40,
marginLeft: -20,
marginTop: -20,
borderRadius: "50%",
border: `2px solid ${withAlpha("#ffffff", 0.7)}`,
transform: `scale(${mix(0.4, 6, ripple)})`,
opacity: rippleLife * 0.8,
pointerEvents: "none",
}}
/>
)}
</div>
{/* the name field that types the episode title */}
<div
style={{
opacity: fieldP,
transform: `translateY(${(1 - fieldP) * 12}px)`,
}}
>
<div
style={{
fontFamily: MONO,
fontSize: 10,
letterSpacing: 3,
textTransform: "uppercase",
color: C.textDim,
marginBottom: 8,
}}
>
episode name
</div>
<div
style={{
padding: "12px 16px",
borderRadius: 12,
background: withAlpha("#0a0e16", 0.75),
border: `1px solid ${withAlpha(C.accent, 0.45)}`,
boxShadow: `inset 0 0 16px ${withAlpha("#000", 0.5)}`,
minHeight: 26,
display: "flex",
alignItems: "center",
}}
>
<TypeOn
text="Motion Graphics — Pilot"
at={TYPE}
cps={16}
size={20}
mono={false}
color={C.text}
caret
/>
</div>
</div>
</div>
{/* RIGHT — the back-end directory skeleton self-assembling */}
<div
style={{
position: "relative",
borderRadius: 14,
padding: "18px 18px",
background: withAlpha("#0a0e16", 0.6),
border: `1px solid ${C.glassBorder}`,
display: "flex",
flexDirection: "column",
gap: 11,
justifyContent: "center",
}}
>
<div
style={{
fontFamily: MONO,
fontSize: 10,
letterSpacing: 3,
textTransform: "uppercase",
color: withAlpha(C.accent2, 0.7),
marginBottom: 4,
}}
>
scaffolding
</div>
{tree.map((row, i) => {
const rp = beat(
frame,
fps,
SCAFFOLD + i * 0.28,
SCAFFOLD + i * 0.28 + 0.5,
EASE.backOut,
);
const isDir = row.label.endsWith("/");
return (
<div
key={i}
style={{
display: "flex",
alignItems: "center",
gap: 10,
marginLeft: row.depth * 22,
opacity: rp,
transform: `translateX(${(1 - rp) * -14}px)`,
}}
>
{/* node glyph: folder vs file */}
<div
style={{
width: 14,
height: 11,
borderRadius: 3,
flexShrink: 0,
background: isDir
? withAlpha(C.accent, 0.55)
: "transparent",
border: isDir
? "none"
: `1.5px solid ${withAlpha(C.accent2, 0.7)}`,
boxShadow: isDir
? `0 0 8px ${withAlpha(C.accent, 0.5)}`
: "none",
}}
/>
<div
style={{
fontFamily: MONO,
fontSize: 15,
color: isDir ? C.text : withAlpha(C.text, 0.7),
letterSpacing: 0.5,
}}
>
{row.label}
</div>
</div>
);
})}
</div>
</div>
{/* lock ring that snaps tight as the scaffold finishes (calm settle) */}
<div
style={{
position: "absolute",
inset: 16,
borderRadius: 24,
border: `2px solid ${withAlpha(C.accent, 0.25 + 0.4 * ringPulse)}`,
opacity: lockRing,
boxShadow: `0 0 28px ${withAlpha(C.accent, 0.28 * ringPulse)}`,
pointerEvents: "none",
}}
/>
</div>
);
return (
<Plinth>
<Eyebrow at={0.3}>One Click To Scaffold</Eyebrow>
{/* riser glow swelling behind the hero into the settle */}
<AbsoluteFill style={{ pointerEvents: "none", zIndex: 1 }}>
<GlowPulse
size={1000}
color={withAlpha(C.accent, 0.09 * riser)}
strength={riser}
/>
</AbsoluteFill>
{/* a soft flash + shockwave the instant the button is clicked */}
<AbsoluteFill style={{ pointerEvents: "none", zIndex: 6 }}>
<GlowPulse
size={900}
color={withAlpha(C.accent2, 0.14 * rippleLife)}
strength={rippleLife}
/>
<Shockwave at={CLICK} size={620} />
</AbsoluteFill>
{/* a gentle settle flash as the scaffold locks in on the payoff */}
<AbsoluteFill style={{ pointerEvents: "none", zIndex: 6 }}>
<GlowPulse
size={1200}
color={withAlpha(C.accent, 0.14 * flash)}
strength={flash}
/>
<Shockwave at={LOCK} size={780} />
<LightSweep at={LOCK} dur={1.1} strength={0.13} />
</AbsoluteFill>
<OrbitWorld legs={legs} perspective={2300} bobAmp={12}>
<DustMotes layers={3} opacity={0.42} />
<Extrude w={820} h={460} depth={22} layers={16} radius={28}>
{HeroFace}
</Extrude>
{/* billboarded spec callouts firing at orbit waypoints */}
<Callout
title="The Trigger"
value="+ CREATE EPISODE"
at={fps * CLICK}
x={0}
y={-310}
leader={120}
side="right"
/>
<Callout
title="Instant"
value="AUTO-SCAFFOLD"
at={fps * (SCAFFOLD + 0.4)}
x={490}
y={140}
leader={150}
side="right"
/>
<Callout
title="Zero Setup"
value="READY TO BUILD"
at={fps * (LOCK + 0.3)}
x={-490}
y={150}
leader={160}
side="left"
/>
</OrbitWorld>
{/* flat masked mirror sibling — re-renders the hero, simplified & matte */}
<Plinth.Reflection>
<Extrude w={820} h={460} depth={22} layers={10} radius={28} sheen={false}>
{HeroFace}
</Extrude>
</Plinth.Reflection>
</Plinth>
);
};
export default MgOrbitScene08CreateEpisode;