// 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 */}

{/* Accent overlay tinting Kuwait landmass */}
{/* Region labels */}
IRAQ
SAUDI ARABIA
ARABIAN GULF
KUWAIT
{/* Compass */}
);
}
// 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 */}
{/* Depth meter — between coords and map */}
{/* 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 */}
);
})}
);
}
// ───────────────────────────────────────────────────────────────────────────
// 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 */}
);
})}
{/* 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) */}
{/* 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 });