padhle - post migration

This commit is contained in:
2026-09-15 04:08:55 -04:00
parent 534b51b41b
commit b2d8109019
509 changed files with 147378 additions and 303 deletions
+226
View File
@@ -0,0 +1,226 @@
import React from "react";
import {
AbsoluteFill,
Interactive,
interpolate,
useCurrentFrame,
} from "remotion";
import { COLORS, monoFont, SNAP } from "../theme";
/**
* Scene 4 — The AI Reveal (8.211.0s)
* ------------------------------------------------------------
* The narrative turn: S1S3 were the problem and the promise.
* Now the screen goes dark and the experience is revealed —
* softly, in a pool of light, line by line, like a terminal
* coming alive. This is the first "AI" mention (~9s, per the
* late-reveal direction).
*
* Your AI tutor.
* Built for CBSE.
* Grounded in your textbook.
*
* Strictly monochrome. The soft light is made of blurred white
* ellipses (no gradients / no glassmorphism).
*/
export const THE_AI_REVEAL_FRAMES = 168; // 2.8s @ 60fps
type Line = { text: string; start: number; step: number; size: number; y: number };
const LINES: Line[] = [
{ text: "Your AI tutor.", start: 24, step: 2.1, size: 96, y: 700 },
{ text: "Built for CBSE.", start: 78, step: 2.0, size: 72, y: 952 },
{ text: "Grounded in your textbook.", start: 116, step: 1.5, size: 64, y: 1180 },
];
const X0 = 150; // left margin (gap between caret-block and glyphs)
const monoAdvance = 0.6; // JetBrains Mono advance ratio (~0.6em)
// ── Soft spotlight pool (blurred white ellipses, no gradients) ──
const SpotlightLayer: React.FC<{
w: number;
h: number;
blur: number;
opacity: number;
breathe: number;
}> = ({ w, h, blur, opacity, breathe }) => (
<div
style={{
position: "absolute",
left: 540 - w / 2,
top: 920 - h / 2,
width: w,
height: h,
borderRadius: "50%",
backgroundColor: COLORS.white,
filter: `blur(${blur}px)`,
opacity,
scale: String((1 + 0.012 * breathe).toFixed(4)),
}}
/>
);
// ── Terminal caret (blinks after the last typed line) ─────────
const Caret: React.FC<{ x: number; y: number; size: number; blink: boolean }> = ({
x,
y,
size,
blink,
}) => {
const frame = useCurrentFrame();
const on = !blink || (frame % 40) < 24;
return (
<div
style={{
position: "absolute",
left: x,
top: y,
width: Math.max(10, size * 0.2),
height: size * 0.86,
backgroundColor: COLORS.white,
opacity: on ? 1 : 0.06,
}}
/>
);
};
export const AiReveal: React.FC = () => {
const frame = useCurrentFrame();
// Spotlight breathing + ambient drift
const breathe = Math.sin((frame / 60) * (2 * Math.PI) / 9);
const driftX = Math.sin((frame / 60) * (2 * Math.PI) / 13) * 7;
const driftY = Math.cos((frame / 60) * (2 * Math.PI) / 17) * 5;
const push = interpolate(frame, [0, 120], [1, 1.03], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: SNAP,
});
// White out at the end → clean seam into Scene 5 (white UI)
const out = interpolate(frame, [158, 166], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: SNAP,
});
// Active line (the one most recently typed)
let active: Line | null = null;
for (const l of LINES) {
if (frame >= l.start) active = l;
}
const activeCount = active
? Math.min(active.text.length, Math.max(0, Math.floor((frame - active.start) / active.step) + 1))
: 0;
const activeDone = active ? activeCount >= active.text.length : false;
const caretX = active ? X0 + activeCount * active.size * monoAdvance : X0;
const caretY = active ? active.y : 700;
return (
<AbsoluteFill name="Scene" style={{ backgroundColor: COLORS.black }}>
<Interactive.Div
name="Drift"
style={{
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
translate: `${driftX.toFixed(2)}px ${driftY.toFixed(2)}px`,
scale: String(push),
}}
>
{/* Spotlight pool */}
<Interactive.Div name="Spotlight">
<SpotlightLayer w={1650} h={1050} blur={120} opacity={0.05} breathe={breathe} />
<SpotlightLayer w={1180} h={700} blur={72} opacity={0.06} breathe={breathe} />
<SpotlightLayer w={800} h={440} blur={34} opacity={0.09} breathe={breathe} />
</Interactive.Div>
{/* Dust motes in the light */}
<Interactive.Div name="Dust">
{[
{ x: 300, y: 1180, r: 7, d: 90 },
{ x: 760, y: 940, r: 5, d: 120 },
{ x: 420, y: 1330, r: 4, d: 150 },
].map((m, i) => {
const rise = ((frame + m.d) % 220) / 220;
return (
<div
key={i}
style={{
position: "absolute",
left: m.x + Math.sin((frame + i * 40) / 30) * 30,
top: m.y - rise * 420,
width: m.r * 2,
height: m.r * 2,
borderRadius: "50%",
backgroundColor: COLORS.white,
filter: "blur(3px)",
opacity: 0.16,
}}
/>
);
})}
</Interactive.Div>
{/* Typewriter lines */}
<Interactive.Div name="Typewriter">
{LINES.map((line, li) => {
const visible = Math.min(
line.text.length,
Math.max(0, Math.floor((frame - line.start) / line.step) + 1),
);
return (
<Interactive.Div
key={li}
name={`Line-${li}`}
style={{
position: "absolute",
left: X0 + 18,
top: line.y,
whiteSpace: "nowrap",
fontFamily: monoFont,
fontSize: line.size,
fontWeight: 500,
color: COLORS.white,
letterSpacing: "0.02em",
}}
>
{line.text.split("").map((ch, i) => (
<span
key={i}
style={{
opacity: i < visible ? 1 : 0,
}}
>
{ch === " " ? "\u00A0" : ch}
</span>
))}
</Interactive.Div>
);
})}
<Caret
x={caretX}
y={caretY}
size={active?.size ?? 96}
blink={activeDone}
/>
</Interactive.Div>
</Interactive.Div>
{/* White-out exit */}
<Interactive.Div
name="RevealOut"
style={{
position: "absolute",
inset: 0,
backgroundColor: COLORS.white,
opacity: out,
}}
/>
</AbsoluteFill>
);
};