padhle - post migration
This commit is contained in:
@@ -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.6–14.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 6–12"
|
||||
: 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)
|
||||
Reference in New Issue
Block a user