const { useState, useRef, useEffect, useMemo, useCallback } = React;
/* ------------------------------------------------------------------ *
 *  Pixel Pitch & Density Scaler
 *  Illustrates how LED/video-wall pixel pitch drives resolution,
 *  density, and perceived sharpness across dimensions and distances.
 *
 *  Core model (standard display-acuity physics):
 *    cols       = floor(width_mm  / pitch_mm)
 *    rows       = floor(height_mm / pitch_mm)
 *    PPI        = 25.4 / pitch_mm
 *    px / m^2   = (1000 / pitch_mm)^2
 *    retina D   = pitch_mm / (acuity_arcmin * ARCMIN_RAD)   [mm]
 *    px angle   = pitch_mm / viewDist_mm  -> /ARCMIN_RAD = arcmin
 *    PPD        = 60 / pxAngle_arcmin
 * ------------------------------------------------------------------ */

const MM_PER_M = 1000;
const MM_PER_FT = 304.8;
const MM_PER_IN = 25.4;
const ARCMIN_RAD = Math.PI / (180 * 60); // radians subtended by one arcminute

const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
const fmt = (n, d = 0) =>
  n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });

const PITCH_PRESETS = [0.9, 1.2, 1.5, 1.9, 2.5, 2.9, 3.9, 4.8, 6.0, 10.0];
const ACUITY = [
  { label: "20/10", arcmin: 0.5, note: "sharp eyes" },
  { label: "20/15", arcmin: 0.75, note: "above avg" },
  { label: "20/20", arcmin: 1.0, note: "normal" },
  { label: "20/40", arcmin: 2.0, note: "below avg" },
];
const DIM_PRESETS = [
  { label: "LED ceiling", w: 22, h: 16, unit: "ft" },
  { label: "Stage wall", w: 6, h: 3, unit: "m" },
  { label: "Storefront", w: 3, h: 1.5, unit: "m" },
  { label: "Billboard", w: 14, h: 48, unit: "ft" },
];
const PATCH_PRESETS = [50, 100, 200];
const COMPARE_PITCHES = [0.9, 1.5, 2.5, 3.9, 6.0, 10.0];

/* warm "scene" sampled by the dot renderer so blending is visible:
   an off-center bright disc with a ring on a dark field */
function colorAt(u, v) {
  const cx = 0.42,
    cy = 0.46,
    r = 0.34;
  const d = Math.hypot(u - cx, v - cy);
  if (d < r) {
    const t = 1 - d / r;
    const g = 70 + t * 150;
    return [255, 150 + t * 90, 40 + t * 60]; // amber core
  }
  if (d < r + 0.04) return [120, 60, 20]; // ring
  // faint diagonal background wash so empty space isn't pure black
  const wash = 14 + 18 * (u * 0.5 + v * 0.5);
  return [wash, wash * 0.9, wash * 1.05];
}

/* draws crisp dots into an offscreen canvas, returns it */
function makeDots(sizePx, patchMm, pitchMm) {
  const cols = clamp(Math.round(patchMm / pitchMm), 1, 600);
  const off = document.createElement("canvas");
  off.width = sizePx;
  off.height = sizePx;
  const ctx = off.getContext("2d");
  ctx.fillStyle = "#08090b";
  ctx.fillRect(0, 0, sizePx, sizePx);
  const cell = sizePx / cols;
  const useRect = cell < 6;
  const r = cell * 0.38;
  for (let y = 0; y < cols; y++) {
    for (let x = 0; x < cols; x++) {
      const u = (x + 0.5) / cols;
      const v = (y + 0.5) / cols;
      const [rr, gg, bb] = colorAt(u, v);
      ctx.fillStyle = `rgb(${rr | 0},${gg | 0},${bb | 0})`;
      const px = x * cell + cell / 2;
      const py = y * cell + cell / 2;
      if (useRect) {
        const s = Math.max(1, cell * 0.78);
        ctx.fillRect(px - s / 2, py - s / 2, s, s);
      } else {
        ctx.beginPath();
        ctx.arc(px, py, r, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }
  return { canvas: off, cols };
}

function GridCanvas({ patchMm, pitchMm, blurPx = 0, height = 240, label, sub }) {
  const ref = useRef(null);
  useEffect(() => {
    const cvs = ref.current;
    if (!cvs) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const size = Math.round(height * dpr);
    cvs.width = size;
    cvs.height = size;
    const ctx = cvs.getContext("2d");
    ctx.clearRect(0, 0, size, size);
    const { canvas: dots } = makeDots(size, patchMm, pitchMm);
    ctx.filter = blurPx > 0.05 ? `blur(${blurPx * dpr}px)` : "none";
    ctx.drawImage(dots, 0, 0);
    ctx.filter = "none";
  }, [patchMm, pitchMm, blurPx, height]);
  return (
    <div className="pps-canvas-wrap">
      <canvas ref={ref} style={{ width: height, height: height }} className="pps-canvas" />
      {label && (
        <div className="pps-canvas-cap">
          <span>{label}</span>
          {sub && <em>{sub}</em>}
        </div>
      )}
    </div>
  );
}

/* small inline Heroicons (outline) — no emoji */
const Icon = {
  squares: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
      <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h1.5A2.25 2.25 0 019.75 6v1.5A2.25 2.25 0 017.5 9.75H6A2.25 2.25 0 013.75 7.5V6zM14.25 6A2.25 2.25 0 0116.5 3.75H18A2.25 2.25 0 0120.25 6v1.5A2.25 2.25 0 0118 9.75h-1.5A2.25 2.25 0 0114.25 7.5V6zM3.75 16.5A2.25 2.25 0 016 14.25h1.5A2.25 2.25 0 019.75 16.5V18A2.25 2.25 0 017.5 20.25H6A2.25 2.25 0 013.75 18v-1.5zM14.25 16.5a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-1.5A2.25 2.25 0 0114.25 18v-1.5z" />
    </svg>
  ),
  eye: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
      <path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
      <path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
    </svg>
  ),
  ruler: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
      <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v16.5h16.5M7.5 15.75l3-3 3 3 4.5-4.5" />
    </svg>
  ),
  adjust: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
      <path strokeLinecap="round" strokeLinejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
    </svg>
  ),
};

function App() {
  const [unit, setUnit] = useState("ft");
  const [width, setWidth] = useState(22);
  const [height, setHeight] = useState(16);
  const [pitch, setPitch] = useState(2.5);
  const [acuityArcmin, setAcuityArcmin] = useState(1.0);
  const [viewDist, setViewDist] = useState(6); // in current unit
  const [patchMm, setPatchMm] = useState(100);
  const [showModel, setShowModel] = useState(false);

  useEffect(() => {
    const l = document.createElement("link");
    l.rel = "stylesheet";
    l.href =
      "https://fonts.googleapis.com/css2?family=Chivo:wght@400;700;900&family=JetBrains+Mono:wght@400;500;700&display=swap";
    document.head.appendChild(l);
    return () => {
      document.head.removeChild(l);
    };
  }, []);

  const toMM = useCallback((v) => v * (unit === "ft" ? MM_PER_FT : MM_PER_M), [unit]);

  const m = useMemo(() => {
    const wmm = toMM(width);
    const hmm = toMM(height);
    const cols = Math.max(0, Math.floor(wmm / pitch));
    const rows = Math.max(0, Math.floor(hmm / pitch));
    const total = cols * rows;
    const ppi = MM_PER_IN / pitch;
    const ppm2 = Math.pow(MM_PER_M / pitch, 2);
    const retinaMM = pitch / (acuityArcmin * ARCMIN_RAD);
    const distMM = toMM(viewDist);
    const pxArcmin = distMM > 0 ? pitch / distMM / ARCMIN_RAD : Infinity;
    const ppd = pxArcmin > 0 ? 60 / pxArcmin : 0;
    const past = distMM >= retinaMM; // pixels indistinguishable
    const aspect = rows > 0 ? cols / rows : 0;
    return { wmm, hmm, cols, rows, total, ppi, ppm2, retinaMM, pxArcmin, ppd, past, aspect, distMM };
  }, [width, height, pitch, acuityArcmin, viewDist, toMM]);

  // perceived blur: eye resolves detail no finer than acuity angle projected onto the patch
  const perceivedBlur = useMemo(() => {
    const eyeMm = m.distMM * acuityArcmin * ARCMIN_RAD; // smallest resolved detail on screen, mm
    const px = (eyeMm / patchMm) * 240 * 0.55;
    return clamp(px, 0, 60);
  }, [m.distMM, acuityArcmin, patchMm]);

  const unitLabel = unit === "ft" ? "ft" : "m";
  const distAltLabel =
    unit === "ft"
      ? `${fmt((m.distMM / MM_PER_M), 1)} m`
      : `${fmt(m.distMM / MM_PER_FT, 1)} ft`;
  const retinaInUnit =
    unit === "ft" ? m.retinaMM / MM_PER_FT : m.retinaMM / MM_PER_M;

  // proportional screen diagram
  const diagW = 300;
  const diagH = m.wmm > 0 ? clamp((m.hmm / m.wmm) * diagW, 40, 240) : 120;

  return (
    <div className="pps-root">
      <style>{CSS}</style>

      <header className="pps-head">
        <div className="pps-head-mark" />
        <div>
          <h1>Pixel Pitch · Density Scaler</h1>
          <p>Resolution, density and perceived sharpness across dimensions and viewing distance</p>
        </div>
      </header>

      <div className="pps-grid">
        {/* ---------------- controls ---------------- */}
        <section className="pps-card pps-controls">
          <div className="pps-sec-title">{Icon.ruler}<span>Surface dimensions</span></div>

          <div className="pps-unit-toggle">
            {["ft", "m"].map((u) => (
              <button
                key={u}
                className={unit === u ? "on" : ""}
                onClick={() => setUnit(u)}
              >
                {u}
              </button>
            ))}
          </div>

          <div className="pps-dim-row">
            <label>
              <span>Width</span>
              <input
                type="number"
                min="0.1"
                step="0.1"
                value={width}
                onChange={(e) => setWidth(clamp(parseFloat(e.target.value) || 0, 0.1, 1000))}
              />
              <em>{unitLabel}</em>
            </label>
            <span className="pps-times">×</span>
            <label>
              <span>Height</span>
              <input
                type="number"
                min="0.1"
                step="0.1"
                value={height}
                onChange={(e) => setHeight(clamp(parseFloat(e.target.value) || 0, 0.1, 1000))}
              />
              <em>{unitLabel}</em>
            </label>
          </div>

          <div className="pps-chips">
            {DIM_PRESETS.map((d) => (
              <button
                key={d.label}
                onClick={() => {
                  setUnit(d.unit);
                  setWidth(d.w);
                  setHeight(d.h);
                }}
              >
                {d.label}
              </button>
            ))}
          </div>

          <div className="pps-sec-title pps-mt">{Icon.squares}<span>Pixel pitch</span></div>
          <div className="pps-pitch-readout">
            <strong>{pitch.toFixed(1)}</strong>
            <span>mm</span>
            <em>P{pitch.toFixed(1)}</em>
          </div>
          <input
            type="range"
            min="0.6"
            max="12"
            step="0.1"
            value={pitch}
            onChange={(e) => setPitch(parseFloat(e.target.value))}
            className="pps-range pps-range-heat"
          />
          <div className="pps-chips pps-chips-sm">
            {PITCH_PRESETS.map((p) => (
              <button
                key={p}
                className={Math.abs(p - pitch) < 0.05 ? "on" : ""}
                onClick={() => setPitch(p)}
              >
                P{p}
              </button>
            ))}
          </div>

          <div className="pps-sec-title pps-mt">{Icon.eye}<span>Viewer</span></div>
          <div className="pps-acuity">
            {ACUITY.map((a) => (
              <button
                key={a.label}
                className={Math.abs(a.arcmin - acuityArcmin) < 0.01 ? "on" : ""}
                onClick={() => setAcuityArcmin(a.arcmin)}
              >
                <strong>{a.label}</strong>
                <em>{a.note}</em>
              </button>
            ))}
          </div>

          <div className="pps-pitch-readout pps-mt-sm">
            <strong>{viewDist.toFixed(1)}</strong>
            <span>{unitLabel}</span>
            <em>viewing distance</em>
          </div>
          <input
            type="range"
            min="0.5"
            max={unit === "ft" ? 150 : 50}
            step="0.5"
            value={viewDist}
            onChange={(e) => setViewDist(parseFloat(e.target.value))}
            className="pps-range"
          />
        </section>

        {/* ---------------- readouts ---------------- */}
        <section className="pps-card pps-readout">
          <div className="pps-stats">
            <Stat label="Resolution" value={`${fmt(m.cols)} × ${fmt(m.rows)}`} unit="px" big />
            <Stat label="Total pixels" value={fmt(m.total)} unit="px" />
            <Stat label="Density" value={fmt(m.ppi, 1)} unit="PPI" />
            <Stat label="Areal density" value={fmt(m.ppm2)} unit="px / m²" />
            <Stat label="Aspect" value={m.aspect ? m.aspect.toFixed(2) : "—"} unit=": 1" />
            <Stat
              label={`Retina dist · ${ACUITY.find((a) => Math.abs(a.arcmin - acuityArcmin) < 0.01)?.label || ""}`}
              value={fmt(retinaInUnit, 1)}
              unit={unitLabel}
              wide
            />
            <Stat label="At your distance" value={m.ppd ? fmt(m.ppd, 0) : "—"} unit="px / deg" wide emph />
            <Stat
              label="Pixel angular size"
              value={isFinite(m.pxArcmin) ? m.pxArcmin.toFixed(2) : "—"}
              unit="arcmin"
              wide
              emph
            />
          </div>

          <div className={`pps-verdict ${m.past ? "good" : "warn"}`}>
            <div className="pps-verdict-dot" />
            <div>
              {m.past ? (
                <>
                  <strong>Smooth at this distance.</strong>
                  <span>
                    Individual pixels fall below the {ACUITY.find((a) => Math.abs(a.arcmin - acuityArcmin) < 0.01)?.label}{" "}
                    acuity limit — the grid blends into continuous image. Retina distance is{" "}
                    {fmt(retinaInUnit, 1)} {unitLabel}; you are at {viewDist.toFixed(1)} {unitLabel} ({distAltLabel}).
                  </span>
                </>
              ) : (
                <>
                  <strong>Pixels visible.</strong>
                  <span>
                    Each pixel subtends {m.pxArcmin.toFixed(2)}′, above the{" "}
                    {ACUITY.find((a) => Math.abs(a.arcmin - acuityArcmin) < 0.01)?.label} limit. Back off to{" "}
                    {fmt(retinaInUnit, 1)} {unitLabel} or drop to a finer pitch to hide the grid.
                  </span>
                </>
              )}
            </div>
          </div>
        </section>

        {/* ---------------- scale diagram ---------------- */}
        <section className="pps-card pps-diagram">
          <div className="pps-sec-title">{Icon.ruler}<span>Surface · to scale</span></div>
          <div className="pps-diag-stage">
            <svg width={diagW + 70} height={diagH + 60} className="pps-diag-svg">
              <defs>
                <pattern id="ppsgrid" width="14" height="14" patternUnits="userSpaceOnUse">
                  <path d="M14 0H0V14" fill="none" stroke="rgba(255,150,60,0.10)" strokeWidth="1" />
                </pattern>
              </defs>
              <rect x="40" y="20" width={diagW} height={diagH} fill="url(#ppsgrid)" stroke="rgba(255,150,60,0.55)" strokeWidth="1.5" />
              <text x={40 + diagW / 2} y={diagH + 44} className="pps-diag-lbl" textAnchor="middle">
                {width} {unitLabel} · {fmt(m.cols)} px
              </text>
              <text
                x="22"
                y={20 + diagH / 2}
                className="pps-diag-lbl"
                textAnchor="middle"
                transform={`rotate(-90 22 ${20 + diagH / 2})`}
              >
                {height} {unitLabel} · {fmt(m.rows)} px
              </text>
            </svg>
          </div>
        </section>

        {/* ---------------- loupe vs perceived ---------------- */}
        <section className="pps-card pps-loupe">
          <div className="pps-sec-title">
            {Icon.eye}<span>{patchMm} mm sample</span>
            <div className="pps-patch-toggle">
              {PATCH_PRESETS.map((p) => (
                <button key={p} className={patchMm === p ? "on" : ""} onClick={() => setPatchMm(p)}>
                  {p}
                </button>
              ))}
            </div>
          </div>
          <div className="pps-loupe-row">
            <GridCanvas
              patchMm={patchMm}
              pitchMm={pitch}
              blurPx={0}
              height={232}
              label="Physical grid"
              sub={`${Math.round(patchMm / pitch)} px across`}
            />
            <GridCanvas
              patchMm={patchMm}
              pitchMm={pitch}
              blurPx={perceivedBlur}
              height={232}
              label={`Perceived @ ${viewDist}${unitLabel}`}
              sub={m.past ? "below acuity → blends" : "above acuity → visible"}
            />
          </div>
        </section>

        {/* ---------------- density comparison ---------------- */}
        <section className="pps-card pps-compare">
          <div className="pps-sec-title">{Icon.adjust}<span>Density across pitches · same {patchMm} mm window</span></div>
          <div className="pps-compare-row">
            {COMPARE_PITCHES.map((p) => (
              <div key={p} className={`pps-compare-cell ${Math.abs(p - pitch) < 0.05 ? "active" : ""}`}>
                <GridCanvas patchMm={patchMm} pitchMm={p} blurPx={0} height={120} />
                <button onClick={() => setPitch(p)}>
                  <strong>P{p}</strong>
                  <em>{fmt(MM_PER_IN / p, 0)} PPI</em>
                </button>
              </div>
            ))}
          </div>
        </section>
      </div>

      <button className="pps-model-toggle" onClick={() => setShowModel((s) => !s)}>
        {showModel ? "Hide" : "Show"} model &amp; formulas
      </button>
      {showModel && (
        <div className="pps-card pps-model">
          <pre>{`cols   = floor(width_mm  / pitch_mm)        rows = floor(height_mm / pitch_mm)
total  = cols × rows
PPI    = 25.4 / pitch_mm                    px/m² = (1000 / pitch_mm)²
retina = pitch_mm / (acuity_arcmin × ${ARCMIN_RAD.toExponential(3)})   [mm]   ≈ 3.44 × pitch_mm m  @ 20/20
pxAng  = (pitch_mm / dist_mm) / ${ARCMIN_RAD.toExponential(3)}          [arcmin]
PPD    = 60 / pxAng_arcmin
perceived blur ∝ dist_mm × acuity_arcmin × ARCMIN_RAD   (eye's finest resolvable detail on the surface)`}</pre>
          <p>
            Acuity presets map Snellen ratios to resolved angle: 20/20 → 1′, 20/15 → 0.75′, 20/10 → 0.5′,
            20/40 → 2′. If your documentation uses a different distance rule (e.g. a vendor "10× pitch"
            minimum) or a non-Snellen acuity model, point me at it and I'll swap the constant.
          </p>
        </div>
      )}
    </div>
  );
}

function Stat({ label, value, unit, big, wide, emph }) {
  const cls = ["pps-stat", big && "big", wide && "wide", emph && "emph"].filter(Boolean).join(" ");
  return (
    <div className={cls}>
      <span className="pps-stat-lbl">{label}</span>
      <span className="pps-stat-val">
        {value}
        {unit && <em>{unit}</em>}
      </span>
    </div>
  );
}

const CSS = `
.pps-root{
  --bg:#0c0e12; --panel:#13161c; --panel2:#171b22; --line:#262b34;
  --ink:#e7eaf0; --dim:#8b929e; --heat:#ff8a3c; --heat2:#ff5638; --cyan:#56d4d4; --good:#4ad07a;
  font-family:'Chivo',system-ui,sans-serif; background:var(--bg); color:var(--ink);
  padding:22px; min-height:100%; box-sizing:border-box;
  background-image:radial-gradient(circle at 18% 0%,rgba(255,138,60,0.07),transparent 42%),radial-gradient(circle at 100% 100%,rgba(86,212,212,0.05),transparent 40%);
}
.pps-root *{box-sizing:border-box}
.pps-head{display:flex;align-items:center;gap:14px;margin-bottom:20px}
.pps-head-mark{width:10px;height:38px;background:linear-gradient(180deg,var(--heat),var(--heat2));border-radius:2px;box-shadow:0 0 18px rgba(255,90,56,0.5)}
.pps-head h1{font-size:22px;font-weight:900;letter-spacing:-0.02em;margin:0;text-transform:uppercase}
.pps-head p{margin:2px 0 0;color:var(--dim);font-size:12.5px;letter-spacing:0.01em}
.pps-grid{display:grid;grid-template-columns:300px 1fr;gap:14px;align-items:start}
@media(max-width:880px){.pps-grid{grid-template-columns:1fr}}
.pps-card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px}
.pps-controls{grid-row:span 3}
@media(max-width:880px){.pps-controls{grid-row:auto}}
.pps-sec-title{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--dim);margin-bottom:12px}
.pps-sec-title svg{width:16px;height:16px;color:var(--heat)}
.pps-mt{margin-top:22px}
.pps-mt-sm{margin-top:16px}

.pps-unit-toggle{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;margin-bottom:12px}
.pps-unit-toggle button{background:transparent;color:var(--dim);border:none;padding:6px 16px;font-family:'JetBrains Mono',monospace;font-size:12px;cursor:pointer}
.pps-unit-toggle button.on{background:var(--heat);color:#1a0d05;font-weight:700}

.pps-dim-row{display:flex;align-items:flex-end;gap:8px}
.pps-dim-row label{flex:1;display:flex;flex-direction:column;gap:4px;position:relative}
.pps-dim-row label span{font-size:10.5px;color:var(--dim);letter-spacing:0.08em;text-transform:uppercase}
.pps-dim-row input{background:var(--panel2);border:1px solid var(--line);border-radius:8px;color:var(--ink);padding:9px 30px 9px 10px;font-family:'JetBrains Mono',monospace;font-size:15px;width:100%}
.pps-dim-row input:focus{outline:none;border-color:var(--heat)}
.pps-dim-row label em{position:absolute;right:10px;bottom:10px;font-style:normal;color:var(--dim);font-size:11px;font-family:'JetBrains Mono',monospace}
.pps-times{color:var(--dim);padding-bottom:9px;font-size:14px}

.pps-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
.pps-chips button{background:var(--panel2);border:1px solid var(--line);color:var(--dim);border-radius:7px;padding:6px 9px;font-size:11px;cursor:pointer;font-family:'JetBrains Mono',monospace;transition:.15s}
.pps-chips button:hover{border-color:var(--heat);color:var(--ink)}
.pps-chips button.on{background:rgba(255,138,60,0.16);border-color:var(--heat);color:var(--heat)}
.pps-chips-sm button{padding:4px 7px}

.pps-pitch-readout{display:flex;align-items:baseline;gap:6px;margin-bottom:8px}
.pps-pitch-readout strong{font-family:'JetBrains Mono',monospace;font-size:30px;font-weight:700;color:var(--heat);line-height:1}
.pps-pitch-readout span{color:var(--dim);font-size:13px;font-family:'JetBrains Mono',monospace}
.pps-pitch-readout em{margin-left:auto;font-style:normal;color:var(--dim);font-size:11px;letter-spacing:0.08em;text-transform:uppercase}

.pps-range{width:100%;-webkit-appearance:none;height:5px;border-radius:4px;background:var(--panel2);outline:none}
.pps-range-heat{background:linear-gradient(90deg,var(--heat2),var(--heat),#ffd089)}
.pps-range::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;border-radius:50%;background:#fff;border:3px solid var(--heat);cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.5)}
.pps-range::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:#fff;border:3px solid var(--heat);cursor:pointer}

.pps-acuity{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.pps-acuity button{background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:8px;cursor:pointer;display:flex;flex-direction:column;align-items:flex-start;gap:1px;transition:.15s}
.pps-acuity button strong{font-family:'JetBrains Mono',monospace;font-size:14px;color:var(--ink)}
.pps-acuity button em{font-style:normal;font-size:9.5px;color:var(--dim);letter-spacing:0.04em}
.pps-acuity button.on{border-color:var(--cyan);background:rgba(86,212,212,0.12)}
.pps-acuity button.on strong{color:var(--cyan)}

.pps-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);border-radius:10px;overflow:hidden}
@media(max-width:620px){.pps-stats{grid-template-columns:repeat(2,1fr)}}
.pps-stat{background:var(--panel2);padding:12px 13px;display:flex;flex-direction:column;gap:5px}
.pps-stat.big{grid-column:span 2}
.pps-stat.wide{grid-column:span 2}
.pps-stat.emph .pps-stat-val{font-size:23px}
.pps-stat.emph .pps-stat-val em{font-size:12px}
@media(max-width:620px){.pps-stat.wide{grid-column:span 2}}
.pps-stat-lbl{font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--dim)}
.pps-stat-val{font-family:'JetBrains Mono',monospace;font-size:19px;font-weight:500;color:var(--ink);display:flex;align-items:baseline;gap:5px}
.pps-stat.big .pps-stat-val{font-size:24px;color:var(--heat)}
.pps-stat-val em{font-style:normal;font-size:11px;color:var(--dim)}

.pps-verdict{display:flex;gap:12px;align-items:flex-start;margin-top:14px;padding:14px;border-radius:10px;border:1px solid var(--line)}
.pps-verdict.good{background:rgba(74,208,122,0.07);border-color:rgba(74,208,122,0.3)}
.pps-verdict.warn{background:rgba(255,90,56,0.07);border-color:rgba(255,90,56,0.3)}
.pps-verdict-dot{width:10px;height:10px;border-radius:50%;margin-top:4px;flex:none}
.pps-verdict.good .pps-verdict-dot{background:var(--good);box-shadow:0 0 10px var(--good)}
.pps-verdict.warn .pps-verdict-dot{background:var(--heat2);box-shadow:0 0 10px var(--heat2)}
.pps-verdict strong{display:block;font-size:13.5px;margin-bottom:3px}
.pps-verdict span{font-size:12px;color:var(--dim);line-height:1.5}

.pps-diag-stage{display:flex;justify-content:center;padding:6px 0}
.pps-diag-lbl{fill:var(--dim);font-family:'JetBrains Mono',monospace;font-size:11px}

.pps-patch-toggle{margin-left:auto;display:inline-flex;gap:4px}
.pps-patch-toggle button{background:var(--panel2);border:1px solid var(--line);color:var(--dim);border-radius:6px;padding:3px 8px;font-size:10.5px;cursor:pointer;font-family:'JetBrains Mono',monospace}
.pps-patch-toggle button.on{border-color:var(--heat);color:var(--heat)}
.pps-loupe-row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center}
.pps-canvas-wrap{display:flex;flex-direction:column;gap:7px;align-items:center}
.pps-canvas{border-radius:10px;border:1px solid var(--line);background:#08090b;display:block}
.pps-canvas-cap{display:flex;flex-direction:column;align-items:center;gap:1px}
.pps-canvas-cap span{font-size:11px;font-weight:700;letter-spacing:0.06em}
.pps-canvas-cap em{font-style:normal;font-size:10px;color:var(--dim);font-family:'JetBrains Mono',monospace}

.pps-compare{grid-column:1 / -1}
.pps-compare-row{display:grid;grid-template-columns:repeat(6,1fr);gap:10px}
@media(max-width:720px){.pps-compare-row{grid-template-columns:repeat(3,1fr)}}
.pps-compare-cell{display:flex;flex-direction:column;gap:6px;align-items:center;padding:6px;border-radius:10px;border:1px solid transparent;min-width:0}
.pps-compare-cell.active{border-color:var(--heat);background:rgba(255,138,60,0.07)}
.pps-compare-cell .pps-canvas{width:100%!important;height:auto!important;aspect-ratio:1}
.pps-compare-cell button{background:transparent;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:0;color:var(--ink)}
.pps-compare-cell button strong{font-family:'JetBrains Mono',monospace;font-size:13px}
.pps-compare-cell button em{font-style:normal;font-size:10px;color:var(--dim);font-family:'JetBrains Mono',monospace}

.pps-model-toggle{margin-top:14px;background:transparent;border:1px solid var(--line);color:var(--dim);border-radius:8px;padding:8px 14px;font-size:12px;cursor:pointer;font-family:'JetBrains Mono',monospace}
.pps-model-toggle:hover{border-color:var(--heat);color:var(--ink)}
.pps-model{margin-top:12px}
.pps-model pre{font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--cyan);line-height:1.7;overflow-x:auto;margin:0 0 12px}
.pps-model p{font-size:12px;color:var(--dim);line-height:1.6;margin:0}
`;

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
