// axis-rescale-shock-v2 — axis off-the-charts rescale v2 (batch 6 "tweak-and-review" rework) // Upgrades over v1: the off-the-charts point bursts past the card top 80→220px (really reaching into the title // text area); the burst segment thickens to 10px and turns amber. At the rescale moment — "whoosh" — old tick // numbers fly out downward and fade out while new ones slide in from above, and the grid densifies from 4→8 lines // in the same frame. Real-chart context: real title "Monthly revenue", real axis ticks // $25k/$50k/$75k/$100k → $100k/$200k/$300k/$400k, a real month x-axis, and an endpoint popping a real value // label "$340k"; card shake 3→8px. After the ending at f120, true stillness for 40f. // Frame determinism: data is hard-coded and everything derives from frame; no Math.random / Date.now. import React from 'react'; import { useCurrentFrame, interpolate, Easing } from 'remotion'; import { G, TitleBlock } from '../../_fixtures/Fixtures'; const AMBER = '#b45309'; const CARD_W = 1060; const CARD_H = 600; const CX = (1920 - CARD_W) / 2; const CY = (1080 - CARD_H) / 2 + 40; const PAD = 52; const AXIS_W = 96; // left $ tick space const PLOT_W = CARD_W - PAD * 2 - AXIS_W; const PLOT_H = 360; const PLOT_X = PAD + AXIS_W; const PLOT_Y = 130; // historical data (in $k, gently climbing within the 0–100 range), the last point breaking the scale at 340 const DATA = [22, 30, 26, 38, 35, 47, 44, 58, 55, 66, 72, 340]; const N = DATA.length; const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const HOLD = 12; const DRAW_END = HOLD + 34; // f46: historical segment fully drawn const SHOCK_END = DRAW_END + 16; // f62: off-the-charts point hits the top const BEAT = SHOCK_END + 16; // f78: pause for a beat (hovering in the title area) const RESCALE_END = BEAT + 12; // f90: rescale complete const MARK_END = RESCALE_END + 8; // f98: endpoint marker pops in const VAL_END = MARK_END + 10; // f108: real-value label pops in const easeDraw = Easing.inOut(Easing.cubic); export const AxisRescaleShockV2: React.FC = () => { const frame = useCurrentFrame(); // range: 0–100 → 0–400, rescale over 12f with out-cubic const range = interpolate(frame, [BEAT, RESCALE_END], [100, 400], { easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const yOf = (v: number): number => PLOT_H - (v / range) * PLOT_H; const drawT = interpolate(frame, [HOLD, DRAW_END], [0, N - 2], { easing: easeDraw, extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const shockT = interpolate(frame, [DRAW_END + 2, SHOCK_END], [0, 1], { easing: Easing.in(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); // burst rest position: 220px above the card's top edge (PLOT_Y=130 → -350 relative to the plot top), really reaching into the title text const SHOCK_Y = -(PLOT_Y + 220); const rescaleP = interpolate(frame, [BEAT, RESCALE_END], [0, 1], { easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const xOf = (i: number): number => (i / (N - 1)) * PLOT_W; // historical segment point set const basePts: string[] = []; const upto = Math.min(drawT, N - 2); for (let i = 0; i <= Math.floor(upto); i++) basePts.push(`${xOf(i).toFixed(2)},${yOf(DATA[i]).toFixed(2)}`); if (upto < N - 2 && upto > Math.floor(upto)) { const i = Math.floor(upto); const f = upto - i; const x = xOf(i) + (xOf(i + 1) - xOf(i)) * f; const y = yOf(DATA[i]) + (yOf(DATA[i + 1]) - yOf(DATA[i])) * f; basePts.push(`${x.toFixed(2)},${y.toFixed(2)}`); } // the off-the-charts segment is its own line (amber + 10px thick), starting from the last historical point let headX = basePts.length ? Number(basePts[basePts.length - 1].split(',')[0]) : 0; let headY = basePts.length ? Number(basePts[basePts.length - 1].split(',')[1]) : 0; let shockSeg: string[] = []; if (shockT > 0) { const x0 = xOf(N - 2); const y0 = yOf(DATA[N - 2]); const x = x0 + (xOf(N - 1) - x0) * shockT; const yEnd = SHOCK_Y + (yOf(DATA[N - 1]) - SHOCK_Y) * rescaleP; const y = y0 + (yEnd - y0) * shockT; shockSeg = [`${x0.toFixed(2)},${y0.toFixed(2)}`, `${x.toFixed(2)},${y.toFixed(2)}`]; headX = x; headY = y; } // rescale "whoosh": old ticks fly out downward and fade out, new ticks slide in from above const swap = interpolate(frame, [BEAT, BEAT + 10], [0, 1], { easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const OLD_TICKS = ['$25k', '$50k', '$75k', '$100k']; const NEW_TICKS = ['$100k', '$200k', '$300k', '$400k']; // grid density 4→8: the 4 new thin gridlines fade in with swap const denseOp = swap; const markS = interpolate(frame, [RESCALE_END, MARK_END], [0, 1], { easing: Easing.out(Easing.back(2.2)), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const valS = interpolate(frame, [MARK_END, VAL_END], [0, 1], { easing: Easing.out(Easing.back(1.8)), extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); // card shakes at the shock moment (±8px, decaying to zero within 8f) const kick = frame >= SHOCK_END && frame < SHOCK_END + 8 ? 8 * (1 - (frame - SHOCK_END) / 8) * Math.sin((frame - SHOCK_END) * 2.6) : 0; const shockOn = frame >= DRAW_END; // period when the off-the-charts segment styling is active return (