GenerTeam's picture
Update index.html
d2d4628 verified
Raw
History Blame Contribute Delete
45.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Per-base, BPE, and 6-mer on the same DNA sequence</title>
<meta name="color-scheme" content="light">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<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">
<style>
:root {
--bg: #f7f5ee;
--ink: #1f1f1d;
--ink-soft: #5b5b56;
--ink-faint: #8a8a83;
--rule: #e3e1d6;
--hair: #eee;
--green: #317f3f;
--green-dark: #1f5024;
--green-tint: #f4f8f4;
--amber: #b8862c;
--amber-dark: #6b4d18;
--red: #b00020;
--blue: #2c5aa0;
--card: #fff;
--soft-cream: #fafaf6;
--base-a: #1A7A40;
--base-t: #b00020;
--base-c: #2c5aa0;
--base-g: #b8862c;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: "Inter", "Helvetica Neue", sans-serif;
font-size: 14px; font-weight: 300; line-height: 1.7;
color: var(--ink);
background: var(--bg);
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }
/* --- Page header ----------------------------------------------------- */
.page-header {
border-bottom: 1px solid var(--rule);
background: var(--bg);
position: sticky; top: 0; z-index: 50;
backdrop-filter: saturate(180%) blur(6px);
}
.page-header__inner {
max-width: 1200px;
margin: 0 auto;
padding: 14px 32px;
display: flex; align-items: baseline; gap: 16px;
}
.wordmark {
font-family: "JetBrains Mono", monospace;
font-weight: 700;
font-size: 16px;
letter-spacing: 1px;
}
.wordmark .caret { color: var(--green); margin-right: 4px; }
.wordmark__sub {
font-family: "JetBrains Mono", monospace;
font-size: 11px; font-weight: 500;
text-transform: uppercase; letter-spacing: 2px;
color: var(--ink-soft);
margin-left: 4px;
}
.page-header__spacer { flex: 1; }
.page-header__crumbs {
font-family: "JetBrains Mono", monospace;
font-size: 10px; letter-spacing: 1.4px;
text-transform: uppercase; color: var(--ink-faint);
}
.page-header__crumbs a { color: var(--ink-soft); text-decoration: none; }
.page-header__crumbs a:hover { color: var(--green); }
/* --- Lede ----------------------------------------------------------- */
.tab-lede {
max-width: 1200px; margin: 56px auto 0;
padding: 0 32px;
}
.tab-lede__rail {
border-left: 3px solid var(--green);
padding: 4px 0 4px 22px;
max-width: 820px;
}
.tab-lede__eyebrow {
display: block;
font-family: "JetBrains Mono", monospace;
font-size: 11px; font-weight: 500;
letter-spacing: 0.22em; text-transform: uppercase;
color: var(--green); margin-bottom: 12px;
}
.tab-lede__title {
margin: 0 0 22px;
font-family: "JetBrains Mono", monospace;
font-size: 34px; font-weight: 500;
letter-spacing: -0.01em; line-height: 1.12;
color: var(--ink);
}
.tab-lede__lead {
margin: 0; max-width: 760px;
font-family: "Inter", sans-serif;
font-size: 19px; font-weight: 300; line-height: 1.5;
letter-spacing: -0.005em; color: #2d2d2a;
}
/* --- Post body ------------------------------------------------------ */
.post {
max-width: 760px;
margin: 32px auto 0;
padding: 0 32px 96px;
}
.post h2 {
margin: 64px 0 18px;
font-family: "JetBrains Mono", monospace;
font-size: 22px; font-weight: 500;
letter-spacing: -0.005em; line-height: 1.3;
color: var(--ink);
padding-top: 12px;
border-top: 1px solid var(--rule);
}
.post h2:first-child { border-top: none; padding-top: 0; }
.post h3 {
margin: 40px 0 12px;
font-family: "JetBrains Mono", monospace;
font-size: 15px; font-weight: 500;
letter-spacing: 0;
color: var(--ink);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.post p { margin: 0 0 14px; }
.post p > code, .post li > code {
font-family: "JetBrains Mono", monospace;
font-size: 0.86em;
background: var(--soft-cream);
border: 1px solid var(--rule);
padding: 1px 5px;
border-radius: 2px;
color: var(--green-dark);
}
.post pre {
margin: 14px 0 18px;
padding: 14px 16px;
background: var(--card);
border: 1px solid #ddd;
overflow-x: auto;
}
.post pre code {
font-family: "JetBrains Mono", monospace;
font-size: 12px; line-height: 1.6;
color: var(--ink);
background: transparent; border: none; padding: 0;
}
.post a {
color: var(--green-dark);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
}
.post a:hover { color: var(--green); }
.post strong { font-weight: 600; color: var(--ink); }
.post-image {
margin: 24px 0;
}
.post-image img {
display: block;
width: 100%; height: auto;
border: 1px solid var(--rule);
}
/* --- Widget shell (carbon-demo style) ------------------------------- */
.widget {
max-width: 920px;
margin: 40px auto 8px;
}
.widget__head {
display: flex; align-items: baseline; gap: 14px;
margin-bottom: 6px;
}
.widget__eyebrow {
font-family: "JetBrains Mono", monospace;
font-size: 10px; font-weight: 500;
letter-spacing: 0.22em; text-transform: uppercase;
color: var(--green);
}
.widget__title {
font-family: "JetBrains Mono", monospace;
font-size: 14px; font-weight: 500;
color: var(--ink); letter-spacing: 0;
}
.widget__caption {
font-family: "Inter", sans-serif;
font-size: 12px; color: var(--ink-soft);
margin: 8px 4px 14px;
line-height: 1.5;
}
.demo {
background: var(--card); border: 1px solid #ddd;
padding: 22px; margin: 8px 0;
}
.demo-toolbar {
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
margin-bottom: 14px;
font-family: "JetBrains Mono", monospace; font-size: 10px;
color: #666; text-transform: uppercase; letter-spacing: 1.4px;
}
.demo-toolbar .spacer { flex: 1; }
.demo-label {
font-family: "JetBrains Mono", monospace;
font-size: 9.5px; color: #6b7a6e;
text-transform: uppercase; letter-spacing: 1.6px;
margin: 6px 0 6px;
}
.pill, button.action {
font-family: "JetBrains Mono", monospace;
font-size: 11px; font-weight: 400;
padding: 5px 11px; border: 1px solid #ccc; border-radius: 3px;
background: #fff; color: #555; cursor: pointer;
text-transform: uppercase; letter-spacing: 1.5px;
transition: all 0.12s;
}
.pill:hover, button.action:hover { border-color: #888; color: var(--ink); }
.pill.active, button.action.primary { background: var(--ink); color: #fff; border-color: var(--ink); }
.pill.active:hover, button.action.primary:hover { background: #000; }
.pill.gh { background: var(--green); color: #fff; border-color: var(--green); }
.pill.gh:hover { background: var(--green-dark); border-color: var(--green-dark); }
.pills { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.status {
font-family: "JetBrains Mono", monospace;
font-size: 10px; color: #666;
text-transform: uppercase; letter-spacing: 1.5px;
display: inline-flex; align-items: center; gap: 6px;
margin-left: 8px;
}
.status .dot {
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
background: var(--green);
}
input.seq-input, textarea.seq-input {
width: 100%;
font-family: "JetBrains Mono", monospace;
font-size: 13px;
padding: 9px 12px;
border: 1px solid #ccc;
border-radius: 3px;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--ink);
background: var(--soft-cream);
resize: vertical;
}
textarea.seq-input { letter-spacing: 0.5px; }
input.seq-input:focus, textarea.seq-input:focus {
outline: none;
border-color: var(--green);
background: #fff;
}
/* base + token chips */
.base, .tok {
font-family: "JetBrains Mono", monospace;
font-size: 11px;
letter-spacing: 0.5px;
}
.base {
display: inline-block;
padding: 2px 5px;
font-weight: 500;
margin: 1px;
color: #fff;
border-radius: 2px;
min-width: 16px;
text-align: center;
}
.base.A { background: var(--base-a); }
.base.T { background: var(--base-t); }
.base.C { background: var(--base-c); }
.base.G { background: var(--base-g); }
.base.dim { opacity: 0.4; }
.base.outline {
background: transparent;
border: 1px solid currentColor;
}
.base.outline.A { color: var(--base-a); }
.base.outline.T { color: var(--base-t); }
.base.outline.C { color: var(--base-c); }
.base.outline.G { color: var(--base-g); }
.tok {
display: inline-flex;
align-items: center;
padding: 4px 8px;
margin: 2px;
border: 1px solid #ccc;
border-radius: 3px;
background: #fff;
color: var(--ink);
}
.tok.kmer {
background: rgba(49,127,63,0.10);
border-color: rgba(49,127,63,0.5);
color: var(--green-dark);
font-weight: 500;
}
.tok.bpe {
background: rgba(184,134,44,0.10);
border-color: rgba(184,134,44,0.5);
color: var(--amber-dark);
}
.tok.text {
background: #fff;
border-color: #ccc;
color: var(--ink);
}
.tok.boundary {
background: var(--ink);
color: #fff;
border-color: var(--ink);
text-transform: uppercase;
letter-spacing: 1px;
font-size: 10px;
}
.tok.dim { color: #888; background: var(--soft-cream); }
.tok.ok { background: var(--green); color: #fff; border-color: var(--green); }
.tok.bad { color: var(--red); border-color: rgba(176,0,32,0.4); background: rgba(176,0,32,0.05); }
.tok.selected {
outline: 2px solid var(--green);
outline-offset: 1px;
}
.token-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0; }
/* count badges */
.count-badge {
display: inline-block;
font-family: "JetBrains Mono", monospace;
font-size: 10px; font-weight: 600;
background: var(--ink); color: var(--bg);
padding: 2px 7px; border-radius: 2px;
letter-spacing: 1px;
}
.count-badge.green { background: var(--green); color: #fff; }
.count-badge.amber { background: var(--amber); color: #fff; }
/* W1 · animated tokenization (source on top, schemes drop down) ------ */
.tk-anim { margin-top: 18px; }
.tk-source, .tk-scheme {
display: grid;
grid-template-columns: 124px 1fr;
gap: 18px;
align-items: start;
padding: 8px 0;
}
.tk-scheme { padding: 10px 0; min-height: 32px; }
.tk-source { padding-bottom: 14px; border-bottom: 1px solid var(--rule); margin-bottom: 6px; }
.tk-meta {
display: flex;
flex-direction: column;
gap: 3px;
padding-top: 2px;
}
.tk-label {
font-family: "JetBrains Mono", monospace;
font-size: 9.5px;
font-weight: 500;
color: var(--ink-soft);
text-transform: uppercase;
letter-spacing: 1.6px;
}
.tk-stat {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
color: var(--ink-faint);
letter-spacing: 0.5px;
font-variant-numeric: tabular-nums;
}
.tk-stat .n {
font-weight: 600;
color: var(--ink);
}
.tk-scheme[data-scheme="bpe"] .tk-stat .n { color: var(--amber-dark); }
.tk-scheme[data-scheme="kmer"] .tk-stat .n { color: var(--green-dark); }
/* Source: tight grid of small chips */
.tk-source-strip {
display: flex;
flex-wrap: wrap;
row-gap: 2px;
}
.tk-source-strip .tk-base {
display: inline-flex;
align-items: center;
justify-content: center;
width: 13px;
height: 17px;
color: #fff;
font-family: "JetBrains Mono", monospace;
font-size: 10px;
font-weight: 500;
border-radius: 1px;
flex: 0 0 13px;
transition: opacity 0.35s ease, filter 0.35s ease;
}
.tk-source-strip .tk-base + .tk-base { margin-left: 1px; }
.tk-source-strip .tk-base.consumed { opacity: 0.18; filter: grayscale(0.6); }
.tk-base.A { background: var(--base-a); }
.tk-base.T { background: var(--base-t); }
.tk-base.C { background: var(--base-c); }
.tk-base.G { background: var(--base-g); }
/* Scheme strips: hold dropped tokens with <> brackets */
.tk-scheme-strip {
display: flex;
flex-wrap: wrap;
gap: 1px 8px;
min-height: 20px;
}
.tk-token {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.5px;
display: inline-flex;
align-items: center;
opacity: 0;
will-change: transform, opacity;
}
.tk-token.dropped {
opacity: 1;
transition:
transform 0.48s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.32s ease;
}
.tk-token .br { color: var(--ink-faint); font-weight: 400; }
.tk-token.tail .br { color: var(--ink-faint); }
.tk-token.tail { opacity: 0.55; }
.tk-token.tail.dropped { opacity: 0.55; }
/* Layouts */
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 14px;
margin-top: 14px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
margin-top: 14px;
}
@media (max-width: 720px) {
.grid-3, .grid-2 { grid-template-columns: 1fr; }
}
.col-card {
background: var(--soft-cream);
border: 1px solid var(--rule);
padding: 14px;
}
.col-card .col-title {
font-family: "JetBrains Mono", monospace;
font-size: 10px; font-weight: 500;
letter-spacing: 1.6px; text-transform: uppercase;
color: var(--ink-soft);
margin-bottom: 8px;
}
.col-card .col-foot {
margin-top: 10px;
display: flex; align-items: center; gap: 8px;
font-family: "JetBrains Mono", monospace;
font-size: 10px; color: var(--ink-soft);
text-transform: uppercase; letter-spacing: 1.4px;
}
/* Bar chart helpers */
.bar-row {
display: grid;
grid-template-columns: 18px 1fr 64px;
align-items: center;
gap: 8px;
margin: 3px 0;
font-family: "JetBrains Mono", monospace;
font-size: 11px;
}
.bar-row .bar-label { color: var(--ink-soft); font-weight: 500; text-align: center; }
.bar-row .bar-track {
position: relative;
height: 14px;
background: var(--soft-cream);
border: 1px solid var(--rule);
}
.bar-row .bar-fill {
height: 100%;
transition: width 220ms ease, background-color 220ms ease;
}
.bar-row .bar-val { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }
.bar-row.highlight .bar-track { box-shadow: 0 0 0 2px var(--green-tint); }
/* 6-position grid (used in W4, W5, W6) */
.posgrid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
}
.posgrid__col {
background: var(--soft-cream);
border: 1px solid var(--rule);
border-radius: 2px;
padding: 8px 6px 6px;
}
.posgrid__pos {
font-family: "JetBrains Mono", monospace;
font-size: 9px; color: var(--ink-faint);
text-transform: uppercase; letter-spacing: 1.4px;
text-align: center; margin-bottom: 6px;
}
.posgrid__bars {
display: flex; flex-direction: column; gap: 3px;
}
.posgrid .mini-row {
display: grid; grid-template-columns: 12px 1fr 32px;
align-items: center; gap: 4px;
font-family: "JetBrains Mono", monospace; font-size: 10px;
}
.posgrid .mini-row .ml { color: var(--ink-soft); text-align: center; font-weight: 500; }
.posgrid .mini-row .mt { position: relative; height: 10px; background: #fff; border: 1px solid var(--rule); }
.posgrid .mini-row .mf { display: block; height: 100%; transition: width 200ms ease; }
.posgrid .mini-row .mv { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; font-size: 9.5px; }
.posgrid__col.argmax-A { box-shadow: inset 0 0 0 1px var(--base-a); }
.posgrid__col.argmax-T { box-shadow: inset 0 0 0 1px var(--base-t); }
.posgrid__col.argmax-C { box-shadow: inset 0 0 0 1px var(--base-c); }
.posgrid__col.argmax-G { box-shadow: inset 0 0 0 1px var(--base-g); }
.posgrid__col.locked { background: #fff; }
.posgrid__col.frozen .posgrid__bars { opacity: 0.4; }
.posgrid__sel { display: flex; gap: 3px; margin-top: 8px; }
.cond-pick {
flex: 1 1 0; min-width: 0;
font-family: "JetBrains Mono", monospace; font-size: 10px; font-weight: 600;
padding: 3px 0; border: 1px solid var(--rule); border-radius: 2px;
background: #fff; cursor: pointer;
transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.cond-pick:hover { border-color: #888; }
.cond-pick.active { color: #fff; }
.cond-pick.active.b-A { background: var(--base-a); border-color: var(--base-a); }
.cond-pick.active.b-T { background: var(--base-t); border-color: var(--base-t); }
.cond-pick.active.b-C { background: var(--base-c); border-color: var(--base-c); }
.cond-pick.active.b-G { background: var(--base-g); border-color: var(--base-g); }
/* W3 · real Carbon-3B per-token scoring --------------------------- */
.w3-itok { cursor: pointer; transition: outline-color 0.12s; }
.w3-itok:hover { border-color: #888; }
.w3-sub {
font-family: "JetBrains Mono", monospace;
font-size: 11px; color: var(--ink-soft);
margin: 2px 0 10px; line-height: 1.7;
}
.w3-sub b { color: var(--ink); font-weight: 600; }
.w3-sub .tok { margin: 0 2px; }
.w3-toprow {
display: grid;
grid-template-columns: 92px 1fr 52px;
gap: 12px; align-items: center;
margin: 4px 0;
font-family: "JetBrains Mono", monospace; font-size: 11px;
}
.w3-toprow .km { font-weight: 500; letter-spacing: 0.5px; }
.w3-toprow .bar { height: 13px; background: var(--soft-cream); border: 1px solid var(--rule); }
.w3-toprow .fill { height: 100%; background: var(--green); transition: width 0.35s ease; }
.w3-toprow .pct { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); }
.w3-toprow.observed .km { color: var(--ink); }
.w3-toprow.observed .fill { background: var(--amber); }
.w3-io { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; margin-top: 6px; }
.w3-io__group { display: flex; flex-direction: column; gap: 6px; }
.w3-io__lab {
font-family: "JetBrains Mono", monospace;
font-size: 9.5px; letter-spacing: 1.6px; text-transform: uppercase; color: var(--ink-faint);
}
.w3-io__arrow { font-family: "JetBrains Mono", monospace; font-size: 16px; color: var(--ink-faint); padding-bottom: 5px; }
.w3-gt-tok { background: rgba(184,134,44,0.12); border-color: rgba(184,134,44,0.5) !important; }
.w3-gt-tok { text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 2px; }
.w3-chart { display: block; width: 100%; height: auto; margin-top: 6px; background: #fff; border: 1px solid #eee; }
.w3-legend {
display: flex; gap: 20px; flex-wrap: wrap; margin-top: 8px;
font-family: "JetBrains Mono", monospace; font-size: 10px;
color: var(--ink-soft); text-transform: uppercase; letter-spacing: 1px;
}
.w3-legend span { display: inline-flex; align-items: center; gap: 6px; }
.w3-legend svg { width: 20px; height: 8px; display: inline-block; vertical-align: middle; }
.w3-top { display: flex; gap: 6px; margin-top: 12px; }
.w3-topchip {
flex: 1 1 0; min-width: 0;
display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
font-family: "JetBrains Mono", monospace;
border: 1px solid var(--rule); background: var(--soft-cream);
border-radius: 3px; padding: 6px 4px;
}
.w3-topchip .km { font-size: 12px; font-weight: 500; letter-spacing: 0.5px; }
.w3-topchip.ground-truth .km { text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 2px; }
.w3-topchip .pct { font-size: 9px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.posgrid__col.clickable { cursor: pointer; }
.posgrid__col.sel { outline: 2px solid var(--ink); outline-offset: 2px; }
.w3-allgrid {
display: grid;
grid-template-columns: repeat(128, 1fr);
gap: 1px;
margin-top: 8px;
}
.w3-cell { aspect-ratio: 1; background: #d3d0c4; transition: background 0.15s ease; }
.w3-cell.first {
outline: 2px solid var(--ink);
outline-offset: 1px;
position: relative; z-index: 2;
}
.w3-order {
display: inline-flex; align-items: center; gap: 8px;
font-family: "JetBrains Mono", monospace; font-size: 9.5px;
text-transform: uppercase; letter-spacing: 1.4px; color: var(--ink-faint);
}
.w3-snake { width: 42px; height: auto; color: var(--ink-soft); display: block; }
/* WCOND · conditional single-nucleotide decoding -------------------- */
.cond-slots { display: flex; gap: 6px; }
.cond-slot {
width: 40px; height: 40px;
display: flex; align-items: center; justify-content: center;
border: 1px solid var(--rule); border-radius: 3px; background: var(--soft-cream);
font-family: "JetBrains Mono", monospace; font-size: 17px; font-weight: 600;
color: var(--ink);
}
.cond-slot.active { outline: 2px solid var(--ink); outline-offset: 1px; color: var(--ink-faint); }
.cond-slot.pending { color: var(--ink-faint); background: transparent; border-style: dashed; }
.cond-slot.A { background: rgba(26,122,64,0.13); border-color: rgba(26,122,64,0.42); color: var(--base-a); }
.cond-slot.T { background: rgba(176,0,32,0.09); border-color: rgba(176,0,32,0.36); color: var(--base-t); }
.cond-slot.C { background: rgba(44,90,160,0.11); border-color: rgba(44,90,160,0.40); color: var(--base-c); }
.cond-slot.G { background: rgba(184,134,44,0.14); border-color: rgba(184,134,44,0.42); color: var(--amber-dark); }
.cond-choices { display: flex; gap: 10px; }
.cond-choice {
flex: 1 1 0; cursor: pointer; border: 1px solid var(--rule); border-radius: 3px;
padding: 10px 12px; background: var(--soft-cream);
display: flex; flex-direction: column; gap: 7px;
transition: background 0.12s, border-color 0.12s;
}
.cond-choice:hover { background: #fff; border-color: #888; }
.cond-choice__top { display: flex; justify-content: space-between; align-items: baseline; font-family: "JetBrains Mono", monospace; }
.cond-choice__base { font-size: 16px; font-weight: 600; }
.cond-choice__pct { font-size: 11px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.cond-choice__bar { height: 8px; background: #fff; border: 1px solid var(--rule); }
.cond-choice__fill { display: block; height: 100%; transition: width 0.25s ease; }
.cond-done { font-family: "JetBrains Mono", monospace; font-size: 13px; color: var(--ink); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cond-note {
font-family: "Inter", sans-serif; font-size: 12px; color: var(--ink-soft);
background: var(--soft-cream); border-left: 2px solid var(--green);
padding: 9px 12px; margin-top: 14px; line-height: 1.5;
}
.cond-note .tok { margin: 0 2px; }
/* FNS factorization figure ------------------------------------------ */
.fns-seq-input {
width: 120px;
text-align: center;
text-transform: uppercase;
}
.fns-viz {
font-family: "JetBrains Mono", monospace;
padding: 10px 0 6px;
}
.fns-truth, .fns-row {
display: grid;
grid-template-columns: 222px repeat(6, 34px) 28px;
align-items: center;
column-gap: 7px;
min-width: 480px;
}
.fns-truth { margin-bottom: 16px; }
.fns-row { margin: 12px 0; }
.fns-gtlabel {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
font-weight: 400;
letter-spacing: 1.6px;
text-transform: uppercase;
color: var(--ink-faint);
}
.fns-eq {
font-size: 16px;
color: var(--ink);
text-align: right;
white-space: nowrap;
}
.fns-eq sub, .fns-eq sup { font-size: 11px; }
.fns-eq .sum { color: var(--ink-soft); }
.fns-bracket {
font-family: "Inter", sans-serif;
font-size: 34px;
font-weight: 200;
line-height: 0;
color: var(--ink-soft);
vertical-align: middle;
transform: scaleX(0.7);
display: inline-block;
}
.fns-box {
width: 30px; height: 30px;
display: inline-flex; align-items: center; justify-content: center;
font-family: "JetBrains Mono", monospace;
font-size: 14px; font-weight: 600;
border-radius: 3px; border: 1px solid;
transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.fns-box.A { background: rgba(26,122,64,0.13); border-color: rgba(26,122,64,0.42); color: var(--base-a); }
.fns-box.T { background: rgba(176,0,32,0.09); border-color: rgba(176,0,32,0.36); color: var(--base-t); }
.fns-box.C { background: rgba(44,90,160,0.11); border-color: rgba(44,90,160,0.40); color: var(--base-c); }
.fns-box.G { background: rgba(184,134,44,0.14); border-color: rgba(184,134,44,0.42); color: var(--amber-dark); }
.fns-box.wild { opacity: 0.38; }
.fns-loss {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--rule);
font-family: "JetBrains Mono", monospace;
font-size: 16px;
color: var(--ink);
text-align: center;
}
.fns-loss sup, .fns-loss sub { font-size: 11px; }
.fns-loss .frac { display: inline-block; margin: 0 1px; }
.fns-loss .fns-term { margin: 0 3px; white-space: nowrap; }
.fns-explain {
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid var(--rule);
font-family: "Inter", sans-serif;
font-size: 13px;
line-height: 1.62;
color: var(--ink-soft);
}
.fns-explain p { margin: 0 0 11px; }
.fns-explain p:last-child { margin-bottom: 0; }
.fns-explain strong { color: var(--ink); font-weight: 600; }
.fns-explain .mono {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
color: var(--ink);
}
.fns-explain .mono sup, .fns-explain .mono sub { font-size: 9px; }
/* Big stat tile */
.stat-tile {
background: var(--soft-cream);
border: 1px solid var(--rule);
padding: 14px 16px;
text-align: left;
}
.stat-tile__label {
font-family: "JetBrains Mono", monospace;
font-size: 10px; color: var(--ink-soft);
text-transform: uppercase; letter-spacing: 1.6px;
margin-bottom: 6px;
}
.stat-tile__value {
font-family: "JetBrains Mono", monospace;
font-size: 26px; font-weight: 500;
color: var(--ink);
}
.stat-tile__sub {
font-family: "JetBrains Mono", monospace;
font-size: 10px; color: var(--ink-soft);
margin-top: 4px; letter-spacing: 0.5px;
}
.stat-tile.green .stat-tile__value { color: var(--green-dark); }
.stat-tile.red .stat-tile__value { color: var(--red); }
/* Footer */
.post-foot {
max-width: 760px;
margin: 64px auto 0;
padding: 24px 32px 64px;
border-top: 1px solid var(--rule);
font-family: "JetBrains Mono", monospace;
font-size: 11px; color: var(--ink-soft);
text-transform: uppercase; letter-spacing: 1.4px;
}
.post-foot a { color: var(--green-dark); text-decoration: none; }
.post-foot a:hover { color: var(--green); }
/* Position picker (W7) */
.seq-track {
font-family: "JetBrains Mono", monospace;
font-size: 14px;
letter-spacing: 0;
background: var(--soft-cream);
border: 1px solid var(--rule);
padding: 10px 12px;
display: flex; flex-wrap: wrap; gap: 1px;
user-select: none;
}
.seq-track__b {
display: inline-flex; align-items: center; justify-content: center;
width: 22px; height: 26px;
cursor: pointer;
border-radius: 2px;
color: #fff;
font-weight: 500;
transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.seq-track__b:hover { transform: translateY(-1px); }
.seq-track__b.A { background: var(--base-a); }
.seq-track__b.T { background: var(--base-t); }
.seq-track__b.C { background: var(--base-c); }
.seq-track__b.G { background: var(--base-g); }
.seq-track__b.selected {
box-shadow: 0 0 0 2px var(--ink), 0 0 0 4px var(--bg);
}
/* Per-base text color (used wherever DNA letters appear in prose) */
.b-A, .b-T, .b-C, .b-G { font-weight: 500; }
.b-A { color: var(--base-a); }
.b-T { color: var(--base-t); }
.b-C { color: var(--base-c); }
.b-G { color: var(--base-g); }
/* k-mer token: colored bases inside <>, no chrome */
.kmer-token, .raw-seq {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.5px;
}
.kmer-token {
display: inline-flex;
align-items: center;
padding: 2px 0;
white-space: nowrap;
}
.kmer-token .br { color: var(--ink-faint); font-weight: 400; }
.kmer-token.muted { opacity: 0.45; }
.raw-seq { display: inline-flex; padding: 2px 0; }
.bpe-flow {
display: flex; align-items: center; flex-wrap: wrap;
gap: 18px;
margin: 10px 0 8px;
}
.bpe-flow .arrow {
font-family: "JetBrains Mono", monospace;
font-size: 16px;
color: var(--ink-faint);
}
/* Line 2: source token, SVG fan-out, candidate rows ----------------- */
.bpe-branches {
display: grid;
grid-template-columns: auto 96px 1fr;
grid-template-rows: auto 150px;
gap: 0;
margin-top: 12px;
}
.branch-source {
grid-column: 1; grid-row: 2;
align-self: center;
display: flex; flex-direction: column; align-items: center; gap: 6px;
padding-right: 8px;
}
.branch-source__hint {
font-family: "JetBrains Mono", monospace;
font-size: 9px; color: var(--ink-faint);
text-transform: uppercase; letter-spacing: 1.6px;
}
.branch-lines {
grid-column: 2; grid-row: 2;
width: 96px; height: 150px;
display: block;
}
.branch-lines path {
fill: none;
stroke: #ccc;
stroke-width: 1.4;
transition: stroke 0.15s, stroke-width 0.15s;
}
.branch-lines path.selected { stroke: var(--green); stroke-width: 2.2; }
.branch-header {
grid-column: 3; grid-row: 1;
display: grid;
grid-template-columns: 100px 60px 60px;
gap: 18px;
padding: 0 12px 10px;
font-family: "JetBrains Mono", monospace;
font-size: 9.5px;
font-weight: 500;
letter-spacing: 1.6px;
text-transform: uppercase;
color: var(--ink-soft);
}
.branch-header > span { text-align: center; }
.branch-header > span:first-child { text-align: left; color: var(--ink-faint); }
.branch-targets {
grid-column: 3; grid-row: 2;
display: grid;
grid-template-rows: repeat(5, 26px);
row-gap: 5px;
align-content: center;
justify-content: start;
height: 150px;
}
.cand-row {
display: grid;
grid-template-columns: 100px 60px 60px;
gap: 18px;
align-items: center;
padding: 2px 12px;
cursor: pointer;
border-radius: 2px;
transition: background 0.12s;
}
.cand-row:hover { background: rgba(0,0,0,0.03); }
.cand-row.selected { background: var(--green-tint); }
.mark {
font-family: "JetBrains Mono", monospace;
font-size: 14px;
font-weight: 500;
text-align: center;
line-height: 1;
user-select: none;
}
.mark.ok { color: var(--green); }
.mark.bad { color: var(--red); }
/* explanation note */
.note {
font-family: "Inter", sans-serif;
font-size: 12px; line-height: 1.55;
color: var(--ink-soft);
background: var(--soft-cream);
border-left: 2px solid var(--green);
padding: 10px 12px;
margin-top: 14px;
}
.note code {
font-family: "JetBrains Mono", monospace;
font-size: 11px;
background: #fff;
border: 1px solid var(--rule);
padding: 0 4px; border-radius: 2px;
color: var(--green-dark);
}
.formula {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
background: var(--soft-cream);
border: 1px solid var(--rule);
padding: 10px 12px;
margin-top: 10px;
color: var(--ink);
overflow-x: auto;
}
.formula .hl { color: var(--green-dark); font-weight: 600; }
/* Iframe wrapper: white page, no widget chrome — the host page provides the
heading and description, so we only ship the interactive demo card. */
body { padding: 20px 24px; background: #fff; }
.widget { max-width: 880px; margin: 0 auto; padding: 0; border: 0; }
.widget__head, .widget__caption { display: none; }
</style>
</head>
<body>
<aside class="widget" id="w1">
<div class="widget__head">
<span class="widget__eyebrow">§ Widget · 02</span>
<span class="widget__title">Per-base, BPE, and 6-mer on the same DNA sequence</span>
</div>
<p class="widget__caption">
Press <strong>play</strong> to watch the same sequence get cut three different ways — per-base, with a toy BPE
vocabulary (greedy longest-match), and with non-overlapping 6-mers. The cuts appear left-to-right on the same
time axis, so divergence between the schemes is visible directly.
</p>
<div class="demo">
<div class="demo-toolbar">
<span>Sequence · <span id="w1-len">0 bp</span></span>
<span class="spacer"></span>
<button class="action primary" id="w1-play">▶ TOKENIZE</button>
</div>
<input class="seq-input" id="w1-seq" value="ACGTATCGTATAGGCTAACGGATCATGCTAACGGATCATGCTAGCTAATGCATGCATGCAATCGATCGGGCCTTAAGCTAGCTACGATCGTAGCAT">
<div class="tk-anim">
<div class="tk-source">
<div class="tk-meta">
<span class="tk-label">Sequence</span>
<span class="tk-stat"><span class="n" id="w1-bp">0</span> bp</span>
</div>
<div class="tk-source-strip" id="w1-source"></div>
</div>
<div class="tk-scheme" data-scheme="base">
<div class="tk-meta">
<span class="tk-label">Per-base</span>
<span class="tk-stat">Tokens: <span class="n" id="w1-base-count">0</span></span>
<span class="tk-stat">Compression: <span class="n" id="w1-base-ratio">1.0</span></span>
</div>
<div class="tk-scheme-strip" id="w1-base"></div>
</div>
<div class="tk-scheme" data-scheme="bpe">
<div class="tk-meta">
<span class="tk-label">BPE</span>
<span class="tk-stat">Tokens: <span class="n" id="w1-bpe-count">0</span></span>
<span class="tk-stat">Compression: <span class="n" id="w1-bpe-ratio"></span></span>
</div>
<div class="tk-scheme-strip" id="w1-bpe"></div>
</div>
<div class="tk-scheme" data-scheme="kmer">
<div class="tk-meta">
<span class="tk-label">6-mer</span>
<span class="tk-stat">Tokens: <span class="n" id="w1-kmer-count">0</span></span>
<span class="tk-stat">Compression: <span class="n" id="w1-kmer-ratio"></span></span>
</div>
<div class="tk-scheme-strip" id="w1-kmer"></div>
</div>
</div>
</div>
</aside>
<script>
// ============================================================
// Shared utilities
// ============================================================
const BASES = ['A', 'T', 'C', 'G'];
const BASE_IDX = {A: 0, T: 1, C: 2, G: 3};
const BASE_COLOR = { A: 'var(--base-a)', T: 'var(--base-t)', C: 'var(--base-c)', G: 'var(--base-g)' };
// FNV-1a 32-bit
function hash32(s) {
let h = 0x811c9dc5;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 0x01000193);
}
return h >>> 0;
}
function rand01(seed) {
const h = hash32(String(seed));
return (h % 1000003) / 1000003;
}
function softmax(arr) {
let m = -Infinity;
for (let i = 0; i < arr.length; i++) if (arr[i] > m) m = arr[i];
const out = new Float64Array(arr.length);
let s = 0;
for (let i = 0; i < arr.length; i++) {
out[i] = Math.exp(arr[i] - m);
s += out[i];
}
for (let i = 0; i < arr.length; i++) out[i] /= s;
return out;
}
function cleanDNA(s) {
return (s || '').toUpperCase().replace(/[^ACGT]/g, '');
}
function escapeHTML(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// 6-mer integer <-> string helpers
function kmerToIdx(k) {
let i = 0;
for (let p = 0; p < k.length; p++) {
const b = BASE_IDX[k[p]];
if (b == null) return -1;
i = i * 4 + b;
}
return i;
}
function idxToKmer(i, k = 6) {
let s = '';
for (let p = k - 1; p >= 0; p--) {
s = BASES[(i >> (2 * p)) & 3] + s;
}
return s;
}
function baseAt(i, p) {
// p in [0..5], 0 is leftmost
return (i >> (2 * (5 - p))) & 3;
}
// generate a 4096-d distribution conditioned on a string seed
function distribution4096(seed) {
const logits = new Float64Array(4096);
for (let i = 0; i < 4096; i++) {
const a = rand01(seed + ':a:' + i);
const b = rand01(seed + ':b:' + (i * 7919));
const c = rand01(seed + ':c:' + (i * 65537));
// mix a few uniforms → roughly-normal logit centered near 0
logits[i] = (a + b + c - 1.5) * 3.2;
}
return softmax(logits);
}
function marginals6x4(probs) {
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]];
for (let i = 0; i < 4096; i++) {
const p = probs[i];
for (let pos = 0; pos < 6; pos++) {
m[pos][baseAt(i, pos)] += p;
}
}
return m;
}
function fmtPct(p) { return (p * 100).toFixed(2) + '%'; }
function fmt(p, d=4) { return Number(p).toFixed(d); }
// Render a row of per-base bars (used for variants of W4/W6/W7)
function renderPosCol(parent, posIndex, dist4, mode) {
const max = Math.max(0.0001, ...dist4);
let argmax = 0;
for (let i = 1; i < 4; i++) if (dist4[i] > dist4[argmax]) argmax = i;
const col = document.createElement('div');
col.className = 'posgrid__col' + (mode === 'argmax' ? ' argmax-' + BASES[argmax] : '');
col.innerHTML =
`<div class="posgrid__pos">pos ${posIndex + 1}</div>` +
`<div class="posgrid__bars">` +
BASES.map((b, j) => {
const w = (dist4[j] / max) * 100;
const isMax = j === argmax;
return `<div class="mini-row">
<span class="ml">${b}</span>
<span class="mt"><span class="mf" style="width:${w.toFixed(1)}%;background:${BASE_COLOR[b]};opacity:${isMax ? 1 : 0.55}"></span></span>
<span class="mv">${(dist4[j] * 100).toFixed(1)}</span>
</div>`;
}).join('') +
`</div>`;
parent.appendChild(col);
}
function basesHTML(seq) {
return Array.from(seq).map((c) => `<span class="base ${c}">${c}</span>`).join('');
}
// ============================================================
// W2 · BPE prefix ambiguity
// ============================================================
(function setupW1() {
// toy BPE vocab: longer entries first for greedy match
const BPE_VOCAB = [
'ACGGATCATGCTA', 'ATGCATGCA', 'CGATCGGGCCTT', 'AAGCTAGCTA', 'CGATCGTAG',
'ACGGATCA', 'ATGCATGC', 'CGATCGGG', 'GGCCTTAA', 'AGCTAGCT', 'CGTAGCAT',
'ACGGAT', 'GATCAT', 'ATGCTA', 'CATGCA', 'CGATCG', 'GGCCTT', 'AAGCTA', 'CTAGCT', 'CGTAGC',
'ACGTA', 'TATAG', 'ATCGA', 'GATCA', 'TAGCT', 'AGCTA', 'CGATC', 'TGCTA',
'ATCG', 'GATC', 'TATA', 'AGCT', 'GCTA', 'CTAG', 'ACGT', 'TGCA',
'AT', 'CG', 'GC', 'TA', 'AA', 'TT', 'CC', 'GG', 'AC', 'GT', 'CT', 'GA',
'A', 'C', 'G', 'T',
];
function bpeTokens(seq) {
const out = [];
let i = 0;
while (i < seq.length) {
let hit = null;
for (const v of BPE_VOCAB) if (seq.startsWith(v, i)) { hit = v; break; }
out.push(hit || seq[i]);
i += (hit ? hit.length : 1);
}
return out;
}
// tokensFor: returns [{ text, startPos, endPos, tail }]
function tokensBase(seq) {
return Array.from(seq).map((c, i) => ({ text: c, startPos: i, endPos: i + 1, tail: false }));
}
function tokensBPEArr(seq) {
const out = []; let p = 0;
for (const t of bpeTokens(seq)) {
out.push({ text: t, startPos: p, endPos: p + t.length, tail: false });
p += t.length;
}
return out;
}
function tokensKmer(seq, k = 6) {
const out = []; let p = 0;
while (p + k <= seq.length) {
out.push({ text: seq.slice(p, p + k), startPos: p, endPos: p + k, tail: false });
p += k;
}
if (p < seq.length) {
out.push({ text: seq.slice(p), startPos: p, endPos: seq.length, tail: true });
}
return out;
}
const SCHEMES = [
{ id: 'base', el: document.getElementById('w1-base'), tokens: tokensBase, countEl: document.getElementById('w1-base-count'), ratioEl: document.getElementById('w1-base-ratio') },
{ id: 'bpe', el: document.getElementById('w1-bpe'), tokens: tokensBPEArr, countEl: document.getElementById('w1-bpe-count'), ratioEl: document.getElementById('w1-bpe-ratio') },
{ id: 'kmer', el: document.getElementById('w1-kmer'), tokens: tokensKmer, countEl: document.getElementById('w1-kmer-count'), ratioEl: document.getElementById('w1-kmer-ratio') },
];
const seqInput = document.getElementById('w1-seq');
const lenEl = document.getElementById('w1-len');
const bpEl = document.getElementById('w1-bp');
const sourceEl = document.getElementById('w1-source');
const playBtn = document.getElementById('w1-play');
let rafId = null;
function colored(s) {
return Array.from(s).map((c) => 'ACGT'.includes(c)
? '<span class="b-' + c + '">' + c + '</span>'
: c).join('');
}
function tokenHTML(text) {
return '<span class="br">&lt;</span>' + colored(text) + '<span class="br">&gt;</span>';
}
function ratio(seqLen, n) {
if (!n) return '−';
const r = seqLen / n;
return (r >= 10 ? r.toFixed(0) : r.toFixed(1));
}
function countLabel(sc, seqLen, tks) {
return (sc.id === 'kmer' && seqLen % 6 !== 0)
? Math.floor(seqLen / 6) + '+1'
: String(tks.length);
}
// Build the source strip; render each scheme's tokens statically (resting
// state) and set the final counts. play() then clears + re-animates.
function rebuild() {
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
const seq = cleanDNA(seqInput.value);
lenEl.textContent = seq.length + ' bp';
bpEl.textContent = seq.length;
sourceEl.innerHTML = '';
for (let i = 0; i < seq.length; i++) {
const sp = document.createElement('span');
sp.className = 'tk-base ' + seq[i];
sp.dataset.pos = i;
sp.textContent = seq[i];
sourceEl.appendChild(sp);
}
for (const sc of SCHEMES) {
sc.el.innerHTML = '';
const tks = sc.tokens(seq);
for (const tk of tks) {
const el = document.createElement('span');
el.className = 'tk-token dropped' + (tk.tail ? ' tail' : '');
el.innerHTML = tokenHTML(tk.text);
sc.el.appendChild(el);
}
sc.countEl.textContent = countLabel(sc, seq.length, tks);
sc.ratioEl.textContent = ratio(seq.length, tks.length);
}
}
// FLIP-style emit: place the token at its final spot in the scheme strip,
// then apply a transform that moves it back over the source chips and
// transition that transform to identity so it visually flies into place.
function emitToken(scheme, tk, sourceChips) {
const el = document.createElement('span');
el.className = 'tk-token' + (tk.tail ? ' tail' : '');
el.innerHTML = tokenHTML(tk.text);
scheme.el.appendChild(el);
// Measure destination (the natural position after appending)
const dRect = el.getBoundingClientRect();
const destCx = (dRect.left + dRect.right) / 2;
const destCy = (dRect.top + dRect.bottom) / 2;
// Measure source span (bounding box of contributing source chips)
const first = sourceChips[tk.startPos];
const last = sourceChips[tk.endPos - 1];
if (!first || !last) { el.classList.add('dropped'); return; }
const fR = first.getBoundingClientRect();
const lR = last.getBoundingClientRect();
const srcCx = (Math.min(fR.left, lR.left) + Math.max(fR.right, lR.right)) / 2;
const srcCy = (Math.min(fR.top, lR.top) + Math.max(fR.bottom, lR.bottom)) / 2;
const dx = srcCx - destCx;
const dy = srcCy - destCy;
// No transition for the snap-back; then add .dropped on next frame
// so the transition kicks in for the journey back to identity.
el.style.transform = 'translate(' + dx + 'px, ' + dy + 'px) scale(0.92)';
requestAnimationFrame(() => {
el.classList.add('dropped');
el.style.transform = '';
});
}
function play() {
if (rafId) cancelAnimationFrame(rafId);
const seq = cleanDNA(seqInput.value);
if (!seq) return;
// Reset: clear scheme strips, zero the counters, un-consume the source.
for (const sc of SCHEMES) {
sc.el.innerHTML = '';
sc.countEl.textContent = '0';
sc.ratioEl.textContent = '—';
}
const sourceChips = Array.from(sourceEl.querySelectorAll('.tk-base'));
sourceChips.forEach((c) => c.classList.remove('consumed'));
// Pre-compute the token list for each scheme
const schemeTokens = {};
for (const sc of SCHEMES) schemeTokens[sc.id] = sc.tokens(seq);
const emitted = { base: 0, bpe: 0, kmer: 0 };
const emittedBp = { base: 0, bpe: 0, kmer: 0 };
const duration = Math.max(1800, seq.length * 95);
const start = performance.now();
function step(now) {
const t = Math.min(1, (now - start) / duration);
const cursor = t * seq.length;
// Dim source chips up to cursor
for (let i = 0; i < sourceChips.length; i++) {
if (i < cursor && !sourceChips[i].classList.contains('consumed')) {
sourceChips[i].classList.add('consumed');
}
}
// Emit any token whose end position has been reached, counting in real time
for (const sc of SCHEMES) {
const tks = schemeTokens[sc.id];
while (emitted[sc.id] < tks.length && tks[emitted[sc.id]].endPos <= cursor) {
const tk = tks[emitted[sc.id]];
emitToken(sc, tk, sourceChips);
emitted[sc.id]++;
emittedBp[sc.id] = tk.endPos;
sc.countEl.textContent = String(emitted[sc.id]);
sc.ratioEl.textContent = ratio(emittedBp[sc.id], emitted[sc.id]);
}
}
if (t < 1) {
rafId = requestAnimationFrame(step);
} else {
// Settle on the exact final figures
for (const sc of SCHEMES) {
const tks = schemeTokens[sc.id];
sc.countEl.textContent = countLabel(sc, seq.length, tks);
sc.ratioEl.textContent = ratio(seq.length, tks.length);
}
rafId = null;
}
}
rafId = requestAnimationFrame(step);
}
seqInput.addEventListener('input', rebuild);
playBtn.addEventListener('click', play);
rebuild();
// Auto-play once when the widget scrolls into view
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
if (e.isIntersecting) { io.disconnect(); setTimeout(play, 300); break; }
}
}, { threshold: 0.3 });
io.observe(document.getElementById('w1'));
} else {
setTimeout(play, 600);
}
})();
</script>
</body>
</html>