lvwerra HF Staff commited on
Commit
3cf1b0c
·
1 Parent(s): 468dae5

Demo polish: header specs, primer rewrite, hide endpoint URL, tab rename

Browse files

- Banner: 393,216 bp context (was 49,152); footer model badge removed
- Intro tab: rewrite primer §0/§1/§3/§4 paragraphs; clarify 1T tokens (6T bp)
- DNA Lab intro: use "Carbon-3B" to differentiate from upcoming releases
- Hide endpoint URL in frontend (sandbox header + code snippets); /config
no longer returns it; backend still uses real ENDPOINT_URL via env/default
- Code snippets: rename tabs to "API" / "transformers", generic
https://<your-endpoint>/v1/ placeholder, escape angle brackets
- Sandbox: fold "Connected to {model}" into the Input card header
- Rename tabs demo->dna-lab and model->recipe (URLs now #dna-lab, #recipe)

app.py CHANGED
@@ -158,7 +158,7 @@ def social_banner():
158
 
159
  @app.get("/config")
160
  def config():
161
- return {"model": MODEL_NAME, "endpoint": ENDPOINT_URL}
162
 
163
 
164
  @app.get("/genes")
 
158
 
159
  @app.get("/config")
160
  def config():
161
+ return {"model": MODEL_NAME}
162
 
163
 
164
  @app.get("/genes")
assets/js/sections/sandbox.js CHANGED
@@ -450,7 +450,7 @@
450
  // Init meta from /config (fires regardless of which tab is active).
451
  // Reuses the shared promise from fetchConfig(), no double network roundtrip.
452
  fetchConfig().then(cfg => {
453
- els.meta.textContent = `${cfg.model} · ${cfg.endpoint}`;
454
  }).catch(() => { els.meta.textContent = "config unavailable"; });
455
 
456
  updateStats();
 
450
  // Init meta from /config (fires regardless of which tab is active).
451
  // Reuses the shared promise from fetchConfig(), no double network roundtrip.
452
  fetchConfig().then(cfg => {
453
+ els.meta.textContent = cfg.model;
454
  }).catch(() => { els.meta.textContent = "config unavailable"; });
455
 
456
  updateStats();
assets/js/shared/config.js CHANGED
@@ -15,7 +15,6 @@ async function loadConfig() {
15
  try {
16
  const cfg = await fetchConfig();
17
  document.getElementById("meta").textContent = cfg.model;
18
- document.getElementById("footer-model").textContent = cfg.model;
19
  } catch {
20
  document.getElementById("meta").textContent = "config unavailable";
21
  }
 
15
  try {
16
  const cfg = await fetchConfig();
17
  document.getElementById("meta").textContent = cfg.model;
 
18
  } catch {
19
  document.getElementById("meta").textContent = "config unavailable";
20
  }
assets/js/tabs.js CHANGED
@@ -2,7 +2,7 @@
2
  // Tab switching + hash routing
3
  // =========================================================================
4
  (function initTabs() {
5
- const TABS = ["intro", "demo", "model", "sandbox"];
6
  // What we land on when the URL has no hash. Also the safety fallback
7
  // when setTab is called with an unrecognised tab name.
8
  const DEFAULT_TAB = "intro";
@@ -30,8 +30,8 @@
30
  // Map a section anchor → which tab contains it
31
  const SECTION_TO_TAB = {
32
  primer: "intro",
33
- completion: "demo", vep: "demo", track: "demo", species: "demo", folding: "demo", umap: "demo",
34
- tokenizer: "model", loss: "model", data: "model", architecture: "model", longcontext: "model", results: "model", efficiency: "model",
35
  sandbox: "sandbox",
36
  };
37
 
 
2
  // Tab switching + hash routing
3
  // =========================================================================
4
  (function initTabs() {
5
+ const TABS = ["intro", "dna-lab", "recipe", "sandbox"];
6
  // What we land on when the URL has no hash. Also the safety fallback
7
  // when setTab is called with an unrecognised tab name.
8
  const DEFAULT_TAB = "intro";
 
30
  // Map a section anchor → which tab contains it
31
  const SECTION_TO_TAB = {
32
  primer: "intro",
33
+ completion: "dna-lab", vep: "dna-lab", track: "dna-lab", species: "dna-lab", folding: "dna-lab", umap: "dna-lab",
34
+ tokenizer: "recipe", loss: "recipe", data: "recipe", architecture: "recipe", longcontext: "recipe", results: "recipe", efficiency: "recipe",
35
  sandbox: "sandbox",
36
  };
37
 
assets/styles/sandbox.css CHANGED
@@ -34,22 +34,10 @@
34
  color: #3a2a04;
35
  }
36
 
37
- /* --- Connection header strip (model + endpoint at the top of the panel).
38
- Same paper/border treatment as the .sb-card panels below so the whole
39
- page reads as a stack of layered cards. --- */
40
- #panel-sandbox .sb-header {
41
- display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
42
- padding: 12px 18px;
43
- background: #fbfaf3;
44
- border: 1px solid var(--hairline);
45
- border-radius: 4px;
46
- margin-bottom: 18px;
47
- }
48
  #panel-sandbox .sb-header__meta {
49
  font-family: "JetBrains Mono", monospace;
50
  font-size: 11px; color: #555; font-weight: 400;
51
  letter-spacing: 0.4px; word-break: break-all;
52
- flex: 1 1 0; min-width: 0;
53
  }
54
 
55
  /* --- Card panel (Prompt / Sequence). Replaces the old .sb-section-title
@@ -94,6 +82,15 @@
94
  padding: 1px 5px; border-radius: 2px;
95
  color: #1f1f1d;
96
  }
 
 
 
 
 
 
 
 
 
97
  #panel-sandbox .sb-card__body {
98
  padding: 16px 20px 22px;
99
  }
 
34
  color: #3a2a04;
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
37
  #panel-sandbox .sb-header__meta {
38
  font-family: "JetBrains Mono", monospace;
39
  font-size: 11px; color: #555; font-weight: 400;
40
  letter-spacing: 0.4px; word-break: break-all;
 
41
  }
42
 
43
  /* --- Card panel (Prompt / Sequence). Replaces the old .sb-section-title
 
82
  padding: 1px 5px; border-radius: 2px;
83
  color: #1f1f1d;
84
  }
85
+ #panel-sandbox .sb-card__header--with-meta {
86
+ display: flex; align-items: flex-start; justify-content: space-between; gap: 18px;
87
+ }
88
+ #panel-sandbox .sb-card__heading { min-width: 0; }
89
+ #panel-sandbox .sb-card__meta {
90
+ text-align: right;
91
+ flex: 0 0 auto;
92
+ max-width: 50%;
93
+ }
94
  #panel-sandbox .sb-card__body {
95
  padding: 16px 20px 22px;
96
  }
demo.html CHANGED
@@ -102,8 +102,8 @@
102
  that separates the banner from the page content (margin-bottom: -1px). -->
103
  <nav id="tab-nav" class="banner-tabs">
104
  <button class="tab active" data-tab="intro">Intro</button>
105
- <button class="tab" data-tab="demo">DNA Lab</button>
106
- <button class="tab" data-tab="model">Carbon Recipe</button>
107
  <button class="tab" data-tab="sandbox">Sandbox</button>
108
  </nav>
109
  </div>
@@ -133,8 +133,8 @@
133
  </a>
134
  <div class="sticky-nav__tabs">
135
  <button class="tab active" data-tab="intro">Intro</button>
136
- <button class="tab" data-tab="demo">DNA Lab</button>
137
- <button class="tab" data-tab="model">Carbon Recipe</button>
138
  <button class="tab" data-tab="sandbox">Sandbox</button>
139
  </div>
140
  </div>
@@ -173,9 +173,9 @@
173
  <ul class="intro-guide-list">
174
  <li><a href="#primer">Intro<span class="arrow" aria-hidden="true">↓</span></a>
175
  continue reading to learn the basics of genetics.</li>
176
- <li><a href="#demo">DNA Lab<span class="arrow" aria-hidden="true">→</span></a>
177
  explore what the model can do, with live interactions against the 3B checkpoint.</li>
178
- <li><a href="#model">Carbon Recipe<span class="arrow" aria-hidden="true">→</span></a>
179
  learn how Carbon was trained: tokenizer, loss, dataset, results.</li>
180
  <li><a href="#sandbox">Sandbox<span class="arrow" aria-hidden="true">→</span></a>
181
  run Carbon on your own DNA sequences.</li>
@@ -202,9 +202,9 @@
202
  <div class="section-num">§1 · Bases</div>
203
  <div class="section-title">A four-letter alphabet</div>
204
  <p class="lede">
205
- The alphabet of DNA is <em>four small molecules</em>: adenine, cytosine, guanine, thymine.
206
- Two purines (A, G, twin-ring) and two pyrimidines (C, T, single-ring). Everything
207
- that follows is built out of these.
208
  </p>
209
  </div>
210
  <div class="section-body">
@@ -261,9 +261,10 @@
261
  <div class="section-num">§3 · Gene</div>
262
  <div class="section-title">Promoter, exons, introns</div>
263
  <p class="lede">
264
- Most of the string is non-coding. The functional regions are organised into a
265
- <em>promoter</em> (where the cell starts reading), with the coding sequence broken into
266
- <em>exons</em> (kept) interrupted by <em>introns</em> (spliced out).
 
267
  </p>
268
  </div>
269
  <div class="section-body">
@@ -285,9 +286,9 @@
285
  <div class="section-num">§4 · RNA</div>
286
  <div class="section-title">Splicing into the working copy</div>
287
  <p class="lede">
288
- The gene gets read and copied into RNA, then the <em>introns are spliced out</em> and the
289
- <em>exons joined together</em>. What's left is the working mRNA: just the coding parts,
290
- in order. (And T is rewritten as U along the way: a small alphabet quirk between DNA
291
  and RNA.)
292
  </p>
293
  </div>
@@ -366,19 +367,20 @@
366
  </div>
367
  </div>
368
 
369
- <div class="tab-panel" id="panel-demo" data-tab="demo">
370
 
371
  <div class="tab-lede">
372
  <div class="tab-lede__rail">
373
  <span class="tab-lede__eyebrow">Intro</span>
374
  <p>
375
- <strong>Carbon</strong> is a 3-billion-parameter language model for DNA. We trained it
376
- on roughly 1&nbsp;trillion bases of genomic sequence with a single objective: given some
377
- DNA, predict what comes next (six bases at a time, autoregressively). That's it: no
378
- annotations, no labels, no biology curriculum. Just <em>read DNA, predict more DNA</em>.
 
379
  </p>
380
  <p class="tab-lede__note">
381
- The interesting question is what else falls out of that. We didn't tell Carbon what an
382
  exon is. We didn't tell it which mutations are pathogenic. We didn't tell it how genes
383
  differ between species. The sections below are ways to read what it picked up
384
  anyway: autocomplete a gene <a class="lede-chip" href="#completion">§1</a>, see
@@ -508,17 +510,17 @@
508
  <summary>Run this from code</summary>
509
  <div class="code-snippet__body">
510
  <div class="code-snippet__tabs">
511
- <button class="code-snippet__tab active" data-tab="endpoint" type="button">Endpoint</button>
512
- <button class="code-snippet__tab" data-tab="local" type="button">Local (transformers)</button>
513
  </div>
514
  <button class="code-snippet__copy" type="button">Copy</button>
515
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
516
  from openai import OpenAI
517
 
518
- # Carbon-3B is served behind a private HF inference endpoint; an HF token
519
- # with access is required (`huggingface-cli login` or HF_TOKEN env var).
520
  client = OpenAI(
521
- base_url="https://cr2l9w72ys5pp8le.us-east-1.aws.endpoints.huggingface.cloud/v1/",
522
  api_key=get_token(),
523
  )
524
 
@@ -630,15 +632,15 @@ print(tok.decode(new_ids))</code></pre></div>
630
  <summary>Run this from code</summary>
631
  <div class="code-snippet__body">
632
  <div class="code-snippet__tabs">
633
- <button class="code-snippet__tab active" data-tab="endpoint" type="button">Endpoint</button>
634
- <button class="code-snippet__tab" data-tab="local" type="button">Local (transformers)</button>
635
  </div>
636
  <button class="code-snippet__copy" type="button">Copy</button>
637
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
638
  from openai import OpenAI
639
 
640
  client = OpenAI(
641
- base_url="https://cr2l9w72ys5pp8le.us-east-1.aws.endpoints.huggingface.cloud/v1/",
642
  api_key=get_token(),
643
  )
644
 
@@ -744,15 +746,15 @@ for t, lp in zip(tok.convert_ids_to_tokens(ids[0, 1:].tolist()),
744
  <summary>Run this from code</summary>
745
  <div class="code-snippet__body">
746
  <div class="code-snippet__tabs">
747
- <button class="code-snippet__tab active" data-tab="endpoint" type="button">Endpoint</button>
748
- <button class="code-snippet__tab" data-tab="local" type="button">Local (transformers)</button>
749
  </div>
750
  <button class="code-snippet__copy" type="button">Copy</button>
751
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
752
  from openai import OpenAI
753
 
754
  client = OpenAI(
755
- base_url="https://cr2l9w72ys5pp8le.us-east-1.aws.endpoints.huggingface.cloud/v1/",
756
  api_key=get_token(),
757
  )
758
 
@@ -868,8 +870,8 @@ print(f"delta = {delta:+.2f} (less likely if negative)")</code></pre></div>
868
  <summary>Run this from code</summary>
869
  <div class="code-snippet__body">
870
  <div class="code-snippet__tabs">
871
- <button class="code-snippet__tab active" data-tab="endpoint" type="button">Endpoint</button>
872
- <button class="code-snippet__tab" data-tab="local" type="button">Local (transformers)</button>
873
  </div>
874
  <button class="code-snippet__copy" type="button">Copy</button>
875
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
@@ -877,7 +879,7 @@ from openai import OpenAI
877
  from concurrent.futures import ThreadPoolExecutor
878
 
879
  client = OpenAI(
880
- base_url="https://cr2l9w72ys5pp8le.us-east-1.aws.endpoints.huggingface.cloud/v1/",
881
  api_key=get_token(),
882
  )
883
 
@@ -1177,9 +1179,9 @@ for name, ids in zip(species_prefixes, new_ids):
1177
 
1178
  </div>
1179
 
1180
- </div> <!-- /panel-demo -->
1181
 
1182
- <div class="tab-panel" id="panel-model" data-tab="model">
1183
 
1184
  <div class="tab-lede">
1185
  <div class="tab-lede__rail">
@@ -1341,7 +1343,7 @@ for name, ids in zip(species_prefixes, new_ids):
1341
 
1342
  <div class="section-body">
1343
  <div class="demo" id="demo9">
1344
- <div class="seq-label" style="margin-top:0">corpus composition · 1T bases</div>
1345
  <div id="d9-bars" class="d9-bars" style="margin-bottom:22px"></div>
1346
 
1347
  <div class="seq-label">signal-to-noise · raw genome vs annotation-aware curation</div>
@@ -1526,7 +1528,7 @@ for name, ids in zip(species_prefixes, new_ids):
1526
 
1527
  </div>
1528
 
1529
- </div> <!-- /panel-model -->
1530
 
1531
  <!-- ============================================================ -->
1532
  <!-- TAB 3 · SANDBOX (the open-ended playground) -->
@@ -1554,17 +1556,18 @@ for name, ids in zip(species_prefixes, new_ids):
1554
  Same eyebrow + value pattern reused by the two card headers below so
1555
  the whole panel reads as a single layered stack rather than a flat
1556
  wall of controls. -->
1557
- <div class="sb-header">
1558
- <span class="sb-card__eyebrow">Connected to</span>
1559
- <div id="sb-meta" class="sb-header__meta">loading…</div>
1560
- </div>
1561
-
1562
  <!-- INPUT card: examples → prompt → controls → status. -->
1563
  <section class="sb-card">
1564
- <header class="sb-card__header">
1565
- <span class="sb-card__eyebrow">§ Input</span>
1566
- <h2 class="sb-card__title">Prompt</h2>
1567
- <p class="sb-card__hint">DNA prefix in <code>{A, C, G, T}</code>: pick an example or type your own.</p>
 
 
 
 
 
 
1568
  </header>
1569
 
1570
  <div class="sb-card__body">
@@ -1657,10 +1660,6 @@ for name, ids in zip(species_prefixes, new_ids):
1657
  </div>
1658
  </div> <!-- /panel-sandbox -->
1659
 
1660
- <footer>
1661
- <span style="color:#888">model:</span> <span id="footer-model">…</span>
1662
- </footer>
1663
-
1664
  <!-- Modular JS, served from /assets/js/. Load order matters because
1665
  section IIFEs reference shared globals (lerp, logprobRgb, GENES,
1666
  loadConfig, etc.) defined in shared/. Each file ends with its own
 
102
  that separates the banner from the page content (margin-bottom: -1px). -->
103
  <nav id="tab-nav" class="banner-tabs">
104
  <button class="tab active" data-tab="intro">Intro</button>
105
+ <button class="tab" data-tab="dna-lab">DNA Lab</button>
106
+ <button class="tab" data-tab="recipe">Carbon Recipe</button>
107
  <button class="tab" data-tab="sandbox">Sandbox</button>
108
  </nav>
109
  </div>
 
133
  </a>
134
  <div class="sticky-nav__tabs">
135
  <button class="tab active" data-tab="intro">Intro</button>
136
+ <button class="tab" data-tab="dna-lab">DNA Lab</button>
137
+ <button class="tab" data-tab="recipe">Carbon Recipe</button>
138
  <button class="tab" data-tab="sandbox">Sandbox</button>
139
  </div>
140
  </div>
 
173
  <ul class="intro-guide-list">
174
  <li><a href="#primer">Intro<span class="arrow" aria-hidden="true">↓</span></a>
175
  continue reading to learn the basics of genetics.</li>
176
+ <li><a href="#dna-lab">DNA Lab<span class="arrow" aria-hidden="true">→</span></a>
177
  explore what the model can do, with live interactions against the 3B checkpoint.</li>
178
+ <li><a href="#recipe">Carbon Recipe<span class="arrow" aria-hidden="true">→</span></a>
179
  learn how Carbon was trained: tokenizer, loss, dataset, results.</li>
180
  <li><a href="#sandbox">Sandbox<span class="arrow" aria-hidden="true">→</span></a>
181
  run Carbon on your own DNA sequences.</li>
 
202
  <div class="section-num">§1 · Bases</div>
203
  <div class="section-title">A four-letter alphabet</div>
204
  <p class="lede">
205
+ DNA is written in <em>four small molecules</em>: adenine, cytosine, guanine, thymine.
206
+ Two are purines (A and G, twin-ring), two are pyrimidines (C and T, single-ring).
207
+ Everything that follows is built from these four.
208
  </p>
209
  </div>
210
  <div class="section-body">
 
261
  <div class="section-num">§3 · Gene</div>
262
  <div class="section-title">Promoter, exons, introns</div>
263
  <p class="lede">
264
+ A gene is a stretch of DNA that the cell turns into protein. Most of the genome is
265
+ not. Each gene begins with a <em>promoter</em>, where the cell starts reading. What
266
+ follows is broken into two kinds of segment: <em>exons</em>, which the cell keeps,
267
+ and <em>introns</em>, which it splices out and often serve regulatory purposes.
268
  </p>
269
  </div>
270
  <div class="section-body">
 
286
  <div class="section-num">§4 · RNA</div>
287
  <div class="section-title">Splicing into the working copy</div>
288
  <p class="lede">
289
+ The cell copies the gene into RNA. Then it <em>splices out the introns</em> and
290
+ <em>joins the exons together</em>. What's left is the working mRNA: just the exons,
291
+ in order. (T is rewritten as U along the way: a small alphabet quirk between DNA
292
  and RNA.)
293
  </p>
294
  </div>
 
367
  </div>
368
  </div>
369
 
370
+ <div class="tab-panel" id="panel-dna-lab" data-tab="dna-lab">
371
 
372
  <div class="tab-lede">
373
  <div class="tab-lede__rail">
374
  <span class="tab-lede__eyebrow">Intro</span>
375
  <p>
376
+ <strong>Carbon-3B</strong> is a 3-billion-parameter language model for DNA. We trained it
377
+ on roughly 1&nbsp;trillion tokens (6&nbsp;trillion base pairs) of genomic sequence with a
378
+ single objective: given some DNA, predict what comes next (six bases at a time,
379
+ autoregressively). That's it: no annotations, no labels, no biology curriculum.
380
+ Just <em>read DNA, predict more DNA</em>.
381
  </p>
382
  <p class="tab-lede__note">
383
+ The interesting question is what else falls out of that. We didn't tell Carbon-3B what an
384
  exon is. We didn't tell it which mutations are pathogenic. We didn't tell it how genes
385
  differ between species. The sections below are ways to read what it picked up
386
  anyway: autocomplete a gene <a class="lede-chip" href="#completion">§1</a>, see
 
510
  <summary>Run this from code</summary>
511
  <div class="code-snippet__body">
512
  <div class="code-snippet__tabs">
513
+ <button class="code-snippet__tab active" data-tab="endpoint" type="button">API</button>
514
+ <button class="code-snippet__tab" data-tab="local" type="button">transformers</button>
515
  </div>
516
  <button class="code-snippet__copy" type="button">Copy</button>
517
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
518
  from openai import OpenAI
519
 
520
+ # Carbon-3B can be served behind any OpenAI-compatible API (vLLM, TGI, an
521
+ # HF inference endpoint, etc.). Point base_url at your deployment.
522
  client = OpenAI(
523
+ base_url="https://&lt;your-endpoint&gt;/v1/",
524
  api_key=get_token(),
525
  )
526
 
 
632
  <summary>Run this from code</summary>
633
  <div class="code-snippet__body">
634
  <div class="code-snippet__tabs">
635
+ <button class="code-snippet__tab active" data-tab="endpoint" type="button">API</button>
636
+ <button class="code-snippet__tab" data-tab="local" type="button">transformers</button>
637
  </div>
638
  <button class="code-snippet__copy" type="button">Copy</button>
639
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
640
  from openai import OpenAI
641
 
642
  client = OpenAI(
643
+ base_url="https://&lt;your-endpoint&gt;/v1/",
644
  api_key=get_token(),
645
  )
646
 
 
746
  <summary>Run this from code</summary>
747
  <div class="code-snippet__body">
748
  <div class="code-snippet__tabs">
749
+ <button class="code-snippet__tab active" data-tab="endpoint" type="button">API</button>
750
+ <button class="code-snippet__tab" data-tab="local" type="button">transformers</button>
751
  </div>
752
  <button class="code-snippet__copy" type="button">Copy</button>
753
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
754
  from openai import OpenAI
755
 
756
  client = OpenAI(
757
+ base_url="https://&lt;your-endpoint&gt;/v1/",
758
  api_key=get_token(),
759
  )
760
 
 
870
  <summary>Run this from code</summary>
871
  <div class="code-snippet__body">
872
  <div class="code-snippet__tabs">
873
+ <button class="code-snippet__tab active" data-tab="endpoint" type="button">API</button>
874
+ <button class="code-snippet__tab" data-tab="local" type="button">transformers</button>
875
  </div>
876
  <button class="code-snippet__copy" type="button">Copy</button>
877
  <div class="code-snippet__panel active" data-tab="endpoint"><pre><code>from huggingface_hub import get_token
 
879
  from concurrent.futures import ThreadPoolExecutor
880
 
881
  client = OpenAI(
882
+ base_url="https://&lt;your-endpoint&gt;/v1/",
883
  api_key=get_token(),
884
  )
885
 
 
1179
 
1180
  </div>
1181
 
1182
+ </div> <!-- /panel-dna-lab -->
1183
 
1184
+ <div class="tab-panel" id="panel-recipe" data-tab="recipe">
1185
 
1186
  <div class="tab-lede">
1187
  <div class="tab-lede__rail">
 
1343
 
1344
  <div class="section-body">
1345
  <div class="demo" id="demo9">
1346
+ <div class="seq-label" style="margin-top:0">corpus composition · 1T tokens (6T base pairs)</div>
1347
  <div id="d9-bars" class="d9-bars" style="margin-bottom:22px"></div>
1348
 
1349
  <div class="seq-label">signal-to-noise · raw genome vs annotation-aware curation</div>
 
1528
 
1529
  </div>
1530
 
1531
+ </div> <!-- /panel-recipe -->
1532
 
1533
  <!-- ============================================================ -->
1534
  <!-- TAB 3 · SANDBOX (the open-ended playground) -->
 
1556
  Same eyebrow + value pattern reused by the two card headers below so
1557
  the whole panel reads as a single layered stack rather than a flat
1558
  wall of controls. -->
 
 
 
 
 
1559
  <!-- INPUT card: examples → prompt → controls → status. -->
1560
  <section class="sb-card">
1561
+ <header class="sb-card__header sb-card__header--with-meta">
1562
+ <div class="sb-card__heading">
1563
+ <span class="sb-card__eyebrow">§ Input</span>
1564
+ <h2 class="sb-card__title">Prompt</h2>
1565
+ <p class="sb-card__hint">DNA prefix in <code>{A, C, G, T}</code>: pick an example or type your own.</p>
1566
+ </div>
1567
+ <div class="sb-card__meta">
1568
+ <span class="sb-card__eyebrow">Connected to</span>
1569
+ <div id="sb-meta" class="sb-header__meta">loading…</div>
1570
+ </div>
1571
  </header>
1572
 
1573
  <div class="sb-card__body">
 
1660
  </div>
1661
  </div> <!-- /panel-sandbox -->
1662
 
 
 
 
 
1663
  <!-- Modular JS, served from /assets/js/. Load order matters because
1664
  section IIFEs reference shared globals (lerp, logprobRgb, GENES,
1665
  loadConfig, etc.) defined in shared/. Each file ends with its own