karlexmarin Claude Opus 4.7 (1M context) commited on
Commit
b4eccb8
·
1 Parent(s): ffda3dc

v0.7.7: UX restructure — task tiles + 'Why static?' README FAQ

Browse files

UX RESTRUCTURE — addresses the 14-tabs-overflow problem before forum-post traffic arrives.

NEW <section id='task-tiles'>: 5 tiles grouping the 14 modes by user intent (not feature):
- 🔬 Diagnose a model (Profile, Unmask, NIAH→Reason, Quant, Inspector)
- ✓ Trust a benchmark score (Contamination, Drift, Arena CI)
- ⚙️ Set up an eval correctly (Chat-template, Diagnose CLI)
- 🆚 Compare models (Compare, Phase diagram)
- 📋 Manual / free-form (Recipe, Ask)

Tile buttons emit [data-mode-link='X'] clicks → delegated handler triggers the corresponding mode-btn (no duplicate state, fully reuses the existing mode switcher) and smooth-scrolls to the section so the user immediately sees the form they expected.

Mode-tabs strip stays as a power-user backup below the tiles (still wrapped foldable by wrapMainSectionsAsFoldable). Subtitle on tiles points users there if they want full access.

Cognitive load drops from 14 buttons → 5 categorical tiles. Adding more modes in v0.8 won't require new top-level UI — just new tile entries.

README FAQ — preempts 'why not Gradio?' criticism before forum post traffic arrives. Cites three USPs (privacy + $0 + Lean formal verification) that are only viable with browser-only architecture.

i18n: 12 new keys × EN/ES/FR/ZH (693 total, 0 missing / 0 extra).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (5) hide show
  1. README.md +14 -0
  2. index.html +52 -0
  3. js/i18n.js +56 -0
  4. js/main.js +25 -0
  5. style.css +50 -0
README.md CHANGED
@@ -132,6 +132,20 @@ nothing is hallucinated.
132
 
133
  If 1 user or 1 million users hit it, our cost stays the same: $0.
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  ## Architecture coverage
136
 
137
  Supports any model whose `config.json` is parseable:
 
132
 
133
  If 1 user or 1 million users hit it, our cost stays the same: $0.
134
 
135
+ ## Why static HTML+JS+Pyodide instead of Gradio/Streamlit?
136
+
137
+ A reasonable question. Three TAF Agent USPs are **only possible** with browser-only architecture:
138
+
139
+ 1. **Your inputs never leave the tab.** No server = no privacy compromise. The "anti-bullshit" framing depends on this.
140
+ 2. **$0 forever, even at infinite scale.** Static Spaces have unlimited HF bandwidth; there is no cold-start, no queue, no rate limit. Going viral can't bankrupt the project.
141
+ 3. **Lean+Mathlib formal verification** ships as a static manifest. The 37 theorem badges link to source lines that anyone can `lake build` themselves — no hidden server logic.
142
+
143
+ Bonus: in-browser LLM (WebLLM running Qwen2.5-0.5B in your GPU/CPU) for the 💬 Ask mode is only viable in static. Pyodide running deterministic Python in your browser means you can audit every number — no opaque server.
144
+
145
+ The cost: HuggingFace's "Trending Spaces" algorithm favours Gradio/Streamlit Spaces. We compensate with detailed tags + forum presence + this README. If you'd prefer a Python-API client, that's a planned `gradio_client` companion (v0.9).
146
+
147
+ ---
148
+
149
  ## Architecture coverage
150
 
151
  Supports any model whose `config.json` is parseable:
index.html CHANGED
@@ -349,6 +349,58 @@
349
  </div>
350
  </section>
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  <!-- Mode toggle -->
353
  <section id="mode-section">
354
  <h2><span data-i18n="modes.title">🎯 Mode</span>
 
349
  </div>
350
  </section>
351
 
352
+ <!-- v0.7.7 — Task tiles: friendlier entry point, groups 14 modes by user intent. -->
353
+ <section id="task-tiles" aria-labelledby="tiles-title">
354
+ <h2 id="tiles-title" data-i18n="tiles.title">🎯 What do you want to do?</h2>
355
+ <p class="recipe-desc" data-i18n="tiles.subtitle">Pick a task. Each one opens the right tool below. Or scroll down for the full list of 14 modes.</p>
356
+ <div class="tiles-grid">
357
+ <div class="task-tile">
358
+ <h3 data-i18n="tile.diagnose.title">🔬 Diagnose a model</h3>
359
+ <p class="tile-desc" data-i18n="tile.diagnose.desc">Will this specific model work for my use case?</p>
360
+ <div class="tile-modes">
361
+ <button data-mode-link="profile" data-i18n="modes.profile">📇 Profile a model</button>
362
+ <button data-mode-link="unmask" data-i18n="modes.unmask">🪟 Unmask</button>
363
+ <button data-mode-link="niah" data-i18n="modes.niah">🔍 NIAH→Reason</button>
364
+ <button data-mode-link="quant" data-i18n="modes.quant">⚖️ Quant</button>
365
+ <button data-mode-link="inspector" data-i18n="modes.inspector">🔍 Inspect config</button>
366
+ </div>
367
+ </div>
368
+ <div class="task-tile">
369
+ <h3 data-i18n="tile.trust.title">✓ Trust a benchmark score</h3>
370
+ <p class="tile-desc" data-i18n="tile.trust.desc">Should I believe this number? Bug or noise?</p>
371
+ <div class="tile-modes">
372
+ <button data-mode-link="contam" data-i18n="modes.contam">🧪 Contamination</button>
373
+ <button data-mode-link="drift" data-i18n="modes.drift">🔀 Drift</button>
374
+ <button data-mode-link="arena" data-i18n="modes.arena">🎯 Arena CI</button>
375
+ </div>
376
+ </div>
377
+ <div class="task-tile">
378
+ <h3 data-i18n="tile.eval.title">⚙️ Set up an eval correctly</h3>
379
+ <p class="tile-desc" data-i18n="tile.eval.desc">Get the exact CLI flag for lm-eval / vLLM / transformers.</p>
380
+ <div class="tile-modes">
381
+ <button data-mode-link="template" data-i18n="modes.template">📜 Chat-template</button>
382
+ <button data-mode-link="diagnose" data-i18n="modes.diagnose">🩺 Diagnose CLI</button>
383
+ </div>
384
+ </div>
385
+ <div class="task-tile">
386
+ <h3 data-i18n="tile.compare.title">🆚 Compare models</h3>
387
+ <p class="tile-desc" data-i18n="tile.compare.desc">Side-by-side, or browse the empirical model landscape.</p>
388
+ <div class="tile-modes">
389
+ <button data-mode-link="compare" data-i18n="modes.compare">🆚 Compare models</button>
390
+ <button data-mode-link="phase" data-i18n="modes.phase">📊 Phase diagram</button>
391
+ </div>
392
+ </div>
393
+ <div class="task-tile">
394
+ <h3 data-i18n="tile.manual.title">📋 Manual / free-form</h3>
395
+ <p class="tile-desc" data-i18n="tile.manual.desc">Pick a specific recipe by hand, or ask in plain English.</p>
396
+ <div class="tile-modes">
397
+ <button data-mode-link="recipe" data-i18n="modes.recipe">📋 Pick recipe</button>
398
+ <button data-mode-link="ask" data-i18n="modes.ask">💬 Ask plain English</button>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ </section>
403
+
404
  <!-- Mode toggle -->
405
  <section id="mode-section">
406
  <h2><span data-i18n="modes.title">🎯 Mode</span>
js/i18n.js CHANGED
@@ -465,6 +465,20 @@ export const TRANSLATIONS = {
465
  "niah.status.fetched": "✅ Config fetched for {modelId}. Set T_eval and click Predict (or Sweep contexts).",
466
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
467
  "niah.status.sweep_done": "✅ Swept {n} context lengths.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  "share.import_desc": "Got a JSON file from someone else's TAF analysis? Load it here to see the verdict + chain locally. Same view as if you'd run it yourself.",
469
  "share.import_btn": "📂 Load shared JSON",
470
  "synthesis.system": "You are a precise transformer LLM diagnostic assistant. Given pre-computed TAF formula results, write a clear plain-English summary in 4-6 sentences. Cite the section number (§X.Y) for each number you mention. Always give a concrete recommendation. Do NOT invent numbers.",
@@ -1354,6 +1368,20 @@ export const TRANSLATIONS = {
1354
  "niah.status.fetched": "✅ Config obtenido para {modelId}. Pon T_eval y click Predecir (o Barrer contextos).",
1355
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
1356
  "niah.status.sweep_done": "✅ Barridos {n} largos de contexto.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1357
  "share.import_desc": "¿Tienes un fichero JSON del análisis TAF de alguien? Cárgalo aquí para ver el veredicto + cadena localmente. La misma vista que si lo hubieras ejecutado tú.",
1358
  "share.import_btn": "📂 Cargar JSON compartido",
1359
  "synthesis.system": "Eres un asistente de diagnóstico preciso para LLMs transformer. Dados resultados de fórmulas TAF pre-calculados, escribe un resumen claro en español de 4-6 frases. Cita el número de sección (§X.Y) para cada número que menciones. Da siempre una recomendación concreta. NO inventes números.",
@@ -2107,6 +2135,20 @@ export const TRANSLATIONS = {
2107
  "niah.status.fetched": "✅ Config récupéré pour {modelId}. Réglez T_eval et cliquez Prédire (ou Balayer les contextes).",
2108
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
2109
  "niah.status.sweep_done": "✅ Balayé {n} longueurs de contexte.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2110
  "share.import_desc": "Vous avez un fichier JSON de l'analyse TAF de quelqu'un ? Chargez-le ici pour voir le verdict + la chaîne localement. La même vue que si vous l'aviez exécuté vous-même.",
2111
  "share.import_btn": "📂 Charger JSON partagé",
2112
  "synthesis.system": "Vous êtes un assistant de diagnostic précis pour LLMs transformer. Étant donné des résultats de formules TAF pré-calculés, écrivez un résumé clair en français de 4-6 phrases. Citez le numéro de section (§X.Y) pour chaque nombre mentionné. Donnez toujours une recommandation concrète. N'INVENTEZ PAS de nombres.",
@@ -2860,6 +2902,20 @@ export const TRANSLATIONS = {
2860
  "niah.status.fetched": "✅ 已获取 {modelId} 的 config。设置 T_eval 并点击预测(或扫描上下文)。",
2861
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
2862
  "niah.status.sweep_done": "✅ 已扫描 {n} 个上下文长度。",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2863
  "share.import_desc": "有他人 TAF 分析的 JSON 文件? 在这里加载以本地查看判定 + 链。与您自己运行的视图相同。",
2864
  "share.import_btn": "📂 加载共享的 JSON",
2865
  "synthesis.system": "您是 transformer LLM 的精确诊断助手。给定预先计算的 TAF 公式结果,用 4-6 句中文写出清晰的摘要。为每个提到的数字引用章节号 (§X.Y)。始终给出具体建议。不要编造数字。",
 
465
  "niah.status.fetched": "✅ Config fetched for {modelId}. Set T_eval and click Predict (or Sweep contexts).",
466
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
467
  "niah.status.sweep_done": "✅ Swept {n} context lengths.",
468
+
469
+ // v0.7.7 — Task tiles (UX restructure: 14 modes grouped by user intent)
470
+ "tiles.title": "🎯 What do you want to do?",
471
+ "tiles.subtitle": "Pick a task. Each one opens the right tool below. Or scroll down for the full list of 14 modes.",
472
+ "tile.diagnose.title": "🔬 Diagnose a model",
473
+ "tile.diagnose.desc": "Will this specific model work for my use case?",
474
+ "tile.trust.title": "✓ Trust a benchmark score",
475
+ "tile.trust.desc": "Should I believe this number? Bug or noise?",
476
+ "tile.eval.title": "⚙️ Set up an eval correctly",
477
+ "tile.eval.desc": "Get the exact CLI flag for lm-eval / vLLM / transformers.",
478
+ "tile.compare.title": "🆚 Compare models",
479
+ "tile.compare.desc": "Side-by-side, or browse the empirical model landscape.",
480
+ "tile.manual.title": "📋 Manual / free-form",
481
+ "tile.manual.desc": "Pick a specific recipe by hand, or ask in plain English.",
482
  "share.import_desc": "Got a JSON file from someone else's TAF analysis? Load it here to see the verdict + chain locally. Same view as if you'd run it yourself.",
483
  "share.import_btn": "📂 Load shared JSON",
484
  "synthesis.system": "You are a precise transformer LLM diagnostic assistant. Given pre-computed TAF formula results, write a clear plain-English summary in 4-6 sentences. Cite the section number (§X.Y) for each number you mention. Always give a concrete recommendation. Do NOT invent numbers.",
 
1368
  "niah.status.fetched": "✅ Config obtenido para {modelId}. Pon T_eval y click Predecir (o Barrer contextos).",
1369
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
1370
  "niah.status.sweep_done": "✅ Barridos {n} largos de contexto.",
1371
+
1372
+ // v0.7.7 — Tiles de tareas (UX restructure: 14 modos agrupados por intención)
1373
+ "tiles.title": "🎯 ¿Qué quieres hacer?",
1374
+ "tiles.subtitle": "Elige una tarea. Cada una abre la herramienta adecuada debajo. O baja para la lista completa de 14 modos.",
1375
+ "tile.diagnose.title": "🔬 Diagnosticar un modelo",
1376
+ "tile.diagnose.desc": "¿Servirá este modelo concreto para mi caso de uso?",
1377
+ "tile.trust.title": "✓ Confiar en un score de benchmark",
1378
+ "tile.trust.desc": "¿Me creo este número? ¿Es bug o ruido?",
1379
+ "tile.eval.title": "⚙️ Configurar bien una eval",
1380
+ "tile.eval.desc": "Obtén el flag CLI exacto para lm-eval / vLLM / transformers.",
1381
+ "tile.compare.title": "🆚 Comparar modelos",
1382
+ "tile.compare.desc": "Lado a lado, o explora el panel empírico de modelos.",
1383
+ "tile.manual.title": "📋 Manual / libre",
1384
+ "tile.manual.desc": "Elige una receta concreta a mano, o pregunta en inglés llano.",
1385
  "share.import_desc": "¿Tienes un fichero JSON del análisis TAF de alguien? Cárgalo aquí para ver el veredicto + cadena localmente. La misma vista que si lo hubieras ejecutado tú.",
1386
  "share.import_btn": "📂 Cargar JSON compartido",
1387
  "synthesis.system": "Eres un asistente de diagnóstico preciso para LLMs transformer. Dados resultados de fórmulas TAF pre-calculados, escribe un resumen claro en español de 4-6 frases. Cita el número de sección (§X.Y) para cada número que menciones. Da siempre una recomendación concreta. NO inventes números.",
 
2135
  "niah.status.fetched": "✅ Config récupéré pour {modelId}. Réglez T_eval et cliquez Prédire (ou Balayer les contextes).",
2136
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
2137
  "niah.status.sweep_done": "✅ Balayé {n} longueurs de contexte.",
2138
+
2139
+ // v0.7.7 — Tuiles de tâches (refonte UX : 14 modes regroupés par intention)
2140
+ "tiles.title": "🎯 Que voulez-vous faire ?",
2141
+ "tiles.subtitle": "Choisissez une tâche. Chacune ouvre l'outil adéquat ci-dessous. Ou faites défiler pour la liste complète des 14 modes.",
2142
+ "tile.diagnose.title": "🔬 Diagnostiquer un modèle",
2143
+ "tile.diagnose.desc": "Ce modèle conviendra-t-il à mon cas d'usage ?",
2144
+ "tile.trust.title": "✓ Faire confiance à un score",
2145
+ "tile.trust.desc": "Dois-je croire ce nombre ? Bug ou bruit ?",
2146
+ "tile.eval.title": "⚙️ Configurer une éval correctement",
2147
+ "tile.eval.desc": "Obtenez le flag CLI exact pour lm-eval / vLLM / transformers.",
2148
+ "tile.compare.title": "🆚 Comparer des modèles",
2149
+ "tile.compare.desc": "Côte à côte, ou explorez le panel empirique de modèles.",
2150
+ "tile.manual.title": "📋 Manuel / libre",
2151
+ "tile.manual.desc": "Choisissez une recette à la main, ou demandez en langage naturel.",
2152
  "share.import_desc": "Vous avez un fichier JSON de l'analyse TAF de quelqu'un ? Chargez-le ici pour voir le verdict + la chaîne localement. La même vue que si vous l'aviez exécuté vous-même.",
2153
  "share.import_btn": "📂 Charger JSON partagé",
2154
  "synthesis.system": "Vous êtes un assistant de diagnostic précis pour LLMs transformer. Étant donné des résultats de formules TAF pré-calculés, écrivez un résumé clair en français de 4-6 phrases. Citez le numéro de section (§X.Y) pour chaque nombre mentionné. Donnez toujours une recommandation concrète. N'INVENTEZ PAS de nombres.",
 
2902
  "niah.status.fetched": "✅ 已获取 {modelId} 的 config。设置 T_eval 并点击预测(或扫描上下文)。",
2903
  "niah.status.done": "✅ {verdict} — NIAH {niah}% · reasoning {reasoning}%",
2904
  "niah.status.sweep_done": "✅ 已扫描 {n} 个上下文长度。",
2905
+
2906
+ // v0.7.7 — 任务卡片(UX 重构:按用户意图分组的 14 个模式)
2907
+ "tiles.title": "🎯 你想做什么?",
2908
+ "tiles.subtitle": "选择一项任务。每一项会打开下方对应的工具。或往下滚动查看完整的 14 个模式列表。",
2909
+ "tile.diagnose.title": "🔬 诊断一个模型",
2910
+ "tile.diagnose.desc": "这个具体模型符合我的用例吗?",
2911
+ "tile.trust.title": "✓ 相信 benchmark 分数",
2912
+ "tile.trust.desc": "我该相信这个数字吗?是 bug 还是噪声?",
2913
+ "tile.eval.title": "⚙️ 正确设置 eval",
2914
+ "tile.eval.desc": "获取 lm-eval / vLLM / transformers 的精确 CLI flag。",
2915
+ "tile.compare.title": "🆚 比较模型",
2916
+ "tile.compare.desc": "并排,或浏览经验模型面板。",
2917
+ "tile.manual.title": "📋 手动 / 自由",
2918
+ "tile.manual.desc": "手动挑一个具体 recipe,或用自然语言提问。",
2919
  "share.import_desc": "有他人 TAF 分析的 JSON 文件? 在这里加载以本地查看判定 + 链。与您自己运行的视图相同。",
2920
  "share.import_btn": "📂 加载共享的 JSON",
2921
  "synthesis.system": "您是 transformer LLM 的精确诊断助手。给定预先计算的 TAF 公式结果,用 4-6 句中文写出清晰的摘要。为每个提到的数字引用章节号 (§X.Y)。始终给出具体建议。不要编造数字。",
js/main.js CHANGED
@@ -184,6 +184,31 @@ wrapMainSectionsAsFoldable();
184
  // ════════════════════════════════════════════════════════════════════
185
  // Mode toggle
186
  // ════════════════════════════════════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  document.querySelectorAll(".mode-btn").forEach(btn => {
188
  btn.addEventListener("click", () => {
189
  document.querySelectorAll(".mode-btn").forEach(b => {
 
184
  // ════════════════════════════════════════════════════════════════════
185
  // Mode toggle
186
  // ════════════════════════════════════════════════════════════════════
187
+ // v0.7.7 — task tiles: clicking a tile-mode-link button triggers the equivalent mode-btn.
188
+ // Reuses the mode switcher entirely (no duplicate state). Smoothly scrolls to the
189
+ // activated section so the user immediately sees the form they expected.
190
+ document.addEventListener("click", (e) => {
191
+ const linkBtn = e.target.closest("[data-mode-link]");
192
+ if (!linkBtn) return;
193
+ const targetMode = linkBtn.dataset.modeLink;
194
+ const targetTab = document.querySelector(`.mode-btn[data-mode="${targetMode}"]`);
195
+ if (targetTab) {
196
+ targetTab.click();
197
+ // Scroll the activated section into view so the tile click feels responsive.
198
+ const sectionId = {
199
+ ask: "ask-section", recipe: "recipe-section", profile: "profile-section",
200
+ compare: "compare-section", inspector: "inspector-section",
201
+ diagnose: "diagnose-section", phase: "phase-section", unmask: "unmask-section",
202
+ template: "template-section", arena: "arena-section", contam: "contam-section",
203
+ quant: "quant-section", drift: "drift-section", niah: "niah-section",
204
+ }[targetMode];
205
+ if (sectionId) {
206
+ const sec = document.getElementById(sectionId);
207
+ if (sec) sec.scrollIntoView({ behavior: "smooth", block: "start" });
208
+ }
209
+ }
210
+ });
211
+
212
  document.querySelectorAll(".mode-btn").forEach(btn => {
213
  btn.addEventListener("click", () => {
214
  document.querySelectorAll(".mode-btn").forEach(b => {
style.css CHANGED
@@ -33,6 +33,56 @@
33
  flex: 1;
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  /* v0.7.5 — Cross-framework drift bound */
37
  /* Drift section overrides the default 980px main width so the two-column form
38
  has room without overlapping. Mobile (<800px) collapses to single column. */
 
33
  flex: 1;
34
  }
35
 
36
+ /* v0.7.7 — Task tiles: groups 14 modes by user intent, primary entry point */
37
+ #task-tiles { margin-bottom: 1em; }
38
+ #task-tiles h2 { margin-bottom: 0.3em; }
39
+ #task-tiles .recipe-desc { margin-bottom: 0.8em; opacity: 0.85; }
40
+ .tiles-grid {
41
+ display: grid;
42
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
43
+ gap: 0.8em;
44
+ }
45
+ .task-tile {
46
+ padding: 0.9em 1em;
47
+ background: #12181f;
48
+ border: 1px solid rgba(88, 166, 255, 0.2);
49
+ border-radius: 10px;
50
+ transition: border-color 0.15s, transform 0.15s;
51
+ }
52
+ .task-tile:hover {
53
+ border-color: rgba(88, 166, 255, 0.5);
54
+ }
55
+ .task-tile h3 {
56
+ margin: 0 0 0.3em;
57
+ font-size: 1.05em;
58
+ color: #58a6ff;
59
+ }
60
+ .task-tile .tile-desc {
61
+ font-size: 0.88em;
62
+ opacity: 0.8;
63
+ margin: 0 0 0.6em;
64
+ line-height: 1.4;
65
+ }
66
+ .task-tile .tile-modes {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ gap: 0.35em;
70
+ }
71
+ .task-tile .tile-modes button {
72
+ font-size: 0.85em;
73
+ padding: 0.35em 0.7em;
74
+ background: rgba(88, 166, 255, 0.10);
75
+ border: 1px solid rgba(88, 166, 255, 0.25);
76
+ color: #c9d1d9;
77
+ border-radius: 5px;
78
+ cursor: pointer;
79
+ transition: background 0.12s, border-color 0.12s;
80
+ }
81
+ .task-tile .tile-modes button:hover {
82
+ background: rgba(88, 166, 255, 0.22);
83
+ border-color: rgba(88, 166, 255, 0.55);
84
+ }
85
+
86
  /* v0.7.5 — Cross-framework drift bound */
87
  /* Drift section overrides the default 980px main width so the two-column form
88
  has room without overlapping. Mobile (<800px) collapses to single column. */