const { useState, useMemo, useEffect } = React;
/* ------------------------------------------------------------------ *
 *  Foodlight — Lighting Decision Engine
 *  One lighting call for food, from color theory + facial-emotion data.
 *
 *  Empirical spine (Shu et al., Foods 2025, 14, 3440 — n=18, 108 obs.):
 *    Willingness-to-eat means:  2700K 8.33 · 4200K 7.33 · 6500K 5.67
 *                               red 3.67 · green 1.50 · blue 1.61
 *    Liking (appearance) means: 2700K 8.22 · 4200K 7.33 · 6500K 5.61
 *                               red 3.50 · green 1.67 · blue 1.61
 *    Facial emotion (GFER):     2700K happy .25 · 4200K happy .21
 *                               6500K sad .30 · red sad .38 · green sad .31
 *                               blue sad .39 (strongest negative)
 *
 *  Transferable principle: food-color × light-color interaction.
 *  Complementary pairings suppress appetite; warm/harmonious lift it;
 *  green reads as spoilage; warm/dim light can mask doneness (safety).
 * ------------------------------------------------------------------ */

const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));

// circular hue harmony: +1 identical hue, -1 complementary (180 deg apart)
const harmony = (a, b) => {
  const d = Math.abs((((a - b) % 360) + 540) % 360 - 180);
  return Math.cos((d * Math.PI) / 180);
};

const W_MIN = 1.5;
const W_MAX = 8.333;
const normEmp = (w) => (w - W_MIN) / (W_MAX - W_MIN);

const LIGHTS = [
  {
    id: "2700K", label: "Warm white", tag: "2700 K", white: true, warmth: 1.0,
    kelvinNorm: 0.0, hue: 38, willing: 8.333, liking: 8.222,
    emo: "Happy", emoVal: 0.25, emoTone: "pos",
    tint: "rgba(255,164,58,0.30)", filter: "saturate(1.05)",
  },
  {
    id: "4200K", label: "Neutral-warm white", tag: "4200 K", white: true, warmth: 0.6,
    kelvinNorm: 0.395, hue: 45, willing: 7.333, liking: 7.333,
    emo: "Happy", emoVal: 0.21, emoTone: "pos",
    tint: "rgba(255,222,182,0.16)", filter: "none",
  },
  {
    id: "6500K", label: "Cool white", tag: "6500 K", white: true, warmth: 0.15,
    kelvinNorm: 1.0, hue: 210, willing: 5.667, liking: 5.611,
    emo: "Neutral / cool", emoVal: 0.30, emoTone: "neu",
    tint: "rgba(190,214,255,0.14)", filter: "brightness(1.04)",
  },
  {
    id: "red", label: "Red", tag: "monochromatic", white: false, warmth: 0.9,
    kelvinNorm: 0.2, hue: 0, willing: 3.667, liking: 3.5,
    emo: "Sad", emoVal: 0.38, emoTone: "neg",
    tint: "rgba(224,42,32,0.52)", filter: "brightness(0.92) saturate(1.1)",
  },
  {
    id: "green", label: "Green", tag: "monochromatic", white: false, warmth: 0.3,
    kelvinNorm: 0.5, hue: 120, willing: 1.5, liking: 1.667,
    emo: "Sad", emoVal: 0.31, emoTone: "neg",
    tint: "rgba(46,184,74,0.46)", filter: "brightness(0.98)",
  },
  {
    id: "blue", label: "Blue", tag: "monochromatic", white: false, warmth: 0.05,
    kelvinNorm: 0.85, hue: 240, willing: 1.611, liking: 1.611,
    emo: "Sad", emoVal: 0.39, emoTone: "neg",
    tint: "rgba(34,74,224,0.56)", filter: "brightness(0.82)",
  },
];

const FOODS = [
  { id: "fries", name: "Fries & fried chicken", hue: 40, note: "golden / brown", warm: true, meat: true, tested: true },
  { id: "redmeat", name: "Red meat / steak", hue: 8, note: "red", warm: true, meat: true },
  { id: "tomato", name: "Tomato / red sauce", hue: 6, note: "red", warm: true },
  { id: "citrus", name: "Citrus / cheese / corn", hue: 52, note: "yellow", warm: true },
  { id: "greens", name: "Leafy greens / salad", hue: 110, note: "green" },
  { id: "dairy", name: "Dairy / rice / white fish", hue: 46, note: "pale / neutral", pale: true },
  { id: "berry", name: "Berries / eggplant", hue: 275, note: "purple" },
  { id: "choc", name: "Chocolate / coffee", hue: 28, note: "dark brown", warm: true },
  { id: "seafood", name: "Fresh fish / seafood", hue: 190, note: "cool pale", pale: true, meat: true },
];

const GOALS = [
  { id: "appetite", label: "Drive appetite", hint: "menus, dining, food photos" },
  { id: "accuracy", label: "True-to-life color", hint: "retail, produce, freshness" },
  { id: "calm", label: "Calm / suppress", hint: "portion control, wellness" },
];

// appetite score in 0..1 for a light given a food hue
function appetiteScore(L, foodHue) {
  const simTest = (harmony(foodHue, 40) + 1) / 2; // how close food is to the tested golden food
  let theory;
  if (L.white) theory = clamp(0.45 + 0.35 * L.warmth, 0, 1);
  else theory = clamp(0.4 + 0.4 * harmony(foodHue, L.hue), 0, 1);
  return clamp(simTest * normEmp(L.willing) + (1 - simTest) * theory, 0, 1);
}

function accuracyScore(L) {
  if (L.white) return clamp(0.6 + 0.4 * L.kelvinNorm, 0, 1); // daylight renders truest
  return 0.12; // monochromatic light destroys color fidelity
}

function goalScore(L, foodHue, goal) {
  if (goal === "appetite") return appetiteScore(L, foodHue);
  if (goal === "calm") return 1 - appetiteScore(L, foodHue);
  return accuracyScore(L);
}

function App() {
  const [foodId, setFoodId] = useState("fries");
  const [customHue, setCustomHue] = useState(null);
  const [goal, setGoal] = useState("appetite");
  const [preview, setPreview] = useState(null); // light id being previewed, null = recommended
  const [showModel, setShowModel] = useState(false);

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

  const food = FOODS.find((f) => f.id === foodId);
  const foodHue = customHue != null ? customHue : food.hue;

  const ranked = useMemo(() => {
    return LIGHTS.map((L) => ({ L, score: goalScore(L, foodHue, goal) })).sort(
      (a, b) => b.score - a.score
    );
  }, [foodHue, goal]);

  const winner = ranked[0].L;
  const shown = preview ? LIGHTS.find((l) => l.id === preview) : winner;

  const isGolden = customHue == null && food.tested;
  const predWilling = (L) => 1 + 8 * appetiteScore(L, foodHue);

  // rationale chips for the winner
  const rules = useMemo(() => {
    const out = [];
    if (goal === "accuracy") {
      out.push({ k: "info", t: `Daylight-balanced white renders food truest to life — accuracy peaks toward ${winner.tag}.` });
      if (winner.white && winner.warmth > 0.7 && food.meat)
        out.push({ k: "warn", t: "Warm, low light can make meat look more cooked than it is — a doneness / food-safety risk." });
      return out;
    }
    if (goal === "calm") {
      out.push({ k: "info", t: "To dampen appetite, the model steers toward cool / complementary light — the study's lowest willingness conditions." });
    }
    if (winner.white && winner.warmth >= 0.6)
      out.push({ k: "good", t: `Warm white flatters ${food.warm ? "warm-toned" : "this"} food and topped the study for appetite and positive affect (happy ≈ ${LIGHTS[0].emoVal}).` });

    LIGHTS.filter((l) => !l.white).forEach((l) => {
      const h = harmony(foodHue, l.hue);
      if (h < -0.55 && goal === "appetite")
        out.push({ k: "warn", t: `${l.label} light is near-complementary to this food — that pairing suppressed willingness in the study.` });
      if (h > 0.72 && goal !== "calm")
        out.push({ k: "good", t: `${l.label} light is chromatically close to the food, so a ${l.label.toLowerCase()} accent can reinforce appeal.` });
    });
    if (harmony(foodHue, 120) > 0.4 === false && goal === "appetite")
      out.push({ k: "warn", t: "Green light reads as mold / spoilage on non-green food — avoid as a key light." });
    if (winner.meat && winner.white && winner.warmth > 0.7 && goal === "appetite")
      out.push({ k: "warn", t: "If this is raw or grilled meat, verify doneness under neutral light — warm light can mask it." });

    // de-dup + cap
    const seen = new Set();
    return out.filter((r) => (seen.has(r.t) ? false : seen.add(r.t))).slice(0, 4);
  }, [winner, goal, foodHue, food]);

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

      <header className="fl-head">
        <div className="fl-lamp">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
            <path strokeLinecap="round" strokeLinejoin="round" d="M12 3v1.5M5.6 5.6l1.06 1.06M3 12h1.5M18 12h1.5M17.4 6.66l1.06-1.06M8.25 15a3.75 3.75 0 117.5 0c0 1.2-.56 2.02-1.2 2.62-.4.38-.6.9-.6 1.45v.18a.75.75 0 01-.75.75h-2.4a.75.75 0 01-.75-.75v-.18c0-.55-.2-1.07-.6-1.45C8.81 17.02 8.25 16.2 8.25 15z" />
          </svg>
        </div>
        <div>
          <h1>Foodlight</h1>
          <p>One lighting call for food — color theory crossed with facial-emotion data</p>
        </div>
      </header>

      <div className="fl-grid">
        {/* -------- controls -------- */}
        <section className="fl-panel fl-controls">
          <div className="fl-lbl">Food</div>
          <div className="fl-chips">
            {FOODS.map((f) => (
              <button
                key={f.id}
                className={foodId === f.id && customHue == null ? "on" : ""}
                onClick={() => { setFoodId(f.id); setCustomHue(null); setPreview(null); }}
              >
                {f.name}
                <em>{f.note}</em>
              </button>
            ))}
          </div>

          <div className="fl-hue-row">
            <span>Custom food hue</span>
            <input
              type="range" min="0" max="360" step="1"
              value={foodHue}
              onChange={(e) => { setCustomHue(parseInt(e.target.value)); setPreview(null); }}
            />
            <i style={{ background: `hsl(${foodHue} 65% 50%)` }} />
            <b>{foodHue}&deg;</b>
          </div>

          <div className="fl-lbl fl-mt">Goal</div>
          <div className="fl-goals">
            {GOALS.map((g) => (
              <button
                key={g.id}
                className={goal === g.id ? "on" : ""}
                onClick={() => { setGoal(g.id); setPreview(null); }}
              >
                <strong>{g.label}</strong>
                <em>{g.hint}</em>
              </button>
            ))}
          </div>
        </section>

        {/* -------- recommendation -------- */}
        <section className="fl-panel fl-reco">
          <div className="fl-reco-tag">Recommended</div>
          <div className="fl-reco-main">
            <div className="fl-swatch" style={{ background: swatchOf(winner) }} />
            <div className="fl-reco-txt">
              <h2>{winner.label}</h2>
              <span className="fl-reco-sub">{winner.tag}</span>
              <div className="fl-reco-metrics">
                <Metric
                  label="Predicted willingness"
                  value={predWilling(winner).toFixed(1)}
                  unit="/ 9"
                  sub={isGolden ? "study mean" : "modeled"}
                />
                <Metric
                  label="Dominant affect"
                  value={winner.emo}
                  unit={winner.emoTone === "neu" ? "" : `${winner.emoVal}`}
                  tone={winner.emoTone}
                />
              </div>
            </div>
          </div>
          <ul className="fl-rules">
            {rules.map((r, i) => (
              <li key={i} className={`r-${r.k}`}>
                <span className="fl-rule-dot" />
                {r.t}
              </li>
            ))}
          </ul>
        </section>

        {/* -------- live plate -------- */}
        <section className="fl-panel fl-preview">
          <div className="fl-lbl">
            Under {shown.label} {preview && <button className="fl-clear" onClick={() => setPreview(null)}>reset</button>}
          </div>
          <div className="fl-plate-big">
            <Plate />
            <div className="fl-light" style={{ background: shown.tint, filter: shown.filter }} />
          </div>
        </section>

        {/* -------- six-light strip -------- */}
        <section className="fl-panel fl-strip-wrap">
          <div className="fl-lbl">Same plate, six lights &middot; predicted willingness for {customHue != null ? "custom hue" : food.name.toLowerCase()}</div>
          <div className="fl-strip">
            {ranked.map(({ L, score }, i) => (
              <button
                key={L.id}
                className={`fl-strip-cell ${L.id === shown.id ? "active" : ""}`}
                onClick={() => setPreview(L.id)}
              >
                {i === 0 && goal === "appetite" && <div className="fl-badge">top</div>}
                <div className="fl-plate-sm">
                  <Plate small />
                  <div className="fl-light" style={{ background: L.tint, filter: L.filter }} />
                </div>
                <div className="fl-strip-meta">
                  <strong>{L.label}</strong>
                  <span>{goal === "accuracy" ? `${Math.round(score * 100)}% fidelity` : `${predWilling(L).toFixed(1)} / 9`}</span>
                </div>
              </button>
            ))}
          </div>
        </section>

        {/* -------- ranked bars -------- */}
        <section className="fl-panel fl-bars-wrap">
          <div className="fl-lbl">{GOALS.find((g) => g.id === goal).label} &middot; ranked</div>
          <div className="fl-bars">
            {ranked.map(({ L, score }) => (
              <div key={L.id} className="fl-bar-row">
                <span className="fl-bar-name">{L.label}</span>
                <div className="fl-bar-track">
                  <div
                    className="fl-bar-fill"
                    style={{ width: `${Math.max(3, score * 100)}%`, background: swatchOf(L) }}
                  />
                </div>
                <span className="fl-bar-val">{Math.round(score * 100)}</span>
              </div>
            ))}
          </div>
        </section>
      </div>

      <button className="fl-model-toggle" onClick={() => setShowModel((s) => !s)}>
        {showModel ? "Hide" : "Show"} model, formulas &amp; caveats
      </button>
      {showModel && (
        <div className="fl-panel fl-model">
          <pre>{`simTest        = (harmony(foodHue, 40°) + 1) / 2        // closeness to the tested golden food
harmony(a,b)   = cos( circularAngle(a − b) )           // +1 same hue, −1 complementary
appetite(L)    = simTest · normEmp(L) + (1−simTest) · theory(L)
theory(white)  = 0.45 + 0.35 · warmth(L)
theory(color)  = 0.40 + 0.40 · harmony(foodHue, hue(L))
accuracy(white)= 0.60 + 0.40 · kelvinNorm(L)           // daylight renders truest
goal = appetite → appetite(L) · calm → 1−appetite(L) · accuracy → accuracy(L)`}</pre>
          <p>
            The empirical spine (normEmp) is the willingness-to-eat means from Shu, Gao, Wang &amp; Wei,
            <em> Food Emotional Perception and Eating Willingness Under Different Lighting Colors</em>, Foods 2025, 14, 3440.
            Those means were measured only on golden/brown fried food (n = 18, 108 observations), so the model leans
            on them fully for similar foods and shifts toward pure color-theory as the food&rsquo;s hue diverges. Treat
            this as directional guidance from a single preliminary study plus color theory — not a validated
            spec. Cultural color associations vary, the sample is small, and only one food color was tested. If you
            want, I can swap in your own measured willingness data per food and it becomes empirical end-to-end.
          </p>
        </div>
      )}
    </div>
  );
}

function Metric({ label, value, unit, sub, tone }) {
  return (
    <div className="fl-metric">
      <span className="fl-metric-lbl">{label}</span>
      <span className={`fl-metric-val ${tone ? "t-" + tone : ""}`}>
        {value}
        {unit && <em>{unit}</em>}
      </span>
      {sub && <span className="fl-metric-sub">{sub}</span>}
    </div>
  );
}

function swatchOf(L) {
  const map = {
    "2700K": "linear-gradient(135deg,#ffd18a,#ff9d3c)",
    "4200K": "linear-gradient(135deg,#fff0dc,#ffd7a8)",
    "6500K": "linear-gradient(135deg,#eaf1ff,#bcd2ff)",
    red: "linear-gradient(135deg,#ff6b52,#d61f16)",
    green: "linear-gradient(135deg,#5fe08a,#1e9e46)",
    blue: "linear-gradient(135deg,#5c8bff,#1b39c4)",
  };
  return map[L.id];
}

function Plate({ small }) {
  // porcelain plate with red meat + leafy greens — chosen so colored light
  // visibly warps the food: red meat goes muddy/weird, greens desaturate to gray
  return (
    <svg viewBox="0 0 200 150" className="fl-plate-svg" preserveAspectRatio="xMidYMid meet">
      <rect x="0" y="0" width="200" height="150" fill="#efe4d2" />
      <ellipse cx="100" cy="120" rx="80" ry="14" fill="#00000018" />
      <ellipse cx="100" cy="80" rx="84" ry="54" fill="#fbf8f2" stroke="#e6ddcd" strokeWidth="2" />
      <ellipse cx="100" cy="80" rx="64" ry="40" fill="#f4eee2" />

      {/* leafy greens — back-right cluster */}
      {[
        [118, 58, 22, 13, -18, "#4f9e3a"],
        [136, 64, 24, 14, 14, "#63b34c"],
        [128, 73, 20, 12, 34, "#3f8a30"],
        [110, 68, 18, 11, -42, "#57a83f"],
        [142, 76, 18, 11, 58, "#4c9636"],
      ].map(([cx, cy, rx, ry, r, fill], i) => (
        <g key={i} transform={`translate(${cx} ${cy}) rotate(${r})`}>
          <ellipse rx={rx} ry={ry} fill={fill} stroke="#2f6d22" strokeWidth="0.8" />
          {!small && <line x1={-rx} y1="0" x2={rx} y2="0" stroke="#2f6d22" strokeWidth="0.8" opacity="0.45" />}
        </g>
      ))}

      {/* red meat / steak — front-left */}
      <g transform="translate(74 88) rotate(-8)">
        <ellipse rx="37" ry="26" fill="#8f3a2a" />
        <ellipse rx="31" ry="21" fill="#b8483a" />
        <ellipse rx="20" ry="12" fill="#d47d6f" />
        {!small && (
          <g stroke="#5f241a" strokeWidth="2" opacity="0.5" strokeLinecap="round">
            <line x1="-24" y1="-9" x2="6" y2="17" />
            <line x1="-9" y1="-16" x2="21" y2="10" />
            <line x1="-32" y1="1" x2="-6" y2="23" />
          </g>
        )}
      </g>

      {/* cherry tomatoes — extra red pop */}
      {[[150, 94], [161, 84]].map(([cx, cy], i) => (
        <g key={i}>
          <circle cx={cx} cy={cy} r="7" fill="#d8352a" stroke="#a71f16" strokeWidth="0.8" />
          <circle cx={cx - 2} cy={cy - 2} r="2" fill="#ff8a7a" opacity="0.85" />
        </g>
      ))}
    </svg>
  );
}

const CSS = `
.fl-root{
  --paper:#f4ebdc; --panel:#fdfaf3; --line:#e6dbc7; --ink:#2c231b; --dim:#8a7b66;
  --amber:#d9812a; --deep:#b5462f; --cool:#3f6f8f; --good:#3f8f5a; --warn:#c2622a;
  font-family:'Hanken Grotesk',system-ui,sans-serif; color:var(--ink);
  background:var(--paper); padding:24px; min-height:100%; box-sizing:border-box;
  background-image:radial-gradient(circle at 12% -5%,rgba(217,129,42,0.12),transparent 40%);
}
.fl-root *{box-sizing:border-box}
.fl-head{display:flex;align-items:center;gap:14px;margin-bottom:22px}
.fl-lamp{width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,#ffd18a,#e0872f);display:flex;align-items:center;justify-content:center;color:#4a2a08;box-shadow:0 6px 18px rgba(217,129,42,0.35)}
.fl-lamp svg{width:26px;height:26px}
.fl-head h1{font-family:'Fraunces',serif;font-weight:900;font-size:30px;margin:0;letter-spacing:-0.02em}
.fl-head p{margin:2px 0 0;color:var(--dim);font-size:13.5px}

.fl-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;
  grid-template-areas:"controls controls" "reco preview" "strip preview" "bars preview"}
@media(max-width:820px){.fl-grid{grid-template-columns:1fr;
  grid-template-areas:"controls" "reco" "preview" "strip" "bars"}}
.fl-panel{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:18px;box-shadow:0 2px 10px rgba(80,55,20,0.04)}
.fl-controls{grid-area:controls}
.fl-reco{grid-area:reco}
.fl-preview{grid-area:preview}
.fl-strip-wrap{grid-area:strip}
.fl-bars-wrap{grid-area:bars}
.fl-lbl{font-family:'Fraunces',serif;font-weight:600;font-size:13px;letter-spacing:0.02em;color:var(--ink);text-transform:uppercase;margin-bottom:10px;display:flex;align-items:center;gap:8px}
.fl-mt{margin-top:20px}

.fl-chips{display:flex;flex-wrap:wrap;gap:8px}
.fl-chips button{background:#fff;border:1px solid var(--line);border-radius:11px;padding:8px 12px;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:1px;font-family:inherit;color:var(--ink);transition:.15s}
.fl-chips button em{font-style:normal;font-size:10px;color:var(--dim)}
.fl-chips button:hover{border-color:var(--amber)}
.fl-chips button.on{background:#fff4e4;border-color:var(--amber);box-shadow:0 0 0 1px var(--amber)}

.fl-hue-row{display:flex;align-items:center;gap:10px;margin-top:14px}
.fl-hue-row span{font-size:12px;color:var(--dim);white-space:nowrap}
.fl-hue-row input{flex:1;-webkit-appearance:none;height:8px;border-radius:5px;background:linear-gradient(90deg,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)}
.fl-hue-row input::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;border-radius:50%;background:#fff;border:2px solid var(--ink);cursor:pointer}
.fl-hue-row input::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:#fff;border:2px solid var(--ink);cursor:pointer}
.fl-hue-row i{width:22px;height:22px;border-radius:6px;border:1px solid var(--line)}
.fl-hue-row b{font-size:12px;font-variant-numeric:tabular-nums;width:34px}

.fl-goals{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
@media(max-width:560px){.fl-goals{grid-template-columns:1fr}}
.fl-goals button{background:#fff;border:1px solid var(--line);border-radius:11px;padding:11px;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:2px;font-family:inherit;color:var(--ink);transition:.15s}
.fl-goals button strong{font-size:14px}
.fl-goals button em{font-style:normal;font-size:10.5px;color:var(--dim)}
.fl-goals button.on{background:#fff4e4;border-color:var(--amber);box-shadow:0 0 0 1px var(--amber)}

.fl-reco{position:relative;overflow:hidden}
.fl-reco-tag{position:absolute;top:14px;right:16px;font-size:10px;letter-spacing:0.16em;text-transform:uppercase;color:var(--amber);font-weight:700}
.fl-reco-main{display:flex;gap:16px;align-items:center}
.fl-swatch{width:66px;height:66px;border-radius:16px;flex:none;box-shadow:inset 0 0 0 1px rgba(0,0,0,0.06),0 4px 12px rgba(0,0,0,0.1)}
.fl-reco-txt h2{font-family:'Fraunces',serif;font-weight:900;font-size:26px;margin:0;line-height:1}
.fl-reco-sub{font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums}
.fl-reco-metrics{display:flex;gap:20px;margin-top:12px}
.fl-metric{display:flex;flex-direction:column;gap:1px}
.fl-metric-lbl{font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:var(--dim)}
.fl-metric-val{font-family:'Fraunces',serif;font-weight:600;font-size:22px;display:flex;align-items:baseline;gap:4px}
.fl-metric-val em{font-style:normal;font-size:12px;color:var(--dim)}
.fl-metric-val.t-pos{color:var(--good)} .fl-metric-val.t-neg{color:var(--deep)} .fl-metric-val.t-neu{color:var(--cool)}
.fl-metric-sub{font-size:10px;color:var(--dim);font-style:italic}

.fl-rules{list-style:none;margin:16px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
.fl-rules li{display:flex;gap:9px;align-items:flex-start;font-size:12.5px;line-height:1.45;color:var(--ink)}
.fl-rule-dot{width:7px;height:7px;border-radius:50%;margin-top:5px;flex:none;background:var(--dim)}
.r-good .fl-rule-dot{background:var(--good)} .r-warn .fl-rule-dot{background:var(--warn)} .r-info .fl-rule-dot{background:var(--cool)}

.fl-clear{margin-left:auto;background:none;border:1px solid var(--line);border-radius:6px;color:var(--dim);font-size:10px;padding:2px 7px;cursor:pointer;text-transform:none;letter-spacing:0;font-family:inherit}
.fl-plate-big{position:relative;border-radius:12px;overflow:hidden;border:1px solid var(--line)}
.fl-plate-sm{position:relative;border-radius:9px;overflow:hidden}
.fl-plate-svg{display:block;width:100%;height:auto}
.fl-light{position:absolute;inset:0;mix-blend-mode:multiply;pointer-events:none;transition:background .3s,filter .3s}

.fl-strip{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}
@media(max-width:560px){.fl-strip{grid-template-columns:repeat(3,1fr)}}
.fl-strip-cell{position:relative;background:none;border:1px solid transparent;border-radius:11px;padding:5px;cursor:pointer;font-family:inherit;transition:.15s}
.fl-strip-cell:hover{border-color:var(--line)}
.fl-strip-cell.active{border-color:var(--amber);background:#fff4e4}
.fl-badge{position:absolute;top:-6px;left:50%;transform:translateX(-50%);background:var(--amber);color:#fff;font-size:9px;letter-spacing:0.1em;text-transform:uppercase;padding:2px 7px;border-radius:20px;z-index:2;font-weight:700}
.fl-strip-meta{display:flex;flex-direction:column;align-items:center;gap:0;margin-top:5px}
.fl-strip-meta strong{font-size:10.5px;color:var(--ink)}
.fl-strip-meta span{font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}

.fl-bars{display:flex;flex-direction:column;gap:9px}
.fl-bar-row{display:flex;align-items:center;gap:10px}
.fl-bar-name{width:120px;font-size:12px;flex:none}
.fl-bar-track{flex:1;height:14px;background:#efe6d4;border-radius:8px;overflow:hidden}
.fl-bar-fill{height:100%;border-radius:8px;transition:width .35s ease}
.fl-bar-val{width:26px;text-align:right;font-size:12px;font-variant-numeric:tabular-nums;color:var(--dim)}

.fl-model-toggle{margin-top:16px;background:none;border:1px solid var(--line);border-radius:9px;color:var(--dim);font-size:12.5px;padding:9px 15px;cursor:pointer;font-family:inherit}
.fl-model-toggle:hover{border-color:var(--amber);color:var(--ink)}
.fl-model{margin-top:12px}
.fl-model pre{font-family:'Fraunces',serif;white-space:pre-wrap;font-size:12px;line-height:1.7;background:#fbf5ea;border-radius:10px;padding:14px;overflow-x:auto;margin:0 0 12px;color:#5a4426}
.fl-model p{font-size:12.5px;color:var(--dim);line-height:1.6;margin:0}
.fl-model p em{color:var(--ink)}
`;

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