Spaces:
Running
Integrate editorial banner as hero, adopt warm paper palette
Browse filesThe new banner (animated DNA helix + CARBON-0 wordmark + spec line)
replaces the per-tab logo+title hero blocks. Page palette shifts from
cool gray (#fafafa / #1a1a1a / #1a8a3a / #d83a2a) to the banner's
warm paper tone (#f7f5ee / #1f1f1d / #317f3f / #bc2e25) — including
the corresponding RGB tuples used in the JS logprob coloring so the
sequence-rendering colors stay aligned with the page chrome.
Banner is scoped under .carbon-banner with cb-* prefixed selectors
and SVG IDs to avoid collisions; aspect-ratio 2048:620 preserved.
Double frame removed (kept the outer container border, dropped the
inner stroke rect). Per-tab heroes collapsed into a single lede
paragraph since the banner already provides identity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@@ -11,7 +11,7 @@
|
|
| 11 |
body {
|
| 12 |
font-family: "Inter", "Helvetica Neue", sans-serif;
|
| 13 |
font-size: 13px; font-weight: 300; line-height: 1.7;
|
| 14 |
-
color: #
|
| 15 |
padding: 0;
|
| 16 |
}
|
| 17 |
.container { max-width: 760px; margin: 0 auto; padding: 48px 32px 96px; }
|
|
@@ -22,7 +22,7 @@
|
|
| 22 |
border-bottom: 1px solid #ccc; /* separator line under the tab strip */
|
| 23 |
padding: 24px 32px 0; /* no bottom padding — tabs sit on the line */
|
| 24 |
margin-bottom: 0;
|
| 25 |
-
background: #
|
| 26 |
position: sticky; top: 0; z-index: 10;
|
| 27 |
}
|
| 28 |
.header-inner {
|
|
@@ -58,10 +58,10 @@
|
|
| 58 |
cursor: pointer; transition: background 0.15s, color 0.15s;
|
| 59 |
}
|
| 60 |
nav#tab-nav .tab + .tab { margin-left: -1px; } /* shared border between adjacent tabs */
|
| 61 |
-
nav#tab-nav .tab:hover { color: #
|
| 62 |
nav#tab-nav .tab.active {
|
| 63 |
-
color: #
|
| 64 |
-
border-bottom-color: #
|
| 65 |
z-index: 2;
|
| 66 |
}
|
| 67 |
|
|
@@ -69,33 +69,89 @@
|
|
| 69 |
.tab-panel { display: none; }
|
| 70 |
.tab-panel.active { display: block; }
|
| 71 |
|
| 72 |
-
/*
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
display: block;
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
/* --- Sections --- */
|
|
@@ -111,11 +167,11 @@
|
|
| 111 |
padding: 80px 32px 48px;
|
| 112 |
border-top: 1px solid #eee;
|
| 113 |
border-bottom: 1px solid #eee;
|
| 114 |
-
background: linear-gradient(to bottom, rgba(
|
| 115 |
}
|
| 116 |
.part-divider .part-eyebrow {
|
| 117 |
font-family: "JetBrains Mono", monospace;
|
| 118 |
-
font-size: 10px; color: #
|
| 119 |
letter-spacing: 4px; text-transform: uppercase;
|
| 120 |
margin-bottom: 6px;
|
| 121 |
}
|
|
@@ -130,7 +186,7 @@
|
|
| 130 |
|
| 131 |
.section-num {
|
| 132 |
font-family: "JetBrains Mono", monospace;
|
| 133 |
-
font-size: 10px; color: #
|
| 134 |
letter-spacing: 2px; text-transform: uppercase;
|
| 135 |
margin-bottom: 8px;
|
| 136 |
}
|
|
@@ -138,7 +194,7 @@
|
|
| 138 |
font-family: "JetBrains Mono", monospace;
|
| 139 |
font-size: 22px; font-weight: 400; letter-spacing: -0.3px;
|
| 140 |
margin-bottom: 24px;
|
| 141 |
-
color: #
|
| 142 |
}
|
| 143 |
.lede {
|
| 144 |
color: #444; font-size: 14px; margin-bottom: 32px;
|
|
@@ -146,14 +202,14 @@
|
|
| 146 |
}
|
| 147 |
.takeaway {
|
| 148 |
margin-top: 32px;
|
| 149 |
-
padding: 16px 20px; border-left: 3px solid #
|
| 150 |
background: #f4f8f4; color: #333;
|
| 151 |
font-size: 13px; max-width: 640px;
|
| 152 |
}
|
| 153 |
.takeaway strong {
|
| 154 |
font-family: "JetBrains Mono", monospace;
|
| 155 |
font-weight: 500; letter-spacing: 1px; text-transform: uppercase;
|
| 156 |
-
font-size: 10px; color: #
|
| 157 |
}
|
| 158 |
|
| 159 |
/* --- Demo card (the interactive box inside each section) --- */
|
|
@@ -178,12 +234,12 @@
|
|
| 178 |
text-transform: uppercase; letter-spacing: 1.5px;
|
| 179 |
transition: all 0.15s;
|
| 180 |
}
|
| 181 |
-
button.action:hover, .pill:hover { border-color: #888; color: #
|
| 182 |
-
button.action.primary { background: #
|
| 183 |
button.action.primary:hover { background: #000; }
|
| 184 |
button.action:disabled { opacity: 0.4; cursor: not-allowed; }
|
| 185 |
button.action.primary:disabled { background: #888; border-color: #888; }
|
| 186 |
-
.pill.active { background: #
|
| 187 |
.pills { display: inline-flex; gap: 4px; }
|
| 188 |
.pills .pill { font-size: 9px; padding: 4px 8px; }
|
| 189 |
|
|
@@ -207,15 +263,15 @@
|
|
| 207 |
margin: 4px 0 12px;
|
| 208 |
min-height: 14px;
|
| 209 |
}
|
| 210 |
-
.gene-info strong { color: #
|
| 211 |
.gene-track {
|
| 212 |
width: 100%; height: 28px; display: block;
|
| 213 |
margin: 4px 0 8px;
|
| 214 |
}
|
| 215 |
-
.gene-track .exon { fill: #
|
| 216 |
.gene-track .intron { stroke: #aaa; stroke-width: 1; }
|
| 217 |
-
.gene-track .playhead { stroke: #
|
| 218 |
-
.gene-track .gen-region { fill: #
|
| 219 |
.gene-track text { font-family: "JetBrains Mono", monospace; font-size: 9px; fill: #888; }
|
| 220 |
.track-axis-label {
|
| 221 |
font-family: "JetBrains Mono", monospace; font-size: 9px;
|
|
@@ -238,11 +294,11 @@
|
|
| 238 |
}
|
| 239 |
.stat-pair { display: flex; flex-direction: column; gap: 2px; }
|
| 240 |
.stat-pair-label { font-size: 9px; color: #999; text-transform: uppercase; letter-spacing: 1.2px; }
|
| 241 |
-
.stat-pair-val { color: #
|
| 242 |
.stat-pair-val.muted { color: #aaa; }
|
| 243 |
|
| 244 |
/* Mismatch highlighting in reference row */
|
| 245 |
-
.ref-mismatch { background: rgba(
|
| 246 |
.ref-match { color: #999; }
|
| 247 |
|
| 248 |
/* Model section responsive overrides */
|
|
@@ -277,19 +333,19 @@
|
|
| 277 |
background: #fff; color: #666; cursor: pointer;
|
| 278 |
transition: all 0.15s;
|
| 279 |
}
|
| 280 |
-
#panel-sandbox .sb-ex-btn:hover { border-color: #888; color: #
|
| 281 |
#panel-sandbox .sb-ex-btn .sb-ex-label {
|
| 282 |
color: #aaa; font-size: 9px; margin-left: 6px; text-transform: uppercase;
|
| 283 |
letter-spacing: 0.5px;
|
| 284 |
}
|
| 285 |
#panel-sandbox .sb-prompt-area, #panel-sandbox input[type=number] {
|
| 286 |
font-family: "JetBrains Mono", monospace;
|
| 287 |
-
font-size: 12px; font-weight: 300; color: #
|
| 288 |
background: #fff; border: 1px solid #ddd; border-radius: 3px;
|
| 289 |
padding: 8px 12px; outline: none; transition: border 0.15s;
|
| 290 |
}
|
| 291 |
#panel-sandbox .sb-prompt-area:focus,
|
| 292 |
-
#panel-sandbox input[type=number]:focus { border-color: #
|
| 293 |
#panel-sandbox .sb-prompt-area {
|
| 294 |
width: 100%; resize: none; overflow: hidden;
|
| 295 |
letter-spacing: 1px; line-height: 1.7;
|
|
@@ -323,8 +379,8 @@
|
|
| 323 |
}
|
| 324 |
#panel-sandbox .sb-mode-btn:first-child { border-radius: 3px 0 0 3px; }
|
| 325 |
#panel-sandbox .sb-mode-btn:last-child { border-right: 1px solid #ccc; border-radius: 0 3px 3px 0; }
|
| 326 |
-
#panel-sandbox .sb-mode-btn:hover { color: #
|
| 327 |
-
#panel-sandbox .sb-mode-btn.active { background: #
|
| 328 |
|
| 329 |
#panel-sandbox .sb-button-row { margin-left: auto; display: flex; gap: 6px; }
|
| 330 |
|
|
@@ -339,7 +395,7 @@
|
|
| 339 |
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
| 340 |
background: #888; margin-right: 6px; vertical-align: middle;
|
| 341 |
}
|
| 342 |
-
#panel-sandbox .sb-status.streaming .dot { background: #
|
| 343 |
|
| 344 |
#panel-sandbox .sb-output-row {
|
| 345 |
display: grid;
|
|
@@ -361,9 +417,9 @@
|
|
| 361 |
text-transform: uppercase; letter-spacing: 1px;
|
| 362 |
transition: all 0.15s;
|
| 363 |
}
|
| 364 |
-
#panel-sandbox .sb-copy-btn:hover { border-color: #888; color: #
|
| 365 |
#panel-sandbox .sb-copy-btn:disabled { opacity: 0; pointer-events: none; }
|
| 366 |
-
#panel-sandbox .sb-copy-btn.copied { background: #
|
| 367 |
|
| 368 |
#panel-sandbox .sb-seq-block {
|
| 369 |
font-family: "JetBrains Mono", monospace;
|
|
@@ -379,7 +435,7 @@
|
|
| 379 |
#panel-sandbox .sb-seq-line.tail::after {
|
| 380 |
content: "";
|
| 381 |
display: inline-block; width: 7px; height: 14px;
|
| 382 |
-
background: #
|
| 383 |
margin-left: 2px;
|
| 384 |
animation: blink 1s step-end infinite;
|
| 385 |
}
|
|
@@ -401,7 +457,7 @@
|
|
| 401 |
text-transform: uppercase; letter-spacing: 1.2px; font-weight: 300;
|
| 402 |
}
|
| 403 |
#panel-sandbox .sb-stat-value {
|
| 404 |
-
font-size: 12px; font-weight: 400; color: #
|
| 405 |
font-variant-numeric: tabular-nums;
|
| 406 |
}
|
| 407 |
#panel-sandbox .sb-stat-value .sb-unit { font-size: 9px; color: #999; margin-left: 3px; font-weight: 300; }
|
|
@@ -418,7 +474,7 @@
|
|
| 418 |
#panel-sandbox .sb-legend.show { display: block; }
|
| 419 |
#panel-sandbox .sb-legend-bar {
|
| 420 |
height: 6px; margin: 4px 0 3px;
|
| 421 |
-
background: linear-gradient(to right, #
|
| 422 |
border-radius: 1px;
|
| 423 |
}
|
| 424 |
#panel-sandbox .sb-legend-row { display: flex; justify-content: space-between; }
|
|
@@ -437,8 +493,8 @@
|
|
| 437 |
text-align: center;
|
| 438 |
}
|
| 439 |
.vep-window .ctx { color: #888; }
|
| 440 |
-
.vep-window .var-ref { background: rgba(
|
| 441 |
-
.vep-window .var-alt { background: rgba(
|
| 442 |
.vep-result {
|
| 443 |
display: grid; grid-template-columns: 100px 1fr 80px;
|
| 444 |
gap: 8px 12px; align-items: center;
|
|
@@ -447,14 +503,14 @@
|
|
| 447 |
.vep-result .row-label { color: #666; text-transform: uppercase; letter-spacing: 1px; font-size: 10px; }
|
| 448 |
.vep-result .row-bar { height: 14px; background: #f0f0f0; border-radius: 2px; position: relative; overflow: hidden; }
|
| 449 |
.vep-result .row-bar .fill { position: absolute; top: 0; bottom: 0; left: 0; }
|
| 450 |
-
.vep-result .row-bar.ref .fill { background: #
|
| 451 |
-
.vep-result .row-bar.alt .fill { background: #
|
| 452 |
-
.vep-result .row-val { text-align: right; color: #
|
| 453 |
.vep-result .row-delta { font-weight: 500; }
|
| 454 |
|
| 455 |
-
.pill.sig-Pathogenic { border-left: 3px solid #
|
| 456 |
.pill.sig-Risk { border-left: 3px solid #e69500; }
|
| 457 |
-
.pill.sig-Benign { border-left: 3px solid #
|
| 458 |
|
| 459 |
/* --- Species rows (§4) --- */
|
| 460 |
.species-row {
|
|
@@ -471,7 +527,7 @@
|
|
| 471 |
font-size: 11px;
|
| 472 |
}
|
| 473 |
.species-name {
|
| 474 |
-
font-weight: 500; color: #
|
| 475 |
text-transform: uppercase; letter-spacing: 1px; font-size: 11px;
|
| 476 |
border-left: 4px solid;
|
| 477 |
padding-left: 8px;
|
|
@@ -481,11 +537,11 @@
|
|
| 481 |
text-align: right; font-family: "JetBrains Mono", monospace;
|
| 482 |
font-size: 11px; color: #666;
|
| 483 |
}
|
| 484 |
-
.species-stats .stat-id { font-size: 16px; color: #
|
| 485 |
.species-stats .stat-sub { font-size: 10px; color: #999; margin-top: 2px; }
|
| 486 |
.species-seq {
|
| 487 |
font-family: "JetBrains Mono", monospace;
|
| 488 |
-
background: #
|
| 489 |
padding: 8px 12px; overflow-x: auto;
|
| 490 |
white-space: pre; font-size: 11px;
|
| 491 |
line-height: 1.7; letter-spacing: 0.5px;
|
|
@@ -501,7 +557,7 @@
|
|
| 501 |
border-top: 1px solid #eee;
|
| 502 |
}
|
| 503 |
footer a { color: #666; text-decoration: none; }
|
| 504 |
-
footer a:hover { color: #
|
| 505 |
|
| 506 |
/* --- Sequence display (shared with sandbox) --- */
|
| 507 |
.seq-block {
|
|
@@ -526,7 +582,7 @@
|
|
| 526 |
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
| 527 |
background: #888;
|
| 528 |
}
|
| 529 |
-
.status.streaming .dot { background: #
|
| 530 |
.status.error { color: #b00020; }
|
| 531 |
@keyframes pulse { 50% { opacity: 0.3; } }
|
| 532 |
|
|
@@ -551,16 +607,73 @@
|
|
| 551 |
</div>
|
| 552 |
</header>
|
| 553 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
<div class="tab-panel active" id="panel-demo" data-tab="demo">
|
| 555 |
|
| 556 |
-
<div class="
|
| 557 |
-
<
|
| 558 |
-
<img src="/img/logo.png" alt="Carbon" class="hero-logo">
|
| 559 |
-
<div class="hero-text">
|
| 560 |
-
<h2>A model that learned <em>biology</em> from raw DNA — no labels, no annotations, just sequence.</h2>
|
| 561 |
-
<p>Carbon is a 3B-parameter autoregressive model trained on a trillion bases of genomic DNA. We didn't tell it what an exon is. We didn't tell it which mutations are pathogenic. We didn't tell it how genes differ between species. Below, four ways to see what it picked up anyway.</p>
|
| 562 |
-
</div>
|
| 563 |
-
</div>
|
| 564 |
</div>
|
| 565 |
|
| 566 |
<div class="container wide">
|
|
@@ -604,10 +717,10 @@
|
|
| 604 |
<div class="gene-info" id="d1-info">loading genes…</div>
|
| 605 |
<svg class="gene-track" id="d1-track" viewBox="0 0 1000 28" preserveAspectRatio="none"></svg>
|
| 606 |
<div class="track-axis-label">
|
| 607 |
-
<span><span class="legend-swatch" style="background:#
|
| 608 |
<span><span class="legend-swatch" style="background:#aaa;height:2px;border-radius:0"></span>intron</span>
|
| 609 |
-
<span><span class="legend-swatch" style="background:rgba(
|
| 610 |
-
<span style="color:#
|
| 611 |
</div>
|
| 612 |
|
| 613 |
<div class="seq-label">model output · <span style="color:#aaa">prompt in gray</span> · <span>generated colored by logprob (red=uncertain)</span></div>
|
|
@@ -666,9 +779,9 @@
|
|
| 666 |
<svg id="d2-bars" style="display:block;width:100%;height:auto;background:#fff;border:1px solid #eee;margin-top:12px" preserveAspectRatio="xMinYMin meet"></svg>
|
| 667 |
|
| 668 |
<div class="track-axis-label">
|
| 669 |
-
<span><span class="legend-swatch" style="background:#
|
| 670 |
<span><span class="legend-swatch" style="background:#e69500;border-radius:50%"></span>risk</span>
|
| 671 |
-
<span><span class="legend-swatch" style="background:#
|
| 672 |
<span style="color:#888">dot = ClinVar label · bar = model signal</span>
|
| 673 |
</div>
|
| 674 |
</div>
|
|
@@ -683,7 +796,7 @@
|
|
| 683 |
likely culprit is allele frequency: alt alleles common enough in human populations look
|
| 684 |
perfectly normal to a model trained on natural sequence. For sharper variant effect
|
| 685 |
prediction, Carbon can be fine-tuned (see the
|
| 686 |
-
<a href="https://huggingface.co/spaces/hf-carbon/dna-vep-explainer" style="color:#
|
| 687 |
</div>
|
| 688 |
</section>
|
| 689 |
|
|
@@ -713,7 +826,7 @@
|
|
| 713 |
<svg class="gene-track" id="d3-track" viewBox="0 0 1000 28" preserveAspectRatio="none"></svg>
|
| 714 |
<svg id="d3-chart" style="display:block;width:100%;height:140px;background:#fff;border:1px solid #eee;margin-top:6px" preserveAspectRatio="none" viewBox="0 0 1000 140"></svg>
|
| 715 |
<div class="track-axis-label">
|
| 716 |
-
<span><span class="legend-swatch" style="background:#
|
| 717 |
<span style="color:#aaa">y-axis: log P per 6-bp token (higher = more confident)</span>
|
| 718 |
<span id="d3-bp-label" style="color:#888">0 bp</span>
|
| 719 |
</div>
|
|
@@ -773,7 +886,7 @@
|
|
| 773 |
|
| 774 |
<div class="track-axis-label" style="margin-top:14px">
|
| 775 |
<span style="color:#aaa">prompt in gray</span>
|
| 776 |
-
<span style="color:#
|
| 777 |
<span style="color:#b00020">mismatches in reference highlighted</span>
|
| 778 |
</div>
|
| 779 |
</div>
|
|
@@ -831,14 +944,8 @@
|
|
| 831 |
|
| 832 |
<div class="tab-panel" id="panel-model" data-tab="model">
|
| 833 |
|
| 834 |
-
<div class="
|
| 835 |
-
<
|
| 836 |
-
<img src="/img/logo.png" alt="Carbon" class="hero-logo">
|
| 837 |
-
<div class="hero-text">
|
| 838 |
-
<h2>How Carbon was <em>built</em> — what's specific to DNA, not the standard LLM playbook.</h2>
|
| 839 |
-
<p>Three places where the recipe needed adjustment for biology: the way DNA gets tokenized, how the loss handles near-miss tokens, and which sequence ends up in the corpus. Plus a deliberately vanilla architecture so any improvement can be attributed to the recipe rather than custom blocks.</p>
|
| 840 |
-
</div>
|
| 841 |
-
</div>
|
| 842 |
</div>
|
| 843 |
|
| 844 |
<div class="container wide">
|
|
@@ -865,7 +972,7 @@
|
|
| 865 |
value="ATGGCCAAGCTGACCAGCGAGCTGCTGGCC"
|
| 866 |
style="font-family:'JetBrains Mono',monospace;font-size:12px;padding:6px 10px;border:1px solid #ccc;border-radius:3px;flex:1 1 280px;letter-spacing:1px;text-transform:uppercase">
|
| 867 |
<span class="spacer"></span>
|
| 868 |
-
<span class="status"><span class="dot" style="background:#
|
| 869 |
</div>
|
| 870 |
|
| 871 |
<div id="d7-cols" style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-top:8px">
|
|
@@ -893,8 +1000,8 @@
|
|
| 893 |
|
| 894 |
<div class="track-axis-label">
|
| 895 |
<span>same DNA span</span>
|
| 896 |
-
<span style="color:#
|
| 897 |
-
<span id="d7-speedup" style="color:#
|
| 898 |
</div>
|
| 899 |
</div>
|
| 900 |
|
|
@@ -982,7 +1089,7 @@
|
|
| 982 |
<div class="seq-label">signal-to-noise · raw genome vs annotation-aware curation</div>
|
| 983 |
<svg id="d9-snr" viewBox="0 0 1000 100" preserveAspectRatio="none" style="display:block;width:100%;height:90px;background:#fff;border:1px solid #eee"></svg>
|
| 984 |
<div class="track-axis-label">
|
| 985 |
-
<span><span class="legend-swatch" style="background:#
|
| 986 |
<span><span class="legend-swatch" style="background:#ddd"></span>background</span>
|
| 987 |
<span style="color:#888">curating raises the density of biological signal in the gradient</span>
|
| 988 |
</div>
|
|
@@ -1037,14 +1144,8 @@
|
|
| 1037 |
<!-- ============================================================ -->
|
| 1038 |
<div class="tab-panel" id="panel-sandbox" data-tab="sandbox">
|
| 1039 |
|
| 1040 |
-
<div class="
|
| 1041 |
-
<
|
| 1042 |
-
<img src="/img/logo.png" alt="Carbon" class="hero-logo">
|
| 1043 |
-
<div class="hero-text">
|
| 1044 |
-
<h2>Run Carbon <em>yourself</em>.</h2>
|
| 1045 |
-
<p>Open-ended DNA continuation. Type any prefix in {A, C, G, T}, watch the model continue token by token. Toggle base-coloring or per-token logprob coloring to see where the model is confident and where it's guessing. Track GC content, perplexity, and throughput live.</p>
|
| 1046 |
-
</div>
|
| 1047 |
-
</div>
|
| 1048 |
</div>
|
| 1049 |
|
| 1050 |
<div class="container" style="max-width:1280px">
|
|
@@ -1134,9 +1235,9 @@
|
|
| 1134 |
// =========================================================================
|
| 1135 |
// Shared helpers
|
| 1136 |
// =========================================================================
|
| 1137 |
-
const DARK_RGB = [
|
| 1138 |
const MID_RGB = [136, 136, 136];
|
| 1139 |
-
const RED_RGB = [
|
| 1140 |
const PROMPT_RGB = [170, 170, 170];
|
| 1141 |
|
| 1142 |
function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
|
|
@@ -1346,7 +1447,7 @@ async function loadGenes() {
|
|
| 1346 |
const matches = genText[genIdx] === base;
|
| 1347 |
return matches
|
| 1348 |
? { style: "color:#bbb" }
|
| 1349 |
-
: { style: "color:#b00020;background:rgba(
|
| 1350 |
};
|
| 1351 |
renderSeq(els.ref, refSeq, bpl, colorRef);
|
| 1352 |
}
|
|
@@ -1557,7 +1658,7 @@ async function loadGenes() {
|
|
| 1557 |
const wRef = (Math.abs(c.refSum) / range) * 100;
|
| 1558 |
const wAlt = (Math.abs(c.altSum) / range) * 100;
|
| 1559 |
const delta = c.altSum - c.refSum;
|
| 1560 |
-
const dColor = delta < -0.5 ? "#
|
| 1561 |
els.result.innerHTML = `
|
| 1562 |
<div class="row-label">ref · ${v.ref}</div>
|
| 1563 |
<div class="row-bar ref"><div class="fill" style="width:${wRef.toFixed(1)}%"></div></div>
|
|
@@ -1592,7 +1693,7 @@ async function loadGenes() {
|
|
| 1592 |
const innerW = W - padL - padR;
|
| 1593 |
const center = padL + innerW / 2;
|
| 1594 |
const scale = (innerW / 2) / absMax;
|
| 1595 |
-
const sigColor = (s) => s === "Pathogenic" ? "#
|
| 1596 |
|
| 1597 |
// Bar color: encode the *model's* opinion of the alt allele
|
| 1598 |
// - Δ < 0 : red (model finds alt unusual). Saturation ~ |Δ|.
|
|
@@ -1614,13 +1715,13 @@ async function loadGenes() {
|
|
| 1614 |
let svg = "";
|
| 1615 |
|
| 1616 |
// --- Top axis: directional caption ---
|
| 1617 |
-
svg += `<text x="${padL.toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#
|
| 1618 |
svg += `<text x="${(W - padR).toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#333" letter-spacing="1" text-anchor="end">MODEL FINE WITH ALT →</text>`;
|
| 1619 |
svg += `<text x="${center.toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#888" text-anchor="middle" letter-spacing="1">Δ (alt − ref)</text>`;
|
| 1620 |
svg += `<text x="${(padL - 12).toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#888" text-anchor="end" letter-spacing="1">VARIANT</text>`;
|
| 1621 |
|
| 1622 |
// Faint shading: pathogenic-expected zone (left of 0)
|
| 1623 |
-
svg += `<rect x="${padL.toFixed(1)}" y="${(padT - 4).toFixed(1)}" width="${(center - padL).toFixed(1)}" height="${(ordered.length * rowH + 8).toFixed(1)}" fill="#
|
| 1624 |
|
| 1625 |
// Center line
|
| 1626 |
svg += `<line x1="${center}" y1="${padT - 4}" x2="${center}" y2="${H - padB + 4}" stroke="#bbb" stroke-width="1"/>`;
|
|
@@ -1653,7 +1754,7 @@ async function loadGenes() {
|
|
| 1653 |
const color = barColor(d);
|
| 1654 |
const barX = Math.min(center, x);
|
| 1655 |
const barW = Math.max(2, Math.abs(x - center));
|
| 1656 |
-
svg += `<rect x="${barX.toFixed(1)}" y="${(y - 8).toFixed(1)}" width="${barW.toFixed(1)}" height="14" fill="${color}" stroke="${v === selected ? '#
|
| 1657 |
|
| 1658 |
const label = (d >= 0 ? "+" : "") + d.toFixed(2);
|
| 1659 |
const insideOK = barW >= VALUE_INSIDE_MIN && Math.abs(d) >= 0.5; // color is dark enough only away from neutral
|
|
@@ -1876,7 +1977,7 @@ async function loadGenes() {
|
|
| 1876 |
if (e.start > scoredLen) continue;
|
| 1877 |
const x = xScale(e.start);
|
| 1878 |
const w = xScale(Math.min(e.end, scoredLen)) - x;
|
| 1879 |
-
svg += `<rect x="${x.toFixed(1)}" y="0" width="${Math.max(1, w).toFixed(1)}" height="${H}" fill="#
|
| 1880 |
}
|
| 1881 |
|
| 1882 |
// Smoothed line: a moving average over the points (window=5)
|
|
@@ -1901,7 +2002,7 @@ async function loadGenes() {
|
|
| 1901 |
smoothed.forEach((p, i) => {
|
| 1902 |
d += (i === 0 ? "M" : "L") + xScale(p.pos).toFixed(1) + " " + yScale(p.lp).toFixed(1);
|
| 1903 |
});
|
| 1904 |
-
svg += `<path d="${d}" fill="none" stroke="#
|
| 1905 |
|
| 1906 |
// Y-axis ticks
|
| 1907 |
const tickLps = [yMin + (yMax - yMin) * 0.1, yMin + (yMax - yMin) * 0.5, yMin + (yMax - yMin) * 0.9];
|
|
@@ -2151,7 +2252,7 @@ async function loadGenes() {
|
|
| 2151 |
const matches = genText[genIdx] === base;
|
| 2152 |
return matches
|
| 2153 |
? { style: "color:#bbb" }
|
| 2154 |
-
: { style: "color:#b00020;background:rgba(
|
| 2155 |
};
|
| 2156 |
const bpl2 = basesPerLine(refEl);
|
| 2157 |
renderSeq(refEl, refSeq, bpl2, colorRef);
|
|
@@ -2297,7 +2398,7 @@ async function loadGenes() {
|
|
| 2297 |
speedup: document.getElementById("d7-speedup"),
|
| 2298 |
};
|
| 2299 |
// 8-color palette for 6-mer tokens (cycle); 1-mer uses base coloring.
|
| 2300 |
-
const TOKEN_PALETTE = ["#
|
| 2301 |
const BASE_FILL = { A:"#3a8a3e", C:"#2e6bb8", G:"#b5891e", T:"#b53a3a", N:"#888" };
|
| 2302 |
|
| 2303 |
function clean(s) { return (s || "").toUpperCase().replace(/[^ACGTN]/g, ""); }
|
|
@@ -2340,7 +2441,7 @@ async function loadGenes() {
|
|
| 2340 |
let svg = "";
|
| 2341 |
const rows = [
|
| 2342 |
{ label: "1-mer", n: n1, cost: n1 * n1, color: "#888" },
|
| 2343 |
-
{ label: "6-mer", n: n6, cost: n6 * n6, color: "#
|
| 2344 |
];
|
| 2345 |
rows.forEach((r, i) => {
|
| 2346 |
const y = padT + i * (rowH + 8);
|
|
@@ -2404,8 +2505,8 @@ async function loadGenes() {
|
|
| 2404 |
html += `<div style="display:grid;grid-template-columns:140px 1fr 1fr;gap:8px 14px;align-items:center;font-family:'JetBrains Mono',monospace;font-size:11px">`;
|
| 2405 |
// Header
|
| 2406 |
html += `<div></div>`;
|
| 2407 |
-
html += `<div style="font-size:9px;color:${mode==='fns'?'#aaa':'#
|
| 2408 |
-
html += `<div style="font-size:9px;color:${mode==='ce'?'#aaa':'#
|
| 2409 |
|
| 2410 |
cands.forEach(c => {
|
| 2411 |
const isTarget = c === t1Equal(c, target);
|
|
@@ -2413,25 +2514,25 @@ async function loadGenes() {
|
|
| 2413 |
let badges = "";
|
| 2414 |
for (let i = 0; i < 6; i++) {
|
| 2415 |
const match = c[i] === target[i];
|
| 2416 |
-
const color = match ? "#
|
| 2417 |
-
const bg = match ? "rgba(
|
| 2418 |
badges += `<span style="display:inline-block;background:${bg};color:${color};padding:2px 5px;margin:1px;border-radius:2px;font-weight:${match?500:400}">${c[i]}</span>`;
|
| 2419 |
}
|
| 2420 |
const isExact = c === target;
|
| 2421 |
const labelText = isExact ? "exact target" : `${nMatches(c, target)}/6 match`;
|
| 2422 |
html += `<div style="display:flex;flex-direction:column;gap:2px">
|
| 2423 |
<div>${badges}</div>
|
| 2424 |
-
<div style="font-size:9px;color:${isExact?'#
|
| 2425 |
</div>`;
|
| 2426 |
|
| 2427 |
// CE column
|
| 2428 |
const ceVal = ceCredit(c, target);
|
| 2429 |
-
html += creditCell(ceVal, mode === "fns", c === target ? "credit = 1" : "credit = 0", "#
|
| 2430 |
|
| 2431 |
// FNS column
|
| 2432 |
const fnsVal = fnsCredit(c, target);
|
| 2433 |
const fnsLabel = c === target ? "credit = 1" : `credit = ${fnsVal.toFixed(2)} (${nMatches(c, target)}/6)`;
|
| 2434 |
-
html += creditCell(fnsVal, mode === "ce", fnsLabel, "#
|
| 2435 |
});
|
| 2436 |
html += `</div>`;
|
| 2437 |
canvas.innerHTML = html;
|
|
@@ -2462,8 +2563,8 @@ async function loadGenes() {
|
|
| 2462 |
let svg = "";
|
| 2463 |
|
| 2464 |
// Background two-tone (CE phase + FNS phase)
|
| 2465 |
-
svg += `<rect x="${padL}" y="${padT}" width="${(switchX - padL).toFixed(1)}" height="${H - padT - padB}" fill="#
|
| 2466 |
-
svg += `<rect x="${switchX.toFixed(1)}" y="${padT}" width="${(W - padR - switchX).toFixed(1)}" height="${H - padT - padB}" fill="#
|
| 2467 |
|
| 2468 |
// Mock loss curve: smooth descent then a "staircase" that gets cleaned by the FNS switch
|
| 2469 |
const points = [];
|
|
@@ -2486,20 +2587,20 @@ async function loadGenes() {
|
|
| 2486 |
}
|
| 2487 |
let d = "";
|
| 2488 |
points.forEach(([x, y], i) => { d += (i === 0 ? "M" : "L") + x.toFixed(1) + " " + y.toFixed(1); });
|
| 2489 |
-
svg += `<path d="${d}" fill="none" stroke="#
|
| 2490 |
|
| 2491 |
// Switch marker
|
| 2492 |
-
svg += `<line x1="${switchX.toFixed(1)}" y1="${padT}" x2="${switchX.toFixed(1)}" y2="${H - padB}" stroke="#
|
| 2493 |
-
svg += `<text x="${switchX.toFixed(1)}" y="${(padT - 4)}" font-family="JetBrains Mono" font-size="9" fill="#
|
| 2494 |
|
| 2495 |
// Phase labels
|
| 2496 |
svg += `<text x="${(padL + (switchX - padL) / 2).toFixed(1)}" y="${(H - padB + 14).toFixed(1)}" font-family="JetBrains Mono" font-size="10" fill="#666" text-anchor="middle" letter-spacing="1">CROSS-ENTROPY · learn joint structure</text>`;
|
| 2497 |
-
svg += `<text x="${(switchX + (W - padR - switchX) / 2).toFixed(1)}" y="${(H - padB + 14).toFixed(1)}" font-family="JetBrains Mono" font-size="10" fill="#
|
| 2498 |
// y-axis label
|
| 2499 |
svg += `<text x="${padL}" y="${(padT - 4)}" font-family="JetBrains Mono" font-size="9" fill="#aaa" letter-spacing="0.5">training loss</text>`;
|
| 2500 |
// staircase callout
|
| 2501 |
const staircaseX = padL + innerW * (switchAt - 0.04);
|
| 2502 |
-
svg += `<text x="${staircaseX.toFixed(1)}" y="${(H - padB - 4).toFixed(1)}" font-family="JetBrains Mono" font-size="9" fill="#
|
| 2503 |
|
| 2504 |
schedule.innerHTML = svg;
|
| 2505 |
}
|
|
@@ -2534,7 +2635,7 @@ async function loadGenes() {
|
|
| 2534 |
const tplEl = document.getElementById("d9-templates");
|
| 2535 |
|
| 2536 |
const COMPOSITION = [
|
| 2537 |
-
{ label: "GENERator-v2", pct: 70, color: "#
|
| 2538 |
{ label: "mRNA", pct: 16, color: "#2c5aa0", desc: "OpenGenome2 mature transcripts · RNA-level functional context" },
|
| 2539 |
{ label: "GTDB", pct: 10, color: "#c08030", desc: "OpenGenome2 prokaryotic genomes · compact bacterial structure" },
|
| 2540 |
{ label: "mRNA-splice", pct: 4, color: "#7a4baa", desc: "OpenGenome2 transcript-derived · splice-related signal" },
|
|
@@ -2569,8 +2670,8 @@ async function loadGenes() {
|
|
| 2569 |
}
|
| 2570 |
}
|
| 2571 |
// Inner hole for donut
|
| 2572 |
-
svg += `<circle cx="${cx}" cy="${cy}" r="${r * 0.42}" fill="#
|
| 2573 |
-
svg += `<text x="${cx}" y="${cy - 2}" font-family="JetBrains Mono" font-size="11" fill="#
|
| 2574 |
svg += `<text x="${cx}" y="${cy + 12}" font-family="JetBrains Mono" font-size="9" fill="#888" text-anchor="middle">corpus</text>`;
|
| 2575 |
pieEl.innerHTML = svg;
|
| 2576 |
}
|
|
@@ -2580,7 +2681,7 @@ async function loadGenes() {
|
|
| 2580 |
<div style="display:flex;align-items:flex-start;gap:10px;padding:6px 0;border-bottom:1px solid #f0f0f0">
|
| 2581 |
<span style="display:inline-block;flex:0 0 12px;height:12px;background:${s.color};border-radius:2px;margin-top:3px"></span>
|
| 2582 |
<div style="flex:1">
|
| 2583 |
-
<div><strong style="color:#
|
| 2584 |
<div style="color:#888;font-size:10px;margin-top:1px">${s.desc}</div>
|
| 2585 |
</div>
|
| 2586 |
</div>
|
|
@@ -2602,7 +2703,7 @@ async function loadGenes() {
|
|
| 2602 |
const rowW = W - padL - 12;
|
| 2603 |
for (const seg of segs) {
|
| 2604 |
const w = (seg.frac * rowW);
|
| 2605 |
-
svg += `<rect x="${cursor.toFixed(1)}" y="${y}" width="${Math.max(0.5, w).toFixed(1)}" height="${segH}" fill="${seg.func ? '#
|
| 2606 |
cursor += w;
|
| 2607 |
}
|
| 2608 |
}
|
|
@@ -2627,8 +2728,8 @@ async function loadGenes() {
|
|
| 2627 |
function renderTemplates() {
|
| 2628 |
let html = "";
|
| 2629 |
for (const t of TEMPLATES) {
|
| 2630 |
-
html += `<div style="text-align:right;color:#
|
| 2631 |
-
html += `<div><span style="background:#f4f4f4;padding:2px 6px;border-radius:2px;color:#
|
| 2632 |
}
|
| 2633 |
tplEl.innerHTML = html;
|
| 2634 |
}
|
|
@@ -2660,16 +2761,16 @@ async function loadGenes() {
|
|
| 2660 |
let html = `<thead>
|
| 2661 |
<tr>
|
| 2662 |
<th style="text-align:left;padding:10px 6px 8px;border-bottom:1px solid #ddd;font-size:10px;color:#888;text-transform:uppercase;letter-spacing:1.5px;font-weight:400"></th>
|
| 2663 |
-
<th style="text-align:left;padding:10px 12px 8px;border-bottom:1px solid #ddd;font-size:11px;color:#
|
| 2664 |
-
<th style="text-align:left;padding:10px 12px 8px;border-bottom:1px solid #ddd;font-size:11px;color:#
|
| 2665 |
</tr>
|
| 2666 |
</thead><tbody>`;
|
| 2667 |
ROWS.forEach((r, i) => {
|
| 2668 |
-
const bg = i % 2 === 0 ? "#
|
| 2669 |
html += `<tr style="background:${bg}">
|
| 2670 |
<td style="padding:6px;color:#666;font-size:10px;text-transform:uppercase;letter-spacing:1px">${r[0]}</td>
|
| 2671 |
-
<td style="padding:6px 12px;color:#
|
| 2672 |
-
<td style="padding:6px 12px;color:#
|
| 2673 |
</tr>`;
|
| 2674 |
});
|
| 2675 |
html += `</tbody>`;
|
|
@@ -2709,9 +2810,9 @@ async function loadGenes() {
|
|
| 2709 |
A: [58, 138, 62], C: [46, 107, 184], G: [181, 137, 30], T: [181, 58, 58], N: [136, 136, 136],
|
| 2710 |
};
|
| 2711 |
const PROMPT_RGB_S = [170, 170, 170];
|
| 2712 |
-
const DARK_RGB_S = [
|
| 2713 |
const MID_RGB_S = [136, 136, 136];
|
| 2714 |
-
const RED_RGB_S = [
|
| 2715 |
const BG_ALPHA = 0.12;
|
| 2716 |
|
| 2717 |
let promptBases = "";
|
|
@@ -2744,14 +2845,14 @@ async function loadGenes() {
|
|
| 2744 |
const bar = document.getElementById("sb-legend-bar");
|
| 2745 |
if (!lpRange) {
|
| 2746 |
minEl.textContent = midEl.textContent = maxEl.textContent = "—";
|
| 2747 |
-
bar.style.background = "linear-gradient(to right, #
|
| 2748 |
} else {
|
| 2749 |
const { min, mid, max } = lpRange;
|
| 2750 |
minEl.textContent = min.toFixed(1);
|
| 2751 |
midEl.textContent = mid.toFixed(1);
|
| 2752 |
maxEl.textContent = max.toFixed(1);
|
| 2753 |
const midPct = max > min ? ((mid - min) / (max - min)) * 100 : 50;
|
| 2754 |
-
bar.style.background = `linear-gradient(to right, #
|
| 2755 |
}
|
| 2756 |
updateLpChart();
|
| 2757 |
}
|
|
@@ -2787,9 +2888,9 @@ async function loadGenes() {
|
|
| 2787 |
svg.innerHTML = `
|
| 2788 |
<defs>
|
| 2789 |
<linearGradient id="sb-lp-grad" gradientUnits="userSpaceOnUse" x1="0" y1="${H - pad}" x2="0" y2="${pad}">
|
| 2790 |
-
<stop offset="0%" stop-color="#
|
| 2791 |
<stop offset="${midPct.toFixed(1)}%" stop-color="#888"/>
|
| 2792 |
-
<stop offset="100%" stop-color="#
|
| 2793 |
</linearGradient>
|
| 2794 |
</defs>
|
| 2795 |
<path d="${d}" fill="none" stroke="url(#sb-lp-grad)" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
|
@@ -3140,6 +3241,193 @@ async function loadGenes() {
|
|
| 3140 |
}
|
| 3141 |
})();
|
| 3142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3143 |
// =========================================================================
|
| 3144 |
// Tab switching + hash routing
|
| 3145 |
// =========================================================================
|
|
|
|
| 11 |
body {
|
| 12 |
font-family: "Inter", "Helvetica Neue", sans-serif;
|
| 13 |
font-size: 13px; font-weight: 300; line-height: 1.7;
|
| 14 |
+
color: #1f1f1d; background: #f7f5ee;
|
| 15 |
padding: 0;
|
| 16 |
}
|
| 17 |
.container { max-width: 760px; margin: 0 auto; padding: 48px 32px 96px; }
|
|
|
|
| 22 |
border-bottom: 1px solid #ccc; /* separator line under the tab strip */
|
| 23 |
padding: 24px 32px 0; /* no bottom padding — tabs sit on the line */
|
| 24 |
margin-bottom: 0;
|
| 25 |
+
background: #f7f5ee;
|
| 26 |
position: sticky; top: 0; z-index: 10;
|
| 27 |
}
|
| 28 |
.header-inner {
|
|
|
|
| 58 |
cursor: pointer; transition: background 0.15s, color 0.15s;
|
| 59 |
}
|
| 60 |
nav#tab-nav .tab + .tab { margin-left: -1px; } /* shared border between adjacent tabs */
|
| 61 |
+
nav#tab-nav .tab:hover { color: #1f1f1d; background: #f6f6f6; }
|
| 62 |
nav#tab-nav .tab.active {
|
| 63 |
+
color: #1f1f1d; background: #f7f5ee;
|
| 64 |
+
border-bottom-color: #f7f5ee; /* hides bottom border so tab merges into content */
|
| 65 |
z-index: 2;
|
| 66 |
}
|
| 67 |
|
|
|
|
| 69 |
.tab-panel { display: none; }
|
| 70 |
.tab-panel.active { display: block; }
|
| 71 |
|
| 72 |
+
/* ====================================================================== */
|
| 73 |
+
/* Carbon banner — editorial hero (scoped to .carbon-banner) */
|
| 74 |
+
/* ====================================================================== */
|
| 75 |
+
.carbon-banner {
|
| 76 |
+
--paper: #f7f5ee;
|
| 77 |
+
--ink: #1f1f1d;
|
| 78 |
+
--muted: #8c918b;
|
| 79 |
+
--hairline: #b9bcb7;
|
| 80 |
+
--green: #317f3f;
|
| 81 |
+
--red: #bc2e25;
|
| 82 |
+
|
| 83 |
display: block;
|
| 84 |
+
margin: 28px auto;
|
| 85 |
+
width: min(1280px, calc(100vw - 32px));
|
| 86 |
+
aspect-ratio: 2048 / 620;
|
| 87 |
+
position: relative;
|
| 88 |
+
overflow: hidden;
|
| 89 |
+
border: 1px solid #8f928d;
|
| 90 |
+
background:
|
| 91 |
+
radial-gradient(circle at 22% 32%, rgba(0, 0, 0, 0.035), transparent 1px),
|
| 92 |
+
radial-gradient(circle at 78% 64%, rgba(0, 0, 0, 0.03), transparent 1px),
|
| 93 |
+
linear-gradient(90deg, rgba(49, 127, 63, 0.025), transparent 34%, transparent 66%, rgba(49, 127, 63, 0.025)),
|
| 94 |
+
var(--paper);
|
| 95 |
+
background-size: 7px 7px, 11px 11px, auto, auto;
|
| 96 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
| 97 |
+
}
|
| 98 |
+
.carbon-banner .carbon-art { width: 100%; height: 100%; display: block; }
|
| 99 |
+
.carbon-banner .cb-mono {
|
| 100 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
| 101 |
+
letter-spacing: 0.24em;
|
| 102 |
+
text-transform: uppercase;
|
| 103 |
+
}
|
| 104 |
+
.carbon-banner .cb-rule { stroke: var(--hairline); stroke-width: 1; vector-effect: non-scaling-stroke; }
|
| 105 |
+
.carbon-banner .cb-label { fill: var(--ink); font-size: 17px; letter-spacing: 0.24em; }
|
| 106 |
+
.carbon-banner .cb-label-green { fill: var(--green); }
|
| 107 |
+
.carbon-banner .cb-label-red { fill: var(--red); }
|
| 108 |
+
.carbon-banner .cb-carbon-word {
|
| 109 |
+
fill: var(--ink);
|
| 110 |
+
font-family: "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
|
| 111 |
+
font-size: 255px;
|
| 112 |
+
font-stretch: condensed;
|
| 113 |
+
font-weight: 900;
|
| 114 |
+
letter-spacing: -0.02em;
|
| 115 |
+
}
|
| 116 |
+
.carbon-banner .cb-paper-grain { mix-blend-mode: multiply; opacity: 0.62; pointer-events: none; }
|
| 117 |
+
.carbon-banner .cb-helix-shadow { fill: #aeb5ad; opacity: 0.15; }
|
| 118 |
+
.carbon-banner .cb-helix-body {
|
| 119 |
+
fill: #e4e5dc;
|
| 120 |
+
stroke: rgba(49, 127, 63, 0.14);
|
| 121 |
+
stroke-width: 0.8;
|
| 122 |
+
}
|
| 123 |
+
.carbon-banner .cb-helix-edge {
|
| 124 |
+
fill: none; stroke: #2d332e; stroke-width: 1.15;
|
| 125 |
+
stroke-linecap: round; stroke-linejoin: round;
|
| 126 |
+
vector-effect: non-scaling-stroke;
|
| 127 |
+
}
|
| 128 |
+
.carbon-banner .cb-helix-texture { fill: url(#cb-helixGrain); opacity: 0.46; }
|
| 129 |
+
.carbon-banner .cb-base-pair {
|
| 130 |
+
fill: none; stroke: var(--green); stroke-width: 1.35;
|
| 131 |
+
stroke-linecap: round; vector-effect: non-scaling-stroke;
|
| 132 |
+
}
|
| 133 |
+
.carbon-banner .cb-base-letter-node {
|
| 134 |
+
will-change: transform, opacity;
|
| 135 |
+
transform-box: fill-box; transform-origin: 0 0;
|
| 136 |
+
}
|
| 137 |
+
.carbon-banner .cb-base-glyph {
|
| 138 |
+
fill: none; stroke: var(--green); stroke-width: 1.8;
|
| 139 |
+
stroke-linecap: square; stroke-linejoin: miter;
|
| 140 |
+
vector-effect: non-scaling-stroke;
|
| 141 |
+
}
|
| 142 |
+
.carbon-banner .cb-tiny-bars rect { fill: var(--green); }
|
| 143 |
+
@media (max-width: 760px) {
|
| 144 |
+
.carbon-banner { width: calc(100vw - 20px); margin: 16px auto; }
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/* --- Tab lede (short narrative paragraph below the banner) --- */
|
| 148 |
+
.tab-lede {
|
| 149 |
+
max-width: 1080px; margin: 4px auto 0;
|
| 150 |
+
padding: 14px 32px 0;
|
| 151 |
+
}
|
| 152 |
+
.tab-lede p {
|
| 153 |
+
color: #555; font-size: 14px; line-height: 1.7;
|
| 154 |
+
max-width: 760px; margin: 0;
|
| 155 |
}
|
| 156 |
|
| 157 |
/* --- Sections --- */
|
|
|
|
| 167 |
padding: 80px 32px 48px;
|
| 168 |
border-top: 1px solid #eee;
|
| 169 |
border-bottom: 1px solid #eee;
|
| 170 |
+
background: linear-gradient(to bottom, rgba(49,127,63,0.04), transparent);
|
| 171 |
}
|
| 172 |
.part-divider .part-eyebrow {
|
| 173 |
font-family: "JetBrains Mono", monospace;
|
| 174 |
+
font-size: 10px; color: #317f3f;
|
| 175 |
letter-spacing: 4px; text-transform: uppercase;
|
| 176 |
margin-bottom: 6px;
|
| 177 |
}
|
|
|
|
| 186 |
|
| 187 |
.section-num {
|
| 188 |
font-family: "JetBrains Mono", monospace;
|
| 189 |
+
font-size: 10px; color: #317f3f;
|
| 190 |
letter-spacing: 2px; text-transform: uppercase;
|
| 191 |
margin-bottom: 8px;
|
| 192 |
}
|
|
|
|
| 194 |
font-family: "JetBrains Mono", monospace;
|
| 195 |
font-size: 22px; font-weight: 400; letter-spacing: -0.3px;
|
| 196 |
margin-bottom: 24px;
|
| 197 |
+
color: #1f1f1d;
|
| 198 |
}
|
| 199 |
.lede {
|
| 200 |
color: #444; font-size: 14px; margin-bottom: 32px;
|
|
|
|
| 202 |
}
|
| 203 |
.takeaway {
|
| 204 |
margin-top: 32px;
|
| 205 |
+
padding: 16px 20px; border-left: 3px solid #317f3f;
|
| 206 |
background: #f4f8f4; color: #333;
|
| 207 |
font-size: 13px; max-width: 640px;
|
| 208 |
}
|
| 209 |
.takeaway strong {
|
| 210 |
font-family: "JetBrains Mono", monospace;
|
| 211 |
font-weight: 500; letter-spacing: 1px; text-transform: uppercase;
|
| 212 |
+
font-size: 10px; color: #317f3f; display: block; margin-bottom: 4px;
|
| 213 |
}
|
| 214 |
|
| 215 |
/* --- Demo card (the interactive box inside each section) --- */
|
|
|
|
| 234 |
text-transform: uppercase; letter-spacing: 1.5px;
|
| 235 |
transition: all 0.15s;
|
| 236 |
}
|
| 237 |
+
button.action:hover, .pill:hover { border-color: #888; color: #1f1f1d; }
|
| 238 |
+
button.action.primary { background: #1f1f1d; color: #fff; border-color: #1f1f1d; }
|
| 239 |
button.action.primary:hover { background: #000; }
|
| 240 |
button.action:disabled { opacity: 0.4; cursor: not-allowed; }
|
| 241 |
button.action.primary:disabled { background: #888; border-color: #888; }
|
| 242 |
+
.pill.active { background: #1f1f1d; color: #fff; border-color: #1f1f1d; }
|
| 243 |
.pills { display: inline-flex; gap: 4px; }
|
| 244 |
.pills .pill { font-size: 9px; padding: 4px 8px; }
|
| 245 |
|
|
|
|
| 263 |
margin: 4px 0 12px;
|
| 264 |
min-height: 14px;
|
| 265 |
}
|
| 266 |
+
.gene-info strong { color: #1f1f1d; font-weight: 500; }
|
| 267 |
.gene-track {
|
| 268 |
width: 100%; height: 28px; display: block;
|
| 269 |
margin: 4px 0 8px;
|
| 270 |
}
|
| 271 |
+
.gene-track .exon { fill: #317f3f; }
|
| 272 |
.gene-track .intron { stroke: #aaa; stroke-width: 1; }
|
| 273 |
+
.gene-track .playhead { stroke: #bc2e25; stroke-width: 2; }
|
| 274 |
+
.gene-track .gen-region { fill: #bc2e25; opacity: 0.10; }
|
| 275 |
.gene-track text { font-family: "JetBrains Mono", monospace; font-size: 9px; fill: #888; }
|
| 276 |
.track-axis-label {
|
| 277 |
font-family: "JetBrains Mono", monospace; font-size: 9px;
|
|
|
|
| 294 |
}
|
| 295 |
.stat-pair { display: flex; flex-direction: column; gap: 2px; }
|
| 296 |
.stat-pair-label { font-size: 9px; color: #999; text-transform: uppercase; letter-spacing: 1.2px; }
|
| 297 |
+
.stat-pair-val { color: #1f1f1d; font-variant-numeric: tabular-nums; }
|
| 298 |
.stat-pair-val.muted { color: #aaa; }
|
| 299 |
|
| 300 |
/* Mismatch highlighting in reference row */
|
| 301 |
+
.ref-mismatch { background: rgba(188, 46, 37, 0.18); color: #b00020; }
|
| 302 |
.ref-match { color: #999; }
|
| 303 |
|
| 304 |
/* Model section responsive overrides */
|
|
|
|
| 333 |
background: #fff; color: #666; cursor: pointer;
|
| 334 |
transition: all 0.15s;
|
| 335 |
}
|
| 336 |
+
#panel-sandbox .sb-ex-btn:hover { border-color: #888; color: #1f1f1d; }
|
| 337 |
#panel-sandbox .sb-ex-btn .sb-ex-label {
|
| 338 |
color: #aaa; font-size: 9px; margin-left: 6px; text-transform: uppercase;
|
| 339 |
letter-spacing: 0.5px;
|
| 340 |
}
|
| 341 |
#panel-sandbox .sb-prompt-area, #panel-sandbox input[type=number] {
|
| 342 |
font-family: "JetBrains Mono", monospace;
|
| 343 |
+
font-size: 12px; font-weight: 300; color: #1f1f1d;
|
| 344 |
background: #fff; border: 1px solid #ddd; border-radius: 3px;
|
| 345 |
padding: 8px 12px; outline: none; transition: border 0.15s;
|
| 346 |
}
|
| 347 |
#panel-sandbox .sb-prompt-area:focus,
|
| 348 |
+
#panel-sandbox input[type=number]:focus { border-color: #1f1f1d; }
|
| 349 |
#panel-sandbox .sb-prompt-area {
|
| 350 |
width: 100%; resize: none; overflow: hidden;
|
| 351 |
letter-spacing: 1px; line-height: 1.7;
|
|
|
|
| 379 |
}
|
| 380 |
#panel-sandbox .sb-mode-btn:first-child { border-radius: 3px 0 0 3px; }
|
| 381 |
#panel-sandbox .sb-mode-btn:last-child { border-right: 1px solid #ccc; border-radius: 0 3px 3px 0; }
|
| 382 |
+
#panel-sandbox .sb-mode-btn:hover { color: #1f1f1d; }
|
| 383 |
+
#panel-sandbox .sb-mode-btn.active { background: #1f1f1d; color: #fff; border-color: #1f1f1d; }
|
| 384 |
|
| 385 |
#panel-sandbox .sb-button-row { margin-left: auto; display: flex; gap: 6px; }
|
| 386 |
|
|
|
|
| 395 |
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
| 396 |
background: #888; margin-right: 6px; vertical-align: middle;
|
| 397 |
}
|
| 398 |
+
#panel-sandbox .sb-status.streaming .dot { background: #317f3f; animation: pulse 1.2s ease-in-out infinite; }
|
| 399 |
|
| 400 |
#panel-sandbox .sb-output-row {
|
| 401 |
display: grid;
|
|
|
|
| 417 |
text-transform: uppercase; letter-spacing: 1px;
|
| 418 |
transition: all 0.15s;
|
| 419 |
}
|
| 420 |
+
#panel-sandbox .sb-copy-btn:hover { border-color: #888; color: #1f1f1d; }
|
| 421 |
#panel-sandbox .sb-copy-btn:disabled { opacity: 0; pointer-events: none; }
|
| 422 |
+
#panel-sandbox .sb-copy-btn.copied { background: #317f3f; color: #fff; border-color: #317f3f; }
|
| 423 |
|
| 424 |
#panel-sandbox .sb-seq-block {
|
| 425 |
font-family: "JetBrains Mono", monospace;
|
|
|
|
| 435 |
#panel-sandbox .sb-seq-line.tail::after {
|
| 436 |
content: "";
|
| 437 |
display: inline-block; width: 7px; height: 14px;
|
| 438 |
+
background: #1f1f1d; vertical-align: text-bottom;
|
| 439 |
margin-left: 2px;
|
| 440 |
animation: blink 1s step-end infinite;
|
| 441 |
}
|
|
|
|
| 457 |
text-transform: uppercase; letter-spacing: 1.2px; font-weight: 300;
|
| 458 |
}
|
| 459 |
#panel-sandbox .sb-stat-value {
|
| 460 |
+
font-size: 12px; font-weight: 400; color: #1f1f1d;
|
| 461 |
font-variant-numeric: tabular-nums;
|
| 462 |
}
|
| 463 |
#panel-sandbox .sb-stat-value .sb-unit { font-size: 9px; color: #999; margin-left: 3px; font-weight: 300; }
|
|
|
|
| 474 |
#panel-sandbox .sb-legend.show { display: block; }
|
| 475 |
#panel-sandbox .sb-legend-bar {
|
| 476 |
height: 6px; margin: 4px 0 3px;
|
| 477 |
+
background: linear-gradient(to right, #bc2e25, #888, #1f1f1d);
|
| 478 |
border-radius: 1px;
|
| 479 |
}
|
| 480 |
#panel-sandbox .sb-legend-row { display: flex; justify-content: space-between; }
|
|
|
|
| 493 |
text-align: center;
|
| 494 |
}
|
| 495 |
.vep-window .ctx { color: #888; }
|
| 496 |
+
.vep-window .var-ref { background: rgba(49, 127, 63, 0.18); color: #215a2a; padding: 0 4px; border-radius: 2px; font-weight: 500; }
|
| 497 |
+
.vep-window .var-alt { background: rgba(188, 46, 37, 0.20); color: #b00020; padding: 0 4px; border-radius: 2px; font-weight: 500; }
|
| 498 |
.vep-result {
|
| 499 |
display: grid; grid-template-columns: 100px 1fr 80px;
|
| 500 |
gap: 8px 12px; align-items: center;
|
|
|
|
| 503 |
.vep-result .row-label { color: #666; text-transform: uppercase; letter-spacing: 1px; font-size: 10px; }
|
| 504 |
.vep-result .row-bar { height: 14px; background: #f0f0f0; border-radius: 2px; position: relative; overflow: hidden; }
|
| 505 |
.vep-result .row-bar .fill { position: absolute; top: 0; bottom: 0; left: 0; }
|
| 506 |
+
.vep-result .row-bar.ref .fill { background: #317f3f; }
|
| 507 |
+
.vep-result .row-bar.alt .fill { background: #bc2e25; }
|
| 508 |
+
.vep-result .row-val { text-align: right; color: #1f1f1d; font-variant-numeric: tabular-nums; }
|
| 509 |
.vep-result .row-delta { font-weight: 500; }
|
| 510 |
|
| 511 |
+
.pill.sig-Pathogenic { border-left: 3px solid #bc2e25; }
|
| 512 |
.pill.sig-Risk { border-left: 3px solid #e69500; }
|
| 513 |
+
.pill.sig-Benign { border-left: 3px solid #317f3f; }
|
| 514 |
|
| 515 |
/* --- Species rows (§4) --- */
|
| 516 |
.species-row {
|
|
|
|
| 527 |
font-size: 11px;
|
| 528 |
}
|
| 529 |
.species-name {
|
| 530 |
+
font-weight: 500; color: #1f1f1d;
|
| 531 |
text-transform: uppercase; letter-spacing: 1px; font-size: 11px;
|
| 532 |
border-left: 4px solid;
|
| 533 |
padding-left: 8px;
|
|
|
|
| 537 |
text-align: right; font-family: "JetBrains Mono", monospace;
|
| 538 |
font-size: 11px; color: #666;
|
| 539 |
}
|
| 540 |
+
.species-stats .stat-id { font-size: 16px; color: #1f1f1d; font-weight: 500; font-variant-numeric: tabular-nums; }
|
| 541 |
.species-stats .stat-sub { font-size: 10px; color: #999; margin-top: 2px; }
|
| 542 |
.species-seq {
|
| 543 |
font-family: "JetBrains Mono", monospace;
|
| 544 |
+
background: #f7f5ee; border: 1px solid #eee;
|
| 545 |
padding: 8px 12px; overflow-x: auto;
|
| 546 |
white-space: pre; font-size: 11px;
|
| 547 |
line-height: 1.7; letter-spacing: 0.5px;
|
|
|
|
| 557 |
border-top: 1px solid #eee;
|
| 558 |
}
|
| 559 |
footer a { color: #666; text-decoration: none; }
|
| 560 |
+
footer a:hover { color: #1f1f1d; }
|
| 561 |
|
| 562 |
/* --- Sequence display (shared with sandbox) --- */
|
| 563 |
.seq-block {
|
|
|
|
| 582 |
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
| 583 |
background: #888;
|
| 584 |
}
|
| 585 |
+
.status.streaming .dot { background: #317f3f; animation: pulse 1.2s ease-in-out infinite; }
|
| 586 |
.status.error { color: #b00020; }
|
| 587 |
@keyframes pulse { 50% { opacity: 0.3; } }
|
| 588 |
|
|
|
|
| 607 |
</div>
|
| 608 |
</header>
|
| 609 |
|
| 610 |
+
<section class="carbon-banner" aria-label="Carbon DNA model banner">
|
| 611 |
+
<svg class="carbon-art" viewBox="0 0 2048 620" role="img" aria-labelledby="cb-carbonTitle cb-carbonDesc">
|
| 612 |
+
<title id="cb-carbonTitle">Carbon genomic language model banner</title>
|
| 613 |
+
<desc id="cb-carbonDesc">A technical editorial banner with the CARBON-0 model name and an adjacent animated DNA strand.</desc>
|
| 614 |
+
|
| 615 |
+
<defs>
|
| 616 |
+
<pattern id="cb-paperDot" width="12" height="12" patternUnits="userSpaceOnUse">
|
| 617 |
+
<circle cx="2" cy="2" r="0.8" fill="#1f1f1d" opacity="0.045" />
|
| 618 |
+
<circle cx="9" cy="8" r="0.55" fill="#317f3f" opacity="0.035" />
|
| 619 |
+
</pattern>
|
| 620 |
+
<pattern id="cb-helixGrain" width="9" height="9" patternUnits="userSpaceOnUse">
|
| 621 |
+
<rect width="9" height="9" fill="transparent" />
|
| 622 |
+
<circle cx="1.4" cy="1.7" r="0.7" fill="#263128" opacity="0.34" />
|
| 623 |
+
<circle cx="6.2" cy="5.5" r="0.45" fill="#263128" opacity="0.24" />
|
| 624 |
+
<line x1="3" y1="8" x2="7" y2="8" stroke="#263128" stroke-width="0.45" opacity="0.18" />
|
| 625 |
+
</pattern>
|
| 626 |
+
</defs>
|
| 627 |
+
|
| 628 |
+
<rect class="cb-paper-grain" x="25" y="25" width="1998" height="570" fill="url(#cb-paperDot)" />
|
| 629 |
+
|
| 630 |
+
<g class="cb-mono">
|
| 631 |
+
<text class="cb-label" x="63" y="76">CARBON-0</text>
|
| 632 |
+
<text class="cb-label cb-label-green" x="240" y="76">3B Autoregressive Genomic Foundation Model</text>
|
| 633 |
+
<text class="cb-label" x="1162" y="76">49,152 BP Context</text>
|
| 634 |
+
<circle cx="1456" cy="70" r="4" fill="#317f3f" />
|
| 635 |
+
<text class="cb-label" x="1490" y="76">6-Mer Tokenizer</text>
|
| 636 |
+
<circle cx="1760" cy="70" r="4" fill="#317f3f" />
|
| 637 |
+
<text class="cb-label" x="1795" y="76">1T Train Tokens</text>
|
| 638 |
+
</g>
|
| 639 |
+
|
| 640 |
+
<line class="cb-rule" x1="40" y1="110" x2="2010" y2="110" />
|
| 641 |
+
|
| 642 |
+
<g class="cb-mono">
|
| 643 |
+
<text class="cb-carbon-word" x="60" y="405" textLength="795" lengthAdjust="spacingAndGlyphs">CARBON-0</text>
|
| 644 |
+
</g>
|
| 645 |
+
|
| 646 |
+
<g id="cb-helix" aria-hidden="true">
|
| 647 |
+
<g id="cb-strandBackLayer"></g>
|
| 648 |
+
<g id="cb-rungs"></g>
|
| 649 |
+
<g id="cb-baseLetters" class="cb-mono"></g>
|
| 650 |
+
<g id="cb-strandFrontLayer"></g>
|
| 651 |
+
</g>
|
| 652 |
+
|
| 653 |
+
<line class="cb-rule" x1="40" y1="545" x2="2010" y2="545" />
|
| 654 |
+
<g class="cb-mono">
|
| 655 |
+
<text class="cb-label" x="64" y="578">Carbon Labs</text>
|
| 656 |
+
<line x1="240" y1="570" x2="266" y2="570" stroke="#317f3f" stroke-width="2" />
|
| 657 |
+
<text class="cb-label" x="294" y="578">Building Foundational Models For Genomic Sequences</text>
|
| 658 |
+
<text class="cb-label" x="1378" y="578">CLB-2026-05-11</text>
|
| 659 |
+
<text class="cb-label" x="1638" y="578">DNA-LM-49K</text>
|
| 660 |
+
<text class="cb-label" x="1784" y="578">CARBON-0 v0</text>
|
| 661 |
+
<g class="cb-tiny-bars" transform="translate(1956 562)">
|
| 662 |
+
<rect x="0" y="0" width="3" height="20" />
|
| 663 |
+
<rect x="8" y="0" width="3" height="20" />
|
| 664 |
+
<rect x="16" y="0" width="3" height="20" />
|
| 665 |
+
<rect x="24" y="0" width="3" height="20" />
|
| 666 |
+
<rect x="32" y="0" width="3" height="20" />
|
| 667 |
+
<rect x="40" y="0" width="3" height="20" />
|
| 668 |
+
</g>
|
| 669 |
+
</g>
|
| 670 |
+
</svg>
|
| 671 |
+
</section>
|
| 672 |
+
|
| 673 |
<div class="tab-panel active" id="panel-demo" data-tab="demo">
|
| 674 |
|
| 675 |
+
<div class="tab-lede">
|
| 676 |
+
<p>We didn't tell Carbon what an exon is. We didn't tell it which mutations are pathogenic. We didn't tell it how genes differ between species. Four ways to see what it picked up anyway.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
</div>
|
| 678 |
|
| 679 |
<div class="container wide">
|
|
|
|
| 717 |
<div class="gene-info" id="d1-info">loading genes…</div>
|
| 718 |
<svg class="gene-track" id="d1-track" viewBox="0 0 1000 28" preserveAspectRatio="none"></svg>
|
| 719 |
<div class="track-axis-label">
|
| 720 |
+
<span><span class="legend-swatch" style="background:#317f3f"></span>exon</span>
|
| 721 |
<span><span class="legend-swatch" style="background:#aaa;height:2px;border-radius:0"></span>intron</span>
|
| 722 |
+
<span><span class="legend-swatch" style="background:rgba(188,46,37,0.20)"></span>generated region</span>
|
| 723 |
+
<span style="color:#bc2e25">│ prompt end</span>
|
| 724 |
</div>
|
| 725 |
|
| 726 |
<div class="seq-label">model output · <span style="color:#aaa">prompt in gray</span> · <span>generated colored by logprob (red=uncertain)</span></div>
|
|
|
|
| 779 |
<svg id="d2-bars" style="display:block;width:100%;height:auto;background:#fff;border:1px solid #eee;margin-top:12px" preserveAspectRatio="xMinYMin meet"></svg>
|
| 780 |
|
| 781 |
<div class="track-axis-label">
|
| 782 |
+
<span><span class="legend-swatch" style="background:#bc2e25;border-radius:50%"></span>pathogenic (curated)</span>
|
| 783 |
<span><span class="legend-swatch" style="background:#e69500;border-radius:50%"></span>risk</span>
|
| 784 |
+
<span><span class="legend-swatch" style="background:#317f3f;border-radius:50%"></span>benign</span>
|
| 785 |
<span style="color:#888">dot = ClinVar label · bar = model signal</span>
|
| 786 |
</div>
|
| 787 |
</div>
|
|
|
|
| 796 |
likely culprit is allele frequency: alt alleles common enough in human populations look
|
| 797 |
perfectly normal to a model trained on natural sequence. For sharper variant effect
|
| 798 |
prediction, Carbon can be fine-tuned (see the
|
| 799 |
+
<a href="https://huggingface.co/spaces/hf-carbon/dna-vep-explainer" style="color:#317f3f">dna-vep-explainer</a>).
|
| 800 |
</div>
|
| 801 |
</section>
|
| 802 |
|
|
|
|
| 826 |
<svg class="gene-track" id="d3-track" viewBox="0 0 1000 28" preserveAspectRatio="none"></svg>
|
| 827 |
<svg id="d3-chart" style="display:block;width:100%;height:140px;background:#fff;border:1px solid #eee;margin-top:6px" preserveAspectRatio="none" viewBox="0 0 1000 140"></svg>
|
| 828 |
<div class="track-axis-label">
|
| 829 |
+
<span><span class="legend-swatch" style="background:#317f3f"></span>exon (shaded)</span>
|
| 830 |
<span style="color:#aaa">y-axis: log P per 6-bp token (higher = more confident)</span>
|
| 831 |
<span id="d3-bp-label" style="color:#888">0 bp</span>
|
| 832 |
</div>
|
|
|
|
| 886 |
|
| 887 |
<div class="track-axis-label" style="margin-top:14px">
|
| 888 |
<span style="color:#aaa">prompt in gray</span>
|
| 889 |
+
<span style="color:#1f1f1d">generated colored by logprob</span>
|
| 890 |
<span style="color:#b00020">mismatches in reference highlighted</span>
|
| 891 |
</div>
|
| 892 |
</div>
|
|
|
|
| 944 |
|
| 945 |
<div class="tab-panel" id="panel-model" data-tab="model">
|
| 946 |
|
| 947 |
+
<div class="tab-lede">
|
| 948 |
+
<p>Three places where the recipe needed adjustment for biology: the way DNA gets tokenized, how the loss handles near-miss tokens, and which sequence ends up in the corpus. Plus a deliberately vanilla architecture so any improvement can be attributed to the recipe rather than custom blocks.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 949 |
</div>
|
| 950 |
|
| 951 |
<div class="container wide">
|
|
|
|
| 972 |
value="ATGGCCAAGCTGACCAGCGAGCTGCTGGCC"
|
| 973 |
style="font-family:'JetBrains Mono',monospace;font-size:12px;padding:6px 10px;border:1px solid #ccc;border-radius:3px;flex:1 1 280px;letter-spacing:1px;text-transform:uppercase">
|
| 974 |
<span class="spacer"></span>
|
| 975 |
+
<span class="status"><span class="dot" style="background:#317f3f"></span><span id="d7-len">30 bp</span></span>
|
| 976 |
</div>
|
| 977 |
|
| 978 |
<div id="d7-cols" style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-top:8px">
|
|
|
|
| 1000 |
|
| 1001 |
<div class="track-axis-label">
|
| 1002 |
<span>same DNA span</span>
|
| 1003 |
+
<span style="color:#317f3f">▼ shorter token sequence = cheaper attention</span>
|
| 1004 |
+
<span id="d7-speedup" style="color:#317f3f;font-weight:500">36× cheaper</span>
|
| 1005 |
</div>
|
| 1006 |
</div>
|
| 1007 |
|
|
|
|
| 1089 |
<div class="seq-label">signal-to-noise · raw genome vs annotation-aware curation</div>
|
| 1090 |
<svg id="d9-snr" viewBox="0 0 1000 100" preserveAspectRatio="none" style="display:block;width:100%;height:90px;background:#fff;border:1px solid #eee"></svg>
|
| 1091 |
<div class="track-axis-label">
|
| 1092 |
+
<span><span class="legend-swatch" style="background:#317f3f"></span>functional / annotated</span>
|
| 1093 |
<span><span class="legend-swatch" style="background:#ddd"></span>background</span>
|
| 1094 |
<span style="color:#888">curating raises the density of biological signal in the gradient</span>
|
| 1095 |
</div>
|
|
|
|
| 1144 |
<!-- ============================================================ -->
|
| 1145 |
<div class="tab-panel" id="panel-sandbox" data-tab="sandbox">
|
| 1146 |
|
| 1147 |
+
<div class="tab-lede">
|
| 1148 |
+
<p>Open-ended DNA continuation. Type any prefix in {A, C, G, T}, watch the model continue token by token. Toggle base-coloring or per-token logprob coloring to see where Carbon is confident and where it's guessing. Track GC content, perplexity, and throughput live.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1149 |
</div>
|
| 1150 |
|
| 1151 |
<div class="container" style="max-width:1280px">
|
|
|
|
| 1235 |
// =========================================================================
|
| 1236 |
// Shared helpers
|
| 1237 |
// =========================================================================
|
| 1238 |
+
const DARK_RGB = [31, 31, 29];
|
| 1239 |
const MID_RGB = [136, 136, 136];
|
| 1240 |
+
const RED_RGB = [188, 46, 37];
|
| 1241 |
const PROMPT_RGB = [170, 170, 170];
|
| 1242 |
|
| 1243 |
function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
|
|
|
|
| 1447 |
const matches = genText[genIdx] === base;
|
| 1448 |
return matches
|
| 1449 |
? { style: "color:#bbb" }
|
| 1450 |
+
: { style: "color:#b00020;background:rgba(188,46,37,0.18)" };
|
| 1451 |
};
|
| 1452 |
renderSeq(els.ref, refSeq, bpl, colorRef);
|
| 1453 |
}
|
|
|
|
| 1658 |
const wRef = (Math.abs(c.refSum) / range) * 100;
|
| 1659 |
const wAlt = (Math.abs(c.altSum) / range) * 100;
|
| 1660 |
const delta = c.altSum - c.refSum;
|
| 1661 |
+
const dColor = delta < -0.5 ? "#bc2e25" : (delta > 0.5 ? "#317f3f" : "#888");
|
| 1662 |
els.result.innerHTML = `
|
| 1663 |
<div class="row-label">ref · ${v.ref}</div>
|
| 1664 |
<div class="row-bar ref"><div class="fill" style="width:${wRef.toFixed(1)}%"></div></div>
|
|
|
|
| 1693 |
const innerW = W - padL - padR;
|
| 1694 |
const center = padL + innerW / 2;
|
| 1695 |
const scale = (innerW / 2) / absMax;
|
| 1696 |
+
const sigColor = (s) => s === "Pathogenic" ? "#bc2e25" : s === "Benign" ? "#317f3f" : "#e69500";
|
| 1697 |
|
| 1698 |
// Bar color: encode the *model's* opinion of the alt allele
|
| 1699 |
// - Δ < 0 : red (model finds alt unusual). Saturation ~ |Δ|.
|
|
|
|
| 1715 |
let svg = "";
|
| 1716 |
|
| 1717 |
// --- Top axis: directional caption ---
|
| 1718 |
+
svg += `<text x="${padL.toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#bc2e25" letter-spacing="1">← MODEL SURPRISED BY ALT</text>`;
|
| 1719 |
svg += `<text x="${(W - padR).toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#333" letter-spacing="1" text-anchor="end">MODEL FINE WITH ALT →</text>`;
|
| 1720 |
svg += `<text x="${center.toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#888" text-anchor="middle" letter-spacing="1">Δ (alt − ref)</text>`;
|
| 1721 |
svg += `<text x="${(padL - 12).toFixed(1)}" y="14" font-family="JetBrains Mono" font-size="9" fill="#888" text-anchor="end" letter-spacing="1">VARIANT</text>`;
|
| 1722 |
|
| 1723 |
// Faint shading: pathogenic-expected zone (left of 0)
|
| 1724 |
+
svg += `<rect x="${padL.toFixed(1)}" y="${(padT - 4).toFixed(1)}" width="${(center - padL).toFixed(1)}" height="${(ordered.length * rowH + 8).toFixed(1)}" fill="#bc2e25" opacity="0.04"/>`;
|
| 1725 |
|
| 1726 |
// Center line
|
| 1727 |
svg += `<line x1="${center}" y1="${padT - 4}" x2="${center}" y2="${H - padB + 4}" stroke="#bbb" stroke-width="1"/>`;
|
|
|
|
| 1754 |
const color = barColor(d);
|
| 1755 |
const barX = Math.min(center, x);
|
| 1756 |
const barW = Math.max(2, Math.abs(x - center));
|
| 1757 |
+
svg += `<rect x="${barX.toFixed(1)}" y="${(y - 8).toFixed(1)}" width="${barW.toFixed(1)}" height="14" fill="${color}" stroke="${v === selected ? '#1f1f1d' : 'none'}" stroke-width="${v === selected ? 1 : 0}"/>`;
|
| 1758 |
|
| 1759 |
const label = (d >= 0 ? "+" : "") + d.toFixed(2);
|
| 1760 |
const insideOK = barW >= VALUE_INSIDE_MIN && Math.abs(d) >= 0.5; // color is dark enough only away from neutral
|
|
|
|
| 1977 |
if (e.start > scoredLen) continue;
|
| 1978 |
const x = xScale(e.start);
|
| 1979 |
const w = xScale(Math.min(e.end, scoredLen)) - x;
|
| 1980 |
+
svg += `<rect x="${x.toFixed(1)}" y="0" width="${Math.max(1, w).toFixed(1)}" height="${H}" fill="#317f3f" opacity="0.08"/>`;
|
| 1981 |
}
|
| 1982 |
|
| 1983 |
// Smoothed line: a moving average over the points (window=5)
|
|
|
|
| 2002 |
smoothed.forEach((p, i) => {
|
| 2003 |
d += (i === 0 ? "M" : "L") + xScale(p.pos).toFixed(1) + " " + yScale(p.lp).toFixed(1);
|
| 2004 |
});
|
| 2005 |
+
svg += `<path d="${d}" fill="none" stroke="#1f1f1d" stroke-width="1.2" stroke-linejoin="round"/>`;
|
| 2006 |
|
| 2007 |
// Y-axis ticks
|
| 2008 |
const tickLps = [yMin + (yMax - yMin) * 0.1, yMin + (yMax - yMin) * 0.5, yMin + (yMax - yMin) * 0.9];
|
|
|
|
| 2252 |
const matches = genText[genIdx] === base;
|
| 2253 |
return matches
|
| 2254 |
? { style: "color:#bbb" }
|
| 2255 |
+
: { style: "color:#b00020;background:rgba(188,46,37,0.18)" };
|
| 2256 |
};
|
| 2257 |
const bpl2 = basesPerLine(refEl);
|
| 2258 |
renderSeq(refEl, refSeq, bpl2, colorRef);
|
|
|
|
| 2398 |
speedup: document.getElementById("d7-speedup"),
|
| 2399 |
};
|
| 2400 |
// 8-color palette for 6-mer tokens (cycle); 1-mer uses base coloring.
|
| 2401 |
+
const TOKEN_PALETTE = ["#317f3f","#2c5aa0","#c08030","#7a4baa","#2a8a8a","#b03b6e","#5a6e30","#a87a30"];
|
| 2402 |
const BASE_FILL = { A:"#3a8a3e", C:"#2e6bb8", G:"#b5891e", T:"#b53a3a", N:"#888" };
|
| 2403 |
|
| 2404 |
function clean(s) { return (s || "").toUpperCase().replace(/[^ACGTN]/g, ""); }
|
|
|
|
| 2441 |
let svg = "";
|
| 2442 |
const rows = [
|
| 2443 |
{ label: "1-mer", n: n1, cost: n1 * n1, color: "#888" },
|
| 2444 |
+
{ label: "6-mer", n: n6, cost: n6 * n6, color: "#317f3f" },
|
| 2445 |
];
|
| 2446 |
rows.forEach((r, i) => {
|
| 2447 |
const y = padT + i * (rowH + 8);
|
|
|
|
| 2505 |
html += `<div style="display:grid;grid-template-columns:140px 1fr 1fr;gap:8px 14px;align-items:center;font-family:'JetBrains Mono',monospace;font-size:11px">`;
|
| 2506 |
// Header
|
| 2507 |
html += `<div></div>`;
|
| 2508 |
+
html += `<div style="font-size:9px;color:${mode==='fns'?'#aaa':'#1f1f1d'};text-transform:uppercase;letter-spacing:1.5px">${mode==='fns'?'cross-entropy':'cross-entropy'} <span style="font-weight:500">${mode==='ce'||mode==='both'?'· active':''}</span></div>`;
|
| 2509 |
+
html += `<div style="font-size:9px;color:${mode==='ce'?'#aaa':'#1f1f1d'};text-transform:uppercase;letter-spacing:1.5px">FNS <span style="font-weight:500">${mode==='fns'||mode==='both'?'· active':''}</span></div>`;
|
| 2510 |
|
| 2511 |
cands.forEach(c => {
|
| 2512 |
const isTarget = c === t1Equal(c, target);
|
|
|
|
| 2514 |
let badges = "";
|
| 2515 |
for (let i = 0; i < 6; i++) {
|
| 2516 |
const match = c[i] === target[i];
|
| 2517 |
+
const color = match ? "#317f3f" : "#bc2e25";
|
| 2518 |
+
const bg = match ? "rgba(49,127,63,0.10)" : "rgba(188,46,37,0.08)";
|
| 2519 |
badges += `<span style="display:inline-block;background:${bg};color:${color};padding:2px 5px;margin:1px;border-radius:2px;font-weight:${match?500:400}">${c[i]}</span>`;
|
| 2520 |
}
|
| 2521 |
const isExact = c === target;
|
| 2522 |
const labelText = isExact ? "exact target" : `${nMatches(c, target)}/6 match`;
|
| 2523 |
html += `<div style="display:flex;flex-direction:column;gap:2px">
|
| 2524 |
<div>${badges}</div>
|
| 2525 |
+
<div style="font-size:9px;color:${isExact?'#317f3f':'#888'};letter-spacing:1px;text-transform:uppercase;padding-left:4px">${labelText}</div>
|
| 2526 |
</div>`;
|
| 2527 |
|
| 2528 |
// CE column
|
| 2529 |
const ceVal = ceCredit(c, target);
|
| 2530 |
+
html += creditCell(ceVal, mode === "fns", c === target ? "credit = 1" : "credit = 0", "#317f3f", "#bc2e25");
|
| 2531 |
|
| 2532 |
// FNS column
|
| 2533 |
const fnsVal = fnsCredit(c, target);
|
| 2534 |
const fnsLabel = c === target ? "credit = 1" : `credit = ${fnsVal.toFixed(2)} (${nMatches(c, target)}/6)`;
|
| 2535 |
+
html += creditCell(fnsVal, mode === "ce", fnsLabel, "#317f3f", "#bc2e25");
|
| 2536 |
});
|
| 2537 |
html += `</div>`;
|
| 2538 |
canvas.innerHTML = html;
|
|
|
|
| 2563 |
let svg = "";
|
| 2564 |
|
| 2565 |
// Background two-tone (CE phase + FNS phase)
|
| 2566 |
+
svg += `<rect x="${padL}" y="${padT}" width="${(switchX - padL).toFixed(1)}" height="${H - padT - padB}" fill="#1f1f1d" opacity="0.04"/>`;
|
| 2567 |
+
svg += `<rect x="${switchX.toFixed(1)}" y="${padT}" width="${(W - padR - switchX).toFixed(1)}" height="${H - padT - padB}" fill="#317f3f" opacity="0.06"/>`;
|
| 2568 |
|
| 2569 |
// Mock loss curve: smooth descent then a "staircase" that gets cleaned by the FNS switch
|
| 2570 |
const points = [];
|
|
|
|
| 2587 |
}
|
| 2588 |
let d = "";
|
| 2589 |
points.forEach(([x, y], i) => { d += (i === 0 ? "M" : "L") + x.toFixed(1) + " " + y.toFixed(1); });
|
| 2590 |
+
svg += `<path d="${d}" fill="none" stroke="#1f1f1d" stroke-width="1.4"/>`;
|
| 2591 |
|
| 2592 |
// Switch marker
|
| 2593 |
+
svg += `<line x1="${switchX.toFixed(1)}" y1="${padT}" x2="${switchX.toFixed(1)}" y2="${H - padB}" stroke="#317f3f" stroke-width="1.5" stroke-dasharray="3,3"/>`;
|
| 2594 |
+
svg += `<text x="${switchX.toFixed(1)}" y="${(padT - 4)}" font-family="JetBrains Mono" font-size="9" fill="#317f3f" text-anchor="middle" letter-spacing="1">CE → FNS</text>`;
|
| 2595 |
|
| 2596 |
// Phase labels
|
| 2597 |
svg += `<text x="${(padL + (switchX - padL) / 2).toFixed(1)}" y="${(H - padB + 14).toFixed(1)}" font-family="JetBrains Mono" font-size="10" fill="#666" text-anchor="middle" letter-spacing="1">CROSS-ENTROPY · learn joint structure</text>`;
|
| 2598 |
+
svg += `<text x="${(switchX + (W - padR - switchX) / 2).toFixed(1)}" y="${(H - padB + 14).toFixed(1)}" font-family="JetBrains Mono" font-size="10" fill="#317f3f" text-anchor="middle" letter-spacing="1">FNS · smooth, BF16-stable refinement</text>`;
|
| 2599 |
// y-axis label
|
| 2600 |
svg += `<text x="${padL}" y="${(padT - 4)}" font-family="JetBrains Mono" font-size="9" fill="#aaa" letter-spacing="0.5">training loss</text>`;
|
| 2601 |
// staircase callout
|
| 2602 |
const staircaseX = padL + innerW * (switchAt - 0.04);
|
| 2603 |
+
svg += `<text x="${staircaseX.toFixed(1)}" y="${(H - padB - 4).toFixed(1)}" font-family="JetBrains Mono" font-size="9" fill="#bc2e25" text-anchor="end">↑ staircase</text>`;
|
| 2604 |
|
| 2605 |
schedule.innerHTML = svg;
|
| 2606 |
}
|
|
|
|
| 2635 |
const tplEl = document.getElementById("d9-templates");
|
| 2636 |
|
| 2637 |
const COMPOSITION = [
|
| 2638 |
+
{ label: "GENERator-v2", pct: 70, color: "#317f3f", desc: "annotation-aware functional genomic backbone (eukaryotic, gene-centric)" },
|
| 2639 |
{ label: "mRNA", pct: 16, color: "#2c5aa0", desc: "OpenGenome2 mature transcripts · RNA-level functional context" },
|
| 2640 |
{ label: "GTDB", pct: 10, color: "#c08030", desc: "OpenGenome2 prokaryotic genomes · compact bacterial structure" },
|
| 2641 |
{ label: "mRNA-splice", pct: 4, color: "#7a4baa", desc: "OpenGenome2 transcript-derived · splice-related signal" },
|
|
|
|
| 2670 |
}
|
| 2671 |
}
|
| 2672 |
// Inner hole for donut
|
| 2673 |
+
svg += `<circle cx="${cx}" cy="${cy}" r="${r * 0.42}" fill="#f7f5ee"/>`;
|
| 2674 |
+
svg += `<text x="${cx}" y="${cy - 2}" font-family="JetBrains Mono" font-size="11" fill="#1f1f1d" text-anchor="middle" font-weight="500">CARBON</text>`;
|
| 2675 |
svg += `<text x="${cx}" y="${cy + 12}" font-family="JetBrains Mono" font-size="9" fill="#888" text-anchor="middle">corpus</text>`;
|
| 2676 |
pieEl.innerHTML = svg;
|
| 2677 |
}
|
|
|
|
| 2681 |
<div style="display:flex;align-items:flex-start;gap:10px;padding:6px 0;border-bottom:1px solid #f0f0f0">
|
| 2682 |
<span style="display:inline-block;flex:0 0 12px;height:12px;background:${s.color};border-radius:2px;margin-top:3px"></span>
|
| 2683 |
<div style="flex:1">
|
| 2684 |
+
<div><strong style="color:#1f1f1d">${s.label}</strong> <span style="color:#888">· ${s.pct}%</span></div>
|
| 2685 |
<div style="color:#888;font-size:10px;margin-top:1px">${s.desc}</div>
|
| 2686 |
</div>
|
| 2687 |
</div>
|
|
|
|
| 2703 |
const rowW = W - padL - 12;
|
| 2704 |
for (const seg of segs) {
|
| 2705 |
const w = (seg.frac * rowW);
|
| 2706 |
+
svg += `<rect x="${cursor.toFixed(1)}" y="${y}" width="${Math.max(0.5, w).toFixed(1)}" height="${segH}" fill="${seg.func ? '#317f3f' : '#ddd'}"/>`;
|
| 2707 |
cursor += w;
|
| 2708 |
}
|
| 2709 |
}
|
|
|
|
| 2728 |
function renderTemplates() {
|
| 2729 |
let html = "";
|
| 2730 |
for (const t of TEMPLATES) {
|
| 2731 |
+
html += `<div style="text-align:right;color:#317f3f;font-weight:500">${t.pct}</div>`;
|
| 2732 |
+
html += `<div><span style="background:#f4f4f4;padding:2px 6px;border-radius:2px;color:#1f1f1d">${t.body.replace(/</g,"<").replace(/>/g,">")}</span> <span style="color:#888;font-size:10px;margin-left:8px">${t.note}</span></div>`;
|
| 2733 |
}
|
| 2734 |
tplEl.innerHTML = html;
|
| 2735 |
}
|
|
|
|
| 2761 |
let html = `<thead>
|
| 2762 |
<tr>
|
| 2763 |
<th style="text-align:left;padding:10px 6px 8px;border-bottom:1px solid #ddd;font-size:10px;color:#888;text-transform:uppercase;letter-spacing:1.5px;font-weight:400"></th>
|
| 2764 |
+
<th style="text-align:left;padding:10px 12px 8px;border-bottom:1px solid #ddd;font-size:11px;color:#1f1f1d;letter-spacing:1px">Carbon · 3B</th>
|
| 2765 |
+
<th style="text-align:left;padding:10px 12px 8px;border-bottom:1px solid #ddd;font-size:11px;color:#1f1f1d;letter-spacing:1px">Carbon · 8B</th>
|
| 2766 |
</tr>
|
| 2767 |
</thead><tbody>`;
|
| 2768 |
ROWS.forEach((r, i) => {
|
| 2769 |
+
const bg = i % 2 === 0 ? "#f7f5ee" : "#fff";
|
| 2770 |
html += `<tr style="background:${bg}">
|
| 2771 |
<td style="padding:6px;color:#666;font-size:10px;text-transform:uppercase;letter-spacing:1px">${r[0]}</td>
|
| 2772 |
+
<td style="padding:6px 12px;color:#1f1f1d">${r[1]}</td>
|
| 2773 |
+
<td style="padding:6px 12px;color:#1f1f1d">${r[2]}</td>
|
| 2774 |
</tr>`;
|
| 2775 |
});
|
| 2776 |
html += `</tbody>`;
|
|
|
|
| 2810 |
A: [58, 138, 62], C: [46, 107, 184], G: [181, 137, 30], T: [181, 58, 58], N: [136, 136, 136],
|
| 2811 |
};
|
| 2812 |
const PROMPT_RGB_S = [170, 170, 170];
|
| 2813 |
+
const DARK_RGB_S = [31, 31, 29];
|
| 2814 |
const MID_RGB_S = [136, 136, 136];
|
| 2815 |
+
const RED_RGB_S = [188, 46, 37];
|
| 2816 |
const BG_ALPHA = 0.12;
|
| 2817 |
|
| 2818 |
let promptBases = "";
|
|
|
|
| 2845 |
const bar = document.getElementById("sb-legend-bar");
|
| 2846 |
if (!lpRange) {
|
| 2847 |
minEl.textContent = midEl.textContent = maxEl.textContent = "—";
|
| 2848 |
+
bar.style.background = "linear-gradient(to right, #bc2e25, #888, #1f1f1d)";
|
| 2849 |
} else {
|
| 2850 |
const { min, mid, max } = lpRange;
|
| 2851 |
minEl.textContent = min.toFixed(1);
|
| 2852 |
midEl.textContent = mid.toFixed(1);
|
| 2853 |
maxEl.textContent = max.toFixed(1);
|
| 2854 |
const midPct = max > min ? ((mid - min) / (max - min)) * 100 : 50;
|
| 2855 |
+
bar.style.background = `linear-gradient(to right, #bc2e25 0%, #888 ${midPct.toFixed(1)}%, #1f1f1d 100%)`;
|
| 2856 |
}
|
| 2857 |
updateLpChart();
|
| 2858 |
}
|
|
|
|
| 2888 |
svg.innerHTML = `
|
| 2889 |
<defs>
|
| 2890 |
<linearGradient id="sb-lp-grad" gradientUnits="userSpaceOnUse" x1="0" y1="${H - pad}" x2="0" y2="${pad}">
|
| 2891 |
+
<stop offset="0%" stop-color="#bc2e25"/>
|
| 2892 |
<stop offset="${midPct.toFixed(1)}%" stop-color="#888"/>
|
| 2893 |
+
<stop offset="100%" stop-color="#1f1f1d"/>
|
| 2894 |
</linearGradient>
|
| 2895 |
</defs>
|
| 2896 |
<path d="${d}" fill="none" stroke="url(#sb-lp-grad)" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
|
|
|
| 3241 |
}
|
| 3242 |
})();
|
| 3243 |
|
| 3244 |
+
// =========================================================================
|
| 3245 |
+
// Carbon banner — animated DNA helix
|
| 3246 |
+
// =========================================================================
|
| 3247 |
+
(function initCarbonBanner() {
|
| 3248 |
+
const banner = document.querySelector(".carbon-banner");
|
| 3249 |
+
if (!banner) return;
|
| 3250 |
+
const svgNS = "http://www.w3.org/2000/svg";
|
| 3251 |
+
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
| 3252 |
+
const sequence = ["A","T","A","A","C","G","A","C","T","T","C","C","C","T","A","T","T","G"];
|
| 3253 |
+
const complement = { A: "T", T: "A", C: "G", G: "C" };
|
| 3254 |
+
|
| 3255 |
+
const helix = {
|
| 3256 |
+
startX: 868, endX: 1988, centerY: 318, amplitude: 88,
|
| 3257 |
+
cycles: 3.62, speed: 0.00015,
|
| 3258 |
+
rungCount: 27, segmentCount: 96,
|
| 3259 |
+
bodyRadius: 6.4, shadowRadius: 8.4,
|
| 3260 |
+
rungInset: 9.2, glyphGap: 13.5,
|
| 3261 |
+
};
|
| 3262 |
+
|
| 3263 |
+
const $ = (id) => banner.querySelector("#" + id);
|
| 3264 |
+
const els = {
|
| 3265 |
+
strandBackLayer: $("cb-strandBackLayer"),
|
| 3266 |
+
strandFrontLayer: $("cb-strandFrontLayer"),
|
| 3267 |
+
rungs: $("cb-rungs"),
|
| 3268 |
+
baseLetters: $("cb-baseLetters"),
|
| 3269 |
+
};
|
| 3270 |
+
|
| 3271 |
+
function makeEl(name, attrs = {}) {
|
| 3272 |
+
const node = document.createElementNS(svgNS, name);
|
| 3273 |
+
for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, v);
|
| 3274 |
+
return node;
|
| 3275 |
+
}
|
| 3276 |
+
function makeBaseGlyph(letter) {
|
| 3277 |
+
const glyph = makeEl("g", { class: "cb-base-glyph" });
|
| 3278 |
+
const paths = {
|
| 3279 |
+
A: "M -5 8 L 0 -8 L 5 8 M -3.1 2 L 3.1 2",
|
| 3280 |
+
C: "M 5 -6 C 1 -9 -6 -7 -6 0 C -6 7 1 9 5 6",
|
| 3281 |
+
G: "M 5 -6 C 1 -9 -6 -7 -6 0 C -6 7 1 9 5 6 M 5 1 L 1 1 M 5 1 L 5 6",
|
| 3282 |
+
T: "M -6 -7 L 6 -7 M 0 -7 L 0 8",
|
| 3283 |
+
};
|
| 3284 |
+
glyph.appendChild(makeEl("path", { d: paths[letter] }));
|
| 3285 |
+
return glyph;
|
| 3286 |
+
}
|
| 3287 |
+
|
| 3288 |
+
const segmentNodes = [];
|
| 3289 |
+
const rungNodes = [];
|
| 3290 |
+
const letterNodes = [];
|
| 3291 |
+
|
| 3292 |
+
function makeSegmentNode(strandOffset, index) {
|
| 3293 |
+
const group = makeEl("g");
|
| 3294 |
+
const shadow = makeEl("path", { class: "cb-helix-shadow" });
|
| 3295 |
+
const body = makeEl("path", { class: "cb-helix-body" });
|
| 3296 |
+
const texture= makeEl("path", { class: "cb-helix-texture" });
|
| 3297 |
+
const edgeA = makeEl("path", { class: "cb-helix-edge" });
|
| 3298 |
+
const edgeB = makeEl("path", { class: "cb-helix-edge" });
|
| 3299 |
+
group.append(shadow, body, texture, edgeA, edgeB);
|
| 3300 |
+
return { group, shadow, body, texture, edgeA, edgeB, strandOffset, index, z: 0 };
|
| 3301 |
+
}
|
| 3302 |
+
|
| 3303 |
+
for (let i = 0; i < helix.segmentCount; i++) {
|
| 3304 |
+
segmentNodes.push(makeSegmentNode(0, i));
|
| 3305 |
+
segmentNodes.push(makeSegmentNode(Math.PI, i));
|
| 3306 |
+
}
|
| 3307 |
+
for (let i = 0; i < helix.rungCount; i++) {
|
| 3308 |
+
const path = makeEl("path", { class: "cb-base-pair" });
|
| 3309 |
+
els.rungs.appendChild(path);
|
| 3310 |
+
rungNodes.push(path);
|
| 3311 |
+
const pair = sequence[i % sequence.length];
|
| 3312 |
+
const mate = complement[pair];
|
| 3313 |
+
const groupA = makeEl("g", { class: "cb-base-letter-node" });
|
| 3314 |
+
const groupB = makeEl("g", { class: "cb-base-letter-node" });
|
| 3315 |
+
groupA.appendChild(makeBaseGlyph(pair));
|
| 3316 |
+
groupB.appendChild(makeBaseGlyph(mate));
|
| 3317 |
+
els.baseLetters.append(groupA, groupB);
|
| 3318 |
+
letterNodes.push([groupA, groupB]);
|
| 3319 |
+
}
|
| 3320 |
+
|
| 3321 |
+
function pointAt(x, offset, phase) {
|
| 3322 |
+
const t = (x - helix.startX) / (helix.endX - helix.startX);
|
| 3323 |
+
const theta = t * helix.cycles * Math.PI * 2 + phase + offset;
|
| 3324 |
+
const slope = Math.cos(theta) * helix.amplitude * helix.cycles * Math.PI * 2 / (helix.endX - helix.startX);
|
| 3325 |
+
const normalLength = Math.hypot(slope, 1);
|
| 3326 |
+
return {
|
| 3327 |
+
x,
|
| 3328 |
+
y: helix.centerY + Math.sin(theta) * helix.amplitude,
|
| 3329 |
+
z: Math.cos(theta),
|
| 3330 |
+
nx: -slope / normalLength,
|
| 3331 |
+
ny: 1 / normalLength,
|
| 3332 |
+
};
|
| 3333 |
+
}
|
| 3334 |
+
function samplesFor(offset, phase) {
|
| 3335 |
+
const points = [];
|
| 3336 |
+
for (let i = 0; i <= helix.segmentCount; i++) {
|
| 3337 |
+
const x = helix.startX + ((helix.endX - helix.startX) * i) / helix.segmentCount;
|
| 3338 |
+
points.push(pointAt(x, offset, phase));
|
| 3339 |
+
}
|
| 3340 |
+
return points;
|
| 3341 |
+
}
|
| 3342 |
+
const shiftedPoint = (p, r) => ({ x: p.x + p.nx * r, y: p.y + p.ny * r });
|
| 3343 |
+
function lineSegmentPath(a, b, radius) {
|
| 3344 |
+
const p0 = shiftedPoint(a, radius), p1 = shiftedPoint(b, radius);
|
| 3345 |
+
return `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} L ${p1.x.toFixed(2)} ${p1.y.toFixed(2)}`;
|
| 3346 |
+
}
|
| 3347 |
+
function ribbonSegmentPath(a, b, radius) {
|
| 3348 |
+
const a0 = shiftedPoint(a, radius);
|
| 3349 |
+
const b0 = shiftedPoint(b, radius);
|
| 3350 |
+
const b1 = shiftedPoint(b, -radius);
|
| 3351 |
+
const a1 = shiftedPoint(a, -radius);
|
| 3352 |
+
return `M ${a0.x.toFixed(2)} ${a0.y.toFixed(2)} L ${b0.x.toFixed(2)} ${b0.y.toFixed(2)} L ${b1.x.toFixed(2)} ${b1.y.toFixed(2)} L ${a1.x.toFixed(2)} ${a1.y.toFixed(2)} Z`;
|
| 3353 |
+
}
|
| 3354 |
+
function setPath(el, d, opacity = 1) {
|
| 3355 |
+
el.setAttribute("d", d);
|
| 3356 |
+
el.style.opacity = opacity;
|
| 3357 |
+
}
|
| 3358 |
+
function updateSegment(node, points) {
|
| 3359 |
+
const a = points[node.index];
|
| 3360 |
+
const b = points[node.index + 1];
|
| 3361 |
+
const z = (a.z + b.z) / 2;
|
| 3362 |
+
const front = Math.max(0, Math.min(1, (z + 1) / 2));
|
| 3363 |
+
node.z = z;
|
| 3364 |
+
setPath(node.shadow, ribbonSegmentPath(a, b, helix.shadowRadius), 0.05 + front * 0.12);
|
| 3365 |
+
setPath(node.body, ribbonSegmentPath(a, b, helix.bodyRadius), 0.5 + front * 0.42);
|
| 3366 |
+
setPath(node.texture, ribbonSegmentPath(a, b, helix.bodyRadius - 0.8), 0.14 + front * 0.22);
|
| 3367 |
+
setPath(node.edgeA, lineSegmentPath(a, b, helix.bodyRadius + 0.35), 0.2 + front * 0.68);
|
| 3368 |
+
setPath(node.edgeB, lineSegmentPath(a, b, -helix.bodyRadius - 0.35), 0.2 + front * 0.68);
|
| 3369 |
+
}
|
| 3370 |
+
function brokenRungPath(x, yStart, letterYs, yEnd, gap) {
|
| 3371 |
+
const start = Math.min(yStart, yEnd);
|
| 3372 |
+
const end = Math.max(yStart, yEnd);
|
| 3373 |
+
const gaps = letterYs
|
| 3374 |
+
.map(y => [Math.max(start, y - gap), Math.min(end, y + gap)])
|
| 3375 |
+
.filter(([f, t]) => t > f)
|
| 3376 |
+
.sort((a, b) => a[0] - b[0]);
|
| 3377 |
+
const merged = [];
|
| 3378 |
+
for (const r of gaps) {
|
| 3379 |
+
const last = merged[merged.length - 1];
|
| 3380 |
+
if (!last || r[0] > last[1]) merged.push(r);
|
| 3381 |
+
else last[1] = Math.max(last[1], r[1]);
|
| 3382 |
+
}
|
| 3383 |
+
const parts = [];
|
| 3384 |
+
const addPart = (f, t) => { if (t - f > 0.7) parts.push(`M ${x.toFixed(2)} ${f.toFixed(2)} L ${x.toFixed(2)} ${t.toFixed(2)}`); };
|
| 3385 |
+
let cursor = start;
|
| 3386 |
+
for (const [f, t] of merged) { addPart(cursor, f); cursor = t; }
|
| 3387 |
+
addPart(cursor, end);
|
| 3388 |
+
return parts.join(" ");
|
| 3389 |
+
}
|
| 3390 |
+
function draw(ts = 0) {
|
| 3391 |
+
const phase = prefersReduced ? 0.6 : ts * helix.speed;
|
| 3392 |
+
const pointsByOffset = new Map([
|
| 3393 |
+
[0, samplesFor(0, phase)],
|
| 3394 |
+
[Math.PI, samplesFor(Math.PI, phase)],
|
| 3395 |
+
]);
|
| 3396 |
+
for (const node of segmentNodes) {
|
| 3397 |
+
updateSegment(node, pointsByOffset.get(node.strandOffset));
|
| 3398 |
+
}
|
| 3399 |
+
els.strandBackLayer.replaceChildren();
|
| 3400 |
+
els.strandFrontLayer.replaceChildren();
|
| 3401 |
+
for (const node of segmentNodes.slice().sort((a, b) => a.z - b.z)) {
|
| 3402 |
+
const target = node.z >= 0 ? els.strandFrontLayer : els.strandBackLayer;
|
| 3403 |
+
target.appendChild(node.group);
|
| 3404 |
+
}
|
| 3405 |
+
for (let i = 0; i < helix.rungCount; i++) {
|
| 3406 |
+
const t = i / (helix.rungCount - 1);
|
| 3407 |
+
const x = helix.startX + (helix.endX - helix.startX) * t;
|
| 3408 |
+
const a = pointAt(x, 0, phase);
|
| 3409 |
+
const b = pointAt(x, Math.PI, phase);
|
| 3410 |
+
const yTop = Math.min(a.y, b.y);
|
| 3411 |
+
const yBottom = Math.max(a.y, b.y);
|
| 3412 |
+
const span = yBottom - yTop;
|
| 3413 |
+
const inset = Math.min(helix.rungInset, Math.max(0, span * 0.5 - 3));
|
| 3414 |
+
const visible = Math.max(0, Math.min(1, (span - 34) / 70));
|
| 3415 |
+
const aLetterY = a.y + (b.y - a.y) * 0.34;
|
| 3416 |
+
const bLetterY = b.y + (a.y - b.y) * 0.34;
|
| 3417 |
+
const letterGap = Math.min(helix.glyphGap, Math.max(7, span * 0.13));
|
| 3418 |
+
rungNodes[i].setAttribute("d", brokenRungPath(x, yTop + inset, [aLetterY, bLetterY], yBottom - inset, letterGap));
|
| 3419 |
+
rungNodes[i].style.opacity = 0.18 + visible * 0.72;
|
| 3420 |
+
const [sA, sB] = letterNodes[i];
|
| 3421 |
+
sA.setAttribute("transform", `translate(${x} ${aLetterY})`);
|
| 3422 |
+
sB.setAttribute("transform", `translate(${x} ${bLetterY})`);
|
| 3423 |
+
sA.style.opacity = 0.16 + visible * 0.84;
|
| 3424 |
+
sB.style.opacity = 0.16 + visible * 0.84;
|
| 3425 |
+
}
|
| 3426 |
+
if (!prefersReduced) requestAnimationFrame(draw);
|
| 3427 |
+
}
|
| 3428 |
+
draw();
|
| 3429 |
+
})();
|
| 3430 |
+
|
| 3431 |
// =========================================================================
|
| 3432 |
// Tab switching + hash routing
|
| 3433 |
// =========================================================================
|