cn0303 commited on
Commit
562dfe1
·
verified ·
1 Parent(s): 412a862

Reskin: CRT-pixel technical-instrument UI (dense option table, roofline centerpiece, pixel icons, phosphor-green, no emoji/em-dash)

Browse files
Files changed (4) hide show
  1. static/app.js +32 -32
  2. static/icons.js +82 -55
  3. static/index.html +13 -9
  4. static/style.css +372 -329
static/app.js CHANGED
@@ -1,5 +1,5 @@
1
  /* ============================================================
2
- FitCheck — frontend logic (UI brick)
3
  Gathers inputs, calls the /api/advise connector, renders results.
4
  The backend currently returns input-aware PLACEHOLDER data; the
5
  real engine plugs into the same /api/advise contract later.
@@ -113,7 +113,7 @@ function sanitizeHtml(html) {
113
  return tpl.innerHTML;
114
  }
115
  const state = { mode: "have", task: "run", computer: "Windows laptop", provider: "none", priority: "balanced", usecases: ["chat"], checked: false };
116
- let lastAdvice = null; // the most recent /api/advise result — facts the model explains
117
  let multiCache = null; // {ucs, results} when several goals are checked at once
118
 
119
  // ---- Buy-vs-check mode -----------------------------------------------------
@@ -185,7 +185,7 @@ function setActive(id, val) {
185
  $(id).querySelectorAll(".seg-btn").forEach(b => b.classList.toggle("active", b.dataset.val === val));
186
  }
187
 
188
- // Run vs Train sliding switch — recomputes the recommendations for training.
189
  function wireTaskToggle() {
190
  const tog = $("#task-toggle");
191
  if (!tog) return;
@@ -249,7 +249,7 @@ function findSpecsText() {
249
  <ul style="margin:0;padding-left:18px">
250
  <li><b>RAM:</b> press <code>Ctrl + Shift + Esc</code>, then <b>Performance</b>, then <b>Memory</b>.</li>
251
  <li><b>Graphics card:</b> same window, <b>GPU</b>. The name is top-right (e.g. <i>NVIDIA RTX 3060</i>).</li>
252
- <li>No real GPU listed? You have built-in graphics. Pick “None / built-in”.</li>
253
  </ul>`;
254
  }
255
 
@@ -321,7 +321,7 @@ async function check() {
321
  // finetune that walks to a catalogue base) re-renders the full breakdown; a
322
  // model we don't know falls back to clearly-labelled raw parameter math.
323
  async function lookupRepo(payload) {
324
- $("#results").innerHTML = `<div class="reveal"><div class="ans-loading"><span class="spinner"></span> Looking up ${esc(payload.repo)} on Hugging Face…</div></div>`;
325
  let d;
326
  try {
327
  const res = await fetch("/api/lookup", {
@@ -363,7 +363,7 @@ async function lookupRepo(payload) {
363
  if (wrap) {
364
  const banner = document.createElement("div");
365
  banner.className = "lookup-banner estimate";
366
- banner.innerHTML = `<span class="live-tag">Live Hugging Face lookup · estimate</span><p>${sanitizeHtml(d.explain || "")}</p>`;
367
  wrap.prepend(banner);
368
  }
369
  return;
@@ -383,7 +383,7 @@ function renderMulti(results) {
383
  <div class="goal-card" data-i="${i}" style="--status:${v.cls};--status-soft:${v.soft}">
384
  <div class="goal-top"><span class="badge"><span class="dot"></span>${esc(d.verdict_word || v.word)}</span></div>
385
  <div class="goal-name">${esc(d.use_case || "")}</div>
386
- <div class="goal-pick">${d.headline_model ? `→ ${esc(d.headline_model)}` : "Nothing realistic on this machine"}</div>
387
  <div class="goal-need">${esc(need)}</div>
388
  <div class="goal-more">See full breakdown</div>
389
  </div>`;
@@ -402,7 +402,7 @@ function renderMulti(results) {
402
  $("#results").querySelectorAll(".goal-card").forEach(c => c.addEventListener("click", () => {
403
  render(multiCache.results[parseInt(c.dataset.i, 10)]);
404
  }));
405
- $("#cat-version").textContent = (results[0] || {}).catalogue_version || "—";
406
  }
407
 
408
  // ---- Buy-advice render: multi-platform, click any card for what to look for -
@@ -413,7 +413,7 @@ function renderBuy(d) {
413
  const tierCard = (t, kind) => {
414
  if (!t) return `<div class="buy-card empty">
415
  <div class="bc-head"><span class="bc-kind ${kind}">${kind === "min" ? "Minimum" : "Comfortable"}</span></div>
416
- <p class="bc-none">No build on this platform ${ft ? "trains" : "runs"} ${multi ? "all of these" : "this"}${ft ? "" : " well"} — it wants more than this ladder offers.</p>
417
  </div>`;
418
  const goalsHtml = multi
419
  ? `<ul class="bc-goals">${t.goals.map(g => `<li><span>${esc(g.goal)}</span><b>${esc(g.model)}</b>${g.verdict === "tight" ? " <i>(tight)</i>" : ""}</li>`).join("")}</ul>`
@@ -435,7 +435,7 @@ function renderBuy(d) {
435
  $("#results").innerHTML = `
436
  <div class="reveal">
437
  <div class="verdict" style="--status:var(--accent);--status-soft:var(--accent-soft)">
438
- <span class="badge"><span class="dot"></span>Buying advice · to ${verb}</span>
439
  <h2>What to buy to ${verb} ${esc((d.use_case || "this").toLowerCase())}</h2>
440
  <p>${esc(d.disclaimer || "")}</p>
441
  ${d.note ? `<div class="note">${sanitizeHtml(d.note)}</div>` : ""}
@@ -449,21 +449,21 @@ function renderBuy(d) {
449
  if (opening) det.removeAttribute("hidden"); else det.setAttribute("hidden", "");
450
  b.classList.toggle("open", opening);
451
  }));
452
- $("#cat-version").textContent = d.catalogue_version || "—";
453
  }
454
 
455
  // ---- Render results -------------------------------------------------------
456
  const VMAP = {
457
- great: { cls: "var(--ok)", soft: "var(--ok-soft)", word: "Runs great", em: "✓" },
458
- tight: { cls: "var(--warn)", soft: "var(--warn-soft)", word: "Tight, but works", em: "≈" },
459
- no: { cls: "var(--no)", soft: "var(--no-soft)", word: "Won't fit", em: "✕" },
460
  };
461
 
462
  function render(d) {
463
  lastAdvice = d;
464
  const v = VMAP[d.verdict] || VMAP.tight;
465
  const g = d.gauge || {};
466
- $("#cat-version").textContent = d.catalogue_version || "—";
467
 
468
  const licChip = (o) => {
469
  if (!o.license) return "";
@@ -481,9 +481,9 @@ function render(d) {
481
  ? `<a href="${safeUrl(o.url)}" target="_blank" rel="noopener">${esc(o.model)}</a>` : esc(o.model);
482
  const cur = o.model === current ? " opt-current" : "";
483
  return `<div class="opt${cur}" data-model="${esc(o.model)}" style="--status:${ov.cls};--status-soft:${ov.soft}">
484
- <div class="vdot">${ov.em}</div>
485
  <div><div class="name">${name}${licChip(o)}</div><div class="desc">${esc(o.desc)}</div></div>
486
- <div class="meta"><b>${esc(o.memory)}</b><div class="feel">${esc(o.setting)}${o.feel && o.feel !== "—" ? " · " + esc(o.feel) : ""}</div></div>
487
  </div>`;
488
  }).join("");
489
 
@@ -531,8 +531,8 @@ function render(d) {
531
  <div class="gauge" style="--pct:${g.fill_pct};--gcolor:${v.cls}">
532
  <div class="gauge-fill"></div>
533
  </div>
534
- ${g.has_fast && g.fast_label ? `<div class="gmark gmark-gpu" style="left:${g.mark_pct}%" data-tip="${esc(g.fast_label)} — your graphics card's own memory. A model that fits left of this line runs at full GPU speed."><span class="gmark-lab">${esc(g.fast_label.replace("On the GPU ","").replace("GPU can use","GPU"))} ${esc(g.fast_gb)}</span></div>` : ""}
535
- <div class="gmark gmark-total" style="left:${g.total_pct}%" data-tip="${g.has_fast ? "Everything available: GPU memory plus the system RAM a model can spill into (the spilled part runs slower)." : "Your system RAM — with no graphics card, the model runs here on the processor."}"><span class="gmark-lab">${esc(g.total_label)} ${esc(g.total_gb)}</span></div>
536
  </div>
537
  <div class="gauge-legend">
538
  ${(g.breakdown||[]).map(b=>`<span class="item"><span class="sw" style="background:${esc(b.color)}"></span>${esc(b.label)}</span>`).join("")}
@@ -549,18 +549,18 @@ function render(d) {
549
  <p class="viz-caption">
550
  Every grey dot is a <b>real benchmark run</b> from the
551
  <a href="https://www.localscore.ai" target="_blank" rel="noopener">LocalScore</a> community database.
552
- Generation speed tracks <b>memory bandwidth</b> — that's the one number that matters most for local AI
553
  (<a href="https://kipp.ly/transformer-inference-arithmetic/" target="_blank" rel="noopener">why</a>).
554
  The dashed line is the theoretical ceiling for <b>${esc(d.speed.model)}</b> at your setting;
555
- your machine is the marked dot at ≈<b>${esc(d.speed.tps)} tok/s</b>
556
  (${d.speed.method === "measured-model"
557
  ? `predicted by a model trained on these measurements, following IBM's <a href="https://arxiv.org/abs/2410.02425" target="_blank" rel="noopener">LLM-Pilot</a> methodology`
558
- : `an analytical estimate; a learned predictor trained on these runs — <a href="https://arxiv.org/abs/2410.02425" target="_blank" rel="noopener">LLM-Pilot</a> methodology — takes over once trained`}).
559
  </p>
560
  </div>
561
  </details>` : ""}
562
 
563
- ${opts ? `<div class="section-title">${isFT ? "What you can fine-tune" : "What you can run"} <span class="sub">biggest to smallest — click any model for its full breakdown; names link to Hugging Face</span></div>
564
  <div class="opt-grid">${opts}</div>` : ""}
565
 
566
  ${tools ? `<div class="section-title">${isFT ? "How to fine-tune it" : "How to actually run it"}</div>
@@ -591,7 +591,7 @@ function render(d) {
591
  if (multiCache) {
592
  const back = document.createElement("button");
593
  back.className = "back-link";
594
- back.textContent = "← All goals";
595
  back.addEventListener("click", () => renderMulti(multiCache.results));
596
  $("#results").firstElementChild.prepend(back);
597
  }
@@ -599,7 +599,7 @@ function render(d) {
599
  if (d.speed) drawRoofline(d.speed);
600
  $("#results").querySelectorAll(".copy-btn").forEach(b => b.addEventListener("click", () => {
601
  navigator.clipboard.writeText(decodeURIComponent(b.dataset.code));
602
- b.textContent = "Copied ✓"; b.classList.add("done");
603
  setTimeout(() => { b.textContent = "Copy"; b.classList.remove("done"); }, 1500);
604
  }));
605
  // Click any option card to re-render the whole breakdown for THAT model.
@@ -683,7 +683,7 @@ async function drawRoofline(speed) {
683
  // flip the label to the left when the dot sits near the right edge
684
  const flip = ux > W * 0.72;
685
  s += `<circle cx="${ux}" cy="${uy}" r="6" class="rl-you" data-you="1" data-m="${esc(speed.model)}" data-t="${esc(speed.tps)}" data-b="${esc(Math.round(speed.eff_bw || speed.bw))}"/>` +
686
- `<text x="${flip ? ux - 10 : ux + 10}" y="${uy + 4}" class="rl-you-label" text-anchor="${flip ? "end" : "start"}">you ≈${speed.tps} tok/s</text>`;
687
  }
688
  s += `</svg>
689
  <div class="rl-legend">
@@ -709,8 +709,8 @@ async function drawRoofline(speed) {
709
  if (!cls || (!cls.includes("rl-dot") && !cls.includes("rl-you"))) { tip.hidden = true; return; }
710
  const d = t.dataset;
711
  tip.innerHTML = d.you
712
- ? `<b>Your machine</b><br>${esc(d.m || "your pick")} ≈ <b>${esc(d.t)} tok/s</b><br><span class="rl-tip-sub">at ~${esc(d.b)} GB/s memory bandwidth</span>`
713
- : `<b>${esc(d.m || "model")}</b>${d.p ? ` · ${esc(d.p)}B` : ""}<br>${esc(d.a || "")}<br><b>${esc(d.t)} tok/s</b> <span class="rl-tip-sub">at ~${esc(d.b)} GB/s</span>`;
714
  tip.hidden = false;
715
  const r = host.getBoundingClientRect();
716
  const x = e.clientX - r.left, y = e.clientY - r.top;
@@ -728,7 +728,7 @@ function startTicker(el, base) {
728
  const tick = () => {
729
  if (el.dataset.ticking !== "1") return;
730
  const s = Math.round((Date.now() - t0) / 1000);
731
- el.innerHTML = `<span class="spinner"></span> ${base} — ${s}s` +
732
  (s > 8 ? " <span class='tick-note'>(cold start: the model is waking, up to ~1 min)</span>" : "");
733
  setTimeout(tick, 500);
734
  };
@@ -742,7 +742,7 @@ async function parsePaste() {
742
  const hint = $("#parse-hint");
743
  const btn = $("#parse-btn");
744
  if (!text) { hint.textContent = "Type or paste something first."; return; }
745
- if (btn.disabled) return; // one in-flight call, ever — GPU time is real money
746
  btn.disabled = true;
747
  const stop = startTicker(hint, "Reading your description");
748
  try {
@@ -793,8 +793,8 @@ function applyParsed(d, hint) {
793
  got.push(`${d.ram_gb} GB RAM`);
794
  }
795
  hint.textContent = got.length
796
- ? `Understood: ${got.join(", ")}. Anything you didn't mention stayed blank — confirm and press Check.`
797
- : "Couldn't find any specs in that text — nothing was filled in (the parser doesn't guess).";
798
  maybeLiveUpdate();
799
  }
800
 
 
1
  /* ============================================================
2
+ FitCheck, frontend logic (UI brick)
3
  Gathers inputs, calls the /api/advise connector, renders results.
4
  The backend currently returns input-aware PLACEHOLDER data; the
5
  real engine plugs into the same /api/advise contract later.
 
113
  return tpl.innerHTML;
114
  }
115
  const state = { mode: "have", task: "run", computer: "Windows laptop", provider: "none", priority: "balanced", usecases: ["chat"], checked: false };
116
+ let lastAdvice = null; // the most recent /api/advise result, facts the model explains
117
  let multiCache = null; // {ucs, results} when several goals are checked at once
118
 
119
  // ---- Buy-vs-check mode -----------------------------------------------------
 
185
  $(id).querySelectorAll(".seg-btn").forEach(b => b.classList.toggle("active", b.dataset.val === val));
186
  }
187
 
188
+ // Run vs Train sliding switch, recomputes the recommendations for training.
189
  function wireTaskToggle() {
190
  const tog = $("#task-toggle");
191
  if (!tog) return;
 
249
  <ul style="margin:0;padding-left:18px">
250
  <li><b>RAM:</b> press <code>Ctrl + Shift + Esc</code>, then <b>Performance</b>, then <b>Memory</b>.</li>
251
  <li><b>Graphics card:</b> same window, <b>GPU</b>. The name is top-right (e.g. <i>NVIDIA RTX 3060</i>).</li>
252
+ <li>No real GPU listed? You have built-in graphics. Pick "None / built-in".</li>
253
  </ul>`;
254
  }
255
 
 
321
  // finetune that walks to a catalogue base) re-renders the full breakdown; a
322
  // model we don't know falls back to clearly-labelled raw parameter math.
323
  async function lookupRepo(payload) {
324
+ $("#results").innerHTML = `<div class="reveal"><div class="ans-loading"><span class="spinner"></span> Looking up ${esc(payload.repo)} on Hugging Face...</div></div>`;
325
  let d;
326
  try {
327
  const res = await fetch("/api/lookup", {
 
363
  if (wrap) {
364
  const banner = document.createElement("div");
365
  banner.className = "lookup-banner estimate";
366
+ banner.innerHTML = `<span class="live-tag">Live Hugging Face lookup / estimate</span><p>${sanitizeHtml(d.explain || "")}</p>`;
367
  wrap.prepend(banner);
368
  }
369
  return;
 
383
  <div class="goal-card" data-i="${i}" style="--status:${v.cls};--status-soft:${v.soft}">
384
  <div class="goal-top"><span class="badge"><span class="dot"></span>${esc(d.verdict_word || v.word)}</span></div>
385
  <div class="goal-name">${esc(d.use_case || "")}</div>
386
+ <div class="goal-pick">${d.headline_model ? `<span class="ic" data-ic="arrow"></span>${esc(d.headline_model)}` : "Nothing realistic on this machine"}</div>
387
  <div class="goal-need">${esc(need)}</div>
388
  <div class="goal-more">See full breakdown</div>
389
  </div>`;
 
402
  $("#results").querySelectorAll(".goal-card").forEach(c => c.addEventListener("click", () => {
403
  render(multiCache.results[parseInt(c.dataset.i, 10)]);
404
  }));
405
+ $("#cat-version").textContent = (results[0] || {}).catalogue_version || "local";
406
  }
407
 
408
  // ---- Buy-advice render: multi-platform, click any card for what to look for -
 
413
  const tierCard = (t, kind) => {
414
  if (!t) return `<div class="buy-card empty">
415
  <div class="bc-head"><span class="bc-kind ${kind}">${kind === "min" ? "Minimum" : "Comfortable"}</span></div>
416
+ <p class="bc-none">No build on this platform ${ft ? "trains" : "runs"} ${multi ? "all of these" : "this"}${ft ? "" : " well"}, it wants more than this ladder offers.</p>
417
  </div>`;
418
  const goalsHtml = multi
419
  ? `<ul class="bc-goals">${t.goals.map(g => `<li><span>${esc(g.goal)}</span><b>${esc(g.model)}</b>${g.verdict === "tight" ? " <i>(tight)</i>" : ""}</li>`).join("")}</ul>`
 
435
  $("#results").innerHTML = `
436
  <div class="reveal">
437
  <div class="verdict" style="--status:var(--accent);--status-soft:var(--accent-soft)">
438
+ <span class="badge"><span class="dot"></span>Buying advice, to ${verb}</span>
439
  <h2>What to buy to ${verb} ${esc((d.use_case || "this").toLowerCase())}</h2>
440
  <p>${esc(d.disclaimer || "")}</p>
441
  ${d.note ? `<div class="note">${sanitizeHtml(d.note)}</div>` : ""}
 
449
  if (opening) det.removeAttribute("hidden"); else det.setAttribute("hidden", "");
450
  b.classList.toggle("open", opening);
451
  }));
452
+ $("#cat-version").textContent = d.catalogue_version || "local";
453
  }
454
 
455
  // ---- Render results -------------------------------------------------------
456
  const VMAP = {
457
+ great: { cls: "var(--ok)", soft: "var(--ok-soft)", word: "Runs great", ic: "check" },
458
+ tight: { cls: "var(--warn)", soft: "var(--warn-soft)", word: "Tight, but works", ic: "approx" },
459
+ no: { cls: "var(--no)", soft: "var(--no-soft)", word: "Won't fit", ic: "x" },
460
  };
461
 
462
  function render(d) {
463
  lastAdvice = d;
464
  const v = VMAP[d.verdict] || VMAP.tight;
465
  const g = d.gauge || {};
466
+ $("#cat-version").textContent = d.catalogue_version || "local";
467
 
468
  const licChip = (o) => {
469
  if (!o.license) return "";
 
481
  ? `<a href="${safeUrl(o.url)}" target="_blank" rel="noopener">${esc(o.model)}</a>` : esc(o.model);
482
  const cur = o.model === current ? " opt-current" : "";
483
  return `<div class="opt${cur}" data-model="${esc(o.model)}" style="--status:${ov.cls};--status-soft:${ov.soft}">
484
+ <div class="vdot"><span class="ic" data-ic="${ov.ic}"></span></div>
485
  <div><div class="name">${name}${licChip(o)}</div><div class="desc">${esc(o.desc)}</div></div>
486
+ <div class="meta"><b>${esc(o.memory)}</b><div class="feel">${esc(o.setting)}${o.feel && o.feel !== "-" ? " / " + esc(o.feel) : ""}</div></div>
487
  </div>`;
488
  }).join("");
489
 
 
531
  <div class="gauge" style="--pct:${g.fill_pct};--gcolor:${v.cls}">
532
  <div class="gauge-fill"></div>
533
  </div>
534
+ ${g.has_fast && g.fast_label ? `<div class="gmark gmark-gpu" style="left:${g.mark_pct}%" data-tip="${esc(g.fast_label)}, your graphics card's own memory. A model that fits left of this line runs at full GPU speed."><span class="gmark-lab">${esc(g.fast_label.replace("On the GPU ","").replace("GPU can use","GPU"))} ${esc(g.fast_gb)}</span></div>` : ""}
535
+ <div class="gmark gmark-total" style="left:${g.total_pct}%" data-tip="${g.has_fast ? "Everything available: GPU memory plus the system RAM a model can spill into (the spilled part runs slower)." : "Your system RAM, with no graphics card the model runs here on the processor."}"><span class="gmark-lab">${esc(g.total_label)} ${esc(g.total_gb)}</span></div>
536
  </div>
537
  <div class="gauge-legend">
538
  ${(g.breakdown||[]).map(b=>`<span class="item"><span class="sw" style="background:${esc(b.color)}"></span>${esc(b.label)}</span>`).join("")}
 
549
  <p class="viz-caption">
550
  Every grey dot is a <b>real benchmark run</b> from the
551
  <a href="https://www.localscore.ai" target="_blank" rel="noopener">LocalScore</a> community database.
552
+ Generation speed tracks <b>memory bandwidth</b>, that's the one number that matters most for local AI
553
  (<a href="https://kipp.ly/transformer-inference-arithmetic/" target="_blank" rel="noopener">why</a>).
554
  The dashed line is the theoretical ceiling for <b>${esc(d.speed.model)}</b> at your setting;
555
+ your machine is the marked dot at about <b>${esc(d.speed.tps)} tok/s</b>
556
  (${d.speed.method === "measured-model"
557
  ? `predicted by a model trained on these measurements, following IBM's <a href="https://arxiv.org/abs/2410.02425" target="_blank" rel="noopener">LLM-Pilot</a> methodology`
558
+ : `an analytical estimate; a learned predictor trained on these runs, <a href="https://arxiv.org/abs/2410.02425" target="_blank" rel="noopener">LLM-Pilot</a> methodology, takes over once trained`}).
559
  </p>
560
  </div>
561
  </details>` : ""}
562
 
563
+ ${opts ? `<div class="section-title">${isFT ? "What you can fine-tune" : "What you can run"} <span class="sub">biggest to smallest, click any model for its full breakdown; names link to Hugging Face</span></div>
564
  <div class="opt-grid">${opts}</div>` : ""}
565
 
566
  ${tools ? `<div class="section-title">${isFT ? "How to fine-tune it" : "How to actually run it"}</div>
 
591
  if (multiCache) {
592
  const back = document.createElement("button");
593
  back.className = "back-link";
594
+ back.innerHTML = '<span class="ic" data-ic="arrow"></span>All goals';
595
  back.addEventListener("click", () => renderMulti(multiCache.results));
596
  $("#results").firstElementChild.prepend(back);
597
  }
 
599
  if (d.speed) drawRoofline(d.speed);
600
  $("#results").querySelectorAll(".copy-btn").forEach(b => b.addEventListener("click", () => {
601
  navigator.clipboard.writeText(decodeURIComponent(b.dataset.code));
602
+ b.textContent = "Copied"; b.classList.add("done");
603
  setTimeout(() => { b.textContent = "Copy"; b.classList.remove("done"); }, 1500);
604
  }));
605
  // Click any option card to re-render the whole breakdown for THAT model.
 
683
  // flip the label to the left when the dot sits near the right edge
684
  const flip = ux > W * 0.72;
685
  s += `<circle cx="${ux}" cy="${uy}" r="6" class="rl-you" data-you="1" data-m="${esc(speed.model)}" data-t="${esc(speed.tps)}" data-b="${esc(Math.round(speed.eff_bw || speed.bw))}"/>` +
686
+ `<text x="${flip ? ux - 10 : ux + 10}" y="${uy + 4}" class="rl-you-label" text-anchor="${flip ? "end" : "start"}">you ~${speed.tps} tok/s</text>`;
687
  }
688
  s += `</svg>
689
  <div class="rl-legend">
 
709
  if (!cls || (!cls.includes("rl-dot") && !cls.includes("rl-you"))) { tip.hidden = true; return; }
710
  const d = t.dataset;
711
  tip.innerHTML = d.you
712
+ ? `<b>Your machine</b><br>${esc(d.m || "your pick")} ~ <b>${esc(d.t)} tok/s</b><br><span class="rl-tip-sub">at ~${esc(d.b)} GB/s memory bandwidth</span>`
713
+ : `<b>${esc(d.m || "model")}</b>${d.p ? ` / ${esc(d.p)}B` : ""}<br>${esc(d.a || "")}<br><b>${esc(d.t)} tok/s</b> <span class="rl-tip-sub">at ~${esc(d.b)} GB/s</span>`;
714
  tip.hidden = false;
715
  const r = host.getBoundingClientRect();
716
  const x = e.clientX - r.left, y = e.clientY - r.top;
 
728
  const tick = () => {
729
  if (el.dataset.ticking !== "1") return;
730
  const s = Math.round((Date.now() - t0) / 1000);
731
+ el.innerHTML = `<span class="spinner"></span> ${base}, ${s}s` +
732
  (s > 8 ? " <span class='tick-note'>(cold start: the model is waking, up to ~1 min)</span>" : "");
733
  setTimeout(tick, 500);
734
  };
 
742
  const hint = $("#parse-hint");
743
  const btn = $("#parse-btn");
744
  if (!text) { hint.textContent = "Type or paste something first."; return; }
745
+ if (btn.disabled) return; // one in-flight call, ever, GPU time is real money
746
  btn.disabled = true;
747
  const stop = startTicker(hint, "Reading your description");
748
  try {
 
793
  got.push(`${d.ram_gb} GB RAM`);
794
  }
795
  hint.textContent = got.length
796
+ ? `Understood: ${got.join(", ")}. Anything you didn't mention stayed blank, confirm and press Check.`
797
+ : "Couldn't find any specs in that text, nothing was filled in (the parser doesn't guess).";
798
  maybeLiveUpdate();
799
  }
800
 
static/icons.js CHANGED
@@ -1,55 +1,82 @@
1
- /* Auto-generated by build_icons.py. Inline icons = offline-safe. */
2
- window.ICONS = {
3
- "chat": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719\" /> </svg>",
4
- "run": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <circle cx=\"15\" cy=\"4\" r=\"1.4\" /> <path d=\"M7 18l4 .8 .6-1.4\" /> <path d=\"M17 21v-4l-4-3 1-6\" /> <path d=\"M9 12V9l5-1 3 3 3 1\" /> <path d=\"M1 8h3\" /> <path d=\"M0 12h2.5\" /> <path d=\"M1.5 16h2.5\" /> </svg>",
5
- "writing": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" /> <path d=\"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z\" /> </svg>",
6
- "coding": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m18 16 4-4-4-4\" /> <path d=\"m6 8-4 4 4 4\" /> <path d=\"m14.5 4-5 16\" /> </svg>",
7
- "agents": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 8V4H8\" /> <rect x=\"4\" y=\"8\" rx=\"2\" /> <path d=\"M2 14h2\" /> <path d=\"M20 14h2\" /> <path d=\"M15 13v2\" /> <path d=\"M9 13v2\" /> </svg>",
8
- "rag": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 7v14\" /> <path d=\"M16 12h2\" /> <path d=\"M16 8h2\" /> <path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\" /> <path d=\"M6 12h2\" /> <path d=\"M6 8h2\" /> </svg>",
9
- "translate": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m5 8 6 6\" /> <path d=\"m4 14 6-6 2-3\" /> <path d=\"M2 5h12\" /> <path d=\"M7 2h1\" /> <path d=\"m22 22-5-10-5 10\" /> <path d=\"M14 18h6\" /> </svg>",
10
- "detect": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" /> <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" /> <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" /> <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" /> <circle cx=\"12\" cy=\"12\" r=\"3\" /> <path d=\"m16 16-1.9-1.9\" /> </svg>",
11
- "segment": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z\" /> <path d=\"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12\" /> <path d=\"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17\" /> </svg>",
12
- "pose": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\" /> <path d=\"m3.3 7 8.7 5 8.7-5\" /> <path d=\"M12 22V12\" /> </svg>",
13
- "classify": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z\" /> <path d=\"M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193\" /> <circle cx=\"10.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\" /> </svg>",
14
- "depth": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m8 3 4 8 5-5 5 15H2L8 3z\" /> </svg>",
15
- "ocr": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" /> <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" /> <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" /> <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" /> <path d=\"M7 8h8\" /> <path d=\"M7 12h10\" /> <path d=\"M7 16h6\" /> </svg>",
16
- "imagegen": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <rect x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" /> <circle cx=\"9\" cy=\"9\" r=\"2\" /> <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" /> </svg>",
17
- "inpaint": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72\" /> <path d=\"m14 7 3 3\" /> <path d=\"M5 6v4\" /> <path d=\"M19 14v4\" /> <path d=\"M10 2v2\" /> <path d=\"M7 8H3\" /> <path d=\"M21 16h-4\" /> <path d=\"M11 3H9\" /> </svg>",
18
- "upscale": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" /> <path d=\"M14 15H9v-5\" /> <path d=\"M16 3h5v5\" /> <path d=\"M21 3 9 15\" /> </svg>",
19
- "videogen": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <rect x=\"3\" y=\"3\" rx=\"2\" /> <path d=\"M7 3v18\" /> <path d=\"M3 7.5h4\" /> <path d=\"M3 12h18\" /> <path d=\"M3 16.5h4\" /> <path d=\"M17 3v18\" /> <path d=\"M17 7.5h4\" /> <path d=\"M17 16.5h4\" /> </svg>",
20
- "bgremove": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <circle cx=\"6\" cy=\"6\" r=\"3\" /> <path d=\"M8.12 8.12 12 12\" /> <path d=\"M20 4 8.12 15.88\" /> <circle cx=\"6\" cy=\"18\" r=\"3\" /> <path d=\"M14.8 14.8 20 20\" /> </svg>",
21
- "stt": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 19v3\" /> <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" /> <rect x=\"9\" y=\"2\" rx=\"3\" /> </svg>",
22
- "tts": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2 10v3\" /> <path d=\"M6 6v11\" /> <path d=\"M10 3v18\" /> <path d=\"M14 8v7\" /> <path d=\"M18 5v13\" /> <path d=\"M22 10v3\" /> </svg>",
23
- "music": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M9 18V5l12-2v13\" /> <circle cx=\"6\" cy=\"18\" r=\"3\" /> <circle cx=\"18\" cy=\"16\" r=\"3\" /> </svg>",
24
- "embed": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <circle cx=\"12\" cy=\"12\" r=\"10\" /> <path d=\"m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z\" /> </svg>",
25
- "forecast": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M16 7h6v6\" /> <path d=\"m22 7-8.5 8.5-5-5L2 17\" /> </svg>",
26
- "tabular": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3v18\" /> <rect x=\"3\" y=\"3\" rx=\"2\" /> <path d=\"M3 9h18\" /> <path d=\"M3 15h18\" /> </svg>",
27
- "finetune": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z\" /> <path d=\"M22 10v6\" /> <path d=\"M6 12.5V16a6 3 0 0 0 12 0v-3.5\" /> </svg>",
28
- "train-vision": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z\" /> </svg>",
29
- "custom": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\" /> <path d=\"m15 5 4 4\" /> </svg>",
30
- "cat-text": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z\" /> </svg>",
31
- "cat-vision": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" /> <circle cx=\"12\" cy=\"12\" r=\"3\" /> </svg>",
32
- "cat-gen": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z\" /> <circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\" /> <circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\" /> <circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\" /> <circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\" /> </svg>",
33
- "cat-audio": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2\" /> </svg>",
34
- "cat-data": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M3 3v16a2 2 0 0 0 2 2h16\" /> <path d=\"M18 17V9\" /> <path d=\"M13 17V5\" /> <path d=\"M8 17v-3\" /> </svg>",
35
- "cat-train": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z\" /> <path d=\"M22 10v6\" /> <path d=\"M6 12.5V16a6 3 0 0 0 12 0v-3.5\" /> </svg>",
36
- "cat-custom": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\" /> <path d=\"m15 5 4 4\" /> </svg>",
37
- "check": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M20 6 9 17l-5-5\" /> </svg>",
38
- "search": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m21 21-4.34-4.34\" /> <circle cx=\"11\" cy=\"11\" r=\"8\" /> </svg>",
39
- "book": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 7v14\" /> <path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\" /> </svg>",
40
- "balanced": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3v18\" /> <path d=\"m19 8 3 8a5 5 0 0 1-6 0zV7\" /> <path d=\"M3 7h1a17 17 0 0 0 8-2 17 17 0 0 0 8 2h1\" /> <path d=\"m5 8 3 8a5 5 0 0 1-6 0zV7\" /> <path d=\"M7 21h10\" /> </svg>",
41
- "quality": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z\" /> <path d=\"M20 2v4\" /> <path d=\"M22 4h-4\" /> <circle cx=\"4\" cy=\"20\" r=\"2\" /> </svg>",
42
- "speed": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z\" /> </svg>",
43
- "offline": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" /> </svg>",
44
- "monitor": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m9 10 2 2 4-4\" /> <rect x=\"2\" y=\"3\" rx=\"2\" /> <path d=\"M12 17v4\" /> <path d=\"M8 21h8\" /> </svg>",
45
- "download": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 15V3\" /> <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" /> <path d=\"m7 10 5 5 5-5\" /> </svg>",
46
- "chevron": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m6 9 6 6 6-6\" /> </svg>",
47
- "arrow": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M5 12h14\" /> <path d=\"m12 5 7 7-7 7\" /> </svg>",
48
- "brand-windows": "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"3\" y=\"3\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"13\" y=\"3\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"3\" y=\"13\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"13\" y=\"13\" width=\"8\" height=\"8\" rx=\"1\"/></svg>",
49
- "brand-apple": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\"/></svg>",
50
- "brand-linux": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.398-.466.195-.135.445-.334.648-.467.156-.136.149-.267.279-.267.128.016.034.134-.147.332a8.097 8.097 0 01-.69.468zm-1.082-1.583V5.64c-.006-.02.013-.042.029-.05.074-.043.18-.027.26.004.063 0 .16.067.15.135-.006.049-.085.066-.135.066-.055 0-.092-.043-.141-.068-.052-.018-.146-.008-.163-.065zm-.551 0c-.02.058-.113.049-.166.066-.047.025-.086.068-.14.068-.05 0-.13-.02-.136-.068-.01-.066.088-.133.15-.133.08-.031.184-.047.259-.005.019.009.036.03.03.05v.02h.003z\"/></svg>",
51
- "brand-raspberrypi": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m19.8955 10.8961-.1726-.3028c.0068-2.1746-1.0022-3.061-2.1788-3.7348.356-.0938.7237-.1711.8245-.6182.6118-.1566.7397-.4398.8011-.7398.16-.1066.6955-.4061.6394-.9211.2998-.2069.4669-.4725.3819-.8487.3222-.3515.407-.6419.2702-.9096.3868-.4805.2152-.7295.05-.9817.2897-.5254.0341-1.0887-.7758-.9944-.3221-.4733-1.0244-.3659-1.133-.3637-.1215-.1519-.2819-.2821-.7755-.219-.3197-.2851-.6771-.2364-1.0458-.0964-.4378-.3403-.7275-.0675-1.0584.0356-.53-.1706-.6513.0631-.9117.1583-.5781-.1203-.7538.1416-1.0309.4182l-.3224-.0063c-.8719.5061-1.305 1.5366-1.4585 2.0664-.1536-.5299-.5858-1.5604-1.4575-2.0664l-.3223.0063C9.942.5014 9.7663.2394 9.1883.3597 8.9279.2646 8.807.0309 8.2766.2015c-.2172-.0677-.417-.2084-.6522-.2012l.0004.0002C7.5017.0041 7.369.049 7.2185.166c-.3688-.1401-.7262-.1887-1.0459.0964-.4936-.0631-.654.0671-.7756.219C5.2887.4791 4.5862.3717 4.264.845c-.8096-.0943-1.0655.4691-.7756.9944-.1653.2521-.3366.5013.05.9819-.1367.2677-.0519.5581.2703.9096-.085.3763.0822.6418.3819.8487-.0561.515.4795.8144.6394.9211.0614.3001.1894.5832.8011.7398.1008.4472.4685.5244.8245.6183-1.1766.6737-2.1856 1.56-2.1788 3.7348l-.1724.3028c-1.3491.8082-2.5629 3.4056-.6648 5.5167.124.6609.3319 1.1355.5171 1.6609.2769 2.117 2.0841 3.1082 2.5608 3.2255.6984.524 1.4423 1.0212 2.449 1.3696.949.964 1.977 1.3314 3.0107 1.3308.0152 0 .0306.0002.0457 0 1.0337.0006 2.0618-.3668 3.0107-1.3308 1.0067-.3483 1.7506-.8456 2.4491-1.3696.4766-.1173 2.2838-1.1085 2.5607-3.2255.1851-.5253.3931-1 .517-1.6609 1.8981-2.1113.6843-4.7089-.6649-5.517zm-1.0386-.3715c-.0704.8759-4.6354-3.0504-3.8472-3.1808 2.1391-.3558 3.9191.896 3.8472 3.1808zm-2.0155 4.3649c-1.1481.7409-2.8025.2626-3.6953-1.0681-.8928-1.3306-.6858-3.0101.4623-3.7509 1.1481-.7409 2.8025-.2627 3.6953 1.068.8927 1.3307.6858 3.0101-.4623 3.751zM13.6591 1.3721c.0396.1967.0843.321.1354.3577.2537-.272.4611-.5506.7878-.8123.0011.1537-.0776.3205.1169.4425.1752-.2356.4119-.4459.7263-.6244-.1514.2611-.026.3404.0554.4486.24-.2059.4681-.4144.9109-.5759-.121.1474-.2902.2914-.1108.4607.2473-.1544.496-.3086 1.0833-.4183-.1323.1475-.4059.295-.2401.4426.3104-.1186.6539-.2047 1.034-.2546-.182.1496-.3337.2963-.1846.4122.3323-.1022.7899-.2398 1.2372-.1212l-.2832.2849c-.0314.0382.6623.0297 1.1202.0364-.167.2321-.3375.4562-.437.8548.0454.0459.2723.0204.4862 0-.2194.4618-.6004.5783-.6893.776.134.1015.32.075.5232.006-.158.3254-.4892.5484-.7509.8123.0662.047.1818.075.4555.0425-.2418.257-.5339.492-.8802.7032.0614.0708.2722.0681.4678.0727-.3136.3069-.7173.466-1.0955.6668.1885.1288.3234.0988.4678.097-.2676.2198-.7225.3342-1.1448.4668.0803.1249.1607.1589.3324.194-.447.2473-1.0873.1343-1.2679.2607.0435.1243.1665.2053.3139.2728-.7197.0418-2.6879-.0262-3.0652-1.5156.7367-.8094 2.0813-1.7593 4.394-2.934-1.7994.6022-3.4229 1.405-4.7817 2.5096-1.5978-.7436-.4965-2.6197.283-3.3645zm-1.6126 5.3718c1.1329-.0123 2.5356.8325 2.53 1.6286-.005.7027-.9851 1.2715-2.5213 1.2607-1.5043-.0177-2.5172-.7148-2.5137-1.3957.003-.5603 1.2282-1.5263 2.505-1.4936zm-5.7646-.6006c.1717-.0351.252-.0692.3323-.194-.4223-.1327-.8772-.247-1.1448-.4668.1444.0018.2792.0318.4678-.097-.3783-.2008-.782-.3599-1.0956-.6668.1955-.0048.4064-.002.4677-.0728-.3462-.2113-.6383-.4463-.8801-.7033.2738.0325.3893.0045.4555-.0425-.2617-.264-.593-.487-.7509-.8123.2032.069.3892.0954.5232-.006-.089-.1977-.47-.3142-.6894-.776.214.0204.4409.0459.4863 0-.0994-.3985-.2698-.6226-.4369-.8547.4579-.0067 1.1516.0018 1.1202-.0364l-.2831-.2849c.4472-.1186.9049.019 1.2371.1213.1492-.1159-.0026-.2626-.1847-.4123.3801.05.7236.1361 1.034.2547.1659-.1476-.1076-.2951-.24-.4426.5872.1097.8361.2639 1.0833.4183.1794-.1694.0103-.3133-.1108-.4607.4428.1615.6709.37.911.5759.0814-.1082.2068-.1875.0554-.4486.3143.1785.5511.3888.7263.6244.1945-.122.1159-.2888.1169-.4426.3267.2618.534.5404.7879.8124.0511-.0366.0959-.161.1354-.3577.7794.7448 1.8807 2.6208.2831 3.3646-1.3589-1.1039-2.9817-1.9064-4.78-2.5086 2.3115 1.174 3.6556 2.1239 4.392 2.9328-.3773 1.4895-2.3455 1.5575-3.0651 1.5157.1473-.0676.2703-.1485.3139-.2728-.1806-.1264-.8209-.0134-1.2679-.2607zm2.8175 1.1334c.7881.1304-3.7769 4.0567-3.8472 3.1809-.0719-2.2846 1.7079-3.5367 3.8472-3.1809zm-4.847 8.7567c-1.1094-.8789-1.4668-3.4529.5901-4.6097 1.2394-.3273.4184 5.051-.5901 4.6097zm4.2656 4.5989c-.6257.3719-2.1452.2187-3.2252-1.3095-.7283-1.2823-.6345-2.5872-.123-2.9705.7648-.4589 1.9464.1609 2.8559 1.2003.7923.9405 1.1536 2.5927.4923 3.0797zm-1.2415-5.6086c-1.1481-.7409-1.3551-2.4203-.4623-3.7511.8928-1.3307 2.5472-1.8089 3.6952-1.068 1.1481.7409 1.3551 2.4203.4623 3.7509-.8926 1.3308-2.5471 1.809-3.6952 1.0682zm4.7948 8.2279c-1.3763.0584-2.7258-1.1105-2.7081-1.5157-.0206-.594 1.6758-1.0578 2.782-1.0306 1.1131-.0479 2.6068.3531 2.6097.8851.0184.5166-1.3547 1.6838-2.6836 1.6612zm2.7584-5.8578c.0081 1.3899-1.226 2.5225-2.7562 2.5299-1.5302.0073-2.7773-1.1135-2.7854-2.5033v-.0265c-.008-1.3899 1.2259-2.5226 2.7562-2.5299 1.5302-.0073 2.7773 1.1134 2.7853 2.5033a.7794.7794 0 0 1 .0001.0265zm3.855 2.0029c-1.186 1.6208-2.7916 1.684-3.3896 1.2325-.6255-.5811-.148-2.3854.7094-3.3747v-.0003c.9812-1.0912 2.0302-1.8037 2.7609-1.2469.4919.4828.7805 2.3008-.0807 3.3894zm1.0724-3.4301c-1.0086.4413-1.8298-4.9372-.5901-4.61 2.0568 1.1569 1.6994 3.731.5901 4.61zm-.0256-8.3279h.2985v-.5304h.2986c.1502 0 .2053.0624.2262.2052.0152.1088.0113.2395.0477.3253h.2984c-.0533-.0763-.0515-.2358-.0571-.3213-.0097-.1373-.0513-.2796-.1977-.3176v-.0037c.1502-.061.2149-.1807.2149-.341 0-.2048-.1539-.3738-.3974-.3738h-.732v1.3573zm.2985-1.1255h.3269c.1333 0 .2054.0573.2054.188 0 .1369-.0721.1942-.2054.1942H20.03v-.3822zm-1.0337.4633c0 .7009.5682 1.2694 1.2695 1.2694s1.2695-.5684 1.2695-1.2694c0-.7013-.5683-1.2697-1.2695-1.2697-.7013 0-1.2695.5684-1.2695 1.2697zm2.3275 0c0 .5845-.4737 1.058-1.058 1.058s-1.058-.4735-1.058-1.058c0-.5849.4737-1.058 1.058-1.058s1.058.4731 1.058 1.058z\"/></svg>",
52
- "brand-nvidia": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z\"/></svg>",
53
- "brand-amd": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M18.324 9.137l1.559 1.56h2.556v2.557L24 14.814V9.137zM2 9.52l-2 4.96h1.309l.37-.982H3.9l.408.982h1.338L3.432 9.52zm4.209 0v4.955h1.238v-3.092l1.338 1.562h.188l1.338-1.556v3.091h1.238V9.52H10.47l-1.592 1.845L7.287 9.52zm6.283 0v4.96h2.057c1.979 0 2.88-1.046 2.88-2.472 0-1.36-.937-2.488-2.747-2.488zm1.237.91h.792c1.17 0 1.63.711 1.63 1.57 0 .728-.372 1.572-1.616 1.572h-.806zm-10.985.273l.791 1.932H2.008zm17.137.307l-1.604 1.603v2.25h2.246l1.604-1.607h-2.246z\"/></svg>",
54
- "brand-intel": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M20.42 7.345v9.18h1.651v-9.18zM0 7.475v1.737h1.737V7.474zm9.78.352v6.053c0 .513.044.945.13 1.292.087.34.235.618.44.828.203.21.475.359.803.451.334.093.754.136 1.255.136h.216v-1.533c-.24 0-.445-.012-.593-.037a.672.672 0 0 1-.39-.173.693.693 0 0 1-.173-.377 4.002 4.002 0 0 1-.037-.606v-2.182h1.193v-1.416h-1.193V7.827zm-3.505 2.312c-.396 0-.76.08-1.082.241-.327.161-.6.384-.822.668l-.087.117v-.902H2.658v6.256h1.639v-3.214c.018-.588.16-1.02.433-1.299.29-.297.642-.445 1.044-.445.476 0 .841.149 1.082.433.235.284.359.686.359 1.2v3.324h1.663V12.97c.006-.89-.229-1.595-.686-2.09-.458-.495-1.1-.742-1.917-.742zm10.065.006a3.252 3.252 0 0 0-2.306.946c-.29.29-.525.637-.692 1.033a3.145 3.145 0 0 0-.254 1.273c0 .452.08.878.241 1.274.161.395.39.742.674 1.032.284.29.637.526 1.045.693.408.173.86.26 1.342.26 1.397 0 2.262-.637 2.782-1.23l-1.187-.904c-.248.297-.841.699-1.583.699-.464 0-.847-.105-1.138-.321a1.588 1.588 0 0 1-.593-.872l-.019-.056h4.915v-.587c0-.451-.08-.872-.235-1.267a3.393 3.393 0 0 0-.661-1.033 3.013 3.013 0 0 0-1.02-.692 3.345 3.345 0 0 0-1.311-.248zm-16.297.118v6.256h1.651v-6.256zm16.278 1.286c1.132 0 1.664.797 1.664 1.255l-3.32.006c0-.458.525-1.255 1.656-1.261zm7.073 3.814a.606.606 0 0 0-.606.606.606.606 0 0 0 .606.606.606.606 0 0 0 .606-.606.606.606 0 0 0-.606-.606zm-.008.105a.5.5 0 0 1 .002 0 .5.5 0 0 1 .5.501.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .498-.5zm-.233.155v.699h.13v-.285h.093l.173.285h.136l-.18-.297a.191.191 0 0 0 .118-.056c.03-.03.05-.074.05-.136 0-.068-.02-.117-.063-.154-.037-.038-.105-.056-.185-.056zm.13.099h.154c.019 0 .037.006.056.012a.064.064 0 0 1 .037.031c.013.013.012.031.012.056a.124.124 0 0 1-.012.055.164.164 0 0 1-.037.031c-.019.006-.037.013-.056.013h-.154Z\"/></svg>"
55
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Pixel-art icon set. Crisp integer-grid blocky forms (shape-rendering=crispEdges).
2
+ Same icon NAMES + API as before: <span class="ic" data-ic="NAME"> still works.
3
+ Line icons are drawn on an 8x8 grid with rect "pixels"; brand marks keep their
4
+ real silhouette but render crisp. All use currentColor so theme tints apply. */
5
+ (function () {
6
+ // helper: build an 8x8 pixel icon from a list of [x,y] (optionally [x,y,w,h]) cells
7
+ function px(cells) {
8
+ var r = cells.map(function (c) {
9
+ var x = c[0], y = c[1], w = c[2] || 1, h = c[3] || 1;
10
+ return '<rect x="' + x + '" y="' + y + '" width="' + w + '" height="' + h + '"/>';
11
+ }).join("");
12
+ return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" fill="currentColor" shape-rendering="crispEdges">' + r + '</svg>';
13
+ }
14
+
15
+ window.ICONS = {
16
+ // ---- text & chat ----
17
+ "chat": px([[0,1,8,4],[1,5],[2,6,2,1],[2,2],[4,2],[6,2],[2,3,4,1]]),
18
+ "run": px([[0,1,8,5],[3,6,2,1],[2,7,4,1]]), // monitor/run = screen + stand
19
+ "writing": px([[5,0],[4,1],[3,2],[2,3],[1,4],[0,5],[0,6,2,1],[1,7]]), // pencil diagonal
20
+ "coding": px([[1,2],[0,3,1,2],[1,5],[6,2],[7,3,1,2],[6,5],[4,1],[3,3],[4,5]]), // < / >
21
+ "agents": px([[1,1,6,5],[2,2,1,2],[5,2,1,2],[3,5,2,1],[0,3],[7,3],[2,6],[5,6]]), // bot head
22
+ "rag": px([[1,1,2,6],[5,1,2,6],[3,1,2,1],[3,6,2,1]]), // open book
23
+ "translate": px([[0,1,4,1],[1,2,2,1],[1,3],[5,4],[4,5,3,1],[3,6,5,1]]), // A / character
24
+ // ---- vision ----
25
+ "detect": px([[0,0,3,1],[5,0,3,1],[0,1],[7,1],[0,6],[7,6],[0,7,3,1],[5,7,3,1],[3,3,2,2]]), // crop + target
26
+ "segment": px([[1,1,3,3],[5,1,2,2],[1,5,2,2],[4,4,3,3]]), // tiles
27
+ "pose": px([[2,1,4,1],[1,2,1,5],[6,2,1,5],[2,7,4,1],[3,3,2,2]]), // cube/box
28
+ "classify": px([[1,1,4,1],[1,2,1,4],[5,2],[2,3,4,3],[5,5],[5,6],[6,4,1,3]]), // tag
29
+ "depth": px([[3,1],[2,2,2,1],[5,3,1,3],[1,4,6,1],[0,6,8,1]]), // mountain depth
30
+ "ocr": px([[0,0,3,1],[5,0,3,1],[0,1],[7,1],[0,6],[7,6],[0,7,3,1],[5,7,3,1],[2,3,4,1],[2,4,3,1]]), // frame + text lines
31
+ // ---- create ----
32
+ "imagegen": px([[0,1,8,6],[2,3,2,2],[5,4,2,2],[1,5,6,1]]), // picture
33
+ "inpaint": px([[5,0],[4,1],[3,2],[2,3],[1,4],[0,5,1,2],[1,6],[6,1,1,1]]), // brush
34
+ "upscale": px([[0,0,1,2],[0,0,2,1],[6,6,2,1],[7,5,1,2],[2,2],[3,3],[4,4],[5,5]]), // expand arrows
35
+ "videogen": px([[0,1,8,6],[1,1,1,6],[6,1,1,6],[3,3,2,2]]), // film frame
36
+ "bgremove": px([[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[1,6],[2,5],[5,2],[6,1]]), // scissors X
37
+ // ---- audio ----
38
+ "stt": px([[3,0,2,4],[2,1],[5,1],[1,3],[6,3],[3,5,2,1],[3,6,2,2]]), // mic
39
+ "tts": px([[0,3,1,2],[1,2,1,4],[2,3,1,2],[4,1,1,6],[5,2,1,4],[6,3,1,2],[7,4,1,1]]), // waveform
40
+ "music": px([[6,0,1,5],[3,1,3,1],[1,4,2,2],[4,4,2,2]]), // note
41
+ // ---- data ----
42
+ "embed": px([[3,0,2,1],[2,1],[5,1],[1,2],[6,2],[1,5],[6,5],[2,6],[5,6],[3,7,2,1],[3,3,2,2]]), // node graph
43
+ "forecast": px([[0,6],[1,5],[2,6],[3,4],[4,5],[5,2],[6,3],[7,1],[6,0],[5,0]]), // trend up
44
+ "tabular": px([[0,1,8,1],[0,3,8,1],[0,5,8,1],[3,1,1,5],[5,1,1,5]]), // table grid
45
+ // ---- training / custom ----
46
+ "finetune": px([[1,1,1,6],[1,1,5,1],[1,3,4,1],[1,5,3,1],[6,2],[6,5]]), // sliders
47
+ "train-vision": px([[5,0],[4,1],[3,2],[2,3],[1,4],[0,5,1,2],[1,6],[6,1]]),
48
+ "custom": px([[5,0],[4,1],[3,2],[2,3],[1,4],[0,5,1,2],[1,6],[6,1]]), // pencil
49
+ // ---- category headers ----
50
+ "cat-text": px([[0,1,8,4],[1,5],[2,6,2,1],[2,2,4,1]]),
51
+ "cat-vision": px([[0,3,8,2],[2,1,4,1],[2,5,4,1],[3,2,2,4]]), // eye
52
+ "cat-gen": px([[1,1,6,1],[1,1,1,6],[6,1,1,5],[2,6,4,1],[3,3,2,2]]),
53
+ "cat-audio": px([[0,3,1,2],[2,1,1,6],[4,0,1,8],[6,2,1,4]]),
54
+ "cat-data": px([[0,1,8,6],[1,4,1,2],[3,3,1,3],[5,2,1,4]]), // bar chart
55
+ "cat-train": px([[1,1,1,6],[1,1,5,1],[1,3,4,1],[1,5,3,1]]),
56
+ "cat-custom": px([[5,0],[4,1],[3,2],[2,3],[1,4],[0,5,1,2],[1,6]]),
57
+ // ---- ui / status ----
58
+ "check": px([[0,4],[1,5],[2,6],[3,5],[4,4],[5,3],[6,2],[7,1]]), // checkmark
59
+ "approx": px([[0,2],[1,1],[2,2],[3,3],[4,2],[0,5],[1,4],[2,5],[3,6],[4,5]]), // ~ approx
60
+ "x": px([[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[6,1],[5,2],[4,3],[2,5],[1,6],[0,7]]), // X
61
+ "search": px([[1,1,4,1],[1,2],[1,3],[5,2],[5,3],[1,4,4,1],[5,5],[6,6],[7,7]]), // magnifier
62
+ "book": px([[1,1,2,6],[5,1,2,6],[3,1,2,1],[3,6,2,1]]),
63
+ "balanced": px([[3,0,2,1],[0,2,8,1],[1,3,2,2],[6,3,2,2],[3,1,2,5],[2,6,4,1]]), // scales
64
+ "quality": px([[3,0,2,1],[3,7,2,1],[0,3,1,2],[7,3,1,2],[2,2],[5,2],[2,5],[5,5],[3,3,2,2]]), // sparkle/star
65
+ "speed": px([[5,0],[4,1],[3,2],[2,3,3,1],[5,4],[4,5],[3,6,1,1],[2,7]]), // lightning bolt
66
+ "offline": px([[2,0,4,1],[1,1],[6,1],[1,2,6,4],[2,6,4,1],[3,7,2,1]]), // shield
67
+ "monitor": px([[0,1,8,5],[3,6,2,1],[2,7,4,1],[2,3],[3,4],[4,3,2,1],[5,2]]), // monitor + check
68
+ "download": px([[3,0,2,4],[1,3,2,1],[5,3,2,1],[2,4],[5,4],[3,5,2,1],[0,7,8,1]]), // download arrow
69
+ "chevron": px([[0,2],[1,3],[2,4],[3,5],[4,4],[5,3],[6,2]]), // down chevron
70
+ "arrow": px([[0,3,6,2],[4,1,1,1],[5,2,1,1],[6,3,1,2],[5,5,1,1],[4,6,1,1]]), // right arrow
71
+
72
+ // ---- brand marks: crisp pixel silhouettes, ALL same neutral tone via currentColor ----
73
+ // Each provider rendered in the SAME style so none is favoured.
74
+ "brand-windows": px([[0,0,3,3],[5,0,3,3],[0,5,3,3],[5,5,3,3]]),
75
+ "brand-apple": px([[4,0],[3,1,2,1],[1,2,6,1],[1,3,6,4],[2,7],[5,7]]),
76
+ "brand-linux": px([[3,0,2,2],[2,2,4,1],[2,3,1,2],[5,3,1,2],[3,4,2,1],[1,5,6,1],[0,6,3,2],[5,6,3,2],[3,6,2,1]]),
77
+ "brand-raspberrypi": px([[2,0],[5,0],[2,1,4,1],[1,2,6,1],[0,3,3,2],[5,3,3,2],[1,5,6,1],[2,6,4,2]]),
78
+ "brand-nvidia": px([[1,1,6,6],[2,2,2,3],[4,3,2,2],[1,1,3,1]]),
79
+ "brand-amd": px([[0,0,2,8],[2,0,4,2],[6,2,2,2],[3,3,2,2]]),
80
+ "brand-intel": px([[0,2,8,4],[2,3],[4,3,2,2]])
81
+ };
82
+ })();
static/index.html CHANGED
@@ -3,10 +3,10 @@
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>FitCheck — what AI can your computer run?</title>
7
  <meta name="description" content="Tell FitCheck about your computer in plain words. Get an honest answer about what AI you can actually run on it, from chatbots to object detection, and how to start." />
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="/static/style.css" />
11
  </head>
12
  <body>
@@ -20,6 +20,7 @@
20
  <a href="#how">How it works</a>
21
  <a href="#glossary-anchor">Jargon, explained</a>
22
  </nav>
 
23
  </header>
24
 
25
  <main class="container">
@@ -32,6 +33,8 @@
32
  <div class="layout">
33
  <!-- ============================== INPUTS ============================== -->
34
  <aside class="panel form-panel">
 
 
35
  <!-- Mode: check what I own, or advise what to buy -->
36
  <div class="field" style="margin-bottom:var(--s-5)">
37
  <div class="segmented" id="mode-seg">
@@ -46,9 +49,9 @@
46
 
47
  <div class="field">
48
  <span class="label">Describe it in your own words <span class="optional">(fastest)</span></span>
49
- <textarea id="paste" placeholder="'Dell XPS, RTX 3050, 16GB' — or paste your dxdiag / Task Manager output…"></textarea>
50
  <button class="parse-btn" id="parse-btn">Fill the form from this</button>
51
- <div class="hint" id="parse-hint">A small model fine-tuned for this reads your text and fills the pickers below. It's trained not to invent specs (≈1% invented-field rate on our dev set); anything you didn't say stays blank.</div>
52
  </div>
53
 
54
  <div class="field">
@@ -69,7 +72,7 @@
69
  <option selected>16 GB</option><option>24 GB</option><option>32 GB</option>
70
  <option>48 GB</option><option>64 GB</option><option>96 GB</option><option>128 GB</option>
71
  </select>
72
- <div class="hint">Not sure? See “How do I find my specs?” below.</div>
73
  </div>
74
 
75
  <div class="field" id="provider-field">
@@ -110,7 +113,7 @@
110
  <div class="field" id="repo-field" style="margin-top:var(--s-3); margin-bottom:0; display:none">
111
  <span class="label">Paste the model to check</span>
112
  <input type="text" id="repo-check" placeholder="Hugging Face model id or link, e.g. lerobot/smolvla_base" />
113
- <div class="hint">FitCheck gives a full breakdown for <b>many</b> models — memory, and for text models speed and a comparison — <b>even ones not in our catalogue</b>, via a live Hugging Face lookup (needs Safetensors metadata). The lookup needs network; the deterministic advice is local.</div>
114
  </div>
115
  </div>
116
 
@@ -139,15 +142,16 @@
139
  <summary><span class="ic sum-ic" data-ic="book"></span>What do these words mean? <span class="chev ic" data-ic="chevron"></span></summary>
140
  <div class="disc-body">
141
  <dl>
142
- <div><dt>Model</dt><dd>The AI’s “brain”. Bigger means smarter, but heavier.</dd></div>
143
  <div><dt>Parameters (e.g. 7B)</dt><dd>How big the brain is. 7B is 7 billion. More means smarter and hungrier for memory.</dd></div>
144
  <div><dt>Quantisation (4-bit, 8-bit)</dt><dd>Shrinking a model so it fits. 4-bit is the popular sweet spot: much smaller, barely-noticeable quality loss.</dd></div>
145
  <div><dt>VRAM</dt><dd>The fast memory on a graphics card. The biggest factor in what runs quickly.</dd></div>
146
- <div><dt>RAM</dt><dd>Your computer’s normal memory. Models can use it too, but it is slower.</dd></div>
147
  <div><dt>GGUF</dt><dd>A single-file model format made for running locally.</dd></div>
148
  </dl>
149
  </div>
150
  </details>
 
151
  </aside>
152
 
153
  <!-- ============================== RESULTS ============================== -->
@@ -164,7 +168,7 @@
164
  <p>FitCheck gives <b>conservative</b> estimates from a transparent rules engine.
165
  GGUF sizes are <b>real Hugging Face file sizes</b> (shown rounded); other sizes and all chat-memory
166
  figures are deliberately pessimistic estimates. It would rather under-promise than over-promise. Real speed depends on your exact chip, drivers and settings.</p>
167
- <p style="margin-top:8px">Catalogue of real models last verified <b id="cat-version">—</b>. Built for the Hugging Face Build Small hackathon.</p>
168
  </footer>
169
  </main>
170
 
 
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>FitCheck - what AI can your computer run?</title>
7
  <meta name="description" content="Tell FitCheck about your computer in plain words. Get an honest answer about what AI you can actually run on it, from chatbots to object detection, and how to start." />
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="/static/style.css" />
11
  </head>
12
  <body>
 
20
  <a href="#how">How it works</a>
21
  <a href="#glossary-anchor">Jargon, explained</a>
22
  </nav>
23
+ <div class="statuschip"><span class="dot"></span><span class="lbl">engine</span> <b id="cat-version-chip">local</b></div>
24
  </header>
25
 
26
  <main class="container">
 
33
  <div class="layout">
34
  <!-- ============================== INPUTS ============================== -->
35
  <aside class="panel form-panel">
36
+ <div class="panel-head"><span class="lbl">input</span><span class="lbl">01</span></div>
37
+ <div class="panel-body">
38
  <!-- Mode: check what I own, or advise what to buy -->
39
  <div class="field" style="margin-bottom:var(--s-5)">
40
  <div class="segmented" id="mode-seg">
 
49
 
50
  <div class="field">
51
  <span class="label">Describe it in your own words <span class="optional">(fastest)</span></span>
52
+ <textarea id="paste" placeholder="'Dell XPS, RTX 3050, 16GB' - or paste your dxdiag / Task Manager output..."></textarea>
53
  <button class="parse-btn" id="parse-btn">Fill the form from this</button>
54
+ <div class="hint" id="parse-hint">A small model fine-tuned for this reads your text and fills the pickers below. It's trained not to invent specs (about 1% invented-field rate on our dev set); anything you didn't say stays blank.</div>
55
  </div>
56
 
57
  <div class="field">
 
72
  <option selected>16 GB</option><option>24 GB</option><option>32 GB</option>
73
  <option>48 GB</option><option>64 GB</option><option>96 GB</option><option>128 GB</option>
74
  </select>
75
+ <div class="hint">Not sure? See "How do I find my specs?" below.</div>
76
  </div>
77
 
78
  <div class="field" id="provider-field">
 
113
  <div class="field" id="repo-field" style="margin-top:var(--s-3); margin-bottom:0; display:none">
114
  <span class="label">Paste the model to check</span>
115
  <input type="text" id="repo-check" placeholder="Hugging Face model id or link, e.g. lerobot/smolvla_base" />
116
+ <div class="hint">FitCheck gives a full breakdown for <b>many</b> models, memory, and for text models speed and a comparison, <b>even ones not in our catalogue</b>, via a live Hugging Face lookup (needs Safetensors metadata). The lookup needs network; the deterministic advice is local.</div>
117
  </div>
118
  </div>
119
 
 
142
  <summary><span class="ic sum-ic" data-ic="book"></span>What do these words mean? <span class="chev ic" data-ic="chevron"></span></summary>
143
  <div class="disc-body">
144
  <dl>
145
+ <div><dt>Model</dt><dd>The AI's "brain". Bigger means smarter, but heavier.</dd></div>
146
  <div><dt>Parameters (e.g. 7B)</dt><dd>How big the brain is. 7B is 7 billion. More means smarter and hungrier for memory.</dd></div>
147
  <div><dt>Quantisation (4-bit, 8-bit)</dt><dd>Shrinking a model so it fits. 4-bit is the popular sweet spot: much smaller, barely-noticeable quality loss.</dd></div>
148
  <div><dt>VRAM</dt><dd>The fast memory on a graphics card. The biggest factor in what runs quickly.</dd></div>
149
+ <div><dt>RAM</dt><dd>Your computer's normal memory. Models can use it too, but it is slower.</dd></div>
150
  <div><dt>GGUF</dt><dd>A single-file model format made for running locally.</dd></div>
151
  </dl>
152
  </div>
153
  </details>
154
+ </div>
155
  </aside>
156
 
157
  <!-- ============================== RESULTS ============================== -->
 
168
  <p>FitCheck gives <b>conservative</b> estimates from a transparent rules engine.
169
  GGUF sizes are <b>real Hugging Face file sizes</b> (shown rounded); other sizes and all chat-memory
170
  figures are deliberately pessimistic estimates. It would rather under-promise than over-promise. Real speed depends on your exact chip, drivers and settings.</p>
171
+ <p style="margin-top:var(--s-2)">Catalogue of real models last verified <b id="cat-version">local</b>. Built for the Hugging Face Build Small hackathon.</p>
172
  </footer>
173
  </main>
174
 
static/style.css CHANGED
@@ -1,50 +1,58 @@
1
  /* ============================================================
2
- FitCheck — design system
3
- Dark-first. Every text/status colour clears WCAG AA on the
4
- card surface (#181B20). Tokens verified by research agent.
 
 
 
5
  ============================================================ */
6
 
7
  :root {
8
- /* Background layers */
9
- --bg-base: #0F1115;
10
- --bg-raised: #181B20;
11
- --bg-inset: #22262E;
12
- --border: #2A2F38;
13
- --border-hi: #3A4150;
14
-
15
- /* Text */
16
- --text-primary: #ECEEF2;
17
- --text-secondary: #B4BAC4;
18
- --text-muted: #868E9C;
19
-
20
- /* Accent — warm indigo */
21
- --accent: #818CF8;
22
- --accent-strong: #6366F1;
23
- --accent-soft: rgba(129,140,248,0.14);
 
 
 
24
 
25
  /* Status */
26
- --ok: #4ADE80;
27
- --warn: #FBBF24;
28
- --no: #F87171;
29
- --ok-soft: rgba(74,222,128,0.13);
30
- --warn-soft: rgba(251,191,36,0.13);
31
- --no-soft: rgba(248,113,113,0.13);
32
 
33
  /* Spacing scale (4px base) */
34
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px;
35
  --s-5:24px; --s-6:32px; --s-7:48px; --s-8:64px;
36
 
37
- /* Radius */
38
- --r-sm:8px; --r-md:14px; --r-lg:20px; --r-pill:999px;
39
 
40
- /* Shadow */
41
- --shadow-sm: 0 1px 2px rgba(0,0,0,0.30);
42
- --shadow-md: 0 4px 16px rgba(0,0,0,0.35);
43
- --shadow-lg: 0 12px 32px rgba(0,0,0,0.45);
44
- --glow: 0 0 0 1px var(--accent-soft), 0 8px 24px rgba(99,102,241,0.22);
45
 
46
- --font-head: 'Plus Jakarta Sans', system-ui, sans-serif;
47
- --font-body: 'Inter', system-ui, sans-serif;
 
 
 
48
  }
49
 
50
  *, *::before, *::after { box-sizing: border-box; }
@@ -53,92 +61,112 @@
53
  html { scroll-behavior: smooth; }
54
  body {
55
  font-family: var(--font-body);
56
- background:
57
- radial-gradient(1200px 600px at 80% -10%, rgba(99,102,241,0.10), transparent 60%),
58
- radial-gradient(1000px 500px at -10% 10%, rgba(74,222,128,0.05), transparent 55%),
59
- var(--bg-base);
 
60
  color: var(--text-primary);
61
  font-size: 16px;
62
- line-height: 1.6;
63
  -webkit-font-smoothing: antialiased;
64
  min-height: 100vh;
65
  }
 
 
 
 
 
 
66
 
67
- h1,h2,h3 { font-family: var(--font-head); line-height: 1.2; letter-spacing: -0.02em; }
68
  a { color: var(--accent); text-decoration: none; }
69
  a:hover { text-decoration: underline; }
70
  button { font-family: inherit; cursor: pointer; }
71
 
 
 
 
 
 
 
 
 
72
  .label {
73
  display: block;
74
- font-size: 13px; font-weight: 600;
75
- text-transform: uppercase; letter-spacing: 0.04em;
76
- color: var(--text-secondary);
77
  margin-bottom: var(--s-2);
78
  }
79
  .hint { font-size: 13px; color: var(--text-muted); margin-top: var(--s-1); }
80
 
81
- /* Icons (inline SVG; lucide uses stroke, brand logos use fill) */
82
  .ic { display: inline-flex; align-items: center; justify-content: center; line-height: 0; flex: none; }
83
- .ic svg { width: 1em; height: 1em; display: block; }
84
- .optional { font-weight: 500; color: var(--text-muted); font-size: 13px; }
85
 
86
  /* ----------------------------------------------------------- Topbar */
87
  .topbar {
88
  display: flex; align-items: center; gap: var(--s-3);
89
- padding: var(--s-4) var(--s-5);
90
  max-width: 1180px; margin: 0 auto;
 
91
  }
92
  .logo {
93
  display: flex; align-items: center; gap: var(--s-2);
94
- font-family: var(--font-head); font-weight: 800; font-size: 20px;
95
- letter-spacing: -0.03em;
96
  }
97
  .logo-mark {
98
- width: 32px; height: 32px; border-radius: 9px;
99
- background: linear-gradient(135deg, var(--accent), var(--accent-strong));
100
  display: grid; place-items: center;
101
- box-shadow: var(--glow);
102
- color: #fff;
103
  }
104
- .logo-mark .ic { font-size: 18px; }
105
- .logo-mark .ic svg { stroke-width: 3; }
106
- .logo .tick { color: var(--ok); }
107
  .topbar .spacer { flex: 1; }
108
- .topbar nav a { color: var(--text-secondary); font-size: 14px; font-weight: 500; margin-left: var(--s-4); }
109
- .topbar nav a:hover { color: var(--text-primary); text-decoration: none; }
 
 
 
 
110
 
111
  /* ----------------------------------------------------------- Layout */
112
  .container { max-width: 1180px; margin: 0 auto; padding: 0 var(--s-5) var(--s-8); }
113
 
114
- .hero { text-align: center; padding: var(--s-6) 0 var(--s-7); }
115
- .hero .eyebrow {
116
- display: inline-flex; align-items: center; gap: var(--s-2);
117
- font-size: 13px; font-weight: 600; color: var(--accent);
118
- background: var(--accent-soft); padding: 5px 12px; border-radius: var(--r-pill);
119
- margin-bottom: var(--s-4);
120
  }
121
- .hero h1 { font-size: clamp(28px, 4vw, 44px); font-weight: 800; }
122
- .hero h1 .accent { color: var(--accent); }
123
- .hero p { color: var(--text-secondary); font-size: clamp(15px,1.6vw,18px); max-width: 620px; margin: var(--s-4) auto 0; }
124
 
125
  .layout {
126
  display: grid;
127
- grid-template-columns: 420px 1fr;
128
  gap: var(--s-5);
129
  align-items: start;
130
  }
131
  @media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
132
 
 
133
  .panel {
134
  background: var(--bg-raised);
135
  border: 1px solid var(--border);
136
- border-radius: var(--r-lg);
137
- box-shadow: var(--shadow-md);
138
  }
139
- .form-panel { padding: var(--s-5); position: sticky; top: var(--s-4); }
 
 
 
 
 
140
  @media (min-width: 901px) {
141
- /* tall forms (long descriptions, many pickers) scroll inside the panel */
142
  .form-panel { max-height: calc(100vh - 32px); overflow-y: auto; scrollbar-width: thin; }
143
  }
144
  @media (max-width: 900px) { .form-panel { position: static; } }
@@ -146,37 +174,37 @@ button { font-family: inherit; cursor: pointer; }
146
  .step { margin-bottom: var(--s-5); }
147
  .step-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
148
  .step-num {
149
- width: 26px; height: 26px; border-radius: var(--r-pill); flex: none;
150
- background: var(--accent-soft); color: var(--accent);
151
- display: grid; place-items: center; font-weight: 700; font-size: 14px;
152
- font-family: var(--font-head);
153
  }
154
- .step-head h2 { font-size: 17px; font-weight: 700; }
155
 
156
  .field { margin-bottom: var(--s-4); }
157
 
158
- /* Segmented control */
159
  .segmented { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
160
  .segmented.cols-3 { grid-template-columns: repeat(3, 1fr); }
161
  .seg-btn {
162
  background: var(--bg-inset); border: 1px solid var(--border);
163
  color: var(--text-secondary); border-radius: var(--r-sm);
164
- padding: 10px 12px; font-size: 14px; font-weight: 500;
165
  display: flex; align-items: center; gap: var(--s-2); justify-content: center;
166
- transition: transform .15s, border-color .15s, color .15s, background .15s;
167
  }
168
  .seg-btn:hover { border-color: var(--border-hi); color: var(--text-primary); }
169
  .seg-btn.active {
170
- background: var(--accent-soft); border-color: var(--accent);
171
- color: var(--text-primary); box-shadow: var(--shadow-sm);
172
  }
173
- .seg-btn .ic { font-size: 17px; color: var(--text-muted); }
174
- .seg-btn.active .ic { color: var(--segc, var(--accent)); }
175
  .seg-btn .ic.brand { color: var(--text-secondary); }
176
- .seg-btn.active .ic.brand { color: var(--segc, var(--text-primary)); }
177
  .seg-btn.disabled { opacity: .3; pointer-events: none; }
178
 
179
- /* Hover tooltip: explains a control on hover/focus (priority row, etc.). */
180
  [data-tip] { position: relative; }
181
  [data-tip]:hover::after, [data-tip]:focus-visible::after {
182
  content: attr(data-tip);
@@ -184,456 +212,465 @@ button { font-family: inherit; cursor: pointer; }
184
  width: max-content; max-width: 240px; z-index: 30;
185
  background: var(--bg-raised); color: var(--text-secondary);
186
  border: 1px solid var(--border-hi); border-radius: var(--r-sm);
187
- padding: 8px 10px; font-size: 12px; font-weight: 500; line-height: 1.45; text-align: left;
188
  box-shadow: var(--shadow-lg); pointer-events: none; white-space: normal;
189
  }
190
 
191
- /* Each position gets its own accent when selected — coloured boundaries,
192
- line icons stay line icons. */
193
- .segmented .seg-btn:nth-child(1) { --segc: #60A5FA; }
194
- .segmented .seg-btn:nth-child(2) { --segc: #4ADE80; }
195
- .segmented .seg-btn:nth-child(3) { --segc: #F472B6; }
196
- .segmented .seg-btn:nth-child(4) { --segc: #FBBF24; }
197
- .segmented .seg-btn:nth-child(5) { --segc: #2DD4BF; }
198
- .segmented .seg-btn:nth-child(6) { --segc: #C084FC; }
199
- .seg-btn.active {
200
- border-color: var(--segc, var(--accent)) !important;
201
- background: color-mix(in srgb, var(--segc, var(--accent)) 13%, transparent);
202
- }
203
-
204
- /* Native select / input styling */
205
  select, input[type="text"], input[type="number"], textarea {
206
- width: 100%; font-family: inherit; font-size: 15px;
207
  color: var(--text-primary);
208
  background: var(--bg-inset);
209
  border: 1px solid var(--border); border-radius: var(--r-sm);
210
- padding: 11px 13px;
211
  transition: border-color .15s, box-shadow .15s;
212
  }
 
213
  select:hover, input:hover, textarea:hover { border-color: var(--border-hi); }
214
  select:focus, input:focus, textarea:focus {
215
- outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
216
  }
217
  select {
218
  appearance: none;
219
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23868E9C' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
220
  background-repeat: no-repeat; background-position: right 12px center;
221
- padding-right: 38px;
222
  }
223
  textarea { resize: vertical; min-height: 70px; line-height: 1.5; }
224
  .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
225
 
226
- /* Run vs Train toggle: a sliding two-way switch above the use-case picker. */
 
227
  .task-toggle {
228
  position: relative; display: grid; grid-template-columns: 1fr 1fr;
229
  gap: 0; background: var(--bg-inset); border: 1px solid var(--border);
230
- border-radius: var(--r-pill); padding: 3px; margin-bottom: var(--s-4);
231
  }
232
  .tt-opt {
233
  position: relative; z-index: 2; background: transparent; border: 0;
234
- color: var(--text-secondary); font-size: 13.5px; font-weight: 600;
235
- padding: 8px 12px; border-radius: var(--r-pill); cursor: pointer;
236
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
237
  transition: color .2s;
238
  }
239
  .tt-opt .ic { font-size: 16px; }
240
- .tt-opt.active { color: #fff; }
241
  .tt-slider {
242
  position: absolute; z-index: 1; top: 3px; bottom: 3px; left: 3px;
243
- width: calc(50% - 3px); border-radius: var(--r-pill);
244
- background: linear-gradient(135deg, var(--accent), #7C9CF6);
245
- box-shadow: var(--shadow-sm); transition: transform .25s cubic-bezier(.4,0,.2,1);
246
- }
247
- .task-toggle.train .tt-slider { transform: translateX(100%); }
248
- .task-toggle.train .tt-slider {
249
- background: linear-gradient(135deg, #F472B6, #FB923C);
250
  }
 
251
 
252
- /* Use-case picker */
253
  .uc-group { margin-bottom: var(--s-4); }
254
  .uc-cat {
255
- font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
256
  color: var(--text-muted); margin-bottom: var(--s-2); display: flex; align-items: center; gap: 6px;
257
  }
258
  .uc-grid { display: flex; flex-wrap: wrap; gap: var(--s-2); }
259
  .uc-pill {
260
  background: var(--bg-inset); border: 1px solid var(--border);
261
- color: var(--text-secondary); border-radius: var(--r-pill);
262
- padding: 7px 13px; font-size: 13.5px; font-weight: 500;
263
  display: inline-flex; align-items: center; gap: 6px;
264
- transition: transform .15s, border-color .15s, color .15s, background .15s;
265
  }
266
- .uc-pill:hover { border-color: var(--border-hi); color: var(--text-primary); transform: translateY(-1px); }
267
  .uc-pill.active {
268
  background: var(--accent-soft); border-color: var(--accent); color: var(--text-primary);
 
269
  }
270
  .uc-pill .pic { font-size: 15px; color: var(--text-muted); }
271
  .uc-pill:hover .pic { color: var(--text-secondary); }
272
  .uc-pill.active .pic { color: var(--accent); }
273
  .uc-cat .ic { font-size: 14px; color: var(--text-muted); }
274
 
275
- /* Same coloured-boundary treatment as the segmented rows: each position in a
276
- group gets its own accent when selected (palette cycles past 6 items). */
277
- .uc-grid .uc-pill:nth-child(6n+1) { --segc: #60A5FA; }
278
- .uc-grid .uc-pill:nth-child(6n+2) { --segc: #4ADE80; }
279
- .uc-grid .uc-pill:nth-child(6n+3) { --segc: #F472B6; }
280
- .uc-grid .uc-pill:nth-child(6n+4) { --segc: #FBBF24; }
281
- .uc-grid .uc-pill:nth-child(6n+5) { --segc: #2DD4BF; }
282
- .uc-grid .uc-pill:nth-child(6n) { --segc: #C084FC; }
283
- .uc-pill.active {
284
- border-color: var(--segc, var(--accent));
285
- background: color-mix(in srgb, var(--segc, var(--accent)) 13%, transparent);
286
- }
287
- .uc-pill.active .pic { color: var(--segc, var(--accent)); }
288
-
289
  /* Disclosure (advanced / accordions) */
290
  details.disc {
291
  border: 1px solid var(--border); border-radius: var(--r-md);
292
  background: var(--bg-inset); margin-top: var(--s-3); overflow: hidden;
293
  }
294
  details.disc > summary {
295
- list-style: none; cursor: pointer; padding: 12px 14px;
296
- font-size: 14px; font-weight: 600; color: var(--text-secondary);
297
  display: flex; align-items: center; gap: var(--s-2);
298
  }
299
  details.disc > summary::-webkit-details-marker { display: none; }
300
- details.disc > summary .chev { margin-left: auto; transition: transform .2s; color: var(--text-muted); font-size: 16px; }
301
  details.disc[open] > summary .chev { transform: rotate(180deg); }
302
  details.disc > summary .sum-ic { font-size: 16px; color: var(--text-muted); }
303
  details.disc > summary:hover { color: var(--text-primary); }
304
- .disc-body { padding: 0 14px 14px; font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
305
  .disc-body code {
306
- background: var(--bg-base); border: 1px solid var(--border); border-radius: 5px;
307
- padding: 1px 6px; font-size: 13px; color: var(--text-primary);
308
  }
309
  .disc-body dl { display: grid; gap: var(--s-3); }
310
- .disc-body dt { font-weight: 700; color: var(--text-primary); font-family: var(--font-head); }
311
  .disc-body dd { color: var(--text-muted); }
312
 
313
  .parse-btn {
314
  margin-top: var(--s-2); width: 100%;
315
- background: var(--accent-soft); border: 1px solid var(--accent);
316
- color: var(--text-primary); border-radius: var(--r-sm);
317
- padding: 9px 12px; font-size: 13.5px; font-weight: 600;
318
- transition: filter .15s, transform .15s;
319
  }
320
- .parse-btn:hover { filter: brightness(1.15); transform: translateY(-1px); }
321
 
322
- /* Primary CTA */
323
  .cta {
324
- width: 100%; border: none; border-radius: var(--r-md);
325
- background: linear-gradient(135deg, var(--accent), var(--accent-strong));
326
- color: #fff; font-weight: 700; font-size: 16px; font-family: var(--font-head);
327
- padding: 14px; margin-top: var(--s-2);
328
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
329
- transition: transform .15s, box-shadow .15s, filter .15s;
 
330
  }
331
- .cta:hover { transform: translateY(-2px); box-shadow: var(--glow); filter: brightness(1.05); }
332
- .cta:active { transform: translateY(0); }
333
- .cta .ic { font-size: 18px; }
334
 
335
  /* ----------------------------------------------------------- Results */
336
- .results-panel { padding: var(--s-5); min-height: 400px; }
337
 
338
  .empty-state { text-align: center; color: var(--text-muted); padding: var(--s-8) var(--s-4); }
339
  .empty-state .big { font-size: 46px; margin-bottom: var(--s-3); color: var(--text-muted); display: flex; justify-content: center; }
340
- .empty-state p { max-width: 360px; margin: var(--s-2) auto 0; }
 
341
 
 
342
  .verdict {
343
- border: 1px solid var(--border); border-left: 4px solid var(--status, var(--accent));
344
- border-radius: var(--r-md); background: var(--bg-raised);
345
- padding: var(--s-5); margin-bottom: var(--s-5);
 
 
 
346
  }
347
  .verdict .badge {
348
- display: inline-flex; align-items: center; gap: 7px;
349
- padding: 5px 12px; border-radius: var(--r-pill);
350
- background: var(--status-soft, var(--accent-soft)); color: var(--status, var(--accent));
351
- font: 700 13px/1 var(--font-head); text-transform: uppercase; letter-spacing: .04em;
352
- margin-bottom: var(--s-3);
353
- }
354
- .verdict .badge .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
355
- .verdict h2 { font-size: 22px; margin-bottom: var(--s-3); }
356
- .verdict p { color: var(--text-secondary); margin-bottom: var(--s-2); }
357
  .verdict .note {
358
- font-size: 13.5px; color: var(--text-muted); margin-top: var(--s-3);
359
- padding-top: var(--s-3); border-top: 1px solid var(--border);
360
  }
361
 
362
  .section-title {
363
- font-size: 15px; font-weight: 700; font-family: var(--font-head);
364
- color: var(--text-primary); margin: var(--s-5) 0 var(--s-3);
365
- display: flex; align-items: center; gap: var(--s-2);
366
  }
367
- .section-title .sub { font-size: 13px; font-weight: 500; color: var(--text-muted); font-family: var(--font-body); }
 
368
 
369
- /* Gauge */
370
  .gauge-card { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--s-4) var(--s-4) var(--s-3); }
371
- .gauge-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
372
- .gauge-top .need { font-weight: 700; color: var(--status, var(--text-primary)); font-family: var(--font-head); }
373
- /* Marker labels sit above the bar, so leave headroom. */
374
  .gauge-wrap { position: relative; padding-top: 22px; }
375
  .gauge {
376
- position: relative; height: 16px; border-radius: var(--r-pill);
377
  background: var(--bg-base); overflow: hidden; border: 1px solid var(--border);
378
  }
379
  .gauge-fill {
380
- height: 100%; width: calc(var(--pct,0) * 1%); border-radius: var(--r-pill);
381
- background: linear-gradient(90deg, var(--gcolor, var(--accent)), color-mix(in srgb, var(--gcolor, var(--accent)) 65%, #fff));
382
  transition: width .8s cubic-bezier(.22,1,.36,1), background .4s;
383
  }
384
- /* Vertical "what you have" markers, each labelled, drawn over the bar. */
385
  .gmark {
386
- position: absolute; top: 22px; height: 16px; width: 2px;
387
  transition: left .8s cubic-bezier(.22,1,.36,1);
388
  }
389
- .gmark::before { /* the line, extended a touch above and below the bar */
390
- content: ""; position: absolute; top: -4px; bottom: -4px; left: 0; width: 2px; border-radius: 2px;
391
  }
392
  .gmark-gpu::before { background: var(--text-primary); opacity: .9; }
393
  .gmark-total::before { background: var(--text-muted); }
394
  .gmark-gpu { border-left: 0; }
395
  .gmark-lab {
396
  position: absolute; bottom: calc(100% + 6px); left: 0; transform: translateX(-50%);
397
- white-space: nowrap; font-size: 11px; font-weight: 700; color: var(--text-secondary);
398
  background: var(--bg-inset); padding: 1px 5px; border-radius: var(--r-sm);
399
  }
400
  .gmark-total .gmark-lab { color: var(--text-muted); }
401
- /* Keep edge labels from clipping out of the card. */
402
  .gmark[style*="left: 0%"] .gmark-lab, .gmark-gpu .gmark-lab { transform: translateX(-20%); }
403
  .gmark-total .gmark-lab { transform: translateX(-80%); }
404
  .gauge-hint { font-style: italic; color: var(--text-muted); }
405
- .gauge-legend { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: 12px; font-size: 12.5px; color: var(--text-muted); }
406
  .gauge-legend .item { display: inline-flex; align-items: center; gap: 6px; }
407
- .gauge-legend .sw { width: 10px; height: 10px; border-radius: 3px; }
408
  .gauge-legend .marker { font-weight: 600; color: var(--text-secondary); }
409
 
410
- /* Option cards (the matrix, re-imagined) */
411
- .opt-grid { display: grid; gap: var(--s-3); }
412
- .opt {
413
- display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: var(--s-3);
414
- background: var(--bg-raised); border: 1px solid var(--border);
415
- border-left: 4px solid var(--status, var(--border)); border-radius: var(--r-md);
416
- padding: var(--s-4);
417
- transition: transform .2s, box-shadow .2s, border-color .2s;
418
  }
419
- .opt:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
 
 
 
 
 
 
 
 
 
 
420
  .opt .vdot {
421
- width: 30px; height: 30px; border-radius: 50%;
422
- background: var(--status-soft); color: var(--status);
423
- display: grid; place-items: center; font-size: 15px; font-weight: 700;
424
  }
425
- .opt .name { font-weight: 700; font-family: var(--font-head); font-size: 15px; }
 
426
  .opt .name a { color: var(--text-primary); }
427
  .opt .name a:hover { color: var(--accent); text-decoration: none; }
428
  .lic {
429
- display: inline-block; margin-left: 8px; padding: 1px 8px; vertical-align: 1px;
430
- border-radius: var(--r-pill); border: 1px solid var(--border-hi);
431
- font: 600 10.5px/1.6 var(--font-body); letter-spacing: .02em;
432
  color: var(--text-muted); text-transform: uppercase;
433
  }
434
- .lic.warn { color: var(--warn); border-color: var(--warn); opacity: .9; }
435
  .lic.gatechip { color: var(--accent); border-color: var(--accent); }
436
  .prov {
437
- margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
438
- font-size: 12px; color: var(--text-muted); line-height: 1.55;
439
- }
440
- .opt .desc { font-size: 13px; color: var(--text-muted); margin-top: 2px; }
441
- .opt .meta { text-align: right; font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
442
- .opt .meta b { color: var(--text-primary); font-family: var(--font-head); }
443
- .opt .feel { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
444
- /* Every option card is clickable: re-renders the full breakdown for that model. */
445
  .opt[data-model] { cursor: pointer; }
446
- .opt[data-model]:hover { border-color: var(--border-hi); }
 
447
  .opt-current {
448
- border-color: var(--status) !important;
449
- box-shadow: inset 0 0 0 1px var(--status), var(--shadow-lg);
 
450
  }
451
 
452
- /* Tool cards */
453
  .tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px,1fr)); gap: var(--s-3); }
454
  .tool {
455
- background: var(--bg-raised); border: 1px solid var(--border);
456
  border-radius: var(--r-md); padding: var(--s-4);
457
- transition: transform .2s, box-shadow .2s, border-color .2s;
458
  }
459
- .tool:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--border-hi); }
460
  .tool .tool-head { display: flex; align-items: center; justify-content: space-between; }
461
- .tool .tname { font-weight: 700; font-family: var(--font-head); font-size: 15px; }
462
- .tool .tag { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; padding: 3px 9px; border-radius: var(--r-pill); }
463
- .tool .tag.best { background: var(--ok-soft); color: var(--ok); }
464
- .tool .tag.mid { background: var(--accent-soft); color: var(--accent); }
465
- .tool .twhat { font-size: 13.5px; color: var(--text-secondary); margin: var(--s-2) 0; line-height: 1.5; }
466
- .tool .tinstall { font-size: 12.5px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
467
  .tool .tinstall .ic { font-size: 14px; }
468
 
469
- /* Buy advice: per-platform, click-to-expand cards */
470
  .buy-platform { margin-top: var(--s-5); }
471
  .bp-head { display: flex; align-items: center; gap: 9px; }
472
- .bp-head .ic { font-size: 19px; color: var(--text-secondary); }
473
- .bp-head .ic.brand { color: var(--text-primary); }
474
- .bp-name { font-family: var(--font-head); font-weight: 800; font-size: 16px; }
475
- .bp-blurb { font-size: 13px; color: var(--text-muted); margin: 4px 0 var(--s-3); line-height: 1.5; }
476
  .buy-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
477
  @media (max-width: 620px) { .buy-grid { grid-template-columns: 1fr; } }
478
  .buy-card {
479
- background: var(--bg-raised); border: 1px solid var(--border);
480
  border-radius: var(--r-md); padding: var(--s-3) var(--s-4);
481
  }
482
  .buy-card.empty { opacity: .7; }
483
  .bc-head { display: flex; align-items: center; justify-content: space-between; }
484
- .bc-kind { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
485
  .bc-kind.min { color: var(--warn); } .bc-kind.comfy { color: var(--ok); }
486
- .bc-label { font-family: var(--font-head); font-weight: 700; font-size: 15px; margin: 6px 0; }
487
- .bc-runs { font-size: 13.5px; color: var(--text-secondary); }
488
  .bc-runs b { color: var(--text-primary); }
489
- .bc-none { font-size: 13px; color: var(--text-muted); margin: 6px 0 0; line-height: 1.5; }
490
  .bc-goals { margin: 6px 0 0; padding: 0; list-style: none; font-size: 13px; }
491
- .bc-goals li { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; border-top: 1px solid var(--border); }
492
  .bc-goals li:first-child { border-top: 0; }
493
- .bc-goals b { color: var(--text-primary); font-weight: 600; text-align: right; }
494
  .bc-more {
495
  margin-top: var(--s-3); background: none; border: 0; cursor: pointer; padding: 0;
496
- color: var(--accent); font-size: 12.5px; font-weight: 600;
497
  display: inline-flex; align-items: center; gap: 5px;
498
  }
499
- .bc-more .chev { font-size: 14px; transition: transform .2s; }
500
  .bc-more.open .chev { transform: rotate(180deg); }
501
- .bc-detail { margin-top: var(--s-3); border-top: 1px solid var(--border); padding-top: var(--s-3); }
502
  .bc-spec {
503
- font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12px;
504
  color: var(--text-secondary); background: var(--bg-base);
505
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 7px 10px; margin-bottom: var(--s-2);
506
  }
507
- .bc-detail p { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin: 0; }
508
 
509
- /* Command block */
510
- .cmd-intro { font-size: 14px; color: var(--text-secondary); margin: var(--s-2) 0 var(--s-3); }
511
  .cmd { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
512
  @media (max-width: 620px) { .cmd { grid-template-columns: 1fr; } }
513
  .cmd-box { background: var(--bg-base); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
514
- .cmd-box .cmd-label { font-size: 12px; font-weight: 600; color: var(--text-muted); padding: 10px 13px 0; display:flex; justify-content:space-between; align-items:center; }
515
- .cmd-box pre { padding: 8px 13px 13px; overflow-x: auto; }
516
- .cmd-box code { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 13px; color: #a5f3c4; }
517
  .copy-btn {
518
  background: var(--bg-inset); border: 1px solid var(--border); color: var(--text-secondary);
519
- border-radius: 6px; padding: 3px 9px; font-size: 12px; font-weight: 600;
 
520
  }
521
  .copy-btn:hover { color: var(--text-primary); border-color: var(--border-hi); }
522
  .copy-btn.done { color: var(--ok); border-color: var(--ok); }
523
-
524
- /* "Why this speed?" roofline visualizer */
525
- .viz-disc { margin-top: var(--s-5); background: var(--bg-raised); }
526
- .viz-disc > summary .sub-inline { font-weight: 500; color: var(--text-muted); font-size: 12.5px; margin-left: 4px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  .roofline-wrap svg { width: 100%; height: auto; display: block; }
528
- .rl-grid { stroke: var(--border); stroke-width: 1; opacity: .5; }
529
- .rl-tick { fill: var(--text-muted); font: 500 10.5px var(--font-body); }
530
- .rl-axis { fill: var(--text-secondary); font: 600 11.5px var(--font-body); }
531
- .rl-dot { opacity: .6; transition: r .1s, opacity .1s; cursor: pointer; }
532
  .rl-dot:hover { opacity: 1; r: 4.5; }
533
  .rl-you { cursor: pointer; }
534
- /* Hover info bubble for chart points */
535
  .rl-tip {
536
  position: absolute; z-index: 20; pointer-events: none;
537
  background: var(--bg-raised); border: 1px solid var(--border-hi);
538
- border-radius: var(--r-sm); padding: 7px 10px; font-size: 12px; line-height: 1.5;
539
- color: var(--text-primary); box-shadow: var(--shadow-lg); max-width: 200px;
540
  }
541
  .rl-tip-sub { color: var(--text-muted); }
542
- .rl-p1 { fill: #2DD4BF; } /* ~1B runs: teal */
543
- .rl-p8 { fill: #F472B6; } /* ~8B runs: pink */
544
- .rl-p14 { fill: #93C5FD; } /* ~14B runs: light blue */
545
- .rl-roof { stroke: var(--warn); stroke-width: 1.8; stroke-dasharray: 6 5; opacity: .9; }
546
- .rl-band { stroke: var(--accent); stroke-width: 5; opacity: .35; stroke-linecap: round; }
547
- .rl-you { fill: var(--accent); stroke: #fff; stroke-width: 1.5; }
548
- .rl-you-label { fill: var(--text-primary); font: 700 12px var(--font-head); }
549
- .rl-legend { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: var(--s-2); font-size: 12px; color: var(--text-muted); }
 
550
  .rl-legend .item { display: inline-flex; align-items: center; gap: 6px; }
551
- .rl-legend .sw { width: 10px; height: 10px; border-radius: 50%; }
552
- .rl-p1-sw { background: #2DD4BF; } .rl-p8-sw { background: #F472B6; } .rl-p14-sw { background: #93C5FD; }
553
- .rl-roof-sw { background: var(--warn); border-radius: 2px; height: 3px; width: 14px; }
554
- .rl-you-sw { background: var(--accent); }
555
- .viz-caption { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin-top: var(--s-3); }
556
 
557
- /* Multi-goal overview */
558
  .goal-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--s-3); }
559
  .goal-card {
560
- background: var(--bg-raised); border: 1px solid var(--border);
561
- border-left: 4px solid var(--status, var(--border)); border-radius: var(--r-md);
562
  padding: var(--s-4); cursor: pointer;
563
- transition: transform .2s, box-shadow .2s;
564
  }
565
- .goal-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
566
  .goal-card .badge {
567
  display: inline-flex; align-items: center; gap: 6px;
568
- padding: 4px 10px; border-radius: var(--r-pill);
569
- background: var(--status-soft); color: var(--status);
570
- font: 700 11.5px/1 var(--font-head); text-transform: uppercase; letter-spacing: .04em;
571
- }
572
- .goal-card .badge .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
573
- .goal-name { font: 700 15px/1.3 var(--font-head); margin-top: var(--s-3); }
574
- .goal-pick { font-size: 13.5px; color: var(--text-secondary); margin-top: 4px; }
575
- .goal-need { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
576
- .goal-more { font-size: 12.5px; font-weight: 600; color: var(--accent); margin-top: var(--s-3); }
577
  .back-link {
578
  background: var(--bg-inset); border: 1px solid var(--border); color: var(--text-secondary);
579
- border-radius: var(--r-pill); padding: 6px 14px; font-size: 13px; font-weight: 600;
580
- margin-bottom: var(--s-4); display: inline-block;
581
  }
582
  .back-link:hover { color: var(--text-primary); border-color: var(--border-hi); }
 
583
  .goal-lines { list-style: none; padding: 0; margin: var(--s-2) 0 0; }
584
  .goal-lines li {
585
  display: flex; justify-content: space-between; gap: var(--s-3);
586
- font-size: 13px; color: var(--text-secondary); padding: 3px 0;
587
  border-bottom: 1px dashed var(--border);
588
  }
589
  .goal-lines li:last-child { border-bottom: none; }
590
- .goal-lines li b { color: var(--text-primary); font-weight: 600; text-align: right; }
591
  .goal-lines li i { color: var(--text-muted); font-style: normal; font-size: 12px; }
592
 
593
  /* Live single-model lookup card */
594
  .lookup-card {
595
- border: 1px solid var(--border); border-left: 4px solid var(--status, var(--accent));
596
- border-radius: var(--r-md); background: var(--bg-raised);
597
  padding: var(--s-4) var(--s-5); margin-bottom: var(--s-5);
598
  }
599
  .lookup-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s-2); }
600
  .live-tag {
601
- font: 600 11px/1 var(--font-body); text-transform: uppercase; letter-spacing: .05em;
602
- color: var(--accent); border: 1px dashed var(--accent); border-radius: var(--r-pill);
603
- padding: 4px 10px; opacity: .85;
604
  }
605
- .lookup-explain { color: var(--text-secondary); font-size: 14.5px; }
606
- .lookup-meta { display: flex; gap: var(--s-4); margin-top: var(--s-2); font-size: 13.5px; color: var(--text-secondary); }
607
- /* Banner above a remade breakdown when the answer came from a live lookup. */
608
  .lookup-banner {
609
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
610
  background: var(--accent-soft); border: 1px solid var(--accent);
611
  border-radius: var(--r-md); padding: var(--s-3) var(--s-4); margin-bottom: var(--s-4);
612
  }
613
- .lookup-banner p { color: var(--text-secondary); font-size: 13.5px; margin: 0; }
614
  .lookup-banner.estimate { background: var(--warn-soft); border-color: var(--warn); }
615
- /* When the goal is "Custom", the model box is the expected input, not optional. */
616
- #repo-field.required input { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
617
 
618
  /* Ask a follow-up (the model brick) */
619
  .ask-row { display: flex; gap: var(--s-2); }
620
  .ask-row input {
621
  flex: 1; background: var(--bg-inset); border: 1px solid var(--border);
622
- border-radius: var(--r-md); padding: 12px 14px; font-size: 15px;
623
  }
624
  .ask-btn {
625
- flex: none; width: 46px; border: none; border-radius: var(--r-md);
626
- background: linear-gradient(135deg, var(--accent), var(--accent-strong));
627
- color: #fff; display: grid; place-items: center;
628
- transition: transform .15s, box-shadow .15s, filter .15s;
629
- }
630
- .ask-btn:hover { transform: translateY(-2px); box-shadow: var(--glow); filter: brightness(1.05); }
631
- .ask-btn .ic { font-size: 18px; }
 
632
  .ask-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-3); }
633
  .ask-chip {
634
  background: var(--bg-inset); border: 1px solid var(--border);
635
- color: var(--text-secondary); border-radius: var(--r-pill);
636
- padding: 6px 13px; font-size: 13px; font-weight: 500;
637
  transition: border-color .15s, color .15s, background .15s;
638
  }
639
  .ask-chip:hover { border-color: var(--accent); color: var(--text-primary); background: var(--accent-soft); }
@@ -641,20 +678,20 @@ details.disc > summary:hover { color: var(--text-primary); }
641
  .ask-answer { margin-top: var(--s-4); }
642
  .ans-card {
643
  background: var(--bg-inset); border: 1px solid var(--border);
644
- border-left: 4px solid var(--accent); border-radius: var(--r-md);
645
  padding: var(--s-4) var(--s-5);
646
  }
647
- .ans-card h3 { font-size: 17px; font-weight: 700; margin-bottom: var(--s-2); }
648
- .ans-card p { color: var(--text-secondary); font-size: 14.5px; line-height: 1.6; }
649
  .ans-next {
650
  display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-3);
651
- font-size: 14px; font-weight: 600; color: var(--accent);
652
  }
653
- .ans-next .ic { font-size: 15px; flex: none; }
654
  .ans-card.ans-error { border-left-color: var(--no); }
655
  .ans-card.ans-error h3 { color: var(--no); }
656
- .ans-card.ans-error p { color: var(--text-secondary); font-size: 13.5px; word-break: break-word; }
657
- .ans-loading { display: flex; align-items: center; gap: var(--s-2); color: var(--text-muted); font-size: 14px; padding: var(--s-2) 0; flex-wrap: wrap; }
658
  .ans-cold { font-size: 12px; color: var(--text-muted); opacity: .75; }
659
  .tick-note { font-size: 12px; color: var(--text-muted); opacity: .8; }
660
  #parse-hint .spinner, .ans-loading .spinner { display: inline-block; vertical-align: -2px; margin-right: 6px; }
@@ -666,13 +703,19 @@ details.disc > summary:hover { color: var(--text-primary); }
666
  @keyframes spin { to { transform: rotate(360deg); } }
667
 
668
  /* Footer */
669
- .foot { text-align: center; color: var(--text-muted); font-size: 13px; margin-top: var(--s-7); line-height: 1.6; }
 
670
  .foot b { color: var(--text-secondary); }
671
 
672
- /* Reveal animation */
673
- .reveal { animation: reveal .5s cubic-bezier(.22,1,.36,1) both; }
674
- @keyframes reveal { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
675
 
676
  @media (prefers-reduced-motion: reduce) {
677
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
 
 
 
 
 
678
  }
 
1
  /* ============================================================
2
+ FitCheck - design system :: CRT pixel "technical instrument"
3
+ Dark phosphor-green. Hairline-bordered panels, ruled rows, a
4
+ dense option table, monospace numerics. Class/id names are kept
5
+ identical to the prior build so app.js keeps working unchanged.
6
+ Green is FitCheck's identity (NOT a vendor's); deliberately
7
+ away from NVIDIA olive #76B900.
8
  ============================================================ */
9
 
10
  :root {
11
+ /* Ink */
12
+ --text-primary: #d7f5e4;
13
+ --text-secondary: #8fb6a3;
14
+ --text-muted: #5d7d6e;
15
+
16
+ /* Background layers (matte, no gradient blooms) */
17
+ --bg-base: #070b08;
18
+ --bg-raised: #0c120d;
19
+ --bg-inset: #0a0f0b;
20
+ --border: #1c2a20;
21
+ --border-hi: #2bbf5f;
22
+ --line-2: #152019;
23
+
24
+ /* Phosphor accent (identity colour) */
25
+ --accent: #43f57f;
26
+ --accent-strong: #2bbf5f;
27
+ --accent-soft: rgba(67,245,127,0.10);
28
+ --glow: rgba(67,245,127,0.35);
29
+ --grid: rgba(67,245,127,0.045);
30
 
31
  /* Status */
32
+ --ok: #43f57f;
33
+ --warn: #ffb000;
34
+ --no: #ff5a48;
35
+ --ok-soft: rgba(67,245,127,0.10);
36
+ --warn-soft: rgba(255,176,0,0.12);
37
+ --no-soft: rgba(255,90,72,0.12);
38
 
39
  /* Spacing scale (4px base) */
40
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px;
41
  --s-5:24px; --s-6:32px; --s-7:48px; --s-8:64px;
42
 
43
+ /* Squarer radii - an instrument is not a soft card */
44
+ --r-sm:3px; --r-md:4px; --r-lg:5px; --r-pill:3px;
45
 
46
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.40);
47
+ --shadow-md: 0 2px 10px rgba(0,0,0,0.45);
48
+ --shadow-lg: 0 4px 18px rgba(0,0,0,0.55);
49
+ --glowshadow: 0 0 14px var(--glow);
 
50
 
51
+ --font-pixel: 'Press Start 2P', monospace; /* wordmark, verdict word, labels, CTA */
52
+ --font-crt: 'VT323', ui-monospace, monospace; /* numerics, data table, inputs, hero */
53
+ --font-head: 'VT323', ui-monospace, monospace; /* headings live in the CRT face */
54
+ --font-body: 'Inter', system-ui, sans-serif; /* body sentences only */
55
+ --font-mono: 'VT323', ui-monospace, monospace;
56
  }
57
 
58
  *, *::before, *::after { box-sizing: border-box; }
 
61
  html { scroll-behavior: smooth; }
62
  body {
63
  font-family: var(--font-body);
64
+ background: var(--bg-base);
65
+ background-image:
66
+ linear-gradient(var(--grid) 1px, transparent 1px),
67
+ linear-gradient(90deg, var(--grid) 1px, transparent 1px);
68
+ background-size: 32px 32px;
69
  color: var(--text-primary);
70
  font-size: 16px;
71
+ line-height: 1.55;
72
  -webkit-font-smoothing: antialiased;
73
  min-height: 100vh;
74
  }
75
+ /* Subtle CRT scanline overlay - low opacity, must not hurt readability. */
76
+ body::after {
77
+ content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 9;
78
+ background: repeating-linear-gradient(0deg, rgba(0,0,0,0.16) 0 1px, transparent 1px 3px);
79
+ opacity: .30; mix-blend-mode: multiply;
80
+ }
81
 
82
+ h1,h2,h3 { font-family: var(--font-head); line-height: 1.1; letter-spacing: .01em; font-weight: 400; }
83
  a { color: var(--accent); text-decoration: none; }
84
  a:hover { text-decoration: underline; }
85
  button { font-family: inherit; cursor: pointer; }
86
 
87
+ /* Numeric face + small uppercase labels (the instrument grammar) */
88
+ .num, .crt { font-family: var(--font-crt); }
89
+ .lbl {
90
+ font-family: var(--font-crt); font-size: 15px; letter-spacing: .12em;
91
+ text-transform: uppercase; color: var(--text-muted);
92
+ }
93
+ .pixel { font-family: var(--font-pixel); }
94
+
95
  .label {
96
  display: block;
97
+ font-family: var(--font-crt); font-size: 16px; font-weight: 400;
98
+ text-transform: uppercase; letter-spacing: 0.10em;
99
+ color: var(--text-muted);
100
  margin-bottom: var(--s-2);
101
  }
102
  .hint { font-size: 13px; color: var(--text-muted); margin-top: var(--s-1); }
103
 
104
+ /* Icons (pixel SVG; currentColor tints) */
105
  .ic { display: inline-flex; align-items: center; justify-content: center; line-height: 0; flex: none; }
106
+ .ic svg { width: 1em; height: 1em; display: block; shape-rendering: crispEdges; }
107
+ .optional { font-weight: 400; color: var(--text-muted); font-size: 13px; font-family: var(--font-crt); }
108
 
109
  /* ----------------------------------------------------------- Topbar */
110
  .topbar {
111
  display: flex; align-items: center; gap: var(--s-3);
112
+ padding: 0 var(--s-5); height: 56px;
113
  max-width: 1180px; margin: 0 auto;
114
+ border-bottom: 1px solid var(--border);
115
  }
116
  .logo {
117
  display: flex; align-items: center; gap: var(--s-2);
118
+ font-family: var(--font-pixel); font-weight: 400; font-size: 14px;
119
+ letter-spacing: .02em;
120
  }
121
  .logo-mark {
122
+ width: 26px; height: 26px; border-radius: 0;
123
+ background: transparent; border: 2px solid var(--accent);
124
  display: grid; place-items: center;
125
+ box-shadow: var(--glowshadow);
126
+ color: var(--accent);
127
  }
128
+ .logo-mark .ic { font-size: 14px; }
129
+ .logo .tick { color: var(--accent); text-shadow: 0 0 8px var(--glow); }
 
130
  .topbar .spacer { flex: 1; }
131
+ .topbar nav a { color: var(--text-secondary); font-size: 17px; font-weight: 400; font-family: var(--font-crt); margin-left: var(--s-4); }
132
+ .topbar nav a:hover { color: var(--accent); text-decoration: none; }
133
+ .statuschip { display: flex; align-items: center; gap: 7px; font-family: var(--font-crt); font-size: 16px; color: var(--text-muted); margin-left: var(--s-4); }
134
+ .statuschip b { color: var(--text-secondary); font-weight: 400; }
135
+ .statuschip .dot { width: 8px; height: 8px; background: var(--ok); box-shadow: var(--glowshadow); }
136
+ .statuschip .lbl { font-size: 13px; }
137
 
138
  /* ----------------------------------------------------------- Layout */
139
  .container { max-width: 1180px; margin: 0 auto; padding: 0 var(--s-5) var(--s-8); }
140
 
141
+ .hero {
142
+ text-align: left; padding: var(--s-6) 0 var(--s-5);
143
+ border-bottom: 1px solid var(--border); margin-bottom: var(--s-6);
 
 
 
144
  }
145
+ .hero h1 { font-family: var(--font-crt); font-size: clamp(34px, 5vw, 48px); font-weight: 400; line-height: .98; }
146
+ .hero h1 .accent { color: var(--accent); text-shadow: 0 0 12px var(--glow); }
147
+ .hero p { color: var(--text-secondary); font-family: var(--font-body); font-size: 14px; max-width: 60ch; margin: var(--s-3) 0 0; }
148
 
149
  .layout {
150
  display: grid;
151
+ grid-template-columns: 340px 1fr;
152
  gap: var(--s-5);
153
  align-items: start;
154
  }
155
  @media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
156
 
157
+ /* Hairline-bordered panels - the core motif (no rounded soft cards, no lift) */
158
  .panel {
159
  background: var(--bg-raised);
160
  border: 1px solid var(--border);
161
+ border-radius: var(--r-md);
 
162
  }
163
+ .panel-head {
164
+ display: flex; align-items: center; justify-content: space-between;
165
+ padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--line-2);
166
+ }
167
+ .panel-body { padding: var(--s-4); }
168
+ .form-panel { position: sticky; top: var(--s-4); }
169
  @media (min-width: 901px) {
 
170
  .form-panel { max-height: calc(100vh - 32px); overflow-y: auto; scrollbar-width: thin; }
171
  }
172
  @media (max-width: 900px) { .form-panel { position: static; } }
 
174
  .step { margin-bottom: var(--s-5); }
175
  .step-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
176
  .step-num {
177
+ width: 24px; height: 24px; border-radius: 0; flex: none;
178
+ background: transparent; color: var(--accent); border: 1px solid var(--border-hi);
179
+ display: grid; place-items: center; font-weight: 400; font-size: 14px;
180
+ font-family: var(--font-crt);
181
  }
182
+ .step-head h2 { font-family: var(--font-crt); font-size: 22px; font-weight: 400; letter-spacing: .02em; }
183
 
184
  .field { margin-bottom: var(--s-4); }
185
 
186
+ /* Segmented control - hairline cells, selected state = accent fill, NO lift */
187
  .segmented { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
188
  .segmented.cols-3 { grid-template-columns: repeat(3, 1fr); }
189
  .seg-btn {
190
  background: var(--bg-inset); border: 1px solid var(--border);
191
  color: var(--text-secondary); border-radius: var(--r-sm);
192
+ padding: 10px 8px; font-family: var(--font-crt); font-size: 16px; font-weight: 400; letter-spacing: .03em;
193
  display: flex; align-items: center; gap: var(--s-2); justify-content: center;
194
+ transition: border-color .15s, color .15s, background .15s;
195
  }
196
  .seg-btn:hover { border-color: var(--border-hi); color: var(--text-primary); }
197
  .seg-btn.active {
198
+ background: var(--accent); border-color: var(--accent);
199
+ color: #04130a; box-shadow: var(--glowshadow);
200
  }
201
+ .seg-btn .ic { font-size: 16px; color: var(--text-muted); }
202
+ .seg-btn.active .ic { color: #04130a; }
203
  .seg-btn .ic.brand { color: var(--text-secondary); }
204
+ .seg-btn.active .ic.brand { color: #04130a; }
205
  .seg-btn.disabled { opacity: .3; pointer-events: none; }
206
 
207
+ /* Hover tooltip */
208
  [data-tip] { position: relative; }
209
  [data-tip]:hover::after, [data-tip]:focus-visible::after {
210
  content: attr(data-tip);
 
212
  width: max-content; max-width: 240px; z-index: 30;
213
  background: var(--bg-raised); color: var(--text-secondary);
214
  border: 1px solid var(--border-hi); border-radius: var(--r-sm);
215
+ padding: 8px 10px; font-size: 12px; font-family: var(--font-body); font-weight: 400; line-height: 1.45; text-align: left;
216
  box-shadow: var(--shadow-lg); pointer-events: none; white-space: normal;
217
  }
218
 
219
+ /* Native select / input - CRT terminal face */
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  select, input[type="text"], input[type="number"], textarea {
221
+ width: 100%; font-family: var(--font-crt); font-size: 18px;
222
  color: var(--text-primary);
223
  background: var(--bg-inset);
224
  border: 1px solid var(--border); border-radius: var(--r-sm);
225
+ padding: 8px 11px;
226
  transition: border-color .15s, box-shadow .15s;
227
  }
228
+ textarea { font-family: var(--font-body); font-size: 14px; }
229
  select:hover, input:hover, textarea:hover { border-color: var(--border-hi); }
230
  select:focus, input:focus, textarea:focus {
231
+ outline: none; border-color: var(--accent-strong); box-shadow: 0 0 0 1px var(--accent-strong);
232
  }
233
  select {
234
  appearance: none;
235
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8' fill='%235d7d6e' shape-rendering='crispEdges'%3E%3Crect x='0' y='2' width='1' height='1'/%3E%3Crect x='1' y='3' width='1' height='1'/%3E%3Crect x='2' y='4' width='1' height='1'/%3E%3Crect x='3' y='5' width='1' height='1'/%3E%3Crect x='4' y='4' width='1' height='1'/%3E%3Crect x='5' y='3' width='1' height='1'/%3E%3Crect x='6' y='2' width='1' height='1'/%3E%3C/svg%3E");
236
  background-repeat: no-repeat; background-position: right 12px center;
237
+ padding-right: 36px;
238
  }
239
  textarea { resize: vertical; min-height: 70px; line-height: 1.5; }
240
  .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
241
 
242
+ /* Run vs Train toggle - two hairline cells, selected = accent, no slide gradient.
243
+ The .tt-slider is kept (app.js toggles .train) but rendered as a flat marker. */
244
  .task-toggle {
245
  position: relative; display: grid; grid-template-columns: 1fr 1fr;
246
  gap: 0; background: var(--bg-inset); border: 1px solid var(--border);
247
+ border-radius: var(--r-sm); padding: 3px; margin-bottom: var(--s-4);
248
  }
249
  .tt-opt {
250
  position: relative; z-index: 2; background: transparent; border: 0;
251
+ color: var(--text-secondary); font-family: var(--font-crt); font-size: 16px; font-weight: 400; letter-spacing: .03em;
252
+ padding: 8px 12px; border-radius: var(--r-sm); cursor: pointer;
253
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
254
  transition: color .2s;
255
  }
256
  .tt-opt .ic { font-size: 16px; }
257
+ .tt-opt.active { color: #04130a; }
258
  .tt-slider {
259
  position: absolute; z-index: 1; top: 3px; bottom: 3px; left: 3px;
260
+ width: calc(50% - 3px); border-radius: var(--r-sm);
261
+ background: var(--accent); box-shadow: var(--glowshadow);
262
+ transition: transform .2s cubic-bezier(.4,0,.2,1);
 
 
 
 
263
  }
264
+ .task-toggle.train .tt-slider { transform: translateX(100%); background: var(--warn); box-shadow: 0 0 14px rgba(255,176,0,.35); }
265
 
266
+ /* Use-case picker - flat hairline pills, selected = accent ring, NO rainbow, NO lift */
267
  .uc-group { margin-bottom: var(--s-4); }
268
  .uc-cat {
269
+ font-family: var(--font-crt); font-size: 14px; font-weight: 400; letter-spacing: 0.10em; text-transform: uppercase;
270
  color: var(--text-muted); margin-bottom: var(--s-2); display: flex; align-items: center; gap: 6px;
271
  }
272
  .uc-grid { display: flex; flex-wrap: wrap; gap: var(--s-2); }
273
  .uc-pill {
274
  background: var(--bg-inset); border: 1px solid var(--border);
275
+ color: var(--text-secondary); border-radius: var(--r-sm);
276
+ padding: 6px 12px; font-family: var(--font-crt); font-size: 16px; font-weight: 400;
277
  display: inline-flex; align-items: center; gap: 6px;
278
+ transition: border-color .15s, color .15s, background .15s;
279
  }
280
+ .uc-pill:hover { border-color: var(--border-hi); color: var(--text-primary); }
281
  .uc-pill.active {
282
  background: var(--accent-soft); border-color: var(--accent); color: var(--text-primary);
283
+ box-shadow: inset 0 0 0 1px var(--accent);
284
  }
285
  .uc-pill .pic { font-size: 15px; color: var(--text-muted); }
286
  .uc-pill:hover .pic { color: var(--text-secondary); }
287
  .uc-pill.active .pic { color: var(--accent); }
288
  .uc-cat .ic { font-size: 14px; color: var(--text-muted); }
289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  /* Disclosure (advanced / accordions) */
291
  details.disc {
292
  border: 1px solid var(--border); border-radius: var(--r-md);
293
  background: var(--bg-inset); margin-top: var(--s-3); overflow: hidden;
294
  }
295
  details.disc > summary {
296
+ list-style: none; cursor: pointer; padding: 11px 14px;
297
+ font-family: var(--font-crt); font-size: 17px; font-weight: 400; color: var(--text-secondary);
298
  display: flex; align-items: center; gap: var(--s-2);
299
  }
300
  details.disc > summary::-webkit-details-marker { display: none; }
301
+ details.disc > summary .chev { margin-left: auto; transition: transform .2s; color: var(--text-muted); font-size: 14px; }
302
  details.disc[open] > summary .chev { transform: rotate(180deg); }
303
  details.disc > summary .sum-ic { font-size: 16px; color: var(--text-muted); }
304
  details.disc > summary:hover { color: var(--text-primary); }
305
+ .disc-body { padding: 0 14px 14px; font-size: 14px; font-family: var(--font-body); color: var(--text-secondary); line-height: 1.6; }
306
  .disc-body code {
307
+ background: var(--bg-base); border: 1px solid var(--border); border-radius: 3px;
308
+ padding: 1px 6px; font-family: var(--font-crt); font-size: 15px; color: var(--accent);
309
  }
310
  .disc-body dl { display: grid; gap: var(--s-3); }
311
+ .disc-body dt { font-weight: 400; color: var(--text-primary); font-family: var(--font-crt); font-size: 17px; letter-spacing: .02em; }
312
  .disc-body dd { color: var(--text-muted); }
313
 
314
  .parse-btn {
315
  margin-top: var(--s-2); width: 100%;
316
+ background: var(--bg-inset); border: 1px solid var(--accent);
317
+ color: var(--accent); border-radius: var(--r-sm);
318
+ padding: 9px 12px; font-family: var(--font-crt); font-size: 16px; font-weight: 400; letter-spacing: .03em;
319
+ transition: background .15s, color .15s;
320
  }
321
+ .parse-btn:hover { background: var(--accent-soft); color: var(--text-primary); }
322
 
323
+ /* Primary CTA - the pixel display face, flat accent, no lift */
324
  .cta {
325
+ width: 100%; border: 1px solid var(--accent); border-radius: var(--r-md);
326
+ background: var(--accent);
327
+ color: #04130a; font-weight: 400; font-size: 11px; font-family: var(--font-pixel); letter-spacing: .02em;
328
+ padding: 13px; margin-top: var(--s-2);
329
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
330
+ box-shadow: var(--glowshadow);
331
+ transition: filter .15s, box-shadow .15s;
332
  }
333
+ .cta:hover { filter: brightness(1.08); box-shadow: 0 0 20px var(--glow); }
334
+ .cta:active { filter: brightness(.95); }
335
+ .cta .ic { font-size: 14px; }
336
 
337
  /* ----------------------------------------------------------- Results */
338
+ .results-panel { padding: var(--s-4); min-height: 400px; }
339
 
340
  .empty-state { text-align: center; color: var(--text-muted); padding: var(--s-8) var(--s-4); }
341
  .empty-state .big { font-size: 46px; margin-bottom: var(--s-3); color: var(--text-muted); display: flex; justify-content: center; }
342
+ .empty-state h2 { font-family: var(--font-crt); font-size: 24px; color: var(--text-secondary); }
343
+ .empty-state p { max-width: 360px; margin: var(--s-2) auto 0; font-family: var(--font-body); }
344
 
345
+ /* Verdict readout - instrument header, accent rail, status word in pixel face */
346
  .verdict {
347
+ border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-raised);
348
+ padding: var(--s-5); margin-bottom: var(--s-5); position: relative; overflow: hidden;
349
+ }
350
+ .verdict::before {
351
+ content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
352
+ background: var(--status, var(--accent)); box-shadow: 0 0 12px var(--glow);
353
  }
354
  .verdict .badge {
355
+ display: inline-flex; align-items: center; gap: 8px;
356
+ padding: 0; background: transparent; color: var(--status, var(--accent));
357
+ font-family: var(--font-pixel); font-size: 12px; line-height: 1.2; text-transform: uppercase; letter-spacing: .02em;
358
+ margin-bottom: var(--s-4); text-shadow: 0 0 10px var(--glow);
359
+ }
360
+ .verdict .badge .dot { width: 8px; height: 8px; border-radius: 0; background: currentColor; box-shadow: 0 0 8px currentColor; }
361
+ .verdict h2 { font-family: var(--font-body); font-weight: 600; font-size: 20px; line-height: 1.25; letter-spacing: -.01em; margin-bottom: var(--s-3); }
362
+ .verdict p { color: var(--text-secondary); font-family: var(--font-body); margin-bottom: var(--s-2); font-size: 14px; }
 
363
  .verdict .note {
364
+ font-size: 13px; color: var(--text-muted); margin-top: var(--s-3); font-family: var(--font-body);
365
+ padding-top: var(--s-3); border-top: 1px dashed var(--border);
366
  }
367
 
368
  .section-title {
369
+ font-family: var(--font-pixel); font-size: 10px; letter-spacing: .02em;
370
+ color: var(--accent); text-shadow: 0 0 8px var(--glow); margin: var(--s-6) 0 var(--s-3);
371
+ display: flex; align-items: center; gap: var(--s-3);
372
  }
373
+ .section-title::after { content: ""; height: 1px; flex: 1; background: var(--border); }
374
+ .section-title .sub { font-family: var(--font-crt); font-size: 14px; font-weight: 400; color: var(--text-muted); letter-spacing: 0; text-transform: none; text-shadow: none; }
375
 
376
+ /* Gauge - mono numerics, hairline track */
377
  .gauge-card { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--s-4) var(--s-4) var(--s-3); }
378
+ .gauge-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--s-3); }
379
+ .gauge-top .need { font-family: var(--font-crt); font-size: 20px; color: var(--status, var(--accent)); text-shadow: 0 0 8px var(--glow); }
 
380
  .gauge-wrap { position: relative; padding-top: 22px; }
381
  .gauge {
382
+ position: relative; height: 14px; border-radius: 0;
383
  background: var(--bg-base); overflow: hidden; border: 1px solid var(--border);
384
  }
385
  .gauge-fill {
386
+ height: 100%; width: calc(var(--pct,0) * 1%); border-radius: 0;
387
+ background: var(--gcolor, var(--accent)); box-shadow: 0 0 10px var(--glow);
388
  transition: width .8s cubic-bezier(.22,1,.36,1), background .4s;
389
  }
 
390
  .gmark {
391
+ position: absolute; top: 22px; height: 14px; width: 2px;
392
  transition: left .8s cubic-bezier(.22,1,.36,1);
393
  }
394
+ .gmark::before {
395
+ content: ""; position: absolute; top: -4px; bottom: -4px; left: 0; width: 2px; border-radius: 0;
396
  }
397
  .gmark-gpu::before { background: var(--text-primary); opacity: .9; }
398
  .gmark-total::before { background: var(--text-muted); }
399
  .gmark-gpu { border-left: 0; }
400
  .gmark-lab {
401
  position: absolute; bottom: calc(100% + 6px); left: 0; transform: translateX(-50%);
402
+ white-space: nowrap; font-family: var(--font-crt); font-size: 14px; font-weight: 400; color: var(--text-secondary);
403
  background: var(--bg-inset); padding: 1px 5px; border-radius: var(--r-sm);
404
  }
405
  .gmark-total .gmark-lab { color: var(--text-muted); }
 
406
  .gmark[style*="left: 0%"] .gmark-lab, .gmark-gpu .gmark-lab { transform: translateX(-20%); }
407
  .gmark-total .gmark-lab { transform: translateX(-80%); }
408
  .gauge-hint { font-style: italic; color: var(--text-muted); }
409
+ .gauge-legend { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: var(--s-3); font-size: 13px; font-family: var(--font-body); color: var(--text-muted); }
410
  .gauge-legend .item { display: inline-flex; align-items: center; gap: 6px; }
411
+ .gauge-legend .sw { width: 10px; height: 10px; border-radius: 0; }
412
  .gauge-legend .marker { font-weight: 600; color: var(--text-secondary); }
413
 
414
+ /* ---- Option breakdown rendered as a DENSE TABLE ----
415
+ app.js emits .opt-grid containing .opt rows (grid: 40px 1fr auto).
416
+ We re-cast each .opt as a ruled instrument row: status dot, model name +
417
+ desc, monospace right-aligned memory/setting. The honest pick (.opt-current)
418
+ gets an inset accent ring, not a floating card. No hover-lift. */
419
+ .opt-grid {
420
+ display: grid; gap: 0;
421
+ border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden;
422
  }
423
+ .opt {
424
+ display: grid; grid-template-columns: 28px 1fr auto; align-items: center; gap: var(--s-3);
425
+ background: var(--bg-raised); border: 0;
426
+ border-bottom: 1px solid var(--line-2);
427
+ border-left: 2px solid var(--status, transparent);
428
+ border-radius: 0;
429
+ padding: 11px var(--s-4);
430
+ transition: background .15s, box-shadow .15s, border-color .15s;
431
+ }
432
+ .opt:last-child { border-bottom: 0; }
433
+ .opt:hover { background: #101a13; }
434
  .opt .vdot {
435
+ width: 18px; height: 18px; border-radius: 0;
436
+ background: transparent; color: var(--status);
437
+ display: grid; place-items: center; font-size: 14px; font-weight: 400;
438
  }
439
+ .opt .vdot .ic svg { width: 14px; height: 14px; }
440
+ .opt .name { font-family: var(--font-crt); font-weight: 400; font-size: 18px; letter-spacing: .01em; }
441
  .opt .name a { color: var(--text-primary); }
442
  .opt .name a:hover { color: var(--accent); text-decoration: none; }
443
  .lic {
444
+ display: inline-block; margin-left: 8px; padding: 0 7px; vertical-align: 2px;
445
+ border-radius: var(--r-sm); border: 1px solid var(--border-hi);
446
+ font-family: var(--font-crt); font-size: 13px; font-weight: 400; letter-spacing: .04em;
447
  color: var(--text-muted); text-transform: uppercase;
448
  }
449
+ .lic.warn { color: var(--warn); border-color: var(--warn); }
450
  .lic.gatechip { color: var(--accent); border-color: var(--accent); }
451
  .prov {
452
+ margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border);
453
+ font-size: 12px; color: var(--text-muted); line-height: 1.55; font-family: var(--font-body);
454
+ }
455
+ .opt .desc { font-size: 12px; color: var(--text-muted); margin-top: 1px; font-family: var(--font-body); }
456
+ .opt .meta { text-align: right; font-family: var(--font-crt); font-size: 16px; color: var(--text-secondary); white-space: nowrap; }
457
+ .opt .meta b { color: var(--text-primary); font-family: var(--font-crt); font-weight: 400; font-size: 18px; }
458
+ .opt .feel { font-family: var(--font-crt); font-size: 14px; color: var(--text-muted); margin-top: 1px; }
 
459
  .opt[data-model] { cursor: pointer; }
460
+ .opt[data-model]:hover { background: #101a13; }
461
+ /* honest pick / currently-focused model: inset accent ring, not a lifted card */
462
  .opt-current {
463
+ background: rgba(67,245,127,.07);
464
+ box-shadow: inset 2px 0 0 var(--accent), inset 0 0 0 1px var(--accent);
465
+ border-left-color: var(--accent);
466
  }
467
 
468
+ /* Tool cards - hairline panels, border state on hover */
469
  .tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px,1fr)); gap: var(--s-3); }
470
  .tool {
471
+ background: var(--bg-inset); border: 1px solid var(--border);
472
  border-radius: var(--r-md); padding: var(--s-4);
473
+ transition: border-color .15s;
474
  }
475
+ .tool:hover { border-color: var(--border-hi); }
476
  .tool .tool-head { display: flex; align-items: center; justify-content: space-between; }
477
+ .tool .tname { font-family: var(--font-crt); font-weight: 400; font-size: 18px; }
478
+ .tool .tag { font-family: var(--font-crt); font-size: 13px; font-weight: 400; text-transform: uppercase; letter-spacing: .05em; padding: 2px 8px; border-radius: var(--r-sm); border: 1px solid; }
479
+ .tool .tag.best { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }
480
+ .tool .tag.mid { background: transparent; color: var(--text-muted); border-color: var(--border-hi); }
481
+ .tool .twhat { font-size: 13px; color: var(--text-secondary); margin: var(--s-2) 0; line-height: 1.5; font-family: var(--font-body); }
482
+ .tool .tinstall { font-family: var(--font-crt); font-size: 15px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
483
  .tool .tinstall .ic { font-size: 14px; }
484
 
485
+ /* Buy advice */
486
  .buy-platform { margin-top: var(--s-5); }
487
  .bp-head { display: flex; align-items: center; gap: 9px; }
488
+ .bp-head .ic { font-size: 18px; color: var(--text-secondary); }
489
+ .bp-head .ic.brand { color: var(--text-secondary); }
490
+ .bp-name { font-family: var(--font-crt); font-weight: 400; font-size: 20px; }
491
+ .bp-blurb { font-size: 13px; color: var(--text-muted); margin: 4px 0 var(--s-3); line-height: 1.5; font-family: var(--font-body); }
492
  .buy-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
493
  @media (max-width: 620px) { .buy-grid { grid-template-columns: 1fr; } }
494
  .buy-card {
495
+ background: var(--bg-inset); border: 1px solid var(--border);
496
  border-radius: var(--r-md); padding: var(--s-3) var(--s-4);
497
  }
498
  .buy-card.empty { opacity: .7; }
499
  .bc-head { display: flex; align-items: center; justify-content: space-between; }
500
+ .bc-kind { font-family: var(--font-crt); font-size: 14px; font-weight: 400; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); }
501
  .bc-kind.min { color: var(--warn); } .bc-kind.comfy { color: var(--ok); }
502
+ .bc-label { font-family: var(--font-crt); font-weight: 400; font-size: 18px; margin: 6px 0; }
503
+ .bc-runs { font-size: 13px; color: var(--text-secondary); font-family: var(--font-body); }
504
  .bc-runs b { color: var(--text-primary); }
505
+ .bc-none { font-size: 13px; color: var(--text-muted); margin: 6px 0 0; line-height: 1.5; font-family: var(--font-body); }
506
  .bc-goals { margin: 6px 0 0; padding: 0; list-style: none; font-size: 13px; }
507
+ .bc-goals li { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; border-top: 1px dashed var(--border); font-family: var(--font-body); }
508
  .bc-goals li:first-child { border-top: 0; }
509
+ .bc-goals b { color: var(--text-primary); font-weight: 600; text-align: right; font-family: var(--font-crt); font-size: 15px; }
510
  .bc-more {
511
  margin-top: var(--s-3); background: none; border: 0; cursor: pointer; padding: 0;
512
+ color: var(--accent); font-family: var(--font-crt); font-size: 15px; font-weight: 400;
513
  display: inline-flex; align-items: center; gap: 5px;
514
  }
515
+ .bc-more .chev { font-size: 13px; transition: transform .2s; }
516
  .bc-more.open .chev { transform: rotate(180deg); }
517
+ .bc-detail { margin-top: var(--s-3); border-top: 1px dashed var(--border); padding-top: var(--s-3); }
518
  .bc-spec {
519
+ font-family: var(--font-crt); font-size: 15px;
520
  color: var(--text-secondary); background: var(--bg-base);
521
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 7px 10px; margin-bottom: var(--s-2);
522
  }
523
+ .bc-detail p { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin: 0; font-family: var(--font-body); }
524
 
525
+ /* Command block - terminal */
526
+ .cmd-intro { font-size: 14px; color: var(--text-secondary); margin: var(--s-2) 0 var(--s-3); font-family: var(--font-body); }
527
  .cmd { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
528
  @media (max-width: 620px) { .cmd { grid-template-columns: 1fr; } }
529
  .cmd-box { background: var(--bg-base); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
530
+ .cmd-box .cmd-label { font-family: var(--font-crt); font-size: 14px; font-weight: 400; color: var(--text-muted); padding: 8px 13px 0; display:flex; justify-content:space-between; align-items:center; text-transform: uppercase; letter-spacing: .06em; }
531
+ .cmd-box pre { padding: 6px 13px 12px; overflow-x: auto; }
532
+ .cmd-box code { font-family: var(--font-crt); font-size: 17px; color: var(--accent); }
533
  .copy-btn {
534
  background: var(--bg-inset); border: 1px solid var(--border); color: var(--text-secondary);
535
+ border-radius: var(--r-sm); padding: 3px 9px; font-family: var(--font-crt); font-size: 14px; font-weight: 400;
536
+ display: inline-flex; align-items: center; gap: 5px;
537
  }
538
  .copy-btn:hover { color: var(--text-primary); border-color: var(--border-hi); }
539
  .copy-btn.done { color: var(--ok); border-color: var(--ok); }
540
+ .copy-btn .ic { font-size: 13px; }
541
+
542
+ /* "Why this speed?" roofline - PROMOTED to a prominent centerpiece panel.
543
+ app.js still emits <details class="disc viz-disc" open>; we render it as a
544
+ flat hairline panel with a graph surface, not a tucked-away disclosure. */
545
+ .viz-disc {
546
+ margin-top: var(--s-6); background: var(--bg-raised);
547
+ border: 1px solid var(--border-hi); box-shadow: 0 0 0 1px var(--accent-soft);
548
+ }
549
+ .viz-disc > summary {
550
+ font-family: var(--font-pixel); font-size: 10px; color: var(--accent); text-shadow: 0 0 8px var(--glow);
551
+ padding: var(--s-4) var(--s-4) var(--s-3); border-bottom: 1px solid var(--line-2); cursor: default;
552
+ text-transform: uppercase; letter-spacing: .02em;
553
+ }
554
+ .viz-disc > summary .chev { display: none; } /* centerpiece: not a collapse affordance */
555
+ .viz-disc > summary .sub-inline { font-family: var(--font-crt); font-weight: 400; color: var(--text-muted); font-size: 14px; margin-left: 8px; letter-spacing: 0; text-transform: none; text-shadow: none; }
556
+ .viz-disc > summary .sum-ic { font-size: 14px; }
557
+ .viz-disc > .disc-body { padding: var(--s-4); }
558
+ .roofline-wrap {
559
+ border: 1px solid var(--line-2); background: var(--bg-inset);
560
+ background-image: linear-gradient(var(--grid) 1px, transparent 1px), linear-gradient(90deg, var(--grid) 1px, transparent 1px);
561
+ background-size: 40px 32px; padding: var(--s-2);
562
+ }
563
  .roofline-wrap svg { width: 100%; height: auto; display: block; }
564
+ .rl-grid { stroke: var(--line-2); stroke-width: 1; opacity: .7; }
565
+ .rl-tick { fill: var(--text-muted); font: 400 13px var(--font-crt); }
566
+ .rl-axis { fill: var(--text-secondary); font: 400 14px var(--font-crt); }
567
+ .rl-dot { opacity: .55; transition: r .1s, opacity .1s; cursor: pointer; }
568
  .rl-dot:hover { opacity: 1; r: 4.5; }
569
  .rl-you { cursor: pointer; }
 
570
  .rl-tip {
571
  position: absolute; z-index: 20; pointer-events: none;
572
  background: var(--bg-raised); border: 1px solid var(--border-hi);
573
+ border-radius: var(--r-sm); padding: 7px 10px; font-family: var(--font-crt); font-size: 15px; line-height: 1.4;
574
+ color: var(--text-primary); box-shadow: var(--shadow-lg); max-width: 220px;
575
  }
576
  .rl-tip-sub { color: var(--text-muted); }
577
+ /* roofline series in the SAME neutral tone family (greens/inks); accent = you */
578
+ .rl-p1 { fill: #34503f; }
579
+ .rl-p8 { fill: #4d6e58; }
580
+ .rl-p14 { fill: #6f9580; }
581
+ .rl-roof { stroke: var(--warn); stroke-width: 1.8; stroke-dasharray: 4 5; opacity: .9; }
582
+ .rl-band { stroke: var(--accent); stroke-width: 5; opacity: .35; stroke-linecap: butt; }
583
+ .rl-you { fill: var(--accent); stroke: var(--bg-base); stroke-width: 1.5; }
584
+ .rl-you-label { fill: var(--accent); font: 400 16px var(--font-crt); }
585
+ .rl-legend { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: var(--s-3); font-family: var(--font-body); font-size: 12px; color: var(--text-muted); }
586
  .rl-legend .item { display: inline-flex; align-items: center; gap: 6px; }
587
+ .rl-legend .sw { width: 10px; height: 10px; border-radius: 0; }
588
+ .rl-p1-sw { background: #34503f; } .rl-p8-sw { background: #4d6e58; } .rl-p14-sw { background: #6f9580; }
589
+ .rl-roof-sw { background: var(--warn); border-radius: 0; height: 3px; width: 14px; }
590
+ .rl-you-sw { background: var(--accent); box-shadow: 0 0 6px var(--glow); }
591
+ .viz-caption { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin-top: var(--s-3); font-family: var(--font-body); }
592
 
593
+ /* Multi-goal overview - hairline panels, inset ring on hover, no lift */
594
  .goal-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--s-3); }
595
  .goal-card {
596
+ background: var(--bg-inset); border: 1px solid var(--border);
597
+ border-left: 2px solid var(--status, var(--border)); border-radius: var(--r-md);
598
  padding: var(--s-4); cursor: pointer;
599
+ transition: box-shadow .15s, background .15s;
600
  }
601
+ .goal-card:hover { background: #101a13; box-shadow: inset 0 0 0 1px var(--border-hi); }
602
  .goal-card .badge {
603
  display: inline-flex; align-items: center; gap: 6px;
604
+ padding: 0; background: transparent; color: var(--status, var(--accent));
605
+ font-family: var(--font-pixel); font-size: 9px; line-height: 1.3; text-transform: uppercase; letter-spacing: .02em;
606
+ }
607
+ .goal-card .badge .dot { width: 7px; height: 7px; border-radius: 0; background: currentColor; box-shadow: 0 0 6px currentColor; }
608
+ .goal-name { font-family: var(--font-crt); font-weight: 400; font-size: 18px; line-height: 1.2; margin-top: var(--s-3); }
609
+ .goal-pick { font-family: var(--font-crt); font-size: 16px; color: var(--text-secondary); margin-top: 4px; display: flex; align-items: center; gap: 6px; }
610
+ .goal-pick .ic { font-size: 13px; color: var(--accent); }
611
+ .goal-need { font-family: var(--font-crt); font-size: 15px; color: var(--text-muted); margin-top: 2px; }
612
+ .goal-more { font-family: var(--font-crt); font-size: 15px; font-weight: 400; color: var(--accent); margin-top: var(--s-3); }
613
  .back-link {
614
  background: var(--bg-inset); border: 1px solid var(--border); color: var(--text-secondary);
615
+ border-radius: var(--r-sm); padding: 6px 14px; font-family: var(--font-crt); font-size: 15px; font-weight: 400;
616
+ margin-bottom: var(--s-4); display: inline-flex; align-items: center; gap: 6px;
617
  }
618
  .back-link:hover { color: var(--text-primary); border-color: var(--border-hi); }
619
+ .back-link .ic { font-size: 13px; transform: scaleX(-1); }
620
  .goal-lines { list-style: none; padding: 0; margin: var(--s-2) 0 0; }
621
  .goal-lines li {
622
  display: flex; justify-content: space-between; gap: var(--s-3);
623
+ font-family: var(--font-body); font-size: 13px; color: var(--text-secondary); padding: 3px 0;
624
  border-bottom: 1px dashed var(--border);
625
  }
626
  .goal-lines li:last-child { border-bottom: none; }
627
+ .goal-lines li b { color: var(--text-primary); font-weight: 600; text-align: right; font-family: var(--font-crt); font-size: 15px; }
628
  .goal-lines li i { color: var(--text-muted); font-style: normal; font-size: 12px; }
629
 
630
  /* Live single-model lookup card */
631
  .lookup-card {
632
+ border: 1px solid var(--border); border-left: 2px solid var(--status, var(--accent));
633
+ border-radius: var(--r-md); background: var(--bg-inset);
634
  padding: var(--s-4) var(--s-5); margin-bottom: var(--s-5);
635
  }
636
  .lookup-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s-2); }
637
  .live-tag {
638
+ font-family: var(--font-crt); font-size: 14px; font-weight: 400; text-transform: uppercase; letter-spacing: .06em;
639
+ color: var(--accent); border: 1px dashed var(--accent); border-radius: var(--r-sm);
640
+ padding: 3px 10px;
641
  }
642
+ .lookup-explain { color: var(--text-secondary); font-size: 14px; font-family: var(--font-body); }
643
+ .lookup-meta { display: flex; gap: var(--s-4); margin-top: var(--s-2); font-family: var(--font-crt); font-size: 15px; color: var(--text-secondary); }
 
644
  .lookup-banner {
645
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
646
  background: var(--accent-soft); border: 1px solid var(--accent);
647
  border-radius: var(--r-md); padding: var(--s-3) var(--s-4); margin-bottom: var(--s-4);
648
  }
649
+ .lookup-banner p { color: var(--text-secondary); font-size: 13px; margin: 0; font-family: var(--font-body); }
650
  .lookup-banner.estimate { background: var(--warn-soft); border-color: var(--warn); }
651
+ .lookup-banner.estimate .live-tag { color: var(--warn); border-color: var(--warn); }
652
+ #repo-field.required input { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
653
 
654
  /* Ask a follow-up (the model brick) */
655
  .ask-row { display: flex; gap: var(--s-2); }
656
  .ask-row input {
657
  flex: 1; background: var(--bg-inset); border: 1px solid var(--border);
658
+ border-radius: var(--r-md); padding: 11px 14px; font-family: var(--font-crt); font-size: 18px;
659
  }
660
  .ask-btn {
661
+ flex: none; width: 46px; border: 1px solid var(--accent); border-radius: var(--r-md);
662
+ background: var(--accent);
663
+ color: #04130a; display: grid; place-items: center;
664
+ box-shadow: var(--glowshadow);
665
+ transition: filter .15s, box-shadow .15s;
666
+ }
667
+ .ask-btn:hover { filter: brightness(1.08); box-shadow: 0 0 20px var(--glow); }
668
+ .ask-btn .ic { font-size: 16px; }
669
  .ask-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-3); }
670
  .ask-chip {
671
  background: var(--bg-inset); border: 1px solid var(--border);
672
+ color: var(--text-secondary); border-radius: var(--r-sm);
673
+ padding: 6px 13px; font-family: var(--font-crt); font-size: 15px; font-weight: 400;
674
  transition: border-color .15s, color .15s, background .15s;
675
  }
676
  .ask-chip:hover { border-color: var(--accent); color: var(--text-primary); background: var(--accent-soft); }
 
678
  .ask-answer { margin-top: var(--s-4); }
679
  .ans-card {
680
  background: var(--bg-inset); border: 1px solid var(--border);
681
+ border-left: 2px solid var(--accent); border-radius: var(--r-md);
682
  padding: var(--s-4) var(--s-5);
683
  }
684
+ .ans-card h3 { font-family: var(--font-crt); font-size: 19px; font-weight: 400; margin-bottom: var(--s-2); }
685
+ .ans-card p { color: var(--text-secondary); font-family: var(--font-body); font-size: 14px; line-height: 1.6; }
686
  .ans-next {
687
  display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-3);
688
+ font-family: var(--font-crt); font-size: 16px; font-weight: 400; color: var(--accent);
689
  }
690
+ .ans-next .ic { font-size: 14px; flex: none; }
691
  .ans-card.ans-error { border-left-color: var(--no); }
692
  .ans-card.ans-error h3 { color: var(--no); }
693
+ .ans-card.ans-error p { color: var(--text-secondary); font-size: 13px; word-break: break-word; }
694
+ .ans-loading { display: flex; align-items: center; gap: var(--s-2); color: var(--text-muted); font-family: var(--font-crt); font-size: 16px; padding: var(--s-2) 0; flex-wrap: wrap; }
695
  .ans-cold { font-size: 12px; color: var(--text-muted); opacity: .75; }
696
  .tick-note { font-size: 12px; color: var(--text-muted); opacity: .8; }
697
  #parse-hint .spinner, .ans-loading .spinner { display: inline-block; vertical-align: -2px; margin-right: 6px; }
 
703
  @keyframes spin { to { transform: rotate(360deg); } }
704
 
705
  /* Footer */
706
+ .foot { text-align: left; color: var(--text-muted); font-family: var(--font-crt); font-size: 16px; margin-top: var(--s-7); padding-top: var(--s-4); border-top: 1px solid var(--border); line-height: 1.55; }
707
+ .foot p { font-family: var(--font-body); font-size: 13px; }
708
  .foot b { color: var(--text-secondary); }
709
 
710
+ /* Reveal animation (kept; subtle) */
711
+ .reveal { animation: reveal .4s cubic-bezier(.22,1,.36,1) both; }
712
+ @keyframes reveal { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
713
 
714
  @media (prefers-reduced-motion: reduce) {
715
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
716
+ body::after { display: none; }
717
+ }
718
+
719
+ @media (max-width: 900px) {
720
+ .hero h1 { font-size: 34px; }
721
  }