// templates-ext.jsx — Extended templates specific to the deck content structure.
// TemplateNav (deck map / index), TemplateIdea (hypothesis cards), TemplateEvidence (CHL vs SUPPORT)

// Inject hover/connector stylesheet once. Two-layer card scheme: the OUTER
// div holds the entry animation + rotation + position (its transform is
// rewritten every RAF tick), while the INNER div carries the hover
// scale-up via plain CSS — so it doesn't fight the RAF-driven transform.
(function injectThumbStyles() {
  if (typeof document === 'undefined') return;
  if (document.getElementById('thumb-card-styles')) return;
  const s = document.createElement('style');
  s.id = 'thumb-card-styles';
  s.textContent = `
    .thumb-card-outer {
      /* When hovered, lift to top of stacking context so neighbouring
         cards (which come later in DOM order = naturally above) don't
         overlap the enlarged card. */
      transition: z-index 0s;
    }
    .thumb-card-outer:hover {
      z-index: 200;
    }
    .thumb-card-inner {
      width: 100%; height: 100%;
      transition: transform 240ms cubic-bezier(.22,1,.36,1),
                  filter 240ms cubic-bezier(.22,1,.36,1),
                  box-shadow 240ms ease-out;
      will-change: transform;
      transform-origin: center;
    }
    .thumb-card-outer:hover .thumb-card-inner {
      transform: scale(1.08);
      filter: brightness(1.08);
    }
    /* Section connector — vertical line + dot between buckets */
    .section-connector {
      display: flex; flex-direction: column; align-items: center;
      gap: 16px;
      margin: 70px 0 24px;
      opacity: 0.78;
    }
    .section-connector .conn-line {
      width: 1.5px; height: 140px;
      background: linear-gradient(to bottom,
        rgba(255,255,255,0) 0%,
        rgba(255,255,255,0.45) 50%,
        rgba(255,255,255,0) 100%);
    }
    .section-connector .conn-dot {
      width: 6px; height: 6px; border-radius: 50%;
      background: rgba(255,255,255,0.65);
    }
    .section-connector .conn-label {
      font-family: 'JetBrains Mono', monospace;
      font-size: 11px; letter-spacing: 0.32em;
      text-transform: uppercase;
      color: rgba(255,255,255,0.5);
      margin-top: 6px;
    }
  `;
  document.head.appendChild(s);
})();

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE NAV — DECK MAP / INDEX
//   • Use as the "Explore the deck" slide
//   • 4 BUCKETS in a 4-col strip (large cards, clickable to jump)
//   • Below: row of SHIFTS (S1..S5)
//   • Below: TAIL AREAS (A..J) as small tiles
//   • Below: IDEAS (IDEA-01..07) as horizontal rail
// ═══════════════════════════════════════════════════════════════════════════
function TemplateNav({ slide, scheme, onJump }) {
  const { title, subtitle, buckets = [], shifts = [], tail = [], ideas = [] } = slide;
  return (
    <SlideShell scheme={scheme}>
      {/* Heading */}
      <div style={{
        position: 'absolute', left: 80, right: 80, top: 130,
        textAlign: 'center',
      }}>
        <div style={{
          fontFamily: Tokens.fontMono, fontSize: 13,
          color: scheme.accent, letterSpacing: '0.32em',
          textTransform: 'uppercase',
          marginBottom: 16,
          fontWeight: Tokens.weight.semibold,
        }}>Deck Index · 4 Buckets + 5 Shifts + Tail + Ideas</div>
        <div style={{
          fontFamily: Tokens.fontDisplay,
          fontSize: 88, fontWeight: Tokens.weight.black,
          color: scheme.text,
          letterSpacing: '-0.025em',
          textTransform: 'uppercase',
          lineHeight: 0.95,
        }}>{title || 'Explore the Deck'}</div>
        {subtitle && (
          <div style={{
            marginTop: 14,
            fontFamily: Tokens.fontDisplay,
            fontSize: 17, color: scheme.textDim,
            maxWidth: 900, margin: '14px auto 0',
            lineHeight: 1.45,
          }}>{subtitle}</div>
        )}
      </div>

      {/* 4 Buckets — top row */}
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 360,
        display: 'grid',
        gridTemplateColumns: 'repeat(4, 1fr)',
        gap: 16,
        height: 240,
      }}>
        {buckets.map((b, i) => {
          const panel = CARD_PANELS[b.panel || 'purpleMid'];
          return (
            <button key={i}
              onClick={() => onJump && onJump(b.slideIdx)}
              style={{
                position: 'relative',
                background: panel.fill,
                color: panel.text,
                border: 'none',
                padding: '20px 22px',
                borderRadius: Tokens.radius.lg,
                textAlign: 'left',
                cursor: 'pointer',
                display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
                overflow: 'hidden',
              }}>
              {/* corner brackets */}
              <CornerBrackets color={panel.text} size={20} thickness={1.5} opacity={0.5}/>
              <div>
                <div style={{
                  fontFamily: Tokens.fontMono, fontSize: 11,
                  letterSpacing: '0.28em', opacity: 0.75,
                  fontWeight: Tokens.weight.semibold,
                }}>BUCKET {String(i + 1).padStart(2, '0')}</div>
                <div style={{
                  marginTop: 8,
                  fontFamily: Tokens.fontDisplay,
                  fontSize: 26, fontWeight: Tokens.weight.extrabold,
                  letterSpacing: '-0.02em',
                  lineHeight: 1.0,
                  textTransform: 'uppercase',
                }}>{b.title}</div>
              </div>
              <div style={{
                display: 'flex', alignItems: 'center',
                justifyContent: 'space-between',
              }}>
                <div style={{
                  fontFamily: Tokens.fontDisplay, fontSize: 12,
                  opacity: 0.85, lineHeight: 1.3,
                  fontWeight: Tokens.weight.medium,
                }}>{b.note}</div>
                <div style={{
                  fontFamily: Tokens.fontMono, fontSize: 11,
                  letterSpacing: '0.18em',
                  fontWeight: Tokens.weight.semibold,
                }}>→</div>
              </div>
            </button>
          );
        })}
      </div>

      {/* SHIFTS — middle row */}
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 630,
      }}>
        <div style={{
          fontFamily: Tokens.fontMono, fontSize: 11,
          color: scheme.textDim, letterSpacing: '0.28em',
          textTransform: 'uppercase', marginBottom: 10,
        }}>Strategic Shifts</div>
        <div style={{
          display: 'grid',
          gridTemplateColumns: `repeat(${shifts.length || 5}, 1fr)`,
          gap: 12,
        }}>
          {shifts.map((s, i) => (
            <button key={i}
              onClick={() => onJump && onJump(s.slideIdx)}
              style={{
                background: 'rgba(255,255,255,0.04)',
                border: `1px solid ${Tokens.inkLine}`,
                borderLeft: `3px solid ${scheme.accent}`,
                borderRadius: Tokens.radius.md,
                padding: '12px 16px',
                textAlign: 'left',
                cursor: 'pointer',
                color: scheme.text,
              }}>
              <div style={{
                fontFamily: Tokens.fontMono, fontSize: 10,
                color: scheme.accent, letterSpacing: '0.22em',
                fontWeight: Tokens.weight.semibold,
              }}>S{String(i + 1).padStart(2, '0')}</div>
              <div style={{
                marginTop: 4,
                fontFamily: Tokens.fontDisplay, fontSize: 14,
                fontWeight: Tokens.weight.bold,
                letterSpacing: '-0.01em',
                lineHeight: 1.15,
                textTransform: 'uppercase',
              }}>{s.title}</div>
            </button>
          ))}
        </div>
      </div>

      {/* TAIL + IDEAS — bottom row, 2 col */}
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 770, height: 100,
        display: 'grid',
        gridTemplateColumns: '1.4fr 1fr',
        gap: 24,
      }}>
        {/* Tail */}
        <div>
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: scheme.textDim, letterSpacing: '0.28em',
            textTransform: 'uppercase', marginBottom: 10,
          }}>Tail Areas · {tail.length}</div>
          <div style={{
            display: 'flex', flexWrap: 'wrap', gap: 6,
          }}>
            {tail.map((t, i) => (
              <button key={i}
                onClick={() => onJump && onJump(t.slideIdx)}
                style={{
                  background: 'transparent',
                  border: `1px solid ${Tokens.inkLine}`,
                  borderRadius: Tokens.radius.sm,
                  padding: '6px 12px',
                  fontFamily: Tokens.fontMono, fontSize: 11,
                  color: scheme.text, letterSpacing: '0.18em',
                  textTransform: 'uppercase',
                  cursor: 'pointer',
                  fontWeight: Tokens.weight.semibold,
                }}>
                <span style={{ color: scheme.accent, marginRight: 6 }}>{t.id}</span>
                {t.title}
              </button>
            ))}
          </div>
        </div>
        {/* Ideas */}
        <div>
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: scheme.textDim, letterSpacing: '0.28em',
            textTransform: 'uppercase', marginBottom: 10,
          }}>Own Ideas · {ideas.length}</div>
          <div style={{
            display: 'flex', flexWrap: 'wrap', gap: 6,
          }}>
            {ideas.map((id, i) => (
              <button key={i}
                onClick={() => onJump && onJump(id.slideIdx)}
                style={{
                  background: 'transparent',
                  border: `1px solid ${scheme.accentBold}`,
                  borderRadius: Tokens.radius.sm,
                  padding: '6px 12px',
                  fontFamily: Tokens.fontMono, fontSize: 11,
                  color: scheme.accent, letterSpacing: '0.18em',
                  textTransform: 'uppercase',
                  cursor: 'pointer',
                  fontWeight: Tokens.weight.semibold,
                }}>
                {id.id}
              </button>
            ))}
          </div>
        </div>
      </div>
    </SlideShell>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE IDEA — HYPOTHESIS / FUTURISTIC
//   • For IDEA-01..07 slides
//   • Big "IDEA · 0X" header
//   • Title + hypothesis statement
//   • Validation grid: SUPPORT × N | CHALLENGE × N
//   • Glow accent, holographic vibe
// ═══════════════════════════════════════════════════════════════════════════
function TemplateIdea({ slide, scheme }) {
  const {
    ideaNum, title, hypothesis, status = 'EARLY',
    upside, caveats = [], proof = [], chl = [],
    quantification, source,
  } = slide;
  return (
    <SlideShell scheme={scheme}>
      {/* Glow halo */}
      <div style={{
        position: 'absolute',
        left: '50%', top: '40%',
        width: 1600, height: 800,
        transform: 'translate(-50%, -50%)',
        background: `radial-gradient(ellipse, ${scheme.accent}25 0%, transparent 60%)`,
        mixBlendMode: 'screen',
        pointerEvents: 'none',
      }}/>

      {/* IDEA header */}
      <div style={{
        position: 'absolute', left: 80, top: 140,
        display: 'flex', alignItems: 'baseline', gap: 24,
      }}>
        <div style={{
          fontFamily: Tokens.fontMono, fontSize: 14,
          color: scheme.accent, letterSpacing: '0.32em',
          fontWeight: Tokens.weight.semibold,
          padding: '6px 14px',
          border: `1px solid ${scheme.accent}`,
          borderRadius: Tokens.radius.sm,
        }}>IDEA · {String(ideaNum || 1).padStart(2, '0')}</div>
        <div style={{
          fontFamily: Tokens.fontMono, fontSize: 11,
          color: scheme.textDim, letterSpacing: '0.28em',
          textTransform: 'uppercase',
        }}>Status · <span style={{ color: scheme.accent }}>{status}</span></div>
      </div>

      {/* Title */}
      <div style={{
        position: 'absolute', left: 80, right: 80, top: 200,
      }}>
        <div style={{
          fontFamily: Tokens.fontDisplay,
          fontSize: title.length > 30 ? 64 : 84,
          fontWeight: Tokens.weight.black,
          color: scheme.text,
          letterSpacing: '-0.025em',
          textTransform: 'uppercase',
          lineHeight: 0.94,
        }}>{renderTitle(title)}</div>
      </div>

      {/* Hypothesis */}
      {hypothesis && (
        <div style={{
          position: 'absolute', left: 80, right: 80, top: 380,
        }}>
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: scheme.accent, letterSpacing: '0.28em',
            marginBottom: 12,
            fontWeight: Tokens.weight.semibold,
          }}>HYPOTHESIS</div>
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: 24, fontWeight: Tokens.weight.medium,
            color: scheme.text,
            letterSpacing: '-0.01em', lineHeight: 1.4,
            maxWidth: 1200,
            paddingLeft: 22,
            borderLeft: `3px solid ${scheme.accent}`,
            fontStyle: 'italic',
          }}>"{hypothesis}"</div>
        </div>
      )}

      {/* Quantification card (centered between hypothesis and evidence) */}
      {quantification && (
        <div style={{
          position: 'absolute',
          right: 80, top: 230, width: 360,
          padding: 24,
          background: 'rgba(255,255,255,0.04)',
          border: `1px solid ${scheme.accent}`,
          borderRadius: Tokens.radius.lg,
        }}>
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: scheme.accent, letterSpacing: '0.24em',
            marginBottom: 8,
            fontWeight: Tokens.weight.semibold,
          }}>QUANTIFICATION</div>
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: 44, fontWeight: Tokens.weight.black,
            color: scheme.accent,
            letterSpacing: '-0.03em',
            lineHeight: 1,
          }}>{quantification.value}</div>
          <div style={{
            marginTop: 10,
            fontFamily: Tokens.fontDisplay, fontSize: 13,
            color: scheme.textDim, lineHeight: 1.4,
          }}>{quantification.label}</div>
        </div>
      )}

      {/* Evidence grid */}
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 580, height: 240,
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        gap: 20,
      }}>
        {/* PROOF */}
        <div style={{
          padding: 22,
          background: 'rgba(46,213,115,0.06)',
          border: `1px solid rgba(46,213,115,0.4)`,
          borderRadius: Tokens.radius.lg,
        }}>
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: '#3DFFA0', letterSpacing: '0.24em',
            marginBottom: 12,
            fontWeight: Tokens.weight.semibold,
          }}>✓ PROOF · {proof.length}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {proof.map((p, i) => (
              <div key={i} style={{
                fontFamily: Tokens.fontDisplay, fontSize: 14,
                color: scheme.text, lineHeight: 1.4,
              }}>
                <span style={{ color: '#3DFFA0', marginRight: 8 }}>•</span>
                {p}
              </div>
            ))}
          </div>
        </div>
        {/* CHALLENGE */}
        <div style={{
          padding: 22,
          background: 'rgba(255,77,46,0.06)',
          border: `1px solid rgba(255,77,46,0.4)`,
          borderRadius: Tokens.radius.lg,
        }}>
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: '#FF6B5A', letterSpacing: '0.24em',
            marginBottom: 12,
            fontWeight: Tokens.weight.semibold,
          }}>⚠ CHALLENGE · {chl.length}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {chl.map((p, i) => (
              <div key={i} style={{
                fontFamily: Tokens.fontDisplay, fontSize: 14,
                color: scheme.text, lineHeight: 1.4,
              }}>
                <span style={{ color: '#FF6B5A', marginRight: 8 }}>•</span>
                {p}
              </div>
            ))}
          </div>
        </div>
      </div>

      {source && (
        <div style={{ position: 'absolute', right: 80, bottom: 200, whiteSpace: 'nowrap' }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}
    </SlideShell>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE EVIDENCE — CHL vs SUPPORT stack
//   • For tail areas / shift slides with strong evidence base
//   • 2 columns: SUPPORT cards on left (green), CHALLENGE cards on right (red)
//   • Each card: tier badge (A/B/C), source, snippet
// ═══════════════════════════════════════════════════════════════════════════
function TemplateEvidence({ slide, scheme }) {
  const {
    kicker, title, subtitle,
    support = [], challenge = [],
    source,
  } = slide;
  return (
    <SlideShell scheme={scheme}>
      {/* Heading */}
      <div style={{
        position: 'absolute', left: 80, right: 80, top: 130,
      }}>
        {kicker && (
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 13,
            color: scheme.accent, letterSpacing: '0.28em',
            textTransform: 'uppercase',
            marginBottom: 14,
            fontWeight: Tokens.weight.semibold,
          }}>{kicker}</div>
        )}
        <div style={{
          fontFamily: Tokens.fontDisplay,
          fontSize: title.length > 28 ? 56 : 72,
          fontWeight: Tokens.weight.black,
          color: scheme.text,
          letterSpacing: '-0.025em',
          textTransform: 'uppercase',
          lineHeight: 0.95,
        }}>{renderTitle(title)}</div>
        {subtitle && (
          <div style={{
            marginTop: 14,
            fontFamily: Tokens.fontDisplay,
            fontSize: 17, color: scheme.textDim,
            maxWidth: 1100, lineHeight: 1.5,
          }}>{subtitle}</div>
        )}
      </div>

      {/* 2-col evidence */}
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 360, bottom: 200,
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        gap: 20,
      }}>
        <EvidenceColumn title="SUPPORT" color="#3DFFA0" items={support}/>
        <EvidenceColumn title="CHALLENGE" color="#FF6B5A" items={challenge}/>
      </div>

      {source && (
        <div style={{ position: 'absolute', right: 80, bottom: 200, whiteSpace: 'nowrap' }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}
    </SlideShell>
  );
}

function EvidenceColumn({ title, color, items }) {
  return (
    <div>
      <div style={{
        display: 'flex', alignItems: 'baseline', gap: 12,
        marginBottom: 16,
      }}>
        <div style={{
          fontFamily: Tokens.fontMono, fontSize: 12,
          color: color, letterSpacing: '0.28em',
          fontWeight: Tokens.weight.semibold,
        }}>{title === 'SUPPORT' ? '✓' : '⚠'} {title}</div>
        <div style={{
          fontFamily: Tokens.fontMono, fontSize: 11,
          color: Tokens.inkSoft, letterSpacing: '0.18em',
        }}>· {items.length} ITEMS</div>
      </div>
      <div style={{
        display: 'flex', flexDirection: 'column', gap: 12,
      }}>
        {items.map((it, i) => (
          <div key={i} style={{
            padding: '14px 16px',
            background: 'rgba(255,255,255,0.03)',
            border: `1px solid ${Tokens.inkLine}`,
            borderLeft: `3px solid ${color}`,
            borderRadius: Tokens.radius.md,
          }}>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              marginBottom: 8,
            }}>
              <span style={{
                fontFamily: Tokens.fontMono, fontSize: 10,
                color: color, letterSpacing: '0.2em',
                fontWeight: Tokens.weight.bold,
                padding: '3px 8px',
                border: `1px solid ${color}`,
                borderRadius: Tokens.radius.sm,
              }}>TIER {it.tier || 'B'}</span>
              <span style={{
                fontFamily: Tokens.fontMono, fontSize: 11,
                color: Tokens.inkSoft, letterSpacing: '0.16em',
                textTransform: 'uppercase',
              }}>{it.source}</span>
            </div>
            <div style={{
              fontFamily: Tokens.fontDisplay, fontSize: 15,
              color: Tokens.ink, lineHeight: 1.45,
              fontWeight: Tokens.weight.medium,
            }}>{it.text}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

Object.assign(window, { TemplateNav, TemplateIdea, TemplateEvidence, EvidenceColumn });

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE GLOBE — Earth + arcing cards (signature, à la Netflix Effect)
//   • Europe-focused earth-from-space at center bottom (CSS sphere)
//   • Fan of N thumbnails arcing across the top of the earth
//   • Each card is tangentially rotated to the arc
//   • Below: STORY 0X label + huge title + paragraph
//   • Corner brackets frame the image area
//   • Use for: bucket intros, where N thumbnails tell the bucket story
// ═══════════════════════════════════════════════════════════════════════════
function TemplateGlobe({ slide, scheme, onJump }) {
  const { kicker, storyNum, title, subtitle, thumbs = [], source } = slide;

  // ── Geometry ────────────────────────────────────────────────────────────
  const stageW = 1920;
  const boxLeft = 220, boxRight = 220, boxTop = 130, boxHeight = 460;
  const boxW = stageW - boxLeft - boxRight;

  // Earth center — below the image box so only the curved top is visible.
  const earthCx = stageW / 2;
  const earthCy = boxTop + boxHeight + 100;  // = 690
  const earthR  = 340;
  const arcR    = earthR + 40;               // = 380

  const N = thumbs.length;
  // Narrower 90° arc keeps every card inside the box bounds, even at the ends.
  const arcSpanDeg = 90;
  const arcStartDeg = -135;

  // ── Animation state machine ─────────────────────────────────────────────
  //   "entering" → cards spin into place (≈1.4s)
  //   "orbiting" → cards drift gently around the earth (forever)
  // (We skip the "playing" phase now that the video is removed.)
  const [phase, setPhase] = React.useState('entering');
  const phaseStartRef = React.useRef(performance.now());
  const [now, setNow] = React.useState(performance.now());

  // RAF clock
  React.useEffect(() => {
    let raf;
    const tick = () => {
      setNow(performance.now());
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  // Transition entering → orbiting
  const ENTRY_MS = 1400;
  React.useEffect(() => {
    if (phase !== 'entering') return;
    const id = setTimeout(() => {
      phaseStartRef.current = performance.now();
      setPhase('orbiting');
    }, ENTRY_MS);
    return () => clearTimeout(id);
  }, [phase]);

  const elapsedSec = (now - phaseStartRef.current) / 1000;

  let cardOpacity = 0;
  let cardScale   = 0.4;
  let arcDriftDeg = 0;
  let cardSpinDeg = 0;

  if (phase === 'entering') {
    const t = Math.min(1, elapsedSec / (ENTRY_MS / 1000));
    const eased = 1 - Math.pow(1 - t, 3);
    cardOpacity = eased;
    cardScale   = 0.4 + 0.6 * eased;
    arcDriftDeg = -45 * (1 - eased);
    cardSpinDeg = -90 * (1 - eased);
  } else if (phase === 'orbiting') {
    cardOpacity = 1;
    cardScale   = 1;
    // Static at base angles — the orbital wobble made the arc midpoint
    // drift ±20px off the earth's vertical axis, which read as "not
    // centered". Locked to 0 keeps the arc symmetric around the earth.
    arcDriftDeg = 0;
  }

  // ── Compute card positions ──────────────────────────────────────────────
  const cardData = thumbs.map((t, i) => {
    const u = N === 1 ? 0.5 : i / (N - 1);
    const baseAngDeg = arcStartDeg + u * arcSpanDeg;
    const angDeg = baseAngDeg + arcDriftDeg;
    const angRad = angDeg * Math.PI / 180;
    const cx = earthCx + Math.cos(angRad) * arcR;
    const cy = earthCy + Math.sin(angRad) * arcR;
    const rot = angDeg + 90 + cardSpinDeg;
    return { ...t, cx, cy, rot };
  });

  return (
    <SlideShell scheme={scheme}>
      {/* Image box ────────────────────────────────────────────────────── */}
      <div style={{
        position: 'absolute',
        left: boxLeft, right: boxRight, top: boxTop,
        height: boxHeight,
        background: '#020610',
        borderRadius: Tokens.radius.lg,
        overflow: 'hidden',
      }}>
        {/* Earth — CSS sphere (clean, no video for now) */}
        <Earth cx={earthCx - boxLeft} cy={earthCy - boxTop} r={earthR} scheme={scheme}/>

        {/* Arcing cards — two-layer outer/inner so hover scale-up doesn't
            collide with the RAF-driven rotation+scale on the outer ─────── */}
        {cardData.map((c, i) => {
          const w = c.w || 112;
          const h = c.h || 156;
          const clickable = !!c.slideIdx;
          return (
            <div key={i}
              className="thumb-card-outer"
              onClick={() => { if (clickable) onJump && onJump(c.slideIdx); }}
              style={{
                position: 'absolute',
                left: c.cx - boxLeft,
                top: c.cy - boxTop,
                width: w, height: h,
                transform: `translate(-50%, -50%) rotate(${c.rot}deg) scale(${cardScale})`,
                transformOrigin: 'center',
                opacity: cardOpacity,
                willChange: 'transform, opacity',
                cursor: clickable ? 'pointer' : 'default',
              }}>
              <div
                className="thumb-card-inner"
                style={{
                  borderRadius: Tokens.radius.lg,
                  overflow: 'hidden',
                  boxShadow: '0 20px 60px rgba(0,0,0,0.7)',
                  border: `1px solid rgba(255,255,255,0.12)`,
                  position: 'relative',
                }}
                onMouseEnter={(e) => {
                  if (!clickable) return;
                  e.currentTarget.style.boxShadow = `0 30px 80px rgba(0,0,0,0.8), 0 0 0 2px ${scheme.accent}`;
                }}
                onMouseLeave={(e) => {
                  if (!clickable) return;
                  e.currentTarget.style.boxShadow = '0 20px 60px rgba(0,0,0,0.7)';
                }}
              >
                <img src={c.src} alt={c.label || ''} style={{
                  width: '100%', height: '100%', objectFit: 'cover', display: 'block',
                }}/>
                <div style={{
                  position: 'absolute', inset: 0,
                  background: 'linear-gradient(0deg, rgba(0,0,0,0.55) 0%, transparent 45%)',
                  pointerEvents: 'none',
                }}/>
                <div style={{
                  position: 'absolute',
                  left: 6, top: 6,
                  padding: '3px 6px',
                  background: 'rgba(0,0,0,0.55)',
                  backdropFilter: 'blur(6px)',
                  border: `1px solid rgba(255,255,255,0.18)`,
                  borderRadius: 4,
                  fontFamily: Tokens.fontMono,
                  fontSize: 8, letterSpacing: '0.14em',
                  color: '#fff', fontWeight: 700,
                  textTransform: 'uppercase',
                }}>
                  {c.channelLogo
                    ? <img src={c.channelLogo} alt={c.channel || ''} style={{ height: 14, display: 'block' }}/>
                    : (c.channel || 'CHANNEL')}
                </div>
              </div>
            </div>
          );
        })}
      </div>

      {/* Corner brackets framing the image area */}
      <div style={{
        position: 'absolute',
        left: boxLeft - 30, top: boxTop - 30,
        right: boxRight - 30, height: boxHeight + 60,
        pointerEvents: 'none',
      }}>
        <CornerBrackets color={scheme.brackets} size={80} thickness={3} opacity={0.75}/>
      </div>

      {/* STORY 0X label */}
      {storyNum != null && (
        <div style={{
          position: 'absolute',
          left: 0, right: 0, top: boxTop + boxHeight + 30,
          display: 'flex', justifyContent: 'center',
        }}>
          <StoryLabel num={storyNum} color={scheme.storyLabel}/>
        </div>
      )}

      {kicker && storyNum == null && (
        <div style={{
          position: 'absolute',
          left: 0, right: 0, top: boxTop + boxHeight + 30,
          textAlign: 'center',
          fontFamily: Tokens.fontMono, fontSize: 13,
          color: scheme.accent, letterSpacing: '0.32em',
          textTransform: 'uppercase',
          fontWeight: Tokens.weight.semibold,
        }}>{kicker}</div>
      )}

      {/* Title */}
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: boxTop + boxHeight + 70,
        textAlign: 'center',
      }}>
        <div style={{
          fontFamily: Tokens.fontDisplay,
          fontSize: title.length > 28 ? 44 : title.length > 18 ? 56 : 68,
          fontWeight: Tokens.weight.black,
          color: scheme.text,
          letterSpacing: '-0.025em',
          textTransform: 'uppercase',
          lineHeight: 0.95,
        }}>{renderTitle(title)}</div>
        {subtitle && (
          <div style={{
            marginTop: 14,
            fontFamily: Tokens.fontDisplay,
            fontSize: 15, fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.45,
            maxWidth: 1000,
            margin: '14px auto 0',
          }}>{subtitle}</div>
        )}
      </div>

      {source && (
        <div style={{ position: 'absolute', right: 80, bottom: 200, whiteSpace: 'nowrap' }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}
    </SlideShell>
  );
}

// ── Earth (CSS sphere with Europe-focus styling) ─────────────────────────
function Earth({ cx, cy, r, scheme }) {
  return (
    <>
      {/* Outer atmospheric glow */}
      <div style={{
        position: 'absolute',
        left: cx - r - 60,
        top: cy - r - 60,
        width: (r + 60) * 2,
        height: (r + 60) * 2,
        borderRadius: '50%',
        background: `radial-gradient(circle, transparent 60%, ${scheme.accent}22 70%, transparent 82%)`,
        filter: 'blur(8px)',
        pointerEvents: 'none',
      }}/>

      {/* Earth sphere — brighter so it reads against the dark box */}
      <div style={{
        position: 'absolute',
        left: cx - r,
        top: cy - r,
        width: r * 2,
        height: r * 2,
        borderRadius: '50%',
        background: `
          radial-gradient(circle at 50% 35%,
            rgba(140,200,255,0.6) 0%,
            rgba(60,110,180,0.85) 25%,
            rgba(20,50,110,0.95) 55%,
            #0A1F4A 80%,
            #050E28 100%)
        `,
        boxShadow: `
          inset 0 0 60px rgba(0,0,0,0.4),
          inset 30px -30px 100px rgba(0,0,0,0.4),
          0 0 100px rgba(100,180,255,0.25)
        `,
      }}>
        {/* Subtle land mass hint via faint city-light pattern (SVG dots) */}
        <svg width="100%" height="100%" viewBox={`0 0 ${r * 2} ${r * 2}`}
          style={{ position: 'absolute', inset: 0 }}>
          {/* Europe nightside subtle glow dots — clustered around top center */}
          {Array.from({ length: 80 }).map((_, i) => {
            // Random scatter within a Europe-shaped patch (top center of sphere)
            const seed = i * 137.51;
            const angle = (seed % 360) * Math.PI / 180;
            const dist = ((seed * 7) % 100) / 100;
            // Bias toward top half + Europe area
            const u = (seed % 100) / 100;
            const v = ((seed * 3) % 100) / 100;
            const x = r + (u - 0.5) * r * 1.3;
            const y = r * 0.45 + (v - 0.5) * r * 0.5;
            // Distance from sphere center for clipping
            const dx = x - r, dy = y - r;
            const distFromCenter = Math.sqrt(dx * dx + dy * dy);
            if (distFromCenter > r * 0.9) return null;
            const op = 0.15 + 0.5 * ((seed * 11) % 100) / 100;
            return (
              <circle key={i} cx={x} cy={y} r={0.8 + ((seed * 5) % 3)}
                fill="rgba(255,220,140,1)" opacity={op}/>
            );
          })}
        </svg>

        {/* Top rim light — brighter atmospheric glow */}
        <div style={{
          position: 'absolute',
          left: 0, top: 0, right: 0, height: '22%',
          borderRadius: '50% 50% 0 0 / 100% 100% 0 0',
          background: `linear-gradient(180deg, ${scheme.accent}cc 0%, ${scheme.accent}33 60%, transparent 100%)`,
          filter: 'blur(14px)',
          opacity: 0.85,
          pointerEvents: 'none',
        }}/>
      </div>
    </>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// DONUT PAIR — animated "oggi vs 2030" pair of donut charts with shared legend
//   • Each segment animates its arc length on mount (easeOutCubic, 1200ms)
//   • Percentage values count up alongside the arcs
//   • Pass `segments: [{ label, color, today, target }]` summing to 100 each side
// ═══════════════════════════════════════════════════════════════════════════
function useAnimatedProgress(duration = 1200, delay = 250) {
  const [p, setP] = React.useState(0);
  React.useEffect(() => {
    let raf;
    const start = performance.now() + delay;
    const tick = (now) => {
      const elapsed = Math.max(0, now - start);
      const t = Math.min(1, elapsed / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      setP(eased);
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => raf && cancelAnimationFrame(raf);
  }, []);
  return p;
}

function Donut({ segments, valueKey, label, sublabel, totalLabel, size = 220, thickness = 26, progress = 1, scheme }) {
  const cx = size / 2, cy = size / 2;
  const r = (size - thickness) / 2;
  const C = 2 * Math.PI * r;

  let cumulative = 0;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
      <div style={{
        fontFamily: Tokens.fontMono, fontSize: 11,
        letterSpacing: '0.28em', textTransform: 'uppercase',
        color: scheme.accent, fontWeight: Tokens.weight.bold,
      }}>{label}</div>
      <div style={{ position: 'relative', width: size, height: size }}>
        <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
          <circle cx={cx} cy={cy} r={r} fill="none"
            stroke="rgba(255,255,255,0.07)" strokeWidth={thickness}/>
          {segments.map((s, i) => {
            const v = (s[valueKey] || 0) * progress;
            const dash = (v / 100) * C;
            const gap = C - dash;
            const rotate = -90 + (cumulative / 100) * 360;
            cumulative += s[valueKey] || 0;
            return (
              <circle key={i}
                cx={cx} cy={cy} r={r}
                fill="none"
                stroke={s.color}
                strokeWidth={thickness}
                strokeLinecap="butt"
                strokeDasharray={`${dash} ${gap}`}
                transform={`rotate(${rotate} ${cx} ${cy})`}/>
            );
          })}
        </svg>
        <div style={{
          position: 'absolute', inset: 0,
          display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center',
          pointerEvents: 'none',
        }}>
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontWeight: Tokens.weight.black,
            fontSize: Math.round(size * 0.16),
            color: scheme.text,
            letterSpacing: '-0.02em', lineHeight: 1,
          }}>{totalLabel}</div>
          {sublabel && <div style={{
            marginTop: 4,
            fontFamily: Tokens.fontMono, fontSize: 10,
            letterSpacing: '0.18em', textTransform: 'uppercase',
            color: Tokens.inkSoft,
          }}>{sublabel}</div>}
        </div>
      </div>
    </div>
  );
}

function DonutPair({ scheme, segments, todayLabel = 'OGGI · 2025', targetLabel = 'TARGET 2030', todayTotal, targetTotal, large = false }) {
  const progress = useAnimatedProgress(1400, 250);
  const donutSize = large ? 280 : 220;
  const donutThickness = large ? 34 : 26;
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: large ? 38 : 28,
      padding: large ? '28px 36px' : '20px 28px',
      border: `${large ? 2 : 1}px solid ${scheme.accent}`,
      borderLeft: `${large ? 6 : 4}px solid ${scheme.accent}`,
      background: large
        ? `linear-gradient(135deg, ${scheme.accent}1A 0%, rgba(255,255,255,0.02) 60%)`
        : 'rgba(255,255,255,0.02)',
      borderRadius: Tokens.radius.lg,
      alignSelf: 'flex-start',
      maxWidth: 1300,
      boxShadow: large ? `0 0 60px ${scheme.accent}22` : 'none',
    }}>
      <Donut scheme={scheme} segments={segments} valueKey="today"
        label={todayLabel} totalLabel={todayTotal} progress={progress}
        size={donutSize} thickness={donutThickness}/>

      {/* arrow */}
      <div style={{
        fontFamily: Tokens.fontDisplay, fontSize: large ? 44 : 32,
        color: scheme.accent, fontWeight: Tokens.weight.bold,
        opacity: 0.4 + progress * 0.6,
      }}>→</div>

      <Donut scheme={scheme} segments={segments} valueKey="target"
        label={targetLabel} totalLabel={targetTotal} progress={progress}
        size={donutSize} thickness={donutThickness}/>

      {/* shared legend */}
      <div style={{
        display: 'flex', flexDirection: 'column', gap: 10,
        paddingLeft: 12, minWidth: 240,
      }}>
        {segments.map((s, i) => {
          const todayVal = Math.round((s.today || 0) * progress);
          const targetVal = Math.round((s.target || 0) * progress);
          return (
            <div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
              <div style={{
                width: 10, height: 10, marginTop: 6,
                background: s.color, borderRadius: 2, flex: '0 0 auto',
              }}/>
              <div>
                <div style={{
                  fontFamily: Tokens.fontDisplay, fontSize: 13,
                  fontWeight: Tokens.weight.semibold, color: scheme.text,
                  letterSpacing: '-0.005em',
                }}>{s.label}</div>
                <div style={{
                  fontFamily: Tokens.fontMono, fontSize: 11,
                  color: Tokens.inkSoft, letterSpacing: '0.06em',
                  marginTop: 1,
                }}>
                  <span style={{ color: Tokens.inkDim }}>{todayVal}%</span>
                  <span style={{ margin: '0 6px', opacity: 0.5 }}>→</span>
                  <span style={{ color: scheme.accent, fontWeight: Tokens.weight.bold }}>{targetVal}%</span>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE CLAIM — Statement-led slide with optional killer number
//   • Big bold statement (centered or left-aligned)
//   • Optional killer number that pops below or to the side
//   • Optional donutPair (oggi vs 2030) replaces killerNumber when provided
//   • Source footer
//   • Use for: thesis/claim slides like "MFE non è più broadcaster italiano"
// ═══════════════════════════════════════════════════════════════════════════
function TemplateClaim({ slide, scheme }) {
  const {
    kicker, statement, subtitle, body,
    killerNumber, killerLabel, killerPrefix, killerSuffix,
    donutPair,
    quote, quoteAuthor,
    numbersStrip,
    bridgeLine,
    numberGrid,
    closingLine,
    cornerIframe,
    cornerMap,
    chartPanel,
    source, align = 'left',
    bridge = false,
  } = slide;
  // Reserve right space for the chart panel OR the EU map; left content
  // area shrinks. Map and chart are mutually exclusive in practice.
  const hasChart = !!chartPanel;
  const hasMap = !!cornerMap;
  const hasRightPanel = hasChart || hasMap;

  // EU network map controller — init after the <object> mounts. Same logic
  // as MorphScroll; the SVG ships with its own JS controller that paints
  // host/peer markets and animated signal arcs.
  const cornerMapRef = React.useRef(null);
  React.useEffect(() => {
    if (!cornerMap || !cornerMapRef.current) return;
    if (window.EUNetworkMap && window.EUNetworkMap.init) {
      window.EUNetworkMap.init(cornerMapRef.current);
    }
  }, [cornerMap]);

  // Bridge mode = minimal pre-reveal slide: huge centered statement, small
  // subtitle, optional thin numbers strip, no body/killer/quote/source.
  // Use larger type and vertical centering.
  const heavyContent = (!!killerNumber && !!quote) || !!donutPair;
  const statementSize = bridge
    ? 96
    : heavyContent ? 42 : statement.length > 60 ? 52 : 60;

  return (
    <SlideShell scheme={scheme}>
      {/* Corner iframe — small dot-cloud watermark (used by slide 40 closing
          to keep the IN morph visible while the page shows the 5 numbers) */}
      {cornerIframe && (
        <div style={{
          position: 'absolute',
          top: cornerIframe.top ?? 70,
          right: cornerIframe.right ?? 50,
          width: cornerIframe.width ?? 320,
          height: cornerIframe.height ?? 240,
          zIndex: 1,
          opacity: cornerIframe.opacity ?? 0.85,
          pointerEvents: 'none',
        }}>
          <iframe
            src={cornerIframe.src}
            title="corner morph"
            allowtransparency="true"
            loading="eager"
            style={{
              width: '100%', height: '100%',
              border: 0, background: 'transparent',
              filter: 'brightness(1.15) saturate(1.10)',
            }}
          />
        </div>
      )}

      {/* EU network map — large decorative map on the right side, used by
          slides like #02 to make the European footprint visible alongside
          the body claim. Same SVG + controller as MorphScroll's cornerMap. */}
      {hasMap && (
        <div
          ref={cornerMapRef}
          className="eu-network-map"
          data-markets={cornerMap.markets || 'IT,ES,DE,AT,CH,PT'}
          data-host={cornerMap.host || 'IT'}
          data-labels={cornerMap.labels || 'off'}
          data-motion={cornerMap.motion || 'auto'}
          style={{
            position: 'absolute',
            top: cornerMap.top ?? 110,
            right: cornerMap.right ?? 60,
            width: cornerMap.width ?? 760,
            height: cornerMap.height ?? 720,
            zIndex: 1,
            opacity: cornerMap.opacity ?? 0.95,
            pointerEvents: 'none',
          }}
        >
          <object
            type="image/svg+xml"
            data="eu-signal-map/eu-network-map.svg"
            style={{ width: '100%', height: '100%' }}
          />
        </div>
      )}

      {/* Chart panel — vertical right-side panel: title + bar rows + averages.
          Used by slides like #03 to show country-level AVOD % gap visually
          alongside the body claim. Renders only when slide.chartPanel set. */}
      {hasChart && (
        <div style={{
          position: 'absolute',
          right: 80, top: 150, bottom: 180,
          width: 660,
          display: 'flex', flexDirection: 'column', gap: 18,
          zIndex: 2,
        }}>
          {chartPanel.title && (
            <div style={{
              fontFamily: Tokens.fontMono, fontSize: 12,
              color: scheme.accent, letterSpacing: '0.28em',
              textTransform: 'uppercase',
              fontWeight: Tokens.weight.bold,
              paddingBottom: 12,
              borderBottom: `1px solid ${scheme.accent}40`,
            }}>{chartPanel.title}</div>
          )}

          {(chartPanel.bars || []).map((b, i) => {
            const max = chartPanel.max || Math.max(...chartPanel.bars.map(x => x.value), 30);
            const pct = Math.min(100, (b.value / max) * 100);
            const highlight = !!b.highlight;
            const barColor = b.color || (highlight ? scheme.accent : `${scheme.accent}b3`);
            return (
              <div key={i} style={{
                display: 'flex', alignItems: 'center', gap: 14,
                fontFamily: Tokens.fontDisplay,
              }}>
                {b.flag && (
                  <div style={{
                    fontSize: 22, lineHeight: 1, width: 32, textAlign: 'center',
                  }}>{b.flag}</div>
                )}
                <div style={{
                  width: 110, fontSize: 15, fontWeight: 600,
                  color: scheme.text, letterSpacing: '-0.005em',
                }}>{b.country}</div>
                <div style={{
                  flex: 1, position: 'relative', height: 26,
                  background: 'rgba(255,255,255,0.06)',
                  borderRadius: 4, overflow: 'hidden',
                }}>
                  <div style={{
                    position: 'absolute', left: 0, top: 0, bottom: 0,
                    width: `${pct}%`,
                    background: barColor,
                    transition: 'width 600ms ease-out',
                  }}/>
                </div>
                <div style={{
                  width: 60, textAlign: 'right',
                  fontSize: 18, fontWeight: 800,
                  color: highlight ? scheme.accent : scheme.text,
                  letterSpacing: '-0.01em',
                }}>{b.value}%</div>
              </div>
            );
          })}

          {/* Averages row(s) — separated visually, each on its own line */}
          {chartPanel.averages && chartPanel.averages.length > 0 && (
            <div style={{
              marginTop: 6, paddingTop: 16,
              borderTop: `1px solid ${scheme.accent}40`,
              display: 'flex', flexDirection: 'column', gap: 12,
            }}>
              {chartPanel.averages.map((a, i) => (
                <div key={i} style={{
                  display: 'flex', alignItems: 'baseline', gap: 14,
                }}>
                  <div style={{
                    flex: 1,
                    fontFamily: Tokens.fontMono, fontSize: 11,
                    color: a.highlight ? scheme.accent : scheme.textDim,
                    letterSpacing: '0.22em',
                    textTransform: 'uppercase',
                    fontWeight: a.highlight ? Tokens.weight.bold : Tokens.weight.semibold,
                  }}>
                    {a.label}
                    {a.sub && <span style={{
                      marginLeft: 8, opacity: 0.7, letterSpacing: '0.10em',
                    }}>{a.sub}</span>}
                  </div>
                  <div style={{
                    fontFamily: Tokens.fontDisplay,
                    fontSize: a.highlight ? 28 : 22,
                    fontWeight: Tokens.weight.black,
                    color: a.highlight ? scheme.accent : scheme.text,
                    letterSpacing: '-0.02em',
                  }}>{a.value}</div>
                </div>
              ))}
            </div>
          )}

          {/* Vertical bar chart — second metric below the horizontal share
              table. Used to show absolute market sizes (e.g. €/$ Bn) for the
              same countries, so the audience reads both share and absolute
              scale in one panel. */}
          {chartPanel.verticalBars && (
            <div style={{
              marginTop: 6, paddingTop: 16,
              borderTop: `1px solid ${scheme.accent}40`,
              display: 'flex', flexDirection: 'column', gap: 14,
            }}>
              {chartPanel.verticalBars.title && (
                <div style={{
                  fontFamily: Tokens.fontMono, fontSize: 11,
                  color: scheme.textDim, letterSpacing: '0.22em',
                  textTransform: 'uppercase',
                  fontWeight: Tokens.weight.semibold,
                }}>{chartPanel.verticalBars.title}</div>
              )}
              {(() => {
                const data = chartPanel.verticalBars.data || [];
                const max = chartPanel.verticalBars.max || Math.max(...data.map(d => d.value), 1);
                const BAR_AREA_H = 200;
                return (
                  <div style={{
                    display: 'grid',
                    gridTemplateColumns: `repeat(${data.length}, 1fr)`,
                    gap: 10, alignItems: 'end',
                    height: BAR_AREA_H + 64,
                  }}>
                    {data.map((d, i) => {
                      const h = Math.max(4, (d.value / max) * BAR_AREA_H);
                      const highlight = !!d.highlight;
                      const c = d.color || (highlight ? scheme.accent : `${scheme.accent}b3`);
                      return (
                        <div key={i} style={{
                          display: 'flex', flexDirection: 'column',
                          alignItems: 'center', gap: 6,
                          height: '100%', justifyContent: 'flex-end',
                        }}>
                          <div style={{
                            fontFamily: Tokens.fontDisplay,
                            fontSize: 14, fontWeight: Tokens.weight.bold,
                            color: highlight ? scheme.accent : scheme.text,
                            letterSpacing: '-0.01em',
                            whiteSpace: 'nowrap',
                          }}>{d.display || d.value}</div>
                          <div style={{
                            width: '70%', height: h,
                            background: c,
                            borderRadius: '2px 2px 0 0',
                            transition: 'height 600ms ease-out',
                          }}/>
                          <div style={{
                            fontSize: 18, lineHeight: 1, marginTop: 4,
                          }}>{d.flag}</div>
                          <div style={{
                            fontFamily: Tokens.fontMono, fontSize: 10,
                            color: scheme.textDim, letterSpacing: '0.10em',
                            textTransform: 'uppercase',
                          }}>{d.country}</div>
                        </div>
                      );
                    })}
                  </div>
                );
              })()}
            </div>
          )}

          {chartPanel.note && (
            <div style={{
              marginTop: 'auto',
              fontFamily: Tokens.fontDisplay, fontSize: 11,
              color: scheme.textDim, opacity: 0.7,
              lineHeight: 1.4,
            }}>{chartPanel.note}</div>
          )}
        </div>
      )}

      <div style={{
        position: 'absolute',
        left: 80,
        right: hasRightPanel ? 820 : 80,
        top: bridge ? 80 : 150, bottom: 180,
        display: 'flex', flexDirection: 'column',
        justifyContent: bridge ? 'center' : 'flex-start',
        alignItems: bridge ? 'center' : 'stretch',
        gap: bridge ? 32 : 22,
      }}>
        {kicker && (
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 13,
            color: scheme.accent, letterSpacing: '0.32em',
            textTransform: 'uppercase',
            fontWeight: Tokens.weight.semibold,
            textAlign: bridge ? 'center' : align,
          }}>{kicker}</div>
        )}

        {/* Statement */}
        <div style={{
          fontFamily: Tokens.fontDisplay,
          fontSize: statementSize,
          fontWeight: Tokens.weight.black,
          color: scheme.text,
          letterSpacing: '-0.035em',
          lineHeight: bridge ? 0.98 : 1.05,
          textAlign: bridge ? 'center' : align,
          maxWidth: 1400,
        }}>{renderTitle(statement)}</div>

        {/* Subtitle — small secondary text under the statement (Bridge mode) */}
        {subtitle && (
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: bridge ? 20 : 17,
            fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.45,
            letterSpacing: '-0.005em',
            textAlign: bridge ? 'center' : align,
            maxWidth: 880,
            opacity: 0.9,
          }}>{subtitle}</div>
        )}

        {/* Body paragraph */}
        {body && (
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: heavyContent ? 17 : 19,
            fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.5,
            maxWidth: 1100,
            textAlign: align,
          }}>{body}</div>
        )}

        {/* Bridge line — "→ vive in IN" type micro-row that ties the bucket
            take-aways back to the IN reveal. Renders just above the donut /
            killer number, in monospace accent color. */}
        {bridgeLine && (
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 13,
            color: scheme.accent, letterSpacing: '0.18em',
            textTransform: 'uppercase',
            fontWeight: Tokens.weight.bold,
            paddingLeft: 14,
            borderLeft: `3px solid ${scheme.accent}`,
            alignSelf: 'flex-start',
          }}>{bridgeLine}</div>
        )}

        {/* DonutPair — oggi vs 2030 with animated arcs + count-up legend.
            Replaces the killer number card when present. */}
        {donutPair && <DonutPair scheme={scheme} {...donutPair}/>}

        {/* Number grid — 5 big "killer" numbers in a horizontal grid.
            Used for closing slides like "IN. IN 5 NUMERI." */}
        {numberGrid && numberGrid.length > 0 && (
          <div style={{
            display: 'grid',
            gridTemplateColumns: `repeat(${numberGrid.length}, 1fr)`,
            gap: 18,
            marginTop: 18,
            maxWidth: 1480,
          }}>
            {numberGrid.map((n, i) => (
              <div key={i} style={{
                padding: '22px 22px',
                border: `1px solid ${scheme.accent}55`,
                borderTop: `4px solid ${scheme.accent}`,
                borderRadius: Tokens.radius.lg,
                background: 'rgba(255,255,255,0.02)',
                display: 'flex', flexDirection: 'column', gap: 10,
                minHeight: 200,
              }}>
                <div style={{
                  fontFamily: Tokens.fontDisplay,
                  fontWeight: Tokens.weight.black,
                  fontSize: n.value.length > 6 ? 44 : 60,
                  color: scheme.accent,
                  letterSpacing: '-0.04em',
                  lineHeight: 0.9,
                }}>{n.value}</div>
                <div style={{
                  fontFamily: Tokens.fontMono, fontSize: 10.5,
                  color: scheme.accent, letterSpacing: '0.22em',
                  textTransform: 'uppercase',
                  fontWeight: Tokens.weight.bold,
                  marginTop: 'auto',
                  paddingTop: 8,
                  borderTop: '1px solid rgba(255,255,255,0.08)',
                }}>{n.tag || `N° ${i + 1}`}</div>
                <div style={{
                  fontFamily: Tokens.fontDisplay, fontSize: 13,
                  color: scheme.textDim,
                  lineHeight: 1.4,
                }}>{n.label}</div>
              </div>
            ))}
          </div>
        )}

        {/* Closing line — single bold one-liner under the number grid */}
        {closingLine && (
          <div style={{
            marginTop: 18,
            fontFamily: Tokens.fontDisplay,
            fontSize: 32,
            fontWeight: Tokens.weight.black,
            color: scheme.text,
            letterSpacing: '-0.02em',
            lineHeight: 1.15,
            textAlign: align,
          }}>{closingLine}</div>
        )}

        {/* Killer number card — number + label vertically centered, card
            left-aligned with the body/statement above. alignItems:center
            instead of baseline so the small label doesn't sink to the bottom
            of the big number. */}
        {killerNumber && !donutPair && (
          <div style={{
            padding: '28px 40px',
            border: `1px solid ${scheme.accent}`,
            borderLeft: `4px solid ${scheme.accent}`,
            background: 'rgba(255,255,255,0.02)',
            borderRadius: Tokens.radius.lg,
            alignSelf: 'flex-start',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'flex-start',
            gap: 32,
            maxWidth: 1100,
          }}>
            <div style={{
              fontFamily: Tokens.fontDisplay,
              fontWeight: Tokens.weight.black,
              fontSize: 92,
              color: scheme.accent,
              letterSpacing: '-0.04em',
              lineHeight: 0.9,
              display: 'flex', alignItems: 'baseline',
              whiteSpace: 'nowrap',
              flex: '0 0 auto',
            }}>
              {killerPrefix && <span style={{ fontSize: 44, marginRight: 4 }}>{killerPrefix}</span>}
              <span>{killerNumber}</span>
              {killerSuffix && <span style={{ fontSize: 44, marginLeft: 4 }}>{killerSuffix}</span>}
            </div>
            {killerLabel && (
              <div style={{
                fontFamily: Tokens.fontDisplay,
                fontSize: 17,
                fontWeight: Tokens.weight.medium,
                color: scheme.textDim,
                lineHeight: 1.45,
                maxWidth: 560,
                textAlign: 'left',
              }}>{killerLabel}</div>
            )}
          </div>
        )}

        {/* Quote */}
        {quote && (
          <div style={{
            paddingLeft: 20,
            borderLeft: `3px solid ${scheme.accent}`,
            fontFamily: Tokens.fontDisplay,
            fontSize: 16,
            fontStyle: 'italic',
            fontWeight: Tokens.weight.medium,
            color: scheme.textDim,
            lineHeight: 1.45,
            maxWidth: 1100,
          }}>
            "{quote}"
            {quoteAuthor && (
              <div style={{
                marginTop: 6,
                fontStyle: 'normal',
                fontFamily: Tokens.fontMono, fontSize: 11,
                color: Tokens.inkSoft, letterSpacing: '0.18em',
                textTransform: 'uppercase',
              }}>— {quoteAuthor}</div>
            )}
          </div>
        )}
      </div>

      {/* Canonical numbers strip — thin tatuaggio row of the 5 numbers we
          want the audience to remember. Renders just above the source line. */}
      {numbersStrip && numbersStrip.length > 0 && (
        <div style={{
          position: 'absolute',
          left: 80, right: 80, bottom: 165,
          display: 'flex',
          flexDirection: 'row',
          justifyContent: bridge ? 'center' : 'flex-start',
          alignItems: 'stretch',
          gap: 0,
          paddingTop: 14,
          borderTop: `1px solid ${scheme.accent}33`,
        }}>
          {numbersStrip.map((item, i) => (
            <React.Fragment key={i}>
              {i > 0 && <div style={{
                width: 1, alignSelf: 'stretch',
                background: 'rgba(255,255,255,0.08)',
                margin: '0 18px',
              }}/>}
              <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                <div style={{
                  fontFamily: Tokens.fontDisplay,
                  fontSize: 18,
                  fontWeight: Tokens.weight.black,
                  color: scheme.accent,
                  letterSpacing: '-0.02em',
                  lineHeight: 1,
                }}>{item.value}</div>
                <div style={{
                  fontFamily: Tokens.fontMono,
                  fontSize: 9.5,
                  color: Tokens.inkSoft,
                  letterSpacing: '0.16em',
                  textTransform: 'uppercase',
                }}>{item.label}</div>
              </div>
            </React.Fragment>
          ))}
        </div>
      )}

      {source && (
        <div style={{ position: 'absolute', left: 80, bottom: 200, whiteSpace: 'nowrap' }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}
    </SlideShell>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE STAT-EXPLAINER — Numeric synthesis to the RIGHT of a deep-dive
//   Pattern: deep-dive title (MorphScroll/LongScroll page 0) → press → →
//   land here: 3-5 numeric cards + 1 italic sense-line. Press → again to
//   advance to the next deck slide. Press ← to return to the parent title.
//
//   Not a SLIDES[] entry: rendered as the sub-step of its parent via
//   `slide.statSlide = { numbers, senseLine, kickerOverride? }`. The parent's
//   kicker/title are inherited so the explainer feels like a continuation.
//
//   Visual signature: kicker prefixed "I NUMERI · …" so the user knows this
//   is the synthesis screen, not the title.
// ═══════════════════════════════════════════════════════════════════════════
function TemplateStatExplainer({ parentSlide, scheme }) {
  const stat = parentSlide.statSlide || {};
  const numbers = stat.numbers || [];
  const table = stat.table;
  const senseLine = stat.senseLine;
  const kicker = stat.kickerOverride
    || (parentSlide.kicker ? `I NUMERI · ${parentSlide.kicker}` : 'I NUMERI');
  // Optional: reframe the title on the stat slide so it doesn't echo the
  // parent verbatim (which feels redundant in a → deck flow). Fallback to
  // parent.title preserves behaviour for slides without an override.
  const title = stat.titleOverride || parentSlide.title;
  const source = parentSlide.source;
  // Tighten vertical density when a table is present, since the explainer
  // needs to fit numbers + table + sense line + source between top 110 and
  // bottom 180 of the slide canvas.
  const hasTable = !!table;

  return (
    <SlideShell scheme={scheme}>
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 110, bottom: 180,
        display: 'flex', flexDirection: 'column',
        gap: hasTable ? 18 : 28,
      }}>
        {kicker && (
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 13,
            color: scheme.accent, letterSpacing: '0.32em',
            textTransform: 'uppercase',
            fontWeight: Tokens.weight.semibold,
            textAlign: 'center',
          }}>{kicker}</div>
        )}

        {title && (
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: hasTable ? 56 : (title.length > 30 ? 72 : 84),
            fontWeight: Tokens.weight.black,
            color: scheme.text,
            letterSpacing: '-0.025em',
            textTransform: 'uppercase',
            lineHeight: 0.95,
            textAlign: 'center',
            marginTop: 4,
          }}>{renderTitle(title)}</div>
        )}

        {numbers.length > 0 && (
          <div style={{
            display: 'grid',
            gridTemplateColumns: `repeat(${numbers.length}, 1fr)`,
            gap: 20,
            marginTop: hasTable ? 12 : 36,
            maxWidth: hasTable ? 1100 : 1600,
            width: '100%',
            alignSelf: 'center',
          }}>
            {numbers.map((n, i) => (
              <div key={i} style={{
                padding: hasTable ? '20px 22px' : '28px 26px',
                border: `1px solid ${scheme.accent}55`,
                borderTop: `4px solid ${scheme.accent}`,
                borderRadius: Tokens.radius.lg,
                background: 'rgba(255,255,255,0.02)',
                display: 'flex', flexDirection: 'column', gap: 10,
                minHeight: hasTable ? 130 : 220,
              }}>
                <div style={{
                  fontFamily: Tokens.fontDisplay,
                  fontWeight: Tokens.weight.black,
                  fontSize: (n.value || '').length > 7 ? 40
                          : (n.value || '').length > 5 ? 50
                          : (hasTable ? 56 : 68),
                  color: scheme.accent,
                  letterSpacing: '-0.04em',
                  lineHeight: 0.9,
                }}>{n.value}</div>
                {n.tag && (
                  <div style={{
                    fontFamily: Tokens.fontMono, fontSize: 10.5,
                    color: scheme.accent, letterSpacing: '0.22em',
                    textTransform: 'uppercase',
                    fontWeight: Tokens.weight.bold,
                    marginTop: 'auto',
                    paddingTop: 8,
                    borderTop: '1px solid rgba(255,255,255,0.08)',
                  }}>{n.tag}</div>
                )}
                {n.label && (
                  <div style={{
                    fontFamily: Tokens.fontDisplay, fontSize: 13,
                    color: scheme.textDim,
                    lineHeight: 1.4,
                  }}>{n.label}</div>
                )}
              </div>
            ))}
          </div>
        )}

        {/* Optional comparison table — used when the stat-explainer carries
            its own structured dataset (e.g. CTV Home Screen Strategy). Styled
            like TemplateComparison for visual consistency. */}
        {hasTable && (
          <div style={{
            marginTop: 14,
            alignSelf: 'center',
            width: '100%',
            maxWidth: 1500,
          }}>
            {table.title && (
              <div style={{
                fontFamily: Tokens.fontMono, fontSize: 11,
                color: scheme.accent, letterSpacing: '0.28em',
                textTransform: 'uppercase',
                fontWeight: Tokens.weight.bold,
                marginBottom: 8,
                textAlign: 'left',
                paddingLeft: 4,
              }}>{table.title}</div>
            )}
            <div style={{
              border: `1px solid rgba(255,255,255,0.10)`,
              borderRadius: Tokens.radius.lg,
              background: 'rgba(255,255,255,0.02)',
              overflow: 'hidden',
            }}>
              <table style={{
                width: '100%',
                borderCollapse: 'collapse',
                tableLayout: 'fixed',
              }}>
                <thead>
                  <tr>
                    {table.columns.map((col, i) => (
                      <th key={i} style={{
                        padding: '10px 16px',
                        textAlign: i === 0 ? 'left' : 'center',
                        borderBottom: `2px solid ${scheme.accent}55`,
                        background: i === table.highlightCol ? `${scheme.accent}18` : 'transparent',
                        fontFamily: Tokens.fontMono, fontSize: 10,
                        letterSpacing: '0.22em',
                        textTransform: 'uppercase',
                        fontWeight: Tokens.weight.bold,
                        color: i === table.highlightCol ? scheme.accent : scheme.textDim,
                      }}>{col}</th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {table.rows.map((row, ri) => (
                    <tr key={ri}>
                      {row.map((cell, ci) => (
                        <td key={ci} style={{
                          padding: '9px 16px',
                          textAlign: ci === 0 ? 'left' : 'center',
                          borderBottom: ri < table.rows.length - 1
                            ? `1px solid rgba(255,255,255,0.06)` : 'none',
                          background: ci === table.highlightCol ? `${scheme.accent}10` : 'transparent',
                          fontFamily: ci === 0 ? Tokens.fontMono : Tokens.fontDisplay,
                          fontSize: ci === 0 ? 11 : 14,
                          color: ci === table.highlightCol
                            ? scheme.accent
                            : (ci === 0 ? scheme.textDim : scheme.text),
                          fontWeight: ci === table.highlightCol ? Tokens.weight.black : Tokens.weight.semibold,
                          letterSpacing: ci === 0 ? '0.18em' : '-0.005em',
                          textTransform: ci === 0 ? 'uppercase' : 'none',
                        }}>{cell}</td>
                      ))}
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            {table.note && (
              <div style={{
                marginTop: 10,
                fontFamily: Tokens.fontDisplay, fontSize: 12,
                fontStyle: 'italic',
                color: scheme.textDim, opacity: 0.7,
                lineHeight: 1.4,
                textAlign: 'left',
              }}>{table.note}</div>
            )}
          </div>
        )}

        {senseLine && (
          <div style={{
            marginTop: hasTable ? 8 : 24,
            fontFamily: Tokens.fontDisplay,
            fontSize: hasTable ? 18 : 22,
            fontStyle: 'italic',
            fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.4,
            textAlign: 'center',
            maxWidth: 1200,
            alignSelf: 'center',
            opacity: 0.85,
          }}>{senseLine}</div>
        )}
      </div>

      {source && (
        <div style={{ position: 'absolute', left: 80, bottom: 200 }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}

      {/* Small nav hint bottom-center — above the StoryCarousel */}
      <div style={{
        position: 'absolute',
        left: 0, right: 0, bottom: 220,
        textAlign: 'center',
        fontFamily: Tokens.fontMono, fontSize: 11,
        color: scheme.textDim, opacity: 0.55,
        letterSpacing: '0.22em',
        textTransform: 'uppercase',
      }}>← titolo · → prossima slide</div>
    </SlideShell>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE PARTNERSHIPS — Grid of "brand × brand" deal cards
//   Used to argue a market consolidation thesis with concrete deals.
//   Each card has: date, two brand badges (colored chips with brand text),
//   short description. Brand chips use signature colors so they read as
//   logo-tier marks without needing external image assets.
//
//   Slide data shape:
//     {
//       template: 'Partnerships', kicker, title, subtitle,
//       deals: [
//         { date: 'GIU 2025', a: { name: 'Netflix', color: '#E50914' },
//           b: { name: 'TF1', color: '#E2231A' },
//           desc: 'First-window deal · MFE keeps the relationship.' },
//         ...
//       ],
//       closingLine: 'The market is consolidating right now.',
//       source: '...',
//     }
// ═══════════════════════════════════════════════════════════════════════════
function TemplatePartnerships({ slide, scheme }) {
  const { kicker, title, subtitle, deals = [], closingLine, source } = slide;
  return (
    <SlideShell scheme={scheme}>
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 130, bottom: 200,
        display: 'flex', flexDirection: 'column',
      }}>
        {kicker && (
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 13,
            color: scheme.accent, letterSpacing: '0.32em',
            textTransform: 'uppercase',
            fontWeight: Tokens.weight.semibold,
            marginBottom: 16,
          }}>{kicker}</div>
        )}

        {title && (
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: 52, fontWeight: Tokens.weight.black,
            color: scheme.text,
            letterSpacing: '-0.025em',
            textTransform: 'uppercase',
            lineHeight: 0.98,
            maxWidth: 1500,
          }}>{renderTitle(title)}</div>
        )}

        {subtitle && (
          <div style={{
            marginTop: 18,
            fontFamily: Tokens.fontDisplay,
            fontSize: 19, fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.5,
            maxWidth: 1400,
          }}>{subtitle}</div>
        )}

        {/* Deal cards — 4 in a row */}
        <div style={{
          marginTop: 48,
          display: 'grid',
          gridTemplateColumns: `repeat(${deals.length}, 1fr)`,
          gap: 18,
          flex: 1,
        }}>
          {deals.map((d, i) => (
            <div key={i} style={{
              padding: '28px 24px',
              borderRadius: Tokens.radius.lg,
              background: 'rgba(255,255,255,0.04)',
              border: `1px solid rgba(255,255,255,0.10)`,
              borderTop: `3px solid ${scheme.accent}`,
              display: 'flex', flexDirection: 'column',
              justifyContent: 'space-between',
              minHeight: 280,
            }}>
              {/* Date stamp */}
              <div style={{
                fontFamily: Tokens.fontMono, fontSize: 11,
                color: scheme.accent, letterSpacing: '0.28em',
                textTransform: 'uppercase',
                fontWeight: Tokens.weight.bold,
              }}>{d.date}</div>

              {/* Brand badge pair */}
              <div style={{
                display: 'flex', flexDirection: 'column', gap: 10,
                alignItems: 'flex-start',
                marginTop: 14, marginBottom: 14,
              }}>
                <BrandBadge {...d.a}/>
                <div style={{
                  fontFamily: Tokens.fontMono, fontSize: 22,
                  color: scheme.textDim, opacity: 0.7,
                  fontWeight: Tokens.weight.bold,
                  letterSpacing: '-0.02em',
                  marginLeft: 8,
                }}>×</div>
                <BrandBadge {...d.b}/>
              </div>

              {/* Description */}
              {d.desc && (
                <div style={{
                  fontFamily: Tokens.fontDisplay, fontSize: 13,
                  color: scheme.textDim,
                  lineHeight: 1.45,
                }}>{d.desc}</div>
              )}
            </div>
          ))}
        </div>

        {/* Closing one-liner */}
        {closingLine && (
          <div style={{
            marginTop: 32,
            fontFamily: Tokens.fontDisplay,
            fontSize: 26, fontWeight: Tokens.weight.black,
            color: scheme.text,
            letterSpacing: '-0.02em',
            lineHeight: 1.2,
            textAlign: 'center',
          }}>{closingLine}</div>
        )}
      </div>

      {source && (
        <div style={{ position: 'absolute', left: 80, bottom: 200 }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}
    </SlideShell>
  );
}

// Brand chip — colored pill with brand name, sits in for a logo when we
// don't bundle the actual mark. Optional `textColor` overrides default white.
function BrandBadge({ name, color, textColor = '#fff' }) {
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center',
      padding: '8px 18px',
      borderRadius: 6,
      background: color || '#444',
      color: textColor,
      fontFamily: Tokens.fontDisplay,
      fontSize: 22, fontWeight: Tokens.weight.black,
      letterSpacing: '-0.01em',
      textTransform: 'none',
      whiteSpace: 'nowrap',
      boxShadow: '0 4px 12px rgba(0,0,0,0.35)',
    }}>{name}</div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE COMPARISON — Title + body + comparison table
//   Used for "asset base vs benchmarks" type slides. The table renders below
//   the body and spans full slide width. One column (highlightCol) is the
//   MFE perspective and gets accent treatment.
//
//   Slide data shape:
//     {
//       template: 'Comparison', kicker, title, body, source,
//       table: {
//         columns: ['Dimension', 'MFE Today', 'RTL Bedrock', ...],
//         rows: [ ['Aggregated MU', '22–25M', '45M', ...], ... ],
//         highlightCol: 1,
//       }
//     }
// ═══════════════════════════════════════════════════════════════════════════
function TemplateComparison({ slide, scheme }) {
  const { kicker, title, body, table, source } = slide;
  return (
    <SlideShell scheme={scheme}>
      <div style={{
        position: 'absolute',
        left: 80, right: 80, top: 130, bottom: 200,
        display: 'flex', flexDirection: 'column', gap: 18,
      }}>
        {kicker && (
          <div style={{
            fontFamily: Tokens.fontMono, fontSize: 13,
            color: scheme.accent, letterSpacing: '0.32em',
            textTransform: 'uppercase',
            fontWeight: Tokens.weight.semibold,
          }}>{kicker}</div>
        )}

        {title && (
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: 44, fontWeight: Tokens.weight.black,
            color: scheme.text,
            letterSpacing: '-0.025em',
            lineHeight: 1.05,
            maxWidth: 1500,
          }}>{renderTitle(title)}</div>
        )}

        {body && (
          <div style={{
            marginTop: 4,
            fontFamily: Tokens.fontDisplay,
            fontSize: 17, fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.5,
            maxWidth: 1500,
            whiteSpace: 'pre-line',
          }}>{body}</div>
        )}

        {table && (
          <div style={{
            marginTop: 22,
            border: `1px solid rgba(255,255,255,0.10)`,
            borderRadius: Tokens.radius.lg,
            background: 'rgba(255,255,255,0.02)',
            overflow: 'hidden',
          }}>
            <table style={{
              width: '100%',
              borderCollapse: 'collapse',
              tableLayout: 'fixed',
            }}>
              <thead>
                <tr>
                  {table.columns.map((col, i) => (
                    <th key={i} style={{
                      padding: '16px 20px',
                      textAlign: i === 0 ? 'left' : 'center',
                      borderBottom: `2px solid ${scheme.accent}55`,
                      background: i === table.highlightCol ? `${scheme.accent}18` : 'transparent',
                      fontFamily: Tokens.fontMono, fontSize: 11,
                      letterSpacing: '0.22em',
                      textTransform: 'uppercase',
                      fontWeight: Tokens.weight.bold,
                      color: i === table.highlightCol ? scheme.accent : scheme.textDim,
                    }}>{col}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {table.rows.map((row, ri) => (
                  <tr key={ri}>
                    {row.map((cell, ci) => (
                      <td key={ci} style={{
                        padding: '14px 20px',
                        textAlign: ci === 0 ? 'left' : 'center',
                        borderBottom: ri < table.rows.length - 1
                          ? `1px solid rgba(255,255,255,0.06)` : 'none',
                        background: ci === table.highlightCol ? `${scheme.accent}10` : 'transparent',
                        fontFamily: ci === 0 ? Tokens.fontMono : Tokens.fontDisplay,
                        fontSize: ci === 0 ? 12 : 17,
                        color: ci === table.highlightCol
                          ? scheme.accent
                          : (ci === 0 ? scheme.textDim : (cell === '-' || cell === '–' ? scheme.textDim : scheme.text)),
                        fontWeight: ci === table.highlightCol
                          ? Tokens.weight.black
                          : (ci === 0 ? Tokens.weight.semibold : Tokens.weight.semibold),
                        letterSpacing: ci === 0 ? '0.18em' : '-0.005em',
                        textTransform: ci === 0 ? 'uppercase' : 'none',
                        opacity: (ci > 0 && (cell === '-' || cell === '–')) ? 0.35 : 1,
                      }}>{cell}</td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </div>

      {source && (
        <div style={{ position: 'absolute', left: 80, bottom: 200 }}>
          <SourceCite>{source}</SourceCite>
        </div>
      )}
    </SlideShell>
  );
}

Object.assign(window, { TemplateGlobe, TemplateClaim, TemplateStatExplainer, TemplatePartnerships, TemplateComparison, Earth });

// ═══════════════════════════════════════════════════════════════════════════
// TEMPLATE LONG-SCROLL — Scroll-driven editorial story page
//   • The slide BECOMES a scrollable canvas: scroll-wheel inside the slide
//     scrolls vertically through stacked sections.
//   • Layout: small hero photo at top → big title → N "ContentHighlight"
//     sections (each: stat + scattered thumbs + paragraph) → "More stories"
//   • Right-side pagination bullets reflect which section is in view.
//   • Thumbnails per section reveal with stagger on scroll-into-view.
//   • Use for: deep-dive stories where you want the Netflix-Effect feel of
//     scrolling through a long editorial page on the same "slide".
//
//  Slide data shape:
//    {
//      template: 'LongScroll',
//      scheme:   'purpleDeep',
//      kicker:   'STORY 01',
//      title:    'GROWING THE\\nEUROPEAN PLATFORM',
//      subtitle: 'short claim under title',
//      image:    __a('show-drama', 'jpg'),   // small hero photo at top
//      imagePos: 'center 30%',
//      sections: [
//        { stat: '€325B', label: 'GLOBAL GVA',
//          body: 'paragraph of context',
//          thumbs: [{ src, x, y, w, h, rotate }, ...] },
//        ...
//      ],
//      moreStories: [{ shortLabel, thumb, slideIdx }, ...]
//    }
// ═══════════════════════════════════════════════════════════════════════════
function TemplateLongScroll({ slide, scheme, onJump, stepperRef }) {
  const {
    kicker, storyNum, title, subtitle,
    image, imagePos = 'center 30%',
    sections = [], moreStories = [], source,
    numbersStrip,
    killerNumber, killerLabel, killerPrefix, killerSuffix,
    decisionCard, decisionCards, decisionClosing,
    statSlide,
  } = slide;

  const scrollRef = React.useRef(null);
  const [activeSection, setActiveSection] = React.useState(0);
  const [scrollY, setScrollY] = React.useState(0);

  // Stepper for ↑/↓: snap one section at a time. LongScroll has the hero
  // as scroll position 0 and sections marked with data-section-idx starting
  // at 0. We treat scroll-top (no section active) as "page 0" so ↑ from
  // section 0 returns to the hero.
  React.useEffect(() => {
    if (!stepperRef) return;
    const stepTo = (delta) => {
      const el = scrollRef.current;
      if (!el) return false;
      const sectionEls = Array.from(el.querySelectorAll('[data-section-idx]'));
      if (sectionEls.length === 0) return false;
      const elR = el.getBoundingClientRect();
      const vpCenter = el.scrollTop + el.clientHeight / 2;
      // Determine current "page": -1 = hero, 0..N = section index
      let curIdx = -1;
      // If the user is near the top, treat as hero
      if (el.scrollTop < 40) {
        curIdx = -1;
      } else {
        let bestDist = Infinity;
        sectionEls.forEach((s) => {
          const i = parseInt(s.getAttribute('data-section-idx'), 10);
          const r = s.getBoundingClientRect();
          const center = r.top - elR.top + el.scrollTop + r.height / 2;
          const d = Math.abs(center - vpCenter);
          if (d < bestDist) { bestDist = d; curIdx = i; }
        });
      }
      const nextIdx = curIdx + delta;
      if (nextIdx < -1 || nextIdx >= sectionEls.length) return false;
      if (nextIdx === -1) {
        el.scrollTo({ top: 0, behavior: 'smooth' });
      } else {
        const target = el.querySelector(`[data-section-idx="${nextIdx}"]`);
        if (!target) return false;
        target.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
      return true;
    };
    stepperRef.current = {
      down: () => stepTo(+1),
      up:   () => stepTo(-1),
    };
    return () => {
      if (stepperRef.current) stepperRef.current = null;
    };
  }, [stepperRef]);

  React.useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onScroll = () => {
      setScrollY(el.scrollTop);
      // find the section closest to the viewport center
      const sectionEls = el.querySelectorAll('[data-section-idx]');
      const viewCenter = el.scrollTop + el.clientHeight / 2;
      let bestIdx = 0;
      let bestDist = Infinity;
      sectionEls.forEach((s) => {
        const idx = parseInt(s.getAttribute('data-section-idx'), 10);
        const r = s.getBoundingClientRect();
        const elR = el.getBoundingClientRect();
        const center = r.top - elR.top + el.scrollTop + r.height / 2;
        const d = Math.abs(center - viewCenter);
        if (d < bestDist) { bestDist = d; bestIdx = idx; }
      });
      setActiveSection(bestIdx);
    };
    el.addEventListener('scroll', onScroll, { passive: true });
    return () => el.removeEventListener('scroll', onScroll);
  }, [sections.length]);

  // Helper: scroll to a section by index
  const jumpToSection = (idx) => {
    const el = scrollRef.current;
    if (!el) return;
    const target = el.querySelector(`[data-section-idx="${idx}"]`);
    if (target) target.scrollIntoView({ behavior: 'smooth', block: 'center' });
  };

  return (
    <SlideShell scheme={scheme}>
      {/* Scrollable canvas — the whole slide body becomes a scroll container */}
      <div
        ref={scrollRef}
        style={{
          position: 'absolute',
          left: 0, right: 0, top: 110, bottom: 0,
          overflowY: 'auto',
          overflowX: 'hidden',
          scrollBehavior: 'smooth',
        }}
      >
        {/* ── REDUCED HERO ────────────────────────────────────────────── */}
        <div style={{
          position: 'relative',
          margin: '0 auto',
          width: 1480,
          height: 400,
          borderRadius: Tokens.radius.xxl,
          overflow: 'hidden',
          marginTop: 30,
          boxShadow: '0 30px 90px rgba(0,0,0,0.55)',
        }}>
          <div style={{
            position: 'absolute', inset: 0,
            backgroundImage: `url(${image})`,
            backgroundSize: 'cover',
            backgroundPosition: imagePos,
          }}/>
          <div style={{
            position: 'absolute', inset: 0,
            background: 'linear-gradient(0deg, rgba(8,8,14,0.92) 0%, rgba(8,8,14,0.25) 60%, transparent 100%)',
          }}/>
          {/* Back to overview pill */}
          <div style={{
            position: 'absolute', top: 24, left: 24,
            padding: '8px 16px 8px 12px',
            background: 'rgba(20,20,28,0.6)',
            border: `1px solid ${Tokens.inkLine}`,
            borderRadius: Tokens.radius.pill,
            backdropFilter: 'blur(14px)',
            color: Tokens.ink,
            fontFamily: Tokens.fontDisplay,
            fontSize: 13, fontWeight: 500,
            display: 'flex', alignItems: 'center', gap: 6,
          }}>
            <span style={{ fontSize: 16, lineHeight: 1 }}>‹</span>
            Back to overview
          </div>
        </div>

        {/* Corner brackets framing the hero photo */}
        <div style={{
          position: 'absolute', top: 30, left: 'calc(50% - 740px - 30px)',
          width: 1540, height: 460,
          pointerEvents: 'none',
        }}>
          <CornerBrackets color={scheme.brackets} size={60} thickness={2.5} opacity={0.85}/>
        </div>

        {/* ── BIG TITLE BLOCK ─────────────────────────────────────────── */}
        <div style={{
          margin: '64px auto 0',
          maxWidth: 1200,
          textAlign: 'center',
        }}>
          {kicker && (
            <div style={{
              fontFamily: Tokens.fontMono, fontSize: 13,
              color: scheme.accent, letterSpacing: '0.32em',
              textTransform: 'uppercase',
              marginBottom: 16,
              fontWeight: Tokens.weight.semibold,
            }}>{kicker}</div>
          )}
          {storyNum != null && (
            <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 18 }}>
              <StoryLabel num={storyNum} color={scheme.storyLabel}/>
            </div>
          )}
          <div style={{
            fontFamily: Tokens.fontDisplay,
            fontSize: title.length > 26 ? 72 : 92,
            fontWeight: Tokens.weight.black,
            color: scheme.text,
            letterSpacing: '-0.025em',
            textTransform: 'uppercase',
            lineHeight: 0.94,
          }}>{renderTitle(title)}</div>
          {subtitle && (
            <div style={{
              marginTop: 24,
              fontFamily: Tokens.fontDisplay,
              fontSize: 18, fontWeight: Tokens.weight.regular,
              color: scheme.textDim,
              lineHeight: 1.55,
              maxWidth: 900, margin: '24px auto 0',
            }}>{subtitle}</div>
          )}

          {/* Hero killer-number card — same surface idiom as Claim's killer,
              centered under the subtitle. Used in deep-dive LongScrolls
              (slides 13 Vertical First, 14 Mediaset Classics) so the "single
              big number" lives in the hero next to title + subtitle. */}
          {killerNumber && (
            <div style={{
              marginTop: 28,
              display: 'inline-flex',
              alignItems: 'center', gap: 24,
              padding: '18px 28px',
              border: `1px solid ${scheme.accent}`,
              borderLeft: `4px solid ${scheme.accent}`,
              background: 'rgba(255,255,255,0.02)',
              borderRadius: Tokens.radius.lg,
              maxWidth: 920,
              textAlign: 'left',
            }}>
              <div style={{
                fontFamily: Tokens.fontDisplay,
                fontWeight: Tokens.weight.black,
                fontSize: 56, color: scheme.accent,
                letterSpacing: '-0.04em', lineHeight: 0.9,
                display: 'flex', alignItems: 'baseline',
                whiteSpace: 'nowrap', flex: '0 0 auto',
              }}>
                {killerPrefix && <span style={{ fontSize: 28, marginRight: 4 }}>{killerPrefix}</span>}
                <span>{killerNumber}</span>
                {killerSuffix && <span style={{ fontSize: 28, marginLeft: 4 }}>{killerSuffix}</span>}
              </div>
              {killerLabel && (
                <div style={{
                  fontFamily: Tokens.fontDisplay,
                  fontSize: 14,
                  fontWeight: Tokens.weight.medium,
                  color: scheme.textDim,
                  lineHeight: 1.4,
                  maxWidth: 500,
                  textAlign: 'left',
                }}>{killerLabel}</div>
              )}
            </div>
          )}

          {/* Scroll affordance */}
          <div style={{
            marginTop: 56,
            display: 'inline-flex', alignItems: 'center', gap: 10,
            fontFamily: Tokens.fontMono, fontSize: 11,
            color: scheme.textDim, letterSpacing: '0.28em',
            textTransform: 'uppercase',
            opacity: scrollY > 50 ? 0 : 0.85,
            transition: 'opacity 200ms',
          }}>
            Scroll
            <span style={{
              display: 'inline-block',
              transform: `translateY(${Math.sin(scrollY / 100) * 3}px)`,
              animation: 'scrollNudge 1.6s ease-in-out infinite',
            }}>↓</span>
          </div>
        </div>

        {/* ── CANONICAL NUMBERS STRIP ─────────────────────────────────── */}
        {numbersStrip && numbersStrip.length > 0 && (
          <div style={{
            margin: '64px auto 0', maxWidth: 1480, padding: '0 80px',
          }}>
            <div style={{
              display: 'flex', flexDirection: 'row',
              justifyContent: 'space-between', alignItems: 'stretch',
              padding: '22px 28px',
              border: `1px solid ${scheme.accent}55`,
              borderLeft: `4px solid ${scheme.accent}`,
              borderRadius: Tokens.radius.lg,
              background: 'rgba(255,255,255,0.02)',
            }}>
              {numbersStrip.map((item, i) => (
                <React.Fragment key={i}>
                  {i > 0 && <div style={{
                    width: 1, alignSelf: 'stretch',
                    background: 'rgba(255,255,255,0.12)',
                  }}/>}
                  <div style={{
                    display: 'flex', flexDirection: 'column', gap: 4,
                    padding: '0 18px', flex: 1, textAlign: 'left',
                  }}>
                    <div style={{
                      fontFamily: Tokens.fontDisplay,
                      fontSize: 22,
                      fontWeight: Tokens.weight.black,
                      color: scheme.accent,
                      letterSpacing: '-0.02em',
                      lineHeight: 1,
                    }}>{item.value}</div>
                    <div style={{
                      fontFamily: Tokens.fontMono,
                      fontSize: 10.5,
                      color: Tokens.inkSoft,
                      letterSpacing: '0.16em',
                      textTransform: 'uppercase',
                      lineHeight: 1.3,
                    }}>{item.label}</div>
                  </div>
                </React.Fragment>
              ))}
            </div>
            <div style={{
              marginTop: 12, textAlign: 'center',
              fontFamily: Tokens.fontMono, fontSize: 10.5,
              color: Tokens.inkSoft, letterSpacing: '0.24em',
              textTransform: 'uppercase',
            }}>5 numeri canonici · li ritrovi nella closing</div>
          </div>
        )}

        {/* ── CONTENT HIGHLIGHT SECTIONS ──────────────────────────────── */}
        {sections.map((s, i) => (
          <ContentHighlight key={i} idx={i} section={s} scheme={scheme} scrollEl={scrollRef.current} onJump={onJump}/>
        ))}

        {/* ── MORE STORIES (footer) ───────────────────────────────────── */}
        {/* ── DECISION CARD(S) ───────────────────────────────────────────
            Per le slide-decision (Owned First, Sport mix, ADV mandate):
            DOMANDA → RACCOMANDAZIONE → RISCHIO. Una o più card. */}
        {(decisionCard || (decisionCards && decisionCards.length > 0)) && (
          <div style={{ margin: '100px auto 0', maxWidth: 1480, padding: '0 80px' }}>
            <div style={{
              fontFamily: Tokens.fontMono, fontSize: 13,
              color: scheme.accent, letterSpacing: '0.32em',
              textTransform: 'uppercase',
              marginBottom: 22,
              fontWeight: Tokens.weight.semibold,
            }}>{decisionCards ? 'Decision cards · 3 deal' : 'Decision card · domanda al management'}</div>
            <div style={{
              display: 'grid',
              gridTemplateColumns: decisionCards
                ? `repeat(${Math.min(decisionCards.length, 3)}, 1fr)`
                : '1fr',
              gap: 18,
            }}>
              {(decisionCards || [decisionCard]).map((dc, i) => (
                <div key={i} style={{
                  padding: '28px 30px',
                  border: `1px solid ${scheme.accent}`,
                  borderLeft: `5px solid ${scheme.accent}`,
                  borderRadius: Tokens.radius.lg,
                  background: `linear-gradient(135deg, ${scheme.accent}10 0%, rgba(255,255,255,0.02) 70%)`,
                  boxShadow: `0 0 60px ${scheme.accent}22`,
                  display: 'flex', flexDirection: 'column', gap: 18,
                }}>
                  {dc.tag && (
                    <div style={{
                      fontFamily: Tokens.fontMono, fontSize: 11,
                      color: scheme.accent, letterSpacing: '0.28em',
                      textTransform: 'uppercase',
                      fontWeight: Tokens.weight.bold,
                    }}>{dc.tag}</div>
                  )}
                  {dc.domanda && (
                    <div>
                      <div style={{
                        fontFamily: Tokens.fontMono, fontSize: 10,
                        color: scheme.accent, letterSpacing: '0.24em',
                        textTransform: 'uppercase',
                        fontWeight: Tokens.weight.bold,
                        marginBottom: 6,
                      }}>Domanda</div>
                      <div style={{
                        fontFamily: Tokens.fontDisplay, fontSize: 17,
                        fontWeight: Tokens.weight.semibold,
                        color: scheme.text, lineHeight: 1.4,
                      }}>{dc.domanda}</div>
                    </div>
                  )}
                  {dc.raccomandazione && (
                    <div>
                      <div style={{
                        fontFamily: Tokens.fontMono, fontSize: 10,
                        color: '#3DFFA0', letterSpacing: '0.24em',
                        textTransform: 'uppercase',
                        fontWeight: Tokens.weight.bold,
                        marginBottom: 6,
                      }}>✓ Raccomandazione</div>
                      <div style={{
                        fontFamily: Tokens.fontDisplay, fontSize: 15,
                        fontWeight: Tokens.weight.regular,
                        color: scheme.text, lineHeight: 1.5,
                      }}>{dc.raccomandazione}</div>
                    </div>
                  )}
                  {dc.rischio && (
                    <div>
                      <div style={{
                        fontFamily: Tokens.fontMono, fontSize: 10,
                        color: '#F7931A', letterSpacing: '0.24em',
                        textTransform: 'uppercase',
                        fontWeight: Tokens.weight.bold,
                        marginBottom: 6,
                      }}>⚠ Rischio</div>
                      <div style={{
                        fontFamily: Tokens.fontDisplay, fontSize: 14,
                        fontWeight: Tokens.weight.regular,
                        color: scheme.textDim, lineHeight: 1.5,
                      }}>{dc.rischio}</div>
                    </div>
                  )}
                </div>
              ))}
            </div>
            {decisionClosing && (
              <div style={{
                marginTop: 28,
                padding: '18px 24px',
                borderLeft: `3px solid ${scheme.accent}`,
                fontFamily: Tokens.fontDisplay, fontSize: 16,
                color: scheme.text, fontStyle: 'italic',
                fontWeight: Tokens.weight.medium,
                lineHeight: 1.5,
                background: 'rgba(255,255,255,0.02)',
              }}>{decisionClosing}</div>
            )}
          </div>
        )}

        {moreStories.length > 0 && (
          <div style={{ margin: '120px auto 80px', maxWidth: 1480, padding: '0 80px' }}>
            <div style={{
              fontFamily: Tokens.fontMono, fontSize: 13,
              color: scheme.textDim, letterSpacing: '0.32em',
              textTransform: 'uppercase',
              marginBottom: 22,
              fontWeight: Tokens.weight.semibold,
            }}>More stories for you</div>
            <div style={{
              display: 'grid',
              gridTemplateColumns: moreStories.length >= 4 ? 'repeat(4, 1fr)' : 'repeat(2, 1fr)',
              gap: 16,
            }}>
              {moreStories.map((ms, mi) => (
                <button key={mi}
                  onClick={() => onJump && ms.slideIdx != null && onJump(ms.slideIdx)}
                  style={{
                    position: 'relative',
                    height: moreStories.length >= 4 ? 180 : 220,
                    borderRadius: Tokens.radius.xl,
                    overflow: 'hidden',
                    border: `1px solid ${Tokens.inkLine}`,
                    cursor: 'pointer',
                    background: 'none', padding: 0, textAlign: 'left',
                  }}>
                  <div style={{
                    position: 'absolute', inset: 0,
                    backgroundImage: ms.thumb ? `url(${ms.thumb})` : 'none',
                    backgroundSize: 'cover',
                    backgroundPosition: ms.imagePos || 'center',
                    backgroundColor: Tokens.bgSurface,
                  }}/>
                  <div style={{
                    position: 'absolute', inset: 0,
                    background: 'linear-gradient(0deg, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.3) 60%, transparent 100%)',
                  }}/>
                  <div style={{
                    position: 'absolute', left: 24, bottom: 22, right: 24,
                  }}>
                    <div style={{
                      fontFamily: Tokens.fontMono, fontSize: 11,
                      color: scheme.accent, letterSpacing: '0.24em',
                      textTransform: 'uppercase',
                      marginBottom: 6,
                      fontWeight: Tokens.weight.semibold,
                    }}>Story {String((ms.slideIdx ?? 0) + 1).padStart(2, '0')} →</div>
                    <div style={{
                      fontFamily: Tokens.fontDisplay, fontSize: 24,
                      fontWeight: Tokens.weight.extrabold,
                      color: Tokens.ink, letterSpacing: '-0.015em',
                      lineHeight: 1.1,
                      textTransform: 'uppercase',
                    }}>{ms.shortLabel || ms.title}</div>
                  </div>
                </button>
              ))}
            </div>
          </div>
        )}

        {source && (
          <div style={{
            margin: '0 auto 60px', maxWidth: 1480, padding: '0 80px',
            textAlign: 'right',
          }}>
            <SourceCite>{source}</SourceCite>
          </div>
        )}
      </div>

      {/* ── PAGINATION BULLETS (vertical, right side) ────────────────── */}
      {sections.length > 1 && (
        <div style={{
          position: 'absolute',
          right: 40, top: '50%',
          transform: 'translateY(-50%)',
          display: 'flex', flexDirection: 'column', gap: 10,
          zIndex: 40,
        }}>
          {sections.map((_, i) => (
            <button key={i}
              onClick={() => jumpToSection(i)}
              aria-label={`Section ${i + 1}`}
              style={{
                width: i === activeSection ? 22 : 10,
                height: 10,
                borderRadius: Tokens.radius.pill,
                background: i === activeSection ? scheme.text : Tokens.inkFaint,
                border: 'none',
                cursor: 'pointer',
                transition: 'all 220ms',
              }}/>
          ))}
        </div>
      )}
    </SlideShell>
  );
}

// ── ContentHighlight: one section of the LongScroll template ──────────────
//   Animates in when its top enters the viewport. Renders big stat + thumb
//   cards (clickable, with #XX/label captions) + a paragraph below.
//
//   Two layouts:
//     • 'scatter' (default) — thumbs scattered around the stat at x/y coords
//     • 'row' — thumbs arranged as a horizontal grid below the stat block
//       (better for filling horizontal viewport space; ignores x/y on thumbs)
//
//   Thumbs sit on TOP (z-index) so their clicks land; the stat block has
//   pointer-events:none so clicks pass through to overlapping cards.
function ContentHighlight({ idx, section, scheme, scrollEl, onJump }) {
  const { stat, prefix, suffix, label, body, thumbs = [], layout = 'scatter' } = section;
  const ref = React.useRef(null);
  const [visible, setVisible] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [floatT, setFloatT] = React.useState(0);

  React.useEffect(() => {
    if (!ref.current) return;
    const obs = new IntersectionObserver(
      (entries) => {
        for (const e of entries) {
          if (e.isIntersecting) setVisible(true);
        }
      },
      { threshold: 0.15, root: scrollEl || null }
    );
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, [scrollEl]);

  // Smooth ramp progress 0→1 after visible; keep ticking afterwards for
  // the idle thumb float and continuous bob.
  React.useEffect(() => {
    if (!visible) return;
    let raf, t0 = performance.now();
    const tick = () => {
      const elapsed = (performance.now() - t0) / 1000;
      setFloatT(elapsed);
      setProgress(Math.min(1, elapsed / 1.4));
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [visible]);

  const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
  const easeOutBack  = (t) => {
    const c1 = 1.70158, c3 = c1 + 1;
    return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
  };

  // Number animation — bouncy easeOutBack, scale 0.45→1, slide-up 80px
  const numScale   = 0.45 + 0.55 * easeOutBack(progress);
  const numOpacity = Math.min(1, progress * 1.6);
  const numTY      = (1 - easeOutCubic(progress)) * 80;

  // Reusable thumb-card renderer (animated, clickable)
  const renderThumb = (t, i, opts = {}) => {
    const { positionMode = 'absolute', cardW = t.w || 240 } = opts;
    // Decorative center thumbs (no label/actionId, x ≈ 0.5) overlap the
    // centered body text area in scatter sections — skip rendering them.
    const hasLabel = !!(t.actionId || t.label);
    const isCentered = positionMode === 'absolute' && t.x != null && t.x > 0.38 && t.x < 0.62;
    if (!hasLabel && isCentered) return null;

    // For decorative side thumbs (no label/actionId), keep the card at least
    // square so portrait source images don't get cropped to a thin horizontal
    // slice by background-size:cover.
    const declaredH = opts.cardH || t.h || 300;
    const cardH = (!hasLabel && typeof cardW === 'number')
      ? Math.max(declaredH, cardW)
      : declaredH;
    const delay = 0.18 + i * 0.10;
    const localP = Math.max(0, Math.min(1, (floatT - delay) / 0.85));
    const ease   = easeOutBack(localP);
    const op     = Math.min(1, localP * 1.4);
    const idleY  = localP >= 1 ? Math.sin(floatT * 0.7 + i * 1.3) * 4 : 0;
    const clickable = !!t.slideIdx;
    const handleClick = () => { if (clickable && onJump) onJump(t.slideIdx); };

    const baseStyle = positionMode === 'absolute' ? {
      position: 'absolute',
      left: `${(t.x ?? 0.5) * 100}%`,
      top: `${(t.y ?? 0.5) * 100}%`,
      transform: `translate(-50%, calc(-50% + ${idleY}px)) rotate(${(t.rotate || 0) * ease}deg) scale(${0.55 + 0.45 * ease})`,
    } : {
      position: 'relative',
      transform: `translateY(${idleY}px) rotate(${(t.rotate || 0) * ease * 0.4}deg) scale(${0.55 + 0.45 * ease})`,
    };

    return (
      <div key={i}
        className="thumb-card-outer"
        onClick={handleClick}
        style={{
          ...baseStyle,
          width: cardW, height: cardH,
          opacity: op * (t.opacity ?? 1),
          cursor: clickable ? 'pointer' : 'default',
          willChange: 'transform',
          flex: positionMode === 'row' ? '1 1 0' : 'none',
        }}
      >
        {/* Inner layer carries hover scale + outline (CSS-driven, doesn't
            fight the RAF transform on the outer layer) */}
        <div
          className="thumb-card-inner"
          style={{
            borderRadius: Tokens.radius.lg,
            overflow: 'hidden',
            boxShadow: localP >= 1
              ? '0 18px 50px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.35)'
              : '0 14px 40px rgba(0,0,0,0.55)',
            border: `1px solid ${Tokens.inkLine}`,
            filter: t.blur ? `blur(${t.blur}px)` : 'none',
            position: 'relative',
          }}
          onMouseEnter={(e) => {
            if (!clickable) return;
            e.currentTarget.style.boxShadow = `0 28px 80px rgba(0,0,0,0.75), 0 0 0 2px ${scheme.accent}`;
          }}
          onMouseLeave={(e) => {
            if (!clickable) return;
            e.currentTarget.style.boxShadow = '0 18px 50px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.35)';
          }}
        >
          {/* Image fills full card when there's no label, otherwise leaves
              68px at bottom for the label strip. Decorative thumbs on scatter
              sections have no label → no wasted black strip. */}
          {(() => {
            const hasLabel = !!(t.actionId || t.label);
            const imgH = hasLabel ? cardH - 68 : cardH;
            return (
              <>
                <div style={{
                  position: 'absolute', left: 0, right: 0, top: 0,
                  height: imgH,
                  background: `url(${t.src}) center/cover no-repeat`,
                }}/>
                {hasLabel && (
                  <div style={{
                    position: 'absolute', left: 0, right: 0, bottom: 0,
                    height: 68,
                    background: 'linear-gradient(0deg, rgba(0,0,0,0.92) 0%, rgba(0,0,0,0.75) 100%)',
                    padding: '10px 14px',
                    display: 'flex', flexDirection: 'column', justifyContent: 'center',
                    borderTop: `1px solid ${scheme.accent}44`,
                  }}>
                    {t.actionId && (
                      <div style={{
                        fontFamily: Tokens.fontMono, fontSize: 11,
                        color: scheme.accent, letterSpacing: '0.20em',
                        fontWeight: 700, marginBottom: 4,
                      }}>{t.actionId}</div>
                    )}
                    {t.label && (
                      <div style={{
                        fontFamily: Tokens.fontDisplay, fontSize: 14,
                        color: Tokens.ink, fontWeight: 700,
                        letterSpacing: '-0.005em',
                        lineHeight: 1.2,
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap',
                      }}>{t.label}</div>
                    )}
                  </div>
                )}
              </>
            );
          })()}
        </div>
      </div>
    );
  };

  // ── ROW layout — used by the cover bucket sections to fill horizontal space ──
  if (layout === 'row') {
    return (
      <div ref={ref} data-section-idx={idx} style={{
        position: 'relative',
        margin: '120px auto 0',
        maxWidth: 1760,
        padding: '40px 60px 60px',
        minHeight: 800,
      }}>
        {/* Stat + label + body stacked at top */}
        <div style={{
          textAlign: 'center',
          opacity: numOpacity,
          transform: `translateY(${numTY}px) scale(${numScale})`,
          transformOrigin: 'center top',
          marginBottom: 48,
          willChange: 'transform, opacity',
        }}>
          <div style={{
            display: 'inline-flex', alignItems: 'baseline',
            fontFamily: Tokens.fontDisplay, fontWeight: Tokens.weight.black,
            color: scheme.accent, letterSpacing: '-0.05em', lineHeight: 0.9,
          }}>
            {prefix && <span style={{ fontSize: 56, marginRight: 8, fontWeight: Tokens.weight.bold }}>{prefix}</span>}
            <span style={{ fontSize: 160 }}>{stat}</span>
            {suffix && <span style={{ fontSize: 56, marginLeft: 8, fontWeight: Tokens.weight.bold }}>{suffix}</span>}
          </div>
          {label && (
            <div style={{
              marginTop: 12,
              fontFamily: Tokens.fontDisplay,
              fontSize: 26, fontWeight: Tokens.weight.semibold,
              color: scheme.text,
              letterSpacing: '0.01em',
              textTransform: 'uppercase',
            }}>{label}</div>
          )}
          {body && (
            <div style={{
              marginTop: 18,
              fontFamily: Tokens.fontDisplay,
              fontSize: 17, fontWeight: Tokens.weight.regular,
              color: scheme.textDim,
              lineHeight: 1.55,
              maxWidth: 900, margin: '18px auto 0',
            }}>{body}</div>
          )}
        </div>

        {/* Row of cards — fills horizontal space, equal flex */}
        <div style={{
          display: 'grid',
          gridTemplateColumns: `repeat(${thumbs.length}, 1fr)`,
          gap: 20,
          width: '100%',
        }}>
          {thumbs.map((t, i) => renderThumb(t, i, { positionMode: 'row', cardW: '100%', cardH: 320 }))}
        </div>
      </div>
    );
  }

  // ── SCATTER layout (default) — thumbs at absolute x/y around the stat ──
  return (
    <div ref={ref} data-section-idx={idx} style={{
      position: 'relative',
      margin: '160px auto 0',
      maxWidth: 1480,
      padding: '40px 80px 80px',
      minHeight: 600,
    }}>
      <div style={{ position: 'absolute', inset: 0, zIndex: 1 }}>
        {thumbs.map((t, i) => renderThumb(t, i, { positionMode: 'absolute' }))}
      </div>

      <div style={{
        position: 'relative',
        zIndex: 5,
        pointerEvents: 'none',
        textAlign: 'center',
        opacity: numOpacity,
        transform: `translateY(${numTY}px) scale(${numScale})`,
        transformOrigin: 'center',
        willChange: 'transform, opacity',
      }}>
        <div style={{
          display: 'inline-flex',
          alignItems: 'baseline',
          fontFamily: Tokens.fontDisplay,
          fontWeight: Tokens.weight.black,
          color: scheme.accent,
          letterSpacing: '-0.05em',
          lineHeight: 0.9,
        }}>
          {prefix && <span style={{ fontSize: 72, marginRight: 8, fontWeight: Tokens.weight.bold }}>{prefix}</span>}
          <span style={{ fontSize: 220 }}>{stat}</span>
          {suffix && <span style={{ fontSize: 72, marginLeft: 8, fontWeight: Tokens.weight.bold }}>{suffix}</span>}
        </div>
        {label && (
          <div style={{
            marginTop: 18,
            fontFamily: Tokens.fontDisplay,
            fontSize: 22, fontWeight: Tokens.weight.semibold,
            color: scheme.text,
            letterSpacing: '0em',
            textTransform: 'uppercase',
          }}>{label}</div>
        )}
        {body && (
          <div style={{
            marginTop: 24,
            fontFamily: Tokens.fontDisplay,
            fontSize: 17, fontWeight: Tokens.weight.regular,
            color: scheme.textDim,
            lineHeight: 1.55,
            maxWidth: 720, margin: '24px auto 0',
          }}>{body}</div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { TemplateLongScroll, ContentHighlight });
