/* global React */
// ============================================================
// Inframind — Animated mockups for the Technology page deepdives
// Six small components rendered inside .dd-visual (4:3 aspect),
// each demonstrating a discipline in action.
// ============================================================

const { useState: useTM, useEffect: useEffTM, useMemo: useMemoTM } = React;

// Shared looping clock (setInterval-based — keeps running when tab is bg)
function useTechLoop(duration = 8) {
  const [t, setT] = useTM(0);
  useEffTM(() => {
    const start = performance.now();
    const id = setInterval(() => {
      setT(((performance.now() - start) / 1000) % duration);
    }, 1000 / 30);
    return () => clearInterval(id);
  }, [duration]);
  return t;
}

const clampTM = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v));
const ease = (x) => x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;

// ============================================================
// 01 — VISION: scan line sweeps a concrete surface,
// crack/spall bounding boxes appear with mm-precision labels.
// ============================================================
function VisionMockup() {
  const t = useTechLoop(9);
  const scanT = clampTM(t / 7);

  const defects = useMemoTM(() => [
    { x: 70,  y: 130, w: 88, h: 22, color: "var(--sev-critical)", label: "CR-04",  measure: "12.8 mm" },
    { x: 220, y: 70,  w: 50, h: 44, color: "var(--sev-high)",     label: "SP-12",  measure: "4.2 mm"  },
    { x: 312, y: 168, w: 68, h: 30, color: "var(--sev-critical)", label: "CR-02",  measure: "9.6 mm"  },
    { x: 430, y: 96,  w: 42, h: 56, color: "var(--ink-700)",      label: "DF-02",  measure: "1.6 mm"  },
    { x: 510, y: 184, w: 72, h: 32, color: "var(--sev-medium)",   label: "CR-09",  measure: "3.1 mm"  },
  ], []);

  const trigger = [0.15, 0.32, 0.48, 0.64, 0.80];
  const scanX = 36 + scanT * 580;
  const detected = trigger.filter(tr => scanT > tr).length;
  const coverage = Math.floor(scanT * 100);

  return (
    <div className="pm-frame">
      <div className="pm-head">
        <div className="pm-head-title">CV defect detection · concrete lining</div>
        <div className="pm-head-stat">
          <span className="pm-mono pm-dim">model</span>
          <span className="pm-mono">crack-seg-v3</span>
        </div>
      </div>

      <div className="pm-body" style={{padding: 0, position: "relative"}}>
        <svg viewBox="0 0 620 280" preserveAspectRatio="xMidYMid slice" style={{width: "100%", height: "100%", display: "block"}}>
          <defs>
            <pattern id="visConc" width="80" height="26" patternUnits="userSpaceOnUse">
              <rect width="80" height="26" fill="#C8C8C6"/>
              <line x1="0" y1="0" x2="80" y2="0" stroke="#9a9a98" strokeWidth="0.6"/>
              <line x1="40" y1="0" x2="40" y2="13" stroke="#9a9a98" strokeWidth="0.5"/>
              <line x1="0" y1="13" x2="80" y2="13" stroke="#9a9a98" strokeWidth="0.4" opacity="0.55"/>
              <line x1="20" y1="13" x2="20" y2="26" stroke="#9a9a98" strokeWidth="0.5"/>
              <line x1="60" y1="13" x2="60" y2="26" stroke="#9a9a98" strokeWidth="0.5"/>
            </pattern>
            <linearGradient id="visScan" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" stopColor="rgba(10,10,10,0)"/>
              <stop offset="0.85" stopColor="rgba(10,10,10,0.04)"/>
              <stop offset="1" stopColor="rgba(10,10,10,0.18)"/>
            </linearGradient>
            <pattern id="noise" width="3" height="3" patternUnits="userSpaceOnUse">
              <rect width="3" height="3" fill="transparent"/>
              <rect x="0" y="0" width="1" height="1" fill="rgba(40,40,40,0.06)"/>
              <rect x="2" y="1" width="1" height="1" fill="rgba(255,255,255,0.05)"/>
            </pattern>
          </defs>

          <rect width="620" height="280" fill="url(#visConc)"/>
          <rect width="620" height="280" fill="url(#noise)"/>

          {/* hairline cracks in the underlying surface */}
          <path d="M52 132 Q120 148 188 134 T300 144 T398 134" stroke="rgba(30,30,30,0.55)" fill="none" strokeWidth="1"/>
          <path d="M218 78 Q244 92 256 110 T268 134" stroke="rgba(30,30,30,0.45)" fill="none" strokeWidth="0.8"/>
          <path d="M520 188 Q550 198 580 205 T610 210" stroke="rgba(30,30,30,0.4)" fill="none" strokeWidth="0.7"/>

          {/* Defect boxes & labels — animated reveal */}
          {defects.map((d, i) => {
            if (scanT < trigger[i]) return null;
            const age = clampTM((scanT - trigger[i]) * 8);
            return (
              <g key={i} opacity={age}>
                {/* segmentation dashed overlay (crack-style) */}
                <rect x={d.x} y={d.y} width={d.w} height={d.h}
                      stroke={d.color} strokeWidth="1.6" fill={d.color} fillOpacity="0.08"
                      strokeDasharray="0"/>
                {/* corner ticks */}
                {[[d.x, d.y],[d.x+d.w,d.y],[d.x,d.y+d.h],[d.x+d.w,d.y+d.h]].map(([cx, cy], j) => (
                  <g key={j}>
                    <line x1={cx-4} y1={cy} x2={cx+4} y2={cy} stroke={d.color} strokeWidth="1.4"/>
                    <line x1={cx} y1={cy-4} x2={cx} y2={cy+4} stroke={d.color} strokeWidth="1.4"/>
                  </g>
                ))}
                {/* label */}
                <rect x={d.x} y={d.y - 16} width={d.label.length * 6.8 + 12} height="14" fill={d.color}/>
                <text x={d.x + 6} y={d.y - 5} fontFamily="JetBrains Mono, monospace" fontSize="9.5" fill="#fff" fontWeight="600">{d.label}</text>
                {/* measurement chip below */}
                <rect x={d.x} y={d.y + d.h + 2} width={d.measure.length * 6.6 + 12} height="14" fill="rgba(10,10,10,0.86)"/>
                <text x={d.x + 6} y={d.y + d.h + 13} fontFamily="JetBrains Mono, monospace" fontSize="9.5" fill="#fff">{d.measure}</text>
              </g>
            );
          })}

          {/* scan trail */}
          <rect x={Math.max(0, scanX - 56)} y="0" width={Math.min(56, scanX)} height="280" fill="url(#visScan)"/>
          {/* leading scan line */}
          <line x1={scanX} y1="0" x2={scanX} y2="280" stroke="var(--ink-1000)" strokeWidth="1.2" opacity="0.85"/>
          <rect x={scanX - 0.5} y="0" width="2" height="280" fill="var(--ink-1000)" opacity="0.18"/>
          {/* scanner head badge */}
          <g transform={`translate(${scanX} 14)`}>
            <rect x="-26" y="-9" width="52" height="18" rx="2" fill="var(--ink-1000)"/>
            <text x="0" y="3" textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="9.5" fill="#fff">SCAN</text>
          </g>
        </svg>

        {/* overlay HUD */}
        <div className="tm-hud tm-hud-tr">
          <div className="pm-mono pm-dim" style={{fontSize: 9.5}}>DEFECTS FOUND</div>
          <div className="tm-hud-big">{detected}</div>
          <div className="tm-hud-bar">
            <div className="tm-hud-bar-fill" style={{width: `${coverage}%`}}></div>
          </div>
          <div className="pm-mono pm-dim" style={{fontSize: 9.5, marginTop: 4}}>
            COVERAGE · {coverage}%
          </div>
        </div>
      </div>

      <div className="pm-foot">
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Width precision</span>
          <span className="pm-mono">±0.4 mm</span>
        </div>
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Confidence</span>
          <span className="pm-mono" style={{color: "var(--status-ok)"}}>{(0.86 + scanT * 0.08).toFixed(2)}</span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// 02 — POINT CLOUD: rotating tunnel cross-section,
// design-intent overlay, deviation profile being built.
// ============================================================
function PointCloudMockup() {
  const t = useTechLoop(10);
  const sweepT = clampTM(t / 7);
  const rot = (t / 10) * Math.PI * 2;

  // Generate a single ring of points at varying densities/deformations
  const points = useMemoTM(() => {
    const arr = [];
    for (let i = 0; i < 240; i++) {
      const ang = (i / 240) * Math.PI * 2;
      // a soft bulge between angle 0.6π and 1.1π (right wall)
      const bulge = ang > 0.55 * Math.PI && ang < 1.15 * Math.PI ? Math.sin((ang - 0.55 * Math.PI) / (0.6 * Math.PI) * Math.PI) * 8 : 0;
      const r = 110 + bulge + (Math.sin(ang * 7) * 0.6);
      arr.push({ ang, r });
    }
    return arr;
  }, []);

  // Profile chart points (built progressively)
  const profile = useMemoTM(() => {
    const arr = [];
    for (let i = 0; i < 100; i++) {
      const ang = (i / 100) * Math.PI * 2;
      const bulge = ang > 0.55 * Math.PI && ang < 1.15 * Math.PI ? Math.sin((ang - 0.55 * Math.PI) / (0.6 * Math.PI) * Math.PI) * 8 : 0;
      arr.push(bulge);
    }
    return arr;
  }, []);

  const cx = 200, cy = 150;
  const builtCount = Math.floor(sweepT * 100);

  return (
    <div className="pm-frame">
      <div className="pm-head">
        <div className="pm-head-title">LiDAR cross-section · ch 418.0 m</div>
        <div className="pm-head-stat">
          <span className="pm-mono pm-dim">points</span>
          <span className="pm-mono">2.4M</span>
        </div>
      </div>

      <div className="pm-body" style={{padding: 0, position: "relative"}}>
        <svg viewBox="0 0 620 280" preserveAspectRatio="xMidYMid meet" style={{width: "100%", height: "100%", display: "block"}}>
          <defs>
            <pattern id="pcGrid" width="32" height="32" patternUnits="userSpaceOnUse">
              <rect width="32" height="32" fill="var(--paper)"/>
              <line x1="0" y1="0" x2="32" y2="0" stroke="var(--ink-150)" strokeWidth="0.5"/>
              <line x1="0" y1="0" x2="0" y2="32" stroke="var(--ink-150)" strokeWidth="0.5"/>
            </pattern>
          </defs>
          <rect width="620" height="280" fill="url(#pcGrid)"/>

          {/* Left: cross-section circle */}
          {/* design-intent (dashed) */}
          <circle cx={cx} cy={cy} r="110" fill="none" stroke="var(--ink-400)" strokeWidth="1" strokeDasharray="3 4"/>
          <text x={cx} y={28} textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="9" fill="var(--ink-500)">DESIGN PROFILE</text>

          {/* axes */}
          <line x1={cx - 130} y1={cy} x2={cx + 130} y2={cy} stroke="var(--ink-200)" strokeWidth="0.6"/>
          <line x1={cx} y1={cy - 130} x2={cx} y2={cy + 130} stroke="var(--ink-200)" strokeWidth="0.6"/>

          {/* points sweeping in */}
          {points.map((p, i) => {
            const reveal = (p.ang + Math.PI) / (2 * Math.PI);
            const visible = (reveal + sweepT * 1.1) % 1;
            if (visible > 0.95) return null;
            const fade = Math.min(1, (1 - visible) * 6);
            const deviation = p.r - 110;
            const color = deviation > 2 ? "#DC2626" : deviation > 0.5 ? "#EA580C" : "#171717";
            const x = cx + Math.cos(p.ang + rot * 0.04) * p.r;
            const y = cy + Math.sin(p.ang + rot * 0.04) * p.r;
            return <circle key={i} cx={x} cy={y} r="1.2" fill={color} opacity={0.85 * fade}/>;
          })}

          {/* deviation arc highlight (the bulge) */}
          {sweepT > 0.5 && (
            <g opacity={clampTM((sweepT - 0.5) * 4)}>
              <path d={`M ${cx + Math.cos(0.55 * Math.PI) * 122} ${cy + Math.sin(0.55 * Math.PI) * 122} A 122 122 0 0 1 ${cx + Math.cos(1.15 * Math.PI) * 122} ${cy + Math.sin(1.15 * Math.PI) * 122}`}
                stroke="#DC2626" strokeWidth="1.4" fill="none" strokeDasharray="2 3"/>
              <g transform={`translate(${cx - 130} ${cy + 80})`}>
                <rect x="0" y="-12" width="86" height="16" fill="#DC2626"/>
                <text x="6" y="-1" fontFamily="JetBrains Mono, monospace" fontSize="9.5" fill="#fff" fontWeight="600">Δ +9.2 mm</text>
              </g>
            </g>
          )}

          {/* Right: unrolled deviation profile */}
          <g transform="translate(380 40)">
            <text x="0" y="-4" fontFamily="JetBrains Mono, monospace" fontSize="9" fill="var(--ink-500)">DEVIATION FROM DESIGN (mm)</text>
            <rect x="0" y="0" width="220" height="200" fill="var(--paper)" stroke="var(--ink-150)"/>
            {/* gridlines */}
            {[-8, -4, 0, 4, 8].map((v, i) => {
              const y = 100 - v * 8;
              return (
                <g key={i}>
                  <line x1="0" y1={y} x2="220" y2={y} stroke={v === 0 ? "var(--ink-300)" : "var(--ink-150)"} strokeWidth="0.5"/>
                  <text x="-4" y={y + 3} textAnchor="end" fontFamily="JetBrains Mono, monospace" fontSize="8" fill="var(--ink-500)">{v > 0 ? `+${v}` : v}</text>
                </g>
              );
            })}
            {/* profile line, built progressively */}
            <path
              d={profile.slice(0, builtCount).map((v, i) => `${i === 0 ? "M" : "L"} ${(i / 99) * 220} ${100 - v * 8}`).join(" ")}
              stroke="#171717" strokeWidth="1.3" fill="none"
            />
            {/* fill below curve in red where deviation > 2mm */}
            <path
              d={(() => {
                const segs = [];
                let inSeg = false;
                let startI = 0;
                for (let i = 0; i < builtCount; i++) {
                  if (profile[i] > 2 && !inSeg) { inSeg = true; startI = i; }
                  if ((profile[i] <= 2 || i === builtCount - 1) && inSeg) {
                    const pts = [];
                    for (let j = startI; j <= i; j++) pts.push(`${(j/99)*220} ${100 - profile[j] * 8}`);
                    segs.push(`M ${(startI/99)*220} 100 L ${pts.join(" L ")} L ${(i/99)*220} 100 Z`);
                    inSeg = false;
                  }
                }
                return segs.join(" ");
              })()}
              fill="#DC2626" fillOpacity="0.18" stroke="none"
            />
            {/* x-axis labels */}
            <text x="0" y="216" fontFamily="JetBrains Mono, monospace" fontSize="8" fill="var(--ink-500)">0°</text>
            <text x="55" y="216" fontFamily="JetBrains Mono, monospace" fontSize="8" fill="var(--ink-500)">90°</text>
            <text x="110" y="216" fontFamily="JetBrains Mono, monospace" fontSize="8" fill="var(--ink-500)">180°</text>
            <text x="165" y="216" fontFamily="JetBrains Mono, monospace" fontSize="8" fill="var(--ink-500)">270°</text>
            <text x="220" y="216" textAnchor="end" fontFamily="JetBrains Mono, monospace" fontSize="8" fill="var(--ink-500)">360°</text>
          </g>
        </svg>

        {/* corner badges */}
        <div className="tm-corner-tag" style={{top: 12, left: 14}}>
          <span className="pm-mono">REGISTERED · campaign 2024-11</span>
        </div>
      </div>

      <div className="pm-foot">
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Cross-section change</span>
          <span className="pm-mono" style={{color: "var(--sev-critical)"}}>+9.2 mm @ 178°</span>
        </div>
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Surface reconstruction</span>
          <span className="pm-mono">{builtCount}% complete</span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// 04 — PHYSICS: raw AI score → calibrated by physical context.
// Three rows: bars shift as factors apply sequentially.
// ============================================================
function PhysicsMockup() {
  const t = useTechLoop(9);

  // Each row has: raw → after geometry → after load → after material+env
  const rows = useMemoTM(() => [
    {
      id: "CR-04", type: "Longitudinal crack · crown",
      raw: 72, factors: [
        { label: "geometry: spans crown rib", delta: +14, color: "var(--sev-critical)" },
        { label: "load path: primary",        delta: +8,  color: "var(--sev-critical)" },
        { label: "material: 50yr concrete",   delta: +4,  color: "var(--sev-high)" },
      ],
    },
    {
      id: "SP-12", type: "Spall · invert",
      raw: 78, factors: [
        { label: "geometry: invert, non-load", delta: -22, color: "var(--ink-700)" },
        { label: "load path: secondary",       delta: -8,  color: "var(--ink-700)" },
        { label: "environment: dry",           delta: -2,  color: "var(--ink-500)" },
      ],
    },
    {
      id: "DF-02", type: "Deformation · right haunch",
      raw: 48, factors: [
        { label: "geometry: bearing zone", delta: +18, color: "var(--sev-high)" },
        { label: "load path: primary",     delta: +10, color: "var(--sev-high)" },
        { label: "history: +2.4mm / yr",   delta: +12, color: "var(--sev-critical)" },
      ],
    },
  ], []);

  // Animation phases: 0=raw reveal, 1=factor1, 2=factor2, 3=factor3
  const phase = Math.min(3, Math.floor(t / 1.6));
  const inPhase = (t - phase * 1.6) / 1.6;

  return (
    <div className="pm-frame">
      <div className="pm-head">
        <div className="pm-head-title">Physics-calibrated severity</div>
        <div className="pm-head-stat">
          <span className="pm-mono pm-dim">step</span>
          <span className="pm-mono">{phase + 1} / 4</span>
        </div>
      </div>

      <div className="pm-body" style={{padding: "10px 14px", display: "flex", flexDirection: "column", gap: 10}}>
        {/* Header row */}
        <div className="tm-phy-headrow pm-mono pm-dim">
          <span>DEFECT</span>
          <span style={{textAlign: "right"}}>RAW AI</span>
          <span style={{textAlign: "center"}}>+ PHYSICS CONTEXT</span>
          <span style={{textAlign: "right"}}>CALIBRATED</span>
        </div>
        {rows.map((row, i) => {
          // sum applied deltas based on phase
          let appliedDelta = 0;
          for (let p = 0; p < 3; p++) {
            if (phase > p) appliedDelta += row.factors[p].delta;
            else if (phase === p) appliedDelta += row.factors[p].delta * ease(clampTM(inPhase));
          }
          const calibrated = Math.max(0, Math.min(100, row.raw + appliedDelta));
          const calColor =
            calibrated >= 80 ? "var(--sev-critical)" :
            calibrated >= 60 ? "var(--sev-high)" :
            calibrated >= 40 ? "var(--sev-medium)" : "var(--ink-700)";
          const currentFactor = phase < 3 ? row.factors[phase] : row.factors[2];
          const factorOpacity = phase < 3 ? clampTM(inPhase * 1.5) : clampTM((9 - t) / 1.5);
          return (
            <div key={row.id} className="tm-phy-row">
              <div className="tm-phy-defect">
                <div className="pm-mono" style={{fontSize: 10, color: "var(--ink-500)"}}>{row.id}</div>
                <div style={{fontSize: 12, color: "var(--ink-1000)"}}>{row.type}</div>
              </div>
              <div className="tm-phy-raw">
                <span className="pm-mono" style={{fontSize: 13, color: "var(--ink-500)"}}>{row.raw}</span>
                <span className="tm-phy-bar"><span className="tm-phy-bar-fill" style={{width: `${row.raw}%`, background: "var(--ink-400)"}}></span></span>
              </div>
              <div className="tm-phy-factor" style={{opacity: factorOpacity}}>
                <span style={{color: currentFactor.delta >= 0 ? "var(--sev-critical)" : "var(--status-ok)", fontWeight: 600}}>
                  {currentFactor.delta >= 0 ? "+" : ""}{currentFactor.delta}
                </span>
                <span className="tm-phy-factor-label">{currentFactor.label}</span>
              </div>
              <div className="tm-phy-cal">
                <span className="tm-phy-bar"><span className="tm-phy-bar-fill" style={{width: `${calibrated}%`, background: calColor, transition: "width 320ms"}}></span></span>
                <span className="pm-mono" style={{fontSize: 13, color: calColor, fontWeight: 600}}>{Math.round(calibrated)}</span>
              </div>
            </div>
          );
        })}
      </div>

      <div className="pm-foot">
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Calibration model</span>
          <span className="pm-mono">EN 1504 · structural reasoning</span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// 05 — HISTORY: campaign comparison. Two timelines stacked,
// new + worsening defects pulse on the latest campaign.
// ============================================================
function HistoryMockup() {
  const t = useTechLoop(9);

  // Two campaigns: 2023 (baseline), 2024 (current)
  // Each defect has chainage + size; some appear only in 2024.
  const baseline = useMemoTM(() => [
    { x: 0.12, s: 4,  id: "CR-01" },
    { x: 0.28, s: 6,  id: "SP-02" },
    { x: 0.52, s: 5,  id: "CR-03" },
    { x: 0.74, s: 7,  id: "DF-01" },
  ], []);

  const current = useMemoTM(() => [
    { x: 0.12, s: 4,  id: "CR-01", status: "stable" },
    { x: 0.28, s: 9,  id: "SP-02", status: "worsening", delta: "+3 mm" },
    { x: 0.40, s: 8,  id: "CR-04", status: "new" },
    { x: 0.52, s: 5,  id: "CR-03", status: "stable" },
    { x: 0.66, s: 6,  id: "SP-07", status: "new" },
    { x: 0.74, s: 11, id: "DF-01", status: "worsening", delta: "+2.4 mm" },
    { x: 0.88, s: 5,  id: "CR-07", status: "new" },
  ], []);

  // Reveal animation: baseline first, then current, then pulses
  const baseReveal = clampTM(t / 1.2);
  const currReveal = clampTM((t - 1.4) / 1.6);
  const pulse = t > 3.5 ? (Math.sin((t - 3.5) * 4) + 1) / 2 : 0;

  const newCount = current.filter(c => c.status === "new").length;
  const worsCount = current.filter(c => c.status === "worsening").length;

  return (
    <div className="pm-frame">
      <div className="pm-head">
        <div className="pm-head-title">Multi-campaign change detection</div>
        <div className="pm-head-stat">
          <span className="pm-mono pm-dim">comparing</span>
          <span className="pm-mono">2023 → 2024</span>
        </div>
      </div>

      <div className="pm-body" style={{padding: "16px 18px", display: "flex", flexDirection: "column", gap: 14, justifyContent: "center"}}>
        <svg viewBox="0 0 560 180" preserveAspectRatio="xMidYMid meet" style={{width: "100%", height: "100%", display: "block"}}>
          {/* labels */}
          <text x="0" y="22" fontFamily="JetBrains Mono, monospace" fontSize="10" fill="var(--ink-500)">2023 BASELINE</text>
          <text x="0" y="112" fontFamily="JetBrains Mono, monospace" fontSize="10" fill="var(--ink-1000)" fontWeight="600">2024 CURRENT</text>

          {/* baseline track */}
          <line x1="20" y1="50" x2="540" y2="50" stroke="var(--ink-200)" strokeWidth="1"/>
          {/* current track */}
          <line x1="20" y1="140" x2="540" y2="140" stroke="var(--ink-300)" strokeWidth="1"/>

          {/* chainage ticks */}
          {[0, 0.25, 0.5, 0.75, 1].map((p, i) => {
            const x = 20 + p * 520;
            return (
              <g key={i}>
                <line x1={x} y1="46" x2={x} y2="54" stroke="var(--ink-300)" strokeWidth="0.6"/>
                <line x1={x} y1="136" x2={x} y2="144" stroke="var(--ink-300)" strokeWidth="0.6"/>
                <text x={x} y="170" textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="9" fill="var(--ink-500)">{(400 + p * 360).toFixed(0)}m</text>
              </g>
            );
          })}

          {/* baseline defects */}
          {baseline.map((d, i) => {
            const x = 20 + d.x * 520;
            const itemReveal = clampTM((baseReveal - i * 0.08) * 5);
            return (
              <g key={i} opacity={itemReveal}>
                <circle cx={x} cy="50" r={d.s} fill="var(--ink-400)" opacity="0.55"/>
                <circle cx={x} cy="50" r={d.s} fill="none" stroke="var(--ink-500)" strokeWidth="0.8"/>
              </g>
            );
          })}

          {/* current defects + change markers */}
          {current.map((d, i) => {
            const x = 20 + d.x * 520;
            const itemReveal = clampTM((currReveal - i * 0.08) * 5);
            const color =
              d.status === "new"       ? "#DC2626" :
              d.status === "worsening" ? "#EA580C" : "var(--ink-700)";
            const pulseScale = (d.status === "new" || d.status === "worsening") ? 1 + pulse * 0.3 : 1;
            return (
              <g key={i} opacity={itemReveal} transform={`translate(${x} 140)`}>
                <circle cx="0" cy="0" r={d.s * pulseScale} fill={color} fillOpacity={d.status === "stable" ? 0.4 : 0.18}/>
                <circle cx="0" cy="0" r={d.s} fill={color} fillOpacity={d.status === "stable" ? 0.55 : 0.85}/>
                {/* connector lines for worsening (compare to baseline) */}
                {d.status === "worsening" && (
                  <line x1="0" y1={-d.s - 2} x2="0" y2={-90 + 12} stroke={color} strokeWidth="0.8" strokeDasharray="2 2" opacity="0.7"/>
                )}
                {d.status === "new" && (
                  <text x="0" y={d.s + 14} textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="8.5" fill={color} fontWeight="600">NEW</text>
                )}
                {d.status === "worsening" && d.delta && (
                  <text x="0" y={d.s + 14} textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="8.5" fill={color} fontWeight="600">{d.delta}</text>
                )}
              </g>
            );
          })}
        </svg>
      </div>

      <div className="pm-foot">
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Change summary</span>
          <span style={{display: "flex", gap: 10}}>
            <span className="pm-mono" style={{color: "#DC2626"}}>● {newCount} new</span>
            <span className="pm-mono" style={{color: "#EA580C"}}>● {worsCount} worsening</span>
            <span className="pm-mono" style={{color: "var(--ink-700)"}}>● {current.length - newCount - worsCount} stable</span>
          </span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// 06 — HUMAN: engineer review queue, rows resolve sequentially,
// audit log entries stack at the bottom.
// ============================================================
function HumanMockup() {
  const t = useTechLoop(10);

  const items = useMemoTM(() => [
    { id: "CR-04", defect: "Longitudinal crack",   reviewer: "JC", action: "approved", color: "var(--status-ok)",  resolve: 1.4 },
    { id: "CR-02", defect: "Transverse crack",     reviewer: "JC", action: "approved", color: "var(--status-ok)",  resolve: 2.4 },
    { id: "SP-12", defect: "Concrete spalling",    reviewer: "MA", action: "edited",   color: "var(--sev-medium)", resolve: 3.6, edit: "S3 → S2" },
    { id: "CR-07", defect: "Shear crack",          reviewer: "RN", action: "queried",  color: "var(--status-info)",resolve: 4.8, note: "re-scan requested" },
    { id: "DF-02", defect: "Deformation",          reviewer: "JC", action: "approved", color: "var(--status-ok)",  resolve: 5.8 },
  ], []);

  // Audit log lines appear at resolve times
  const log = items
    .filter(it => t > it.resolve + 0.1)
    .map(it => `[${(400 + Math.floor(Math.random() * 9 + items.indexOf(it))).toString().padStart(3,'0')}] ${it.reviewer} · ${it.action} · ${it.id}${it.edit ? ` (${it.edit})` : ""}`);

  return (
    <div className="pm-frame">
      <div className="pm-head">
        <div className="pm-head-title">Engineer review · approve / edit / reject</div>
        <div className="pm-head-stat" style={{gap: 0}}>
          {["JC", "MA", "RN", "SP"].map((a, i) => (
            <span key={i} className="pm-avatar">{a}</span>
          ))}
        </div>
      </div>

      <div className="pm-body" style={{padding: "10px 16px", display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 14}}>
        {/* Left: review queue */}
        <div style={{display: "flex", flexDirection: "column", gap: 6, minWidth: 0}}>
          {items.map((it, i) => {
            const visible = clampTM(t * 1.4 - i * 0.18);
            const resolved = t > it.resolve;
            return (
              <div key={it.id} className="tm-hum-row" style={{opacity: visible, transform: `translateX(${(1 - visible) * 10}px)`}}>
                <span className="pm-mono" style={{fontSize: 10, color: "var(--ink-500)", width: 64}}>{it.id}</span>
                <span className="tm-hum-defect">{it.defect}</span>
                {resolved ? (
                  <span className="tm-hum-action" style={{color: it.color, borderColor: it.color}}>
                    <span className="pm-avatar small" style={{background: "var(--ink-100)", color: "var(--ink-1000)", border: "1px solid var(--ink-200)"}}>{it.reviewer}</span>
                    {it.action}{it.edit ? ` · ${it.edit}` : ""}
                  </span>
                ) : (
                  <span className="tm-hum-action" style={{color: "var(--ink-500)", borderColor: "var(--ink-200)", borderStyle: "dashed"}}>
                    pending
                  </span>
                )}
              </div>
            );
          })}
        </div>

        {/* Right: audit log */}
        <div className="tm-audit">
          <div className="pm-mono pm-dim" style={{fontSize: 9.5, marginBottom: 6}}>AUDIT LOG · LIVE</div>
          <div className="tm-audit-lines">
            {log.length === 0 ? (
              <div className="pm-mono pm-dim" style={{fontSize: 10}}>awaiting actions…</div>
            ) : log.map((line, i) => (
              <div key={i} className="tm-audit-line pm-mono">
                <span style={{color: "var(--status-ok)"}}>›</span> {line}
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="pm-foot">
        <div className="pm-foot-row">
          <span className="pm-mono pm-dim">Provenance</span>
          <span className="pm-mono">every action signed, timestamped, reversible</span>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  VisionMockup, PointCloudMockup, PhysicsMockup, HistoryMockup, HumanMockup,
});
