| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <meta name="description" content="AutoMedBench Lite efficiency frontier: overall score against cost, time, tokens, and turns per task."> |
| <meta name="color-scheme" content="light dark"> |
| <title>AutoMedBench Lite - Efficiency Frontier</title> |
| <style> |
| :root { |
| --canvas: #f6f1e6; |
| --panel: #faf6ee; |
| --panel-strong: #fffdf8; |
| --ink: #3b4538; |
| --ink-soft: #737a6d; |
| --ink-faint: #9aa093; |
| --edge: #e6decc; |
| --edge-strong: #d9d0bc; |
| --primary: #4a7355; |
| --primary-dark: #3e6248; |
| --primary-soft: #e6ede5; |
| --mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace; |
| --shadow: 0 12px 32px rgba(59, 69, 56, 0.08); |
| } |
| |
| [data-theme="dark"] { |
| --canvas: #20241d; |
| --panel: #262b21; |
| --panel-strong: #2b3126; |
| --ink: #e9e5d7; |
| --ink-soft: #aab09f; |
| --ink-faint: #7f8474; |
| --edge: #3a4033; |
| --edge-strong: #464d3c; |
| --primary: #82a98d; |
| --primary-dark: #9abea3; |
| --primary-soft: #333f2e; |
| --shadow: 0 12px 32px rgba(0, 0, 0, 0.35); |
| } |
| |
| * { box-sizing: border-box; } |
| |
| html { background: var(--canvas); } |
| |
| body { |
| margin: 0; |
| min-width: 320px; |
| color: var(--ink); |
| background: |
| radial-gradient(circle at 15% 0%, rgba(74, 115, 85, 0.08), transparent 30rem), |
| var(--canvas); |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; |
| font-size: 16px; |
| line-height: 1.5; |
| -webkit-font-smoothing: antialiased; |
| } |
| |
| a { color: inherit; } |
| |
| .page { |
| width: min(100% - 32px, 1180px); |
| margin: 0 auto; |
| padding: 44px 0 36px; |
| } |
| |
| .hero { |
| display: flex; |
| align-items: flex-start; |
| justify-content: space-between; |
| gap: 24px; |
| margin-bottom: 24px; |
| } |
| |
| .brand { |
| display: flex; |
| align-items: center; |
| gap: 14px; |
| } |
| |
| .brand-mark { |
| display: grid; |
| width: 42px; |
| height: 42px; |
| flex: 0 0 auto; |
| place-items: center; |
| border-radius: 11px; |
| color: #fff; |
| background: var(--primary); |
| box-shadow: 0 5px 14px rgba(74, 115, 85, 0.2); |
| font-family: Georgia, "Times New Roman", serif; |
| font-size: 23px; |
| font-weight: 700; |
| } |
| |
| .eyebrow { |
| margin: 0 0 2px; |
| color: var(--primary); |
| font-size: 12px; |
| font-weight: 750; |
| letter-spacing: 0.11em; |
| text-transform: uppercase; |
| } |
| |
| h1 { |
| margin: 0; |
| font-family: Georgia, "Times New Roman", serif; |
| font-size: clamp(28px, 4vw, 38px); |
| font-weight: 650; |
| letter-spacing: -0.025em; |
| line-height: 1.1; |
| } |
| |
| .button.theme-fab { |
| position: fixed; |
| right: 22px; |
| bottom: 22px; |
| z-index: 30; |
| min-height: 50px; |
| padding: 13px 22px; |
| border: 2px solid var(--primary-dark); |
| border-radius: 999px; |
| color: #fff; |
| background: var(--primary); |
| font-size: 15px; |
| font-weight: 750; |
| letter-spacing: 0.01em; |
| box-shadow: 0 10px 26px rgba(0, 0, 0, 0.28); |
| } |
| |
| .button.theme-fab:hover, |
| .button.theme-fab:focus-visible { |
| color: #fff; |
| background: var(--primary-dark); |
| border-color: var(--primary-dark); |
| transform: translateY(-1px); |
| } |
| |
| .actions { |
| display: flex; |
| flex: 0 0 auto; |
| gap: 8px; |
| } |
| |
| .button { |
| display: inline-flex; |
| align-items: center; |
| min-height: 36px; |
| padding: 7px 12px; |
| border: 1px solid var(--edge-strong); |
| border-radius: 8px; |
| color: var(--ink); |
| background: var(--panel-strong); |
| font: inherit; |
| font-size: 13px; |
| font-weight: 650; |
| text-decoration: none; |
| cursor: pointer; |
| transition: border-color 140ms ease, background 140ms ease, color 140ms ease; |
| } |
| |
| .button:hover, |
| .button:focus-visible { |
| border-color: var(--primary); |
| color: var(--primary-dark); |
| background: var(--panel-strong); |
| outline: none; |
| } |
| |
| .frontier { |
| overflow: hidden; |
| border: 1px solid var(--edge-strong); |
| border-radius: 12px; |
| background: var(--panel-strong); |
| box-shadow: var(--shadow); |
| } |
| |
| .tabbar { |
| display: grid; |
| grid-template-columns: repeat(4, minmax(0, 1fr)); |
| gap: 4px; |
| padding: 6px; |
| border-bottom: 1px solid var(--edge); |
| background: var(--panel); |
| } |
| |
| .tab { |
| min-height: 44px; |
| padding: 7px 10px; |
| border: 0; |
| border-radius: 8px; |
| color: var(--ink-soft); |
| background: transparent; |
| font: inherit; |
| font-size: 15.5px; |
| font-weight: 700; |
| cursor: pointer; |
| transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease; |
| } |
| |
| .tab small { |
| display: block; |
| font-size: 11px; |
| font-weight: 650; |
| letter-spacing: 0.06em; |
| text-transform: uppercase; |
| opacity: 0.75; |
| } |
| |
| .tab:hover { color: var(--ink); background: rgba(255, 253, 248, 0.7); } |
| |
| .tab:focus-visible { |
| outline: 2px solid var(--primary); |
| outline-offset: 2px; |
| } |
| |
| .tab[aria-selected="true"] { |
| color: #fff; |
| background: var(--primary); |
| box-shadow: 0 2px 6px rgba(59, 69, 56, 0.14); |
| } |
| |
| .frontier-head { |
| display: flex; |
| align-items: flex-start; |
| justify-content: space-between; |
| gap: 24px; |
| padding: 18px 20px 12px; |
| } |
| |
| .frontier-copy { min-width: 0; } |
| |
| .kicker { |
| margin: 0 0 4px; |
| color: var(--primary); |
| font-size: 11px; |
| font-weight: 800; |
| letter-spacing: 0.09em; |
| text-transform: uppercase; |
| } |
| |
| .frontier h2 { |
| margin: 0; |
| color: var(--ink); |
| font-family: Georgia, "Times New Roman", serif; |
| font-size: clamp(21px, 3vw, 27px); |
| font-weight: 650; |
| letter-spacing: -0.02em; |
| line-height: 1.15; |
| } |
| |
| .frontier-subtitle { |
| max-width: 680px; |
| margin: 6px 0 0; |
| color: var(--ink-soft); |
| font-size: 15px; |
| } |
| |
| .head-controls { |
| display: flex; |
| flex: 0 0 auto; |
| align-items: center; |
| gap: 8px; |
| padding-top: 4px; |
| } |
| |
| .count-pill { |
| display: inline-flex; |
| align-items: center; |
| min-height: 34px; |
| padding: 5px 13px; |
| border: 1px solid var(--edge); |
| border-radius: 999px; |
| color: var(--ink-soft); |
| background: var(--panel); |
| font-family: var(--mono); |
| font-size: 12px; |
| font-weight: 600; |
| white-space: nowrap; |
| } |
| |
| .oss-toggle { |
| display: inline-flex; |
| align-items: center; |
| gap: 7px; |
| min-height: 34px; |
| padding: 5px 13px; |
| border: 1px dashed var(--edge-strong); |
| border-radius: 8px; |
| color: var(--ink-soft); |
| background: var(--panel); |
| font-family: var(--mono); |
| font-size: 12.5px; |
| font-weight: 700; |
| cursor: pointer; |
| transition: background 140ms ease, color 140ms ease, border-color 140ms ease; |
| } |
| |
| .oss-toggle .glyph { |
| color: var(--primary); |
| font-weight: 800; |
| } |
| |
| .oss-toggle:hover { border-color: var(--primary); color: var(--ink); } |
| |
| .oss-toggle:focus-visible { |
| outline: 2px solid var(--primary); |
| outline-offset: 2px; |
| } |
| |
| .oss-toggle[aria-pressed="true"] { |
| border-style: solid; |
| border-color: var(--primary-dark); |
| color: #fff; |
| background: var(--primary); |
| } |
| |
| .oss-toggle[aria-pressed="true"] .glyph { color: #d7e4d8; } |
| |
| .legend { |
| display: flex; |
| flex-wrap: wrap; |
| align-items: center; |
| gap: 8px 14px; |
| padding: 2px 20px 12px; |
| color: var(--ink-soft); |
| font-size: 14px; |
| } |
| |
| .legend-item { |
| display: inline-flex; |
| align-items: center; |
| gap: 7px; |
| white-space: nowrap; |
| } |
| |
| .legend-button { |
| display: inline-flex; |
| align-items: center; |
| gap: 7px; |
| padding: 3px 9px; |
| border: 1px solid transparent; |
| border-radius: 7px; |
| color: var(--ink-soft); |
| background: transparent; |
| font: inherit; |
| white-space: nowrap; |
| cursor: pointer; |
| transition: border-color 140ms ease, color 140ms ease, background 140ms ease; |
| } |
| |
| .legend-button:hover { |
| border-color: var(--edge-strong); |
| color: var(--ink); |
| background: var(--panel); |
| } |
| |
| .legend-button:focus-visible { |
| outline: 2px solid var(--primary); |
| outline-offset: 2px; |
| } |
| |
| .legend-button[aria-pressed="true"] { |
| border-color: var(--primary); |
| color: var(--primary-dark); |
| background: var(--primary-soft); |
| font-weight: 700; |
| } |
| |
| .legend-sep { |
| width: 1px; |
| height: 16px; |
| background: var(--edge-strong); |
| } |
| |
| .swatch-quad { |
| width: 15px; |
| height: 11px; |
| border: 1px solid rgba(74, 115, 85, 0.35); |
| border-radius: 2px; |
| background: rgba(74, 115, 85, 0.1); |
| } |
| |
| .swatch-pareto { |
| width: 20px; |
| border-top: 2px dotted #6b7263; |
| } |
| |
| .provider-dot { |
| width: 9px; |
| height: 9px; |
| flex: 0 0 auto; |
| border-radius: 50%; |
| background: var(--dot-color); |
| } |
| |
| .provider-logo { |
| display: block; |
| width: 16px; |
| height: 16px; |
| flex: 0 0 auto; |
| } |
| |
| .plot-wrap { |
| position: relative; |
| padding: 0 10px; |
| } |
| |
| #plot { |
| display: block; |
| width: 100%; |
| height: auto; |
| } |
| |
| .tooltip { |
| position: fixed; |
| z-index: 20; |
| max-width: 320px; |
| padding: 12px 14px; |
| border: 1px solid var(--edge-strong); |
| border-radius: 9px; |
| color: var(--ink); |
| background: var(--panel-strong); |
| box-shadow: var(--shadow); |
| font-size: 14px; |
| line-height: 1.5; |
| pointer-events: none; |
| } |
| |
| .tooltip .tt-name { |
| display: flex; |
| align-items: center; |
| gap: 7px; |
| margin-bottom: 2px; |
| font-weight: 750; |
| font-size: 15px; |
| } |
| |
| .tt-chip { |
| padding: 1px 6px; |
| border: 1px solid #bed0c0; |
| border-radius: 5px; |
| color: var(--primary-dark); |
| background: var(--primary-soft); |
| font-family: var(--mono); |
| font-size: 11px; |
| font-weight: 700; |
| } |
| |
| .tooltip .tt-provider { |
| color: var(--ink-soft); |
| margin-bottom: 6px; |
| } |
| |
| .tooltip .tt-row { |
| display: flex; |
| justify-content: space-between; |
| gap: 18px; |
| } |
| |
| .tooltip .tt-row span:first-child { color: var(--ink-faint); } |
| |
| .tooltip .tt-row span:last-child { |
| font-family: var(--mono); |
| font-size: 12.5px; |
| font-weight: 600; |
| } |
| |
| .tooltip .tt-or { |
| margin-top: 6px; |
| padding-top: 6px; |
| border-top: 1px dashed var(--edge); |
| color: var(--ink-faint); |
| font-family: var(--mono); |
| font-size: 12px; |
| word-break: break-all; |
| } |
| |
| .frontier-foot { |
| display: flex; |
| align-items: flex-start; |
| justify-content: space-between; |
| gap: 20px; |
| padding: 12px 20px; |
| border-top: 1px solid var(--edge); |
| color: var(--ink-soft); |
| background: var(--panel); |
| font-size: 12px; |
| } |
| |
| .tracks { margin: 0; flex: 0 0 auto; } |
| |
| .method { |
| margin: 0; |
| max-width: 640px; |
| text-align: right; |
| } |
| |
| .note { |
| margin: 16px auto 0; |
| max-width: 900px; |
| color: var(--ink-faint); |
| font-size: 12px; |
| text-align: center; |
| } |
| |
| .note a { color: var(--primary-dark); font-weight: 650; text-decoration: none; } |
| .note a:hover { text-decoration: underline; } |
| |
| @media (max-width: 860px) { |
| .frontier-head { flex-direction: column; gap: 12px; } |
| .head-controls { padding-top: 0; } |
| .frontier-foot { flex-direction: column; gap: 8px; } |
| .method { text-align: left; max-width: none; } |
| } |
| |
| @media (max-width: 640px) { |
| .page { width: min(100% - 20px, 1180px); padding-top: 26px; } |
| .hero { display: block; } |
| .actions { margin: 20px 0 0 56px; } |
| .tab { font-size: 11px; padding: 6px 4px; } |
| .tab small { display: none; } |
| } |
| |
| @media (prefers-reduced-motion: reduce) { |
| *, *::before, *::after { transition: none !important; } |
| } |
| </style> |
| </head> |
| <body> |
| <main class="page"> |
| <header class="hero"> |
| <div> |
| <div class="brand"> |
| <div class="brand-mark" aria-hidden="true">A</div> |
| <div> |
| <p class="eyebrow">AutoMedBench Lite</p> |
| <h1>Efficiency Frontier</h1> |
| </div> |
| </div> |
| </div> |
| <nav class="actions" aria-label="Related links"> |
| <a class="button" href="index.html">Leaderboard</a> |
| <a class="button" href="https://openrouter.ai/models" target="_blank" rel="noreferrer">OpenRouter</a> |
| </nav> |
| </header> |
|
|
| <section class="frontier" aria-labelledby="chart-title"> |
| <div class="tabbar" role="tablist" aria-label="Efficiency metric"> |
| <button class="tab" role="tab" data-metric="cost" aria-selected="true"><small>Overall vs.</small>Cost per Task</button> |
| <button class="tab" role="tab" data-metric="time" aria-selected="false"><small>Overall vs.</small>Time per Task</button> |
| <button class="tab" role="tab" data-metric="tokens" aria-selected="false"><small>Overall vs.</small>Tokens per Task</button> |
| <button class="tab" role="tab" data-metric="turns" aria-selected="false"><small>Overall vs.</small>Turns per Task</button> |
| </div> |
|
|
| <div class="frontier-head"> |
| <div class="frontier-copy"> |
| <p class="kicker">7-track average</p> |
| <h2 id="chart-title">Overall score vs. Cost per Task</h2> |
| <p class="frontier-subtitle" id="chart-subtitle"></p> |
| </div> |
| <div class="head-controls"> |
| <span class="count-pill" id="count-pill">28 of 28 models</span> |
| <button class="oss-toggle" id="oss-toggle" type="button" aria-pressed="false" title="Show only open-weight models"><span class="glyph"></></span>Open Source Only</button> |
| </div> |
| </div> |
|
|
| <div class="legend" id="legend"> |
| <span class="legend-item"><i class="swatch-quad" aria-hidden="true"></i>Most attractive quadrant</span> |
| <span class="legend-item"><i class="swatch-pareto" aria-hidden="true"></i>Pareto line</span> |
| <span class="legend-sep" aria-hidden="true"></span> |
| </div> |
|
|
| <div class="plot-wrap"> |
| <svg id="plot" viewBox="0 0 1120 560" role="img" aria-label="Scatter plot of overall score against per-task efficiency metric"></svg> |
| <div class="tooltip" id="tooltip" hidden></div> |
| </div> |
|
|
| <footer class="frontier-foot"> |
| <p class="tracks">Classification · Synthesis · Detection · Segmentation · VQA · Report Generation · Enhancement</p> |
| <p class="method" id="method-note"></p> |
| </footer> |
| </section> |
|
|
| </main> |
|
|
| <button class="button theme-fab" id="theme-toggle" type="button" aria-pressed="false">☾ Dark mode</button> |
|
|
| <script> |
| (() => { |
| const MODELS = [ |
| { name: "Claude Opus 4.8", provider: "Anthropic", open: false, overall: 81.94, cost: 2.14, time: 14.5, turns: 18.7, slug: "anthropic/claude-opus-4.8", pin: 5, pout: 25 }, |
| { name: "Claude Opus 4.6", provider: "Anthropic", open: false, overall: 77.23, cost: 6.57, time: 18.2, turns: 18.3, slug: "anthropic/claude-opus-4.6", pin: 5, pout: 25 }, |
| { name: "Gemini 3.1 Pro", provider: "Google", open: false, overall: 75.21, cost: 3.85, time: 18.0, turns: 18.1, slug: "google/gemini-3.1-pro-preview", pin: 2, pout: 12 }, |
| { name: "GPT-5.6 Sol", provider: "OpenAI", open: false, overall: 75.05, cost: 0.95, time: 10.8, turns: 11.7, slug: "openai/gpt-5.6-sol", pin: 5, pout: 30 }, |
| { name: "Claude Sonnet 5", provider: "Anthropic", open: false, overall: 75.04, cost: 2.29, time: 15.1, turns: 30.5, slug: "anthropic/claude-sonnet-5", pin: 2, pout: 10 }, |
| { name: "GPT-5.5", provider: "OpenAI", open: false, overall: 74.14, cost: 1.90, time: 20.5, turns: 15.4, slug: "openai/gpt-5.5", pin: 5, pout: 30 }, |
| { name: "GLM-5", provider: "Z.ai", open: true, overall: 72.86, cost: 1.29, time: 17.8, turns: 31.3, slug: "z-ai/glm-5", pin: 0.95, pout: 2.55 }, |
| { name: "DeepSeek V4 Pro", provider: "DeepSeek", open: true, overall: 72.43, cost: 0.30, time: 18.3, turns: 24.6, slug: "deepseek/deepseek-v4-pro", pin: 0.435, pout: 0.87 }, |
| { name: "Qwen3.5-397B", provider: "Alibaba Cloud", open: true, overall: 69.87, cost: 1.01, time: 20.8, turns: 25.1, slug: "qwen/qwen3.5-397b-a17b", pin: 0.39, pout: 2.34 }, |
| { name: "GPT-5.6 Terra", provider: "OpenAI", open: false, overall: 68.29, cost: 0.45, time: 10.7, turns: 10.2, slug: "openai/gpt-5.6-terra", pin: 1, pout: 6 }, |
| { name: "MiniMax M2.5", provider: "MiniMax", open: true, overall: 66.31, cost: 1.36, time: 20.6, turns: 28.5, slug: "minimax/minimax-m2.5", pin: 0.15, pout: 0.9 }, |
| { name: "GPT-5.4", provider: "OpenAI", open: false, overall: 66.09, cost: 2.36, time: 16.7, turns: 10.4, slug: "openai/gpt-5.4", pin: 2.5, pout: 15 }, |
| { name: "Qwen3.6-27B", provider: "Alibaba Cloud", open: true, overall: 65.29, cost: 0, time: 23.0, turns: 32.0, slug: "qwen/qwen3.6-27b", pin: 0.3, pout: 2, local: true }, |
| { name: "GPT-5.6 Luna", provider: "OpenAI", open: false, overall: 63.98, cost: 0.21, time: 8.9, turns: 13.4, slug: "openai/gpt-5.6-luna", pin: 0.1, pout: 0.6 }, |
| { name: "Qwen3.5-35B-A3B", provider: "Alibaba Cloud", open: true, overall: 57.94, cost: 0, time: 28.4, turns: 30.5, slug: "qwen/qwen3.5-35b-a3b", pin: 0.14, pout: 1, local: true }, |
| { name: "GLM-5.2", provider: "Z.ai", open: true, overall: 56.32, cost: 0.73, time: 15.7, turns: 28.6, slug: "z-ai/glm-5.2", pin: 1.19, pout: 3.74 }, |
| { name: "Nemotron 3.5 Lightning 30B-A3B", provider: "NVIDIA", open: true, overall: 39.65, cost: 0.09, time: 14.3, turns: 42.5, slug: "nvidia/nemotron-3.5-lightning", pin: 0.1, pout: 0.25 }, |
| { name: "Gemma 4 26B-A4B", provider: "Google", open: true, overall: 33.89, cost: 0.04885072, time: 17.3, turns: 14.8, tokens: 384651, slug: "google/gemma-4-26b-a4b-it", pin: 0.12, pout: 0.40 }, |
| { name: "Gemma 4 31B", provider: "Google", open: true, overall: 30.43, cost: 0.01653110, time: 27.9, turns: 8.1, tokens: 154003, slug: "google/gemma-4-31b-it", pin: 0.10, pout: 0.34 }, |
| { name: "GPT-OSS-120B", provider: "OpenAI", open: true, overall: 24.33, cost: 0, time: 4.4, turns: 9.4, slug: "openai/gpt-oss-120b", pin: 0.037, pout: 0.17 }, |
| { name: "Gemma 4 12B", provider: "Google", open: true, overall: 22.43, cost: 0.0239965, time: 24.0, turns: 17.1, tokens: 455099, slug: null, pin: 0.05, pout: 0.15, proxy: true }, |
| { name: "Gemma 4 E4B", provider: "Google", open: true, overall: 21.94, cost: 0.007112, time: 7.4, turns: 6.8, tokens: 110196, slug: null, pin: 0.06, pout: 0.12, proxy: true }, |
| { name: "Qwen3.5-9B", provider: "Alibaba Cloud", open: true, overall: 19.87, cost: 0, time: 19.3, turns: 52.4, slug: "qwen/qwen3.5-9b", pin: 0.1, pout: 0.15, local: true }, |
| { name: "Nemotron Nano 9B v2", provider: "NVIDIA", open: true, overall: 18.68, cost: 0, time: 12.0, turns: 22.8, slug: "nvidia/nemotron-nano-9b-v2:free", pin: 0, pout: 0, local: true }, |
| { name: "Nemotron 3 Nano 30B-A3B", provider: "NVIDIA", open: true, overall: 18.01, cost: 0.01507026, time: 2.5, turns: 16.3, tokens: 249310, slug: "nvidia/nemotron-3-nano-30b-a3b", pin: 0.05, pout: 0.20 }, |
| { name: "GPT-OSS-20B", provider: "OpenAI", open: true, overall: 17.63, cost: 0, time: 2.7, turns: 5.1, slug: "openai/gpt-oss-20b", pin: 0.03, pout: 0.13 }, |
| { name: "Nemotron 3 Nano 4B", provider: "NVIDIA", open: true, overall: 9.06, cost: 0.00673588, time: 5.9, turns: 9.7, tokens: 118117, slug: null, pin: 0.05, pout: 0.20, proxy: true }, |
| { name: "Gemma 4 E2B", provider: "Google", open: true, overall: 9.02, cost: 0.02203914, time: 4.3, turns: 18.9, tokens: 359541, slug: null, pin: 0.06, pout: 0.12, proxy: true }, |
| { name: "Mistral 7B Instruct v0.3", provider: "Mistral AI", open: true, overall: 6.80, cost: 0, time: 1.5, turns: 2.7, tokens: 42507, slug: null, pin: null, pout: null }, |
| { name: "Qwen3.5-4B", provider: "Alibaba Cloud", open: true, overall: 6.05, cost: 0, time: 13.6, turns: 72.6, slug: null, pin: null, pout: null, local: true } |
| ]; |
| |
| const blended = (m) => 0.75 * m.pin + 0.25 * m.pout; |
| |
| |
| for (const m of MODELS) { |
| m.tokens = m.tokens ?? ((m.cost > 0 && m.pin != null) ? (m.cost / blended(m)) * 1e6 : null); |
| } |
| |
| |
| |
| const perTurn = MODELS.filter((m) => m.tokens).map((m) => m.tokens / m.turns).sort((a, b) => a - b); |
| const MED_TOK_PER_TURN = perTurn[Math.floor(perTurn.length / 2)]; |
| for (const m of MODELS) { |
| if (m.tokens == null) m.tokens = MED_TOK_PER_TURN * m.turns; |
| m.costEst = (m.cost === 0 && m.pin) ? (m.tokens / 1e6) * blended(m) : 0; |
| } |
| const effCost = (m) => (m.cost > 0 ? m.cost : m.costEst); |
| |
| |
| const FLAGSHIPS = new Set(); |
| { |
| const bestByProvider = new Map(); |
| for (const m of MODELS) { |
| const cur = bestByProvider.get(m.provider); |
| if (!cur || m.overall > cur.overall) bestByProvider.set(m.provider, m); |
| } |
| for (const m of bestByProvider.values()) FLAGSHIPS.add(m.name); |
| } |
| |
| const modelColors = new Map([ |
| ["claude opus 4.8", "#b98272"], ["claude opus 4.6", "#c0917f"], ["claude sonnet 5", "#c9a08f"], |
| ["gemini 3.1 pro", "#829b84"], ["gpt-5.6 sol", "#788b84"], ["gpt-5.5", "#87968f"], |
| ["gpt-5.6 terra", "#96a29a"], ["gpt-5.4", "#8f9188"], ["gpt-5.6 luna", "#a2aaa3"], |
| ["gpt-oss-120b", "#777f79"], ["glm-5", "#7d95a7"], ["glm-5.2", "#91a5b2"], |
| ["deepseek v4 pro", "#858ca5"], ["qwen3.5-397b", "#b8896c"], ["qwen3.6-27b", "#bd9477"], |
| ["qwen3.5-35b-a3b", "#c2a083"], ["qwen3.5-9b", "#aa8f7c"], ["qwen3.5-4b", "#9e8574"], |
| ["minimax m2.5", "#9d8794"] |
| ]); |
| const familyPalette = [ |
| { match: /anthropic|claude/i, color: "#bd907d" }, |
| { match: /openai|chatgpt|gpt-/i, color: "#858e87" }, |
| { match: /google|gemini/i, color: "#8da28e" }, |
| { match: /z\.ai|glm-/i, color: "#8ca1af" }, |
| { match: /deepseek/i, color: "#8d93a8" }, |
| { match: /alibaba|qwen/i, color: "#b99a7c" }, |
| { match: /minimax/i, color: "#a18d99" } |
| ]; |
| const fallbackColors = ["#9a8fa2", "#899da0", "#a29b84", "#a78e86", "#8f99a6"]; |
| |
| const modelColor = (name, provider) => { |
| const exact = modelColors.get(name.toLowerCase()); |
| if (exact) return exact; |
| const identity = `${name} ${provider}`; |
| const family = familyPalette.find(({ match }) => match.test(identity)); |
| if (family) return family.color; |
| const hash = Array.from(identity).reduce((t, c) => (t * 31 + c.charCodeAt(0)) >>> 0, 0); |
| return fallbackColors[hash % fallbackColors.length]; |
| }; |
| |
| const fmtTok = (v) => v >= 1e6 ? (v / 1e6).toFixed(2) + "M" : Math.round(v / 1e3) + "K"; |
| |
| const METRICS = { |
| cost: { |
| title: "Overall score vs. Cost per Task", |
| sub: "AutoMedBench Lite Overall \u00b7 average cost (USD) per task at OpenRouter list prices", |
| xTitle: "Cost per Task (USD, log scale)", |
| dmin: 0.006, dmax: 9, |
| ticks: [[0.01, "$0*"], [0.02, "$0.02"], [0.05, "$0.05"], [0.1, "$0.10"], [0.2, "$0.20"], [0.5, "$0.50"], [1, "$1"], [2, "$2"], [4, "$4"], [8, "$8"]], |
| value: (m) => (effCost(m) > 0 ? effCost(m) : 0.01), |
| shown: () => true, |
| fmt: (m) => (m.cost > 0 ? "$" + m.cost.toFixed(2) : m.costEst > 0 ? "~$" + m.costEst.toFixed(2) + " (est. API)" : "$0"), |
| note: "Price standard: OpenRouter API rates checked Aug 12, 2026. Exact listing unavailable \u2192 comparable proxy rate." |
| }, |
| time: { |
| title: "Overall score vs. Time per Task", |
| sub: "AutoMedBench Lite Overall \u00b7 average wall-clock minutes per task", |
| xTitle: "Time per Task (minutes, log scale)", |
| dmin: 3, dmax: 34, |
| ticks: [[5, "5 min"], [10, "10"], [15, "15"], [20, "20"], [30, "30 min"]], |
| value: (m) => m.time, |
| shown: () => true, |
| fmt: (m) => m.time.toFixed(1) + " min", |
| note: "Average wall-clock minutes per task." |
| }, |
| tokens: { |
| title: "Overall score vs. Tokens per Task", |
| sub: "AutoMedBench Lite Overall \u00b7 estimated total tokens per task (input + output)", |
| xTitle: "Estimated tokens per Task (log scale)", |
| dmin: 8e4, dmax: 6e6, |
| ticks: [[1e5, "100K"], [2e5, "200K"], [5e5, "500K"], [1e6, "1M"], [2e6, "2M"], [4e6, "4M"]], |
| value: (m) => m.tokens, |
| shown: () => true, |
| fmt: (m) => "~" + fmtTok(m.tokens), |
| note: "Tokens per task use recorded input plus output tokens when available; remaining models use the displayed estimate method." |
| }, |
| turns: { |
| title: "Overall score vs. Turns per Task", |
| sub: "AutoMedBench Lite Overall \u00b7 average conversational turns per task", |
| xTitle: "Turns per Task (log scale)", |
| dmin: 6, dmax: 90, |
| ticks: [[10, "10"], [15, "15"], [20, "20"], [30, "30"], [50, "50"], [70, "70"]], |
| value: (m) => m.turns, |
| shown: () => true, |
| fmt: (m) => m.turns.toFixed(1), |
| note: "Average conversational turns per task." |
| } |
| }; |
| |
| const SVGNS = "http://www.w3.org/2000/svg"; |
| const W = 1120, H = 560, M = { t: 26, r: 26, b: 64, l: 66 }; |
| const PW = W - M.l - M.r, PH = H - M.t - M.b; |
| const YMIN = 0, YMAX = 90, YTICKS = [0, 15, 30, 45, 60, 75, 90]; |
| const QY = 55; |
| |
| const svg = document.getElementById("plot"); |
| const tooltip = document.getElementById("tooltip"); |
| const countPill = document.getElementById("count-pill"); |
| const ossToggle = document.getElementById("oss-toggle"); |
| const tabs = Array.from(document.querySelectorAll(".tab")); |
| const titleEl = document.getElementById("chart-title"); |
| const subEl = document.getElementById("chart-subtitle"); |
| const noteEl = document.getElementById("method-note"); |
| |
| let activeMetric = "cost"; |
| let openOnly = false; |
| let activeProvider = null; |
| |
| const PALETTES = { |
| light: { |
| grid: "rgba(115,122,109,0.2)", base: "rgba(77,87,75,0.36)", tickText: "#9aa093", |
| vgrid: "rgba(115,122,109,0.12)", tickMark: "#c8c0ad", |
| quadFill: "rgba(74,115,85,0.09)", quadLine: "rgba(74,115,85,0.25)", |
| pareto: "#6b7263", label: "#3b4538", pointStroke: "#fffdf8" |
| }, |
| dark: { |
| grid: "rgba(170,176,159,0.16)", base: "rgba(200,205,190,0.35)", tickText: "#8f9484", |
| vgrid: "rgba(170,176,159,0.10)", tickMark: "#4a5040", |
| quadFill: "rgba(130,169,141,0.10)", quadLine: "rgba(130,169,141,0.32)", |
| pareto: "#aab09f", label: "#e9e5d7", pointStroke: "#2b3126" |
| } |
| }; |
| |
| let stored = null; |
| try { stored = localStorage.getItem("amb-theme"); } catch (_) {} |
| let theme = stored |
| || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); |
| const pal = () => PALETTES[theme] || PALETTES.light; |
| |
| const hashParams = new URLSearchParams(location.hash.replace(/^#/, "")); |
| if (METRICS[hashParams.get("metric")]) activeMetric = hashParams.get("metric"); |
| if (hashParams.get("open") === "1") openOnly = true; |
| if (hashParams.get("provider")) activeProvider = hashParams.get("provider"); |
| if (hashParams.get("theme") === "dark" || hashParams.get("theme") === "light") theme = hashParams.get("theme"); |
| |
| const el = (tag, attrs = {}) => { |
| const node = document.createElementNS(SVGNS, tag); |
| for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, v); |
| return node; |
| }; |
| |
| const text = (x, y, content, attrs = {}) => { |
| const node = el("text", { x, y, ...attrs }); |
| node.textContent = content; |
| return node; |
| }; |
| |
| function render() { |
| const cfg = METRICS[activeMetric]; |
| const P = pal(); |
| titleEl.textContent = cfg.title; |
| subEl.textContent = cfg.sub; |
| noteEl.textContent = cfg.note; |
| |
| const shown = MODELS |
| .filter((m) => (openOnly ? m.open : true)) |
| .filter((m) => (activeProvider ? m.provider === activeProvider : true)) |
| .filter(cfg.shown); |
| countPill.textContent = `${shown.length} of ${MODELS.length} models`; |
| |
| const xS = (v) => M.l + ((Math.log10(v) - Math.log10(cfg.dmin)) / (Math.log10(cfg.dmax) - Math.log10(cfg.dmin))) * PW; |
| const yS = (s) => M.t + ((YMAX - s) / (YMAX - YMIN)) * PH; |
| |
| svg.replaceChildren(); |
| |
| const qx = Math.sqrt(cfg.dmin * cfg.dmax); |
| svg.append(el("rect", { |
| x: M.l, y: M.t, width: xS(qx) - M.l, height: yS(QY) - M.t, |
| fill: P.quadFill |
| })); |
| svg.append(el("line", { x1: xS(qx), y1: M.t, x2: xS(qx), y2: yS(QY), stroke: P.quadLine, "stroke-dasharray": "3 4" })); |
| svg.append(el("line", { x1: M.l, y1: yS(QY), x2: xS(qx), y2: yS(QY), stroke: P.quadLine, "stroke-dasharray": "3 4" })); |
| |
| for (const t of YTICKS) { |
| const y = yS(t); |
| svg.append(el("line", { |
| x1: M.l, y1: y, x2: M.l + PW, y2: y, |
| stroke: t === 0 ? P.base : P.grid, |
| "stroke-dasharray": t === 0 ? "none" : "4 4" |
| })); |
| svg.append(text(M.l - 10, y + 4, String(t), { "text-anchor": "end", fill: P.tickText, "font-size": "11.5", "font-family": "SFMono-Regular, Consolas, monospace" })); |
| } |
| |
| for (const [v, label] of cfg.ticks) { |
| const x = xS(v); |
| svg.append(el("line", { x1: x, y1: M.t, x2: x, y2: M.t + PH, stroke: P.vgrid })); |
| svg.append(el("line", { x1: x, y1: M.t + PH, x2: x, y2: M.t + PH + 5, stroke: P.tickMark })); |
| svg.append(text(x, M.t + PH + 20, label, { "text-anchor": "middle", fill: P.tickText, "font-size": "11.5", "font-family": "SFMono-Regular, Consolas, monospace" })); |
| } |
| |
| const yTitle = text(18, M.t + PH / 2, "AutoMedBench Lite Overall Score", { |
| "text-anchor": "middle", fill: P.tickText, "font-size": "11.5", "font-weight": "750", |
| "letter-spacing": "0.08em", transform: `rotate(-90 18 ${M.t + PH / 2})` |
| }); |
| yTitle.style.textTransform = "uppercase"; |
| svg.append(yTitle); |
| const xTitle = text(M.l + PW / 2, H - 10, cfg.xTitle, { |
| "text-anchor": "middle", fill: P.tickText, "font-size": "11.5", "font-weight": "750", "letter-spacing": "0.08em" |
| }); |
| xTitle.style.textTransform = "uppercase"; |
| svg.append(xTitle); |
| |
| const pts = shown.map((m) => ({ |
| m, x: xS(cfg.value(m)), y: yS(m.overall), color: modelColor(m.name, m.provider) |
| })); |
| |
| const sorted = [...pts].sort((a, b) => a.x - b.x || b.m.overall - a.m.overall); |
| const frontier = []; |
| let best = -Infinity; |
| for (const p of sorted) { |
| if (p.m.overall > best) { frontier.push(p); best = p.m.overall; } |
| } |
| if (frontier.length > 1) { |
| const d = frontier.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" "); |
| svg.append(el("path", { d, fill: "none", stroke: P.pareto, "stroke-width": "2", "stroke-dasharray": "1 6", "stroke-linecap": "round", opacity: "0.85" })); |
| } |
| |
| |
| |
| const labels = []; |
| const placedBoxes = []; |
| const byScore = [...pts].sort((a, b) => |
| (FLAGSHIPS.has(b.m.name) - FLAGSHIPS.has(a.m.name)) || b.m.overall - a.m.overall |
| ); |
| for (const p of byScore) { |
| const flagship = FLAGSHIPS.has(p.m.name); |
| const w = 10 + p.m.name.length * 7.4; |
| let anchor = "start"; |
| let lx = p.x + 12; |
| let x0 = lx, x1 = lx + w; |
| if (x1 > W - 4) { |
| if (!flagship) continue; |
| anchor = "end"; |
| lx = p.x - 12; |
| x0 = lx - w; x1 = lx; |
| } |
| const fits = (ly) => |
| ly >= M.t + 10 && ly <= M.t + PH - 2 && |
| !placedBoxes.some((b) => Math.abs(b.y - ly) < 16 && x0 < b.x1 && b.x0 < x1) && |
| !pts.some((q) => q !== p && q.x > x0 - 9 && q.x < x1 + 9 && Math.abs(q.y - ly + 4) < 12); |
| let ly = null; |
| for (const dy of flagship ? [0, -16, 16, -30, 30, -44, 44] : [0]) { |
| if (fits(p.y + 4.5 + dy)) { ly = p.y + 4.5 + dy; break; } |
| } |
| if (ly == null) { |
| if (!flagship) continue; |
| ly = Math.max(M.t + 10, Math.min(M.t + PH - 2, p.y + 4.5)); |
| } |
| placedBoxes.push({ y: ly, x0, x1 }); |
| labels.push({ p, x: lx, y: ly, anchor }); |
| } |
| |
| for (const p of pts) { |
| const g = el("g", { cursor: "pointer" }); |
| g.append(el("circle", { cx: p.x, cy: p.y, r: 12, fill: "transparent" })); |
| g.append(el("circle", { cx: p.x, cy: p.y, r: 7, fill: p.color, stroke: P.pointStroke, "stroke-width": "1.6" })); |
| g.addEventListener("mouseenter", (e) => showTip(e, p.m)); |
| g.addEventListener("mousemove", moveTip); |
| g.addEventListener("mouseleave", hideTip); |
| svg.append(g); |
| } |
| for (const l of labels) { |
| if (Math.abs(l.y - (l.p.y + 4.5)) > 8) { |
| svg.append(el("line", { |
| x1: l.p.x + (l.anchor === "start" ? 8 : -8), y1: l.p.y, |
| x2: l.x + (l.anchor === "start" ? -2 : 2), y2: l.y - 4, |
| stroke: P.tickMark, "stroke-width": "1" |
| })); |
| } |
| svg.append(text(l.x, l.y, l.p.m.name, { |
| "text-anchor": l.anchor, |
| fill: P.label, "font-size": "13.5", "font-weight": "600", "letter-spacing": "-0.01em", |
| stroke: P.pointStroke, "stroke-width": "3", "paint-order": "stroke", "stroke-linejoin": "round" |
| })); |
| } |
| } |
| |
| function tipHtml(m) { |
| const rows = [ |
| ["Overall", m.overall.toFixed(2)], |
| ["Cost / task", m.cost > 0 ? "$" + m.cost.toFixed(2) : m.costEst > 0 ? "~$" + m.costEst.toFixed(2) + " (est. API)" : "$0"], |
| ["Time / task", m.time.toFixed(1) + " min"], |
| ["Tokens / task", "~" + fmtTok(m.tokens)], |
| ["Turns / task", m.turns.toFixed(1)] |
| ].map(([k, v]) => `<div class="tt-row"><span>${k}</span><span>${v}</span></div>`).join(""); |
| const or = (m.pin != null && m.pin > 0) |
| ? `$${m.pin}/M in \u00b7 $${m.pout}/M out${m.proxy ? " \u00b7 proxy" : ""}` |
| : "$0"; |
| return `<div class="tt-name">${m.name}${m.open ? '<span class="tt-chip">open source</span>' : ""}</div> |
| <div class="tt-provider">${m.provider}</div>${rows}<div class="tt-or">${or}</div>`; |
| } |
| |
| function showTip(e, m) { |
| tooltip.innerHTML = tipHtml(m); |
| tooltip.hidden = false; |
| moveTip(e); |
| } |
| function moveTip(e) { |
| const pad = 14; |
| const r = tooltip.getBoundingClientRect(); |
| let x = e.clientX + pad, y = e.clientY + pad; |
| if (x + r.width > window.innerWidth - 8) x = e.clientX - r.width - pad; |
| if (y + r.height > window.innerHeight - 8) y = e.clientY - r.height - pad; |
| tooltip.style.left = x + "px"; |
| tooltip.style.top = y + "px"; |
| } |
| function hideTip() { tooltip.hidden = true; } |
| |
| const LOGOS = {"Anthropic": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+QW50aHJvcGljPC90aXRsZT48cGF0aCBkPSJNMTMuODI3IDMuNTJoMy42MDNMMjQgMjBoLTMuNjAzbC02LjU3LTE2LjQ4em0tNy4yNTggMGgzLjc2N0wxNi45MDYgMjBoLTMuNjc0bC0xLjM0My0zLjQ2MUg1LjAxN2wtMS4zNDQgMy40NkgwTDYuNTcgMy41MjJ6bTQuMTMyIDkuOTU5TDguNDUzIDcuNjg3IDYuMjA1IDEzLjQ4SDEwLjd6Ij48L3BhdGg+PC9zdmc+Cg==", "OpenAI": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+T3BlbkFJPC90aXRsZT48cGF0aCBkPSJNOS4yMDUgOC42NTh2LTIuMjZjMC0uMTkuMDcyLS4zMzMuMjM4LS40MjhsNC41NDMtMi42MTZjLjYxOS0uMzU3IDEuMzU2LS41MjMgMi4xMTctLjUyMyAyLjg1NCAwIDQuNjYyIDIuMjEyIDQuNjYyIDQuNTY2IDAgLjE2NyAwIC4zNTctLjAyNC41NDdsLTQuNzEtMi43NTlhLjc5Ny43OTcgMCAwMC0uODU2IDBsLTUuOTcgMy40NzN6bTEwLjYwOSA4LjhWMTIuMDZjMC0uMzMzLS4xNDMtLjU3LS40MjktLjczN2wtNS45Ny0zLjQ3MyAxLjk1LTEuMTE4YS40MzMuNDMzIDAgMDEuNDc2IDBsNC41NDMgMi42MTdjMS4zMDkuNzYgMi4xODkgMi4zNzggMi4xODkgMy45NDggMCAxLjgwOC0xLjA3IDMuNDczLTIuNzYgNC4xNjN6TTcuODAyIDEyLjcwM2wtMS45NS0xLjE0MmMtLjE2Ny0uMDk1LS4yMzktLjIzOC0uMjM5LS40MjhWNS44OTljMC0yLjU0NSAxLjk1LTQuNDcyIDQuNTkxLTQuNDcyIDEgMCAxLjkyNy4zMzMgMi43MTIuOTI4TDguMjMgNS4wNjdjLS4yODUuMTY2LS40MjguNDA0LS40MjguNzM3djYuODk4ek0xMiAxNS4xMjhsLTIuNzk1LTEuNTd2LTMuMzNMMTIgOC42NThsMi43OTUgMS41N3YzLjMzTDEyIDE1LjEyOHptMS43OTYgNy4yM2MtMSAwLTEuOTI3LS4zMzItMi43MTItLjkyN2w0LjY4Ni0yLjcxMmMuMjg1LS4xNjYuNDI4LS40MDQuNDI4LS43Mzd2LTYuODk4bDEuOTc0IDEuMTQyYy4xNjcuMDk1LjIzOC4yMzguMjM4LjQyOHY1LjIzM2MwIDIuNTQ1LTEuOTc0IDQuNDcyLTQuNjE0IDQuNDcyem0tNS42MzctNS4zMDNsLTQuNTQ0LTIuNjE3Yy0xLjMwOC0uNzYxLTIuMTg4LTIuMzc4LTIuMTg4LTMuOTQ4QTQuNDgyIDQuNDgyIDAgMDE0LjIxIDYuMzI3djUuNDIzYzAgLjMzMy4xNDMuNTcxLjQyOC43MzhsNS45NDcgMy40NDktMS45NSAxLjExOGEuNDMyLjQzMiAwIDAxLS40NzYgMHptLS4yNjIgMy45Yy0yLjY4OCAwLTQuNjYyLTIuMDIxLTQuNjYyLTQuNTE5IDAtLjE5LjAyNC0uMzguMDQ3LS41N2w0LjY4NiAyLjcxYy4yODYuMTY3LjU3MS4xNjcuODU2IDBsNS45Ny0zLjQ0OHYyLjI2YzAgLjE5LS4wNy4zMzMtLjIzNy40MjhsLTQuNTQzIDIuNjE2Yy0uNjE5LjM1Ny0xLjM1Ni41MjMtMi4xMTcuNTIzem01Ljg5OSAyLjgzYTUuOTQ3IDUuOTQ3IDAgMDA1LjgyNy00Ljc1NkMyMi4yODcgMTguMzM5IDI0IDE1Ljg0IDI0IDEzLjI5NmMwLTEuNjY1LS43MTMtMy4yODItMS45OTgtNC40NDguMTE5LS41LjE5LS45OTkuMTktMS40OTggMC0zLjQwMS0yLjc1OS01Ljk0Ny01Ljk0Ni01Ljk0Ny0uNjQyIDAtMS4yNi4wOTUtMS44OC4zMUE1Ljk2MiA1Ljk2MiAwIDAwMTAuMjA1IDBhNS45NDcgNS45NDcgMCAwMC01LjgyNyA0Ljc1N0MxLjcxMyA1LjQ0NyAwIDcuOTQ1IDAgMTAuNDljMCAxLjY2Ni43MTMgMy4yODMgMS45OTggNC40NDgtLjExOS41LS4xOSAxLS4xOSAxLjQ5OSAwIDMuNDAxIDIuNzU5IDUuOTQ2IDUuOTQ2IDUuOTQ2LjY0MiAwIDEuMjYtLjA5NSAxLjg4LS4zMDlhNS45NiA1Ljk2IDAgMDA0LjE2MiAxLjcxM3oiPjwvcGF0aD48L3N2Zz4K", "Google": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2VtaW5pPC90aXRsZT48cGF0aCBkPSJNMjAuNjE2IDEwLjgzNWExNC4xNDcgMTQuMTQ3IDAgMDEtNC40NS0zLjAwMSAxNC4xMTEgMTQuMTExIDAgMDEtMy42NzgtNi40NTIuNTAzLjUwMyAwIDAwLS45NzUgMCAxNC4xMzQgMTQuMTM0IDAgMDEtMy42NzkgNi40NTIgMTQuMTU1IDE0LjE1NSAwIDAxLTQuNDUgMy4wMDFjLS42NS4yOC0xLjMxOC41MDUtMi4wMDIuNjc4YS41MDIuNTAyIDAgMDAwIC45NzVjLjY4NC4xNzIgMS4zNS4zOTcgMi4wMDIuNjc3YTE0LjE0NyAxNC4xNDcgMCAwMTQuNDUgMy4wMDEgMTQuMTEyIDE0LjExMiAwIDAxMy42NzkgNi40NTMuNTAyLjUwMiAwIDAwLjk3NSAwYy4xNzItLjY4NS4zOTctMS4zNTEuNjc3LTIuMDAzYTE0LjE0NSAxNC4xNDUgMCAwMTMuMDAxLTQuNDUgMTQuMTEzIDE0LjExMyAwIDAxNi40NTMtMy42NzguNTAzLjUwMyAwIDAwMC0uOTc1IDEzLjI0NSAxMy4yNDUgMCAwMS0yLjAwMy0uNjc4eiI+PC9wYXRoPjwvc3ZnPgo=", "Z.ai": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+Wi5haTwvdGl0bGU+PHBhdGggZD0iTTEyLjEwNSAyTDkuOTI3IDQuOTUzSC42NTNMMi44MyAyaDkuMjc2ek0yMy4yNTQgMTkuMDQ4TDIxLjA3OCAyMmgtOS4yNDJsMi4xNzQtMi45NTJoOS4yNDR6TTI0IDJMOS4yNjQgMjJIMEwxNC43MzYgMkgyNHoiPjwvcGF0aD48L3N2Zz4K", "DeepSeek": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+RGVlcFNlZWs8L3RpdGxlPjxwYXRoIGQ9Ik0yMy43NDggNC42NTFjLS4yNTQtLjEyNC0uMzY0LjExMy0uNTEyLjIzMy0uMDUxLjA0LS4wOTQuMDktLjEzNy4xMzctLjM3Mi4zOTctLjgwNi42NTctMS4zNzMuNjI2LS44MjktLjA0Ni0xLjUzNy4yMTQtMi4xNjMuODQ4LS4xMzMtLjc4Mi0uNTc1LTEuMjQ4LTEuMjQ3LTEuNTQ4LS4zNTItLjE1NS0uNzA4LS4zMTEtLjk1NS0uNjUtLjE3Mi0uMjQtLjIxOS0uNTA5LS4zMDUtLjc3NC0uMDU1LS4xNi0uMTEtLjMyMy0uMjkzLS4zNS0uMi0uMDMxLS4yNzguMTM2LS4zNTYuMjc2LS4zMTMuNTcyLS40MzQgMS4yMDItLjQyMiAxLjg0LjAyNyAxLjQzNi42MzMgMi41OCAxLjgzOCAzLjM5My4xMzcuMDk0LjE3Mi4xODcuMTI5LjMyMy0uMDgyLjI4LS4xOC41NTMtLjI2Ni44MzMtLjA1NS4xNzktLjEzNy4yMTgtLjMyOC4xNGE1LjUgNS41IDAgMCAxLTEuNzM3LTEuMTc5Yy0uODU3LS44MjgtMS42MzEtMS43NDMtMi41OTctMi40NmExMiAxMiAwIDAgMC0uNjg5LS40N2MtLjk4NS0uOTU3LjEzLTEuNzQzLjM4Ny0xLjgzNi4yNy0uMDk4LjA5NC0uNDMzLS43NzgtLjQyOC0uODcyLjAwMy0xLjY3LjI5NS0yLjY4Ny42ODVhMyAzIDAgMCAxLS40NjUuMTM2IDkuNiA5LjYgMCAwIDAtMi44ODMtLjEwMWMtMS44ODUuMjEtMy4zOSAxLjEtNC40OTcgMi42MjJDLjA4MiA4Ljc3Ni0uMjMxIDEwLjg1NC4xNTIgMTMuMDJjLjQwMyAyLjI4NCAxLjU2OCA0LjE3NSAzLjM2IDUuNjUzIDEuODU3IDEuNTMzIDMuOTk3IDIuMjg0IDYuNDM4IDIuMTQgMS40ODItLjA4NSAzLjEzMi0uMjg0IDQuOTk0LTEuODYuNDcuMjM0Ljk2Mi4zMjggMS43OC4zOTguNjI5LjA1OCAxLjIzNS0uMDMxIDEuNzA1LS4xMjkuNzM1LS4xNTUuNjg0LS44MzYuNDE4LS45NjEtMi4xNTUtMS4wMDQtMS42ODItLjU5NS0yLjExMi0uOTI2IDEuMDk1LTEuMjk1IDIuNzY4LTMuNTk4IDMuMjg0LTYuNzMzLjA1LS4zNDYuMTE1LS44MzQuMTA4LTEuMTE0LS4wMDQtLjE3MS4wMzUtLjIzOC4yMy0uMjU3YTQuMiA0LjIgMCAwIDAgMS41NDUtLjQ3NWMxLjM5Ny0uNzYzIDEuOTYtMi4wMTYgMi4wOTMtMy41MTcuMDItLjIzLS4wMDQtLjQ2Ny0uMjQ3LS41ODhNMTEuNTggMTguMTY4Yy0yLjA4OC0xLjY0Mi0zLjEwMS0yLjE4My0zLjUyLTIuMTYtLjM5LjAyNC0uMzIuNDcyLS4yMzQuNzYzLjA5LjI4OC4yMDcuNDg3LjM3MS43NC4xMTQuMTY3LjE5Mi40MTYtLjExMy42MDMtLjY3My40MTYtMS44NDItLjE0LTEuODk3LS4xNjgtMS4zNjEtLjgwMS0yLjUtMS44Ni0zLjMwMS0zLjMwNi0uNzc1LTEuMzkzLTEuMjI1LTIuODg4LTEuMjk5LTQuNDgyLS4wMi0uMzg1LjA5NC0uNTIyLjQ3Ny0uNTkyYTQuNyA0LjcgMCAwIDEgMS41My0uMDM4YzIuMTMxLjMxMSAzLjk0NiAxLjI2NCA1LjQ2NyAyLjc3NC44NjguODYgMS41MjUgMS44ODcgMi4yMDIgMi44OS43MiAxLjA2NiAxLjQ5NCAyLjA4MiAyLjQ4IDIuOTE1LjM0OC4yOTEuNjI2LjUxMy44OTIuNjc3LS44MDIuMDktMi4xNC4xMDktMy4wNTUtLjYxNXptMS4wMDEtNi40NGEuMzA2LjMwNiAwIDAgMSAuNDE1LS4yODcuMy4zIDAgMCAxIC4xMTMuMDc0LjMuMyAwIDAgMSAuMDg2LjIxNGMwIC4xNy0uMTM2LjMwNy0uMzA4LjMwN2EuMzAzLjMwMyAwIDAgMS0uMzA2LS4zMDdtMy4xMSAxLjU5NmMtLjIuMDgxLS40LjE1MS0uNTkxLjE2YTEuMjUgMS4yNSAwIDAgMS0uNzk4LS4yNTRjLS4yNzQtLjIzLS40Ny0uMzU4LS41NTEtLjc1OGExLjcgMS43IDAgMCAxIC4wMTUtLjU4OGMuMDctLjMyNy0uMDA3LS41MzctLjIzOC0uNzI3LS4xODgtLjE1Ni0uNDI2LS4xOTktLjY4OS0uMTk5YS42LjYgMCAwIDEtLjI1NC0uMDc4LjI1My4yNTMgMCAwIDEtLjExNC0uMzU4IDEgMSAwIDAgMSAuMTkyLS4yMWMuMzU2LS4yMDIuNzY3LS4xMzYgMS4xNDYuMDE2LjM1Mi4xNDQuNjE4LjQwOCAxLjAwMS43ODIuMzkyLjQ1MS40NjIuNTc2LjY4NS45MTUuMTc2LjI2NC4zMzYuNTM2LjQ0Ni44NDguMDY2LjE5NC0uMDIuMzUzLS4yNS40NSIvPjwvc3ZnPgo=", "Alibaba Cloud": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+UXdlbjwvdGl0bGU+PHBhdGggZD0iTTEyLjYwNCAxLjM0Yy4zOTMuNjkuNzg0IDEuMzgyIDEuMTc0IDIuMDc1YS4xOC4xOCAwIDAwLjE1Ny4wOTFoNS41NTJjLjE3NCAwIC4zMjIuMTEuNDQ2LjMyN2wxLjQ1NCAyLjU3Yy4xOS4zMzcuMjQuNDc4LjAyNC44MzctLjI2LjQzLS41MTMuODY0LS43NiAxLjNsLS4zNjcuNjU4Yy0uMTA2LjE5Ni0uMjIzLjI4LS4wNC41MTJsMi42NTIgNC42MzdjLjE3Mi4zMDEuMTExLjQ5NC0uMDQzLjc3LS40MzcuNzg1LS44ODIgMS41NjQtMS4zMzUgMi4zNC0uMTU5LjI3Mi0uMzUyLjM3NS0uNjguMzctLjc3Ny0uMDE2LTEuNTUyLS4wMS0yLjMyNy4wMTZhLjA5OS4wOTkgMCAwMC0uMDgxLjA1IDU3NS4wOTcgNTc1LjA5NyAwIDAxLTIuNzA1IDQuNzRjLS4xNjkuMjkzLS4zOC4zNjMtLjcyNS4zNjQtLjk5Ny4wMDMtMi4wMDIuMDA0LTMuMDE3LjAwMmEuNTM3LjUzNyAwIDAxLS40NjUtLjI3MWwtMS4zMzUtMi4zMjNhLjA5LjA5IDAgMDAtLjA4My0uMDQ5SDQuOTgyYy0uMjg1LjAzLS41NTMtLjAwMS0uODA1LS4wOTJsLTEuNjAzLTIuNzdhLjU0My41NDMgMCAwMS0uMDAyLS41NGwxLjIwNy0yLjEyYS4xOTguMTk4IDAgMDAwLS4xOTcgNTUwLjk1MSA1NTAuOTUxIDAgMDEtMS44NzUtMy4yNzJsLS43OS0xLjM5NWMtLjE2LS4zMS0uMTczLS40OTYuMDk1LS45NjUuNDY1LS44MTMuOTI3LTEuNjI1IDEuMzg3LTIuNDM2LjEzMi0uMjM0LjMwNC0uMzM0LjU4NC0uMzM1YTMzOC4zIDMzOC4zIDAgMDEyLjU4OS0uMDAxLjEyNC4xMjQgMCAwMC4xMDctLjA2M2wyLjgwNi00Ljg5NWEuNDg4LjQ4OCAwIDAxLjQyMi0uMjQ2Yy41MjQtLjAwMSAxLjA1MyAwIDEuNTgzLS4wMDZMMTEuNzA0IDFjLjM0MS0uMDAzLjcyNC4wMzIuOS4zNHptLTMuNDMyLjQwM2EuMDYuMDYgMCAwMC0uMDUyLjAzTDYuMjU0IDYuNzg4YS4xNTcuMTU3IDAgMDEtLjEzNS4wNzhIMy4yNTNjLS4wNTYgMC0uMDcuMDI1LS4wNDEuMDc0bDUuODEgMTAuMTU2Yy4wMjUuMDQyLjAxMy4wNjItLjAzNC4wNjNsLTIuNzk1LjAxNWEuMjE4LjIxOCAwIDAwLS4yLjExNmwtMS4zMiAyLjMxYy0uMDQ0LjA3OC0uMDIxLjExOC4wNjguMTE4bDUuNzE2LjAwOGMuMDQ2IDAgLjA4LjAyLjEwNC4wNjFsMS40MDMgMi40NTRjLjA0Ni4wODEuMDkyLjA4Mi4xMzkgMGw1LjAwNi04Ljc2Ljc4My0xLjM4MmEuMDU1LjA1NSAwIDAxLjA5NiAwbDEuNDI0IDIuNTNhLjEyMi4xMjIgMCAwMC4xMDcuMDYybDIuNzYzLS4wMmEuMDQuMDQgMCAwMC4wMzUtLjAyLjA0MS4wNDEgMCAwMDAtLjA0bC0yLjktNS4wODZhLjEwOC4xMDggMCAwMTAtLjExM2wuMjkzLS41MDcgMS4xMi0xLjk3N2MuMDI0LS4wNDEuMDEyLS4wNjItLjAzNS0uMDYySDkuMmMtLjA1OSAwLS4wNzMtLjAyNi0uMDQzLS4wNzdsMS40MzQtMi41MDVhLjEwNy4xMDcgMCAwMDAtLjExNEw5LjIyNSAxLjc3NGEuMDYuMDYgMCAwMC0uMDUzLS4wMzF6bTYuMjkgOC4wMmMuMDQ2IDAgLjA1OC4wMi4wMzQuMDZsLS44MzIgMS40NjUtMi42MTMgNC41ODVhLjA1Ni4wNTYgMCAwMS0uMDUuMDI5LjA1OC4wNTggMCAwMS0uMDUtLjAyOUw4LjQ5OCA5Ljg0MWMtLjAyLS4wMzQtLjAxLS4wNTIuMDI4LS4wNTRsLjIxNi0uMDEyIDYuNzIyLS4wMTJ6Ij48L3BhdGg+PC9zdmc+Cg==", "MiniMax": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+TWluaW1heDwvdGl0bGU+PHBhdGggZD0iTTE2LjI3OCAyYzEuMTU2IDAgMi4wOTMuOTI3IDIuMDkzIDIuMDd2MTIuNTAxYS43NC43NCAwIDAwLjc0NC43MDkuNzQuNzQgMCAwMC43NDMtLjcwOVY5LjA5OWEyLjA2IDIuMDYgMCAwMTIuMDcxLTIuMDQ5QTIuMDYgMi4wNiAwIDAxMjQgOS4xdjYuNTYxYS42NDkuNjQ5IDAgMDEtLjY1Mi42NDUuNjQ5LjY0OSAwIDAxLS42NTMtLjY0NVY5LjFhLjc2Mi43NjIgMCAwMC0uNzY2LS43NTguNzYyLjc2MiAwIDAwLS43NjYuNzU4djcuNDcyYTIuMDM3IDIuMDM3IDAgMDEtMi4wNDggMi4wMjYgMi4wMzcgMi4wMzcgMCAwMS0yLjA0OC0yLjAyNnYtMTIuNWEuNzg1Ljc4NSAwIDAwLS43ODgtLjc1My43ODUuNzg1IDAgMDAtLjc4OS43NTJsLS4wMDEgMTUuOTA0QTIuMDM3IDIuMDM3IDAgMDExMy40NDEgMjJhMi4wMzcgMi4wMzcgMCAwMS0yLjA0OC0yLjAyNlYxOC4wNGMwLS4zNTYuMjkyLS42NDUuNjUyLS42NDUuMzYgMCAuNjUyLjI4OS42NTIuNjQ1djEuOTM0YzAgLjI2My4xNDIuNTA2LjM3Mi42MzguMjMuMTMxLjUxNC4xMzEuNzQ0IDBhLjczNC43MzQgMCAwMC4zNzItLjYzOFY0LjA3YzAtMS4xNDMuOTM3LTIuMDcgMi4wOTMtMi4wN3ptLTUuNjc0IDBjMS4xNTYgMCAyLjA5My45MjcgMi4wOTMgMi4wN3YxMS41MjNhLjY0OC42NDggMCAwMS0uNjUyLjY0NS42NDguNjQ4IDAgMDEtLjY1Mi0uNjQ1VjQuMDdhLjc4NS43ODUgMCAwMC0uNzg5LS43OC43ODUuNzg1IDAgMDAtLjc4OS43OHYxNC4wMTNhMi4wNiAyLjA2IDAgMDEtMi4wNyAyLjA0OCAyLjA2IDIuMDYgMCAwMS0yLjA3MS0yLjA0OFY5LjFhLjc2Mi43NjIgMCAwMC0uNzY2LS43NTguNzYyLjc2MiAwIDAwLS43NjYuNzU4djMuOGEyLjA2IDIuMDYgMCAwMS0yLjA3MSAyLjA0OUEyLjA2IDIuMDYgMCAwMTAgMTIuOXYtMS4zNzhjMC0uMzU3LjI5Mi0uNjQ2LjY1Mi0uNjQ2LjM2IDAgLjY1My4yOS42NTMuNjQ2VjEyLjljMCAuNDE4LjM0My43NTcuNzY2Ljc1N3MuNzY2LS4zMzkuNzY2LS43NTdWOS4wOTlhMi4wNiAyLjA2IDAgMDEyLjA3LTIuMDQ4IDIuMDYgMi4wNiAwIDAxMi4wNzEgMi4wNDh2OC45ODRjMCAuNDE5LjM0My43NTguNzY3Ljc1OC40MjMgMCAuNzY2LS4zMzkuNzY2LS43NThWNC4wN2MwLTEuMTQzLjkzNy0yLjA3IDIuMDkzLTIuMDd6Ij48L3BhdGg+PC9zdmc+Cg==", "NVIDIA": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjNzM3YTZkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iMWVtIiBzdHlsZT0iZmxleDpub25lO2xpbmUtaGVpZ2h0OjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+TnZpZGlhPC90aXRsZT48cGF0aCBkPSJNMTAuMjEyIDguOTc2VjcuNjJjLjEyNy0uMDEuMjU2LS4wMTcuMzg4LS4wMjEgMy41OTYtLjExNyA1Ljk1NyAzLjE4NCA1Ljk1NyAzLjE4NHMtMi41NDggMy42NDctNS4yODIgMy42NDdhMy4yMjcgMy4yMjcgMCAwMS0xLjA2My0uMTc1di00LjEwOWMxLjQuMTc0IDEuNjgxLjgxMiAyLjUyMyAyLjI1OGwxLjg3My0xLjYyN2E0LjkwNSA0LjkwNSAwIDAwLTMuNjctMS44NDYgNi41OTQgNi41OTQgMCAwMC0uNzI5LjA0NG0wLTQuNDc2djIuMDI1Yy4xMy0uMDEuMjU5LS4wMTkuMzg4LS4wMjQgNS4wMDItLjE3NCA4LjI2MSA0LjIyNiA4LjI2MSA0LjIyNnMtMy43NDMgNC42OS03LjY0MyA0LjY5Yy0uMzM4IDAtLjY3NS0uMDMxLTEuMDA3LS4wOTJ2MS4yNWMuMjc4LjAzOC41NTguMDU3LjgzOC4wNTcgMy42MjkgMCA2LjI1My0xLjkxIDguNzk0LTQuMTY5LjQyMS4zNDcgMi4xNDYgMS4xOTMgMi41MDEgMS41NjQtMi40MTYgMi4wODMtOC4wNDggMy43NjMtMTEuMjQgMy43NjMtLjMwOCAwLS42MDMtLjAyLS44OTQtLjA0OFYxOS41SDI0di0xNUgxMC4yMXptMCA5Ljc1NnYxLjA2OGMtMy4zNTYtLjYxNi00LjI4Ny00LjIxLTQuMjg3LTQuMjFhNy4xNzMgNy4xNzMgMCAwMTQuMjg3LTIuMTM4djEuMTcyaC0uMDA1YTMuMTgyIDMuMTgyIDAgMDAtMi41MDIgMS4xNzhzLjYxNSAyLjI3NiAyLjUwNyAyLjkzMW0tNS45NjEtMy4zYzEuNDM2LTEuOTM1IDMuNjA0LTMuMTQ4IDUuOTYxLTMuMzM2VjYuNTIzQzUuODEgNi44ODcgMiAxMC43MjMgMiAxMC43MjNzMi4xNTggNi40MjcgOC4yMSA3LjAxNXYtMS4xNjZDNS43NyAxNiA0LjI1IDEwLjk1OCA0LjI1IDEwLjk1OGgtLjAwMnoiPjwvcGF0aD48L3N2Zz4K"}; |
| |
| LOGOS.Google = "assets/google.svg"; |
| const legend = document.getElementById("legend"); |
| const providerButtons = []; |
| for (const provider of ["Anthropic", "OpenAI", "Google", "Z.ai", "DeepSeek", "Alibaba Cloud", "MiniMax", "NVIDIA"]) { |
| const sample = MODELS.find((m) => m.provider === provider); |
| const btn = document.createElement("button"); |
| btn.type = "button"; |
| btn.className = "legend-button"; |
| btn.dataset.provider = provider; |
| btn.setAttribute("aria-pressed", "false"); |
| btn.title = `Show only ${provider} models`; |
| const dot = document.createElement("i"); |
| dot.className = "provider-dot"; |
| dot.style.setProperty("--dot-color", modelColor(sample.name, sample.provider)); |
| btn.append(dot, document.createTextNode(provider)); |
| if (LOGOS[provider]) { |
| const logo = document.createElement("img"); |
| logo.className = "provider-logo"; |
| logo.src = LOGOS[provider]; |
| logo.alt = ""; |
| btn.append(logo); |
| } |
| btn.addEventListener("click", () => { |
| activeProvider = activeProvider === provider ? null : provider; |
| syncControls(); |
| render(); |
| }); |
| legend.append(btn); |
| providerButtons.push(btn); |
| } |
| |
| function syncControls() { |
| tabs.forEach((t) => t.setAttribute("aria-selected", String(t.dataset.metric === activeMetric))); |
| ossToggle.setAttribute("aria-pressed", String(openOnly)); |
| providerButtons.forEach((b) => b.setAttribute("aria-pressed", String(b.dataset.provider === activeProvider))); |
| themeToggle.setAttribute("aria-pressed", String(theme === "dark")); |
| themeToggle.innerHTML = theme === "dark" ? "☼ Light mode" : "☾ Dark mode"; |
| document.documentElement.dataset.theme = theme; |
| } |
| |
| tabs.forEach((tab) => { |
| tab.addEventListener("click", () => { |
| activeMetric = tab.dataset.metric; |
| syncControls(); |
| render(); |
| }); |
| }); |
| |
| ossToggle.addEventListener("click", () => { |
| openOnly = !openOnly; |
| syncControls(); |
| render(); |
| }); |
| |
| const themeToggle = document.getElementById("theme-toggle"); |
| themeToggle.addEventListener("click", () => { |
| theme = theme === "dark" ? "light" : "dark"; |
| try { localStorage.setItem("amb-theme", theme); } catch (_) {} |
| syncControls(); |
| render(); |
| }); |
| |
| syncControls(); |
| render(); |
| })(); |
| </script> |
| </body> |
| </html> |
|
|