switchboardllm commited on
Commit
99c2f3d
·
verified ·
1 Parent(s): 4007720

Add evaluation page, plain-language how it works, FAQ and raw outputs

Browse files
Files changed (6) hide show
  1. app.js +23 -0
  2. data/outputs-v0.1.jsonl +0 -0
  3. data/outputs-v0.jsonl +0 -0
  4. evaluation.html +137 -0
  5. index.html +194 -169
  6. style.css +89 -0
app.js CHANGED
@@ -116,6 +116,28 @@ function renderResults(r) {
116
  "Small or mid counts as good enough when that answer scored 4 or more. Method and code are in the model card.";
117
  }
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  /* ---------- Demo placeholder types real sample prompts ---------- */
120
 
121
  function startTyper(lines) {
@@ -344,6 +366,7 @@ async function init() {
344
  `${prettyModel(tiers[t].model)}, $${money(tiers[t].price_out)} per 1M output tokens`;
345
  }
346
  renderResults(results.value);
 
347
  } else {
348
  $("results-intro").textContent = "The evaluation results could not be loaded. See the model card on Hugging Face.";
349
  console.error(results.reason);
 
116
  "Small or mid counts as good enough when that answer scored 4 or more. Method and code are in the model card.";
117
  }
118
 
119
+ /* ---------- Plain-language numbers: the two-bills example and FAQ figures ---------- */
120
+
121
+ const BILL_ANSWERS = 1000;
122
+ const BILL_TOKENS_EACH = 500;
123
+
124
+ function renderPlainNumbers(r) {
125
+ const fills = { cost_saved: pct(r.cost_saved), under_routed: pct(r.under_routed) };
126
+ document.querySelectorAll("[data-fill]").forEach((el) => {
127
+ if (fills[el.dataset.fill]) el.textContent = fills[el.dataset.fill];
128
+ });
129
+ const millions = (BILL_ANSWERS * BILL_TOKENS_EACH) / 1e6;
130
+ const allFrontier = millions * r.tiers.frontier.price_out;
131
+ const routed = allFrontier * (1 - r.cost_saved);
132
+ const usd = (x) => `$${x.toFixed(2)}`;
133
+ $("bill-all").textContent = usd(allFrontier);
134
+ $("bill-routed").textContent = usd(routed);
135
+ $("bill-routed-bar").style.width = `${(1 - r.cost_saved) * 100}%`;
136
+ $("bill-note").textContent =
137
+ `Switchboard ${r.version} cut the bill by ${pct(r.cost_saved)} on ${r.n.toLocaleString()} test questions. ` +
138
+ `It also sent ${pct(r.under_routed)} of them to a cheaper AI than they needed.`;
139
+ }
140
+
141
  /* ---------- Demo placeholder types real sample prompts ---------- */
142
 
143
  function startTyper(lines) {
 
366
  `${prettyModel(tiers[t].model)}, $${money(tiers[t].price_out)} per 1M output tokens`;
367
  }
368
  renderResults(results.value);
369
+ renderPlainNumbers(results.value);
370
  } else {
371
  $("results-intro").textContent = "The evaluation results could not be loaded. See the model card on Hugging Face.";
372
  console.error(results.reason);
data/outputs-v0.1.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
data/outputs-v0.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
evaluation.html ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Evaluation · Switchboard</title>
7
+ <meta name="description" content="How Switchboard was measured: routing quality on 10,000 held-out prompts, how each build compares to the full model, speed, and the raw outputs.">
8
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Ccircle cx=%225%22 cy=%2212%22 r=%223%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%225%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%2212%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%2219%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3C/svg%3E">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="style.css?v=9">
13
+ </head>
14
+ <body>
15
+ <header class="top">
16
+ <div class="top-inner">
17
+ <a class="brand" href="index.html">
18
+ <svg class="brand-mark" viewBox="0 0 24 24" aria-hidden="true"><circle cx="5" cy="12" r="3"/><circle cx="19" cy="5" r="2.4"/><circle cx="19" cy="12" r="2.4"/><circle cx="19" cy="19" r="2.4"/><path d="M8 12c4 0 5-7 8.6-7"/></svg>
19
+ Switchboard
20
+ </a>
21
+ <nav class="nav">
22
+ <a href="index.html#demo">Demo</a>
23
+ <a href="index.html#how">How it works</a>
24
+ <a href="evaluation.html" aria-current="page">Evaluation</a>
25
+ <a href="index.html#faq">FAQ</a>
26
+ <a href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Model card</a>
27
+ </nav>
28
+ <a class="btn btn-primary btn-sm" href="index.html#demo">Try the router</a>
29
+ </div>
30
+ </header>
31
+
32
+ <main>
33
+ <section class="hero hero-sm">
34
+ <div class="wrap narrow center">
35
+ <p class="badge"><span class="dot" aria-hidden="true"></span>Updated October 12, 2026</p>
36
+ <h1>What we measured so far</h1>
37
+ <p class="lede">Every version of Switchboard is tested on the same 10,000 prompts it never saw during training. We publish the numbers that look bad too, and you can download every answer below.</p>
38
+ </div>
39
+ </section>
40
+
41
+ <section class="band white">
42
+ <div class="wrap">
43
+ <h2 class="center">Routing quality</h2>
44
+ <p class="section-lede center">10,000 held-out prompts in their natural mix: 6,931 easy, 1,716 medium and 1,353 that need a frontier model. Full-precision models, greedy decoding.</p>
45
+ <div class="metrics">
46
+ <article class="metric"><header><h3>Cost saved</h3><span class="better">Higher is better</span></header><p class="plain">How much cheaper the bill is than sending every prompt to Claude Opus 5.5.</p><div class="ebars"><div class="ebar ours"><span class="ebar-label">Switchboard v0.1</span><span class="ebar-track"><span class="ebar-fill" style="width:64.5%"></span></span><span class="ebar-value">64.5%</span></div>
47
+ <div class="ebar"><span class="ebar-label">Switchboard v0</span><span class="ebar-track"><span class="ebar-fill" style="width:67.8%"></span></span><span class="ebar-value">67.8%</span></div>
48
+ <div class="ebar"><span class="ebar-label">Perfect router</span><span class="ebar-track"><span class="ebar-fill" style="width:76.2%"></span></span><span class="ebar-value">76.2%</span></div>
49
+ <div class="ebar"><span class="ebar-label">Always small</span><span class="ebar-track"><span class="ebar-fill" style="width:97.5%"></span></span><span class="ebar-value">97.5%</span></div></div></article>
50
+ <article class="metric"><header><h3>Prompts sent too low</h3><span class="better">Lower is better</span></header><p class="plain">Prompts that needed the frontier model but went to a cheaper one. This is the mistake you'd notice.</p><div class="ebars"><div class="ebar ours"><span class="ebar-label">Switchboard v0.1</span><span class="ebar-track"><span class="ebar-fill" style="width:27.6%"></span></span><span class="ebar-value">5.5%</span></div>
51
+ <div class="ebar"><span class="ebar-label">Switchboard v0</span><span class="ebar-track"><span class="ebar-fill" style="width:35.5%"></span></span><span class="ebar-value">7.1%</span></div>
52
+ <div class="ebar"><span class="ebar-label">Random router, same budget</span><span class="ebar-track"><span class="ebar-fill" style="width:49.9%"></span></span><span class="ebar-value">10.0%</span></div>
53
+ <div class="ebar"><span class="ebar-label">Always small</span><span class="ebar-track"><span class="ebar-fill" style="width:67.7%"></span></span><span class="ebar-value">13.5%</span></div></div></article>
54
+ <article class="metric"><header><h3>Hard prompts caught</h3><span class="better">Higher is better</span></header><p class="plain">Of the prompts that needed the frontier model, the share Switchboard sent there.</p><div class="ebars"><div class="ebar ours"><span class="ebar-label">Switchboard v0.1</span><span class="ebar-track"><span class="ebar-fill" style="width:59.1%"></span></span><span class="ebar-value">59.1%</span></div>
55
+ <div class="ebar"><span class="ebar-label">Switchboard v0</span><span class="ebar-track"><span class="ebar-fill" style="width:47.5%"></span></span><span class="ebar-value">47.5%</span></div>
56
+ <div class="ebar"><span class="ebar-label">Random router, same budget</span><span class="ebar-track"><span class="ebar-fill" style="width:26.2%"></span></span><span class="ebar-value">26.2%</span></div></div></article>
57
+ <article class="metric"><header><h3>Ranking hard above easy (AUC)</h3><span class="better">Higher is better</span></header><p class="plain">How well the model orders prompts by difficulty, whatever the cut-off. 0.5 is a coin flip, 1.0 is perfect.</p><div class="ebars"><div class="ebar ours"><span class="ebar-label">Switchboard v0.1</span><span class="ebar-track"><span class="ebar-fill" style="width:78.7%"></span></span><span class="ebar-value">0.787</span></div>
58
+ <div class="ebar"><span class="ebar-label">Switchboard v0</span><span class="ebar-track"><span class="ebar-fill" style="width:78.5%"></span></span><span class="ebar-value">0.785</span></div>
59
+ <div class="ebar"><span class="ebar-label">Coin flip</span><span class="ebar-track"><span class="ebar-fill" style="width:50.0%"></span></span><span class="ebar-value">0.500</span></div></div></article>
60
+ <article class="metric"><header><h3>Unreadable answers</h3><span class="better">Lower is better</span></header><p class="plain">Answers that weren't valid JSON, out of 10,000. Those would fall back to frontier.</p><div class="ebars"><div class="ebar ours"><span class="ebar-label">Switchboard v0.1</span><span class="ebar-track"><span class="ebar-fill" style="width:0.0%"></span></span><span class="ebar-value">0</span></div>
61
+ <div class="ebar"><span class="ebar-label">Switchboard v0</span><span class="ebar-track"><span class="ebar-fill" style="width:0.0%"></span></span><span class="ebar-value">0</span></div></div></article>
62
+ </div>
63
+ <p class="fineprint center">The random router sends the same share of prompts to frontier as Switchboard v0.1, picked at random. A perfect router knows every answer in advance.</p>
64
+ </div>
65
+ </section>
66
+
67
+ <section class="band cream">
68
+ <div class="wrap narrow">
69
+ <h2 class="center">Does every build route the same way?</h2>
70
+ <p class="section-lede center">Smaller files make the model faster to download, but they can change its decisions. We checked each build against the full model on 347 test prompts: 300 picked at random from the 10,000 above, plus 47 borderline ones that cover all three tiers.</p>
71
+ <div class="table-wrap">
72
+ <table class="etable">
73
+ <thead><tr><th>Build</th><th class="num">Size</th><th class="num">Same decision</th><th></th></tr></thead>
74
+ <tbody>
75
+ <tr><td class="build"><b>GGUF F16</b><small>Ollama 0.40.3 via ollama run hf.co/..., temperature 0 from the repo params</small><small class="note">347 in order, 346 when the prompts were sent in random order (Ollama reuses cached prompt prefixes)</small></td><td class="num">994 MB</td><td class="num"><b>347 of 347</b><small>100.0%</small></td><td><span class="tag-yes">Shipped</span></td></tr>
76
+ <tr><td class="build"><b>Browser ONNX, 8-bit</b><small>Transformers.js 4.2.0, WebGPU, desktop Chrome</small><small class="note">299 of 300 random, 45 of 47 borderline; domain 347 of 347</small></td><td class="num">660 MB</td><td class="num"><b>344 of 347</b><small>99.1%</small></td><td><span class="tag-yes">Shipped</span></td></tr>
77
+ <tr><td class="build"><b>GGUF Q8_0</b><small>Ollama 0.40.3, GPU</small><small class="note">338 of 347 on CPU</small></td><td class="num">531 MB</td><td class="num"><b>333 of 347</b><small>96.0%</small></td><td><span class="tag-no">Not shipped</span></td></tr>
78
+ <tr><td class="build"><b>GGUF Q4_K_M</b><small>Ollama 0.40.3, GPU</small></td><td class="num">398 MB</td><td class="num"><b>325 of 347</b><small>93.7%</small></td><td><span class="tag-no">Not shipped</span></td></tr>
79
+ <tr><td class="build"><b>GGUF Q6_K</b><small>Ollama 0.40.3, GPU</small></td><td class="num">506 MB</td><td class="num"><b>320 of 347</b><small>92.2%</small></td><td><span class="tag-no">Not shipped</span></td></tr>
80
+ <tr><td class="build"><b>Browser ONNX, 4-bit layers</b><small>onnxruntime 1.24, CPU</small><small class="note">measured on the 300 random prompts only</small></td><td class="num">463 MB</td><td class="num"><b>197 of 300</b><small>65.7%</small></td><td><span class="tag-no">Not shipped</span></td></tr>
81
+ </tbody>
82
+ </table>
83
+ </div>
84
+ <p class="fineprint">For a router, a changed decision is a wrong answer, so we only ship builds that match the full model. 4-bit builds of a 0.5B model lose too much precision on the borderline prompts.</p>
85
+ </div>
86
+ </section>
87
+
88
+ <section class="band white">
89
+ <div class="wrap narrow">
90
+ <h2 class="center">Speed</h2>
91
+ <p class="section-lede center">Time to get one routing decision, measured on one desktop PC.</p>
92
+ <div class="table-wrap">
93
+ <table class="etable">
94
+ <thead><tr><th>Setup</th><th class="num">Median</th><th class="num">Slowest 10%</th></tr></thead>
95
+ <tbody><tr><td>GGUF F16 in Ollama, desktop CPU</td><td class="num">281 ms</td><td class="num">336 ms</td></tr><tr><td>Browser ONNX, WebGPU in desktop Chrome<small>after the one-time 660 MB download</small></td><td class="num">400 ms</td><td class="num">460 ms</td></tr></tbody>
96
+ </table>
97
+ </div>
98
+ </div>
99
+ </section>
100
+
101
+ <section class="band cream" id="raw">
102
+ <div class="wrap narrow">
103
+ <h2 class="center">Every answer, downloadable</h2>
104
+ <p class="section-lede center">One JSON line per prompt: the prompt, its label, the Mixtral score behind the label, Switchboard's raw output and decision, and whether the prompt was served well.</p>
105
+ <ul class="downloads">
106
+ <li><a href="data/outputs-v0.1.jsonl" download>Raw outputs v0.1</a> <small>6.0 MB, 10,000 lines</small></li>
107
+ <li><a href="data/outputs-v0.jsonl" download>Raw outputs v0</a> <small>6.0 MB, 10,000 lines</small></li>
108
+ </ul>
109
+ </div>
110
+ </section>
111
+
112
+ <section class="band white" id="method">
113
+ <div class="wrap narrow">
114
+ <h2 class="center">How we tested</h2>
115
+ <dl class="method">
116
+ <dt>Prompts</dt>
117
+ <dd>The validation split of <a href="https://huggingface.co/datasets/routellm/gpt4_dataset">routellm/gpt4_dataset</a> (Apache-2.0, from LMSYS): 10,000 real prompts from chat logs, instruction sets and coding questions. None were used in training.</dd>
118
+ <dt>What "needs frontier" means</dt>
119
+ <dd>Every prompt comes with a 2023 Mixtral-8x7B answer that a GPT-4 judge scored from 1 to 5. A score of 5 means a small model is enough, 4 means mid, and 1 to 3 means frontier. A 2026 small model is stronger than Mixtral-8x7B, so this label is cautious: some prompts marked frontier would be fine on a smaller model.</dd>
120
+ <dt>Served well</dt>
121
+ <dd>A prompt counts as served well if Switchboard sent it to frontier, or sent it lower and the Mixtral answer scored 4 or 5.</dd>
122
+ <dt>Cost</dt>
123
+ <dd>Output price per 1M tokens on Surplus: Claude Haiku 5.5 ($0.50) for small, Claude Sonnet 5.5 ($10) for mid, Claude Opus 5.5 ($20) for frontier. Savings compare the routed bill with sending every prompt to Opus.</dd>
124
+ <dt>Settings</dt>
125
+ <dd>Greedy decoding (temperature 0), at most 24 new tokens, prompts cut to 512 tokens, the default chat template with no extra instructions.</dd>
126
+ <dt>Builds</dt>
127
+ <dd>The full model in fp32 is the reference. Each build answered the same prompts and we counted how often its tier matched. GGUF builds ran in Ollama 0.40.3, the browser build in Transformers.js 4.2.0 with WebGPU in desktop Chrome.</dd>
128
+ <dt>Where it ran</dt>
129
+ <dd>One desktop PC with an RTX 5070 Ti, for training, evaluation and the speed numbers.</dd>
130
+ <dt>Limits</dt>
131
+ <dd>The labels come from models and a judge, not from people. Most prompts are English. Each number is one run, so differences of a few tenths of a point don't mean much. On prompts that sit right between two tiers, tiny numeric differences can flip the decision.</dd>
132
+ </dl>
133
+ </div>
134
+ </section>
135
+ </main>
136
+ </body>
137
+ </html>
index.html CHANGED
@@ -1,169 +1,194 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Switchboard</title>
7
- <meta name="description" content="A 0.5B open-weights router that picks the cheapest model that can handle each prompt. Try it in your browser.">
8
- <meta property="og:title" content="Switchboard">
9
- <meta property="og:description" content="Pick the cheapest model that can handle the prompt.">
10
- <link rel="preconnect" href="https://fonts.googleapis.com">
11
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
- <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
13
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Ccircle cx=%225%22 cy=%2212%22 r=%223%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%225%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%2212%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%2219%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3C/svg%3E">
14
- <link rel="stylesheet" href="style.css?v=6">
15
- </head>
16
- <body>
17
- <header class="top">
18
- <div class="top-inner">
19
- <a class="brand" href="#">
20
- <svg class="brand-mark" viewBox="0 0 24 24" aria-hidden="true"><circle cx="5" cy="12" r="3"/><circle cx="19" cy="5" r="2.4"/><circle cx="19" cy="12" r="2.4"/><circle cx="19" cy="19" r="2.4"/><path d="M8 12c4 0 5-7 8.6-7"/></svg>
21
- Switchboard
22
- </a>
23
- <nav class="nav">
24
- <a href="#demo">Demo</a>
25
- <a href="#how">How it works</a>
26
- <a href="#results">Results</a>
27
- <a href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Model card</a>
28
- </nav>
29
- <a class="btn btn-primary btn-sm" href="#demo">Try the router</a>
30
- </div>
31
- </header>
32
-
33
- <main>
34
- <section class="hero">
35
- <div class="wrap center">
36
- <p class="badge"><span class="dot" aria-hidden="true"></span>Open weights. Runs in your browser.</p>
37
- <h1>Pick the cheapest model<br><span class="accent">that can handle the prompt</span>.</h1>
38
- <p class="lede">Most prompts don't need a frontier model. Switchboard is a 0.5B router that reads each prompt and sends it to a small, mid or frontier model, so you only pay frontier prices when the prompt needs it.</p>
39
- <div class="cta-row">
40
- <a class="btn btn-primary" href="#demo">Route a prompt</a>
41
- <a class="btn btn-green" href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Get the weights <span class="tag">Apache 2.0</span></a>
42
- </div>
43
- <p class="spec">0.5B parameters · one line of JSON out · about 15 tokens per decision</p>
44
- </div>
45
- <div class="wrap">
46
- <figure class="board-card">
47
- <canvas id="board" class="board-canvas" aria-label="Animation: prompts flowing into the router and out to the small, mid or frontier line"></canvas>
48
- <figcaption class="board-note">Real prompts from the held-out set and the tier Switchboard v0.1 actually picked. We show an equal number per tier, so the mix isn't to scale.</figcaption>
49
- </figure>
50
- </div>
51
- </section>
52
-
53
- <section class="tiers-strip">
54
- <div class="wrap">
55
- <p class="strip-title">Three tiers, one decision per prompt</p>
56
- <ul class="tier-cards" id="tier-cards">
57
- <li class="tier-card" data-tier="small"><span class="lamp" aria-hidden="true"></span><b>Small</b><span class="tier-model" data-model="small"></span></li>
58
- <li class="tier-card" data-tier="mid"><span class="lamp" aria-hidden="true"></span><b>Mid</b><span class="tier-model" data-model="mid"></span></li>
59
- <li class="tier-card" data-tier="frontier"><span class="lamp" aria-hidden="true"></span><b>Frontier</b><span class="tier-model" data-model="frontier"></span></li>
60
- </ul>
61
- </div>
62
- </section>
63
-
64
- <section class="band white" id="demo">
65
- <div class="wrap">
66
- <h2 class="center">Try it on your own prompt</h2>
67
- <p class="section-lede center">The real model runs in this tab. Nothing you type leaves your device.</p>
68
-
69
- <div class="demo">
70
- <label for="prompt" class="field-label">Prompt</label>
71
- <textarea id="prompt" rows="4" maxlength="2000" placeholder="Paste any prompt you'd send to an LLM"></textarea>
72
- <div class="samples" role="group" aria-label="Sample prompts">
73
- <button type="button" class="chip" data-prompt="What is the capital of France?">Capital of France</button>
74
- <button type="button" class="chip" data-prompt="Write a haiku about rain">Haiku about rain</button>
75
- <button type="button" class="chip" data-prompt="Design a rate limiter for 10M requests per second across 5 regions, with failure modes and consistency trade-offs">Global rate limiter</button>
76
- <button type="button" class="chip" data-prompt="Unscramble the following words: eglar, ngareo, kobo">Unscramble words</button>
77
- </div>
78
- <div class="demo-actions">
79
- <button type="button" id="route" class="btn btn-primary">Route prompt</button>
80
- <p id="status" class="status" role="status" aria-live="polite">The first route downloads the router once (660 MB). After that it runs offline in this tab.</p>
81
- </div>
82
- <div class="meter" id="meter" hidden><span id="meter-fill"></span></div>
83
-
84
- <div class="readout" id="readout" hidden>
85
- <div class="readout-tiers" aria-hidden="true">
86
- <span data-pick="small">Small</span><span data-pick="mid">Mid</span><span data-pick="frontier">Frontier</span>
87
- </div>
88
- <pre id="json"></pre>
89
- <p id="verdict"></p>
90
- </div>
91
- </div>
92
- </div>
93
- </section>
94
-
95
- <section class="band cream">
96
- <div class="wrap">
97
- <h2 class="center">Why route at all</h2>
98
- <div class="grid3">
99
- <div>
100
- <h3>Most prompts are easy</h3>
101
- <p>Greetings, lookups, short rewrites and simple code make up most traffic. A small model answers them just as well, at a few percent of the price.</p>
102
- </div>
103
- <div>
104
- <h3>The router costs almost nothing</h3>
105
- <p>Switchboard is 0.5B parameters and writes about 15 tokens per decision. On Surplus that's roughly $0.000005 per prompt.</p>
106
- </div>
107
- <div>
108
- <h3>A bad call fails safe</h3>
109
- <p>If the output can't be parsed, send the prompt to frontier. A broken routing call costs money, never quality.</p>
110
- </div>
111
- </div>
112
- </div>
113
- </section>
114
-
115
- <section class="band white" id="how">
116
- <div class="wrap">
117
- <h2 class="center">Three steps</h2>
118
- <ol class="steps">
119
- <li><span class="step-n">1</span><h3>Send the prompt to Switchboard</h3><p>One short call before the real one.</p></li>
120
- <li><span class="step-n">2</span><h3>Read one line of JSON</h3><p><code>{"tier":"small","domain":"code"}</code></p></li>
121
- <li><span class="step-n">3</span><h3>Call the model for that tier</h3><p>Easy prompts go small. Hard prompts still get the frontier model.</p></li>
122
- </ol>
123
- </div>
124
- </section>
125
-
126
- <section class="band cream" id="results">
127
- <div class="wrap">
128
- <h2 class="center">Results</h2>
129
- <p class="section-lede center" id="results-intro">Loading the latest evaluation.</p>
130
- <div class="stats" id="figures"></div>
131
- <p class="fineprint center" id="fineprint"></p>
132
- </div>
133
- </section>
134
-
135
- <section class="band white">
136
- <div class="wrap narrow">
137
- <h2 class="center">Run it yourself</h2>
138
- <p class="section-lede center">Open weights under Apache 2.0. It runs anywhere Transformers runs.</p>
139
- <pre class="code"><code>from transformers import AutoModelForCausalLM, AutoTokenizer
140
- import json
141
-
142
- repo = "switchboardllm/switchboard-router-0.5b"
143
- tok = AutoTokenizer.from_pretrained(repo)
144
- model = AutoModelForCausalLM.from_pretrained(repo)
145
-
146
- msgs = [{"role": "user", "content": "Write a haiku about rain"}]
147
- ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt")
148
- out = model.generate(ids, max_new_tokens=24, do_sample=False)
149
- print(json.loads(tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True)))</code></pre>
150
- </div>
151
- </section>
152
-
153
- <section class="closing">
154
- <canvas id="closing-board" class="closing-canvas" aria-hidden="true"></canvas>
155
- <div class="wrap center closing-inner">
156
- <h2>Coming to the Surplus API</h2>
157
- <p class="section-lede">After launch, call Switchboard on the same Surplus key as every other model, at $0.03 per million tokens.</p>
158
- <div class="cta-row">
159
- <a class="btn btn-primary" href="#demo">Try the router</a>
160
- <a class="btn btn-ghost" href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Read the model card</a>
161
- </div>
162
- </div>
163
- </section>
164
- </main>
165
-
166
-
167
- <script type="module" src="app.js?v=12"></script>
168
- </body>
169
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Switchboard</title>
7
+ <meta name="description" content="A 0.5B open-weights router that picks the cheapest model that can handle each prompt. Try it in your browser.">
8
+ <meta property="og:title" content="Switchboard">
9
+ <meta property="og:description" content="Pick the cheapest model that can handle the prompt.">
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
13
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Ccircle cx=%225%22 cy=%2212%22 r=%223%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%225%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%2212%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3Ccircle cx=%2219%22 cy=%2219%22 r=%222.4%22 fill=%22%23ea580c%22/%3E%3C/svg%3E">
14
+ <link rel="stylesheet" href="style.css?v=9">
15
+ </head>
16
+ <body>
17
+ <header class="top">
18
+ <div class="top-inner">
19
+ <a class="brand" href="#">
20
+ <svg class="brand-mark" viewBox="0 0 24 24" aria-hidden="true"><circle cx="5" cy="12" r="3"/><circle cx="19" cy="5" r="2.4"/><circle cx="19" cy="12" r="2.4"/><circle cx="19" cy="19" r="2.4"/><path d="M8 12c4 0 5-7 8.6-7"/></svg>
21
+ Switchboard
22
+ </a>
23
+ <nav class="nav">
24
+ <a href="#demo">Demo</a>
25
+ <a href="#how">How it works</a>
26
+ <a href="evaluation.html">Evaluation</a>
27
+ <a href="#faq">FAQ</a>
28
+ <a href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Model card</a>
29
+ </nav>
30
+ <a class="btn btn-primary btn-sm" href="#demo">Try the router</a>
31
+ </div>
32
+ </header>
33
+
34
+ <main>
35
+ <section class="hero">
36
+ <div class="wrap center">
37
+ <p class="badge"><span class="dot" aria-hidden="true"></span>Open weights. Runs in your browser.</p>
38
+ <h1>Pick the cheapest model<br><span class="accent">that can handle the prompt</span>.</h1>
39
+ <p class="lede">Most prompts don't need a frontier model. Switchboard is a 0.5B router that reads each prompt and sends it to a small, mid or frontier model, so you only pay frontier prices when the prompt needs it.</p>
40
+ <div class="cta-row">
41
+ <a class="btn btn-primary" href="#demo">Route a prompt</a>
42
+ <a class="btn btn-green" href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Get the weights <span class="tag">Apache 2.0</span></a>
43
+ </div>
44
+ <p class="spec">0.5B parameters · one line of JSON out · about 15 tokens per decision</p>
45
+ </div>
46
+ <div class="wrap">
47
+ <figure class="board-card">
48
+ <canvas id="board" class="board-canvas" aria-label="Animation: prompts flowing into the router and out to the small, mid or frontier line"></canvas>
49
+ <figcaption class="board-note">Real prompts from the held-out set and the tier Switchboard v0.1 actually picked. We show an equal number per tier, so the mix isn't to scale.</figcaption>
50
+ </figure>
51
+ </div>
52
+ </section>
53
+
54
+ <section class="tiers-strip">
55
+ <div class="wrap">
56
+ <p class="strip-title">Three tiers, one decision per prompt</p>
57
+ <ul class="tier-cards" id="tier-cards">
58
+ <li class="tier-card" data-tier="small"><span class="lamp" aria-hidden="true"></span><b>Small</b><span class="tier-model" data-model="small"></span></li>
59
+ <li class="tier-card" data-tier="mid"><span class="lamp" aria-hidden="true"></span><b>Mid</b><span class="tier-model" data-model="mid"></span></li>
60
+ <li class="tier-card" data-tier="frontier"><span class="lamp" aria-hidden="true"></span><b>Frontier</b><span class="tier-model" data-model="frontier"></span></li>
61
+ </ul>
62
+ </div>
63
+ </section>
64
+
65
+ <section class="band white" id="demo">
66
+ <div class="wrap">
67
+ <h2 class="center">Try it on your own prompt</h2>
68
+ <p class="section-lede center">The real model runs in this tab. Nothing you type leaves your device.</p>
69
+
70
+ <div class="demo">
71
+ <label for="prompt" class="field-label">Prompt</label>
72
+ <textarea id="prompt" rows="4" maxlength="2000" placeholder="Paste any prompt you'd send to an LLM"></textarea>
73
+ <div class="samples" role="group" aria-label="Sample prompts">
74
+ <button type="button" class="chip" data-prompt="What is the capital of France?">Capital of France</button>
75
+ <button type="button" class="chip" data-prompt="Write a haiku about rain">Haiku about rain</button>
76
+ <button type="button" class="chip" data-prompt="Design a rate limiter for 10M requests per second across 5 regions, with failure modes and consistency trade-offs">Global rate limiter</button>
77
+ <button type="button" class="chip" data-prompt="Unscramble the following words: eglar, ngareo, kobo">Unscramble words</button>
78
+ </div>
79
+ <div class="demo-actions">
80
+ <button type="button" id="route" class="btn btn-primary">Route prompt</button>
81
+ <p id="status" class="status" role="status" aria-live="polite">The first route downloads the router once (660 MB). After that it runs offline in this tab.</p>
82
+ </div>
83
+ <div class="meter" id="meter" hidden><span id="meter-fill"></span></div>
84
+
85
+ <div class="readout" id="readout" hidden>
86
+ <div class="readout-tiers" aria-hidden="true">
87
+ <span data-pick="small">Small</span><span data-pick="mid">Mid</span><span data-pick="frontier">Frontier</span>
88
+ </div>
89
+ <pre id="json"></pre>
90
+ <p id="verdict"></p>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </section>
95
+
96
+ <section class="band cream" id="how">
97
+ <div class="wrap">
98
+ <h2 class="center">How it works</h2>
99
+ <p class="analogy">Think of an old telephone switchboard. An operator listened to each caller and plugged the line into the right place. Switchboard does the same with your questions to AI: it reads each one and connects it to an AI that's good enough for it, without paying for the most expensive one every time.</p>
100
+ <div class="explainer">
101
+ <div class="story">
102
+ <div class="story-step"><span class="step-n">1</span><div><h3>You ask something</h3><p>A question, a request to write or fix something, anything you'd type into a chatbot.</p></div></div>
103
+ <div class="story-step"><span class="step-n">2</span><div><h3>Switchboard reads it first</h3><p>A small AI decides how hard the question is. "What's the capital of France?" is easy. A tricky math proof is hard. This takes less than half a second and costs almost nothing.</p></div></div>
104
+ <div class="story-step"><span class="step-n">3</span><div><h3>It goes to the right AI</h3><p>Easy questions go to a small, cheap AI. Medium ones go to a mid-size AI. Only the hard ones go to the most powerful, most expensive AI.</p></div></div>
105
+ <div class="story-step"><span class="step-n">4</span><div><h3>You pay less for the same answers</h3><p>Most everyday questions are easy, so most of them stop being billed at the top price. When Switchboard can't read its own answer, it plays it safe and picks the powerful AI.</p></div></div>
106
+ </div>
107
+ <aside class="bill" aria-labelledby="bill-title">
108
+ <h3 id="bill-title">Same 1,000 questions, two bills</h3>
109
+ <p>Each answer about 500 tokens (roughly 375 words), priced on Surplus.</p>
110
+ <div class="bill-row"><span>Every question to Claude Opus 5.5</span><b id="bill-all">…</b><span class="bill-bar"><i style="width:100%"></i></span></div>
111
+ <div class="bill-row ours"><span>With Switchboard picking the AI</span><b id="bill-routed">…</b><span class="bill-bar"><i id="bill-routed-bar" style="width:0%"></i></span></div>
112
+ <p class="fineprint" id="bill-note">Based on Switchboard's results on 10,000 test questions.</p>
113
+ </aside>
114
+ </div>
115
+ </div>
116
+ </section>
117
+
118
+ <section class="band white" id="results">
119
+ <div class="wrap">
120
+ <h2 class="center">Results</h2>
121
+ <p class="section-lede center" id="results-intro">Loading the latest evaluation.</p>
122
+ <div class="stats" id="figures"></div>
123
+ <p class="fineprint center" id="fineprint"></p>
124
+ <div class="cta-row"><a class="btn btn-ghost" href="evaluation.html">See the full evaluation</a></div>
125
+ </div>
126
+ </section>
127
+
128
+ <section class="band cream" id="start">
129
+ <div class="wrap narrow">
130
+ <h2 class="center">Up and running in a few minutes</h2>
131
+ <p class="section-lede center">Run it on your own computer. No GPU, account or API key needed.</p>
132
+ <ol class="steps">
133
+ <li><span class="step-n">1</span><h3>Get Ollama</h3><p>Download it free from <a href="https://ollama.com">ollama.com</a> and open it once.</p></li>
134
+ <li><span class="step-n">2</span><h3>Ask Switchboard</h3><p>Paste the line below into a terminal. The first run downloads the model once (994 MB).</p></li>
135
+ <li><span class="step-n">3</span><h3>Read the answer</h3><p>The tier tells you which size of AI to use. Here, a small one is enough.</p></li>
136
+ </ol>
137
+ <pre class="code run-code"><code><span class="prompt">$</span> ollama run hf.co/switchboardllm/switchboard-router-0.5b-GGUF "Write a haiku about rain"
138
+ <span class="answer">{"tier":"small","domain":"writing"}</span></code></pre>
139
+ <h3 class="facts-title">What's inside Switchboard</h3>
140
+ <div class="table-wrap">
141
+ <table class="facts">
142
+ <tr><th>Size</th><td>0.5 billion parameters. 994 MB to run on your computer, 660 MB in the browser.</td></tr>
143
+ <tr><th>Built on</th><td>Qwen2.5-0.5B-Instruct (Apache 2.0), fully fine-tuned</td></tr>
144
+ <tr><th>Trained on</th><td>63,749 real prompts from routellm/gpt4_dataset (Apache 2.0)</td></tr>
145
+ <tr><th>Training time</th><td>55 minutes on one RTX 5070 Ti</td></tr>
146
+ <tr><th>Answers with</th><td>One line of JSON: a tier (small, mid or frontier) and a topic (code, math, writing or general)</td></tr>
147
+ <tr><th>Runs on</th><td>Ollama, llama.cpp, Transformers, and your browser</td></tr>
148
+ <tr><th>License</th><td>Apache 2.0, free for commercial use</td></tr>
149
+ </table>
150
+ </div>
151
+ </div>
152
+ </section>
153
+
154
+ <section class="band white" id="faq">
155
+ <div class="wrap">
156
+ <h2 class="center">Questions &amp; answers</h2>
157
+ <div class="faq-groups">
158
+ <div class="faq-group">
159
+ <h3>Switchboard</h3>
160
+ <details class="faq-item"><summary><span class="faq-n">01</span>What is Switchboard?</summary><p>A small, open AI model that reads a prompt and decides how capable an AI needs to be to answer it: small, mid or frontier. You then send the prompt to an AI of that size.</p></details>
161
+ <details class="faq-item"><summary><span class="faq-n">02</span>Why not always use the best AI?</summary><p>Price. On Surplus, an answer from Claude Opus 5.5 costs 40 times more than the same answer from Claude Haiku 5.5. Most everyday prompts get an equally good answer from the smaller model.</p></details>
162
+ <details class="faq-item"><summary><span class="faq-n">03</span>Which base model is it built on?</summary><p>Qwen2.5-0.5B-Instruct from Alibaba, fully fine-tuned on routing examples. It's small on purpose: a router sits in front of every request, so it has to be fast and almost free.</p></details>
163
+ <details class="faq-item"><summary><span class="faq-n">04</span>Who is behind it?</summary><p>An independent project. It is not affiliated with Anthropic, OpenAI, Alibaba, Robinhood, Surplus or the Switchboard oracle network. Model names appear only to describe where prompts can be sent.</p></details>
164
+ </div>
165
+ <div class="faq-group">
166
+ <h3>Model &amp; use</h3>
167
+ <details class="faq-item"><summary><span class="faq-n">05</span>Does it run on my computer?</summary><p>Yes. The 994 MB build runs with Ollama or llama.cpp on Windows, macOS and Linux, on a normal CPU. The demo on this page runs in your browser.</p></details>
168
+ <details class="faq-item"><summary><span class="faq-n">06</span>Does my prompt leave my device?</summary><p>Not in the demo on this page, and not when you run it with Ollama. If you call Switchboard through a hosted API, the prompt goes to that server like any other API call.</p></details>
169
+ <details class="faq-item"><summary><span class="faq-n">07</span>How good is it?</summary><p>On 10,000 test prompts it cut the bill by <span data-fill="cost_saved">…</span> compared with always using Claude Opus 5.5, and sent <span data-fill="under_routed">…</span> of prompts to a cheaper tier than they needed. All the numbers, including the weak spots, are on the <a href="evaluation.html">evaluation page</a>.</p></details>
170
+ <details class="faq-item"><summary><span class="faq-n">08</span>What if it picks the wrong tier?</summary><p>Too high costs a bit more. Too low can mean a weaker answer, which is the mistake we work hardest to reduce. If its answer can't be read, it falls back to the frontier tier, so a broken routing call never sends you to a weaker AI.</p></details>
171
+ <details class="faq-item"><summary><span class="faq-n">09</span>Is it free?</summary><p>Yes. The model is Apache 2.0, and running it yourself costs nothing. After the Surplus launch you can also call it on the Surplus API at $0.03 per million tokens, about $0.000005 per routing decision.</p></details>
172
+ <details class="faq-item"><summary><span class="faq-n">10</span>What data was it trained on?</summary><p>Real prompts from <a href="https://huggingface.co/datasets/routellm/gpt4_dataset">routellm/gpt4_dataset</a> (Apache 2.0, from LMSYS). Each prompt's label comes from how well a 2023 open model answered it, as scored by a GPT-4 judge. The model card has the details.</p></details>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </section>
177
+
178
+ <section class="closing">
179
+ <canvas id="closing-board" class="closing-canvas" aria-hidden="true"></canvas>
180
+ <div class="wrap center closing-inner">
181
+ <h2>Coming to the Surplus API</h2>
182
+ <p class="section-lede">After launch, call Switchboard on the same Surplus key as every other model, at $0.03 per million tokens.</p>
183
+ <div class="cta-row">
184
+ <a class="btn btn-primary" href="#demo">Try the router</a>
185
+ <a class="btn btn-ghost" href="https://huggingface.co/switchboardllm/switchboard-router-0.5b">Read the model card</a>
186
+ </div>
187
+ </div>
188
+ </section>
189
+ </main>
190
+
191
+
192
+ <script type="module" src="app.js?v=13"></script>
193
+ </body>
194
+ </html>
style.css CHANGED
@@ -266,3 +266,92 @@ code { font-family: var(--mono); font-size: 0.86em; }
266
  .word { animation: none; opacity: 1; transform: none; }
267
  * { transition: none !important; }
268
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  .word { animation: none; opacity: 1; transform: none; }
267
  * { transition: none !important; }
268
  }
269
+
270
+ /* ---------- Evaluation page ---------- */
271
+ .nav a[aria-current="page"] { color: var(--ink-strong); font-weight: 600; }
272
+ .hero-sm { padding: 64px 0 52px; }
273
+ .hero-sm h1 { max-width: 20ch; }
274
+ .metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
275
+ .metric { padding: 22px; border-radius: 16px; background: #fffdf7; border: 1px solid var(--line); }
276
+ .metric header { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
277
+ .metric h3 { margin: 0; }
278
+ .better { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
279
+ .plain { margin: 6px 0 16px; font-size: 14.5px; color: var(--body); }
280
+ .ebars { display: grid; gap: 10px; }
281
+ .ebar { display: grid; grid-template-columns: 170px 1fr 64px; align-items: center; gap: 12px; font-size: 14px; }
282
+ .ebar-label { color: var(--body); }
283
+ .ebar-track { height: 12px; border-radius: 999px; background: #f1ebdc; overflow: hidden; }
284
+ .ebar-fill { display: block; height: 100%; border-radius: 999px; background: #cfc5ad; }
285
+ .ebar.ours .ebar-fill { background: linear-gradient(90deg, var(--amber), var(--orange)); }
286
+ .ebar.ours .ebar-label, .ebar.ours .ebar-value { color: var(--ink-strong); font-weight: 600; }
287
+ .ebar-value { text-align: right; font-variant-numeric: tabular-nums; }
288
+ .table-wrap { overflow-x: auto; border-radius: 16px; border: 1px solid var(--line); background: var(--white); }
289
+ .etable { width: 100%; border-collapse: collapse; font-size: 14.5px; }
290
+ .etable th { text-align: left; font-weight: 600; font-size: 13px; color: var(--muted); padding: 12px 16px; border-bottom: 1px solid var(--line); }
291
+ .etable td { padding: 14px 16px; border-bottom: 1px solid var(--line); vertical-align: top; }
292
+ .etable tr:last-child td { border-bottom: 0; }
293
+ .etable td small { display: block; margin-top: 3px; color: var(--muted); font-size: 12.5px; }
294
+ .etable .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
295
+ .tag-yes, .tag-no { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
296
+ .tag-yes { background: #e6f2ea; color: var(--green-deep); }
297
+ .tag-no { background: #f3eee2; color: var(--muted); }
298
+ .downloads { list-style: none; margin: 0 auto; padding: 0; max-width: 520px; display: grid; gap: 10px; }
299
+ .downloads li { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 14px 18px; border-radius: 14px; background: var(--white); border: 1px solid var(--line); }
300
+ .downloads a { font-weight: 600; color: var(--orange-deep); }
301
+ .downloads small { color: var(--muted); }
302
+ .method { margin: 0; display: grid; grid-template-columns: 190px 1fr; gap: 0 28px; }
303
+ .method dt { padding: 16px 0; border-top: 1px solid var(--line); font-family: var(--serif); font-weight: 600; font-size: 17px; }
304
+ .method dd { margin: 0; padding: 16px 0; border-top: 1px solid var(--line); color: var(--body); font-size: 15px; line-height: 1.6; }
305
+ .method a { color: var(--orange-deep); }
306
+
307
+ /* ---------- Plain-language explainer ---------- */
308
+ .explainer { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 40px; align-items: start; margin-top: 36px; }
309
+ .story { display: grid; gap: 16px; }
310
+ .story-step { display: grid; grid-template-columns: 40px 1fr; gap: 14px; }
311
+ .story-step .step-n { margin: 0; }
312
+ .story-step h3 { margin: 4px 0 4px; }
313
+ .story-step p { margin: 0; color: var(--body); line-height: 1.6; }
314
+ .bill { padding: 24px; border-radius: 18px; background: #fffdf8; border: 1px solid var(--line); box-shadow: 0 24px 50px -38px #3b2f1666; }
315
+ .bill h3 { margin: 0 0 4px; }
316
+ .bill > p { margin: 0 0 18px; font-size: 14px; color: var(--muted); }
317
+ .bill-row { display: grid; grid-template-columns: 1fr auto; gap: 8px 12px; align-items: baseline; padding: 14px 0; border-top: 1px solid var(--line); }
318
+ .bill-row span { color: var(--body); }
319
+ .bill-row b { font-family: var(--serif); font-size: 30px; font-weight: 600; color: var(--ink-strong); font-variant-numeric: tabular-nums; }
320
+ .bill-row.ours b { color: var(--orange-deep); }
321
+ .bill-bar { grid-column: 1 / -1; height: 10px; border-radius: 999px; background: #f1ebdc; overflow: hidden; }
322
+ .bill-bar i { display: block; height: 100%; background: #cfc5ad; border-radius: 999px; }
323
+ .bill-row.ours .bill-bar i { background: linear-gradient(90deg, var(--amber), var(--orange)); }
324
+ .bill .fineprint { margin: 14px 0 0; text-align: left; }
325
+ .analogy { max-width: 64ch; margin: 0 auto; font-size: 17px; color: var(--body); line-height: 1.65; text-align: center; }
326
+
327
+ /* ---------- Facts and FAQ ---------- */
328
+ .facts { width: 100%; border-collapse: collapse; font-size: 15px; }
329
+ .facts th { text-align: left; width: 34%; padding: 13px 18px; font-weight: 500; color: var(--muted); border-bottom: 1px solid var(--line); vertical-align: top; }
330
+ .facts td { padding: 13px 18px; border-bottom: 1px solid var(--line); }
331
+ .facts tr:last-child th, .facts tr:last-child td { border-bottom: 0; }
332
+ .faq-groups { display: grid; grid-template-columns: 1fr 1fr; gap: 36px; margin-top: 32px; }
333
+ .faq-group h3 { margin: 0 0 10px; font-size: 15px; font-family: var(--sans); font-weight: 600; color: var(--orange-deep); }
334
+ .faq-item { border-top: 1px solid var(--line); }
335
+ .faq-item:last-child { border-bottom: 1px solid var(--line); }
336
+ .faq-item summary { display: grid; grid-template-columns: 34px 1fr 20px; gap: 6px; align-items: baseline; padding: 16px 0; cursor: pointer; list-style: none; font-weight: 600; color: var(--ink); }
337
+ .faq-item summary::-webkit-details-marker { display: none; }
338
+ .faq-item summary::after { content: "+"; justify-self: end; color: var(--muted); font-weight: 400; font-size: 20px; line-height: 1; }
339
+ .faq-item[open] summary::after { content: "–"; }
340
+ .faq-n { font: 500 13px var(--mono); color: var(--muted); }
341
+ .faq-item p { margin: 0 0 16px 40px; color: var(--body); line-height: 1.6; font-size: 15px; }
342
+ .faq-item a { color: var(--orange-deep); }
343
+
344
+ @media (max-width: 860px) {
345
+ .metrics, .explainer, .faq-groups { grid-template-columns: 1fr; }
346
+ .ebar { grid-template-columns: 120px 1fr 56px; }
347
+ .method { grid-template-columns: 1fr; }
348
+ .method dd { border-top: 0; padding-top: 0; }
349
+ }
350
+
351
+ .facts-title { margin: 44px 0 14px; text-align: center; }
352
+
353
+ .run-code { margin-top: 18px; }
354
+ .run-code .prompt { color: #8fa39a; }
355
+ .run-code .answer { color: #fbbf24; }
356
+ .etable td.build { width: 56%; }
357
+ .etable td.build small.note { color: var(--body); margin-top: 6px; }