lvwerra HF Staff commited on
Commit
a6fa94d
·
1 Parent(s): af87cd0

feat: ship widget (extracted from blog-preview.html)

Browse files
Files changed (3) hide show
  1. README.md +12 -4
  2. index.html +1363 -18
  3. style.css +0 -28
README.md CHANGED
@@ -1,10 +1,18 @@
1
  ---
2
- title: Carbon Tokenization 02 Tokenizers Compared
3
- emoji: 📊
4
  colorFrom: green
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Per-base, BPE, and 6-mer on the same DNA sequence
3
+ emoji: 🧬
4
  colorFrom: green
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
  ---
9
 
10
+ # Per-base, BPE, and 6-mer on the same DNA sequence
11
+
12
+ Standalone widget from the Carbon tokenization article. Embed via:
13
+
14
+ ```html
15
+ <iframe src="https://huggingfacebio-carbon-tokenization-02-tokenizers-compared.hf.space" loading="lazy"></iframe>
16
+ ```
17
+
18
+ Generated from [`blog-preview.html`](https://github.com/HuggingFaceBio/carbon-tokenization) by `widgets/_extract.py` — edit the source there, not here.
index.html CHANGED
@@ -1,19 +1,1364 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Per-base, BPE, and 6-mer on the same DNA sequence</title>
7
+ <meta name="color-scheme" content="light">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap">
11
+ <style>
12
+ :root {
13
+ --bg: #f7f5ee;
14
+ --ink: #1f1f1d;
15
+ --ink-soft: #5b5b56;
16
+ --ink-faint: #8a8a83;
17
+ --rule: #e3e1d6;
18
+ --hair: #eee;
19
+ --green: #317f3f;
20
+ --green-dark: #1f5024;
21
+ --green-tint: #f4f8f4;
22
+ --amber: #b8862c;
23
+ --amber-dark: #6b4d18;
24
+ --red: #b00020;
25
+ --blue: #2c5aa0;
26
+ --card: #fff;
27
+ --soft-cream: #fafaf6;
28
+ --base-a: #1A7A40;
29
+ --base-t: #b00020;
30
+ --base-c: #2c5aa0;
31
+ --base-g: #b8862c;
32
+ }
33
+ * { margin: 0; padding: 0; box-sizing: border-box; }
34
+ html { scroll-behavior: smooth; }
35
+ body {
36
+ font-family: "Inter", "Helvetica Neue", sans-serif;
37
+ font-size: 14px; font-weight: 300; line-height: 1.7;
38
+ color: var(--ink);
39
+ background: var(--bg);
40
+ }
41
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
42
+ ::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
43
+ ::-webkit-scrollbar-track { background: transparent; }
44
+
45
+ /* --- Page header ----------------------------------------------------- */
46
+ .page-header {
47
+ border-bottom: 1px solid var(--rule);
48
+ background: var(--bg);
49
+ position: sticky; top: 0; z-index: 50;
50
+ backdrop-filter: saturate(180%) blur(6px);
51
+ }
52
+ .page-header__inner {
53
+ max-width: 1200px;
54
+ margin: 0 auto;
55
+ padding: 14px 32px;
56
+ display: flex; align-items: baseline; gap: 16px;
57
+ }
58
+ .wordmark {
59
+ font-family: "JetBrains Mono", monospace;
60
+ font-weight: 700;
61
+ font-size: 16px;
62
+ letter-spacing: 1px;
63
+ }
64
+ .wordmark .caret { color: var(--green); margin-right: 4px; }
65
+ .wordmark__sub {
66
+ font-family: "JetBrains Mono", monospace;
67
+ font-size: 11px; font-weight: 500;
68
+ text-transform: uppercase; letter-spacing: 2px;
69
+ color: var(--ink-soft);
70
+ margin-left: 4px;
71
+ }
72
+ .page-header__spacer { flex: 1; }
73
+ .page-header__crumbs {
74
+ font-family: "JetBrains Mono", monospace;
75
+ font-size: 10px; letter-spacing: 1.4px;
76
+ text-transform: uppercase; color: var(--ink-faint);
77
+ }
78
+ .page-header__crumbs a { color: var(--ink-soft); text-decoration: none; }
79
+ .page-header__crumbs a:hover { color: var(--green); }
80
+
81
+ /* --- Lede ----------------------------------------------------------- */
82
+ .tab-lede {
83
+ max-width: 1200px; margin: 56px auto 0;
84
+ padding: 0 32px;
85
+ }
86
+ .tab-lede__rail {
87
+ border-left: 3px solid var(--green);
88
+ padding: 4px 0 4px 22px;
89
+ max-width: 820px;
90
+ }
91
+ .tab-lede__eyebrow {
92
+ display: block;
93
+ font-family: "JetBrains Mono", monospace;
94
+ font-size: 11px; font-weight: 500;
95
+ letter-spacing: 0.22em; text-transform: uppercase;
96
+ color: var(--green); margin-bottom: 12px;
97
+ }
98
+ .tab-lede__title {
99
+ margin: 0 0 22px;
100
+ font-family: "JetBrains Mono", monospace;
101
+ font-size: 34px; font-weight: 500;
102
+ letter-spacing: -0.01em; line-height: 1.12;
103
+ color: var(--ink);
104
+ }
105
+ .tab-lede__lead {
106
+ margin: 0; max-width: 760px;
107
+ font-family: "Inter", sans-serif;
108
+ font-size: 19px; font-weight: 300; line-height: 1.5;
109
+ letter-spacing: -0.005em; color: #2d2d2a;
110
+ }
111
+
112
+ /* --- Post body ------------------------------------------------------ */
113
+ .post {
114
+ max-width: 760px;
115
+ margin: 32px auto 0;
116
+ padding: 0 32px 96px;
117
+ }
118
+ .post h2 {
119
+ margin: 64px 0 18px;
120
+ font-family: "JetBrains Mono", monospace;
121
+ font-size: 22px; font-weight: 500;
122
+ letter-spacing: -0.005em; line-height: 1.3;
123
+ color: var(--ink);
124
+ padding-top: 12px;
125
+ border-top: 1px solid var(--rule);
126
+ }
127
+ .post h2:first-child { border-top: none; padding-top: 0; }
128
+ .post h3 {
129
+ margin: 40px 0 12px;
130
+ font-family: "JetBrains Mono", monospace;
131
+ font-size: 15px; font-weight: 500;
132
+ letter-spacing: 0;
133
+ color: var(--ink);
134
+ text-transform: uppercase;
135
+ letter-spacing: 0.5px;
136
+ }
137
+ .post p { margin: 0 0 14px; }
138
+ .post p > code, .post li > code {
139
+ font-family: "JetBrains Mono", monospace;
140
+ font-size: 0.86em;
141
+ background: var(--soft-cream);
142
+ border: 1px solid var(--rule);
143
+ padding: 1px 5px;
144
+ border-radius: 2px;
145
+ color: var(--green-dark);
146
+ }
147
+ .post pre {
148
+ margin: 14px 0 18px;
149
+ padding: 14px 16px;
150
+ background: var(--card);
151
+ border: 1px solid #ddd;
152
+ overflow-x: auto;
153
+ }
154
+ .post pre code {
155
+ font-family: "JetBrains Mono", monospace;
156
+ font-size: 12px; line-height: 1.6;
157
+ color: var(--ink);
158
+ background: transparent; border: none; padding: 0;
159
+ }
160
+ .post a {
161
+ color: var(--green-dark);
162
+ text-decoration: underline;
163
+ text-decoration-thickness: 1px;
164
+ text-underline-offset: 2px;
165
+ }
166
+ .post a:hover { color: var(--green); }
167
+ .post strong { font-weight: 600; color: var(--ink); }
168
+ .post-image {
169
+ margin: 24px 0;
170
+ }
171
+ .post-image img {
172
+ display: block;
173
+ width: 100%; height: auto;
174
+ border: 1px solid var(--rule);
175
+ }
176
+
177
+ /* --- Widget shell (carbon-demo style) ------------------------------- */
178
+ .widget {
179
+ max-width: 920px;
180
+ margin: 40px auto 8px;
181
+ }
182
+ .widget__head {
183
+ display: flex; align-items: baseline; gap: 14px;
184
+ margin-bottom: 6px;
185
+ }
186
+ .widget__eyebrow {
187
+ font-family: "JetBrains Mono", monospace;
188
+ font-size: 10px; font-weight: 500;
189
+ letter-spacing: 0.22em; text-transform: uppercase;
190
+ color: var(--green);
191
+ }
192
+ .widget__title {
193
+ font-family: "JetBrains Mono", monospace;
194
+ font-size: 14px; font-weight: 500;
195
+ color: var(--ink); letter-spacing: 0;
196
+ }
197
+ .widget__caption {
198
+ font-family: "Inter", sans-serif;
199
+ font-size: 12px; color: var(--ink-soft);
200
+ margin: 8px 4px 14px;
201
+ line-height: 1.5;
202
+ }
203
+ .demo {
204
+ background: var(--card); border: 1px solid #ddd;
205
+ padding: 22px; margin: 8px 0;
206
+ }
207
+ .demo-toolbar {
208
+ display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
209
+ margin-bottom: 14px;
210
+ font-family: "JetBrains Mono", monospace; font-size: 10px;
211
+ color: #666; text-transform: uppercase; letter-spacing: 1.4px;
212
+ }
213
+ .demo-toolbar .spacer { flex: 1; }
214
+ .demo-label {
215
+ font-family: "JetBrains Mono", monospace;
216
+ font-size: 9.5px; color: #6b7a6e;
217
+ text-transform: uppercase; letter-spacing: 1.6px;
218
+ margin: 6px 0 6px;
219
+ }
220
+
221
+ .pill, button.action {
222
+ font-family: "JetBrains Mono", monospace;
223
+ font-size: 11px; font-weight: 400;
224
+ padding: 5px 11px; border: 1px solid #ccc; border-radius: 3px;
225
+ background: #fff; color: #555; cursor: pointer;
226
+ text-transform: uppercase; letter-spacing: 1.5px;
227
+ transition: all 0.12s;
228
+ }
229
+ .pill:hover, button.action:hover { border-color: #888; color: var(--ink); }
230
+ .pill.active, button.action.primary { background: var(--ink); color: #fff; border-color: var(--ink); }
231
+ .pill.active:hover, button.action.primary:hover { background: #000; }
232
+ .pill.gh { background: var(--green); color: #fff; border-color: var(--green); }
233
+ .pill.gh:hover { background: var(--green-dark); border-color: var(--green-dark); }
234
+ .pills { display: inline-flex; flex-wrap: wrap; gap: 6px; }
235
+ .status {
236
+ font-family: "JetBrains Mono", monospace;
237
+ font-size: 10px; color: #666;
238
+ text-transform: uppercase; letter-spacing: 1.5px;
239
+ display: inline-flex; align-items: center; gap: 6px;
240
+ margin-left: 8px;
241
+ }
242
+ .status .dot {
243
+ display: inline-block; width: 6px; height: 6px; border-radius: 50%;
244
+ background: var(--green);
245
+ }
246
+
247
+ input.seq-input, textarea.seq-input {
248
+ width: 100%;
249
+ font-family: "JetBrains Mono", monospace;
250
+ font-size: 13px;
251
+ padding: 9px 12px;
252
+ border: 1px solid #ccc;
253
+ border-radius: 3px;
254
+ letter-spacing: 1px;
255
+ text-transform: uppercase;
256
+ color: var(--ink);
257
+ background: var(--soft-cream);
258
+ resize: vertical;
259
+ }
260
+ textarea.seq-input { letter-spacing: 0.5px; }
261
+ input.seq-input:focus, textarea.seq-input:focus {
262
+ outline: none;
263
+ border-color: var(--green);
264
+ background: #fff;
265
+ }
266
+
267
+ /* base + token chips */
268
+ .base, .tok {
269
+ font-family: "JetBrains Mono", monospace;
270
+ font-size: 11px;
271
+ letter-spacing: 0.5px;
272
+ }
273
+ .base {
274
+ display: inline-block;
275
+ padding: 2px 5px;
276
+ font-weight: 500;
277
+ margin: 1px;
278
+ color: #fff;
279
+ border-radius: 2px;
280
+ min-width: 16px;
281
+ text-align: center;
282
+ }
283
+ .base.A { background: var(--base-a); }
284
+ .base.T { background: var(--base-t); }
285
+ .base.C { background: var(--base-c); }
286
+ .base.G { background: var(--base-g); }
287
+ .base.dim { opacity: 0.4; }
288
+ .base.outline {
289
+ background: transparent;
290
+ border: 1px solid currentColor;
291
+ }
292
+ .base.outline.A { color: var(--base-a); }
293
+ .base.outline.T { color: var(--base-t); }
294
+ .base.outline.C { color: var(--base-c); }
295
+ .base.outline.G { color: var(--base-g); }
296
+
297
+ .tok {
298
+ display: inline-flex;
299
+ align-items: center;
300
+ padding: 4px 8px;
301
+ margin: 2px;
302
+ border: 1px solid #ccc;
303
+ border-radius: 3px;
304
+ background: #fff;
305
+ color: var(--ink);
306
+ }
307
+ .tok.kmer {
308
+ background: rgba(49,127,63,0.10);
309
+ border-color: rgba(49,127,63,0.5);
310
+ color: var(--green-dark);
311
+ font-weight: 500;
312
+ }
313
+ .tok.bpe {
314
+ background: rgba(184,134,44,0.10);
315
+ border-color: rgba(184,134,44,0.5);
316
+ color: var(--amber-dark);
317
+ }
318
+ .tok.text {
319
+ background: #fff;
320
+ border-color: #ccc;
321
+ color: var(--ink);
322
+ }
323
+ .tok.boundary {
324
+ background: var(--ink);
325
+ color: #fff;
326
+ border-color: var(--ink);
327
+ text-transform: uppercase;
328
+ letter-spacing: 1px;
329
+ font-size: 10px;
330
+ }
331
+ .tok.dim { color: #888; background: var(--soft-cream); }
332
+ .tok.ok { background: var(--green); color: #fff; border-color: var(--green); }
333
+ .tok.bad { color: var(--red); border-color: rgba(176,0,32,0.4); background: rgba(176,0,32,0.05); }
334
+ .tok.selected {
335
+ outline: 2px solid var(--green);
336
+ outline-offset: 1px;
337
+ }
338
+ .token-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0; }
339
+
340
+ /* count badges */
341
+ .count-badge {
342
+ display: inline-block;
343
+ font-family: "JetBrains Mono", monospace;
344
+ font-size: 10px; font-weight: 600;
345
+ background: var(--ink); color: var(--bg);
346
+ padding: 2px 7px; border-radius: 2px;
347
+ letter-spacing: 1px;
348
+ }
349
+ .count-badge.green { background: var(--green); color: #fff; }
350
+ .count-badge.amber { background: var(--amber); color: #fff; }
351
+
352
+ /* W1 · animated tokenization (source on top, schemes drop down) ------ */
353
+ .tk-anim { margin-top: 18px; }
354
+ .tk-source, .tk-scheme {
355
+ display: grid;
356
+ grid-template-columns: 124px 1fr;
357
+ gap: 18px;
358
+ align-items: start;
359
+ padding: 8px 0;
360
+ }
361
+ .tk-scheme { padding: 10px 0; min-height: 32px; }
362
+ .tk-source { padding-bottom: 14px; border-bottom: 1px solid var(--rule); margin-bottom: 6px; }
363
+ .tk-meta {
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 3px;
367
+ padding-top: 2px;
368
+ }
369
+ .tk-label {
370
+ font-family: "JetBrains Mono", monospace;
371
+ font-size: 9.5px;
372
+ font-weight: 500;
373
+ color: var(--ink-soft);
374
+ text-transform: uppercase;
375
+ letter-spacing: 1.6px;
376
+ }
377
+ .tk-stat {
378
+ font-family: "JetBrains Mono", monospace;
379
+ font-size: 10px;
380
+ color: var(--ink-faint);
381
+ letter-spacing: 0.5px;
382
+ font-variant-numeric: tabular-nums;
383
+ }
384
+ .tk-stat .n {
385
+ font-weight: 600;
386
+ color: var(--ink);
387
+ }
388
+ .tk-scheme[data-scheme="bpe"] .tk-stat .n { color: var(--amber-dark); }
389
+ .tk-scheme[data-scheme="kmer"] .tk-stat .n { color: var(--green-dark); }
390
+
391
+ /* Source: tight grid of small chips */
392
+ .tk-source-strip {
393
+ display: flex;
394
+ flex-wrap: wrap;
395
+ row-gap: 2px;
396
+ }
397
+ .tk-source-strip .tk-base {
398
+ display: inline-flex;
399
+ align-items: center;
400
+ justify-content: center;
401
+ width: 13px;
402
+ height: 17px;
403
+ color: #fff;
404
+ font-family: "JetBrains Mono", monospace;
405
+ font-size: 10px;
406
+ font-weight: 500;
407
+ border-radius: 1px;
408
+ flex: 0 0 13px;
409
+ transition: opacity 0.35s ease, filter 0.35s ease;
410
+ }
411
+ .tk-source-strip .tk-base + .tk-base { margin-left: 1px; }
412
+ .tk-source-strip .tk-base.consumed { opacity: 0.18; filter: grayscale(0.6); }
413
+ .tk-base.A { background: var(--base-a); }
414
+ .tk-base.T { background: var(--base-t); }
415
+ .tk-base.C { background: var(--base-c); }
416
+ .tk-base.G { background: var(--base-g); }
417
+
418
+ /* Scheme strips: hold dropped tokens with <> brackets */
419
+ .tk-scheme-strip {
420
+ display: flex;
421
+ flex-wrap: wrap;
422
+ gap: 1px 8px;
423
+ min-height: 20px;
424
+ }
425
+ .tk-token {
426
+ font-family: "JetBrains Mono", monospace;
427
+ font-size: 12px;
428
+ font-weight: 500;
429
+ letter-spacing: 0.5px;
430
+ display: inline-flex;
431
+ align-items: center;
432
+ opacity: 0;
433
+ will-change: transform, opacity;
434
+ }
435
+ .tk-token.dropped {
436
+ opacity: 1;
437
+ transition:
438
+ transform 0.48s cubic-bezier(0.22, 1, 0.36, 1),
439
+ opacity 0.32s ease;
440
+ }
441
+ .tk-token .br { color: var(--ink-faint); font-weight: 400; }
442
+ .tk-token.tail .br { color: var(--ink-faint); }
443
+ .tk-token.tail { opacity: 0.55; }
444
+ .tk-token.tail.dropped { opacity: 0.55; }
445
+
446
+
447
+ /* Layouts */
448
+ .grid-3 {
449
+ display: grid;
450
+ grid-template-columns: 1fr 1fr 1fr;
451
+ gap: 14px;
452
+ margin-top: 14px;
453
+ }
454
+ .grid-2 {
455
+ display: grid;
456
+ grid-template-columns: 1fr 1fr;
457
+ gap: 14px;
458
+ margin-top: 14px;
459
+ }
460
+ @media (max-width: 720px) {
461
+ .grid-3, .grid-2 { grid-template-columns: 1fr; }
462
+ }
463
+ .col-card {
464
+ background: var(--soft-cream);
465
+ border: 1px solid var(--rule);
466
+ padding: 14px;
467
+ }
468
+ .col-card .col-title {
469
+ font-family: "JetBrains Mono", monospace;
470
+ font-size: 10px; font-weight: 500;
471
+ letter-spacing: 1.6px; text-transform: uppercase;
472
+ color: var(--ink-soft);
473
+ margin-bottom: 8px;
474
+ }
475
+ .col-card .col-foot {
476
+ margin-top: 10px;
477
+ display: flex; align-items: center; gap: 8px;
478
+ font-family: "JetBrains Mono", monospace;
479
+ font-size: 10px; color: var(--ink-soft);
480
+ text-transform: uppercase; letter-spacing: 1.4px;
481
+ }
482
+
483
+ /* Bar chart helpers */
484
+ .bar-row {
485
+ display: grid;
486
+ grid-template-columns: 18px 1fr 64px;
487
+ align-items: center;
488
+ gap: 8px;
489
+ margin: 3px 0;
490
+ font-family: "JetBrains Mono", monospace;
491
+ font-size: 11px;
492
+ }
493
+ .bar-row .bar-label { color: var(--ink-soft); font-weight: 500; text-align: center; }
494
+ .bar-row .bar-track {
495
+ position: relative;
496
+ height: 14px;
497
+ background: var(--soft-cream);
498
+ border: 1px solid var(--rule);
499
+ }
500
+ .bar-row .bar-fill {
501
+ height: 100%;
502
+ transition: width 220ms ease, background-color 220ms ease;
503
+ }
504
+ .bar-row .bar-val { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }
505
+ .bar-row.highlight .bar-track { box-shadow: 0 0 0 2px var(--green-tint); }
506
+
507
+ /* 6-position grid (used in W4, W5, W6) */
508
+ .posgrid {
509
+ display: grid;
510
+ grid-template-columns: repeat(6, 1fr);
511
+ gap: 8px;
512
+ }
513
+ .posgrid__col {
514
+ background: var(--soft-cream);
515
+ border: 1px solid var(--rule);
516
+ border-radius: 2px;
517
+ padding: 8px 6px 6px;
518
+ }
519
+ .posgrid__pos {
520
+ font-family: "JetBrains Mono", monospace;
521
+ font-size: 9px; color: var(--ink-faint);
522
+ text-transform: uppercase; letter-spacing: 1.4px;
523
+ text-align: center; margin-bottom: 6px;
524
+ }
525
+ .posgrid__bars {
526
+ display: flex; flex-direction: column; gap: 3px;
527
+ }
528
+ .posgrid .mini-row {
529
+ display: grid; grid-template-columns: 12px 1fr 32px;
530
+ align-items: center; gap: 4px;
531
+ font-family: "JetBrains Mono", monospace; font-size: 10px;
532
+ }
533
+ .posgrid .mini-row .ml { color: var(--ink-soft); text-align: center; font-weight: 500; }
534
+ .posgrid .mini-row .mt { position: relative; height: 10px; background: #fff; border: 1px solid var(--rule); }
535
+ .posgrid .mini-row .mf { display: block; height: 100%; transition: width 200ms ease; }
536
+ .posgrid .mini-row .mv { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; font-size: 9.5px; }
537
+ .posgrid__col.argmax-A { box-shadow: inset 0 0 0 1px var(--base-a); }
538
+ .posgrid__col.argmax-T { box-shadow: inset 0 0 0 1px var(--base-t); }
539
+ .posgrid__col.argmax-C { box-shadow: inset 0 0 0 1px var(--base-c); }
540
+ .posgrid__col.argmax-G { box-shadow: inset 0 0 0 1px var(--base-g); }
541
+ .posgrid__col.locked { background: #fff; }
542
+ .posgrid__col.frozen .posgrid__bars { opacity: 0.4; }
543
+ .posgrid__sel { display: flex; gap: 3px; margin-top: 8px; }
544
+ .cond-pick {
545
+ flex: 1 1 0; min-width: 0;
546
+ font-family: "JetBrains Mono", monospace; font-size: 10px; font-weight: 600;
547
+ padding: 3px 0; border: 1px solid var(--rule); border-radius: 2px;
548
+ background: #fff; cursor: pointer;
549
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
550
+ }
551
+ .cond-pick:hover { border-color: #888; }
552
+ .cond-pick.active { color: #fff; }
553
+ .cond-pick.active.b-A { background: var(--base-a); border-color: var(--base-a); }
554
+ .cond-pick.active.b-T { background: var(--base-t); border-color: var(--base-t); }
555
+ .cond-pick.active.b-C { background: var(--base-c); border-color: var(--base-c); }
556
+ .cond-pick.active.b-G { background: var(--base-g); border-color: var(--base-g); }
557
+
558
+ /* W3 · real Carbon-500M per-token scoring --------------------------- */
559
+ .w3-itok { cursor: pointer; transition: outline-color 0.12s; }
560
+ .w3-itok:hover { border-color: #888; }
561
+ .w3-sub {
562
+ font-family: "JetBrains Mono", monospace;
563
+ font-size: 11px; color: var(--ink-soft);
564
+ margin: 2px 0 10px; line-height: 1.7;
565
+ }
566
+ .w3-sub b { color: var(--ink); font-weight: 600; }
567
+ .w3-sub .tok { margin: 0 2px; }
568
+ .w3-toprow {
569
+ display: grid;
570
+ grid-template-columns: 92px 1fr 52px;
571
+ gap: 12px; align-items: center;
572
+ margin: 4px 0;
573
+ font-family: "JetBrains Mono", monospace; font-size: 11px;
574
+ }
575
+ .w3-toprow .km { font-weight: 500; letter-spacing: 0.5px; }
576
+ .w3-toprow .bar { height: 13px; background: var(--soft-cream); border: 1px solid var(--rule); }
577
+ .w3-toprow .fill { height: 100%; background: var(--green); transition: width 0.35s ease; }
578
+ .w3-toprow .pct { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); }
579
+ .w3-toprow.observed .km { color: var(--ink); }
580
+ .w3-toprow.observed .fill { background: var(--amber); }
581
+ .w3-io { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; margin-top: 6px; }
582
+ .w3-io__group { display: flex; flex-direction: column; gap: 6px; }
583
+ .w3-io__lab {
584
+ font-family: "JetBrains Mono", monospace;
585
+ font-size: 9.5px; letter-spacing: 1.6px; text-transform: uppercase; color: var(--ink-faint);
586
+ }
587
+ .w3-io__arrow { font-family: "JetBrains Mono", monospace; font-size: 16px; color: var(--ink-faint); padding-bottom: 5px; }
588
+ .w3-gt-tok { background: rgba(184,134,44,0.12); border-color: rgba(184,134,44,0.5) !important; }
589
+ .w3-chart { display: block; width: 100%; height: auto; margin-top: 6px; background: #fff; border: 1px solid #eee; }
590
+ .w3-legend {
591
+ display: flex; gap: 20px; flex-wrap: wrap; margin-top: 8px;
592
+ font-family: "JetBrains Mono", monospace; font-size: 10px;
593
+ color: var(--ink-soft); text-transform: uppercase; letter-spacing: 1px;
594
+ }
595
+ .w3-legend span { display: inline-flex; align-items: center; gap: 6px; }
596
+ .w3-legend i { width: 12px; height: 3px; display: inline-block; }
597
+ .w3-top { display: flex; gap: 6px; margin-top: 12px; }
598
+ .w3-topchip {
599
+ flex: 1 1 0; min-width: 0;
600
+ display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
601
+ font-family: "JetBrains Mono", monospace;
602
+ border: 1px solid var(--rule); background: var(--soft-cream);
603
+ border-radius: 3px; padding: 6px 4px;
604
+ }
605
+ .w3-topchip .km { font-size: 12px; font-weight: 500; letter-spacing: 0.5px; }
606
+ .w3-topchip .pct { font-size: 9px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
607
+ .posgrid__col.clickable { cursor: pointer; }
608
+ .posgrid__col.sel { outline: 2px solid var(--ink); outline-offset: 2px; }
609
+ .w3-allgrid {
610
+ display: grid;
611
+ grid-template-columns: repeat(128, 1fr);
612
+ gap: 1px;
613
+ margin-top: 8px;
614
+ }
615
+ .w3-cell { aspect-ratio: 1; background: #d3d0c4; transition: background 0.15s ease; }
616
+ .w3-cell.first {
617
+ outline: 2px solid var(--ink);
618
+ outline-offset: 1px;
619
+ position: relative; z-index: 2;
620
+ }
621
+ .w3-order {
622
+ display: inline-flex; align-items: center; gap: 8px;
623
+ font-family: "JetBrains Mono", monospace; font-size: 9.5px;
624
+ text-transform: uppercase; letter-spacing: 1.4px; color: var(--ink-faint);
625
+ }
626
+ .w3-snake { width: 42px; height: auto; color: var(--ink-soft); display: block; }
627
+
628
+ /* WCOND · conditional single-nucleotide decoding -------------------- */
629
+ .cond-slots { display: flex; gap: 6px; }
630
+ .cond-slot {
631
+ width: 40px; height: 40px;
632
+ display: flex; align-items: center; justify-content: center;
633
+ border: 1px solid var(--rule); border-radius: 3px; background: var(--soft-cream);
634
+ font-family: "JetBrains Mono", monospace; font-size: 17px; font-weight: 600;
635
+ color: var(--ink);
636
+ }
637
+ .cond-slot.active { outline: 2px solid var(--ink); outline-offset: 1px; color: var(--ink-faint); }
638
+ .cond-slot.pending { color: var(--ink-faint); background: transparent; border-style: dashed; }
639
+ .cond-slot.A { background: rgba(26,122,64,0.13); border-color: rgba(26,122,64,0.42); color: var(--base-a); }
640
+ .cond-slot.T { background: rgba(176,0,32,0.09); border-color: rgba(176,0,32,0.36); color: var(--base-t); }
641
+ .cond-slot.C { background: rgba(44,90,160,0.11); border-color: rgba(44,90,160,0.40); color: var(--base-c); }
642
+ .cond-slot.G { background: rgba(184,134,44,0.14); border-color: rgba(184,134,44,0.42); color: var(--amber-dark); }
643
+ .cond-choices { display: flex; gap: 10px; }
644
+ .cond-choice {
645
+ flex: 1 1 0; cursor: pointer; border: 1px solid var(--rule); border-radius: 3px;
646
+ padding: 10px 12px; background: var(--soft-cream);
647
+ display: flex; flex-direction: column; gap: 7px;
648
+ transition: background 0.12s, border-color 0.12s;
649
+ }
650
+ .cond-choice:hover { background: #fff; border-color: #888; }
651
+ .cond-choice__top { display: flex; justify-content: space-between; align-items: baseline; font-family: "JetBrains Mono", monospace; }
652
+ .cond-choice__base { font-size: 16px; font-weight: 600; }
653
+ .cond-choice__pct { font-size: 11px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
654
+ .cond-choice__bar { height: 8px; background: #fff; border: 1px solid var(--rule); }
655
+ .cond-choice__fill { display: block; height: 100%; transition: width 0.25s ease; }
656
+ .cond-done { font-family: "JetBrains Mono", monospace; font-size: 13px; color: var(--ink); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
657
+ .cond-note {
658
+ font-family: "Inter", sans-serif; font-size: 12px; color: var(--ink-soft);
659
+ background: var(--soft-cream); border-left: 2px solid var(--green);
660
+ padding: 9px 12px; margin-top: 14px; line-height: 1.5;
661
+ }
662
+ .cond-note .tok { margin: 0 2px; }
663
+
664
+ /* FNS factorization figure ------------------------------------------ */
665
+ .fns-seq-input {
666
+ width: 120px;
667
+ text-align: center;
668
+ text-transform: uppercase;
669
+ }
670
+ .fns-viz {
671
+ font-family: "JetBrains Mono", monospace;
672
+ padding: 10px 0 6px;
673
+ }
674
+ .fns-truth, .fns-row {
675
+ display: grid;
676
+ grid-template-columns: 222px repeat(6, 34px) 28px;
677
+ align-items: center;
678
+ column-gap: 7px;
679
+ min-width: 480px;
680
+ }
681
+ .fns-truth { margin-bottom: 16px; }
682
+ .fns-row { margin: 12px 0; }
683
+ .fns-gtlabel {
684
+ font-family: "JetBrains Mono", monospace;
685
+ font-size: 10px;
686
+ font-weight: 400;
687
+ letter-spacing: 1.6px;
688
+ text-transform: uppercase;
689
+ color: var(--ink-faint);
690
+ }
691
+ .fns-eq {
692
+ font-size: 16px;
693
+ color: var(--ink);
694
+ text-align: right;
695
+ white-space: nowrap;
696
+ }
697
+ .fns-eq sub, .fns-eq sup { font-size: 11px; }
698
+ .fns-eq .sum { color: var(--ink-soft); }
699
+ .fns-bracket {
700
+ font-family: "Inter", sans-serif;
701
+ font-size: 34px;
702
+ font-weight: 200;
703
+ line-height: 0;
704
+ color: var(--ink-soft);
705
+ vertical-align: middle;
706
+ transform: scaleX(0.7);
707
+ display: inline-block;
708
+ }
709
+ .fns-box {
710
+ width: 30px; height: 30px;
711
+ display: inline-flex; align-items: center; justify-content: center;
712
+ font-family: "JetBrains Mono", monospace;
713
+ font-size: 14px; font-weight: 600;
714
+ border-radius: 3px; border: 1px solid;
715
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
716
+ }
717
+ .fns-box.A { background: rgba(26,122,64,0.13); border-color: rgba(26,122,64,0.42); color: var(--base-a); }
718
+ .fns-box.T { background: rgba(176,0,32,0.09); border-color: rgba(176,0,32,0.36); color: var(--base-t); }
719
+ .fns-box.C { background: rgba(44,90,160,0.11); border-color: rgba(44,90,160,0.40); color: var(--base-c); }
720
+ .fns-box.G { background: rgba(184,134,44,0.14); border-color: rgba(184,134,44,0.42); color: var(--amber-dark); }
721
+ .fns-box.wild { opacity: 0.38; }
722
+ .fns-loss {
723
+ margin-top: 20px;
724
+ padding-top: 16px;
725
+ border-top: 1px solid var(--rule);
726
+ font-family: "JetBrains Mono", monospace;
727
+ font-size: 16px;
728
+ color: var(--ink);
729
+ text-align: center;
730
+ }
731
+ .fns-loss sup, .fns-loss sub { font-size: 11px; }
732
+ .fns-loss .frac { display: inline-block; margin: 0 1px; }
733
+ .fns-loss .fns-term { margin: 0 3px; white-space: nowrap; }
734
+ .fns-explain {
735
+ margin-top: 18px;
736
+ padding-top: 16px;
737
+ border-top: 1px solid var(--rule);
738
+ font-family: "Inter", sans-serif;
739
+ font-size: 13px;
740
+ line-height: 1.62;
741
+ color: var(--ink-soft);
742
+ }
743
+ .fns-explain p { margin: 0 0 11px; }
744
+ .fns-explain p:last-child { margin-bottom: 0; }
745
+ .fns-explain strong { color: var(--ink); font-weight: 600; }
746
+ .fns-explain .mono {
747
+ font-family: "JetBrains Mono", monospace;
748
+ font-size: 12px;
749
+ color: var(--ink);
750
+ }
751
+ .fns-explain .mono sup, .fns-explain .mono sub { font-size: 9px; }
752
+
753
+ /* Big stat tile */
754
+ .stat-tile {
755
+ background: var(--soft-cream);
756
+ border: 1px solid var(--rule);
757
+ padding: 14px 16px;
758
+ text-align: left;
759
+ }
760
+ .stat-tile__label {
761
+ font-family: "JetBrains Mono", monospace;
762
+ font-size: 10px; color: var(--ink-soft);
763
+ text-transform: uppercase; letter-spacing: 1.6px;
764
+ margin-bottom: 6px;
765
+ }
766
+ .stat-tile__value {
767
+ font-family: "JetBrains Mono", monospace;
768
+ font-size: 26px; font-weight: 500;
769
+ color: var(--ink);
770
+ }
771
+ .stat-tile__sub {
772
+ font-family: "JetBrains Mono", monospace;
773
+ font-size: 10px; color: var(--ink-soft);
774
+ margin-top: 4px; letter-spacing: 0.5px;
775
+ }
776
+ .stat-tile.green .stat-tile__value { color: var(--green-dark); }
777
+ .stat-tile.red .stat-tile__value { color: var(--red); }
778
+
779
+ /* Footer */
780
+ .post-foot {
781
+ max-width: 760px;
782
+ margin: 64px auto 0;
783
+ padding: 24px 32px 64px;
784
+ border-top: 1px solid var(--rule);
785
+ font-family: "JetBrains Mono", monospace;
786
+ font-size: 11px; color: var(--ink-soft);
787
+ text-transform: uppercase; letter-spacing: 1.4px;
788
+ }
789
+ .post-foot a { color: var(--green-dark); text-decoration: none; }
790
+ .post-foot a:hover { color: var(--green); }
791
+
792
+ /* Position picker (W7) */
793
+ .seq-track {
794
+ font-family: "JetBrains Mono", monospace;
795
+ font-size: 14px;
796
+ letter-spacing: 0;
797
+ background: var(--soft-cream);
798
+ border: 1px solid var(--rule);
799
+ padding: 10px 12px;
800
+ display: flex; flex-wrap: wrap; gap: 1px;
801
+ user-select: none;
802
+ }
803
+ .seq-track__b {
804
+ display: inline-flex; align-items: center; justify-content: center;
805
+ width: 22px; height: 26px;
806
+ cursor: pointer;
807
+ border-radius: 2px;
808
+ color: #fff;
809
+ font-weight: 500;
810
+ transition: transform 0.1s ease, box-shadow 0.1s ease;
811
+ }
812
+ .seq-track__b:hover { transform: translateY(-1px); }
813
+ .seq-track__b.A { background: var(--base-a); }
814
+ .seq-track__b.T { background: var(--base-t); }
815
+ .seq-track__b.C { background: var(--base-c); }
816
+ .seq-track__b.G { background: var(--base-g); }
817
+ .seq-track__b.selected {
818
+ box-shadow: 0 0 0 2px var(--ink), 0 0 0 4px var(--bg);
819
+ }
820
+
821
+ /* Per-base text color (used wherever DNA letters appear in prose) */
822
+ .b-A, .b-T, .b-C, .b-G { font-weight: 500; }
823
+ .b-A { color: var(--base-a); }
824
+ .b-T { color: var(--base-t); }
825
+ .b-C { color: var(--base-c); }
826
+ .b-G { color: var(--base-g); }
827
+
828
+ /* k-mer token: colored bases inside <>, no chrome */
829
+ .kmer-token, .raw-seq {
830
+ font-family: "JetBrains Mono", monospace;
831
+ font-size: 12px;
832
+ font-weight: 500;
833
+ letter-spacing: 0.5px;
834
+ }
835
+ .kmer-token {
836
+ display: inline-flex;
837
+ align-items: center;
838
+ padding: 2px 0;
839
+ white-space: nowrap;
840
+ }
841
+ .kmer-token .br { color: var(--ink-faint); font-weight: 400; }
842
+ .kmer-token.muted { opacity: 0.45; }
843
+ .raw-seq { display: inline-flex; padding: 2px 0; }
844
+
845
+ .bpe-flow {
846
+ display: flex; align-items: center; flex-wrap: wrap;
847
+ gap: 18px;
848
+ margin: 10px 0 8px;
849
+ }
850
+ .bpe-flow .arrow {
851
+ font-family: "JetBrains Mono", monospace;
852
+ font-size: 16px;
853
+ color: var(--ink-faint);
854
+ }
855
+
856
+ /* Line 2: source token, SVG fan-out, candidate rows ----------------- */
857
+ .bpe-branches {
858
+ display: grid;
859
+ grid-template-columns: auto 96px 1fr;
860
+ grid-template-rows: auto 150px;
861
+ gap: 0;
862
+ margin-top: 12px;
863
+ }
864
+ .branch-source {
865
+ grid-column: 1; grid-row: 2;
866
+ align-self: center;
867
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
868
+ padding-right: 8px;
869
+ }
870
+ .branch-source__hint {
871
+ font-family: "JetBrains Mono", monospace;
872
+ font-size: 9px; color: var(--ink-faint);
873
+ text-transform: uppercase; letter-spacing: 1.6px;
874
+ }
875
+ .branch-lines {
876
+ grid-column: 2; grid-row: 2;
877
+ width: 96px; height: 150px;
878
+ display: block;
879
+ }
880
+ .branch-lines path {
881
+ fill: none;
882
+ stroke: #ccc;
883
+ stroke-width: 1.4;
884
+ transition: stroke 0.15s, stroke-width 0.15s;
885
+ }
886
+ .branch-lines path.selected { stroke: var(--green); stroke-width: 2.2; }
887
+ .branch-header {
888
+ grid-column: 3; grid-row: 1;
889
+ display: grid;
890
+ grid-template-columns: 100px 60px 60px;
891
+ gap: 18px;
892
+ padding: 0 12px 10px;
893
+ font-family: "JetBrains Mono", monospace;
894
+ font-size: 9.5px;
895
+ font-weight: 500;
896
+ letter-spacing: 1.6px;
897
+ text-transform: uppercase;
898
+ color: var(--ink-soft);
899
+ }
900
+ .branch-header > span { text-align: center; }
901
+ .branch-header > span:first-child { text-align: left; color: var(--ink-faint); }
902
+ .branch-targets {
903
+ grid-column: 3; grid-row: 2;
904
+ display: grid;
905
+ grid-template-rows: repeat(5, 26px);
906
+ row-gap: 5px;
907
+ align-content: center;
908
+ justify-content: start;
909
+ height: 150px;
910
+ }
911
+ .cand-row {
912
+ display: grid;
913
+ grid-template-columns: 100px 60px 60px;
914
+ gap: 18px;
915
+ align-items: center;
916
+ padding: 2px 12px;
917
+ cursor: pointer;
918
+ border-radius: 2px;
919
+ transition: background 0.12s;
920
+ }
921
+ .cand-row:hover { background: rgba(0,0,0,0.03); }
922
+ .cand-row.selected { background: var(--green-tint); }
923
+ .mark {
924
+ font-family: "JetBrains Mono", monospace;
925
+ font-size: 14px;
926
+ font-weight: 500;
927
+ text-align: center;
928
+ line-height: 1;
929
+ user-select: none;
930
+ }
931
+ .mark.ok { color: var(--green); }
932
+ .mark.bad { color: var(--red); }
933
+
934
+ /* explanation note */
935
+ .note {
936
+ font-family: "Inter", sans-serif;
937
+ font-size: 12px; line-height: 1.55;
938
+ color: var(--ink-soft);
939
+ background: var(--soft-cream);
940
+ border-left: 2px solid var(--green);
941
+ padding: 10px 12px;
942
+ margin-top: 14px;
943
+ }
944
+ .note code {
945
+ font-family: "JetBrains Mono", monospace;
946
+ font-size: 11px;
947
+ background: #fff;
948
+ border: 1px solid var(--rule);
949
+ padding: 0 4px; border-radius: 2px;
950
+ color: var(--green-dark);
951
+ }
952
+ .formula {
953
+ font-family: "JetBrains Mono", monospace;
954
+ font-size: 12px;
955
+ background: var(--soft-cream);
956
+ border: 1px solid var(--rule);
957
+ padding: 10px 12px;
958
+ margin-top: 10px;
959
+ color: var(--ink);
960
+ overflow-x: auto;
961
+ }
962
+ .formula .hl { color: var(--green-dark); font-weight: 600; }
963
+
964
+
965
+
966
+ /* Iframe wrapper: tighten margins so the widget sits flush in an embed. */
967
+ body { padding: 20px 24px; background: var(--bg); }
968
+ .widget { max-width: 880px; margin: 0 auto; padding: 0; border: 0; }
969
+ </style>
970
+ </head>
971
+ <body>
972
+ <aside class="widget" id="w1">
973
+ <div class="widget__head">
974
+ <span class="widget__eyebrow">§ Widget · 02</span>
975
+ <span class="widget__title">Per-base, BPE, and 6-mer on the same DNA sequence</span>
976
+ </div>
977
+ <p class="widget__caption">
978
+ Press <strong>play</strong> to watch the same sequence get cut three different ways — per-base, with a toy BPE
979
+ vocabulary (greedy longest-match), and with non-overlapping 6-mers. The cuts appear left-to-right on the same
980
+ time axis, so divergence between the schemes is visible directly.
981
+ </p>
982
+ <div class="demo">
983
+ <div class="demo-toolbar">
984
+ <span>Sequence · <span id="w1-len">0 bp</span></span>
985
+ <span class="spacer"></span>
986
+ <button class="action primary" id="w1-play">▶ TOKENIZE</button>
987
+ </div>
988
+ <input class="seq-input" id="w1-seq" value="ACGTATCGTATAGGCTAACGGATCATGCTAACGGATCATGCTAGCTAATGCATGCATGCAATCGATCGGGCCTTAAGCTAGCTACGATCGTAGCAT">
989
+
990
+ <div class="tk-anim">
991
+ <div class="tk-source">
992
+ <div class="tk-meta">
993
+ <span class="tk-label">Sequence</span>
994
+ <span class="tk-stat"><span class="n" id="w1-bp">0</span> bp</span>
995
+ </div>
996
+ <div class="tk-source-strip" id="w1-source"></div>
997
+ </div>
998
+ <div class="tk-scheme" data-scheme="base">
999
+ <div class="tk-meta">
1000
+ <span class="tk-label">Per-base</span>
1001
+ <span class="tk-stat">Tokens: <span class="n" id="w1-base-count">0</span></span>
1002
+ <span class="tk-stat">Compression: <span class="n" id="w1-base-ratio">1.0</span></span>
1003
+ </div>
1004
+ <div class="tk-scheme-strip" id="w1-base"></div>
1005
+ </div>
1006
+ <div class="tk-scheme" data-scheme="bpe">
1007
+ <div class="tk-meta">
1008
+ <span class="tk-label">BPE</span>
1009
+ <span class="tk-stat">Tokens: <span class="n" id="w1-bpe-count">0</span></span>
1010
+ <span class="tk-stat">Compression: <span class="n" id="w1-bpe-ratio">−</span></span>
1011
+ </div>
1012
+ <div class="tk-scheme-strip" id="w1-bpe"></div>
1013
+ </div>
1014
+ <div class="tk-scheme" data-scheme="kmer">
1015
+ <div class="tk-meta">
1016
+ <span class="tk-label">6-mer</span>
1017
+ <span class="tk-stat">Tokens: <span class="n" id="w1-kmer-count">0</span></span>
1018
+ <span class="tk-stat">Compression: <span class="n" id="w1-kmer-ratio">−</span></span>
1019
+ </div>
1020
+ <div class="tk-scheme-strip" id="w1-kmer"></div>
1021
+ </div>
1022
+ </div>
1023
+ </div>
1024
+ </aside>
1025
+ <script>
1026
+
1027
+ // ============================================================
1028
+ // Shared utilities
1029
+ // ============================================================
1030
+ const BASES = ['A', 'T', 'C', 'G'];
1031
+ const BASE_IDX = {A: 0, T: 1, C: 2, G: 3};
1032
+ const BASE_COLOR = { A: 'var(--base-a)', T: 'var(--base-t)', C: 'var(--base-c)', G: 'var(--base-g)' };
1033
+
1034
+ // FNV-1a 32-bit
1035
+ function hash32(s) {
1036
+ let h = 0x811c9dc5;
1037
+ for (let i = 0; i < s.length; i++) {
1038
+ h ^= s.charCodeAt(i);
1039
+ h = Math.imul(h, 0x01000193);
1040
+ }
1041
+ return h >>> 0;
1042
+ }
1043
+ function rand01(seed) {
1044
+ const h = hash32(String(seed));
1045
+ return (h % 1000003) / 1000003;
1046
+ }
1047
+ function softmax(arr) {
1048
+ let m = -Infinity;
1049
+ for (let i = 0; i < arr.length; i++) if (arr[i] > m) m = arr[i];
1050
+ const out = new Float64Array(arr.length);
1051
+ let s = 0;
1052
+ for (let i = 0; i < arr.length; i++) {
1053
+ out[i] = Math.exp(arr[i] - m);
1054
+ s += out[i];
1055
+ }
1056
+ for (let i = 0; i < arr.length; i++) out[i] /= s;
1057
+ return out;
1058
+ }
1059
+ function cleanDNA(s) {
1060
+ return (s || '').toUpperCase().replace(/[^ACGT]/g, '');
1061
+ }
1062
+ function escapeHTML(s) {
1063
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1064
+ }
1065
+
1066
+ // 6-mer integer <-> string helpers
1067
+ function kmerToIdx(k) {
1068
+ let i = 0;
1069
+ for (let p = 0; p < k.length; p++) {
1070
+ const b = BASE_IDX[k[p]];
1071
+ if (b == null) return -1;
1072
+ i = i * 4 + b;
1073
+ }
1074
+ return i;
1075
+ }
1076
+ function idxToKmer(i, k = 6) {
1077
+ let s = '';
1078
+ for (let p = k - 1; p >= 0; p--) {
1079
+ s = BASES[(i >> (2 * p)) & 3] + s;
1080
+ }
1081
+ return s;
1082
+ }
1083
+ function baseAt(i, p) {
1084
+ // p in [0..5], 0 is leftmost
1085
+ return (i >> (2 * (5 - p))) & 3;
1086
+ }
1087
+
1088
+ // generate a 4096-d distribution conditioned on a string seed
1089
+ function distribution4096(seed) {
1090
+ const logits = new Float64Array(4096);
1091
+ for (let i = 0; i < 4096; i++) {
1092
+ const a = rand01(seed + ':a:' + i);
1093
+ const b = rand01(seed + ':b:' + (i * 7919));
1094
+ const c = rand01(seed + ':c:' + (i * 65537));
1095
+ // mix a few uniforms → roughly-normal logit centered near 0
1096
+ logits[i] = (a + b + c - 1.5) * 3.2;
1097
+ }
1098
+ return softmax(logits);
1099
+ }
1100
+ function marginals6x4(probs) {
1101
+ const m = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
1102
+ for (let i = 0; i < 4096; i++) {
1103
+ const p = probs[i];
1104
+ for (let pos = 0; pos < 6; pos++) {
1105
+ m[pos][baseAt(i, pos)] += p;
1106
+ }
1107
+ }
1108
+ return m;
1109
+ }
1110
+ function fmtPct(p) { return (p * 100).toFixed(2) + '%'; }
1111
+ function fmt(p, d=4) { return Number(p).toFixed(d); }
1112
+
1113
+ // Render a row of per-base bars (used for variants of W4/W6/W7)
1114
+ function renderPosCol(parent, posIndex, dist4, mode) {
1115
+ const max = Math.max(0.0001, ...dist4);
1116
+ let argmax = 0;
1117
+ for (let i = 1; i < 4; i++) if (dist4[i] > dist4[argmax]) argmax = i;
1118
+ const col = document.createElement('div');
1119
+ col.className = 'posgrid__col' + (mode === 'argmax' ? ' argmax-' + BASES[argmax] : '');
1120
+ col.innerHTML =
1121
+ `<div class="posgrid__pos">pos ${posIndex + 1}</div>` +
1122
+ `<div class="posgrid__bars">` +
1123
+ BASES.map((b, j) => {
1124
+ const w = (dist4[j] / max) * 100;
1125
+ const isMax = j === argmax;
1126
+ return `<div class="mini-row">
1127
+ <span class="ml">${b}</span>
1128
+ <span class="mt"><span class="mf" style="width:${w.toFixed(1)}%;background:${BASE_COLOR[b]};opacity:${isMax ? 1 : 0.55}"></span></span>
1129
+ <span class="mv">${(dist4[j] * 100).toFixed(1)}</span>
1130
+ </div>`;
1131
+ }).join('') +
1132
+ `</div>`;
1133
+ parent.appendChild(col);
1134
+ }
1135
+
1136
+ function basesHTML(seq) {
1137
+ return Array.from(seq).map((c) => `<span class="base ${c}">${c}</span>`).join('');
1138
+ }
1139
+
1140
+ (function setupW1() {
1141
+ // toy BPE vocab: longer entries first for greedy match
1142
+ const BPE_VOCAB = [
1143
+ 'ACGTA', 'TATAG', 'ATCGA', 'GATCA', 'TAGCT', 'AGCTA', 'CGATC', 'TGCTA',
1144
+ 'ATCG', 'GATC', 'TATA', 'AGCT', 'GCTA', 'CTAG', 'ACGT', 'TGCA',
1145
+ 'AT', 'CG', 'GC', 'TA', 'AA', 'TT', 'CC', 'GG', 'AC', 'GT', 'CT', 'GA',
1146
+ 'A', 'C', 'G', 'T',
1147
+ ];
1148
+ function bpeTokens(seq) {
1149
+ const out = [];
1150
+ let i = 0;
1151
+ while (i < seq.length) {
1152
+ let hit = null;
1153
+ for (const v of BPE_VOCAB) if (seq.startsWith(v, i)) { hit = v; break; }
1154
+ out.push(hit || seq[i]);
1155
+ i += (hit ? hit.length : 1);
1156
+ }
1157
+ return out;
1158
+ }
1159
+
1160
+ // tokensFor: returns [{ text, startPos, endPos, tail }]
1161
+ function tokensBase(seq) {
1162
+ return Array.from(seq).map((c, i) => ({ text: c, startPos: i, endPos: i + 1, tail: false }));
1163
+ }
1164
+ function tokensBPEArr(seq) {
1165
+ const out = []; let p = 0;
1166
+ for (const t of bpeTokens(seq)) {
1167
+ out.push({ text: t, startPos: p, endPos: p + t.length, tail: false });
1168
+ p += t.length;
1169
+ }
1170
+ return out;
1171
+ }
1172
+ function tokensKmer(seq, k = 6) {
1173
+ const out = []; let p = 0;
1174
+ while (p + k <= seq.length) {
1175
+ out.push({ text: seq.slice(p, p + k), startPos: p, endPos: p + k, tail: false });
1176
+ p += k;
1177
+ }
1178
+ if (p < seq.length) {
1179
+ out.push({ text: seq.slice(p), startPos: p, endPos: seq.length, tail: true });
1180
+ }
1181
+ return out;
1182
+ }
1183
+
1184
+ const SCHEMES = [
1185
+ { id: 'base', el: document.getElementById('w1-base'), tokens: tokensBase, countEl: document.getElementById('w1-base-count'), ratioEl: document.getElementById('w1-base-ratio') },
1186
+ { id: 'bpe', el: document.getElementById('w1-bpe'), tokens: tokensBPEArr, countEl: document.getElementById('w1-bpe-count'), ratioEl: document.getElementById('w1-bpe-ratio') },
1187
+ { id: 'kmer', el: document.getElementById('w1-kmer'), tokens: tokensKmer, countEl: document.getElementById('w1-kmer-count'), ratioEl: document.getElementById('w1-kmer-ratio') },
1188
+ ];
1189
+
1190
+ const seqInput = document.getElementById('w1-seq');
1191
+ const lenEl = document.getElementById('w1-len');
1192
+ const bpEl = document.getElementById('w1-bp');
1193
+ const sourceEl = document.getElementById('w1-source');
1194
+ const playBtn = document.getElementById('w1-play');
1195
+ let rafId = null;
1196
+
1197
+ function colored(s) {
1198
+ return Array.from(s).map((c) => 'ACGT'.includes(c)
1199
+ ? '<span class="b-' + c + '">' + c + '</span>'
1200
+ : c).join('');
1201
+ }
1202
+ function tokenHTML(text) {
1203
+ return '<span class="br">&lt;</span>' + colored(text) + '<span class="br">&gt;</span>';
1204
+ }
1205
+ function ratio(seqLen, n) {
1206
+ if (!n) return '−';
1207
+ const r = seqLen / n;
1208
+ return (r >= 10 ? r.toFixed(0) : r.toFixed(1));
1209
+ }
1210
+
1211
+ function countLabel(sc, seqLen, tks) {
1212
+ return (sc.id === 'kmer' && seqLen % 6 !== 0)
1213
+ ? Math.floor(seqLen / 6) + '+1'
1214
+ : String(tks.length);
1215
+ }
1216
+
1217
+ // Build the source strip; render each scheme's tokens statically (resting
1218
+ // state) and set the final counts. play() then clears + re-animates.
1219
+ function rebuild() {
1220
+ if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
1221
+ const seq = cleanDNA(seqInput.value);
1222
+ lenEl.textContent = seq.length + ' bp';
1223
+ bpEl.textContent = seq.length;
1224
+
1225
+ sourceEl.innerHTML = '';
1226
+ for (let i = 0; i < seq.length; i++) {
1227
+ const sp = document.createElement('span');
1228
+ sp.className = 'tk-base ' + seq[i];
1229
+ sp.dataset.pos = i;
1230
+ sp.textContent = seq[i];
1231
+ sourceEl.appendChild(sp);
1232
+ }
1233
+
1234
+ for (const sc of SCHEMES) {
1235
+ sc.el.innerHTML = '';
1236
+ const tks = sc.tokens(seq);
1237
+ for (const tk of tks) {
1238
+ const el = document.createElement('span');
1239
+ el.className = 'tk-token dropped' + (tk.tail ? ' tail' : '');
1240
+ el.innerHTML = tokenHTML(tk.text);
1241
+ sc.el.appendChild(el);
1242
+ }
1243
+ sc.countEl.textContent = countLabel(sc, seq.length, tks);
1244
+ sc.ratioEl.textContent = ratio(seq.length, tks.length);
1245
+ }
1246
+ }
1247
+
1248
+ // FLIP-style emit: place the token at its final spot in the scheme strip,
1249
+ // then apply a transform that moves it back over the source chips and
1250
+ // transition that transform to identity so it visually flies into place.
1251
+ function emitToken(scheme, tk, sourceChips) {
1252
+ const el = document.createElement('span');
1253
+ el.className = 'tk-token' + (tk.tail ? ' tail' : '');
1254
+ el.innerHTML = tokenHTML(tk.text);
1255
+ scheme.el.appendChild(el);
1256
+
1257
+ // Measure destination (the natural position after appending)
1258
+ const dRect = el.getBoundingClientRect();
1259
+ const destCx = (dRect.left + dRect.right) / 2;
1260
+ const destCy = (dRect.top + dRect.bottom) / 2;
1261
+
1262
+ // Measure source span (bounding box of contributing source chips)
1263
+ const first = sourceChips[tk.startPos];
1264
+ const last = sourceChips[tk.endPos - 1];
1265
+ if (!first || !last) { el.classList.add('dropped'); return; }
1266
+ const fR = first.getBoundingClientRect();
1267
+ const lR = last.getBoundingClientRect();
1268
+ const srcCx = (Math.min(fR.left, lR.left) + Math.max(fR.right, lR.right)) / 2;
1269
+ const srcCy = (Math.min(fR.top, lR.top) + Math.max(fR.bottom, lR.bottom)) / 2;
1270
+
1271
+ const dx = srcCx - destCx;
1272
+ const dy = srcCy - destCy;
1273
+
1274
+ // No transition for the snap-back; then add .dropped on next frame
1275
+ // so the transition kicks in for the journey back to identity.
1276
+ el.style.transform = 'translate(' + dx + 'px, ' + dy + 'px) scale(0.92)';
1277
+ requestAnimationFrame(() => {
1278
+ el.classList.add('dropped');
1279
+ el.style.transform = '';
1280
+ });
1281
+ }
1282
+
1283
+ function play() {
1284
+ if (rafId) cancelAnimationFrame(rafId);
1285
+ const seq = cleanDNA(seqInput.value);
1286
+ if (!seq) return;
1287
+
1288
+ // Reset: clear scheme strips, zero the counters, un-consume the source.
1289
+ for (const sc of SCHEMES) {
1290
+ sc.el.innerHTML = '';
1291
+ sc.countEl.textContent = '0';
1292
+ sc.ratioEl.textContent = '—';
1293
+ }
1294
+ const sourceChips = Array.from(sourceEl.querySelectorAll('.tk-base'));
1295
+ sourceChips.forEach((c) => c.classList.remove('consumed'));
1296
+
1297
+ // Pre-compute the token list for each scheme
1298
+ const schemeTokens = {};
1299
+ for (const sc of SCHEMES) schemeTokens[sc.id] = sc.tokens(seq);
1300
+
1301
+ const emitted = { base: 0, bpe: 0, kmer: 0 };
1302
+ const emittedBp = { base: 0, bpe: 0, kmer: 0 };
1303
+ const duration = Math.max(1800, seq.length * 95);
1304
+ const start = performance.now();
1305
+
1306
+ function step(now) {
1307
+ const t = Math.min(1, (now - start) / duration);
1308
+ const cursor = t * seq.length;
1309
+
1310
+ // Dim source chips up to cursor
1311
+ for (let i = 0; i < sourceChips.length; i++) {
1312
+ if (i < cursor && !sourceChips[i].classList.contains('consumed')) {
1313
+ sourceChips[i].classList.add('consumed');
1314
+ }
1315
+ }
1316
+
1317
+ // Emit any token whose end position has been reached, counting in real time
1318
+ for (const sc of SCHEMES) {
1319
+ const tks = schemeTokens[sc.id];
1320
+ while (emitted[sc.id] < tks.length && tks[emitted[sc.id]].endPos <= cursor) {
1321
+ const tk = tks[emitted[sc.id]];
1322
+ emitToken(sc, tk, sourceChips);
1323
+ emitted[sc.id]++;
1324
+ emittedBp[sc.id] = tk.endPos;
1325
+ sc.countEl.textContent = String(emitted[sc.id]);
1326
+ sc.ratioEl.textContent = ratio(emittedBp[sc.id], emitted[sc.id]);
1327
+ }
1328
+ }
1329
+
1330
+ if (t < 1) {
1331
+ rafId = requestAnimationFrame(step);
1332
+ } else {
1333
+ // Settle on the exact final figures
1334
+ for (const sc of SCHEMES) {
1335
+ const tks = schemeTokens[sc.id];
1336
+ sc.countEl.textContent = countLabel(sc, seq.length, tks);
1337
+ sc.ratioEl.textContent = ratio(seq.length, tks.length);
1338
+ }
1339
+ rafId = null;
1340
+ }
1341
+ }
1342
+ rafId = requestAnimationFrame(step);
1343
+ }
1344
+
1345
+ seqInput.addEventListener('input', rebuild);
1346
+ playBtn.addEventListener('click', play);
1347
+
1348
+ rebuild();
1349
+
1350
+ // Auto-play once when the widget scrolls into view
1351
+ if ('IntersectionObserver' in window) {
1352
+ const io = new IntersectionObserver((entries) => {
1353
+ for (const e of entries) {
1354
+ if (e.isIntersecting) { io.disconnect(); setTimeout(play, 300); break; }
1355
+ }
1356
+ }, { threshold: 0.3 });
1357
+ io.observe(document.getElementById('w1'));
1358
+ } else {
1359
+ setTimeout(play, 600);
1360
+ }
1361
+ })();
1362
+ </script>
1363
+ </body>
1364
  </html>
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }