/* emotype.jsx — emotype card + case study page.

   "Text carries meaning. Form carries affect."

   The projects bento card is intentionally minimal: a canvas waveform
   flows left-to-right, the title "emotype" sits on top of it, and the
   waveform reshapes itself under the mouse — pointer X moves frequency,
   pointer Y moves amplitude. The full 8-preset specimen lives on the
   case-study page (/projects/emotype), not in the card. */

const { useState: useStateEM, useEffect: useEffectEM, useMemo: useMemoEM, useRef: useRefEM } = React;

const EMOTYPE_REPO = "https://github.com/TaewoooPark/emotype";

/* 8 emotion design tokens, kept entirely inside the site's font stack.
   Only used by the case-study EmotypePresetMatrix + EmotypeRadar; the
   bento card no longer references the per-emotion morph. */
const EM_EMOTIONS = [
  { id: "happiness", mood: "excited",   font: "kr",      weight: 700, italic: false, motion: "bounce",  color: "#EA580C", valence:  0.78, arousal:  0.70, intensity: 0.85 },
  { id: "anger",     mood: "tense",     font: "kr",      weight: 900, italic: false, motion: "shake",   color: "#DC2626", valence: -0.70, arousal:  0.82, intensity: 0.90 },
  { id: "sadness",   mood: "depressed", font: "display", weight: 400, italic: true,  motion: "slump",   color: "#2F5B4F", valence: -0.65, arousal: -0.45, intensity: 0.50 },
  { id: "fear",      mood: "tense",     font: "kr",      weight: 300, italic: false, motion: "jitter",  color: "#3B82F6", valence: -0.55, arousal:  0.60, intensity: 0.70 },
  { id: "disgust",   mood: "tense",     font: "mono",    weight: 500, italic: false, motion: "warp",    color: "#37F712", valence: -0.45, arousal:  0.30, intensity: 0.55 },
  { id: "surprise",  mood: "excited",   font: "kr",      weight: 600, italic: false, motion: "pop",     color: "#DB2777", valence:  0.40, arousal:  0.80, intensity: 0.80 },
  { id: "contempt",  mood: "depressed", font: "display", weight: 600, italic: true,  motion: "tilt",    color: "#111111", valence: -0.40, arousal: -0.20, intensity: 0.45 },
  { id: "neutral",   mood: "content",   font: "kr",      weight: 400, italic: false, motion: "static",  color: "#0C0C09", valence:  0.00, arousal:  0.00, intensity: 0.20 },
];

function emFontFamily(key) {
  if (key === "display") return "var(--font-display)";
  if (key === "mono")    return "var(--font-mono)";
  return "var(--font-kr)";
}

function emMotionStyle(motion, t) {
  switch (motion) {
    case "bounce":
      return { transform: `translateY(${Math.sin(t * 6) * 7}px)` };
    case "shake":
      return {
        transform: `translateX(${(((t * 17) % 1) - 0.5) * 8}px) translateY(${(((t * 23) % 1) - 0.5) * 4}px) rotate(${(((t * 11) % 1) - 0.5) * 2}deg)`,
      };
    case "slump": {
      const dt = (t % 3);
      return { transform: `translateY(${dt * 8}px)`, opacity: Math.max(0.45, 1 - dt * 0.15) };
    }
    case "jitter":
      return {
        transform: `translateX(${(((t * 29) % 1) - 0.5) * 3}px)`,
        opacity: 0.55 + 0.45 * (((t * 41) % 1)),
      };
    case "warp":
      return { transform: `skewX(${Math.sin(t * 3) * 9}deg) scaleX(${1 + Math.sin(t * 2) * 0.06})` };
    case "pop":
      return { transform: `scale(${1 + Math.sin(t * 7) * 0.09})` };
    case "tilt":
      return { transform: `rotate(${Math.sin(t * 1.4) * 3.5}deg) translateY(${Math.sin(t * 1.4) * 2}px)` };
    case "static":
    default:
      return {};
  }
}

function useEmTick(active = true) {
  const [t, setT] = useStateEM(0);
  useEffectEM(() => {
    if (!active) return;
    let raf;
    const start = performance.now();
    const loop = (now) => {
      setT((now - start) / 1000);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [active]);
  return t;
}

function useEmCycle({ dwellMs = 3200, pinIndex = null, paused = false } = {}) {
  const [idx, setIdx] = useStateEM(0);
  useEffectEM(() => {
    if (paused || pinIndex !== null) return;
    const h = setInterval(() => setIdx(i => (i + 1) % EM_EMOTIONS.length), dwellMs);
    return () => clearInterval(h);
  }, [dwellMs, paused, pinIndex]);
  const activeIdx = pinIndex !== null ? pinIndex : idx;
  return [EM_EMOTIONS[activeIdx], activeIdx, setIdx];
}

/* ──────────────────────────────────────────────────────────────
   EmotypeBigGlyph — used by EmotypeRadar to render the centre word
   with the active emotion's typographic dial + per-frame motion.
   ────────────────────────────────────────────────────────────── */
function EmotypeBigGlyph({ emotion, t, size = 64, word = "괜찮아" }) {
  const motion = emMotionStyle(emotion.motion, t);
  return (
    <span style={{
      display: "inline-block",
      fontFamily: emFontFamily(emotion.font),
      fontWeight: emotion.weight,
      fontStyle: emotion.italic ? "italic" : "normal",
      fontSize: size,
      lineHeight: 1,
      color: "var(--fg)",
      letterSpacing: emotion.motion === "warp" ? "0.05em" : "-0.01em",
      transition: "font-family 700ms var(--easing-default), font-weight 700ms var(--easing-default), font-style 700ms var(--easing-default), letter-spacing 700ms var(--easing-default)",
      willChange: "transform",
      ...motion,
    }}>
      {word}
    </span>
  );
}

/* ──────────────────────────────────────────────────────────────
   EmMotionGlyph — one of 8 motion-essence SVGs. Used only inside
   EmotypePresetMatrix on the case-study page.
   ────────────────────────────────────────────────────────────── */
function EmMotionGlyph({ motion, size = 22 }) {
  const stroke = "currentColor";
  const sw = 0.9;
  switch (motion) {
    case "bounce":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <polyline points="2,16 5,9 8,16 11,9 14,16 17,9 20,16" fill="none" stroke={stroke} strokeWidth={sw} />
      </svg>);
    case "shake":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <polyline points="2,11 5,4 8,18 11,3 14,19 17,5 20,11" fill="none" stroke={stroke} strokeWidth={sw} />
      </svg>);
    case "slump":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <line x1="2"  y1="5"  x2="6"  y2="9"  stroke={stroke} strokeWidth={sw} />
        <line x1="8"  y1="10" x2="12" y2="14" stroke={stroke} strokeWidth={sw} />
        <line x1="14" y1="15" x2="18" y2="19" stroke={stroke} strokeWidth={sw} />
      </svg>);
    case "jitter":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        {[[4,6],[10,4],[16,8],[7,12],[14,14],[5,17],[11,18],[18,17]].map(([x,y],i) => (
          <circle key={i} cx={x} cy={y} r="0.9" fill={stroke} />
        ))}
      </svg>);
    case "warp":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <path d="M2,11 Q5,4 8,11 T14,11 T20,11" fill="none" stroke={stroke} strokeWidth={sw} transform="skewX(-12) translate(2,0)" />
      </svg>);
    case "pop":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <circle cx="6"  cy="11" r="4"   fill="none" stroke={stroke} strokeWidth={sw} />
        <circle cx="13" cy="11" r="2"   fill="none" stroke={stroke} strokeWidth={sw} />
        <circle cx="18" cy="11" r="0.9" fill={stroke} />
      </svg>);
    case "tilt":
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <line x1="3"  y1="18" x2="7"  y2="4" stroke={stroke} strokeWidth={sw} />
        <line x1="9"  y1="18" x2="13" y2="4" stroke={stroke} strokeWidth={sw} />
        <line x1="15" y1="18" x2="19" y2="4" stroke={stroke} strokeWidth={sw} />
      </svg>);
    case "static":
    default:
      return (<svg width={size} height={size} viewBox="0 0 22 22" aria-hidden="true">
        <line x1="2" y1="11" x2="20" y2="11" stroke={stroke} strokeWidth={sw} />
      </svg>);
  }
}

/* ──────────────────────────────────────────────────────────────
   EmotypeWaveform — canvas waveform that flows left-to-right.

   Five stacked sinusoidal lines (the middle one drawn darker) scroll
   continuously. mouseRef.current.{x, y} (set by EmotypeCard's
   mousemove handler) drives the parameters: x → frequency, y →
   amplitude (top of card = louder). When the pointer leaves the card,
   the ref resets to (0.5, 0.5) and the waveform settles into its
   default register.
   ────────────────────────────────────────────────────────────── */
function EmotypeWaveform({
  mouseRef,
  strokeMid     = "rgba(10,10,10,0.55)",
  strokeFaint   = "rgba(10,10,10,0.20)",
  baselineColor = "rgba(10,10,10,0.08)",
}) {
  const ref = useRefEM(null);
  useEffectEM(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, t = 0;

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = c.getBoundingClientRect();
      c.width = r.width * dpr;
      c.height = r.height * dpr;
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.scale(dpr, dpr);
    };
    resize();
    window.addEventListener("resize", resize);

    const tick = () => {
      t += 0.018;
      const r = c.getBoundingClientRect();
      const W = r.width, H = r.height;
      ctx.clearRect(0, 0, W, H);

      const mx = mouseRef && mouseRef.current ? mouseRef.current.x : 0.5;
      const my = mouseRef && mouseRef.current ? mouseRef.current.y : 0.5;
      // amplitude: pointer near the TOP of the card => louder
      const amp = (0.20 + (1 - my) * 0.80) * (H * 0.20);
      // frequency: pointer at the LEFT => low; at the RIGHT => high
      const freq = 0.004 + mx * 0.022;

      const lines = 5;
      ctx.lineCap = "round";
      for (let li = 0; li < lines; li++) {
        const phase = t * (1 + li * 0.22) + li * 0.6;
        const yMid  = H * 0.5 + (li - 2) * (H * 0.07);
        const localAmp = amp * (1 - li * 0.10);
        ctx.beginPath();
        for (let x = 0; x <= W; x += 4) {
          const y = yMid
            + Math.sin(x * freq + phase) * localAmp
            + Math.sin(x * freq * 2.3 + phase * 1.2) * (localAmp * 0.35);
          if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        ctx.strokeStyle = li === 2 ? strokeMid : strokeFaint;
        ctx.lineWidth   = li === 2 ? 1.2 : 0.6;
        ctx.stroke();
      }

      // baseline tick — anchors the waveform visually
      ctx.beginPath();
      ctx.moveTo(0, H * 0.5);
      ctx.lineTo(W, H * 0.5);
      ctx.strokeStyle = baselineColor;
      ctx.setLineDash([2, 4]);
      ctx.lineWidth = 0.5;
      ctx.stroke();
      ctx.setLineDash([]);

      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); };
  }, []);
  return <canvas ref={ref}
                 style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }} />;
}

/* ──────────────────────────────────────────────────────────────
   EmotypeCard — projects bento card.

   Waveform-only card. The waveform sits behind the title and follows
   the mouse: x → frequency, y → amplitude. No morphing glyph, no
   per-emotion gymnastics — the full preset specimen lives on the
   case-study page. The card's job is to *invite* a click.
   ────────────────────────────────────────────────────────────── */
function EmotypeCard({ onClick }) {
  const ref = useRefEM(null);
  const mouseRef = useRefEM({ x: 0.5, y: 0.5 });

  const onMove = (e) => {
    if (!window.__hasHover) return;
    const r = ref.current.getBoundingClientRect();
    mouseRef.current = {
      x: Math.max(0, Math.min(1, (e.clientX - r.left) / r.width)),
      y: Math.max(0, Math.min(1, (e.clientY - r.top) / r.height)),
    };
  };
  const onLeave = () => { mouseRef.current = { x: 0.5, y: 0.5 }; };

  return (
    <div
      ref={ref}
      onMouseMove={onMove}
      onMouseLeave={onLeave}
      onClick={onClick}
      data-cursor="link" data-cursor-label={t("View Project")}
      style={{
        gridColumn: "span 2", gridRow: "span 2",
        position: "relative", overflow: "hidden",
        border: "1px solid var(--fg)",
        background: "var(--line)",
        color: "var(--bg)",
        padding: 20,
        display: "flex", flexDirection: "column",
        cursor: "pointer",
      }}>
      <EmotypeWaveform mouseRef={mouseRef}
                       strokeMid="rgba(255,255,255,0.62)"
                       strokeFaint="rgba(255,255,255,0.22)"
                       baselineColor="rgba(255,255,255,0.08)" />

      <div style={{ position: "relative", zIndex: 1, display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 12 }}>
        <span className="micro" style={{ color: "rgba(255,255,255,0.88)" }}>{t("03d · Tool")}</span>
        <span className="micro" style={{ color: "rgba(255,255,255,0.5)" }}>{t("2026 — present · Alpha")}</span>
      </div>

      <div style={{ flex: 1 }} />

      <div style={{ position: "relative", zIndex: 1 }}>
        <div className="display-md" style={{ marginBottom: 6, fontSize: 30, letterSpacing: "-0.01em", color: "var(--bg)" }}>emotype</div>
        <div className="body-sm" style={{ color: "rgba(255,255,255,0.7)", marginBottom: 12, maxWidth: "100%" }}>
          {t("Real-time Korean subtitles whose typography carries the speaker's affect.")}
        </div>
        <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
          <span className="micro" style={{ color: "rgba(255,255,255,0.62)" }}>{t("Python · MediaPipe")}</span>
          <span className="micro" style={{ color: "rgba(255,255,255,0.62)" }}>{t("Apple MLX · ONNX")}</span>
        </div>
      </div>
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   EmotypeRadar — case-study hero figure.

   Eight emotion labels around a ring with the morphing glyph at the
   centre. Auto-cycles every ~3.5s; clicking a label pins the cycle.
   ────────────────────────────────────────────────────────────── */
function EmotypeRadar() {
  const tPhase = useEmTick(true);
  const [active, , setIdx] = useEmCycle({ dwellMs: 3500 });
  const size = 460;
  const cx = size / 2, cy = size / 2;
  const R = size / 2 - 70;

  const ringOrder = ["surprise", "happiness", "neutral", "contempt", "sadness", "disgust", "fear", "anger"];
  const angleFor = (i) => -Math.PI / 2 + (i / 8) * Math.PI * 2;

  return (
    <div style={{ border: "1px solid var(--line)", padding: "28px 24px", marginBottom: 100, background: "var(--bg)" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 14, flexWrap: "wrap", gap: 12 }}>
        <div>
          <div className="micro" style={{ marginBottom: 6 }}>{t("Fig. 01 · Eight forms, one utterance")}</div>
          <div className="display-md" style={{ fontSize: 28 }}>{t("Eight presets, lit one at a time.")}</div>
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)" }}>{t("auto-cycles · click a label to pin")}</div>
      </div>

      <div style={{ position: "relative", display: "flex", justifyContent: "center", padding: "12px 0" }}>
        <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} aria-hidden="true" style={{ maxWidth: "100%", height: "auto" }}>
          <circle cx={cx} cy={cy} r={R}        fill="none" stroke="var(--line-soft)" strokeWidth="1" />
          <circle cx={cx} cy={cy} r={R * 0.62} fill="none" stroke="var(--line-soft)" strokeWidth="1" strokeDasharray="2 3" />
          {ringOrder.map((id, i) => {
            const a = angleFor(i);
            const x2 = cx + Math.cos(a) * R;
            const y2 = cy + Math.sin(a) * R;
            const em = EM_EMOTIONS.find(e => e.id === id);
            const isActive = em.id === active.id;
            const lx = cx + Math.cos(a) * (R + 30);
            const ly = cy + Math.sin(a) * (R + 30);
            return (
              <g key={id} style={{ cursor: "pointer" }} onClick={() => setIdx(EM_EMOTIONS.indexOf(em))}>
                <line x1={cx} y1={cy} x2={x2} y2={y2}
                      stroke={isActive ? "var(--fg)" : "var(--line-soft)"}
                      strokeWidth={isActive ? 1.2 : 0.6} />
                <circle cx={x2} cy={y2} r={isActive ? 4.5 : 2.6}
                        fill={isActive ? "var(--fg)" : "var(--bg)"}
                        stroke="var(--fg)" strokeWidth="1" />
                <text x={lx} y={ly} textAnchor="middle" dominantBaseline="middle"
                      fontSize="11" fontFamily="var(--font-mono)" letterSpacing="0.08em"
                      fill={isActive ? "var(--fg)" : "var(--fg-muted)"}
                      style={{ textTransform: "uppercase" }}>
                  {id}
                </text>
              </g>
            );
          })}
        </svg>
        <div style={{
          position: "absolute", inset: 0,
          display: "flex", alignItems: "center", justifyContent: "center",
          pointerEvents: "none",
        }}>
          <EmotypeBigGlyph emotion={active} t={tPhase} size={64} word="괜찮아" />
        </div>
      </div>

      <div className="micro" style={{ marginTop: 8, color: "var(--fg-muted)", textAlign: "center" }}>
        {t("Same word. Eight shapes. The decision is made once per utterance.")}
      </div>
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   EmotypePresetMatrix — case-study specimen row.

   A long input sits ABOVE the eight-slot grid. Whatever the visitor
   types (up to 16 chars) replaces 괜찮아 in every slot, so each
   preset's typographic dial can be inspected against arbitrary text.
   Empty input falls back to 괜찮아.
   ────────────────────────────────────────────────────────────── */
function EmotypePresetMatrix() {
  const [active, setActive] = useStateEM(0);
  const [inputWord, setInputWord] = useStateEM("");
  const a = EM_EMOTIONS[active];
  const word = inputWord.trim() || "괜찮아";

  return (
    <div style={{ marginBottom: 100 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 16, flexWrap: "wrap", gap: 12 }}>
        <div>
          <div className="micro" style={{ marginBottom: 6 }}>{t("Fig. 02 · Eight presets")}</div>
          <div className="display-md" style={{ fontSize: 28 }}>{t("Same word, eight forms.")}</div>
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)" }}>{t("type to swap the word across all eight slots · max 16 chars")}</div>
      </div>

      <input
        type="text"
        value={inputWord}
        onChange={(e) => setInputWord(e.target.value.slice(0, 16))}
        maxLength={16}
        placeholder="괜찮아"
        aria-label={t("Type a word to render across all eight presets")}
        className="em-matrix-input"
        style={{
          width: "100%",
          padding: "20px 22px",
          border: "1px solid var(--line)",
          borderBottom: 0,
          background: "var(--bg)",
          fontFamily: "var(--font-kr)",
          fontSize: 22,
          fontWeight: 500,
          color: "var(--fg)",
          outline: "none",
          letterSpacing: "-0.01em",
          boxSizing: "border-box",
          borderRadius: 0,
        }}
      />

      <div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)", border: "1px solid var(--line)", background: "var(--bg)" }} className="em-matrix">
        {EM_EMOTIONS.map((em, i) => {
          const isActive = i === active;
          return (
            <button
              key={em.id}
              type="button"
              onMouseEnter={() => window.__hasHover && setActive(i)}
              onClick={() => setActive(i)}
              data-cursor="link" data-cursor-label={t(em.id)}
              style={{
                position: "relative",
                background: isActive ? "var(--fg)" : "var(--bg)",
                color:      isActive ? "var(--bg)" : "var(--fg)",
                border: 0,
                borderRight: i < 7 ? "1px solid var(--line-soft)" : "0",
                padding: "20px 10px 16px",
                display: "flex", flexDirection: "column", alignItems: "center",
                gap: 12,
                cursor: "pointer",
                transition: "background 220ms var(--easing-default), color 220ms var(--easing-default)",
              }}>
              <span className="micro" style={{ color: "inherit", fontSize: 9.5 }}>
                {String(i + 1).padStart(2, "0")} · {em.id}
              </span>
              <span style={{
                fontFamily: emFontFamily(em.font),
                fontWeight: em.weight,
                fontStyle: em.italic ? "italic" : "normal",
                // length-adaptive: shrinks + tightens as the word grows, wraps when it has to
                fontSize:
                  word.length <= 3  ? 30 :
                  word.length <= 5  ? 26 :
                  word.length <= 8  ? 20 :
                  word.length <= 12 ? 16 : 13,
                lineHeight:    word.length > 6 ? 1.22 : 1.1,
                letterSpacing: word.length > 8 ? "-0.02em" : "-0.01em",
                color: "inherit",
                textAlign: "center",
                wordBreak: "break-word",
                overflowWrap: "anywhere",
                whiteSpace: "normal",
                maxWidth: "100%",
                minHeight: 30,
                display: "block",
                transition: "font-size 220ms var(--easing-default), letter-spacing 220ms var(--easing-default), line-height 220ms var(--easing-default)",
              }}>{word}</span>
              <span style={{ display: "inline-flex", color: "inherit" }}>
                <EmMotionGlyph motion={em.motion} />
              </span>
              <span className="micro" style={{ fontFamily: "var(--font-mono)", fontSize: 8, opacity: 0.75, color: "inherit", letterSpacing: "0.06em" }}>
                {em.motion}
              </span>
            </button>
          );
        })}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 24, padding: "20px 4px 4px" }} className="em-matrix-meta">
        <div>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Mood")}</div>
          <div className="body-sm">{a.mood}</div>
        </div>
        <div>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Motion")}</div>
          <div className="body-sm" style={{ fontFamily: "var(--font-mono)" }}>{a.motion}</div>
        </div>
        <div>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Valence × Arousal")}</div>
          <div className="body-sm" style={{ fontFamily: "var(--font-mono)" }}>
            {a.valence.toFixed(2)} × {a.arousal.toFixed(2)}
          </div>
        </div>
        <div>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Intensity (idle)")}</div>
          <div className="body-sm" style={{ fontFamily: "var(--font-mono)" }}>{a.intensity.toFixed(2)}</div>
        </div>
      </div>

      <style>{`
        .em-matrix-input::placeholder { color: var(--fg-faint); }
        .em-matrix-input:focus { border-color: var(--fg); }
        @media (max-width: 760px) {
          .em-matrix { grid-template-columns: repeat(4, 1fr) !important; }
          .em-matrix-meta { grid-template-columns: 1fr 1fr !important; }
        }
      `}</style>
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   EmotypePage — case study, paideia-pattern mirror minus the
   pipeline walkthrough (which read as a duplicate of paideia's
   Fig. 03 terminal panel).
   ────────────────────────────────────────────────────────────── */
function EmotypePage() {
  useLang();
  const { go } = useRouter();

  return (
    <div className="page" style={{ paddingTop: 140 }}>
      <div className="page-eyebrow">
        <button type="button" onClick={() => go("projects")} data-cursor="link" data-cursor-label={t("Back")} className="micro" style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {t("← Index 03 / Projects")}
        </button>
        <span style={{ width: 24, height: 1, background: "var(--fg)" }} />
        <span className="micro micro-fg">{t("Case study · emotype")}</span>
        <span style={{ width: 24, height: 1, background: "var(--fg)" }} />
        <a href={EMOTYPE_REPO} target="_blank" rel="noopener noreferrer"
           data-cursor="link" data-cursor-label={t("Open on GitHub")}
           className="micro" style={{ color: "var(--fg)", textDecoration: "none", borderBottom: "1px solid var(--line)" }}>
          {t("GitHub ↗ TaewoooPark/emotype")}
        </a>
      </div>

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 64, marginBottom: 100, alignItems: "end" }}>
        <h1 className="display-xxl" style={{ fontFamily: "var(--font-kr)", fontWeight: 600, letterSpacing: "-0.03em" }}>
          emotype
        </h1>
        <div>
          <div className="micro" style={{ marginBottom: 12 }}>{t("2026 — present · Solo · Open source · Alpha")}</div>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("Real-time Korean subtitles whose typography carries the speaker's affect. Same words — joy bounces, anger shakes, sadness slumps, fear flickers. emotype reads your face and voice at the utterance level and dresses the caption in one of eight forms.")}
          </p>
        </div>
      </div>

      <div className="four-col" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 32, marginBottom: 100 }}>
        {[
          { l: "Problem",  v: "Subtitles carry the words and throw away the affect. Same neutral typography for joy, rage, and grief. The viewer is left to guess the tone from text alone — and most of the time, they don't." },
          { l: "Approach", v: "Treat each utterance as a unit. Measure emotion at the face (HSEmotion + valence/arousal) and bind it at the audio (VAD + STT). Pick one DesignToken — font, motion type, intensity — per utterance. Stability over accuracy: never mutate the form mid-breath." },
          { l: "Result",   v: "An eight-preset typographic instrument that runs on-device. Anger shakes wider when intensity is high; sadness slumps slower. A frameless click-through overlay sits over any video, on any monitor, with multi-line stagger continuity." },
          { l: "Stack",    v: "Python · PyQt5 · MediaPipe FaceDetector · HSEmotion ENet-B0 ONNX (CoreML EP) · Silero VAD · mlx-whisper (Apple Silicon) · pydantic v2 · YAML design presets · LCH intensity scaling. macOS first; Apple MLX-only local STT." },
        ].map((s, i) => (
          <div key={i}>
            <div className="micro" style={{ marginBottom: 12 }}>{String(i + 1).padStart(2, "0")} · {t(s.l)}</div>
            <p className="body" style={{ color: "var(--fg-muted)" }}>{t(s.v)}</p>
          </div>
        ))}
      </div>

      <div className="four-col" style={{
        display: "grid", gridTemplateColumns: "repeat(4, 1fr)",
        borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)",
        marginBottom: 100,
      }}>
        {[
          ["License",  "MIT",                    EMOTYPE_REPO + "/blob/main/LICENSE"],
          ["Privacy",  "On-device by default",   EMOTYPE_REPO + "#on-device-by-default"],
          ["Status",   "Alpha · macOS first",    EMOTYPE_REPO + "#status"],
          ["GitHub",   "TaewoooPark/emotype",    EMOTYPE_REPO],
        ].map(([k, v, href], i, arr) => (
          <a key={k} href={href} target="_blank" rel="noopener noreferrer"
             data-cursor="link" data-cursor-label={t("Open")}
             style={{
               padding: "26px 20px",
               borderRight: i < arr.length - 1 ? "1px solid var(--line-soft)" : "none",
               textDecoration: "none", color: "var(--fg)",
             }}>
            <div className="micro" style={{ marginBottom: 10, color: "var(--fg-muted)" }}>{t(k)}</div>
            <div style={{ fontFamily: "var(--font-display)", fontSize: 22, lineHeight: 1.2 }}>{t(v)}</div>
          </a>
        ))}
      </div>

      <EmotypeRadar />

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 64, marginBottom: 60 }}>
        <div className="micro">{t("§ 01 — One utterance, one emotion, one design")}</div>
        <div>
          <p className="body-lg" style={{ marginBottom: 20 }}>
            {t("Even if your face shifts mid-sentence, the subtitle's design does not. A subtitle whose form mutates inside one breath is unreadable. The next utterance gets a fresh decision.")}
          </p>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("Affect is multidimensional. emotype keeps three representations side by side: eight categorical labels, a continuous valence × arousal pair (Russell's circumplex), and a scalar intensity in [0, 1]. Form is amplitude — strong anger shakes wider, soft anger trembles.")}
          </p>
        </div>
      </div>

      <EmotypePresetMatrix />

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 64, marginBottom: 32 }}>
        <div className="micro">{t("§ 02 — A tool for expression, not diagnosis")}</div>
        <div>
          <p className="body-lg" style={{ marginBottom: 20 }}>
            {t("The face-emotion model is a hypothesis, not a verdict. HSEmotion is biased toward Western expressions and AffectNet's collection conditions; Korean faces, masks, and non-prototypical expressions degrade the signal sharply.")}
          </p>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("emotype dresses utterances, not people. A track ID never inherits an emotion. No camera frame or audio buffer is written to disk by emotype itself; cloud STT, when chosen, follows the provider's policy with explicit user consent. Do not use this for hiring, medical screening, or any decision-bearing context.")}
          </p>
        </div>
      </div>

      <div className="em-compare" style={{ borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)", marginBottom: 100 }}>
        <div style={{ display: "grid", gridTemplateColumns: "220px 1fr 1fr", padding: "14px 0 12px", borderBottom: "1px solid var(--line-soft)" }}>
          <div className="micro" style={{ color: "var(--fg-muted)" }}>{t("Axis")}</div>
          <div className="micro" style={{ color: "var(--fg-muted)", paddingRight: 24 }}>{t("Traditional subtitles")}</div>
          <div className="micro">{t("emotype")}</div>
        </div>
        {[
          ["Typography",  "Same neutral type for every utterance",   "Per-utterance design — typography carries affect"],
          ["Emotion",     "Left to the viewer's imagination",        "Measured (face) and bound (audio) at the utterance"],
          ["Intensity",   "Static font, color, weight",              "1px tremor → 32px shake; scales with intensity"],
          ["Stream",      "One linear stream of text",               "A typographic instrument that performs the line"],
          ["Locality",    "Cloud captioning by default",             "On-device by default; cloud STT only with consent"],
          ["Persistence", "Streams recorded server-side",            "Frames + audio never written to disk by emotype"],
        ].map(([axis, generic, et], i, arr) => (
          <div key={axis} style={{
            display: "grid", gridTemplateColumns: "220px 1fr 1fr",
            padding: "18px 0",
            borderBottom: i < arr.length - 1 ? "1px solid var(--line-soft)" : "none",
          }}>
            <div className="micro" style={{ alignSelf: "start" }}>{t(axis)}</div>
            <div className="body" style={{ color: "var(--fg-muted)", paddingRight: 24 }}>{t(generic)}</div>
            <div className="body">{t(et)}</div>
          </div>
        ))}
      </div>

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 64, marginBottom: 100 }}>
        <div className="micro">{t("§ 03 — Text carries meaning. Form carries affect.")}</div>
        <div>
          <p className="body-lg" style={{ marginBottom: 20 }}>
            {t("The same Korean line 괜찮아 (\"it's fine\") is a different utterance when the letters tremble on top of anger, slump on top of sadness, or bounce on top of joy.")}
          </p>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("emotype takes the affect that is already in your face and voice and translates it into the typography of the subtitle — so the caption carries both meaning and feeling at once. The tool measures the utterance, not the person.")}
          </p>
        </div>
      </div>

      <div className="em-nav" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, marginBottom: 80 }}>
        <button type="button" onClick={() => go("projects")} data-cursor="link" data-cursor-label={t("Back")} style={{ padding: "32px", border: "1px solid var(--line)", textAlign: "left" }}>
          <div className="micro" style={{ marginBottom: 8, color: "var(--fg-muted)" }}>{t("← Previous")}</div>
          <div className="display-md">{t("Index of all projects")}</div>
        </button>
        <button type="button" onClick={() => go("paideia")} data-cursor="link" data-cursor-label={t("Next")} style={{ padding: "32px", border: "1px solid var(--line)", textAlign: "right", background: "var(--fg)", color: "var(--bg)" }}>
          <div className="micro" style={{ marginBottom: 8, color: "rgba(255,255,255,0.5)" }}>{t("Next →")}</div>
          <div className="display-md">{t("Paideia")}</div>
        </button>
      </div>

      {/* Mobile rules specific to this page — the site CSS already handles
          .responsive-stack and .four-col; everything else lives here. */}
      <style>{`
        @media (max-width: 760px) {
          .em-compare > div {
            grid-template-columns: 1fr !important;
            gap: 6px !important;
            padding: 14px 0 !important;
          }
          .em-nav { grid-template-columns: 1fr !important; }
          .em-nav button { text-align: left !important; padding: 24px !important; }
        }
      `}</style>
    </div>
  );
}

Object.assign(window, {
  EmotypePage, EmotypeCard,
  EM_EMOTIONS, useEmTick, useEmCycle, emFontFamily, emMotionStyle,
  EmotypeBigGlyph, EmMotionGlyph,
  EmotypeWaveform, EmotypeRadar, EmotypePresetMatrix,
});
