// slides-middle.jsx — Slides 8 through 14 (disadvantages, asymmetry, goal, YAML, roles, use cases OCO + Red Team)

// ─── SLIDE 08 · DISADVANTAGES TABLE ───────────────────────────────
function Slide09_Disadvantages() {
  const rows = [
    { category: "Cloud-based", severity: "cost", dealbreaker: true, items: ["Expensive per-seat billing", "No access to underlying infrastructure", "Telemetry exposure — who sees your traffic?"] },
    { category: "Rigid", severity: "fixed", items: ["Fixed domain names", "Fixed IPs", "Fixed user accounts, vulnerabilities, configurations"] },
    { category: "Siloed", severity: "collab", items: ["No easy way to share with teammates", "Single EDR/logging solution — if any", "No concept of OPSEC"] },
    { category: "Hard to extend", severity: "ext", dealbreaker: true, items: ["Cannot install arbitrary software", "Cannot integrate with customer tooling", "No automation hooks"] },
  ];
  return (
    <Slide section="THE MARKET">
      <Content>
        <NumberedEyebrow>GAP ANALYSIS</NumberedEyebrow>
        <Title style={{marginTop: 16}}>Why other tools don't fit</Title>
        <div style={{fontSize: TS.body, color: DK_FG_MUTED, marginTop: 12, maxWidth: 1100}}>
          Commercial and open source alike trade away something operators need.
        </div>

        <div style={{flex:1, marginTop: 40, display:"grid", gridTemplateColumns:"1fr 1fr", gap: 24}}>
          {rows.map((r, i) => (
            <div key={i} style={{
              position:"relative", border: `1px solid ${r.dealbreaker ? CRIMSON : DK_BORDER}`, padding: 0,
              background: "rgba(20,22,28,0.65)",
              display:"flex", flexDirection:"column",
              boxShadow: r.dealbreaker ? `0 0 34px rgba(233,69,96,0.18)` : "none",
            }}>
              <Brackets size={12} color={r.dealbreaker ? CRIMSON : "rgba(197,174,79,0.5)"} inset={-1}/>
              <div style={{
                display:"flex", alignItems:"center", gap: 18,
                padding: "20px 28px",
                borderBottom: `1px solid ${r.dealbreaker ? CRIMSON : DK_BORDER}`,
                background: "rgba(13,15,19,0.7)",
              }}>
                <div style={{
                  fontSize: 14, color: DK_BG, fontWeight: 700, letterSpacing:"0.3em",
                  background: CRIMSON, padding: "5px 12px",
                }}>
                  ISSUE 0{i+1}
                </div>
                <div style={{fontSize: 32, fontWeight: 700, color: DK_FG}}>{r.category}</div>

              </div>
              <div style={{padding: "22px 28px", display:"flex", flexDirection:"column", gap: 12}}>
                {r.items.map((it, j)=>(
                  <div key={j} style={{display:"flex", gap: 14, fontSize: 21, color: DK_FG, lineHeight: 1.45, fontWeight: 500}}>
                    <span style={{color: CRIMSON, fontWeight: 700, fontSize: 22}}>×</span>
                    <span>{it}</span>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      </Content>
    </Slide>
  );
}

// ─── SLIDE 09 · RECENT ASYMMETRY (chart) ───────────────────────────
function Slide10_Asymmetry() {
  // Sweep duration — both the curve draw and marker cue-points derive from this.
  const DRAW_SEC = 2.8;
  // Chart x-range in viewBox units; markers' delays are a fraction of DRAW_SEC.
  const X0 = 80, X1 = 1640;

  const markers = [
    {x: 200, label: "PROXMOX",    year: "2008", tile: 110},
    {x: 480, label: "ANSIBLE",    year: "2012", tile: 170},
    {x: 580, label: "DOCKER",     year: "2013", tile: 110},
    {x: 740, label: "KUBERNETES", year: "2015", tile: 170},
    {x: 900, label: "AMD EPYC",   year: "2017", tile: 110},
  ];

  return (
    <Slide section="THE OPPORTUNITY">
      <TechGrid spacing={40} color="rgba(197,174,79,0.05)" fade={false}/>
      <Content>
        {/* Scoped animations — triggered when <section data-deck-active> appears.
            Clip-rect sweeps L→R to reveal stroke+fill together; markers pop in
            as the sweep crosses their x-position. Linear timing keeps sync. */}
        <style>{`
          @keyframes slide10-sweep { from { width: 0; } to { width: ${X1 - X0}px; } }
          @keyframes slide10-pop   { from { opacity: 0; } to { opacity: 1; } }
          .slide10-reveal-rect { width: 0; }
          .slide10-marker      { opacity: 0; }
          section[data-deck-active] .slide10-reveal-rect {
            animation: slide10-sweep ${DRAW_SEC}s linear forwards;
          }
          section[data-deck-active] .slide10-marker {
            animation: slide10-pop 0.45s cubic-bezier(0.2,0.8,0.2,1) forwards;
            animation-delay: var(--slide10-delay, 0s);
          }
          @media (prefers-reduced-motion: reduce) {
            .slide10-reveal-rect { width: ${X1 - X0}px; animation: none; }
            .slide10-marker { opacity: 1; animation: none; }
          }
        `}</style>

        <div style={{display:"flex", alignItems:"baseline", justifyContent:"space-between"}}>
          <div>
            <NumberedEyebrow>MACRO TREND</NumberedEyebrow>
            <Title style={{marginTop: 16}}>Hardware overtook what software needs</Title>
            <div style={{fontSize: TS.body, color: DK_FG_MUTED, marginTop: 20, maxWidth: 1100}}>
              For the first time, the average commodity machine can run a moderately complex enterprise network. The golden age of hypervisors is <span style={{color: GOLD}}>now</span>.
            </div>
          </div>
        </div>

        {/* Chart */}
        <div style={{flex:1, marginTop: 40, position:"relative"}}>
          <svg viewBox="0 0 1680 540" style={{width:"100%", height:"100%", overflow:"visible"}}>
            <defs>
              <linearGradient id="g_hw" x1="0" y1="1" x2="0" y2="0">
                <stop offset="0%" stopColor="rgba(197,174,79,0.0)"/>
                <stop offset="100%" stopColor="rgba(197,174,79,0.35)"/>
              </linearGradient>
              <linearGradient id="g_sw" x1="0" y1="1" x2="0" y2="0">
                <stop offset="0%" stopColor="rgba(143,29,52,0.0)"/>
                <stop offset="100%" stopColor="rgba(143,29,52,0.3)"/>
              </linearGradient>
              {/* Reveal mask — width animates 0 → (X1-X0) to sweep L→R */}
              <clipPath id="slide10-reveal">
                <rect className="slide10-reveal-rect" x={X0} y="0" height="540"/>
              </clipPath>
            </defs>

            {/* Grid */}
            {[0,1,2,3,4].map(i => (
              <line key={i} x1="80" x2="1640" y1={80 + i*100} y2={80 + i*100}
                    stroke={DK_BORDER_SOFT} strokeDasharray="2 6"/>
            ))}
            <line x1="80" y1="80" x2="80" y2="480" stroke={DK_BORDER}/>
            <line x1="80" y1="480" x2="1640" y2="480" stroke={DK_BORDER}/>

            {/* X axis labels */}
            {[["2008", 200],["2017", 900],["2026", 1580]].map(([y, x],i)=>(
              <text key={i} x={x} y="512" fill={DK_FG_MUTED} fontSize="18" letterSpacing="0.2em" textAnchor="middle">{y}</text>
            ))}

            {/* Curves — clipped to the sweep rect so stroke + fill reveal together */}
            <g clipPath="url(#slide10-reveal)">
              {/* Software-needs curve — gentle, near-linear rise */}
              <path d="M80,385 C 500,375 1000,340 1640,270" fill="url(#g_sw)" stroke="none" opacity="0.35"/>
              <path d="M80,385 C 500,375 1000,340 1640,270"
                    fill="none" stroke={CRIMSON} strokeWidth="3" strokeLinecap="round"/>

              {/* Hardware power — flat early, lift starts ~2015, crosses software at 2017 */}
              <path d="M80,460 C 700,460 820,400 900,341 S 1500,130 1640,110" fill="url(#g_hw)" stroke="none"/>
              <path d="M80,460 C 700,460 820,400 900,341 S 1500,130 1640,110"
                    fill="none" stroke={GOLD} strokeWidth="4" strokeLinecap="round"/>
            </g>

            {/* Tool timeline markers — vertical time gridlines, decoupled
                from the curves. Each line sits on the x-axis with a stacked
                label tile at the top; staggered heights prevent collisions
                across close years. Markers pop in when the sweep crosses
                their x-position (delay = x-fraction × draw duration). */}
            {markers.map((m,i)=>{
              const delay = ((m.x - X0) / (X1 - X0)) * DRAW_SEC;
              return (
                <g key={i} className="slide10-marker" style={{"--slide10-delay": `${delay.toFixed(3)}s`}}>
                  <line x1={m.x} y1="480" x2={m.x} y2={m.tile + 40} stroke={DK_FG_MUTED} strokeWidth="1" strokeDasharray="2 3" opacity="0.7"/>
                  <rect x={m.x - 62} y={m.tile} width="124" height="40" fill={DK_BG} stroke={DK_BORDER}/>
                  <text x={m.x} y={m.tile + 20} fill={DK_FG} fontSize="17" letterSpacing="0.18em" textAnchor="middle" fontWeight="700">{m.label}</text>
                  <text x={m.x} y={m.tile + 34} fill={GOLD} fontSize="13" letterSpacing="0.2em" textAnchor="middle" fontWeight="600">{m.year}</text>
                </g>
              );
            })}

            {/* Labels on curves — positioned well clear of the lines */}
            <rect x="1430" y="46" width="210" height="40" fill={DK_BG} stroke="rgba(197,174,79,0.3)"/>
            <text x="1620" y="72" fill={GOLD} fontSize="20" fontWeight="700" letterSpacing="0.05em" textAnchor="end">Hardware power</text>
            <rect x="1430" y="216" width="210" height="56" fill={DK_BG} stroke="rgba(143,29,52,0.35)"/>
            <text x="1620" y="240" fill={CRIMSON} fontSize="20" fontWeight="700" letterSpacing="0.05em" textAnchor="end">Software needs</text>
            <text x="1620" y="262" fill={DK_FG_MUTED} fontSize="13" letterSpacing="0.2em" textAnchor="end">MODERATE NETWORKS</text>

            {/* Y axis labels */}
            <text x="70" y="80" fill={DK_FG_MUTED} fontSize="14" letterSpacing="0.2em" textAnchor="end">HIGH</text>
            <text x="70" y="480" fill={DK_FG_MUTED} fontSize="14" letterSpacing="0.2em" textAnchor="end">LOW</text>
            <text x="40" y="280" fill={DK_FG_MUTED} fontSize="14" letterSpacing="0.3em" textAnchor="middle" fontWeight="600" transform="rotate(-90 40 280)">CPU · RAM · DISK</text>
          </svg>
        </div>

        {/* readout removed — chart speaks for itself */}
      </Content>
    </Slide>
  );
}

// ─── SLIDE 10 · THE GOAL ─────────────────────────────────────────
function Slide11_Goal() {
  const traits = [
    {
      label: "EASY",
      tagline: "Deploy in minutes",
      items: [
        "One static binary — `./ludus-server`",
        "Static clients for Win/Linux/macOS",
        "Open source, fully documented",
        "API-driven · cloud or on-prem",
      ],
    },
    {
      label: "FLEXIBLE",
      tagline: "Shape any network",
      items: [
        "Templates are base OS installs",
        "Labs build dynamically at deploy",
        "No \"golden master\" templates required",
        "Domains, IPs, users — all user-declared",
      ],
    },
    {
      label: "EXPANDABLE",
      tagline: "Make it your own",
      items: [
        "Any existing Ansible role works",
        "Write your own · share your own",
        "Chocolatey package-manager support",
        "Integrates with your CI/CD",
      ],
    },
  ];
  return (
    <Slide section="THE SOLUTION">
      <Content>
        <NumberedEyebrow>MISSION</NumberedEyebrow>
        <Title style={{marginTop: 16}}>
          Solve infrastructure management for cybersecurity
        </Title>

        <div style={{flex:1, marginTop: 56, display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap: 28}}>
          {traits.map((p, i) => (
            <div key={i} style={{
              position:"relative", border: `1px solid ${DK_BORDER}`,
              background: "rgba(25,28,34,0.55)",
              padding: "44px 38px",
              display:"flex", flexDirection:"column",
            }}>
              <Brackets size={16} color={GOLD} inset={-1} thickness={1.5}/>
              <div style={{
                fontSize: 180, fontWeight: 700, color: "rgba(197,174,79,0.09)",
                position:"absolute", bottom: -24, right: 20, lineHeight: 0.85,
                fontVariantNumeric: "tabular-nums", letterSpacing: "-0.04em",
                pointerEvents: "none",
              }}>0{i+1}</div>
              <div style={{fontSize: 16, letterSpacing:"0.4em", color: GOLD, fontWeight: 700, position:"relative", zIndex: 2}}>
                {p.label}
              </div>
              <div style={{fontSize: 52, fontWeight: 700, marginTop: 16, letterSpacing:"-0.015em", lineHeight: 1.05}}>
                {p.tagline}
              </div>
              <div style={{height: 1, background: GOLD, width: 56, marginTop: 22}}/>
              <ul style={{listStyle:"none", margin: "28px 0 0", padding: 0, display:"flex", flexDirection:"column", gap: 14}}>
                {p.items.map((it, j)=>(
                  <li key={j} style={{display:"flex", gap: 12, fontSize: 18, color: DK_FG, lineHeight: 1.45}}>
                    <span style={{color: GOLD}}>▸</span>
                    <span dangerouslySetInnerHTML={{__html: it.replace(/`([^`]+)`/g, '<code style="color:'+GOLD+';background:rgba(197,174,79,0.1);padding:1px 6px;font-size:16px">$1</code>')}}/>
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </Content>
    </Slide>
  );
}

// ─── SLIDE 11 · YAML RANGE CONFIG ───────────────────────────────
function Slide12_YAML() {
  const [runKey, setRunKey] = React.useState(0);
  const [userTyped, setUserTyped] = React.useState(0);
  const { slideNo } = React.useContext(DeckCtx);

  // Loop the whole animation sequence while the slide is on screen so the
  // audience can watch one panel, then another, then another. Pause the
  // loop when the slide isn't active to avoid wasted renders.
  const LOOP_MS = 5000;
  React.useEffect(() => {
    const stage = document.querySelector("deck-stage");
    if (!stage) return;
    let intervalId = null;
    const startLoop = () => {
      stopLoop();
      setRunKey((k) => k + 1);
      intervalId = setInterval(() => setRunKey((k) => k + 1), LOOP_MS);
    };
    const stopLoop = () => {
      if (intervalId) { clearInterval(intervalId); intervalId = null; }
    };
    const onChange = (e) => {
      if (e.detail && e.detail.index === slideNo - 1) startLoop();
      else stopLoop();
    };
    stage.addEventListener("slidechange", onChange);
    if (stage.index === slideNo - 1) startLoop();
    return () => {
      stopLoop();
      stage.removeEventListener("slidechange", onChange);
    };
  }, [slideNo]);

  // Per-line tokens so the typewriter can reveal colored text char-by-char.
  const YAML_LINES = [
    [{t:"ludus:"}],
    [{t:"  - "}, {t:"vm_name", c:GOLD}, {t:": "}, {t:"ad-dc-win2022-x64", c:"#98c379"}],
    [{t:"    "}, {t:"hostname", c:GOLD}, {t:": "}, {t:"JD-DC01", c:"#98c379"}],
    [{t:"    "}, {t:"template", c:GOLD}, {t:": "}, {t:"win2022-server-x64", c:"#98c379"}],
    [{t:"    "}, {t:"vlan", c:GOLD}, {t:": "}, {t:"10", c:"#d19a66"}],
    [{t:"    "}, {t:"ram_gb", c:GOLD}, {t:": "}, {t:"8", c:"#d19a66"}],
    [{t:"    "}, {t:"windows", c:GOLD}, {t:":"}],
    [{t:"      "}, {t:"sysprep", c:GOLD}, {t:": "}, {t:"true", c:"#d19a66"}],
    [{t:"      "}, {t:"domain", c:GOLD}, {t:":"}],
    [{t:"        "}, {t:"fqdn", c:GOLD}, {t:": "}, {t:"ludus.domain", c:"#98c379"}],
    [{t:"        "}, {t:"role", c:GOLD}, {t:": "}, {t:"primary-dc", c:"#98c379"}],
    [{t:"  - "}, {t:"vm_name", c:GOLD}, {t:": "}, {t:"ad-win11-22h2", c:"#98c379"}],
    [{t:"    "}, {t:"template", c:GOLD}, {t:": "}, {t:"win11-22h2-x64", c:"#98c379"}],
    [{t:"    "}, {t:"vlan", c:GOLD}, {t:": "}, {t:"10", c:"#d19a66"}],
    [{t:"    "}, {t:"windows", c:GOLD}, {t:":"}],
    [{t:"      "}, {t:"domain", c:GOLD}, {t:":"}],
    [{t:"        "}, {t:"role", c:GOLD}, {t:": "}, {t:"member", c:"#98c379"}],
  ];
  // All three panels animate simultaneously in a ~2.5s burst so the slide
  // reads as "three ways to author a range" rather than a sequential story.
  const START = 0.2;
  const CHAR_S = 0.006;
  const LINE_GAP_S = 0.012;
  let cum = START;
  const yamlTiming = YAML_LINES.map((tokens) => {
    const text = tokens.map((x) => x.t).join("");
    const chars = text.length;
    const dur = Math.max(0.08, chars * CHAR_S);
    const delay = cum;
    cum += dur + LINE_GAP_S;
    return { tokens, chars, dur, delay };
  });
  const yamlEndTime = cum;

  // Panel 2 drag — starts with the others, settles at drop zone
  const P2_START = START;
  const P2_DUR = 2.2;
  const P2_END = P2_START + P2_DUR;

  // Panel 3 chat — all unfolds in parallel with the other two
  const P3_START = START;
  const USER_MSG = "build me an AD lab: 1 DC, 2 Win11 clients, Kali on a separate VLAN — then snapshot everything";
  const USER_CHARS = USER_MSG.length;
  const USER_TYPING_MS_PER_CHAR = 9;
  const USER_TYPING_DUR = (USER_CHARS * USER_TYPING_MS_PER_CHAR) / 1000;
  const USER_TYPING_END = P3_START + USER_TYPING_DUR;
  const TOOLCALLS_START = USER_TYPING_END + 0.2;
  const CONFIRM_START = TOOLCALLS_START + 0.55;

  // JS drives the user message's char-by-char typing so wrapping works.
  React.useEffect(() => {
    setUserTyped(0);
    const timers = [];
    timers.push(setTimeout(() => {
      const id = setInterval(() => {
        setUserTyped((n) => {
          if (n >= USER_CHARS) { clearInterval(id); return n; }
          return n + 1;
        });
      }, USER_TYPING_MS_PER_CHAR);
      timers.push(id);
    }, P3_START * 1000));
    return () => timers.forEach((t) => { clearTimeout(t); clearInterval(t); });
  }, [runKey]);

  return (
    <Slide section="THE SOLUTION">
      <style>{`
        @keyframes s12-typeLine {
          from { width: 0; }
          to   { width: var(--target); }
        }
        @keyframes s12-blink {
          0%, 50%      { opacity: 1; }
          50.01%, 100% { opacity: 0; }
        }
        @keyframes s12-fadeIn {
          from { opacity: 0; transform: translateY(6px); }
          to   { opacity: 1; transform: translateY(0); }
        }
        @keyframes s12-appear {
          from { opacity: 0; }
          to   { opacity: 1; }
        }
        @keyframes s12-ghostPath {
          0%   { offset-distance: 0%;   opacity: 0; transform: rotate(-3deg) scale(1); }
          7%   { opacity: 1; }
          72%  { offset-distance: 100%; opacity: 1; transform: rotate(-3deg) scale(1); }
          82%  { offset-distance: 100%; transform: rotate(0deg) scale(0.94); opacity: 1; }
          90%  { offset-distance: 100%; transform: rotate(0deg) scale(1); opacity: 1; }
          100% { offset-distance: 100%; transform: rotate(0deg) scale(1); opacity: 1; }
        }
        @keyframes s12-cursorPath {
          0%   { offset-distance: 0%;   opacity: 0; }
          7%   { opacity: 1; }
          72%  { offset-distance: 100%; opacity: 1; }
          85%  { opacity: 0; }
          100% { opacity: 0; }
        }
        @keyframes s12-dropFlash {
          0%, 70%, 100% {
            background: rgba(197,174,79,0.04);
            border-color: rgba(197,174,79,0.4);
            box-shadow: none;
          }
          80% {
            background: rgba(197,174,79,0.32);
            border-color: ${GOLD};
            box-shadow: 0 0 22px rgba(197,174,79,0.5);
          }
          95%, 100% {
            background: transparent;
            border-color: transparent;
            box-shadow: none;
          }
        }
        @keyframes s12-trailDraw {
          0%   { stroke-dashoffset: 600; opacity: 0; }
          10%  { opacity: 0.7; }
          72%  { stroke-dashoffset: 0;   opacity: 0.7; }
          88%  { opacity: 0; }
          100% { opacity: 0; }
        }
        @keyframes s12-labelOut {
          0%, 72% { opacity: 1; }
          85%, 100% { opacity: 0; }
        }
        @keyframes s12-labelIn {
          0%, 78% { opacity: 0; }
          92%, 100% { opacity: 1; }
        }
        .s12-run .s12-line {
          position: relative;
          white-space: pre;
          overflow: hidden;
          width: 0;
          animation-name: s12-typeLine;
          animation-fill-mode: forwards;
        }
        .s12-run .s12-yaml-caret {
          display: inline-block;
          width: 10px;
          height: 22px;
          background: ${GOLD};
          box-shadow: 0 0 6px ${GOLD};
          vertical-align: -4px;
          margin-left: 2px;
          opacity: 0;
          animation: s12-appear 0.15s linear ${yamlEndTime}s forwards,
                     s12-blink 0.85s steps(2) ${yamlEndTime}s infinite;
        }
        .s12-run .s12-ghost {
          offset-path: path("M 160 260 C 180 130, 130 30, 0 0");
          offset-rotate: 0deg;
          offset-anchor: 0 0;
          animation: s12-ghostPath ${P2_DUR}s cubic-bezier(.45,.02,.3,1) ${P2_START}s forwards;
        }
        .s12-run .s12-cursor {
          offset-path: path("M 160 260 C 180 130, 130 30, 0 0");
          offset-rotate: 0deg;
          offset-anchor: 0 0;
          animation: s12-cursorPath ${P2_DUR}s cubic-bezier(.45,.02,.3,1) ${P2_START}s forwards;
        }
        .s12-run .s12-drop {
          animation: s12-dropFlash ${P2_DUR}s linear ${P2_START}s forwards;
        }
        .s12-run .s12-trail {
          stroke-dasharray: 600 600;
          stroke-dashoffset: 600;
          animation: s12-trailDraw ${P2_DUR}s cubic-bezier(.45,.02,.3,1) ${P2_START}s forwards;
        }
        .s12-run .s12-drag-label {
          animation: s12-labelOut ${P2_DUR}s linear ${P2_START}s forwards;
        }
        .s12-run .s12-placed-label {
          opacity: 0;
          animation: s12-labelIn ${P2_DUR}s linear ${P2_START}s forwards;
        }

        .s12-run .s12-chat-user {
          opacity: 0;
          animation: s12-fadeIn 0.25s ease-out ${P3_START}s forwards;
        }
        .s12-run .s12-assistant {
          opacity: 0;
          animation: s12-fadeIn 0.25s ease-out ${TOOLCALLS_START}s forwards;
        }
        .s12-run .s12-tool-line {
          opacity: 0;
          animation: s12-fadeIn 0.2s ease-out forwards;
        }
        .s12-run .s12-confirm {
          opacity: 0;
          animation: s12-fadeIn 0.35s ease-out ${CONFIRM_START}s forwards;
        }
        .s12-run .s12-user-caret {
          display: inline-block;
          width: 9px;
          height: 19px;
          background: ${GOLD};
          box-shadow: 0 0 6px ${GOLD};
          vertical-align: -3px;
          margin-left: 3px;
          animation: s12-blink 0.85s steps(2) infinite;
        }
      `}</style>
      <Content>
        <div style={{display:"flex", alignItems:"baseline", justifyContent:"space-between"}}>
          <div>
            <NumberedEyebrow>AUTHOR YOUR RANGE</NumberedEyebrow>
            <Title style={{marginTop: 16}}>Write it. Drag it. Prompt it.</Title>
          </div>
        </div>

        <div key={runKey} className="s12-run" style={{position:"relative", flex: 1, marginTop: 44, display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap: 28}}>

          {/* ==== Panel 1 — YAML typewriter ==== */}
          <div style={{position:"relative", display:"flex", flexDirection:"column", minWidth: 0}}>
            <div style={{
              display:"flex", alignItems:"center", gap: 10,
              padding: "14px 20px", background: "rgba(25,28,34,0.9)",
              border: `1px solid ${DK_BORDER}`, borderBottom: "none",
              fontSize: 14, letterSpacing:"0.3em", color: GOLD,
            }}>
              <span style={{width: 9, height: 9, background: GOLD, borderRadius: "50%", boxShadow:`0 0 6px ${GOLD}`}}/>
              RANGE.YAML
              <span style={{marginLeft: "auto", color: DK_FG_MUTED}}>CONFIG-AS-CODE</span>
            </div>
            <div style={{
              position:"relative",
              border: `1px solid ${DK_BORDER}`,
              background: "#0d1117",
              padding: 26,
              flex: 1,
              fontFamily: "JetBrains Mono, monospace",
              fontSize: 20, lineHeight: 1.5,
              color: DK_FG,
              overflow: "hidden",
            }}>
              <Brackets size={12} color="rgba(197,174,79,0.4)" inset={6}/>
              <div>
                {yamlTiming.map(({ tokens, chars, dur, delay }, i) => (
                  <div
                    key={i}
                    className="s12-line"
                    style={{
                      "--target": `${chars}ch`,
                      animationDuration: `${dur}s`,
                      animationTimingFunction: `steps(${chars})`,
                      animationDelay: `${delay}s`,
                    }}
                  >
                    {tokens.map((tok, j) => (
                      <span key={j} style={tok.c ? { color: tok.c } : undefined}>{tok.t}</span>
                    ))}
                  </div>
                ))}
                <span className="s12-yaml-caret"/>
              </div>
            </div>
          </div>

          {/* ==== Panel 2 — Web UI drag & drop ==== */}
          <div style={{position:"relative", display:"flex", flexDirection:"column", minWidth: 0}}>
            <div style={{
              display:"flex", alignItems:"center", gap: 10,
              padding: "14px 20px", background: "rgba(25,28,34,0.9)",
              border: `1px solid ${DK_BORDER}`, borderBottom: "none",
              fontSize: 14, letterSpacing:"0.3em", color: GOLD,
            }}>
              <span style={{width: 9, height: 9, background: GOLD, borderRadius: "50%", boxShadow:`0 0 6px ${GOLD}`}}/>
              WEB UI
              <span style={{marginLeft: "auto", color: DK_FG_MUTED}}>VISUAL EDITOR</span>
            </div>
            <div style={{
              position:"relative",
              border: `1px solid ${DK_BORDER}`,
              background: "#0d1117",
              flex: 1, minHeight: 0, minWidth: 0,
              overflow: "hidden",
              backgroundImage: `radial-gradient(rgba(197,174,79,0.07) 1px, transparent 1px)`,
              backgroundSize: "22px 22px",
              backgroundPosition: "11px 11px",
            }}>
              <Brackets size={12} color="rgba(197,174,79,0.4)" inset={6}/>

              {/* VLAN 10 swimlane */}
              <div style={{
                position:"absolute", left: 22, right: 22, top: 40, height: "42%",
                border: `1px dashed rgba(197,174,79,0.3)`,
                background: "rgba(197,174,79,0.025)",
              }}>
                <div style={{
                  position:"absolute", top: -11, left: 14,
                  padding: "0 8px", background: "#0d1117",
                  fontFamily: "JetBrains Mono, monospace",
                  fontSize: 15, letterSpacing:"0.3em", color: GOLD,
                }}>VLAN 10 · AD</div>

                <div style={{
                  position:"absolute", left: 16, top: 30,
                  padding: "10px 14px",
                  border: `1px solid ${DK_BORDER}`,
                  background: "rgba(25,28,34,0.85)",
                  fontFamily:"JetBrains Mono, monospace",
                  display:"flex", flexDirection:"column", gap: 4,
                  width: 140,
                }}>
                  <div style={{display:"flex", alignItems:"center", gap: 8}}>
                    <span style={{width: 7, height: 7, background: GOLD, borderRadius:"50%", boxShadow:`0 0 6px ${GOLD}`, flexShrink: 0}}/>
                    <span style={{fontSize: 17, color: DK_FG}}>JD-DC01</span>
                  </div>
                  <div style={{fontSize: 13, color: DK_FG_MUTED, letterSpacing:"0.08em"}}>win2022 · dc</div>
                </div>

                <div style={{
                  position:"absolute", left: 172, top: 30,
                  padding: "10px 14px",
                  border: `1px solid ${DK_BORDER}`,
                  background: "rgba(25,28,34,0.85)",
                  fontFamily:"JetBrains Mono, monospace",
                  display:"flex", flexDirection:"column", gap: 4,
                  width: 140,
                }}>
                  <div style={{display:"flex", alignItems:"center", gap: 8}}>
                    <span style={{width: 7, height: 7, background: "rgba(155,169,180,0.8)", borderRadius:"50%", flexShrink: 0}}/>
                    <span style={{fontSize: 17, color: DK_FG}}>WIN11-01</span>
                  </div>
                  <div style={{fontSize: 13, color: DK_FG_MUTED, letterSpacing:"0.08em"}}>win11 · member</div>
                </div>

                <div className="s12-drop" style={{
                  position:"absolute", left: 328, top: 30,
                  width: 150, height: 60,
                  border: `1px dashed rgba(197,174,79,0.4)`,
                  background: "rgba(197,174,79,0.04)",
                }}/>
              </div>

              {/* VLAN 20 swimlane */}
              <div style={{
                position:"absolute", left: 22, right: 22, top: "56%", bottom: 24,
                border: `1px dashed rgba(197,174,79,0.3)`,
                background: "rgba(197,174,79,0.015)",
              }}>
                <div style={{
                  position:"absolute", top: -11, left: 14,
                  padding: "0 8px", background: "#0d1117",
                  fontFamily: "JetBrains Mono, monospace",
                  fontSize: 15, letterSpacing:"0.3em", color: GOLD,
                }}>VLAN 20 · OFFENSE</div>
                <div style={{
                  position:"absolute", left: 16, top: 30,
                  padding: "10px 14px",
                  border: `1px solid ${DK_BORDER}`,
                  background: "rgba(25,28,34,0.85)",
                  fontFamily:"JetBrains Mono, monospace",
                  display:"flex", flexDirection:"column", gap: 4,
                  width: 140,
                }}>
                  <div style={{display:"flex", alignItems:"center", gap: 8}}>
                    <span style={{width: 7, height: 7, background: "rgba(155,169,180,0.8)", borderRadius:"50%", flexShrink: 0}}/>
                    <span style={{fontSize: 17, color: DK_FG}}>KALI-01</span>
                  </div>
                  <div style={{fontSize: 13, color: DK_FG_MUTED, letterSpacing:"0.08em"}}>kali · attacker</div>
                </div>
                <div style={{
                  position:"absolute", left: 172, top: 30,
                  padding: "10px 14px",
                  border: `1px solid ${DK_BORDER}`,
                  background: "rgba(25,28,34,0.85)",
                  fontFamily:"JetBrains Mono, monospace",
                  display:"flex", flexDirection:"column", gap: 4,
                  width: 140,
                }}>
                  <div style={{display:"flex", alignItems:"center", gap: 8}}>
                    <span style={{width: 7, height: 7, background: "rgba(155,169,180,0.8)", borderRadius:"50%", flexShrink: 0}}/>
                    <span style={{fontSize: 17, color: DK_FG}}>ROUTER</span>
                  </div>
                  <div style={{fontSize: 13, color: DK_FG_MUTED, letterSpacing:"0.08em"}}>debian-12 · gw</div>
                </div>
              </div>

              {/* Drag trail */}
              <svg
                style={{position:"absolute", inset: 0, width:"100%", height:"100%", pointerEvents:"none"}}
                preserveAspectRatio="none"
                viewBox="0 0 500 700"
              >
                <path
                  className="s12-trail"
                  d="M 472 358 C 491 217, 444 108, 324 76"
                  stroke={GOLD}
                  strokeWidth="1.8"
                  fill="none"
                  pathLength="600"
                />
              </svg>

              {/* Ghost / placed VM — base position = drop zone; offset-path carries it there */}
              <div className="s12-ghost" style={{
                position:"absolute", left: 350, top: 70,
                transform: "rotate(-3deg)",
                padding: "10px 14px",
                border: `1px solid ${GOLD}`,
                background: "rgba(25,28,34,0.95)",
                fontFamily:"JetBrains Mono, monospace",
                display:"flex", flexDirection:"column", gap: 4,
                width: 150,
                opacity: 0,
                boxShadow: `0 14px 36px rgba(197,174,79,0.32), 0 0 0 1px ${GOLD}`,
              }}>
                <div style={{display:"flex", alignItems:"center", gap: 8}}>
                  <span style={{width: 7, height: 7, background: GOLD, borderRadius:"50%", boxShadow:`0 0 6px ${GOLD}`, flexShrink: 0}}/>
                  <span style={{fontSize: 17, color: DK_FG}}>WIN11-02</span>
                </div>
                <div style={{position:"relative", fontSize: 13, letterSpacing:"0.08em", minHeight: 16}}>
                  <span className="s12-drag-label" style={{color: GOLD}}>◆ dragging...</span>
                  <span className="s12-placed-label" style={{position:"absolute", left: 0, top: 0, color: DK_FG_MUTED}}>win11 · member</span>
                </div>
              </div>

              {/* Cursor */}
              <svg
                className="s12-cursor"
                style={{position:"absolute", left: 478, top: 116, width: 24, height: 28, pointerEvents:"none", filter:`drop-shadow(0 2px 6px rgba(0,0,0,0.75))`, opacity: 0}}
                viewBox="0 0 20 24"
              >
                <path
                  d="M 2 2 L 2 18 L 6.5 13.5 L 9 19.5 L 11.5 18.5 L 9 12.5 L 15 12.5 Z"
                  fill={GOLD}
                  stroke={DK_BG}
                  strokeWidth="1.2"
                  strokeLinejoin="round"
                />
              </svg>
            </div>
          </div>

          {/* ==== Panel 3 — Chat UI ==== */}
          <div style={{position:"relative", display:"flex", flexDirection:"column", minWidth: 0}}>
            <div style={{
              display:"flex", alignItems:"center", gap: 10,
              padding: "14px 20px", background: "rgba(25,28,34,0.9)",
              border: `1px solid ${DK_BORDER}`, borderBottom: "none",
              fontSize: 14, letterSpacing:"0.3em", color: GOLD,
            }}>
              <span style={{width: 9, height: 9, background: GOLD, borderRadius: "50%", boxShadow:`0 0 6px ${GOLD}`}}/>
              AI · MCP
              <span style={{marginLeft: "auto", color: DK_FG_MUTED}}>CHAT</span>
            </div>
            <div style={{
              position:"relative",
              border: `1px solid ${DK_BORDER}`,
              background: "#0d1117",
              padding: "20px 20px 16px",
              flex: 1, minHeight: 0, minWidth: 0,
              color: DK_FG,
              overflow: "hidden",
              display:"flex", flexDirection:"column", gap: 14,
            }}>
              <Brackets size={12} color="rgba(197,174,79,0.4)" inset={6}/>

              {/* Messages area */}
              <div style={{flex:1, display:"flex", flexDirection:"column", gap: 14, overflow:"hidden", minHeight: 0}}>

                {/* User message — right-aligned */}
                <div className="s12-chat-user" style={{display:"flex", justifyContent:"flex-end"}}>
                  <div style={{
                    maxWidth: "88%",
                    padding: "11px 15px",
                    background: "rgba(197,174,79,0.14)",
                    border: `1px solid rgba(197,174,79,0.35)`,
                    borderRadius: "14px 14px 4px 14px",
                    fontSize: 19, lineHeight: 1.45,
                    color: DK_FG,
                    fontFamily: `-apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif`,
                  }}>
                    {USER_MSG.slice(0, userTyped)}
                    {userTyped < USER_CHARS && <span className="s12-user-caret"/>}
                  </div>
                </div>

                {/* Assistant bubble — shows thinking dots, then swaps to tool calls */}
                <div className="s12-assistant" style={{display:"flex", alignItems:"flex-start", gap: 10}}>
                  <div style={{
                    width: 30, height: 30, flexShrink: 0,
                    borderRadius: "50%",
                    background: "rgba(197,174,79,0.18)",
                    border: `1px solid ${GOLD}`,
                    display:"flex", alignItems:"center", justifyContent:"center",
                    fontSize: 14, fontWeight: 700, color: GOLD,
                    fontFamily: "JetBrains Mono, monospace",
                  }}>L</div>
                  <div style={{flex: 1, minWidth: 0}}>
                    <div style={{fontSize: 14, color: GOLD, letterSpacing: "0.2em", marginBottom: 5}}>LUDUS-MCP</div>
                    <div style={{
                      padding: "10px 14px",
                      background: "#0a0c10",
                      border: `1px solid ${DK_BORDER}`,
                      borderRadius: "12px 12px 12px 4px",
                      fontSize: 17, lineHeight: 1.65,
                      fontFamily: "JetBrains Mono, monospace",
                      display:"flex", flexDirection:"column", gap: 3,
                      color: DK_FG,
                    }}>
                      <div className="s12-tool-line" style={{animationDelay: `${TOOLCALLS_START + 0.05}s`}}>
                        <span style={{color: GOLD}}>→</span> <span style={{color:"#d19a66"}}>range.config</span>.set(<span style={{color:"#98c379"}}>ad-lab.yaml</span>)
                      </div>
                      <div className="s12-tool-line" style={{animationDelay: `${TOOLCALLS_START + 0.18}s`}}>
                        <span style={{color: GOLD}}>→</span> <span style={{color:"#d19a66"}}>range</span>.deploy()
                      </div>
                      <div className="s12-tool-line" style={{animationDelay: `${TOOLCALLS_START + 0.31}s`}}>
                        <span style={{color: GOLD}}>→</span> <span style={{color:"#d19a66"}}>snapshot</span>.create(<span style={{color:"#98c379"}}>all-vms</span>)
                      </div>
                    </div>
                  </div>
                </div>

                {/* Assistant: confirmation */}
                <div className="s12-confirm" style={{display:"flex", alignItems:"flex-start", gap: 10}}>
                  <div style={{
                    width: 30, height: 30, flexShrink: 0,
                    borderRadius: "50%",
                    background: "rgba(152,195,121,0.15)",
                    border: `1px solid #98c379`,
                    display:"flex", alignItems:"center", justifyContent:"center",
                    fontSize: 17, fontWeight: 700, color: "#98c379",
                  }}>✓</div>
                  <div style={{flex: 1, minWidth: 0}}>
                    <div style={{fontSize: 14, color: DK_FG_MUTED, letterSpacing: "0.2em", marginBottom: 5}}>STATUS</div>
                    <div style={{
                      padding: "10px 14px",
                      background: "rgba(25,28,34,0.75)",
                      border: `1px solid ${DK_BORDER}`,
                      borderRadius: "12px 12px 12px 4px",
                      fontSize: 17, lineHeight: 1.55,
                      fontFamily: `-apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif`,
                      display:"flex", flexDirection:"column", gap: 3,
                    }}>
                      <div><span style={{color:"#98c379"}}>✓</span> range deployed · 4 VMs across 2 VLANs</div>
                      <div><span style={{color:"#98c379"}}>✓</span> snapshot <span style={{color: GOLD}}>pre-engagement</span> saved</div>
                    </div>
                  </div>
                </div>

              </div>

              {/* Input bar */}
              <div style={{
                display:"flex", alignItems:"center", gap: 10,
                padding: "11px 14px",
                background: "rgba(25,28,34,0.85)",
                border: `1px solid ${DK_BORDER}`,
                borderRadius: 10,
                fontSize: 15, color: DK_FG_MUTED,
                fontFamily: `-apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif`,
              }}>
                <span style={{color: GOLD, fontSize: 17, fontFamily:"JetBrains Mono, monospace"}}>›</span>
                <span style={{flex: 1}}>Message Ludus…</span>
                <span style={{
                  width: 28, height: 28, borderRadius: 6,
                  background: "rgba(197,174,79,0.18)",
                  border: `1px solid ${GOLD}`,
                  display:"flex", alignItems:"center", justifyContent:"center",
                  color: GOLD, fontSize: 13,
                }}>↵</span>
              </div>

            </div>
          </div>
        </div>
      </Content>
    </Slide>
  );
}

// ─── SLIDE 12 · ROLES ────────────────────────────────────────────
function Slide13_Roles() {
  // First-party + enterprise roles merged — all shipped or sold by BSL
  const firstParty = [
    {repo:"ludus_adcs",                 who:"BSL"},
    {repo:"ludus_bloodhound_ce",        who:"BSL"},
    {repo:"ludus_mssql",                who:"BSL"},
    {repo:"ludus_elastic_container",    who:"BSL"},
    {repo:"ludus_elastic_agent",        who:"BSL"},
    {repo:"ludus_vulhub",               who:"BSL"},
    {repo:"ludus_commandovm",           who:"BSL"},
    {repo:"ludus_flarevm",              who:"BSL"},
    {repo:"ludus_remnux",               who:"BSL"},
    {repo:"ludus_emux",                 who:"BSL"},
    {repo:"ludus_xz_backdoor",          who:"BSL"},
    {repo:"ludus_adaptix_c2",           who:"BSL"},
    {repo:"ludus_defender_for_endpoint",who:"BSL · enterprise"},
    {repo:"ludus_google_secops",        who:"BSL · enterprise"},
    {repo:"ludus_mythic_c2",            who:"BSL · enterprise"},
    {repo:"ludus_sysmon",               who:"BSL · enterprise"},
    {repo:"ludus_guacamole_server",     who:"BSL · enterprise"},
    {repo:"ludus_guacamole_client",     who:"BSL · enterprise"},
    {repo:"ludus_bulk_ad_content",      who:"BSL · enterprise"},
    {repo:"ludus_unconstrained_deleg",  who:"BSL · enterprise"},
    {repo:"ludus_zeek",                 who:"BSL · enterprise"},
    {repo:"ludus_ghosts_client",        who:"BSL · enterprise"},
    {repo:"ludus_smb_shares",           who:"BSL · enterprise"},
    {repo:"ludus_windows_wallpaper",    who:"BSL · enterprise"},
    {repo:"ludus_ad_acls",              who:"BSL"},
    {repo:"ludus_ad_anonymous_rpc",     who:"BSL"},
    {repo:"ludus_ad_forest_trust",      who:"BSL"},
    {repo:"ludus_ad_gmsa",              who:"BSL"},
    {repo:"ludus_ad_laps",              who:"BSL"},
    {repo:"ludus_ad_misconfigs",        who:"BSL"},
    {repo:"ludus_ad_password_policy",   who:"BSL"},
    {repo:"ludus_child_domain",         who:"BSL"},
    {repo:"ludus_child_domain_join",    who:"BSL"},
    {repo:"ludus_files",                who:"BSL"},
    {repo:"ludus_iis_webdav",           who:"BSL"},
  ];
  // Community roles (from Discord-tracked releases, 2024-03 → 2026-04)
  const community = [
    {repo:"ludus-gitlab-ce",            who:"@tigrebleu"},
    {repo:"ludus-ad-content",           who:"@tigrebleu"},
    {repo:"ludus-local-users",          who:"@tigrebleu"},
    {repo:"ludus_exchange",             who:"@aleemladha"},
    {repo:"ludus_wazuh_agent",          who:"@aleemladha"},
    {repo:"ludus_velociraptor_server",  who:"@f_Murer"},
    {repo:"ludus_velociraptor_client",  who:"@f_Murer"},
    {repo:"ludus_tailscale",            who:"@__Mastadon"},
    {repo:"ludus_adfs",                 who:"@bagelByt3s"},
    {repo:"ludus-ad-vulns",             who:"@Primusinterp"},
    {repo:"ludus_juiceshop",            who:"@xurger"},
    {repo:"ludus-ranges",               who:"@dfirjesseee"},
    {repo:"ludus_install_spice",        who:"@crokrodile"},
    {repo:"ludus_exegol",               who:"@hugojcqs"},
    {repo:"ludus_ghosts_server",        who:"@frack113"},
    {repo:"ludus_filigran_opencti",     who:"@frack113"},
    {repo:"ludus_docker_engine",        who:"@frack113"},
    {repo:"ludus_rustdesk",             who:"@beardhammer"},
    {repo:"ludus_sliver",               who:"@netpenguins"},
    {repo:"ludus_redirector",           who:"@netpenguins"},
    {repo:"ludus_k3s",                  who:"@netpenguins"},
    {repo:"ludus_fake_configs",         who:"@mojeda101"},
    {repo:"ludus_adtimeline_syncthing", who:"@mojeda101"},
    {repo:"ludus_veeam_vbr",            who:"@mojeda101"},
    {repo:"ludus_ad_group_all",         who:"@mojeda101"},
    {repo:"ludus_win_privesc",          who:"@mojeda101"},
    {repo:"ludus_win_rearm",            who:"@mojeda101"},
    {repo:"ludus_kali_setup",           who:"@mojeda101"},
    {repo:"ludus_litterbox_role",       who:"@professor-moody"},
    {repo:"ludus_badblood",             who:"@curi0usJack"},
    {repo:"ludus_wsus",                 who:"@stuk0v_"},
    {repo:"ludus_vectr",                who:"@stuk0v_"},
    {repo:"ludus_ghostwriter",          who:"@stuk0v_"},
    {repo:"ludus_nemesis",              who:"@_brmkit"},
    {repo:"ludus_guacamole",            who:"@_brmkit"},
    {repo:"ludus-fastmcp",              who:"@tjnull"},
    {repo:"ludus_nginx_redirector",     who:"@hunterino_"},
    {repo:"ludus_forgejo",              who:"@hunterino_"},
    {repo:"ludus-defender-lab",         who:"@ischyr"},
    {repo:"ludus_kubernetes_goat",      who:"@bu5h-d"},
    {repo:"ludus_maldev_tools",         who:"@stishy"},
    {repo:"ludus_sccm",                 who:"@synzack"},
    {repo:"ludus_empire",               who:"@dfirjesseee"},
    {repo:"ansible-role-sccm-agent",    who:"@globalbao"},
    {repo:"ansible-role-dotnet48",      who:"@deekayen"},
    {repo:"ansible-role-winlogbeat",    who:"@j91321"},
    {repo:"ansible_role_wsus_server",   who:"@gamethis"},
    {repo:"ansible-role-squid",         who:"@robertdebock"},
  ];
  // Ansible Galaxy — representative, "and 40,000 more"
  const galaxy = [
    {repo:"geerlingguy.docker",         who:"galaxy"},
    {repo:"community.general",          who:"galaxy"},
    {repo:"ansible.posix",              who:"galaxy"},
    {repo:"ansible.windows",            who:"galaxy"},
    {repo:"redhat.rhel_system_roles",   who:"galaxy"},
    {repo:"elastic.elasticsearch",      who:"galaxy"},
    {repo:"kubernetes.core",            who:"galaxy"},
    {repo:"community.vmware",           who:"galaxy"},
    {repo:"cisco.ios",                  who:"galaxy"},
    {repo:"fortinet.fortios",           who:"galaxy"},
    {repo:"paloaltonetworks.panos",     who:"galaxy"},
    {repo:"arista.eos",                 who:"galaxy"},
  ];

  // Compose three marquee rows, each interleaving provenance so every row feels varied
  const interleave = (...lists) => {
    const out = [];
    const maxLen = Math.max(...lists.map(l => l.length));
    for (let i = 0; i < maxLen; i++) {
      for (const l of lists) if (l[i]) out.push(l[i]);
    }
    return out;
  };
  const row1 = interleave(firstParty.slice(0, 12), community.slice(0, 16), galaxy.slice(0, 4));
  const row2 = interleave(community.slice(16, 32), firstParty.slice(12, 24), galaxy.slice(4, 8));
  const row3 = interleave(community.slice(32), firstParty.slice(24), galaxy.slice(8));

  const tint = (who) => {
    if (who === "galaxy") return {dot: "rgba(155,169,180,0.7)", border: DK_BORDER, bg: "rgba(25,28,34,0.55)"};
    if (who.startsWith("BSL")) return {dot: GOLD, border: "rgba(197,174,79,0.45)", bg: "rgba(197,174,79,0.06)"};
    return {dot: "rgba(197,174,79,0.55)", border: DK_BORDER, bg: "rgba(25,28,34,0.55)"};
  };

  const Chip = ({r}) => {
    const t = tint(r.who);
    return (
      <div style={{
        display:"inline-flex", alignItems:"center", gap: 10,
        padding:"10px 16px", border:`1px solid ${t.border}`, background: t.bg,
        fontFamily:"'JetBrains Mono', ui-monospace, monospace", fontSize: 15,
        whiteSpace:"nowrap", flexShrink: 0,
      }}>
        <span style={{width: 6, height: 6, background: t.dot, borderRadius:"50%", boxShadow:`0 0 6px ${t.dot}`}}/>
        <span style={{color: DK_FG}}>{r.repo}</span>
        <span style={{color: DK_FG_MUTED, fontSize: 14, letterSpacing:"0.04em"}}>{r.who}</span>
      </div>
    );
  };

  const Row = ({items, speed, reverse = false}) => (
    <div style={{overflow:"hidden", position:"relative", maskImage:"linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent)", WebkitMaskImage:"linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent)"}}>
      <div style={{
        display:"flex", gap: 14, whiteSpace:"nowrap", width:"max-content",
        animation:`ludus-marquee ${speed}s linear infinite`,
        animationDirection: reverse ? "reverse" : "normal",
      }}>
        {[...items, ...items, ...items].map((r, i) => <Chip key={i} r={r}/>)}
      </div>
    </div>
  );

  const uniqueAuthors = new Set(community.map(c => c.who)).size;
  const totalRoles = firstParty.length + community.length;

  return (
    <Slide section="THE SOLUTION">
      <style>{`
        @keyframes ludus-marquee {
          from { transform: translateX(0); }
          to   { transform: translateX(-33.3333%); }
        }
      `}</style>
      <Content>
        <div style={{display:"flex", alignItems:"baseline", justifyContent:"space-between"}}>
          <div>
            <NumberedEyebrow>EXTENSIBILITY</NumberedEyebrow>
            <Title style={{marginTop: 16}}>Extending Ludus is never the bottleneck</Title>
            <div style={{fontSize: TS.body, color: DK_FG_MUTED, marginTop: 14, maxWidth: 1200}}>
              Ansible roles — shipped by BSL, built by the community, or pulled from Galaxy.
            </div>
          </div>
          <img src="assets/ansible-logo.png" alt="Ansible" style={{height: 96, filter:"brightness(1.2)"}}/>
        </div>

        {/* Stats band */}
        <div style={{
          marginTop: 36, display:"grid", gridTemplateColumns:"1fr 1fr 1fr 1fr",
          border: `1px solid ${DK_BORDER}`, background: "rgba(20,22,28,0.5)",
        }}>
          {[
            {n: `${firstParty.length}`,    label:"ROLES SHIPPED BY BSL",      sub:"first-party + enterprise"},
            {n: `${community.length}+`,    label:"COMMUNITY ROLES",           sub:`${uniqueAuthors}+ contributors`},
            {n: `37k`,                     label:"ANSIBLE GALAXY",            sub:"bring anything · no wrapper"},
            {n: `${totalRoles}+`,          label:"LUDUS-SPECIFIC ROLES",      sub:"and growing every week"},
          ].map((s, i) => (
            <div key={i} style={{
              padding: "18px 22px",
              borderRight: i < 3 ? `1px solid ${DK_BORDER}` : "none",
              display:"flex", flexDirection:"column", gap: 4,
            }}>
              <div style={{fontSize: 44, fontWeight: 700, color: GOLD, lineHeight: 1, fontFamily:"'JetBrains Mono', ui-monospace, monospace"}}>{s.n}</div>
              <div style={{fontSize: 16, letterSpacing:"0.25em", color: DK_FG, marginTop: 10, fontWeight: 700}}>{s.label}</div>
              <div style={{fontSize: 18, color: DK_FG_MUTED, fontWeight: 500}}>{s.sub}</div>
            </div>
          ))}
        </div>

        {/* Rotating carousel — three rows, different speeds and directions */}
        <div style={{flex: 1, marginTop: 22, display:"flex", flexDirection:"column", gap: 14, justifyContent:"center", minHeight: 0}}>
          <Row items={row1} speed={140}/>
          <Row items={row2} speed={180} reverse/>
          <Row items={row3} speed={120}/>
        </div>

        {/* Legend — sits directly underneath the scrolling panel so the
            dot colors key straight into the rows above. */}
        <div style={{marginTop: 4, display:"flex", alignItems:"center", gap: 24, fontSize: 13, letterSpacing:"0.25em", color: DK_FG_MUTED, fontWeight: 600}}>
          <span style={{display:"inline-flex", alignItems:"center", gap: 8}}>
            <span style={{width: 9, height: 9, background: GOLD, borderRadius:"50%", boxShadow:`0 0 6px ${GOLD}`}}/>
            BSL
          </span>
          <span style={{display:"inline-flex", alignItems:"center", gap: 8}}>
            <span style={{width: 9, height: 9, background: "rgba(197,174,79,0.55)", borderRadius:"50%"}}/>
            COMMUNITY
          </span>
          <span style={{display:"inline-flex", alignItems:"center", gap: 8}}>
            <span style={{width: 9, height: 9, background: "rgba(155,169,180,0.75)", borderRadius:"50%"}}/>
            GALAXY
          </span>
        </div>
      </Content>
    </Slide>
  );
}

// ─── Use-case slide template ────────────────────────────────────
// The giant 0{caseNo} numeral that used to anchor the top-left was
// replaced with the use-case motif icon — the icon now carries the
// visual weight the number used to, while the case-number lives in
// the eyebrow and section chrome.
function UseCaseSlide({ caseNo, titleWord, subtitle, problem, solution, motif }) {
  return (
    <Slide section={`USE CASE · ${caseNo}/06`}>
      <Content>
        <div style={{display:"flex", alignItems:"flex-start", justifyContent:"space-between", marginBottom: 20}}>
          <div style={{display:"flex", flexDirection:"column"}}>
            <Eyebrow>// USE CASE {String(caseNo).padStart(2,"0")}</Eyebrow>
            <div style={{display:"flex", alignItems:"center", gap: 32, marginTop: 16}}>
              {motif && (
                <div style={{
                  width: 128, height: 128,
                  border: `1.5px solid ${GOLD}`,
                  background: "rgba(197,174,79,0.08)",
                  display:"flex", alignItems:"center", justifyContent:"center",
                  color: GOLD, position: "relative",
                  flexShrink: 0,
                }}>
                  <Brackets size={14} color="rgba(197,174,79,0.7)" inset={-1} thickness={1.4}/>
                  <div style={{transform: "scale(2.2)"}}>{motif}</div>
                </div>
              )}
              <div>
                <Title size={64}>{titleWord}</Title>
                <div style={{fontSize: 24, color: DK_FG_MUTED, marginTop: 12, letterSpacing:"0.08em", fontWeight: 500}}>
                  {subtitle}
                </div>
              </div>
            </div>
          </div>
          <div style={{textAlign:"right", fontSize: 13, letterSpacing:"0.3em", color: DK_FG_MUTED, fontWeight: 600, paddingTop: 8}}>
            APPLIES TO<br/>
            <span style={{color: GOLD, fontSize: 16, fontWeight: 700}}>{problem.applies}</span>
          </div>
        </div>

        <div style={{flex: 1, display:"grid", gridTemplateColumns:"1fr 1fr", gap: 36, marginTop: 28}}>
          {/* Problem */}
          <div style={{
            position:"relative",
            border: `1px solid ${CRIMSON}`,
            background: "rgba(20,22,28,0.7)",
            padding: "32px 36px",
            display:"flex", flexDirection:"column",
            boxShadow: `0 0 30px rgba(233,69,96,0.15)`,
          }}>
            <Brackets size={14} color={CRIMSON} inset={-1} thickness={1.5}/>
            <div style={{display:"flex", alignItems:"center", gap: 14, paddingBottom: 18, borderBottom: `1px solid ${CRIMSON}`}}>
              <div style={{
                width: 40, height: 40,
                background: CRIMSON,
                display:"flex", alignItems:"center", justifyContent:"center",
                fontSize: 24, color: DK_BG, fontWeight: 700,
              }}>×</div>
              <div>
                <div style={{fontSize: 13, letterSpacing:"0.35em", color: CRIMSON, fontWeight: 700}}>PROBLEM</div>
                <div style={{fontSize: 23, color: DK_FG, marginTop: 3, lineHeight: 1.3, fontWeight: 600}}>{problem.headline}</div>
              </div>
            </div>
            <ul style={{margin: "24px 0 0", padding: 0, listStyle:"none", display:"flex", flexDirection:"column", gap: 18}}>
              {problem.items.map((it, i)=>(
                <li key={i} style={{display:"flex", gap: 14, fontSize: 22, color: DK_FG, lineHeight: 1.5, fontWeight: 500}}>
                  <span style={{color: CRIMSON, fontSize: 20, paddingTop: 4, fontWeight: 700}}>▸</span>
                  <span dangerouslySetInnerHTML={{__html: it}}/>
                </li>
              ))}
            </ul>
            <div style={{flex: 1}}/>
            <div style={{
              marginTop: 24, paddingTop: 16, borderTop: `1px dashed rgba(233,69,96,0.4)`,
              display:"flex", justifyContent:"space-between", alignItems:"center",
              fontSize: 14, letterSpacing:"0.28em", color: CRIMSON, fontWeight: 700,
            }}>
              <span>STATUS · UNSOLVED</span>
              <span style={{color: DK_FG_MUTED, fontWeight: 600}}>{problem.cost || "COST · TIME + RISK"}</span>
            </div>
          </div>

          {/* Solution */}
          <div style={{
            position:"relative",
            border: `1px solid ${GOLD}`,
            background: "rgba(20,22,28,0.7)",
            padding: "32px 36px",
            display:"flex", flexDirection:"column",
            boxShadow: `0 0 30px rgba(197,174,79,0.12)`,
          }}>
            <Brackets size={14} color={GOLD} inset={-1} thickness={1.5}/>
            <div style={{display:"flex", alignItems:"center", gap: 14, paddingBottom: 18, borderBottom: `1px solid ${GOLD}`}}>
              <div style={{
                width: 40, height: 40,
                background: GOLD,
                display:"flex", alignItems:"center", justifyContent:"center",
                color: DK_BG,
              }}>
                <svg width="20" height="20" viewBox="0 0 18 18" fill="none" stroke={DK_BG} strokeWidth="2.4"><path d="M3 9l4 4 8-9"/></svg>
              </div>
              <div>
                <div style={{fontSize: 13, letterSpacing:"0.35em", color: GOLD, fontWeight: 700}}>SOLUTION · LUDUS</div>
                <div style={{fontSize: 23, color: DK_FG, marginTop: 3, lineHeight: 1.3, fontWeight: 600}}>{solution.headline}</div>
              </div>
            </div>
            <ul style={{margin: "24px 0 0", padding: 0, listStyle:"none", display:"flex", flexDirection:"column", gap: 18}}>
              {solution.items.map((it, i)=>(
                <li key={i} style={{display:"flex", gap: 14, fontSize: 22, color: DK_FG, lineHeight: 1.5, fontWeight: 500}}>
                  <span style={{color: GOLD, fontSize: 20, paddingTop: 4, fontWeight: 700}}>▸</span>
                  <span dangerouslySetInnerHTML={{__html: it}}/>
                </li>
              ))}
            </ul>
            <div style={{flex: 1}}/>
            <div style={{
              marginTop: 24, paddingTop: 16, borderTop: `1px dashed rgba(197,174,79,0.35)`,
              display:"flex", justifyContent:"space-between", alignItems:"center",
              fontSize: 14, letterSpacing:"0.28em", color: GOLD, fontWeight: 700,
            }}>
              <span>STATUS · SHIPPING</span>
              <span style={{color: DK_FG_MUTED, fontWeight: 600}}>{solution.outcome || "OUTCOME · MINUTES"}</span>
            </div>
          </div>
        </div>
      </Content>
    </Slide>
  );
}

// ─── Use-case motif glyphs ──────────────────────────────────────
const MOTIF_OCO = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <circle cx="20" cy="20" r="14"/>
    <circle cx="20" cy="20" r="8"/>
    <circle cx="20" cy="20" r="2" fill="currentColor"/>
    <line x1="20" y1="4" x2="20" y2="10"/><line x1="20" y1="30" x2="20" y2="36"/>
    <line x1="4" y1="20" x2="10" y2="20"/><line x1="30" y1="20" x2="36" y2="20"/>
  </svg>
);
const MOTIF_REDTEAM = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <path d="M20 4 L33 11 L33 22 C33 29 27 34 20 36 C13 34 7 29 7 22 L7 11 Z"/>
    <path d="M14 20 L18 24 L26 15"/>
  </svg>
);
const MOTIF_AI = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <circle cx="10" cy="10" r="3"/><circle cx="30" cy="10" r="3"/>
    <circle cx="10" cy="30" r="3"/><circle cx="30" cy="30" r="3"/>
    <circle cx="20" cy="20" r="4"/>
    <line x1="13" y1="11" x2="17" y2="18"/><line x1="27" y1="11" x2="23" y2="18"/>
    <line x1="13" y1="29" x2="17" y2="22"/><line x1="27" y1="29" x2="23" y2="22"/>
  </svg>
);
const MOTIF_THREATEM = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <rect x="6" y="6" width="12" height="12"/>
    <rect x="22" y="6" width="12" height="12"/>
    <rect x="6" y="22" width="12" height="12"/>
    <rect x="22" y="22" width="12" height="12"/>
    <line x1="18" y1="12" x2="22" y2="12"/>
    <line x1="12" y1="18" x2="12" y2="22"/>
    <line x1="28" y1="18" x2="28" y2="22"/>
    <line x1="18" y1="28" x2="22" y2="28"/>
  </svg>
);
const MOTIF_VULN = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <path d="M6 20 h8 l3 -8 l6 16 l3 -8 h8"/>
    <circle cx="6" cy="20" r="1.5" fill="currentColor"/>
    <circle cx="34" cy="20" r="1.5" fill="currentColor"/>
  </svg>
);
// Gym motif — agent in the centre of a bounded environment, radiating
// out along exploration paths to corner targets. Placeholder; iterate.
const MOTIF_GYM = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <rect x="5" y="5" width="30" height="30"/>
    <circle cx="20" cy="20" r="3.5" fill="currentColor" stroke="none"/>
    <path d="M20 16.5 L20 9 M20 23.5 L20 31 M16.5 20 L9 20 M23.5 20 L31 20"/>
    <circle cx="9" cy="9" r="1.8"/>
    <circle cx="31" cy="9" r="1.8"/>
    <circle cx="9" cy="31" r="1.8"/>
    <circle cx="31" cy="31" r="1.8"/>
  </svg>
);
const MOTIF_INTEL = (
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" stroke="currentColor" strokeWidth="1.5">
    <circle cx="17" cy="17" r="10"/>
    <line x1="25" y1="25" x2="34" y2="34"/>
    <line x1="13" y1="17" x2="21" y2="17"/>
    <line x1="17" y1="13" x2="17" y2="21"/>
  </svg>
);

// ─── SLIDE 13B · USE CASES OVERVIEW ──────────────────────────────
const OVERVIEW_ROWS = [
  { n: 1, name: "Offensive Cyber Ops",     outcome: "Ship tools with their test range built-in", motif: MOTIF_OCO },
  { n: 2, name: "Red Team Infrastructure", outcome: "One isolated range per engagement",         motif: MOTIF_REDTEAM },
  { n: 3, name: "AI & Model Training",     outcome: "Clean-room training data on demand",        motif: MOTIF_AI },
  { n: 4, name: "Threat Emulation",        outcome: "Realistic enterprises in minutes, not days", motif: MOTIF_THREATEM },
  { n: 5, name: "Vuln Scan Testing",       outcome: "CI/CD for detection logic",                 motif: MOTIF_VULN },
  { n: 6, name: "Threat Intelligence",     outcome: "A digital twin attackers can't detect",     motif: MOTIF_INTEL },
];

function Slide13b_Overview() {
  return (
    <Slide section="USE CASES · OVERVIEW">
      <Content>
        <div style={{marginBottom: 32}}>
          <Eyebrow>// COMMON USE CASES</Eyebrow>
          <Title size={84} style={{marginTop: 16}}>
            Six use cases. <span style={{color: GOLD}}>One platform.</span>
          </Title>
        </div>

        <div style={{
          flex: 1, display: "flex", flexDirection: "column",
          justifyContent: "space-around",
        }}>
          {OVERVIEW_ROWS.map(row => (
            <div key={row.n} style={{
              display: "flex", alignItems: "center", gap: 32,
              paddingBottom: 14,
              borderBottom: `1px solid ${DK_BORDER_SOFT}`,
            }}>
              {/* Icon slot — bracketed placeholder; custom icon goes here */}
              <div style={{
                width: 84, height: 84,
                border: `1px solid ${GOLD}`,
                background: "rgba(197,174,79,0.06)",
                display: "flex", alignItems: "center", justifyContent: "center",
                color: GOLD, flexShrink: 0, position: "relative",
              }}>
                <Brackets size={10} color="rgba(197,174,79,0.7)" inset={-1} thickness={1.2}/>
                <div style={{transform: "scale(1.6)"}}>{row.motif}</div>
              </div>
              {/* Name + outcome stacked together as label + sublabel */}
              <div style={{display: "flex", flexDirection: "column", gap: 6, flex: 1, minWidth: 0}}>
                <div style={{
                  fontSize: 32, fontWeight: 700, color: DK_FG,
                  letterSpacing: "0.05em", textTransform: "uppercase",
                  lineHeight: 1,
                }}>
                  {row.name}
                </div>
                <div style={{
                  fontSize: 22, color: DK_FG_MUTED, lineHeight: 1.3,
                  fontWeight: 500,
                }}>
                  {row.outcome}
                </div>
              </div>
            </div>
          ))}
        </div>
      </Content>
    </Slide>
  );
}

// ─── SLIDE 13 · OCO ──────────────────────────────────────────────
function Slide14_OCO() {
  return <UseCaseSlide
    caseNo={1}
    motif={MOTIF_OCO}
    titleWord="Offensive Cyber Operations"
    subtitle="Operators who need a safe test range — offline"
    problem={{
      applies: "OCO · TOOL DEV · TOOL TESTING",
      headline: "Operators need a place to test complex tools safely",
      cost: "COST · HOURS PER TOOL",
      items: [
        "Complex software is hard or time-consuming to set up",
        "Person-hours spent on lab plumbing — not on stakeholder value",
        "On-prem requirement to protect <span style='color:#c5ae4f'>equities</span>",
        "Harder to test ⇒ more <span style='color:#e94560'>YOLO</span> ⇒ operational risk",
      ],
    }}
    solution={{
      headline: "Ship tools with their test range built-in",
      outcome: "SPINUP · 2 COMMANDS",
      items: [
        "Every tool can ship with a Ludus range config for testing",
        "Operators spin up test envs with <code style='color:#c5ae4f'>2 commands</code>",
        "On-prem, behind-the-firewall deployment by default",
        "Range state is reproducible — no \"works on my box\"",
      ],
    }}
  />;
}

// ─── SLIDE 14 · RED TEAMS ────────────────────────────────────────
function Slide15_RedTeam() {
  return <UseCaseSlide
    caseNo={2}
    motif={MOTIF_REDTEAM}
    titleWord="Red Team Infrastructure"
    subtitle="Prevent cross-contamination of customer data"
    problem={{
      applies: "RED TEAM · CONSULTING",
      headline: "RT infrastructure is complex and manually deployed",
      cost: "COST · DAYS PER CUSTOMER",
      items: [
        "Every engagement needs separate, isolated infrastructure",
        "Team-hours burned on setup — not on assessing customers",
        "Cloud tools expose sensitive customer data to the provider",
        "KRBTGT hashes on AWS, <span style='color:#e94560'>third party risk</span>",
        "No test env ⇒ assessors use local VMs or none at all",
      ],
    }}
    solution={{
      headline: "A \"range\" for every engagement",
      outcome: "OUTCOME · ONE RANGE PER CUSTOMER",
      items: [
        "Dedicated red-team infrastructure range per customer",
        "Cloud connectors for AWS / Azure / GCP / redirectors",
        "Customer data stays on hardware <span style='color:#c5ae4f'>you own</span>",
        "Less setup ⇒ more assessing ⇒ more customer value",
        "Testing mode enables seamless <strong>OPSEC</strong>",
      ],
    }}
  />;
}

Object.assign(window, {
  Slide09_Disadvantages, Slide10_Asymmetry, Slide11_Goal, Slide12_YAML,
  Slide13_Roles, Slide13b_Overview, Slide14_OCO, Slide15_RedTeam, UseCaseSlide,
  MOTIF_AI, MOTIF_THREATEM, MOTIF_VULN, MOTIF_INTEL, MOTIF_GYM,
});
