/* T-AV — Home page with 11-beat scroll choreography */

const { useState, useEffect, useRef, useCallback } = React;

/* =========================================================
   Shared scroll-progress tracker (singleton).
   Every Beat subscribes its ref + setter into the registry.
   ONE scroll/resize listener; ONE rAF per tick; rects read
   in succession so the browser flushes layout exactly once.
   ========================================================= */
const __scrollRegistry = new Set();
let __scrollRaf = null;
let __scrollAttached = false;

function __scrollFlush() {
  __scrollRaf = null;
  const vh = window.innerHeight;
  for (const entry of __scrollRegistry) {
    const el = entry.ref.current;
    if (!el) continue;
    const rect = el.getBoundingClientRect();
    const total = rect.height + vh;
    const traveled = vh - rect.top;
    /* Guard the divide. When the viewport is degenerate (window.innerHeight
       is 0 — a transient state browsers can report during early init or
       when the element is display:none) `total` is 0 and `traveled / total`
       is NaN. A NaN here propagates through every Beat's progress prop into
       every derived SVG attribute. Treat a zero-extent measurement as
       "not started" (progress = 0). */
    const p = total > 0 ? Math.max(0, Math.min(1, traveled / total)) : 0;
    entry.setProgress(p);
  }
}

function __scrollSchedule() {
  if (__scrollRaf !== null) return;
  __scrollRaf = requestAnimationFrame(__scrollFlush);
}

function __ensureScrollListeners() {
  if (__scrollAttached) return;
  __scrollAttached = true;
  window.addEventListener('scroll', __scrollSchedule, { passive: true });
  window.addEventListener('resize', __scrollSchedule);
}

/* Hook: track per-element progress through its sticky pin */
function useScrollProgress(ref) {
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    const entry = { ref, setProgress };
    __scrollRegistry.add(entry);
    __ensureScrollListeners();
    __scrollSchedule();
    return () => { __scrollRegistry.delete(entry); };
  }, [ref]);
  return progress;
}

/* =========================================================
   HeroWaves — canvas-based ribbon-cloud animation for the
   hero background. 56 stacked thin bezier curves share an
   11-point control skeleton; each curve gets a small phase
   offset so collectively they read as a translucent
   audio-waveform cloud. Additive blending (`lighter`) makes
   overlaps glow softly.
   ----------------------------------------------------------
   Sanctioned by the brand owner as the diegetic AV-waveform:
   an exception
   to "Earned Motion = no looping decoration" because the
   waveform IS the brand voice (audio made visible). Honors
   prefers-reduced-motion (renders one static frame at phase
   12.0 and skips the rAF loop).
   ========================================================= */
function HeroWaves() {
  const canvasRef = useRef(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');

    const cfg = {
      LINE_COUNT: 56,    // total stacked curves
      LINE_ALPHA: 0.36,  // base per-line opacity
      STROKE_W:   0.8,   // stroke width
      Y_SPREAD:   0.17,  // vertical fan-out of the stack
      BASE_AMP:   0.30,  // base wave amplitude (frac of stageH)
      SPEED:      0.26   // global animation speed
    };
    const POINT_COUNT = 11;
    const REDUCED = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    // Brand palette ("tav"): teal → teal-light → parchment-warm → teal.
    // Each curve lerps to a different stop, giving the cloud subtle
    // color depth without breaking the One Voice rule (teal dominates).
    const STOPS = [
      [62, 140, 132],
      [109, 172, 181],
      [184, 216, 168],
      [62, 140, 132]
    ];

    // 11 anchor points, each with its own two-frequency oscillator.
    const anchors = [];
    for (let i = 0; i < POINT_COUNT; i++) {
      anchors.push({
        f1: 0.6 + Math.random() * 1.4,
        f2: 1.2 + Math.random() * 2.0,
        p1: Math.random() * Math.PI * 2,
        p2: Math.random() * Math.PI * 2,
        w1: 0.65 + Math.random() * 0.35,
        w2: 0.20 + Math.random() * 0.25
      });
    }

    let stageW = 0, stageH = 0, dpr = 1;
    function resize() {
      const rect = canvas.getBoundingClientRect();
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      stageW = rect.width;
      stageH = rect.height;
      canvas.width  = Math.round(stageW * dpr);
      canvas.height = Math.round(stageH * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    resize();
    window.addEventListener('resize', resize);

    function colorForLine(t) {
      const n = STOPS.length - 1;
      const i = Math.min(Math.floor(t * n), n - 1);
      const k = (t * n) - i;
      const a = STOPS[i], b = STOPS[i + 1];
      const r  = Math.round(a[0] + (b[0] - a[0]) * k);
      const g  = Math.round(a[1] + (b[1] - a[1]) * k);
      const bl = Math.round(a[2] + (b[2] - a[2]) * k);
      return `rgba(${r},${g},${bl},${cfg.LINE_ALPHA})`;
    }

    function anchorY(i, time, lineOffset) {
      const a = anchors[i];
      const o = lineOffset * (0.45 + (i % 3) * 0.18);
      const v =
        Math.sin(time * a.f1 + a.p1 + o) * a.w1 +
        Math.sin(time * a.f2 + a.p2 + o * 1.7) * a.w2;
      const u = i / (POINT_COUNT - 1);
      const edgeTaper = Math.sin(Math.PI * u); // 0 at edges, 1 mid
      return v * edgeTaper;
    }

    function drawCurve(time, lineIdx) {
      const lineT = lineIdx / (cfg.LINE_COUNT - 1);
      const stackOffset = (lineT - 0.5) * stageH * cfg.Y_SPREAD;
      const phase = (lineT - 0.5) * 1.6;
      const ampScale = 0.78 + 0.32 * Math.sin(time * 0.6 + lineT * 6.28);

      const pts = [];
      for (let i = 0; i < POINT_COUNT; i++) {
        const x = (i / (POINT_COUNT - 1)) * stageW;
        const y0 = stageH / 2 + stackOffset;
        const y  = y0 + anchorY(i, time, phase) * stageH * cfg.BASE_AMP * ampScale;
        pts.push({ x: x, y: y });
      }

      ctx.beginPath();
      ctx.moveTo(pts[0].x, pts[0].y);
      for (let i = 0; i < pts.length - 1; i++) {
        const p0 = pts[i - 1] || pts[i];
        const p1 = pts[i];
        const p2 = pts[i + 1];
        const p3 = pts[i + 2] || p2;
        const tension = 0.5;
        const cp1x = p1.x + (p2.x - p0.x) / 6 * tension * 2;
        const cp1y = p1.y + (p2.y - p0.y) / 6 * tension * 2;
        const cp2x = p2.x - (p3.x - p1.x) / 6 * tension * 2;
        const cp2y = p2.y - (p3.y - p1.y) / 6 * tension * 2;
        ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
      }
      ctx.strokeStyle = colorForLine(lineT);
      ctx.lineWidth = cfg.STROKE_W;
      ctx.stroke();
    }

    let phaseT = 0;
    let last = performance.now();
    let rafId = null;

    /* Cursor-modulated wave speed: cursor near the canvas center = base
       speed (1.0×), cursor at the far edge of the viewport = 1.25× speed.
       Tightly capped so the effect never reads as parallax (which is on
       the vestibular-trigger list). Damped follow keeps the response
       smooth rather than jerky. Only attached on fine-pointer devices;
       reduced motion does not enter the rAF loop at all so the listener
       is a no-op there too. Motion audit 2026-05-25 — Jhey Opportunity. */
    let speedFactor = 1.0;
    let targetSpeedFactor = 1.0;
    const finePointer = typeof window.matchMedia === 'function'
      && window.matchMedia('(hover: hover) and (pointer: fine)').matches;

    function onPointerMove(e) {
      const rect = canvas.getBoundingClientRect();
      if (rect.width === 0 || rect.height === 0) return;
      const cx = ((e.clientX - rect.left) / rect.width)  * 2 - 1;
      const cy = ((e.clientY - rect.top)  / rect.height) * 2 - 1;
      const d = Math.min(1, Math.sqrt(cx * cx + cy * cy) / 1.4);
      targetSpeedFactor = 1.0 + d * 0.25;
    }

    function frame(now) {
      const dt = now - last;
      last = now;
      speedFactor += (targetSpeedFactor - speedFactor) * 0.06;
      phaseT += dt * cfg.SPEED * speedFactor * 0.001;
      ctx.clearRect(0, 0, stageW, stageH);
      ctx.globalCompositeOperation = 'lighter';
      for (let i = 0; i < cfg.LINE_COUNT; i++) drawCurve(phaseT, i);
      ctx.globalCompositeOperation = 'source-over';
      rafId = requestAnimationFrame(frame);
    }

    if (REDUCED) {
      // Single static frame at a stable phase. No rAF loop.
      ctx.clearRect(0, 0, stageW, stageH);
      ctx.globalCompositeOperation = 'lighter';
      for (let i = 0; i < cfg.LINE_COUNT; i++) drawCurve(12.0, i);
      ctx.globalCompositeOperation = 'source-over';
    } else {
      rafId = requestAnimationFrame(frame);
      if (finePointer) {
        window.addEventListener('pointermove', onPointerMove, { passive: true });
      }
    }

    return () => {
      if (rafId) cancelAnimationFrame(rafId);
      window.removeEventListener('resize', resize);
      window.removeEventListener('pointermove', onPointerMove);
    };
  }, []);

  return (
    <div className="home-hero__waves" aria-hidden="true">
      <canvas ref={canvasRef} className="home-hero__waves-canvas" />
    </div>
  );
}

function Beat({ idx, total, num, tag, title, accent, copy, meta, slug, Visual, reverse, featured, children, navigate, beatRefs, setActiveBeat }) {
  const ref = useRef(null);
  const progress = useScrollProgress(ref);
  // Map outer progress 0..1 to inner animation: 0..0.25 entering, 0.25..0.75 stable, 0.75..1 exiting
  const innerT = Math.max(0, Math.min(1, (progress - 0.15) / 0.7));
  /* Peak-handoff fade: at the show's peak beats (07 Full Production and
     08 Show Live) the visual softly dims as the next beat starts
     entering, so the baton-pass between the heaviest visuals reads as
     a fade-out → fade-in instead of a hard unmount. Other beats keep
     their static handoff. Motion audit 2026-05-25 — Jhey Opportunity. */
  const isPeak = num === '07' || num === '08';
  const peakLeaving = isPeak && progress > 0.85;

  useEffect(() => {
    if (progress > 0.25 && progress < 0.85) setActiveBeat(idx);
  }, [progress, idx, setActiveBeat]);

  return (
    <div className="beat" ref={(el) => {ref.current = el;if (beatRefs) beatRefs.current[idx] = el;}}>
      <div className="beat__sticky">
        <div className={"beat__inner" + (reverse ? " reverse" : "")}>
          <div className={"beat__visual" + (featured ? " featured" : "") + (peakLeaving ? " is-leaving" : "")}>
            {Visual ? <Visual progress={innerT} /> : null}
          </div>
          <div className="beat__text">
            <div className="beat__num">{`Beat ${num} · ${tag}`}</div>
            <h2 className="beat__title">{title}{accent ? <> <span className="accent">{accent}</span></> : null}</h2>
            <p className="beat__copy">{copy}</p>
            {children}
            {meta && <div className="beat__meta">{meta.map((m, i) => <span key={i} className="chip">{m}</span>)}</div>}
            {slug !== undefined && (
              <a className="btn-link beat__cta"
                href={slug ? "#/what-we-do/" + slug : "#/what-we-do"}
                onClick={(e) => {e.preventDefault(); navigate(slug ? 'what-we-do/' + slug : 'what-we-do');}}>
                Open the spec sheet <ArrowRight />
              </a>
            )}
          </div>
        </div>
      </div>
    </div>);

}

/* Beat 9 — IG strip (in-flow, no sticky) */
/* Beat 10 · ON THE RECORD — combined section.
   Merged in 2026-05-12: the former Beat 10 (showcase teaser cards)
   and the former Beat 11 (Instagram strip) are conceptually one
   "the work is visible" beat. They render as two stacked grids under
   a single headline, each with its own CTA below it.

   The showcase cards use the photos starred in /admin (hero: true in
   the CSV, set via the ★ button). The admin's swap popover enforces
   exactly 3; the home page filters and renders them in CSV order.
   No randomness on home.
   The Instagram tiles below use the separate IG_POOL — see below. */

// Fallback tiles used to pad the strip when the IG pool has < 6 photos.
// Keeps the row from looking broken if the owner hasn't flagged enough yet.
const FALLBACK_IG_TILES = [
  { cap: 'CONCERT · DUBAI',    svg: TeaserConcert },
  { cap: 'LAUNCH · ABU DHABI', svg: TeaserLaunch },
  { cap: 'GOVERNMENT',         svg: TeaserGov },
  { cap: 'FESTIVAL · SHARJAH', svg: TeaserFestival },
  { cap: 'PRIVATE · UAE',      svg: TeaserPrivate },
  { cap: 'SPORTS · DUBAI',     svg: TeaserSports }];

function BeatShowcaseTeaser({ navigate, sectionRef }) {
  const showcaseCards = React.useMemo(() => {
    const pool = window.SHOWCASE_ITEMS || [];
    return pool.filter((it) => it.hero === true).slice(0, 3);
  }, []);

  // Shuffle the IG pool once at mount; useMemo with empty deps gives a
  // fresh order on every component remount (= every page navigation /
  // browser reload). Independent of the Showcase page's shuffle.
  const igTiles = React.useMemo(() => {
    const pool = window.IG_POOL || [];
    const shuffle = window.shuffleArray;
    const shuffled = (shuffle ? shuffle(pool) : pool.slice()).slice(0, 6);
    if (shuffled.length >= 6) return shuffled;
    // Pad with placeholder SVGs to keep the row at 6 tiles.
    const padded = [...shuffled];
    for (let i = 0; padded.length < 6 && i < FALLBACK_IG_TILES.length; i++) {
      padded.push(FALLBACK_IG_TILES[i]);
    }
    return padded;
  }, []);

  return (
    <section className="section" ref={sectionRef}>
      <div className="container-wide">
        <div className="beat__inner" style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 24 }}>
          <div className="beat__text" style={{ maxWidth: 760 }}>
            <div className="beat__num">Beat 10 · ON THE RECORD</div>
            <h2 className="beat__title">500+ shows, <span className="accent">on the record.</span></h2>
          </div>

          {/* Showcase teaser cards — 3 photos starred in /admin. No randomness on home. */}
          <div className="showcase-teaser">
            {showcaseCards.map((item) =>
              <a key={item.id} className="teaser-card teaser-card--photo" href="#/showcase" onClick={(e) => {e.preventDefault();navigate('showcase');}}>
                <img className="teaser-card__img" src={item.img} alt={item.title} loading="lazy" decoding="async" />
                <div className="teaser-card__caption">
                  <div className="title">{item.title}</div>
                  <div className="meta">{item.venue} · {item.year}</div>
                </div>
              </a>
            )}
          </div>
          <div style={{ marginTop: 12 }}>
            <a className="btn-link" href="#/showcase" onClick={(e) => {e.preventDefault();navigate('showcase');}}>See the full showcase <ArrowRight /></a>
          </div>

          {/* Instagram strip — 6 tiles, sourced from IG_POOL with SVG fallback. */}
          <div className="ig-strip" style={{ marginTop: 40 }}>
            {igTiles.map((t, i) => {
              // Photo tile (from IG_POOL): has img, category, venue.
              if (t.img) {
                const cap = `${t.category} · ${t.venue}`.toUpperCase();
                return (
                  <a key={`p-${t.id}`} className="ig-tile" href="https://www.instagram.com/av_t_av/" target="_blank" rel="noopener noreferrer">
                    <img src={t.img} alt="" loading="lazy" decoding="async" />
                    <div className="ig-tile__caption">{cap}</div>
                  </a>);
              }
              // Fallback SVG tile: has cap, svg.
              const Svg = t.svg;
              return (
                <a key={`s-${i}`} className="ig-tile" href="https://www.instagram.com/av_t_av/" target="_blank" rel="noopener noreferrer">
                  <Svg />
                  <div className="ig-tile__caption">{t.cap}</div>
                </a>);
            })}
          </div>
          <div style={{ marginTop: 12 }}>
            <a className="btn-link" href="https://www.instagram.com/av_t_av/" target="_blank" rel="noopener noreferrer">Open Instagram <ArrowRight /></a>
          </div>
        </div>
      </div>
    </section>);

}

function HomePage({ navigate }) {
  const [activeBeat, setActiveBeat] = useState(0);
  const beatRefs = useRef([]);
  const railVisible = activeBeat >= 1 && activeBeat <= 7;
  const statusPanelRef = useRef(null);

  /* home-3d quality switch. Decided ONCE before React boots
     (components/Stage3D/quality.js) and stable for the whole mount, so
     the 3D pin renders unconditionally within a mount (§5.8 rule 1).
     3D: Home3DPin replaces the .story section (fixed canvas + scroll
     pin + overlay panels). 2D: today's story renders exactly as is. */
  const q3d = window.TAVQuality;
  const use3d = !!(q3d && q3d.mode === '3d' && window.Home3DPin);

  /* Status-panel scan: one-shot on first viewport intersection. Replaces
     the prior infinite 4s loop (motion audit 2026-05-25 finding #3) so
     the panel reads "armed once" rather than mission-control-decorating
     forever. CSS .status-panel.is-armed::before runs status-scan once. */
  useEffect(() => {
    const el = statusPanelRef.current;
    if (!el) return;
    if (typeof IntersectionObserver === 'undefined') {
      el.classList.add('is-armed');
      return;
    }
    const io = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        el.classList.add('is-armed');
        io.disconnect();
      }
    }, { threshold: 0.15 });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  /* Beat copy is single-sourced from window.BEAT_COPY
     (components/BeatCopy.jsx — shared with the 3D pin's panels). The 2D
     story keeps only its presentational fields here, zipped by index
     onto BEAT_COPY[1..7] (beats 02-08; beat 01 is the standalone <Beat>
     below). Beat 08 is the PEAK: audience present, doors open, house
     lights out — every prior beat was preparation; featured like 07. */
  const B = window.BeatVisuals;
  const BEAT_2D = [
  { Visual: B.BeatFloorPlan },
  { Visual: B.BeatTrusses, reverse: true },
  { Visual: B.BeatAudio },
  { Visual: B.BeatLights, reverse: true },
  { Visual: B.BeatVideo },
  { Visual: B.BeatSynced, reverse: true, featured: true },
  { Visual: B.BeatShowLive, featured: true }];

  const beats = window.BEAT_COPY.slice(1).map((b, i) => ({ ...b, ...BEAT_2D[i] }));
  const b01 = window.BEAT_COPY[0];


  return (
    <div className={'page' + (use3d ? ' page--stage3d' : '')}>
      {/* Hero. In 3D mode the wave field is rendered by Home3DPin's
          FIXED full-viewport canvas behind the whole page (it crossfades
          over the 2D HeroWaves placeholder below via data-stage3d on
          this section); the hero itself hosts no 3D canvas anymore. */}
      <section className="home-hero">
        <HeroWaves />
        <div className="home-hero__dots" />
        <div className="container-wide">
          <div className="home-hero__inner">
            <div>
              <div className="home-hero__overline">T-AV · DUBAI · UAE · SINCE 2011</div>
              <h1 className="home-hero__title">
                Creative AV engineering for events — <span className="accent">designed, engineered, delivered.</span>
              </h1>
              <div className="home-hero__cta-row">
                <button className="btn" onClick={() => navigate('contact')}>Let's talk <ArrowRight /></button>
              </div>
            </div>

            <div className="status-panel" ref={statusPanelRef}>
              <div className="status-panel__head">
                <span>SYS · LIVE STATUS</span>
                <span className="right">ONLINE</span>
              </div>
              <div className="status-panel__row">
                <span className="label">Crew</span>
                <span className="value"><span className="dot"></span>On-site · 12</span>
              </div>
              <div className="status-panel__row">
                <span className="label">Active Shows</span>
                <span className="value">03 · UAE</span>
              </div>
              <div className="status-panel__row">
                <span className="label">Inventory</span>
                <span className="value">12 thousand+ ITEMS</span>
              </div>
              <div className="status-panel__row">
                <span className="label">FOH</span>
                <span className="value">DiGiCo 338 · ARMED</span>
              </div>
              <div className="status-panel__row">
                <span className="label">Lead Engineer</span>
                <span className="value"> 0–14 DAYS</span>
              </div>
              <div className="status-panel__row">
                <span className="label">Last Show</span>
                <span className="value">1h AGO · DXB</span>
              </div>
            </div>
          </div>
        </div>
      </section>

      {/* Story arc — beats 01-08.
          3D: the Home3DPin scroll pin (fixed canvas world + one
          ScrollTrigger + master overlay timeline) REPLACES the story
          section. 2D fallback: today's story renders exactly as is. */}
      {use3d ? (
      <window.Home3DPin navigate={navigate} />
      ) : (
      <section className="story container-wide">
        <div className="story__intro">
          <span>// SCROLL · FROM EMPTY FLOOR TO LIVE SHOW</span>
          <span className="right">12 BEATS</span>
        </div>

        {/* Beat 01 — The Brief. All 8 event types type themselves into the
            BeatEmpty SVG as a scroll-driven intake form (progress 0→0.65),
            then CONCERT stamps with a halo flash + SELECTED tag at 0.65, and
            the locked state holds across 0.70→1.0. Event types also render as
            standard .beat__meta chips below the body copy for SEO + screen-
            reader visibility. The "Open the spec sheet" CTA links to
            /what-we-do (the index). */}
        <Beat idx={0} num={b01.num} tag={b01.tag}
              title={b01.title}
              accent={b01.accent}
              copy={b01.copy}
              meta={b01.meta}
              slug={b01.slug}
              Visual={B.BeatEmpty} navigate={navigate} beatRefs={beatRefs} setActiveBeat={setActiveBeat} />

        {beats.map((b, i) =>
        <Beat key={i} idx={i + 1} {...b} navigate={navigate} beatRefs={beatRefs} setActiveBeat={setActiveBeat} />
        )}
      </section>
      )}

      {/* Beat 09 — stage quiet again (was BEAT 08; bumped by the SHOW LIVE beat insert).
          Beats 09, 10 and 11 all share the beats' composition: a `.section`
          wrapper for the vertical rhythm, and the inline teal `.beat__num`
          label tucked inside the text column (NOT the steel `.divider-row`,
          which the other pages use for plain section headers). All twelve
          beats read as one uniform series.
          3D MODE (§H.5, T10): this standalone section is ABSORBED into the
          pin's §F ending — its num/tag/title/copy render as the final
          in-pin overlay (.stage3d-quiet in Home3DPin) docked beside the
          framed proof photo. Only the 2D fallback keeps the section. */}
      {!use3d && (
      <section className="section" ref={(el) => { beatRefs.current[8] = el; }}>
        <div className="container-wide">
          {/* `reverse`: Beat 09's visual sits on the right (as it always has).
              The beats keep the visual in the wider 1.05fr column either way —
              `reverse` is what puts that wide column on the right. Beats 07 and
              09 are both `reverse` (visual-right) with non-reverse Beat 08
              between them.
              NOTE: this hand-written markup is text-then-visual, the opposite
              of the <Beat> component's visual-then-text order. `.beat__inner.reverse`
              uses absolute `order` values, so both source orders resolve to the
              same layout — but that means `reverse` here is NOT interchangeable
              with a component beat's `reverse`. If you remove it, also swap the
              two children below. */}
          <div className="beat__inner reverse">
            <div className="beat__text">
              {/* NOTE: beat-09 copy is intentionally duplicated verbatim in
                  components/Home3DPin.jsx (.stage3d-quiet). Beat 09 does not
                  live in BEAT_COPY (which covers beats 01-08 only); adding it
                  there would require restructuring the 2D story section. */}
              <div className="beat__num">Beat 09 · THE STAGE GOES QUIET</div>
              <h2 className="beat__title">The stage goes <span className="accent">quiet again.</span></h2>
              <p className="beat__copy">Cases packed by 04:00. Site is clean. The rig is back in the warehouse before the city wakes up. The next floor plan is already on the desk.</p>
            </div>
            <div className="beat__visual">
              <B.BeatQuiet progress={0.5} />
            </div>
          </div>
        </div>
      </section>
      )}

      {/* Beat 10 — ON THE RECORD. Combined showcase teasers + IG strip
          (formerly Beats 10 and 11). One headline, two stacked grids,
          two CTAs. See BeatShowcaseTeaser. */}
      <BeatShowcaseTeaser navigate={navigate} sectionRef={(el) => { beatRefs.current[9] = el; }} />

      {/* (Stats / metrics block removed: the work tells the story.) */}

      {/* Beat 11 — HAPPY PARTNERS. Continuous monochrome logo marquee.
          Previously this section was un-numbered; promoted to a story
          beat in the same commit that merged the former Beat 11 (IG)
          into Beat 10. PartnerMarquee renders all 12 brands as white
          silhouettes via a brightness(0)+invert(1) CSS filter,
          scrolling horizontally with a hover-pause. Same component
          used on the About page so the partner treatment reads
          consistently. Wrapper matches Beats 09/10: `.section` for the
          vertical rhythm, `.container-wide` nested inside (NOT co-classed
          — co-classing lets `.section`'s `padding: … 0` zero the gutters). */}
      <section className="section" ref={(el) => { beatRefs.current[10] = el; }}>
        <div className="container-wide">
          <div className="partners partners--marquee">
            <div className="partners__lead">
              <div className="beat__num">Beat 11 · HAPPY PARTNERS</div>
              <h2 className="beat__title">
                Trusted by <span className="accent">40+ brands</span> across the UAE.
              </h2>
            </div>
            {window.PartnerMarquee && <window.PartnerMarquee />}
          </div>
        </div>
      </section>

      {/* Beat 12 — Drenched final CTA. Single Committed-color moment. */}
      <section className="home-cta" ref={(el) => { beatRefs.current[11] = el; }}>
        <div className="home-cta__grid">
          <div>
            <div className="home-cta__divider">BEAT 12 · LIGHTS DIM</div>
            <h2 className="home-cta__title">Tell us about <em>your show.</em></h2>
          </div>
          <div className="home-cta__right">
            <p className="home-cta__sub">Brief, venue, dates, scope. We come back with a quote, usually inside a working day.</p>
            <button className="home-cta__btn" onClick={() => navigate('contact')}>Request a quote <ArrowRight /></button>
          </div>
        </div>
      </section>

      {/* Story rail (left side). 12 ticks now (SHOW LIVE beat inserted at 08).
          3D mode: visibility, the active tick and the number text are
          driven by the pin's railTick via classList/textContent (zero
          React state in the scroll path). ALL ticks jump INSTANTLY
          (behavior 'instant' — 'auto' would defer to site.css's global
          scroll-behavior:smooth): ticks 01-08 land mid-beat via seekBeat
          (the pin owns the BEATS geometry — no duplicated math here),
          tick 09 seeks the in-pin ending (§H.5 absorbed STAGE QUIET),
          and ticks 10-12 land on the tail sections instantly too,
          because a smooth scroll from above would scrub through the
          whole pin on the way down (M5). */}
      {use3d ? (
      <div className="story__rail">
        <div className="story__rail-num">01/12</div>
        {Array.from({ length: 12 }, (_, i) =>
        <div key={i}
        className="tick"
        onClick={() => {
          if (i < 8) {
            if (window.__tavPin) window.__tavPin.seekBeat(i);
          } else if (i === 8) {
            // §H.5: beat 09 lives INSIDE the pin's ending now (the
            // absorbed STAGE QUIET beside the framed proof photo).
            if (window.__tavPin) window.__tavPin.seek(0.985);
          } else {
            const el = beatRefs.current[i];
            if (el) window.scrollTo({ top: el.offsetTop - 64, behavior: 'instant' });
          }
        }} />
        )}
      </div>
      ) : (
      <div className={"story__rail" + (railVisible ? " is-visible" : "")}>
        <div className="story__rail-num">{String(activeBeat + 1).padStart(2, '0')}/12</div>
        {Array.from({ length: 12 }, (_, i) =>
        <div key={i}
        className={"tick" + (i === activeBeat ? " is-active" : "")}
        onClick={() => {
          const el = beatRefs.current[i];
          if (el) window.scrollTo({ top: el.offsetTop - 64, behavior: 'smooth' });
        }} />
        )}
      </div>
      )}
    </div>);

}

/* ===== Lightweight teaser SVGs (used in Beat 8 / 9 / showcase) ===== */
function TeaserConcert() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      <defs><linearGradient id="tc-g" x1="0.5" y1="0" x2="0.5" y2="1">
        <stop offset="0%" stopColor="#3E8C84" stopOpacity="0.4" />
        <stop offset="100%" stopColor="#3E8C84" stopOpacity="0" />
      </linearGradient></defs>
      <ellipse cx="100" cy="60" rx="80" ry="40" fill="url(#tc-g)" />
      {[40, 80, 120, 160].map((x, i) =>
      <path key={i} d={`M ${x} 30 L ${x - 16} 110 L ${x + 16} 110 Z`} fill="#3E8C84" opacity="0.22" />
      )}
      {Array.from({ length: 30 }, (_, i) =>
      <circle key={i} cx={10 + i % 10 * 20 + Math.floor(i / 10) % 2 * 10} cy={120 + Math.floor(i / 10) * 8} r="2.4"
      fill="none" stroke="#3E8C84" strokeOpacity="0.4" />
      )}
    </svg>);

}
function TeaserLaunch() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      <rect x="50" y="40" width="100" height="60" fill="none" stroke="#3E8C84" strokeWidth="1.5" />
      {Array.from({ length: 50 }, (_, i) =>
      <rect key={i} x={50 + i % 10 * 10 + 2} y={40 + Math.floor(i / 10) * 12 + 2} width="6" height="6"
      fill="#3E8C84" opacity={0.2 + Math.random() * 0.5} />
      )}
      <line x1="0" y1="120" x2="200" y2="120" stroke="#3d3a39" />
      <text x="100" y="140" textAnchor="middle" fontFamily="ui-monospace, monospace" fontSize="9" fill="#8b949e" letterSpacing="2">LAUNCH KEYNOTE</text>
    </svg>);

}
function TeaserGov() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      {Array.from({ length: 12 }, (_, i) =>
      <line key={i} x1={i * 18} y1="0" x2={i * 18} y2="150" stroke="#3d3a39" strokeOpacity="0.3" />
      )}
      <rect x="60" y="50" width="80" height="50" fill="none" stroke="#3E8C84" strokeWidth="1.4" />
      <line x1="60" y1="50" x2="140" y2="100" stroke="#3E8C84" strokeOpacity="0.4" />
      <line x1="140" y1="50" x2="60" y2="100" stroke="#3E8C84" strokeOpacity="0.4" />
      <text x="100" y="130" textAnchor="middle" fontFamily="ui-monospace, monospace" fontSize="9" fill="#6DACB5" letterSpacing="2">EXPO HALL · DXB</text>
    </svg>);

}
function TeaserFestival() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      {Array.from({ length: 6 }, (_, i) =>
      <circle key={i} cx={20 + i * 32} cy="40" r={4 + i % 2 * 2} fill="#3E8C84" opacity="0.4" />
      )}
      {Array.from({ length: 6 }, (_, i) =>
      <line key={i} x1={20 + i * 32} y1="44" x2={100 + Math.sin(i) * 30} y2="120" stroke="#3E8C84" strokeOpacity="0.3" strokeWidth="0.8" />
      )}
      <line x1="0" y1="120" x2="200" y2="120" stroke="#3d3a39" />
    </svg>);

}
function TeaserPrivate() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      <circle cx="100" cy="75" r="40" fill="none" stroke="#3E8C84" strokeWidth="1.4" />
      <circle cx="100" cy="75" r="20" fill="none" stroke="#3E8C84" strokeWidth="0.8" opacity="0.5" />
      <circle cx="100" cy="75" r="3" fill="#3E8C84" />
    </svg>);

}
function TeaserSports() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      <ellipse cx="100" cy="80" rx="80" ry="38" fill="none" stroke="#3E8C84" strokeWidth="1.4" />
      <line x1="100" y1="42" x2="100" y2="118" stroke="#3E8C84" strokeOpacity="0.6" />
      <circle cx="100" cy="80" r="14" fill="none" stroke="#3E8C84" strokeOpacity="0.6" />
    </svg>);

}

window.HomePage = HomePage;
window.TeaserConcert = TeaserConcert;
window.TeaserLaunch = TeaserLaunch;
window.TeaserGov = TeaserGov;
window.TeaserFestival = TeaserFestival;
window.TeaserPrivate = TeaserPrivate;
window.TeaserSports = TeaserSports;