// LanDocs landing page — mini feature demos.
// Each renders an SVG scene scaled to fill its container, with a small
// cursor-driven animation that loops continuously.

// ─── Loop hook ───────────────────────────────────────────────────────────
function useLoopTime(duration) {
  const [t, setT] = React.useState(0);
  React.useEffect(() => {
    let raf;
    let start = null;
    const step = (now) => {
      if (start == null) start = now;
      setT(((now - start) / 1000) % duration);
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [duration]);
  return t;
}

const _lerp = (a, b, t) => a + (b - a) * t;
const _easeInOut = (t) => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3) / 2;
const _easeOut = (t) => 1 - Math.pow(1 - t, 3);

// Interpolate a cursor through keyframes [{t,x,y}, ...].
function _interp(kfs, t) {
  if (t <= kfs[0].t) return { x: kfs[0].x, y: kfs[0].y };
  for (let i = 0; i < kfs.length - 1; i++) {
    const a = kfs[i], b = kfs[i+1];
    if (t >= a.t && t <= b.t) {
      const e = _easeInOut(Math.min(1, Math.max(0, (t - a.t) / (b.t - a.t))));
      return { x: _lerp(a.x, b.x, e), y: _lerp(a.y, b.y, e) };
    }
  }
  const last = kfs[kfs.length-1];
  return { x: last.x, y: last.y };
}

function _isClicking(events, t) {
  for (const e of events) if (t >= e.t && t < e.t + e.dur) return true;
  return false;
}

// A small inline cursor SVG (renders at viewBox coords).
function MiniCursor({ x, y, clicking }) {
  return (
    <g transform={`translate(${x} ${y}) scale(${clicking ? 0.85 : 1})`}>
      <path d="M0 0 L0 18 L5 14 L8.5 22 L11 21 L7.5 13 L14 13 Z"
            fill="#111" stroke="#fff" strokeWidth="1.1" strokeLinejoin="round"/>
    </g>
  );
}

// ─── Mini-demo frame wrapper ─────────────────────────────────────────────
function MiniFrame({ children, label }) {
  return (
    <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column' }}>
      <div style={{
        height: 32,
        background: '#f7f7f7',
        borderBottom: '1px solid #e8e8e8',
        display: 'flex',
        alignItems: 'center',
        padding: '0 14px',
        gap: 8,
      }}>
        <div style={{ display: 'flex', gap: 6 }}>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: '#ff5f57' }}/>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: '#febc2e' }}/>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: '#28c840' }}/>
        </div>
        <div style={{
          marginLeft: 'auto', marginRight: 'auto', fontSize: 11, color: '#888',
          fontWeight: 500, letterSpacing: '0.02em',
        }}>
          {label}
        </div>
        <div style={{ width: 36 }}/>
      </div>
      <div style={{ flex: 1, position: 'relative', background: '#fff', overflow: 'hidden' }}>
        {children}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// 1. Drag & drop
// ─────────────────────────────────────────────────────────────────────────
function DemoDragDrop() {
  const T = 5.0;
  const t = useLoopTime(T);

  const cardX = 60, cardY = 110;
  const dropX = 360, dropY = 170;

  const cursor = _interp([
    { t: 0.0, x: 400, y: 230 },     // rest
    { t: 0.6, x: cardX, y: cardY }, // approach card
    { t: 0.95, x: cardX, y: cardY },// hover/press
    { t: 2.20, x: dropX, y: dropY },// drag to canvas
    { t: 2.50, x: dropX, y: dropY },// release
    { t: 4.50, x: dropX, y: dropY },// hold
    { t: 5.00, x: 400, y: 230 },
  ], t);

  const clicking = _isClicking([{ t: 0.95, dur: 0.18 }], t);
  const dragging = t > 0.95 && t < 2.50;
  const dropped  = t > 2.40;

  // Card "hot" state
  const cardHot = t > 0.55 && t < 0.95;
  // Toolbox card pressed
  const cardPressed = t > 0.95 && t < 1.15;

  // Device placed
  const dt = t - 2.40;
  const devOp = dropped ? Math.min(1, dt / 0.4) : 0;
  const devScale = dropped ? 0.92 + 0.08 * _easeOut(Math.min(1, dt / 0.4)) : 0.92;

  // Card stays hidden while being "carried" (it becomes the drag ghost)
  const cardCarried = t > 0.95 && t < 2.50;

  return (
    <MiniFrame label="Drag & drop">
      <svg viewBox="0 0 600 400" width="100%" height="100%">
        {/* Toolbox panel */}
        <g>
          <rect x="0" y="0" width="140" height="400" fill="#fff" stroke="#e0e0e0"/>
          <text x="14" y="22" fontFamily="system-ui" fontSize="9" fontWeight="600"
                fill="#888" letterSpacing="1">DEVICES</text>
          <line x1="0" y1="32" x2="140" y2="32" stroke="#e0e0e0"/>

          {/* 4 device cards */}
          {[
            { name: 'Switch', color: '#00838f', bg: '#e0f7fa', y: 50 },
            { name: 'Router', color: '#e65100', bg: '#fff3e0', y: 100 },
            { name: 'Server', color: '#3949ab', bg: '#e8eaf6', y: 150 },
            { name: 'PC',     color: '#558b2f', bg: '#f1f8e9', y: 200 },
          ].map((c, i) => {
            const isHighlighted = i === 1 && (cardHot || cardPressed);
            const hide = i === 1 && cardCarried;
            return (
              <g key={c.name} opacity={hide ? 0.35 : 1}>
                <rect x="10" y={c.y} width="120" height="40" rx="3"
                      fill={cardPressed && i === 1 ? '#e8e8e8' : (isHighlighted ? '#f5f5f5' : '#fff')}
                      stroke={isHighlighted ? '#999' : '#e0e0e0'}/>
                <rect x="18" y={c.y + 8} width="24" height="24" rx="2"
                      fill={c.bg} stroke={c.color}/>
                <text x="50" y={c.y + 18} fontFamily="system-ui" fontSize="11" fontWeight="500"
                      fill="#111">{c.name}</text>
                <text x="50" y={c.y + 30} fontFamily="system-ui" fontSize="8"
                      fill="#888">LAN ports</text>
              </g>
            );
          })}
        </g>

        {/* Canvas area */}
        <g>
          <defs>
            <pattern id="mg-1" width="14" height="14" patternUnits="userSpaceOnUse">
              <path d="M 14 0 L 0 0 0 14" fill="none" stroke="#f3f3f3" strokeWidth="1"/>
            </pattern>
          </defs>
          <rect x="140" y="0" width="460" height="400" fill="url(#mg-1)"/>

          {/* Dropped router */}
          {devOp > 0 && (
            <g opacity={devOp}
               transform={`translate(${dropX} ${dropY}) scale(${devScale}) translate(-70 -42)`}>
              <rect width="140" height="84" rx="4" fill="#fff3e0" stroke="#e65100" strokeWidth="1.5"/>
              <text x="70" y="32" textAnchor="middle" fontFamily="system-ui" fontSize="13"
                    fontWeight="600" fill="#111">Edge Router</text>
              <text x="70" y="48" textAnchor="middle" fontFamily="system-ui" fontSize="9"
                    fontWeight="600" fill="#e65100" letterSpacing="1">ROUTER · 6P</text>
              {Array.from({length: 6}).map((_, i) => (
                <rect key={i} x={32 + i * 12} y="66" width="8" height="8" rx="1" fill="#999"/>
              ))}
            </g>
          )}
        </g>

        {/* Drag ghost (carried card) */}
        {dragging && (
          <g transform={`translate(${cursor.x + 12} ${cursor.y + 6})`}>
            <rect width="120" height="40" rx="3" fill="#fff" stroke="#bbb" opacity="0.95"
                  filter="url(#mg-shadow)"/>
            <rect x="8" y="8" width="24" height="24" rx="2" fill="#fff3e0" stroke="#e65100"/>
            <text x="40" y="18" fontFamily="system-ui" fontSize="11" fontWeight="500" fill="#111">Router</text>
            <text x="40" y="30" fontFamily="system-ui" fontSize="8" fill="#888">LAN ports</text>
          </g>
        )}
        <defs>
          <filter id="mg-shadow" x="-50%" y="-50%" width="200%" height="200%">
            <feDropShadow dx="0" dy="3" stdDeviation="3" floodOpacity="0.18"/>
          </filter>
        </defs>

        <MiniCursor x={cursor.x} y={cursor.y} clicking={clicking || dragging}/>
      </svg>
    </MiniFrame>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// 2. Connect ports
// ─────────────────────────────────────────────────────────────────────────
function DemoConnect() {
  const T = 4.5;
  const t = useLoopTime(T);

  const sw = { x: 300, y: 90, w: 200, h: 70 };  // switch top
  const pc = { x: 340, y: 260, w: 120, h: 80 }; // pc bottom

  // Port positions
  const swPortX = sw.x + sw.w / 2;
  const swPortY = sw.y + sw.h;
  const pcPortX = pc.x + pc.w / 2;
  const pcPortY = pc.y;

  const cursor = _interp([
    { t: 0.0,  x: 500, y: 200 },
    { t: 0.7,  x: swPortX, y: swPortY },   // approach switch port
    { t: 1.00, x: swPortX, y: swPortY },   // click
    { t: 1.70, x: pcPortX, y: pcPortY },   // move to pc port
    { t: 2.00, x: pcPortX, y: pcPortY },   // click → draws cable
    { t: 4.00, x: pcPortX, y: pcPortY },   // hold
    { t: 4.50, x: 500, y: 200 },
  ], t);

  const clicking = _isClicking([
    { t: 1.00, dur: 0.20 },
    { t: 2.00, dur: 0.20 },
  ], t);

  // Cable progress (drawStart 2.0 → drawEnd 2.6)
  let cableP = 0;
  if (t >= 2.00 && t < 2.60) cableP = _easeInOut((t - 2.00) / 0.60);
  else if (t >= 2.60 && t < 4.20) cableP = 1;
  else if (t >= 4.20) cableP = 1 - _easeInOut(Math.min(1, (t - 4.20) / 0.30));

  // Port glow when clicked
  const swPortGlow = t > 1.00 && t < 1.50;
  const pcPortGlow = t > 2.00 && t < 2.50;

  return (
    <MiniFrame label="Connect ports">
      <svg viewBox="0 0 600 400" width="100%" height="100%">
        <defs>
          <pattern id="mg-2" width="14" height="14" patternUnits="userSpaceOnUse">
            <path d="M 14 0 L 0 0 0 14" fill="none" stroke="#f3f3f3" strokeWidth="1"/>
          </pattern>
        </defs>
        <rect width="600" height="400" fill="url(#mg-2)"/>

        {/* Cable (under devices) */}
        <path d={`M ${swPortX} ${swPortY} C ${swPortX} ${swPortY + 50}, ${pcPortX} ${pcPortY - 50}, ${pcPortX} ${pcPortY}`}
              fill="none" stroke="#555" strokeWidth="2.5" strokeLinecap="round"
              pathLength="100" strokeDasharray="100"
              strokeDashoffset={100 * (1 - cableP)}/>

        {/* Switch */}
        <g transform={`translate(${sw.x} ${sw.y})`}>
          <rect width={sw.w} height={sw.h} rx="4" fill="#e0f7fa" stroke="#00838f" strokeWidth="1.5"/>
          <text x={sw.w/2} y="28" textAnchor="middle" fontFamily="system-ui" fontSize="13"
                fontWeight="600" fill="#111">core-sw-01</text>
          <text x={sw.w/2} y="42" textAnchor="middle" fontFamily="system-ui" fontSize="9"
                fontWeight="600" fill="#00838f" letterSpacing="1">SWITCH · 24P</text>
          {Array.from({length: 11}).map((_, i) => (
            <rect key={i} x={20 + i * 16} y={sw.h - 12} width="8" height="8" rx="1"
                  fill={i === 5 && swPortGlow ? '#1565c0' : '#999'}/>
          ))}
        </g>

        {/* PC */}
        <g transform={`translate(${pc.x} ${pc.y})`}>
          <rect width={pc.w} height={pc.h} rx="4" fill="#f1f8e9" stroke="#558b2f" strokeWidth="1.5"/>
          <text x={pc.w/2} y="26" textAnchor="middle" fontFamily="system-ui" fontSize="12"
                fontWeight="600" fill="#111">desk-pc</text>
          <text x={pc.w/2} y="40" textAnchor="middle" fontFamily="system-ui" fontSize="9"
                fontWeight="600" fill="#558b2f" letterSpacing="1">PC · 1P</text>
          <rect x={pc.w/2 - 6} y="2" width="12" height="8" rx="1"
                fill={pcPortGlow ? '#1565c0' : '#999'}/>
        </g>

        <MiniCursor x={cursor.x} y={cursor.y} clicking={clicking}/>
      </svg>
    </MiniFrame>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// 3. Configure properties
// ─────────────────────────────────────────────────────────────────────────
function DemoConfigure() {
  const T = 6.6;
  const t = useLoopTime(T);

  // Layout: canvas left (with selected device), props panel right
  const panelX = 360, panelW = 230;
  const ipFieldY = 175;
  const nameFieldY = 115;

  const cursor = _interp([
    { t: 0.00, x: 220, y: 200 },
    { t: 0.45, x: 220, y: 200 },                      // hover device
    { t: 0.65, x: 220, y: 200 },                      // click selects
    { t: 1.20, x: panelX + 110, y: nameFieldY },      // move to name field
    { t: 1.40, x: panelX + 110, y: nameFieldY },      // click
    { t: 2.80, x: panelX + 110, y: nameFieldY },      // typing happens
    { t: 3.20, x: panelX + 110, y: ipFieldY },        // move to ip field
    { t: 3.35, x: panelX + 110, y: ipFieldY },        // click
    { t: 4.80, x: panelX + 110, y: ipFieldY },        // typing happens
    { t: 5.60, x: 220, y: 200 },                      // back to canvas
    { t: 6.60, x: 220, y: 200 },                      // hold reset state
  ], t);

  const clicking = _isClicking([
    { t: 0.65, dur: 0.20 },
    { t: 1.40, dur: 0.18 },
    { t: 3.35, dur: 0.18 },
  ], t);

  // Typing: name then IP. After 5.30, erase back to empty so the loop wraps cleanly.
  const typed = (text, start, end) => {
    if (t < start) return '';
    if (t >= end) return text;
    return text.slice(0, Math.floor(text.length * (t - start) / (end - start)));
  };
  // Erase from full back to empty between (start, end).
  const erased = (text, start, end) => {
    if (t < start) return text;
    if (t >= end) return '';
    const p = (t - start) / (end - start);
    return text.slice(0, Math.ceil(text.length * (1 - p)));
  };

  let nameValue, ipValue;
  if (t < 5.30) {
    nameValue = typed('core-sw-01', 1.50, 2.50);
    ipValue   = typed('192.168.1.10', 3.45, 4.65);
  } else if (t < 5.80) {
    // Erase both quickly
    nameValue = erased('core-sw-01', 5.30, 5.65);
    ipValue   = erased('192.168.1.10', 5.35, 5.75);
  } else {
    nameValue = '';
    ipValue   = '';
  }
  const nameFocused = t >= 1.40 && t < 3.35;
  const ipFocused   = t >= 3.35 && t < 5.30;
  const showCaret = (t % 0.9) < 0.45;

  return (
    <MiniFrame label="Properties">
      <svg viewBox="0 0 600 400" width="100%" height="100%">
        <defs>
          <pattern id="mg-3" width="14" height="14" patternUnits="userSpaceOnUse">
            <path d="M 14 0 L 0 0 0 14" fill="none" stroke="#f3f3f3" strokeWidth="1"/>
          </pattern>
        </defs>
        <rect width="350" height="400" fill="url(#mg-3)"/>

        {/* Selected switch in middle of canvas */}
        <g transform="translate(120 145)">
          <rect width="200" height="90" rx="4" fill="#e0f7fa" stroke="#1565c0" strokeWidth="2.5"/>
          <text x="100" y="32" textAnchor="middle" fontFamily="system-ui" fontSize="14"
                fontWeight="600" fill="#111">core-sw-01</text>
          <text x="100" y="48" textAnchor="middle" fontFamily="system-ui" fontSize="9"
                fontWeight="600" fill="#00838f" letterSpacing="1">SWITCH · 24P</text>
          {Array.from({length: 10}).map((_, i) => (
            <rect key={i} x={18 + i * 17} y="72" width="9" height="9" rx="1" fill="#999"/>
          ))}
        </g>

        {/* Properties panel on right */}
        <g transform="translate(350 0)">
          <rect width="250" height="400" fill="#fff" stroke="#e0e0e0"/>
          {/* Header */}
          <text x="16" y="25" fontFamily="system-ui" fontSize="10" fontWeight="600"
                fill="#555" letterSpacing="1">PROPERTIES</text>
          <line x1="0" y1="38" x2="250" y2="38" stroke="#e0e0e0"/>

          {/* Type badge */}
          <g transform="translate(16 58)">
            <rect width="60" height="20" rx="3" fill="#e0f7fa" stroke="#00838f"/>
            <text x="30" y="14" textAnchor="middle" fontFamily="system-ui" fontSize="9"
                  fontWeight="600" fill="#00838f" letterSpacing="1">SWITCH</text>
          </g>

          {/* Name field */}
          <text x="16" y="100" fontFamily="system-ui" fontSize="9" fontWeight="600"
                fill="#888" letterSpacing="1">NAME</text>
          <rect x="16" y="106" width="220" height="22" rx="2" fill="#fff"
                stroke={nameFocused ? '#1565c0' : '#e0e0e0'}
                strokeWidth={nameFocused ? 1.5 : 1}/>
          <text x="24" y="121" fontFamily={nameValue ? '"JetBrains Mono", monospace' : 'system-ui'}
                fontSize="12" fill={nameValue ? '#111' : '#bbb'}
                fontStyle={nameValue ? 'normal' : 'italic'}>
            {nameValue || 'Name this device…'}
            {nameFocused && t < 2.50 && showCaret && '|'}
          </text>

          {/* IP field */}
          <text x="16" y="160" fontFamily="system-ui" fontSize="9" fontWeight="600"
                fill="#888" letterSpacing="1">IP ADDRESS</text>
          <rect x="16" y="166" width="220" height="22" rx="2" fill="#fff"
                stroke={ipFocused ? '#1565c0' : '#e0e0e0'}
                strokeWidth={ipFocused ? 1.5 : 1}/>
          <text x="24" y="181" fontFamily={ipValue ? '"JetBrains Mono", monospace' : 'system-ui'}
                fontSize="12" fill={ipValue ? '#111' : '#bbb'}
                fontStyle={ipValue ? 'normal' : 'italic'}>
            {ipValue || '192.168.1.x'}
            {ipFocused && t < 4.70 && showCaret && '|'}
          </text>

          {/* Tags */}
          <text x="16" y="220" fontFamily="system-ui" fontSize="9" fontWeight="600"
                fill="#888" letterSpacing="1">TAGS</text>
          <g transform="translate(16 228)">
            <rect width="38" height="18" rx="9" fill="#f5f5f5" stroke="#ccc"/>
            <text x="19" y="12" textAnchor="middle" fontFamily="system-ui" fontSize="10" fill="#555">core</text>
            <rect x="44" width="48" height="18" rx="9" fill="#f5f5f5" stroke="#ccc"/>
            <text x="68" y="12" textAnchor="middle" fontFamily="system-ui" fontSize="10" fill="#555">rack-a</text>
          </g>
        </g>

        <MiniCursor x={cursor.x} y={cursor.y} clicking={clicking}/>
      </svg>
    </MiniFrame>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// 4. AI Prompt
// ─────────────────────────────────────────────────────────────────────────
function DemoAIPrompt() {
  const T = 5.0;
  const t = useLoopTime(T);

  // Topbar AI button position
  const aiBtnX = 540, aiBtnY = 25;
  // Modal copy button position
  const copyX = 410, copyY = 290;

  const cursor = _interp([
    { t: 0.00, x: 300, y: 180 },
    { t: 0.60, x: aiBtnX, y: aiBtnY },     // approach AI button
    { t: 0.85, x: aiBtnX, y: aiBtnY },     // click
    { t: 2.30, x: aiBtnX, y: aiBtnY },     // hold while modal open
    { t: 2.80, x: copyX, y: copyY },       // move to Copy
    { t: 3.00, x: copyX, y: copyY },       // click Copy
    { t: 4.20, x: copyX, y: copyY },       // hold "Copied!"
    { t: 5.00, x: 300, y: 180 },
  ], t);

  const clicking = _isClicking([
    { t: 0.85, dur: 0.20 },
    { t: 3.00, dur: 0.20 },
  ], t);

  const aiBtnPressed = t > 0.85 && t < 1.05;
  const modalOpen = t > 1.05 && t < 4.50;
  const copyClicked = t > 3.00 && t < 4.50;
  const copyPressed = t > 3.00 && t < 3.20;

  const modalT = Math.max(0, t - 1.05);
  const modalFadeIn = Math.min(1, modalT / 0.25);
  const modalFadeOut = t > 4.30 ? Math.max(0, 1 - (t - 4.30) / 0.20) : 1;
  const modalOp = modalFadeIn * modalFadeOut;

  return (
    <MiniFrame label="AI Prompt">
      <svg viewBox="0 0 600 400" width="100%" height="100%">
        {/* Topbar */}
        <rect width="600" height="42" fill="#fff" stroke="#e0e0e0"/>
        <text x="14" y="26" fontFamily="system-ui" fontSize="13" fontWeight="600" fill="#111">LanDocs</text>
        <line x1="80" y1="11" x2="80" y2="31" stroke="#e0e0e0"/>
        {['Save', 'Load', 'PNG', 'TXT'].map((b, i) => (
          <g key={b} transform={`translate(${100 + i * 60} 12)`}>
            <rect width="50" height="20" rx="2" fill="#fff" stroke="#ccc"/>
            <text x="25" y="14" textAnchor="middle" fontFamily="system-ui" fontSize="11" fill="#111">{b}</text>
          </g>
        ))}
        {/* AI Prompt button */}
        <g transform={`translate(${aiBtnX - 50} ${aiBtnY - 13})`}>
          <rect width="100" height="26" rx="3"
                fill={aiBtnPressed ? '#0f1656' : '#1a237e'}
                stroke={aiBtnPressed ? '#0f1656' : '#1a237e'}/>
          <text x="50" y="17" textAnchor="middle" fontFamily="system-ui" fontSize="11"
                fontWeight="600" fill="#fff">AI Prompt</text>
        </g>

        {/* Canvas hint */}
        <defs>
          <pattern id="mg-4" width="14" height="14" patternUnits="userSpaceOnUse">
            <path d="M 14 0 L 0 0 0 14" fill="none" stroke="#f3f3f3" strokeWidth="1"/>
          </pattern>
        </defs>
        <rect y="42" width="600" height="358" fill="url(#mg-4)"/>

        {/* Modal */}
        {modalOp > 0.01 && (
          <g opacity={modalOp}>
            {/* Scrim */}
            <rect y="42" width="600" height="358" fill="rgba(0,0,0,0.35)"/>
            {/* Box */}
            <g transform={`translate(80 100) scale(${0.96 + 0.04 * modalFadeIn})`}>
              <rect width="440" height="220" rx="4" fill="#fff" stroke="#ccc" strokeWidth="1"
                    filter="url(#mg4-shadow)"/>
              <text x="22" y="32" fontFamily="system-ui" fontSize="14" fontWeight="600" fill="#111">
                AI Prompts — Generate Your Network
              </text>
              <text x="22" y="56" fontFamily="system-ui" fontSize="11" fill="#555">
                Copy this prompt, paste it into any AI.
              </text>

              {/* Prompt textarea */}
              <rect x="22" y="68" width="396" height="110" fill="#fafafa" stroke="#ddd"/>
              <text x="30" y="84" fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#444">
                You are a network-architecture assistant.
              </text>
              <text x="30" y="98" fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#444">
                Reply with ONLY a valid JSON object that
              </text>
              <text x="30" y="112" fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#444">
                LanDocs can import.
              </text>
              <text x="30" y="132" fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#444">
                {'Schema: {'}
              </text>
              <text x="38" y="146" fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#444">
                {'"devices": [...], "cables": [...]'}
              </text>
              <text x="30" y="160" fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#444">
                {'}'}
              </text>

              {/* Copied feedback */}
              {copyClicked && (
                <text x="22" y="208" fontFamily="system-ui" fontSize="11" fontWeight="600" fill="#2e7d32">
                  ✓ Copied to clipboard
                </text>
              )}

              {/* Copy button */}
              <g transform={`translate(${copyX - 80 - 60} 192)`}>
                <rect width="120" height="24" rx="3"
                      fill={copyPressed ? '#000' : '#111'}
                      stroke="#111"/>
                <text x="60" y="16" textAnchor="middle" fontFamily="system-ui" fontSize="11"
                      fontWeight="600" fill="#fff">
                  {copyClicked ? '✓ Copied' : 'Copy Prompt'}
                </text>
              </g>
            </g>
            <defs>
              <filter id="mg4-shadow" x="-50%" y="-50%" width="200%" height="200%">
                <feDropShadow dx="0" dy="4" stdDeviation="8" floodOpacity="0.18"/>
              </filter>
            </defs>
          </g>
        )}

        <MiniCursor x={cursor.x} y={cursor.y} clicking={clicking}/>
      </svg>
    </MiniFrame>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// 5. Export
// ─────────────────────────────────────────────────────────────────────────
function DemoExport() {
  const T = 6.6;
  const t = useLoopTime(T);

  const saveBtn = { x: 160, y: 25, w: 50 };
  const pngBtn  = { x: 290, y: 25, w: 50 };
  const txtBtn  = { x: 375, y: 25, w: 50 };

  const cursor = _interp([
    { t: 0.00, x: 400, y: 200 },
    { t: 0.55, x: saveBtn.x, y: saveBtn.y },
    { t: 0.75, x: saveBtn.x, y: saveBtn.y },
    { t: 1.70, x: saveBtn.x, y: saveBtn.y }, // hold while toast
    { t: 2.10, x: pngBtn.x, y: pngBtn.y },
    { t: 2.30, x: pngBtn.x, y: pngBtn.y },
    { t: 3.40, x: pngBtn.x, y: pngBtn.y },   // hold while toast
    { t: 3.75, x: txtBtn.x, y: txtBtn.y },
    { t: 3.95, x: txtBtn.x, y: txtBtn.y },
    { t: 5.30, x: txtBtn.x, y: txtBtn.y },   // hold while toast
    { t: 6.00, x: 400, y: 200 },
  ], t);

  const clicking = _isClicking([
    { t: 0.75, dur: 0.20 },
    { t: 2.30, dur: 0.20 },
    { t: 3.95, dur: 0.20 },
  ], t);

  const saveHot = t > 0.55 && t < 0.75;
  const savePressed = t > 0.75 && t < 0.95;
  const pngHot = t > 2.10 && t < 2.30;
  const pngPressed = t > 2.30 && t < 2.50;
  const txtHot = t > 3.75 && t < 3.95;
  const txtPressed = t > 3.95 && t < 4.15;

  const saveToast = t > 0.85 && t < 1.85;
  const pngToast  = t > 2.40 && t < 3.40;
  const txtToast  = t > 4.05 && t < 5.30;

  const toast = (label, file, show, start, end) => {
    if (!show) return null;
    const dt = t - start;
    const op = Math.min(1, dt * 4) * Math.min(1, (end - t) * 3);
    const slide = Math.max(0, (1 - Math.min(1, dt * 5))) * 16;
    return { label, file, op: Math.max(0, op), slide };
  };
  const tst = toast('Saved',    'network-layout.json',    saveToast, 0.85, 1.85) ||
              toast('Exported', 'network-diagram.png',    pngToast,  2.40, 3.40) ||
              toast('Exported', 'network-topology.txt',   txtToast,  4.05, 5.30);

  return (
    <MiniFrame label="Save & export">
      <svg viewBox="0 0 600 400" width="100%" height="100%">
        {/* Topbar */}
        <rect width="600" height="42" fill="#fff" stroke="#e0e0e0"/>
        <text x="14" y="26" fontFamily="system-ui" fontSize="13" fontWeight="600" fill="#111">LanDocs</text>
        <line x1="80" y1="11" x2="80" y2="31" stroke="#e0e0e0"/>

        {/* Save / Load buttons */}
        <g transform={`translate(${saveBtn.x - saveBtn.w/2} 12)`}>
          <rect width={saveBtn.w} height="20" rx="2"
                fill={savePressed ? '#d8d8d8' : (saveHot ? '#f0f0f0' : '#fff')}
                stroke={saveHot || savePressed ? '#888' : '#ccc'}/>
          <text x={saveBtn.w/2} y="14" textAnchor="middle" fontFamily="system-ui" fontSize="11"
                fontWeight="500" fill="#111">Save</text>
        </g>
        <g transform="translate(220 12)">
          <rect width="50" height="20" rx="2" fill="#fff" stroke="#ccc"/>
          <text x="25" y="14" textAnchor="middle" fontFamily="system-ui" fontSize="11" fill="#111">Load</text>
        </g>

        <line x1="284" y1="11" x2="284" y2="31" stroke="#e0e0e0"/>

        {/* PNG / TXT buttons */}
        <g transform={`translate(${pngBtn.x - pngBtn.w/2} 12)`}>
          <rect width={pngBtn.w} height="20" rx="2"
                fill={pngPressed ? '#d8d8d8' : (pngHot ? '#f0f0f0' : '#fff')}
                stroke={pngHot || pngPressed ? '#888' : '#ccc'}/>
          <text x={pngBtn.w/2} y="14" textAnchor="middle" fontFamily="system-ui" fontSize="11"
                fontWeight="500" fill="#111">PNG</text>
        </g>
        <g transform={`translate(${txtBtn.x - txtBtn.w/2} 12)`}>
          <rect width={txtBtn.w} height="20" rx="2"
                fill={txtPressed ? '#d8d8d8' : (txtHot ? '#f0f0f0' : '#fff')}
                stroke={txtHot || txtPressed ? '#888' : '#ccc'}/>
          <text x={txtBtn.w/2} y="14" textAnchor="middle" fontFamily="system-ui" fontSize="11"
                fontWeight="500" fill="#111">TXT</text>
        </g>

        {/* Canvas with finished network */}
        <defs>
          <pattern id="mg-5" width="14" height="14" patternUnits="userSpaceOnUse">
            <path d="M 14 0 L 0 0 0 14" fill="none" stroke="#f3f3f3" strokeWidth="1"/>
          </pattern>
        </defs>
        <rect y="42" width="600" height="358" fill="url(#mg-5)"/>

        {/* Mini network */}
        <g transform="translate(220 90)">
          <rect width="160" height="50" rx="4" fill="#fff3e0" stroke="#e65100" strokeWidth="1.4"/>
          <text x="80" y="22" textAnchor="middle" fontFamily="system-ui" fontSize="11" fontWeight="600" fill="#111">Edge Router</text>
          <text x="80" y="36" textAnchor="middle" fontFamily="system-ui" fontSize="8" fontWeight="600" fill="#e65100" letterSpacing="1">ROUTER · 6P</text>
        </g>
        <g transform="translate(200 180)">
          <rect width="200" height="60" rx="4" fill="#e0f7fa" stroke="#00838f" strokeWidth="1.4"/>
          <text x="100" y="24" textAnchor="middle" fontFamily="system-ui" fontSize="11" fontWeight="600" fill="#111">core-sw-01</text>
          <text x="100" y="38" textAnchor="middle" fontFamily="system-ui" fontSize="8" fontWeight="600" fill="#00838f" letterSpacing="1">SWITCH · 24P</text>
          {Array.from({length: 9}).map((_, i) => (
            <rect key={i} x={28 + i * 16} y="46" width="8" height="8" rx="1" fill="#999"/>
          ))}
        </g>
        <g transform="translate(170 290)">
          <rect width="100" height="60" rx="4" fill="#e8eaf6" stroke="#3949ab" strokeWidth="1.4"/>
          <text x="50" y="24" textAnchor="middle" fontFamily="system-ui" fontSize="11" fontWeight="600" fill="#111">nas-01</text>
          <text x="50" y="38" textAnchor="middle" fontFamily="system-ui" fontSize="8" fontWeight="600" fill="#3949ab" letterSpacing="1">SERVER</text>
        </g>
        <g transform="translate(330 290)">
          <rect width="100" height="60" rx="4" fill="#f1f8e9" stroke="#558b2f" strokeWidth="1.4"/>
          <text x="50" y="24" textAnchor="middle" fontFamily="system-ui" fontSize="11" fontWeight="600" fill="#111">desk-pc</text>
          <text x="50" y="38" textAnchor="middle" fontFamily="system-ui" fontSize="8" fontWeight="600" fill="#558b2f" letterSpacing="1">PC</text>
        </g>

        {/* Cables */}
        <path d="M 300 140 C 300 160, 300 165, 300 180" fill="none" stroke="#555" strokeWidth="2" strokeLinecap="round"/>
        <path d="M 280 240 C 270 260, 230 285, 220 290" fill="none" stroke="#555" strokeWidth="2" strokeLinecap="round"/>
        <path d="M 320 240 C 340 260, 370 285, 380 290" fill="none" stroke="#555" strokeWidth="2" strokeLinecap="round"/>

        {/* Toast */}
        {tst && (
          <g opacity={tst.op} transform={`translate(370 ${56 - tst.slide})`}>
            <rect width="210" height="38" rx="5" fill="#111" filter="url(#mg5-shadow)"/>
            <circle cx="22" cy="19" r="9" fill="#2e7d32"/>
            <text x="22" y="23" textAnchor="middle" fontFamily="system-ui" fontSize="11" fontWeight="700" fill="#fff">✓</text>
            <text x="38" y="23" fontFamily="system-ui" fontSize="11" fontWeight="500" fill="#fff">{tst.label}</text>
            <text x={38 + (tst.label === 'Saved' ? 36 : 52)} y="23"
                  fontFamily="'JetBrains Mono', monospace" fontSize="10" fill="rgba(255,255,255,0.85)">
              {tst.file}
            </text>
          </g>
        )}
        <defs>
          <filter id="mg5-shadow" x="-50%" y="-50%" width="200%" height="200%">
            <feDropShadow dx="0" dy="3" stdDeviation="5" floodOpacity="0.30"/>
          </filter>
        </defs>

        <MiniCursor x={cursor.x} y={cursor.y} clicking={clicking}/>
      </svg>
    </MiniFrame>
  );
}

Object.assign(window, {
  DemoDragDrop, DemoConnect, DemoConfigure, DemoAIPrompt, DemoExport,
});
