// scenes.jsx — Al Rawdatain brand intro scenes // Reads CSS variables from :root for themeable colors (tweakable). const W = 1080; const H = 1920; const useVar = (name, fallback) => { if (typeof window === 'undefined') return fallback; const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); return v || fallback; }; // ─── Fine-grained hooks ──────────────────────────────────────────────────── const useFrame = () => { const [, force] = React.useReducer(x => x + 1, 0); React.useEffect(() => { let id; const tick = () => { force(); id = requestAnimationFrame(tick); }; id = requestAnimationFrame(tick); return () => cancelAnimationFrame(id); }, []); }; // ─── Shared atoms ────────────────────────────────────────────────────────── function CornerHUD({ time, duration, sceneName, sceneIdx, total }) { const mono = 'JetBrains Mono, ui-monospace, monospace'; const ink = 'var(--ink)'; const dim = 'var(--dim)'; const fmt = (t) => { const s = Math.floor(t % 60); const ms = Math.floor((t * 100) % 100); return `00:${String(s).padStart(2, '0')}.${String(ms).padStart(2, '0')}`; }; return ( <> {/* Top-left: timecode */}
REC {fmt(time)} / {fmt(duration)}
{/* Top-right: scene label */}
{String(sceneIdx).padStart(2,'0')} / {String(total).padStart(2,'0')} {sceneName}
{/* Bottom-left: brand mark */}
AL RAWDATAIN · الروضتين · EST. 1981
{/* Bottom-right: coords */}
29.8°N 48.1°E
N. KUWAIT · AL‑RAWDATAIN FIELD
); } // Animated counter function Counter({ from = 0, to, progress, suffix = '', decimals = 0, style = {} }) { const v = from + (to - from) * Easing.easeOutQuart(clamp(progress, 0, 1)); const display = decimals > 0 ? v.toFixed(decimals) : Math.floor(v).toLocaleString(); return ( {display}{suffix} ); } // Striped placeholder function Placeholder({ label, style = {} }) { return (
{label}
); } // Animated progress line function Ticker({ progress, style = {} }) { return (
); } // Real Kuwait map — uses SVG of Kuwait (1134 × 979 viewBox). // Landmass bbox measured: x [74, 962], y [53, 911]. Bubiyan island extends to ~x=1020. // We crop with margin to include the whole country + surrounding Gulf context. const SRC_W = 1134.2744; const SRC_H = 979.20728; const KW_MIN_X = 30; const KW_MIN_Y = 20; const KW_W = 1000; // includes Bubiyan + Gulf east const KW_H = 920; // full north-to-south // Al-Raudhatain aquifer in source SVG coords. // Computed from geography: 29.85°N, 47.73°E, mapped onto bbox [74..962] × [53..911]. const RAUDHATAIN_SRC_X = 620; const RAUDHATAIN_SRC_Y = 187; // Translate to fractional (0..1) within the cropped view const RAUD_FRAC_X = (RAUDHATAIN_SRC_X - KW_MIN_X) / KW_W; const RAUD_FRAC_Y = (RAUDHATAIN_SRC_Y - KW_MIN_Y) / KW_H; function KuwaitMap({ progress, revealProgress }) { // Scale factor: how much to enlarge the 1134x979 SVG so KW_W maps to 100% width const scaleX = SRC_W / KW_W; const scaleY = SRC_H / KW_H; const imgWidthPct = scaleX * 100; const imgHeightPct = scaleY * 100; const leftPct = -(KW_MIN_X / KW_W) * 100; const topPct = -(KW_MIN_Y / KW_H) * 100; return (
{/* Real map image, scaled+positioned to show Kuwait */} Kuwait {/* Accent overlay tinting Kuwait landmass */}
{/* Region labels */}
IRAQ
SAUDI ARABIA
ARABIAN GULF
KUWAIT
{/* Compass */}
N
); } // Fractional coordinates used by Scene01 pin placement const RAUD_FRAC = { x: RAUD_FRAC_X, y: RAUD_FRAC_Y }; // ─────────────────────────────────────────────────────────────────────────── // SCENE 01 — Origin (0 – 5s) // Coordinates → Kuwait map draws → pin drops on Al-Raudhatain field // ─────────────────────────────────────────────────────────────────────────── function Scene01() { const { progress, localTime } = useSprite(); const latProg = clamp((localTime - 0.2) / 0.9, 0, 1); const longProg = clamp((localTime - 0.6) / 0.9, 0, 1); const mapStrokeProg = clamp((localTime - 0.2) / 2.0, 0, 1); const mapFillProg = clamp((localTime - 0.8) / 1.2, 0, 1); const depthProg = clamp((localTime - 1.4) / 1.2, 0, 1); const pinProg = clamp((localTime - 1.8) / 0.8, 0, 1); const ink = 'var(--ink)'; const accent = 'var(--accent)'; const display = 'var(--display-font)'; const mono = 'JetBrains Mono, monospace'; const arabic = 'var(--arabic-font)'; const ringCount = 4; // Map block — fits full Kuwait country in viewport const MAP_W = 860; const MAP_H = 860; const mapLeft = (W - MAP_W) / 2; const mapTop = 560; // Pin position inside map block (fractional, computed from real SVG coords) const pinX_frac = RAUD_FRAC.x; const pinY_frac = RAUD_FRAC.y; return (
{/* Grid overlay */}
{/* Kuwait map block */}
{/* Ripples emanating from Al-Raudhatain */} {[...Array(ringCount)].map((_, i) => { const delay = i * 0.25; const p = clamp((localTime - 1.8 - delay) / 2.5, 0, 1); const size = 50 + p * 380; const op = (1 - p) * 0.55; return (
); })} {/* Pin */}
{/* Pin callout — line goes up-right to label (toward upper right corner) */}
AL-RAUDHATAIN
AQUIFER · N. KUWAIT
{/* Coordinate readouts — above the map */}
LAT
LONG
{/* Depth meter — between coords and map */}
AQUIFER DEPTH
{/* Bilingual title */}
From the depths
of Kuwait's desert.
من أعماق صحراء الكويت
); } // ─────────────────────────────────────────────────────────────────────────── // SCENE 02 — Heritage timeline (5 – 11s) // ─────────────────────────────────────────────────────────────────────────── function Scene02() { const { localTime, duration } = useSprite(); const ink = 'var(--ink)'; const accent = 'var(--accent)'; const display = 'var(--display-font)'; const mono = 'JetBrains Mono, monospace'; const arabic = 'var(--arabic-font)'; const milestones = [ { year: 1981, t: 0.4, label: 'Incorporated in Sharq,\nKuwait.', ar: 'تأسست' }, { year: 1986, t: 1.3, label: 'First bottling plant\nopens at source.', ar: 'أول مصنع' }, { year: 2004, t: 2.2, label: 'International\ncertifications secured.', ar: 'شهادات دولية' }, { year: 2024, t: 3.1, label: 'Rawdatain mobile app\nlaunches on iOS & Android.', ar: 'التطبيق' }, { year: 2026, t: 4.0, label: 'Leading bottler in Kuwait.', ar: 'الرائد' }, ]; const lineProg = clamp(localTime / 4.2, 0, 1); return (
{/* Header */}
—— 45 years of heritage
A history
poured daily.
تاريخ يُسكب يوميًا
{/* Vertical timeline */}
{/* Milestones */} {milestones.map((m, i) => { const appear = clamp((localTime - m.t) / 0.5, 0, 1); const yBase = 760 + i * 180; return (
{/* Dot on line */}
{/* Year */}
{m.year}
{m.label}
); })}
); } // ─────────────────────────────────────────────────────────────────────────── // SCENE 03 — Stats (11 – 17s) // ─────────────────────────────────────────────────────────────────────────── function Scene03() { const { localTime } = useSprite(); const ink = 'var(--ink)'; const accent = 'var(--accent)'; const display = 'var(--display-font)'; const mono = 'JetBrains Mono, monospace'; const arabic = 'var(--arabic-font)'; const stats = [ { t: 0.3, value: 45, suffix: '+', label: 'Years bottling', ar: 'سنة من التعبئة', decimals: 0 }, { t: 0.9, value: 100, suffix: '%', label: 'Recyclable PET', ar: 'قابل لإعادة التدوير', decimals: 0 }, { t: 1.5, value: 1, suffix: 'st', label: 'In Kuwait', ar: 'الأولى في الكويت', decimals: 0 }, { t: 2.1, value: 24, suffix: '/7', label: 'Daily bottling', ar: 'يوميًا', decimals: 0 }, ]; return (
{/* Header */}
—— by the numbers
Light. Balanced.
Pure.
خفيفة. متوازنة. نقية.
{/* Stats grid */}
{stats.map((s, i) => { const prog = clamp((localTime - s.t) / 1.0, 0, 1); const appear = clamp((localTime - s.t) / 0.4, 0, 1); return (
{s.suffix}
{s.label}
{s.ar}
); })}
{/* Live ticker */}
NSF · KQM · FDA · IAS · CPG ↗ LIVE
); } // ─────────────────────────────────────────────────────────────────────────── // SCENE 04 — Product range (17 – 24s) // One hero product per segment, cycles through the range // ─────────────────────────────────────────────────────────────────────────── function Scene04() { const { localTime } = useSprite(); const ink = 'var(--ink)'; const accent = 'var(--accent)'; const display = 'var(--display-font)'; const mono = 'JetBrains Mono, monospace'; const arabic = 'var(--arabic-font)'; // 5 segments × 1.3s each (+ 0.5s lead-in / tail) const SEG = 1.3; const products = [ { code: '01', name: 'Rawdatain 330 mL', ar: 'روضتين ٣٣٠ مل', size: '330 mL PET · 20-pack', hero: 'uploads/rawdatain-330ml.png', tone: '#1B3A6B', }, { code: '02', name: 'Glass Bottle', ar: 'زجاجة زجاجية', size: '240 mL glass · 24-pack', hero: 'uploads/52996.png', tone: '#2A5588', }, { code: '03', name: 'Bared', ar: 'بارد', size: 'Sparkling citrus soda', hero: 'uploads/54475.png', tone: '#2F7A4F', }, { code: '04', name: 'Bloom', ar: 'بلوم', size: 'Sparkling fruit juice', hero: 'uploads/54457.png', tone: '#B86A4A', }, { code: '05', name: 'Flavored Water', ar: 'مياه بنكهات', size: 'Light & refreshing', hero: 'uploads/53000.png', tone: '#A89334', }, ]; // Current segment index based on time const idx = Math.max(0, Math.min(products.length - 1, Math.floor((localTime - 0.2) / SEG))); return (
{/* Header */}
—— the range
One source.
Many expressions.
منبع واحد. تعبيرات عديدة.
{/* Segment progress chips */}
{products.map((p, i) => { const active = i === idx; const passed = i < idx; const segStart = 0.2 + i * SEG; const fillProg = clamp((localTime - segStart) / SEG, 0, 1); return (
); })}
{/* Hero product stage — single product, cross-fades between segments */}
{products.map((p, i) => { const segStart = 0.2 + i * SEG; const inProg = clamp((localTime - segStart) / 0.4, 0, 1); const outProg = clamp((localTime - segStart - SEG + 0.25) / 0.25, 0, 1); const visible = inProg * (1 - outProg); if (visible <= 0) return null; return (
{/* Big index numeral watermark */}
{p.code}
{/* Product image */} {p.name}
); })}
{/* Footer: current product meta */}
{products.map((p, i) => { const segStart = 0.2 + i * SEG; const inProg = clamp((localTime - segStart) / 0.35, 0, 1); const outProg = clamp((localTime - segStart - SEG + 0.2) / 0.2, 0, 1); const visible = inProg * (1 - outProg); if (visible <= 0) return null; return (
{p.code} / {String(products.length).padStart(2, '0')}
{p.name}
{p.ar}
{p.size}
); })}
); } // ─────────────────────────────────────────────────────────────────────────── // SCENE 05 — Certifications (23 – 28s) // ─────────────────────────────────────────────────────────────────────────── function Scene05() { const { localTime } = useSprite(); const ink = 'var(--ink)'; const accent = 'var(--accent)'; const display = 'var(--display-font)'; const mono = 'JetBrains Mono, monospace'; const arabic = 'var(--arabic-font)'; const certs = [ { t: 0.4, name: 'NSF', full: 'National Sanitation Foundation' }, { t: 0.7, name: 'FDA', full: 'U.S. Food & Drug Administration' }, { t: 1.0, name: 'KQM', full: 'Kuwait Quality Mark' }, { t: 1.3, name: 'CPG', full: 'Current Good Manufacturing Practices' }, { t: 1.6, name: 'IAS', full: 'International Accreditation Service' }, ]; return (
—— verified
Purity, audited
at every step.
نقاء مُدقق في كل خطوة
{/* Cert stack */}
{certs.map((c, i) => { const appear = clamp((localTime - c.t) / 0.4, 0, 1); return (
{/* check box */}
{c.name}
{c.full}
✓ ACTIVE
); })}
); } // ─────────────────────────────────────────────────────────────────────────── // SCENE 06 — Logo / outro (28 – 33s) // ─────────────────────────────────────────────────────────────────────────── function Scene06() { const { localTime } = useSprite(); const ink = 'var(--ink)'; const accent = 'var(--accent)'; const display = 'var(--display-font)'; const mono = 'JetBrains Mono, monospace'; const arabic = 'var(--arabic-font)'; const wordmarkProg = clamp(localTime / 1.2, 0, 1); const tagProg = clamp((localTime - 1.4) / 0.6, 0, 1); const ctaProg = clamp((localTime - 2.4) / 0.5, 0, 1); // Floating water droplet SVG (simple circle is allowed per rules) return (
{/* Radial accent glow */}
{/* Official logo (contains both English wordmark + Arabic) */}
Al Rawdatain
{/* Tagline */}
Heritage, bottled daily.
تراث يُعبأ يوميًا
{/* CTA line */}
alrawdatain.com +965 1881234
{/* Bottom small text override */}
Sharq · Kuwait · Since 1981
); } // ─────────────────────────────────────────────────────────────────────────── // Root composition // ─────────────────────────────────────────────────────────────────────────── const SCENES = [ { name: 'ORIGIN', start: 0, end: 5, Component: Scene01 }, { name: 'HERITAGE', start: 5, end: 11, Component: Scene02 }, { name: 'STATS', start: 11, end: 17, Component: Scene03 }, { name: 'PRODUCTS', start: 17, end: 24, Component: Scene04 }, { name: 'CERTIFICATIONS', start: 24, end: 29, Component: Scene05 }, { name: 'SIGNOFF', start: 29, end: 35, Component: Scene06 }, ]; const TOTAL = SCENES[SCENES.length - 1].end; function Composition() { const { time } = useTimeline(); const activeIdx = SCENES.findIndex(s => time >= s.start && time < s.end); const idx = activeIdx === -1 ? SCENES.length - 1 : activeIdx; const active = SCENES[idx]; return ( <> {SCENES.map((s, i) => ( ))} {/* Global HUD */} {/* Safe-area guides (toggleable later if desired) */} ); } Object.assign(window, { Composition, TOTAL });