/* Shared chrome + building blocks for the SparkleClassic storefront.
   Exports to window for sibling babel scripts. */
const DSc = window.SparkleClassicDesignSystem_7a1470;

const SF_LOGO = 'assets/logo/sparkle-star.png';

/* ---------- prefers-reduced-motion hook ---------- */
function useReducedMotion() {
  const [r, setR] = React.useState(
    () => window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
  );
  React.useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const on = () => setR(mq.matches);
    mq.addEventListener && mq.addEventListener('change', on);
    return () => mq.removeEventListener && mq.removeEventListener('change', on);
  }, []);
  return r;
}

/* ---------- reveal-on-mount wrapper (CSS-driven; end-state is visible) ---------- */
function Reveal({ children, delay = 0, style }) {
  return (
    <div className="spk-reveal" style={{ animationDelay: `${delay}ms`, ...style }}>{children}</div>
  );
}

/* ---------- header ---------- */
function SiteHeader({ onHome, onStart, showCta = true }) {
  const { Button, Icon } = DSc;
  return (
    <header style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '10px 12px', gap: 8, background: 'var(--cotton-blue)',
      boxShadow: '0 3px 0 0 var(--plum-ink)', position: 'sticky', top: 0, zIndex: 100,
    }}>
      <button onClick={onHome} aria-label="Sparkle Classic — home" style={{
        display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, overflow: 'hidden',
        background: 'none', border: 'none', cursor: 'pointer', padding: 0,
      }}>
        <img src={SF_LOGO} alt="" style={{ height: 30, flexShrink: 0, imageRendering: 'pixelated' }} />
        <span className="spk-h2" style={{ fontSize: 16, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>Sparkle&nbsp;Classic</span>
      </button>
      {showCta && (
        <Button variant="primary" size="sm" onClick={onStart} style={{ flexShrink: 0 }}>Make my pet a game</Button>
      )}
    </header>
  );
}

/* ---------- footer ---------- */
function SiteFooter({ onStart }) {
  const { Icon } = DSc;
  return (
    <footer style={{
      background: 'var(--plum-ink)', color: 'var(--cream)', padding: '26px 18px 30px',
      textAlign: 'center', fontFamily: 'var(--font-body)', fontSize: 13,
    }}>
      <div style={{ fontFamily: 'var(--font-pixel)', fontSize: 11, marginBottom: 10, letterSpacing: '0.04em' }}>✦ SPARKLE CLASSIC ✦</div>
      <p style={{ margin: 0, opacity: 0.9 }}>Human-made pixel games, one pet at a time.</p>
      <p style={{ margin: '6px 0 16px', opacity: 0.62, fontSize: 12 }}>Your pet's game, ready in about 24 hours — or your money back.</p>
      <div style={{ display: 'flex', gap: 18, justifyContent: 'center', flexWrap: 'wrap', opacity: 0.85 }}>
        <a href="/store/" style={{ color: 'var(--cream)' }}>Store</a>
        <a href="/store/studio.html" style={{ color: 'var(--cream)' }}>Studio</a>
        <a href="#privacy" style={{ color: 'var(--cream)' }}>Privacy</a>
        <a href="#help" style={{ color: 'var(--cream)' }}>Help</a>
        <a href="#refunds" style={{ color: 'var(--cream)' }}>Refunds</a>
      </div>
    </footer>
  );
}

/* ---------- step rail (intake → checkout) ---------- */
function StepRail({ step }) {
  const steps = ['Your pet', 'Payment'];
  return (
    <div style={{ display: 'flex', gap: 8, padding: '14px 16px 2px', justifyContent: 'center' }}>
      {steps.map((label, i) => {
        const active = i === step, done = i < step;
        return (
          <div key={label} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 7,
              fontFamily: 'var(--font-body)', fontWeight: 800, fontSize: 13,
              color: active || done ? 'var(--cream)' : 'var(--plum-ink)',
              background: active ? 'var(--action)' : done ? 'var(--success)' : 'var(--cream-surface)',
              boxShadow: 'var(--ofx-edges)', padding: '6px 13px', whiteSpace: 'nowrap',
            }}>
              <span style={{ fontFamily: 'var(--font-pixel)', fontSize: 9 }}>{done ? '✓' : i + 1}</span>
              {label}
            </div>
            {i < steps.length - 1 && <span style={{ width: 16, height: 3, background: 'var(--plum-ink)' }} />}
          </div>
        );
      })}
    </div>
  );
}

/* ---------- pixel-sparkle particle field (canvas; drifting + twinkling,
   denser toward a focal X). Freezes to a static frame under reduced-motion.
   Fills its positioned parent. Ported from the Sparkle design system. ---------- */
function SparkleField({ count = 70, focal = 0.5, accent = null, className = '', style = {} }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const COLORS = accent
      ? ['#FFF7EF', '#FFE066', accent, accent, '#FFFFFF']
      : ['#FFF7EF', '#FFE066', '#FBD3DD', '#FFFFFF', '#FFD9A8'];
    let w = 0, h = 0; const dpr = Math.min(2, window.devicePixelRatio || 1);
    let sparks = [];
    const resize = () => {
      const r = canvas.getBoundingClientRect();
      w = r.width; h = r.height;
      canvas.width = w * dpr; canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    const spawn = () => {
      const central = Math.random() < 0.5;
      const fx = central ? focal * w + (Math.random() - 0.5) * w * 0.22 : Math.random() * w;
      return {
        x: fx, y: Math.random() * h,
        size: (Math.random() < 0.3 ? 3 : 2) * (central ? 1.3 : 1),
        spd: 0.15 + Math.random() * 0.5, tw: 0.6 + Math.random() * 2.2, ph: Math.random() * Math.PI * 2,
        color: COLORS[(Math.random() * COLORS.length) | 0],
      };
    };
    resize();
    sparks = Array.from({ length: count }, spawn);
    const drawSpark = (s, alpha) => {
      ctx.globalAlpha = alpha; ctx.fillStyle = s.color; const u = s.size;
      ctx.fillRect(s.x - u / 2, s.y - u / 2, u, u);
      ctx.fillRect(s.x - u / 2, s.y - u * 1.5, u, u);
      ctx.fillRect(s.x - u / 2, s.y + u * 0.5, u, u);
      ctx.fillRect(s.x - u * 1.5, s.y - u / 2, u, u);
      ctx.fillRect(s.x + u * 0.5, s.y - u / 2, u, u);
    };
    let raf;
    const render = (t) => {
      ctx.clearRect(0, 0, w, h);
      for (const s of sparks) {
        const tw = (Math.sin(t / 1000 * s.tw + s.ph) + 1) / 2;
        drawSpark(s, 0.25 + tw * 0.75);
        s.y -= s.spd;
        if (s.y < -6) { Object.assign(s, spawn(), { y: h + 4 }); }
      }
      raf = requestAnimationFrame(render);
    };
    const freeze = () => { ctx.clearRect(0, 0, w, h); for (const s of sparks) drawSpark(s, 0.6); };
    if (reduce) freeze(); else raf = requestAnimationFrame(render);
    const onResize = () => { resize(); sparks = Array.from({ length: count }, spawn); if (reduce) freeze(); };
    window.addEventListener('resize', onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
  }, [count, focal, accent]);
  return (
    <canvas ref={ref} aria-hidden="true" className={className}
      style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0, ...style }} />
  );
}

/* ---------- morph field: pixel particles lift off the real-cat silhouette and
   get pulled, swirling, onto the pixel-cat silhouette — the transformation made
   literal. Pass refs to the two hero <img> elements. ---------- */
function MorphField({ realRef, pixelRef, accent = '#FF7EB6' }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    const TWO_PI = Math.PI * 2;
    const hexRGB = (hex) => { const n = parseInt(hex.replace('#', ''), 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255]; };
    const C_START = [255, 247, 239], C_PINK = hexRGB(accent), C_GOLD = [255, 224, 102];
    const lerp = (a, b, t) => a + (b - a) * t;
    const rnd = (a, b) => a + Math.random() * (b - a);
    const pick = (a) => a[(Math.random() * a.length) | 0];

    let w = 0, h = 0, raf = 0, ready = false;
    let realN = null, pixN = null, src = [], tgt = [], parts = [];

    // silhouette outline of an <img> as normalized [u,v] points (alpha edges)
    function outline(img) {
      const nW = img.naturalWidth, nH = img.naturalHeight;
      if (!nW || !nH) return null;
      const cw = 84, ch = Math.max(1, Math.round(84 * nH / nW));
      const oc = document.createElement('canvas'); oc.width = cw; oc.height = ch;
      const og = oc.getContext('2d'); og.imageSmoothingEnabled = false;
      let data;
      try { og.drawImage(img, 0, 0, cw, ch); data = og.getImageData(0, 0, cw, ch).data; } catch (e) { return null; }
      const inside = (x, y) => x >= 0 && y >= 0 && x < cw && y < ch && data[(y * cw + x) * 4 + 3] > 90;
      const pts = [];
      for (let y = 0; y < ch; y++) for (let x = 0; x < cw; x++) {
        if (inside(x, y) && (!inside(x - 1, y) || !inside(x + 1, y) || !inside(x, y - 1) || !inside(x, y + 1))) pts.push([x / cw, y / ch]);
      }
      return pts.length ? pts : null;
    }
    // map normalized outline points to canvas-local px via the img's live rect
    function mapPts(norm, img) {
      const cr = canvas.getBoundingClientRect(), r = img.getBoundingClientRect();
      const ox = r.left - cr.left, oy = r.top - cr.top;
      return norm.map(([u, v]) => ({ x: ox + u * r.width, y: oy + v * r.height }));
    }
    function build() {
      const ri = realRef && realRef.current, pi = pixelRef && pixelRef.current;
      if (!ri || !pi || !ri.complete || !pi.complete || !ri.naturalWidth || !pi.naturalWidth) return false;
      realN = realN || outline(ri); pixN = pixN || outline(pi);
      if (!realN || !pixN) return false;
      src = mapPts(realN, ri); tgt = mapPts(pixN, pi);
      return src.length > 0 && tgt.length > 0;
    }
    function resize() {
      const r = canvas.getBoundingClientRect();
      w = r.width; h = r.height;
      canvas.width = Math.max(1, Math.round(w * dpr)); canvas.height = Math.max(1, Math.round(h * dpr));
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    function spawn(p, stagger) {
      const s = pick(src), t = pick(tgt);
      p.x = s.x + rnd(-2, 2); p.y = s.y + rnd(-2, 2);
      p.tx = t.x + rnd(-2, 2); p.ty = t.y + rnd(-2, 2);
      p.d0 = Math.max(24, Math.hypot(p.tx - p.x, p.ty - p.y));
      p.vx = rnd(-0.25, 0.25); p.vy = rnd(-0.4, 0.1);
      p.size = Math.random() < 0.28 ? 3 : 2;
      p.tw = rnd(0.7, 2.4); p.ph = Math.random() * TWO_PI;
      p.tint = Math.random() < 0.42 ? C_GOLD : C_PINK;
      p.prog = 0; p.life = stagger ? -((Math.random() * 140) | 0) : 0;
      p.max = rnd(150, 230) | 0; p.state = 'fly'; p.arr = 0;
    }
    function update(p) {
      if (p.life < 0) { p.life++; return; }
      const dx = p.tx - p.x, dy = p.ty - p.y, dist = Math.hypot(dx, dy) || 0.001;
      p.prog = Math.max(0, Math.min(1, 1 - dist / p.d0));
      if (p.state === 'fly') {
        const nx = dx / dist, ny = dy / dist;
        p.vx += nx * 0.072; p.vy += ny * 0.072;            // slow gravitational pull toward the pixel-cat outline
        const swirl = 0.09 * (1 - p.prog);                  // vortex: tangential, one direction, eases off on approach
        p.vx += -ny * swirl; p.vy += nx * swirl;
        p.vx *= 0.94; p.vy *= 0.94;                         // damping so it spirals in and settles
        p.x += p.vx; p.y += p.vy; p.life++;
        if (dist < 5 || p.life > p.max) { p.state = 'arrive'; p.arr = 0; }
      } else {                                              // settle onto the outline point, twinkle, then respawn
        p.x += (p.tx - p.x) * 0.22; p.y += (p.ty - p.y) * 0.22;
        if (++p.arr > 26) spawn(p, false);
      }
    }
    function draw(p, t) {
      if (p.life < 0) return;
      const tw = (Math.sin(t / 1000 * p.tw + p.ph) + 1) / 2;
      const a = p.state === 'arrive' ? (0.55 + tw * 0.45) * (1 - p.arr / 26) : Math.min(1, p.life / 14) * (0.32 + tw * 0.68);
      if (a <= 0.02) return;
      ctx.globalAlpha = a;
      ctx.fillStyle = `rgb(${lerp(C_START[0], p.tint[0], p.prog) | 0},${lerp(C_START[1], p.tint[1], p.prog) | 0},${lerp(C_START[2], p.tint[2], p.prog) | 0})`;
      const u = p.size;
      ctx.fillRect(p.x - u / 2, p.y - u / 2, u, u);
      ctx.fillRect(p.x - u / 2, p.y - u * 1.5, u, u);
      ctx.fillRect(p.x - u / 2, p.y + u * 0.5, u, u);
      ctx.fillRect(p.x - u * 1.5, p.y - u / 2, u, u);
      ctx.fillRect(p.x + u * 0.5, p.y - u / 2, u, u);
    }
    function freeze() {                                     // reduced-motion: a still sprinkle along both silhouettes
      ctx.clearRect(0, 0, w, h); ctx.globalAlpha = 0.7;
      for (let i = 0; i < src.length; i += 3) { ctx.fillStyle = 'rgb(255,247,239)'; ctx.fillRect(src[i].x - 1, src[i].y - 1, 2, 2); }
      for (let i = 0; i < tgt.length; i += 3) { ctx.fillStyle = `rgb(${C_PINK[0]},${C_PINK[1]},${C_PINK[2]})`; ctx.fillRect(tgt[i].x - 1, tgt[i].y - 1, 2, 2); }
      ctx.globalAlpha = 1;
    }
    function init() {
      const N = Math.round(Math.max(70, Math.min(150, w / 3.2)));
      parts = Array.from({ length: N }, () => { const p = {}; spawn(p, true); return p; });
    }
    function frame(t) {
      if (!ready) {
        resize();
        if (build()) { ready = true; if (reduce) { freeze(); return; } init(); }
        else { raf = requestAnimationFrame(frame); return; }
      }
      ctx.clearRect(0, 0, w, h);
      for (const p of parts) { update(p); draw(p, t); }
      ctx.globalAlpha = 1;
      raf = requestAnimationFrame(frame);
    }
    raf = requestAnimationFrame(frame);
    const onResize = () => { ready = false; if (reduce) raf = requestAnimationFrame(frame); };
    window.addEventListener('resize', onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
  }, [realRef, pixelRef, accent]);
  return <canvas ref={ref} aria-hidden="true"
    style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 2 }} />;
}

/* ---------- glowing blocky arrow (between the hero cats) ---------- */
function PixelArrow({ unit = 5, color = '#FFE066', glow = '#FFC15E' }) {
  const cells = [[3], [3, 4], [3, 4, 5], [0, 1, 2, 3, 4, 5, 6], [3, 4, 5], [3, 4], [3]];
  const shadows = [];
  cells.forEach((cols, r) => cols.forEach((c) => { shadows.push(`${c * unit}px ${r * unit}px 0 0 ${color}`); }));
  return (
    <span aria-hidden="true" style={{ display: 'inline-block', width: unit * 7, height: unit * 7, position: 'relative', filter: `drop-shadow(0 0 6px ${glow}) drop-shadow(0 0 12px ${glow})` }}>
      <span style={{ position: 'absolute', top: 0, left: 0, width: unit, height: unit, boxShadow: shadows.join(',') }} />
    </span>
  );
}

/* ---------- platformer scene mock with a droppable hero sprite ---------- */
function GameScene({ slotId, label = 'LV 1 · MEADOW', petName = 'Your pet', spriteSrc }) {
  return (
    <div style={{
      position: 'relative', overflow: 'hidden',
      boxShadow: 'var(--ofx-edges), var(--shadow-block-lg)',
      aspectRatio: '16 / 11', background: 'linear-gradient(180deg, var(--sky), #C5D8EE)',
    }}>
      <div style={{ position: 'absolute', top: 9, left: 9, fontFamily: 'var(--font-pixel)', fontSize: 9, color: 'var(--plum-ink)', background: 'var(--cream)', boxShadow: 'var(--ofx-edges)', padding: '4px 6px' }}>{label}</div>
      <div style={{ position: 'absolute', top: 11, right: 11, fontFamily: 'var(--font-pixel)', fontSize: 9, color: 'var(--sprite-outline)' }}>★ x3</div>
      {/* clouds */}
      <div style={{ position: 'absolute', left: '60%', top: '14%', width: 44, height: 16, background: '#EAF1FA', boxShadow: 'var(--ofx-edges)' }} />
      {/* platforms */}
      <div style={{ position: 'absolute', left: '12%', top: '52%', width: 66, height: 15, background: 'var(--mint)', boxShadow: 'var(--ofx-edges)' }} />
      <div style={{ position: 'absolute', right: '14%', top: '38%', width: 58, height: 15, background: 'var(--mint)', boxShadow: 'var(--ofx-edges)' }} />
      {/* coin */}
      <div style={{ position: 'absolute', right: '22%', top: '23%', width: 15, height: 15, background: 'var(--sunshine)', boxShadow: 'var(--ofx-edges)', borderRadius: '50%' }} />
      {/* hero sprite slot */}
      <image-slot
        id={slotId}
        shape="rect"
        fit="cover"
        src={spriteSrc}
        placeholder="Drop sprite"
        style={{ position: 'absolute', left: '15%', bottom: '22%', width: '46px', height: '46px', boxShadow: 'var(--ofx-edges)', imageRendering: 'pixelated' }}
      ></image-slot>
      {/* ground */}
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: '18%', background: 'var(--mint)', borderTop: '4px solid var(--sprite-outline)' }} />
    </div>
  );
}

/* ---------- before → after pair ---------- */
function BeforeAfter({ photoId, spriteId, photoSrc, spriteSrc, name, world = 'Meadow' }) {
  const { Icon } = DSc;
  const tile = { aspectRatio: '1', width: '100%', boxShadow: 'var(--ofx-edges)', imageRendering: 'pixelated' };
  return (
    <div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr auto 1fr', alignItems: 'center', gap: 8 }}>
        <div style={{ minWidth: 0 }}>
          <image-slot id={photoId} shape="rect" fit="cover" placeholder="Pet photo" src={photoSrc} style={{ ...tile, maxWidth: '100%', display: 'block' }}></image-slot>
          <p style={{ fontFamily: 'var(--font-pixel)', fontSize: 7, textAlign: 'center', margin: '6px 0 0', color: 'var(--plum-accent)' }}>PHOTO</p>
        </div>
        <span style={{ color: 'var(--plum-ink)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20 }}>
          <Icon name="arrow-right" />
        </span>
        <div style={{ minWidth: 0 }}>
          <image-slot id={spriteId} shape="rect" fit="contain" placeholder="Pixel hero" src={spriteSrc} style={{ ...tile, maxWidth: '100%', display: 'block', background: `var(--mint)` }}></image-slot>
          <p style={{ fontFamily: 'var(--font-pixel)', fontSize: 7, textAlign: 'center', margin: '6px 0 0', color: 'var(--plum-accent)' }}>IN-GAME</p>
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginTop: 10 }}>
        <strong style={{ fontFamily: 'var(--font-display)', fontSize: 19, color: 'var(--plum-ink)' }}>{name}</strong>
        <span style={{ fontFamily: 'var(--font-pixel)', fontSize: 8, color: 'var(--plum-accent)' }}>{world.toUpperCase()}</span>
      </div>
    </div>
  );
}

/* ---------- droppable demo-video frame ---------- */
function VideoSlot({ caption }) {
  const { Icon, Badge } = DSc;
  // Bundled looping gameplay demo (built by capture/build-demo.sh). Drag-drop
  // an .mp4 onto the slot to preview a different cut without editing code.
  const DEMO_SRC = '/store/assets/demo/sparkle-demo.mp4';
  const DEMO_POSTER = '/store/assets/demo/sparkle-demo-poster.jpg';
  const [src, setSrc] = React.useState(DEMO_SRC);
  const [over, setOver] = React.useState(false);
  const inputRef = React.useRef(null);
  const load = (file) => {
    if (file && file.type.startsWith('video/')) setSrc(URL.createObjectURL(file));
  };
  return (
    <figure style={{ margin: 0 }}>
      <div
        onDragOver={(e) => { e.preventDefault(); setOver(true); }}
        onDragLeave={() => setOver(false)}
        onDrop={(e) => { e.preventDefault(); setOver(false); load(e.dataTransfer.files[0]); }}
        style={{
          position: 'relative', aspectRatio: '16 / 11', overflow: 'hidden',
          background: src ? 'var(--sprite-outline)' : 'var(--pink-soft)',
          boxShadow: over ? 'var(--ofx-edges), 0 0 0 4px var(--pink-soft)' : 'var(--ofx-edges), var(--shadow-block-lg)',
        }}
      >
        {src ? (
          <video src={src} poster={DEMO_POSTER} autoPlay muted loop playsInline
            style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
        ) : (
          <button onClick={() => inputRef.current && inputRef.current.click()} style={{
            position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', gap: 10,
            alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
            background: 'none', border: 'none', color: 'var(--plum-ink)', padding: 24,
          }}>
            <span style={{
              width: 58, height: 58, display: 'flex', alignItems: 'center', justifyContent: 'center',
              background: 'var(--cream)', boxShadow: 'var(--ofx-edges)', fontSize: 26,
            }}><Icon name="play" /></span>
            <strong style={{ fontSize: 16 }}>Watch the demo</strong>
            <span className="spk-muted" style={{ fontSize: 13, textAlign: 'center', maxWidth: 240 }}>
              Real gameplay — plays muted, with captions. Drop an .mp4 here or tap to load one.
            </span>
            <span style={{ fontFamily: 'var(--font-pixel)', fontSize: 8, color: 'var(--plum-accent)' }}>VIDEO SLOT</span>
          </button>
        )}
        <input ref={inputRef} type="file" accept="video/*" onChange={(e) => load(e.target.files[0])} style={{ display: 'none' }} />
      </div>
      <figcaption style={{ display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'center', marginTop: 12 }}>
        <Icon name="check" size={16} aria-hidden="true" />
        <span style={{ fontWeight: 700, color: 'var(--plum-ink)', fontSize: 14 }}>{caption}</span>
      </figcaption>
    </figure>
  );
}

Object.assign(window, {
  useReducedMotion, Reveal, SiteHeader, SiteFooter, StepRail,
  SparkleField, MorphField, PixelArrow, GameScene, BeforeAfter, VideoSlot, SF_LOGO,
});
