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
@@ -0,0 +1,249 @@
import React from "react";
import { AbsoluteFill, Interactive, interpolate, useCurrentFrame } from "remotion";
import {
AppCursor,
Ripple,
TopBar,
} from "../components/ui";
import { COLORS, bodyFont, headingFont, HIT, SNAP } from "../theme";
/**
* Scene 5 — Choosing Context (10.614.6s)
* ------------------------------------------------------------
* First UI moment. The app, in close-up: top bar drops in, the
* context selector card unfolds, and the oversized cursor walks
* three clicks — Grade 10 → Science → Chapter 4 · Photosynthesis.
* Each click: ripple ring + pill flips black + micro-bounce +
* subtle camera push. Then a confirmation row locks the context.
*/
export const CHOOSING_FRAMES = 240; // 4.0s @ 60fps
type StepType = "grade" | "subject" | "chapter";
const GRADE_OPTIONS = ["Grade 6", "Grade 7", "Grade 8", "Grade 9", "Grade 10", "Grade 11", "Grade 12"];
const SUBJECT_OPTIONS = ["Mathematics", "Science", "History", "Language Arts", "English", "Geography"];
const CHAPTER_OPTIONS = ["Ch 1 · Nutrition in Plants", "Ch 2 · Photosynthesis", "Ch 3 · Respiration", "Ch 4 · Human Body", "Ch 5 · Food Chains", "Ch 6 · Adaptations"];
// beat frames
const CARD_IN = 10;
const CLICK1 = 78;
const CLICK2 = 126;
const CLICK3 = 174;
const CONFIRM = 214;
export const ChoosingContext: React.FC = () => {
const frame = useCurrentFrame();
const step: StepType =
frame < CLICK1 ? "grade" : frame < CLICK2 ? "subject" : frame < CLICK3 ? "chapter" : "chapter";
const options =
step === "grade" ? GRADE_OPTIONS : step === "subject" ? SUBJECT_OPTIONS : CHAPTER_OPTIONS;
const pickIdx = step === "grade" ? 4 : step === "subject" ? 1 : 1; // Grade 10 / Science / Ch2
// camera push-in per click (pulses)
const push =
1 +
interpolate(frame, [CLICK1, CLICK1 + 10], [0, 0.016], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: SNAP }) +
interpolate(frame, [CLICK2, CLICK2 + 10], [0, 0.02], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: SNAP }) +
interpolate(frame, [CLICK3, CLICK3 + 10], [0, 0.024], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: SNAP });
// card entrance
const cardIn = interpolate(frame, [CARD_IN, CARD_IN + 16], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: SNAP,
});
const cardY = 560 + (1 - cardIn) * 46;
// Simple cursor path: move to target, click, move to next target
const curClick = (from: number, to: number, at: number) => {
const t = interpolate(frame, [at, at + 16], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: HIT,
});
return from + (to - from) * t;
};
const press = (at: number) =>
interpolate(frame, [at, at + 6], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: SNAP,
});
// cursor position timeline
let cx = 640,
cy = 760,
cp = 0;
if (frame < 56) {
cx = 1180 - (1180 - 476) * interpolate(frame, [24, 56], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: HIT });
cy = 900;
} else if (frame < CLICK1) {
cx = curClick(476, 476, 56);
cy = 732;
} else if (frame < CLICK1 + 8) {
cx = 470;
cy = 728;
cp = press(CLICK1);
} else if (frame < CLICK2 - 12) {
cx = curClick(470, 460, CLICK1 + 8);
cy = 728 + (880 - 728) * interpolate(frame, [CLICK1 + 8, CLICK2 - 12], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: HIT });
} else if (frame < CLICK2 + 8) {
cx = 460;
cy = 876;
cp = press(CLICK2);
} else if (frame < CLICK3 - 14) {
cx = curClick(460, 480, CLICK2 + 8);
cy = 876 + (1030 - 876) * interpolate(frame, [CLICK2 + 8, CLICK3 - 14], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: HIT });
} else if (frame < CLICK3 + 10) {
cx = 480;
cy = 1026;
cp = press(CLICK3);
} else {
cx = curClick(480, 300, CLICK3 + 10);
cy = 1026 + (620 - 1026) * interpolate(frame, [CLICK3 + 10, CLICK3 + 26], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: HIT });
}
const cursorOpacity = interpolate(frame, [24, 40], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: SNAP,
});
// ripple activations
const ripples = [
<Ripple at={CLICK1} x={470} y={745} />,
<Ripple at={CLICK2} x={460} y={895} />,
<Ripple at={CLICK3} x={480} y={1045} />,
];
// confirm row
const confirmIn = interpolate(frame, [CONFIRM, CONFIRM + 16], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: SNAP,
});
return (
<AbsoluteFill name="Scene" style={{ backgroundColor: COLORS.background }}>
<Interactive.Div name="CameraPush" style={{ position: "absolute", left: 0, top: 0, width: "100%", height: "100%", scale: String(push.toFixed(4)) }}>
<TopBar
from={6}
pills={[
{ label: "Grade 10", active: frame >= CLICK1 },
{ label: "Science", active: frame >= CLICK2 },
{ label: "Ch 2", active: frame >= CLICK3 },
]}
/>
{/* selector card */}
<div
style={{
position: "absolute",
left: 540 - 410,
top: cardY,
width: 820,
height: 880,
backgroundColor: COLORS.surface,
border: `2.5px solid ${COLORS.outlineVariant}`,
borderRadius: 24,
boxShadow: "0 4px 18px rgba(17,24,39,0.10)",
opacity: cardIn,
padding: "40px 48px",
}}
>
<div
style={{
fontFamily: headingFont,
fontSize: 44,
fontWeight: 800,
color: COLORS.onSurface,
}}
>
{step === "grade" ? "Choose your standard" : step === "subject" ? "Choose your subject" : "Choose your chapter"}
</div>
<div
style={{
fontFamily: bodyFont,
fontSize: 26,
color: COLORS.onSurfaceVariant,
marginTop: 10,
}}
>
{step === "grade"
? "For CBSE · Grades 612"
: step === "subject"
? "For Grade 10"
: "Grounded in your textbook"}
</div>
{/* options list (3-col grid for grades, list for subject/chapter) */}
<div style={{ marginTop: 40, display: "flex", flexDirection: "column", gap: 18 }}>
{options.map((opt, i) => {
const isPick = i === pickIdx;
const lit = isPick && frame >= (step === "grade" ? CLICK1 : step === "subject" ? CLICK2 : CLICK3);
return (
<div
key={`${step}-${opt}`}
style={{
display: "flex",
alignItems: "center",
height: 84,
fontFamily: bodyFont,
fontSize: 30,
fontWeight: 600,
color: lit ? COLORS.onPrimary : COLORS.onSurface,
backgroundColor: lit ? COLORS.primary : COLORS.surface,
border: lit ? "none" : `2.5px solid ${COLORS.outlineVariant}`,
borderRadius: 16,
padding: "0 26px",
scale: String((isPick ? 1 + 0.02 : 1).toFixed(4)),
}}
>
<span style={{ width: 16, height: 16, borderRadius: "50%", border: `2.5px solid ${lit ? COLORS.onPrimary : COLORS.outline}`, marginRight: 18 }} />
{opt}
</div>
);
})}
</div>
</div>
{/* cursor + ripples */}
<AppCursor x={cx} y={cy} opacity={cursorOpacity} press={cp} />
{ripples}
{/* confirm bar */}
{frame >= CONFIRM && (
<div
style={{
position: "absolute",
left: 540,
top: 1620,
translate: "-50% 0",
opacity: confirmIn,
scale: String((1.06 - 0.06 * confirmIn).toFixed(4)),
fontFamily: bodyFont,
fontSize: 30,
fontWeight: 600,
color: COLORS.onSurface,
backgroundColor: COLORS.surface,
border: `2.5px solid ${COLORS.primary}`,
borderRadius: 18,
padding: "18px 34px",
display: "flex",
alignItems: "center",
gap: 16,
}}
>
<span style={{ fontSize: 30, color: COLORS.primary }}></span>
Grade 10 · Science · Chapter 2 · Photosynthesis
</div>
)}
</Interactive.Div>
</AbsoluteFill>
);
};
// (Ripple is imported from ../components/ui)