/* ───── Main app: scroll-driven reading-table experience ───── */
const { useState, useEffect, useRef } = React;
// helpers
const clamp = (v, a = 0, b = 1) => Math.min(b, Math.max(a, v));
const lerp = (a, b, t) => a + (b - a) * t;
const easeOut = t => 1 - Math.pow(1 - t, 3);
const easeIO = t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const STAGES = {
IDLE: [0.00, 0.08],
SHUFFLE: [0.08, 0.32],
FAN: [0.32, 0.55],
DEAL: [0.55, 0.88],
SETTLE: [0.88, 1.00],
};
const stageP = (p, [a, b]) => clamp((p - a) / (b - a));
const DECK_COUNT = 10;
// Returns the transform values for card i at progress p.
function deckCardTransform(i, p) {
const shuf = stageP(p, STAGES.SHUFFLE);
const fan = stageP(p, STAGES.FAN);
const deal = stageP(p, STAGES.DEAL);
// base — stacked at center, slight offset for depth
let x = -i * 0.4;
let y = -i * 0.6;
let z = i * 0.3;
let rot = ((i * 7) % 5) - 2; // -2..2 jitter
let opacity = 1;
let scale = 1;
// ── SHUFFLE ──
if (shuf > 0) {
const t = shuf;
const fade = 1 - t; // dies out as we approach fan
const phase = Math.sin(t * Math.PI * 7 + i * 0.7);
const phase2 = Math.cos(t * Math.PI * 9 + i * 1.3);
x += phase * 14 * fade;
y += phase2 * 8 * fade;
rot += phase * 6 * fade;
// tiny scale pulse
scale = 1 + Math.sin(t * Math.PI * 12 + i) * 0.02 * fade;
}
// ── FAN ──
if (fan > 0) {
const t = easeOut(fan);
const half = (DECK_COUNT - 1) / 2;
const offset = i - half;
const fanAngle = offset * 9; // -40..40
const fanRadius = 230;
// fan upward, like a hand of cards held above the table
const fx = Math.sin(fanAngle * Math.PI / 180) * fanRadius;
const fy = -Math.abs(offset) * 4 - 30; // slight arc lift
x = lerp(x, fx, t);
y = lerp(y, fy, t);
rot = lerp(rot, fanAngle, t);
z = lerp(z, i * 0.8, t);
}
// ── DEAL ──
if (deal > 0) {
// top 3 (i=0..2) fly out to positions; the rest gather back to a tidy stack
const targets = [
{ x: -280, y: 30, rot: 0 }, // Past
{ x: 0, y: 30, rot: 0 }, // Present
{ x: 280, y: 30, rot: 0 }, // Future
];
if (i < 3) {
// stagger start times
const delay = i * 0.13;
const local = clamp((deal - delay) / 0.45);
const e = easeOut(local);
const target = targets[i];
x = lerp(x, target.x, e);
y = lerp(y, target.y, e);
rot = lerp(rot, target.rot, e);
// arc lift
y -= Math.sin(local * Math.PI) * 60;
z = 100 + i;
} else {
// collapse rest back to tidy stack just below the candle, slightly to right
const t = easeOut(deal);
const idx = i - 3;
x = lerp(x, 240 + idx * 0.4, t);
y = lerp(y, -120 + idx * 0.6, t);
rot = lerp(rot, -6 + (idx * 3) % 4, t);
scale = lerp(scale, 0.55, t);
opacity = lerp(opacity, 0.7, t);
z = lerp(z, idx * 0.3, t);
}
}
return { x, y, rot, z, opacity, scale };
}
const TopBar = () => (
A
Astro Annie
astroanjilina.com
The Reader
Services
Training
);
const IntroShowcase = () => (
{/* Left stat column */}
12
राशियाँ
All
Twelve
Signs
Read
{/* Center — the banner image */}
{/* Right stat column */}
4
courses
Small
Batches
of Six
{/* Tagline below the banner */}
Print · Hindi & English · Available at the Studio
{/* CTA buttons */}
);
const Footer = () => (
);
const ShuffleText = () => (
shuffling…
);
const Embers = ({ active }) => {
const [pts] = useState(() => Array.from({ length: 18 }).map((_, i) => ({
x: 40 + Math.random() * 20, // % horizontal start
delay: i * 0.6 + Math.random(),
duration: 5 + Math.random() * 4,
drift: (Math.random() - 0.5) * 18,
size: 1 + Math.random() * 1.8,
})));
if (!active) return null;
return (
{pts.map((p, i) => (
))}
);
};
const IS_DAY = (typeof window !== 'undefined' && window.__THEME === 'day');
// Card flip audio — references the