1656 lines
63 KiB
HTML
1656 lines
63 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>SCO — Streaming-Compatible Compressed Output · Eyes Only · Neuron Technologies</title>
|
||
<style>
|
||
/* ─── Reset & Base ──────────────────────────────────────────── */
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
:root {
|
||
--bg: #FAFAF8;
|
||
--navy: #0052A0;
|
||
--navy-dim: rgba(0,82,160,.35);
|
||
--ink: #1A1A2E;
|
||
--muted: #6B6B7E;
|
||
--faint: rgba(13,13,20,.35);
|
||
--card: #FFFFFF;
|
||
--section: #F0F0EC;
|
||
--border: rgba(0,82,160,.20);
|
||
--green: #1A6B3A;
|
||
--red: #8B1A1A;
|
||
--amber: #7A5A00;
|
||
--font-head: 'Georgia', serif;
|
||
--font-body: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||
}
|
||
|
||
html { scroll-behavior: smooth; }
|
||
|
||
body {
|
||
font-family: var(--font-body);
|
||
background: var(--bg);
|
||
color: var(--ink);
|
||
font-size: 15px;
|
||
line-height: 1.65;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
/* ─── Layout ────────────────────────────────────────────────── */
|
||
.container { max-width: 1040px; margin: 0 auto; padding: 0 32px; }
|
||
section { padding: 72px 0; }
|
||
section + section { border-top: 1px solid var(--border); }
|
||
|
||
h1, h2, h3, h4 { font-family: var(--font-head); color: var(--ink); font-weight: normal; }
|
||
h1 { font-size: clamp(2rem, 5vw, 3.2rem); line-height: 1.15; letter-spacing: -0.02em; }
|
||
h2 { font-size: clamp(1.4rem, 3vw, 2rem); letter-spacing: -0.01em; margin-bottom: 8px; }
|
||
h3 { font-size: 1.1rem; letter-spacing: 0.01em; color: var(--navy); text-transform: uppercase; font-family: var(--font-body); font-weight: 600; margin-bottom: 4px; }
|
||
|
||
p { color: var(--ink); margin-bottom: 1em; }
|
||
p:last-child { margin-bottom: 0; }
|
||
|
||
.label {
|
||
font-family: var(--font-body);
|
||
font-size: 0.72rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: var(--navy);
|
||
margin-bottom: 12px;
|
||
display: block;
|
||
}
|
||
|
||
/* ─── Nav ───────────────────────────────────────────────────── */
|
||
nav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
background: rgba(250,250,248,.94);
|
||
backdrop-filter: blur(8px);
|
||
border-bottom: 1px solid var(--border);
|
||
padding: 0 32px;
|
||
}
|
||
nav .nav-inner {
|
||
max-width: 1040px;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 52px;
|
||
}
|
||
.nav-brand {
|
||
font-family: var(--font-body);
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
color: var(--navy);
|
||
text-decoration: none;
|
||
}
|
||
.nav-links {
|
||
display: flex;
|
||
gap: 28px;
|
||
list-style: none;
|
||
}
|
||
.nav-links a {
|
||
font-size: 0.78rem;
|
||
color: var(--muted);
|
||
text-decoration: none;
|
||
letter-spacing: 0.04em;
|
||
transition: color 0.15s;
|
||
}
|
||
.nav-links a:hover { color: var(--navy); }
|
||
|
||
/* ─── Hero ──────────────────────────────────────────────────── */
|
||
#hero {
|
||
padding: 96px 0 80px;
|
||
background: var(--bg);
|
||
}
|
||
.hero-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 340px;
|
||
gap: 64px;
|
||
align-items: center;
|
||
}
|
||
.hero-sub {
|
||
font-size: 1.15rem;
|
||
color: var(--muted);
|
||
max-width: 520px;
|
||
margin-top: 20px;
|
||
line-height: 1.7;
|
||
}
|
||
.hero-tagline {
|
||
font-family: var(--font-head);
|
||
font-size: 1.05rem;
|
||
color: var(--navy);
|
||
font-style: italic;
|
||
margin-top: 28px;
|
||
padding-left: 16px;
|
||
border-left: 2px solid var(--navy-dim);
|
||
}
|
||
|
||
.hero-stat-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
padding: 36px 32px;
|
||
text-align: center;
|
||
}
|
||
.stat-label {
|
||
font-size: 0.72rem;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
margin-bottom: 12px;
|
||
font-weight: 600;
|
||
}
|
||
.stat-number {
|
||
font-family: var(--font-head);
|
||
font-size: 4rem;
|
||
color: var(--navy);
|
||
line-height: 1;
|
||
letter-spacing: -0.03em;
|
||
font-weight: normal;
|
||
display: block;
|
||
}
|
||
.stat-unit {
|
||
font-size: 0.85rem;
|
||
color: var(--muted);
|
||
margin-top: 8px;
|
||
display: block;
|
||
}
|
||
.stat-bar-track {
|
||
height: 4px;
|
||
background: var(--section);
|
||
margin-top: 20px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.stat-bar-fill {
|
||
height: 100%;
|
||
background: var(--navy);
|
||
width: 100%;
|
||
transition: width 2s cubic-bezier(.22,.68,0,1.2);
|
||
}
|
||
|
||
/* ─── Streaming Demo ─────────────────────────────────────────── */
|
||
#demo { background: var(--section); }
|
||
.demo-intro { max-width: 620px; margin-bottom: 40px; }
|
||
|
||
.demo-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 24px;
|
||
align-items: center;
|
||
}
|
||
.btn {
|
||
font-family: var(--font-body);
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
border: 1px solid var(--navy);
|
||
background: transparent;
|
||
color: var(--navy);
|
||
padding: 8px 20px;
|
||
cursor: pointer;
|
||
transition: background 0.15s, color 0.15s;
|
||
}
|
||
.btn:hover, .btn.active { background: var(--navy); color: #fff; }
|
||
.btn.primary { background: var(--navy); color: #fff; }
|
||
.btn.primary:hover { background: #003d78; }
|
||
.btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
||
|
||
.ratio-badge {
|
||
margin-left: auto;
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
color: var(--navy);
|
||
padding: 6px 14px;
|
||
border: 1px solid var(--navy-dim);
|
||
background: var(--card);
|
||
}
|
||
|
||
.demo-panels {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1px;
|
||
background: var(--border);
|
||
border: 1px solid var(--border);
|
||
}
|
||
.demo-panel {
|
||
background: var(--card);
|
||
padding: 0;
|
||
}
|
||
.panel-header {
|
||
padding: 14px 20px;
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: var(--section);
|
||
}
|
||
.panel-title {
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
}
|
||
.token-counter {
|
||
font-size: 0.72rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
color: var(--navy);
|
||
}
|
||
.panel-body {
|
||
padding: 20px;
|
||
min-height: 200px;
|
||
font-family: 'Georgia', serif;
|
||
font-size: 0.88rem;
|
||
line-height: 1.75;
|
||
color: var(--ink);
|
||
}
|
||
|
||
/* Token highlighting */
|
||
.tok-schema { color: var(--navy); font-weight: 600; }
|
||
.tok-code { color: #1A6B3A; font-weight: 700; background: rgba(26,107,58,.08); padding: 1px 3px; }
|
||
.tok-label-def { color: #7A3A00; background: rgba(122,90,0,.08); padding: 1px 3px; }
|
||
.tok-delta { color: #4A1A6B; font-weight: 700; background: rgba(74,26,107,.08); padding: 1px 3px; }
|
||
.tok-new { animation: tokenIn 0.12s ease-out; }
|
||
.expanded-label { color: #7A3A00; font-style: italic; }
|
||
.expanded-delta { color: #4A1A6B; font-style: italic; }
|
||
.expanded-code { color: #1A6B3A; }
|
||
|
||
.sco-init-badge {
|
||
display: inline-block;
|
||
font-size: 0.68rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
background: var(--navy);
|
||
color: #fff;
|
||
padding: 2px 8px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
@keyframes tokenIn {
|
||
from { opacity: 0; transform: translateY(2px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* ─── Four Layers ────────────────────────────────────────────── */
|
||
#layers { background: var(--bg); }
|
||
.layers-intro { max-width: 600px; margin-bottom: 40px; }
|
||
|
||
.tab-bar {
|
||
display: flex;
|
||
border-bottom: 1px solid var(--border);
|
||
margin-bottom: 32px;
|
||
gap: 0;
|
||
}
|
||
.tab-btn {
|
||
font-family: var(--font-body);
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--muted);
|
||
padding: 12px 20px;
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -1px;
|
||
transition: color 0.15s, border-color 0.15s;
|
||
}
|
||
.tab-btn:hover { color: var(--navy); }
|
||
.tab-btn.active { color: var(--navy); border-bottom-color: var(--navy); }
|
||
|
||
.tab-panel { display: none; }
|
||
.tab-panel.active { display: block; }
|
||
|
||
.layer-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 32px;
|
||
align-items: start;
|
||
}
|
||
.layer-info h2 { margin-bottom: 12px; }
|
||
.layer-desc { color: var(--muted); font-size: 0.92rem; line-height: 1.7; margin-bottom: 16px; }
|
||
|
||
.gain-bar-wrap { margin-top: 16px; }
|
||
.gain-label {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 0.72rem;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
font-weight: 600;
|
||
margin-bottom: 6px;
|
||
}
|
||
.gain-track {
|
||
height: 8px;
|
||
background: var(--section);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.gain-fill {
|
||
height: 100%;
|
||
background: var(--navy);
|
||
width: 0;
|
||
transition: width 0.8s cubic-bezier(.22,.68,0,1.2);
|
||
}
|
||
|
||
.layer-demo-box {
|
||
background: var(--section);
|
||
border: 1px solid var(--border);
|
||
padding: 20px;
|
||
}
|
||
.layer-demo-header {
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
margin-bottom: 14px;
|
||
}
|
||
.demo-run-btn {
|
||
font-family: var(--font-body);
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
border: 1px solid var(--navy-dim);
|
||
background: transparent;
|
||
color: var(--navy);
|
||
padding: 5px 14px;
|
||
cursor: pointer;
|
||
margin-bottom: 14px;
|
||
transition: background 0.15s, color 0.15s;
|
||
}
|
||
.demo-run-btn:hover { background: var(--navy); color: #fff; }
|
||
|
||
.before-after {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
}
|
||
.ba-box {
|
||
padding: 10px 12px;
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
}
|
||
.ba-label {
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
margin-bottom: 6px;
|
||
}
|
||
.ba-content {
|
||
font-family: monospace;
|
||
font-size: 0.8rem;
|
||
color: var(--ink);
|
||
line-height: 1.5;
|
||
}
|
||
.ba-highlight { color: var(--navy); font-weight: 700; }
|
||
.codebook-word { cursor: pointer; text-decoration: underline dotted var(--navy-dim); }
|
||
.codebook-word:hover .code-tip { display: inline; }
|
||
.code-tip {
|
||
display: none;
|
||
font-size: 0.7rem;
|
||
background: var(--navy);
|
||
color: #fff;
|
||
padding: 1px 5px;
|
||
margin-left: 3px;
|
||
font-family: monospace;
|
||
vertical-align: super;
|
||
}
|
||
|
||
.animate-out {
|
||
color: var(--muted);
|
||
}
|
||
.animate-in {
|
||
color: var(--green);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ─── Tokenization Trap ──────────────────────────────────────── */
|
||
#tokenizer { background: var(--section); }
|
||
.tok-intro { max-width: 580px; margin-bottom: 40px; }
|
||
|
||
.token-inspector {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
max-width: 620px;
|
||
}
|
||
.ti-header {
|
||
padding: 14px 20px;
|
||
border-bottom: 1px solid var(--border);
|
||
background: var(--section);
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
}
|
||
.ti-rows { padding: 8px 0; }
|
||
.ti-row {
|
||
display: grid;
|
||
grid-template-columns: 120px 140px 1fr;
|
||
align-items: center;
|
||
padding: 10px 20px;
|
||
border-bottom: 1px solid rgba(0,82,160,.06);
|
||
gap: 20px;
|
||
}
|
||
.ti-row:last-child { border-bottom: none; }
|
||
.ti-candidate {
|
||
font-family: monospace;
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--ink);
|
||
}
|
||
.ti-token-count {
|
||
display: flex;
|
||
gap: 4px;
|
||
align-items: center;
|
||
}
|
||
.tok-box {
|
||
display: inline-block;
|
||
width: 18px;
|
||
height: 18px;
|
||
border: 2px solid;
|
||
font-size: 0.6rem;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
line-height: 14px;
|
||
}
|
||
.tok-box.filled { background: var(--navy); border-color: var(--navy); color: #fff; }
|
||
.tok-box.bad { background: var(--red); border-color: var(--red); color: #fff; }
|
||
.tok-box.good { background: var(--green); border-color: var(--green); color: #fff; }
|
||
.ti-verdict {
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
}
|
||
.verdict-bad { color: var(--red); }
|
||
.verdict-good { color: var(--green); }
|
||
.verdict-note { font-weight: normal; color: var(--muted); font-size: 0.75rem; }
|
||
|
||
/* ─── State Machine ──────────────────────────────────────────── */
|
||
#statemachine { background: var(--bg); }
|
||
.sm-intro { max-width: 580px; margin-bottom: 40px; }
|
||
|
||
.sm-canvas-wrap {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
padding: 32px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.sm-current-token {
|
||
font-family: monospace;
|
||
font-size: 0.85rem;
|
||
background: var(--section);
|
||
border: 1px solid var(--border);
|
||
padding: 8px 16px;
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
.sm-tok-label {
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
}
|
||
.sm-tok-val {
|
||
font-family: monospace;
|
||
font-weight: 700;
|
||
color: var(--navy);
|
||
min-width: 60px;
|
||
}
|
||
.sm-tok-action {
|
||
font-size: 0.78rem;
|
||
color: var(--muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
.sm-states {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
.sm-state {
|
||
padding: 12px 18px;
|
||
border: 1.5px solid var(--border);
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
transition: background 0.2s, color 0.2s, border-color 0.2s;
|
||
min-width: 110px;
|
||
text-align: center;
|
||
background: var(--section);
|
||
}
|
||
.sm-state.active {
|
||
background: var(--navy);
|
||
color: #fff;
|
||
border-color: var(--navy);
|
||
box-shadow: 0 2px 12px rgba(0,82,160,.25);
|
||
}
|
||
.sm-state.burst {
|
||
border-style: dashed;
|
||
border-color: #4A1A6B;
|
||
color: #4A1A6B;
|
||
background: rgba(74,26,107,.06);
|
||
}
|
||
.sm-state.active.burst {
|
||
background: #4A1A6B;
|
||
border-color: #4A1A6B;
|
||
color: #fff;
|
||
border-style: solid;
|
||
}
|
||
|
||
.sm-arrow {
|
||
color: var(--faint);
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.sm-log {
|
||
font-family: monospace;
|
||
font-size: 0.78rem;
|
||
color: var(--muted);
|
||
background: var(--section);
|
||
border: 1px solid var(--border);
|
||
padding: 12px 16px;
|
||
max-height: 120px;
|
||
overflow-y: auto;
|
||
line-height: 1.6;
|
||
}
|
||
.sm-log-entry { color: var(--ink); }
|
||
.sm-log-entry span { color: var(--navy); font-weight: 700; }
|
||
|
||
/* ─── Compression Numbers ────────────────────────────────────── */
|
||
#numbers { background: var(--section); }
|
||
.numbers-intro { max-width: 600px; margin-bottom: 40px; }
|
||
|
||
.chart-wrap {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
padding: 28px;
|
||
}
|
||
.chart-legend {
|
||
display: flex;
|
||
gap: 24px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
}
|
||
.legend-swatch {
|
||
width: 14px;
|
||
height: 14px;
|
||
}
|
||
.legend-swatch.prompt { background: var(--navy); }
|
||
.legend-swatch.tuned { background: rgba(0,82,160,.35); }
|
||
|
||
.chart-row {
|
||
margin-bottom: 20px;
|
||
}
|
||
.chart-row-label {
|
||
font-size: 0.78rem;
|
||
color: var(--ink);
|
||
margin-bottom: 6px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: baseline;
|
||
}
|
||
.chart-row-pct {
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
}
|
||
.chart-bars { display: flex; flex-direction: column; gap: 3px; }
|
||
.chart-bar-track {
|
||
height: 16px;
|
||
background: var(--section);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.chart-bar-fill {
|
||
height: 100%;
|
||
width: 0;
|
||
transition: width 1s cubic-bezier(.22,.68,0,1.2);
|
||
position: relative;
|
||
}
|
||
.chart-bar-fill.prompt-bar { background: var(--navy); }
|
||
.chart-bar-fill.tuned-bar { background: rgba(0,82,160,.40); }
|
||
.chart-bar-tip {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
padding: 0 6px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.hover-detail {
|
||
margin-top: 8px;
|
||
font-size: 0.75rem;
|
||
color: var(--muted);
|
||
display: none;
|
||
padding: 8px 12px;
|
||
background: var(--section);
|
||
border-left: 2px solid var(--navy-dim);
|
||
}
|
||
.chart-row:hover .hover-detail { display: block; }
|
||
|
||
/* ─── Dual-Purpose Delta ─────────────────────────────────────── */
|
||
#delta { background: var(--bg); }
|
||
.delta-intro { max-width: 600px; margin-bottom: 40px; }
|
||
|
||
.delta-diagram {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
padding: 32px;
|
||
position: relative;
|
||
}
|
||
.delta-center {
|
||
text-align: center;
|
||
margin-bottom: 32px;
|
||
}
|
||
.delta-node {
|
||
display: inline-block;
|
||
padding: 10px 22px;
|
||
border: 1.5px solid var(--navy);
|
||
background: var(--navy);
|
||
color: #fff;
|
||
font-family: monospace;
|
||
font-weight: 700;
|
||
font-size: 0.9rem;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.delta-paths {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 32px;
|
||
}
|
||
.delta-path {
|
||
padding: 20px;
|
||
border: 1px solid var(--border);
|
||
background: var(--section);
|
||
cursor: pointer;
|
||
transition: border-color 0.2s, background 0.2s;
|
||
}
|
||
.delta-path:hover, .delta-path.highlighted {
|
||
border-color: var(--navy);
|
||
background: rgba(0,82,160,.04);
|
||
}
|
||
.delta-path-title {
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--navy);
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.path-num {
|
||
width: 20px;
|
||
height: 20px;
|
||
background: var(--navy);
|
||
color: #fff;
|
||
font-size: 0.65rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 700;
|
||
}
|
||
.delta-step {
|
||
font-size: 0.8rem;
|
||
color: var(--muted);
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid rgba(0,82,160,.08);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
transition: color 0.3s;
|
||
}
|
||
.delta-step:last-child { border-bottom: none; }
|
||
.delta-step.lit { color: var(--navy); font-weight: 600; }
|
||
.step-arrow { color: var(--faint); font-size: 0.7rem; flex-shrink: 0; }
|
||
|
||
/* ─── Footer ─────────────────────────────────────────────────── */
|
||
footer {
|
||
padding: 40px 0;
|
||
border-top: 1px solid var(--border);
|
||
background: var(--bg);
|
||
}
|
||
.footer-inner {
|
||
max-width: 1040px;
|
||
margin: 0 auto;
|
||
padding: 0 32px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.footer-copy {
|
||
font-size: 0.75rem;
|
||
color: var(--faint);
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.footer-tag {
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: var(--navy);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* ─── Scrolled-in animations ─────────────────────────────────── */
|
||
.reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.5s, transform 0.5s; }
|
||
.reveal.visible { opacity: 1; transform: none; }
|
||
|
||
/* ─── Responsive ─────────────────────────────────────────────── */
|
||
@media (max-width: 768px) {
|
||
.hero-grid { grid-template-columns: 1fr; gap: 40px; }
|
||
.demo-panels { grid-template-columns: 1fr; }
|
||
.layer-grid { grid-template-columns: 1fr; }
|
||
.delta-paths { grid-template-columns: 1fr; }
|
||
.sm-states { gap: 4px; }
|
||
.sm-state { min-width: 80px; font-size: 0.65rem; padding: 10px 10px; }
|
||
nav .nav-links { display: none; }
|
||
.container { padding: 0 20px; }
|
||
section { padding: 48px 0; }
|
||
.ti-row { grid-template-columns: 80px 120px 1fr; gap: 12px; }
|
||
.before-after { grid-template-columns: 1fr; }
|
||
}
|
||
.nav-badge{font-family:monospace;font-size:.54rem;letter-spacing:.14em;text-transform:uppercase;
|
||
background:rgba(26,127,75,.06);border:1px solid rgba(26,127,75,.22);color:#1A7F4B;padding:3px 10px;border-radius:99px;margin-left:8px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ─── Navigation ──────────────────────────────────────────── -->
|
||
<nav>
|
||
<div class="nav-inner">
|
||
<a class="nav-brand" href="#hero">SCO Protocol</a>
|
||
<ul class="nav-links">
|
||
<li><a href="#demo">Live Demo</a></li>
|
||
<li><a href="#layers">Four Layers</a></li>
|
||
<li><a href="#tokenizer">Token Trap</a></li>
|
||
<li><a href="#statemachine">State Machine</a></li>
|
||
<li><a href="#numbers">Numbers</a></li>
|
||
<li><a href="#delta">Delta</a></li>
|
||
</ul>
|
||
</div>
|
||
<span class="nav-badge">Eyes Only · Internal</span>
|
||
</nav>
|
||
|
||
<!-- ─── Hero ─────────────────────────────────────────────────── -->
|
||
<section id="hero">
|
||
<div class="container">
|
||
<div class="hero-grid">
|
||
<div>
|
||
<span class="label">Streaming-Compatible Compressed Output</span>
|
||
<h1>The model<br>is the encoder.</h1>
|
||
<p class="hero-sub">SCO is a session-level compression protocol that directs the inference model itself to emit compact encoded output. The client decompresses in real-time as tokens arrive, without any modification to inference infrastructure.</p>
|
||
<p class="hero-tagline">"65–80% output token reduction. Zero latency overhead. Fully backward-compatible."</p>
|
||
</div>
|
||
<div>
|
||
<div class="hero-stat-card">
|
||
<div class="stat-label">Output token reduction</div>
|
||
<span class="stat-number" id="heroCounter">100</span>
|
||
<span class="stat-unit">tokens emitted (vs. baseline)</span>
|
||
<div class="stat-bar-track">
|
||
<div class="stat-bar-fill" id="heroBar"></div>
|
||
</div>
|
||
<div style="margin-top:16px; font-size: 0.75rem; color: var(--muted); line-height: 1.6;">
|
||
Animating to ~25 tokens — equivalent output, 4× fewer tokens billed.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── Live Streaming Demo ───────────────────────────────────── -->
|
||
<section id="demo">
|
||
<div class="container">
|
||
<div class="demo-intro reveal">
|
||
<span class="label">Centerpiece</span>
|
||
<h2>Live Streaming Demo</h2>
|
||
<p style="color: var(--muted);">Watch the compressed token stream arrive on the left and the decompressed output materialize on the right. The compression ratio updates in real time as each token is processed.</p>
|
||
</div>
|
||
|
||
<div class="demo-controls reveal">
|
||
<button class="btn primary" id="demoPlay" onclick="demoPlay()">Play</button>
|
||
<button class="btn" id="demoPause" onclick="demoPause()" disabled>Pause</button>
|
||
<button class="btn" id="demoRestart" onclick="demoRestart()">Restart</button>
|
||
<div class="ratio-badge" id="ratioBadge">Ratio: —</div>
|
||
</div>
|
||
|
||
<div class="demo-panels reveal">
|
||
<div class="demo-panel">
|
||
<div class="panel-header">
|
||
<span class="panel-title">Encoded stream — what the model generates</span>
|
||
<span class="token-counter">Tokens: <span id="leftCount">0</span></span>
|
||
</div>
|
||
<div class="panel-body" id="leftPanel"></div>
|
||
</div>
|
||
<div class="demo-panel">
|
||
<div class="panel-header">
|
||
<span class="panel-title">Expanded output — what you see</span>
|
||
<span class="token-counter">Equivalent tokens: <span id="rightCount">0</span></span>
|
||
</div>
|
||
<div class="panel-body" id="rightPanel"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── Four Layers ───────────────────────────────────────────── -->
|
||
<section id="layers">
|
||
<div class="container">
|
||
<div class="layers-intro reveal">
|
||
<span class="label">Architecture</span>
|
||
<h2>The Four Compression Layers</h2>
|
||
<p style="color: var(--muted);">SCO stacks four independent compression techniques. Each layer compounds the gains of the prior layers. Used together, they achieve 65–80% token reduction using only prompt engineering.</p>
|
||
</div>
|
||
|
||
<div class="tab-bar reveal">
|
||
<button class="tab-btn active" onclick="showTab(0)">Layer 0 — SFOP</button>
|
||
<button class="tab-btn" onclick="showTab(1)">Layer 1 — Codebook</button>
|
||
<button class="tab-btn" onclick="showTab(2)">Layer 2 — Labels</button>
|
||
<button class="tab-btn" onclick="showTab(3)">Layer 3 — Deltas</button>
|
||
</div>
|
||
|
||
<!-- Layer 0 -->
|
||
<div class="tab-panel active" id="tab-0">
|
||
<div class="layer-grid">
|
||
<div class="layer-info">
|
||
<span class="label">Layer 0</span>
|
||
<h2>Schema-First Output Protocol</h2>
|
||
<p class="layer-desc">Instead of prose, the model emits pipe-delimited schema fields. <code>ACTION:called_api|RESULT:success_200|NEXT:validate</code> is fully parseable and expands to a readable sentence at zero streaming overhead. The schema is negotiated in the <code>sco-init</code> handshake.</p>
|
||
<div class="gain-bar-wrap">
|
||
<div class="gain-label"><span>Token gain</span><span id="gain0">0%</span></div>
|
||
<div class="gain-track"><div class="gain-fill" id="gainBar0" style="background: var(--navy);"></div></div>
|
||
<div style="margin-top: 6px; font-size: 0.72rem; color: var(--muted);">40–60% reduction</div>
|
||
</div>
|
||
</div>
|
||
<div class="layer-demo-box">
|
||
<div class="layer-demo-header">Interactive example</div>
|
||
<button class="demo-run-btn" onclick="runLayer0()">Run animation</button>
|
||
<div class="before-after">
|
||
<div class="ba-box">
|
||
<div class="ba-label">Model emits</div>
|
||
<div class="ba-content" id="l0before">ACTION:<span class="ba-highlight">validated_schema</span>|RESULT:<span class="ba-highlight">pass_3of3</span>|ISSUES:<span class="ba-highlight">none</span>|NEXT:<span class="ba-highlight">deploy_stage</span></div>
|
||
</div>
|
||
<div class="ba-box">
|
||
<div class="ba-label">Client expands</div>
|
||
<div class="ba-content" id="l0after" style="color: var(--muted); font-style: italic;">Click Run to animate</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layer 1 -->
|
||
<div class="tab-panel" id="tab-1">
|
||
<div class="layer-grid">
|
||
<div class="layer-info">
|
||
<span class="label">Layer 1</span>
|
||
<h2>Codebook Substitution</h2>
|
||
<p class="layer-desc">A pre-shared codebook maps single-token codes to common phrases. <code>[fn]</code> → <code>function</code>, <code>[ret]</code> → <code>returns</code>. Critically, each code must be verified as a <em>single token</em> in the target tokenizer — Unicode symbols silently fail this requirement.</p>
|
||
<div class="gain-bar-wrap">
|
||
<div class="gain-label"><span>Token gain</span><span id="gain1">0%</span></div>
|
||
<div class="gain-track"><div class="gain-fill" id="gainBar1"></div></div>
|
||
<div style="margin-top: 6px; font-size: 0.72rem; color: var(--muted);">20–35% reduction</div>
|
||
</div>
|
||
</div>
|
||
<div class="layer-demo-box">
|
||
<div class="layer-demo-header">Hover words to reveal codes</div>
|
||
<div class="ba-box" style="margin-bottom:12px;">
|
||
<div class="ba-label">Encoded</div>
|
||
<div class="ba-content">The <span class="tok-code">[fn]</span> validate(<span class="tok-code">[ret]</span> cfg) uses <span class="tok-code">[§ARCH]</span> lookup.</div>
|
||
</div>
|
||
<div class="ba-box">
|
||
<div class="ba-label">Expanded (hover to inspect)</div>
|
||
<div class="ba-content">The <span class="codebook-word">function<span class="code-tip">[fn]</span></span> validate(<span class="codebook-word">returns<span class="code-tip">[ret]</span></span> <span class="codebook-word">configuration<span class="code-tip">[cfg]</span></span>) uses <span class="codebook-word">three-tier cache architecture<span class="code-tip">[§ARCH]</span></span> lookup.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layer 2 -->
|
||
<div class="tab-panel" id="tab-2">
|
||
<div class="layer-grid">
|
||
<div class="layer-info">
|
||
<span class="label">Layer 2</span>
|
||
<h2>Semantic Labels</h2>
|
||
<p class="layer-desc">The model defines a label once using the syntax <code>↦LABEL: full text↤</code>, then references it as <code>[§LABEL]</code> thereafter. Labels are scoped per-session and accumulate across a multi-step execution. Ideal for recurring proper nouns, system names, and long noun phrases.</p>
|
||
<div class="gain-bar-wrap">
|
||
<div class="gain-label"><span>Token gain</span><span id="gain2">0%</span></div>
|
||
<div class="gain-track"><div class="gain-fill" id="gainBar2"></div></div>
|
||
<div style="margin-top: 6px; font-size: 0.72rem; color: var(--muted);">10–20% reduction</div>
|
||
</div>
|
||
</div>
|
||
<div class="layer-demo-box">
|
||
<div class="layer-demo-header">Interactive example</div>
|
||
<button class="demo-run-btn" onclick="runLayer2()">Run animation</button>
|
||
<div style="margin-bottom: 10px;">
|
||
<div class="ba-box" style="margin-bottom:6px;">
|
||
<div class="ba-label">Definition (emitted once)</div>
|
||
<div class="ba-content" id="l2def"><span class="tok-label-def">↦ARCH: three-tier cache architecture↤</span></div>
|
||
</div>
|
||
<div class="ba-box">
|
||
<div class="ba-label">Later reference</div>
|
||
<div class="ba-content">The <span class="tok-label-def">[§ARCH]</span> confirmed compatibility.<br><span id="l2expand" style="color: var(--muted); font-style: italic; font-size: 0.78rem;">↳ click Run to expand</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layer 3 -->
|
||
<div class="tab-panel" id="tab-3">
|
||
<div class="layer-grid">
|
||
<div class="layer-info">
|
||
<span class="label">Layer 3</span>
|
||
<h2>Delta References</h2>
|
||
<p class="layer-desc">The model emits <code>[Δstep_id]</code> to reference a prior step's complete output from the client's execution cache — inserting its full content without re-emitting a single token. The same reference doubles as a GC eviction back-pointer for the persistent context cache.</p>
|
||
<div class="gain-bar-wrap">
|
||
<div class="gain-label"><span>Token gain</span><span id="gain3">0%</span></div>
|
||
<div class="gain-track"><div class="gain-fill" id="gainBar3"></div></div>
|
||
<div style="margin-top: 6px; font-size: 0.72rem; color: var(--muted);">15–25% reduction</div>
|
||
</div>
|
||
</div>
|
||
<div class="layer-demo-box">
|
||
<div class="layer-demo-header">Interactive example</div>
|
||
<button class="demo-run-btn" onclick="runLayer3()">Run animation</button>
|
||
<div class="ba-box" style="margin-bottom:6px;">
|
||
<div class="ba-label">Model emits (1 token)</div>
|
||
<div class="ba-content"><span class="tok-delta">[Δstep_1]</span></div>
|
||
</div>
|
||
<div class="ba-box">
|
||
<div class="ba-label">Client expands (from cache)</div>
|
||
<div class="ba-content" id="l3expand" style="color: var(--muted); font-style: italic; font-size: 0.78rem;">click Run to burst-expand</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── Tokenization Trap ──────────────────────────────────────── -->
|
||
<section id="tokenizer">
|
||
<div class="container">
|
||
<div class="tok-intro reveal">
|
||
<span class="label">Critical Constraint</span>
|
||
<h2>The Tokenization Trap</h2>
|
||
<p style="color: var(--muted);">Not all short strings are single tokens. Codebook codes must be verified against the actual tokenizer — Unicode symbols and many punctuation sequences silently expand to multiple tokens, negating the compression gain entirely.</p>
|
||
</div>
|
||
|
||
<div class="token-inspector reveal">
|
||
<div class="ti-header">Tokenizer Inspector — BPE token analysis</div>
|
||
<div class="ti-rows">
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">Ω</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box bad">T</div>
|
||
<div class="tok-box bad">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-bad">2 tokens <span class="verdict-note">Unicode escapes split</span></div>
|
||
</div>
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">→</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box bad">T</div>
|
||
<div class="tok-box bad">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-bad">2 tokens <span class="verdict-note">Arrow chars split in BPE</span></div>
|
||
</div>
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">↦</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box bad">T</div>
|
||
<div class="tok-box bad">T</div>
|
||
<div class="tok-box bad">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-bad">3 tokens <span class="verdict-note">Multi-byte, not in BPE vocab</span></div>
|
||
</div>
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">[fn]</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box good">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-good">1 token <span class="verdict-note">Bracket-word pattern in vocab</span></div>
|
||
</div>
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">v1</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box good">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-good">1 token <span class="verdict-note">Alphanumeric short codes work</span></div>
|
||
</div>
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">ok</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box good">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-good">1 token <span class="verdict-note">Common word — in vocab</span></div>
|
||
</div>
|
||
<div class="ti-row">
|
||
<div class="ti-candidate">[ret]</div>
|
||
<div class="ti-token-count">
|
||
<div class="tok-box good">T</div>
|
||
</div>
|
||
<div class="ti-verdict verdict-good">1 token <span class="verdict-note">Short bracket codes verified safe</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 20px; max-width: 620px; font-size: 0.82rem; color: var(--muted); line-height: 1.7;" class="reveal">
|
||
<strong style="color: var(--ink);">Rule:</strong> All codebook codes are verified at codebook compile time by running each candidate through the target tokenizer and asserting <code>len(tokens) == 1</code>. Codes that fail are rejected. The verified codebook is transmitted in the <code>sco-init</code> SSE event alongside its HMAC signature.
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── State Machine ──────────────────────────────────────────── -->
|
||
<section id="statemachine">
|
||
<div class="container">
|
||
<div class="sm-intro reveal">
|
||
<span class="label">Decompressor Internals</span>
|
||
<h2>State Machine</h2>
|
||
<p style="color: var(--muted);">The client-side decompressor is a deterministic state machine. It processes the raw byte stream character by character, resolving SCO constructs as they arrive without buffering or lookahead.</p>
|
||
</div>
|
||
|
||
<div class="sm-canvas-wrap reveal">
|
||
<div class="sm-current-token" id="smCurrentTok">
|
||
<span class="sm-tok-label">Current token</span>
|
||
<span class="sm-tok-val" id="smTokVal">—</span>
|
||
<span class="sm-tok-action" id="smTokAction">Press Animate to begin</span>
|
||
</div>
|
||
|
||
<div class="sm-states">
|
||
<div class="sm-state" id="sm-NORMAL">NORMAL</div>
|
||
<div class="sm-arrow">→</div>
|
||
<div class="sm-state" id="sm-CODE_FRAME">CODE_FRAME</div>
|
||
<div class="sm-arrow">→</div>
|
||
<div class="sm-state" id="sm-LABEL_DEFINE">LABEL_DEFINE</div>
|
||
<div class="sm-arrow">→</div>
|
||
<div class="sm-state" id="sm-PASSTHROUGH">PASSTHROUGH</div>
|
||
<div class="sm-arrow">→</div>
|
||
<div class="sm-state burst" id="sm-BURST">BURST EMIT</div>
|
||
</div>
|
||
|
||
<div class="sm-log" id="smLog">
|
||
<div style="color: var(--faint); font-style: italic;">State machine log will appear here…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="demo-controls reveal">
|
||
<button class="btn primary" id="smPlayBtn" onclick="smPlay()">Animate</button>
|
||
<button class="btn" id="smResetBtn" onclick="smReset()">Reset</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── Compression Numbers ───────────────────────────────────── -->
|
||
<section id="numbers">
|
||
<div class="container">
|
||
<div class="numbers-intro reveal">
|
||
<span class="label">Empirical Results</span>
|
||
<h2>Compression by Layer</h2>
|
||
<p style="color: var(--muted);">Token counts measured on multi-step agentic execution traces. "Prompt-only" uses system-prompt directives alone. "Fine-tuned" uses a model specifically trained to emit SCO output, achieving closer to theoretical maximum.</p>
|
||
</div>
|
||
|
||
<div class="chart-wrap reveal" id="chartWrap">
|
||
<div class="chart-legend">
|
||
<div class="legend-item"><div class="legend-swatch prompt"></div>Prompt-only</div>
|
||
<div class="legend-item"><div class="legend-swatch tuned"></div>Fine-tuned</div>
|
||
</div>
|
||
|
||
<div class="chart-row">
|
||
<div class="chart-row-label"><span>No compression (baseline)</span><span class="chart-row-pct" id="pct0">100%</span></div>
|
||
<div class="chart-bars">
|
||
<div class="chart-bar-track"><div class="chart-bar-fill prompt-bar" id="bar0p" data-pct="100"><span class="chart-bar-tip">100%</span></div></div>
|
||
<div class="chart-bar-track"><div class="chart-bar-fill tuned-bar" id="bar0t" data-pct="100"><span class="chart-bar-tip">100%</span></div></div>
|
||
</div>
|
||
<div class="hover-detail">Baseline: uncompressed model output. Tokens billed at full rate. No structured output contract.</div>
|
||
</div>
|
||
|
||
<div class="chart-row">
|
||
<div class="chart-row-label"><span>Layer 0 only (SFOP)</span><span class="chart-row-pct" id="pct1">—</span></div>
|
||
<div class="chart-bars">
|
||
<div class="chart-bar-track"><div class="chart-bar-fill prompt-bar" id="bar1p" data-pct="58"><span class="chart-bar-tip">58%</span></div></div>
|
||
<div class="chart-bar-track"><div class="chart-bar-fill tuned-bar" id="bar1t" data-pct="45"><span class="chart-bar-tip">45%</span></div></div>
|
||
</div>
|
||
<div class="hover-detail">SFOP alone: schema fields eliminate conversational filler. Prompt-only achieves ~42% reduction; fine-tuned reaches ~55%. Break-even at ~200 output tokens.</div>
|
||
</div>
|
||
|
||
<div class="chart-row">
|
||
<div class="chart-row-label"><span>Layers 0 + 1 (SFOP + Codebook)</span><span class="chart-row-pct" id="pct2">—</span></div>
|
||
<div class="chart-bars">
|
||
<div class="chart-bar-track"><div class="chart-bar-fill prompt-bar" id="bar2p" data-pct="42"><span class="chart-bar-tip">42%</span></div></div>
|
||
<div class="chart-bar-track"><div class="chart-bar-fill tuned-bar" id="bar2t" data-pct="32"><span class="chart-bar-tip">32%</span></div></div>
|
||
</div>
|
||
<div class="hover-detail">Adding codebook substitution compounds: frequently-repeated domain terms compress well. Prompt-only ~58%; fine-tuned ~68%. Break-even at ~300 tokens.</div>
|
||
</div>
|
||
|
||
<div class="chart-row">
|
||
<div class="chart-row-label"><span>Layers 0 + 1 + 2 (+ Labels)</span><span class="chart-row-pct" id="pct3">—</span></div>
|
||
<div class="chart-bars">
|
||
<div class="chart-bar-track"><div class="chart-bar-fill prompt-bar" id="bar3p" data-pct="33"><span class="chart-bar-tip">33%</span></div></div>
|
||
<div class="chart-bar-track"><div class="chart-bar-fill tuned-bar" id="bar3t" data-pct="24"><span class="chart-bar-tip">24%</span></div></div>
|
||
</div>
|
||
<div class="hover-detail">Semantic labels shine in multi-step sessions where proper nouns recur. Prompt-only ~67%; fine-tuned ~76%.</div>
|
||
</div>
|
||
|
||
<div class="chart-row">
|
||
<div class="chart-row-label"><span>All four layers</span><span class="chart-row-pct" id="pct4">—</span></div>
|
||
<div class="chart-bars">
|
||
<div class="chart-bar-track"><div class="chart-bar-fill prompt-bar" id="bar4p" data-pct="25"><span class="chart-bar-tip">25%</span></div></div>
|
||
<div class="chart-bar-track"><div class="chart-bar-fill tuned-bar" id="bar4t" data-pct="18"><span class="chart-bar-tip">18%</span></div></div>
|
||
</div>
|
||
<div class="hover-detail">Full stack: delta references remove repeated step payloads entirely. Prompt-only ~75%; fine-tuned ~82%. Maximum practical gain on multi-step agentic workflows.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── Dual-Purpose Delta ────────────────────────────────────── -->
|
||
<section id="delta">
|
||
<div class="container">
|
||
<div class="delta-intro reveal">
|
||
<span class="label">Layer 3 Deep Dive</span>
|
||
<h2>The Dual-Purpose Delta</h2>
|
||
<p style="color: var(--muted);">A single <code>[Δstep_id]</code> token does two jobs simultaneously: it triggers decompression expansion for the current response, and it records a GC eviction pointer for the persistent context cache.</p>
|
||
</div>
|
||
|
||
<div class="delta-diagram reveal">
|
||
<div class="delta-center">
|
||
<div class="delta-node">[Δstep_1]</div>
|
||
<div style="margin-top: 8px; font-size: 0.75rem; color: var(--muted);">Single token — dual routing</div>
|
||
<div style="margin-top: 4px; color: var(--faint); font-size: 1.4rem; letter-spacing: 4px;">↙ ↘</div>
|
||
</div>
|
||
|
||
<div class="delta-paths">
|
||
<div class="delta-path" id="deltaPath1" onclick="highlightPath(1)">
|
||
<div class="delta-path-title"><span class="path-num">1</span>Decompression Path</div>
|
||
<div class="delta-step" id="dp1s1"><span class="step-arrow">→</span>StreamingDecompressor receives token</div>
|
||
<div class="delta-step" id="dp1s2"><span class="step-arrow">→</span>Looks up step_1 in Execution Cache</div>
|
||
<div class="delta-step" id="dp1s3"><span class="step-arrow">→</span>Cache hit: retrieves compiled output object</div>
|
||
<div class="delta-step" id="dp1s4"><span class="step-arrow">→</span>Burst-emits full expanded text to display</div>
|
||
<div class="delta-step" id="dp1s5"><span class="step-arrow">→</span>User sees complete prior result inline</div>
|
||
</div>
|
||
|
||
<div class="delta-path" id="deltaPath2" onclick="highlightPath(2)">
|
||
<div class="delta-path-title"><span class="path-num">2</span>GC Eviction Path</div>
|
||
<div class="delta-step" id="dp2s1"><span class="step-arrow">→</span>GC Eviction Record receives back-pointer</div>
|
||
<div class="delta-step" id="dp2s2"><span class="step-arrow">→</span>Marks step_1 as referenced this turn</div>
|
||
<div class="delta-step" id="dp2s3"><span class="step-arrow">→</span>Updates recency weight in Persistent Cache</div>
|
||
<div class="delta-step" id="dp2s4"><span class="step-arrow">→</span>Prevents eviction for N subsequent steps</div>
|
||
<div class="delta-step" id="dp2s5"><span class="step-arrow">→</span>Context window stays compact</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 20px; font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--border); padding-top: 16px;">
|
||
Click either path to animate its traversal. Both paths execute simultaneously for every <code>[Δstep_id]</code> token received.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ─── Footer ─────────────────────────────────────────────────── -->
|
||
<footer>
|
||
<div class="footer-inner">
|
||
<span class="footer-copy">SCO — Streaming-Compatible Compressed Output · Provisional Patent Application</span>
|
||
<span class="footer-tag">Soma / Neuron</span>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
HERO COUNTER ANIMATION
|
||
═══════════════════════════════════════════════════════════════ */
|
||
(function() {
|
||
let started = false;
|
||
function startCounter() {
|
||
if (started) return;
|
||
started = true;
|
||
const el = document.getElementById('heroCounter');
|
||
const bar = document.getElementById('heroBar');
|
||
let val = 100;
|
||
const target = 25;
|
||
const duration = 2200;
|
||
const start = performance.now();
|
||
bar.style.width = '100%';
|
||
setTimeout(() => { bar.style.width = '25%'; }, 100);
|
||
function tick(now) {
|
||
const t = Math.min((now - start) / duration, 1);
|
||
const eased = 1 - Math.pow(1 - t, 3);
|
||
val = Math.round(100 - (100 - target) * eased);
|
||
el.textContent = val;
|
||
if (t < 1) requestAnimationFrame(tick);
|
||
}
|
||
requestAnimationFrame(tick);
|
||
}
|
||
// Start after short delay
|
||
setTimeout(startCounter, 600);
|
||
})();
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
LIVE STREAMING DEMO
|
||
═══════════════════════════════════════════════════════════════ */
|
||
const CODEBOOK = {
|
||
'[fn]': 'function',
|
||
'[ret]': 'returns',
|
||
'[cfg]': 'configuration',
|
||
};
|
||
const LABELS = {
|
||
'ARCH': 'three-tier cache architecture',
|
||
};
|
||
const DELTAS = {
|
||
'step_1': 'the compiled step package from Stage 1',
|
||
};
|
||
|
||
// Demo script: array of { type, value, leftDisplay, rightDisplay, leftTokens, rightTokens }
|
||
// type: 'init', 'schema', 'text', 'code', 'label-def', 'label-ref', 'delta', 'pause'
|
||
const DEMO_SCRIPT = [
|
||
{ type: 'init' },
|
||
{ type: 'schema', value: 'ACTION', display: 'ACTION:', expanded: 'Action: ' },
|
||
{ type: 'schema', value: 'validated_schema', display: 'validated_schema', expanded: '<strong>Validated schema</strong>' },
|
||
{ type: 'schema', value: '|RESULT:', display: '|RESULT:', expanded: ' · Result: ' },
|
||
{ type: 'schema', value: 'pass_3of3', display: 'pass_3of3', expanded: '<strong>Pass (3 of 3)</strong>' },
|
||
{ type: 'schema', value: '|ISSUES:', display: '|ISSUES:', expanded: ' · Issues: ' },
|
||
{ type: 'schema', value: 'none', display: 'none', expanded: '<strong>None</strong>' },
|
||
{ type: 'schema', value: '|NEXT:', display: '|NEXT:', expanded: ' · Next: ' },
|
||
{ type: 'schema', value: 'deploy_stage', display: 'deploy_stage', expanded: '<strong>Deploy to staging</strong>' },
|
||
{ type: 'pause' },
|
||
{ type: 'newline' },
|
||
{ type: 'text', value: 'The ', display: 'The ', expanded: 'The ' },
|
||
{ type: 'label-ref', value: '[§ARCH]', display: '[§ARCH]', expanded: '<span class="expanded-label">three-tier cache architecture</span>' },
|
||
{ type: 'text', value: ' confirmed ', display: ' confirmed ', expanded: ' confirmed ' },
|
||
{ type: 'delta', value: '[Δstep_1]', display: '[Δstep_1]', expanded: '<span class="expanded-delta">the compiled step package from Stage 1</span>' },
|
||
{ type: 'text', value: ' is compatible. ', display: ' is compatible. ', expanded: ' is compatible. ' },
|
||
{ type: 'code', value: '[fn]', display: '[fn]', expanded: '<span class="expanded-code">function</span>' },
|
||
{ type: 'text', value: ' validate(', display: ' validate(', expanded: ' validate(' },
|
||
{ type: 'code', value: '[ret]', display: '[ret]', expanded: '<span class="expanded-code">returns</span>' },
|
||
{ type: 'text', value: ' cfg) uses ', display: ' cfg) uses ', expanded: ' cfg) uses ' },
|
||
{ type: 'label-ref', value: '[§ARCH]', display: '[§ARCH]', expanded: '<span class="expanded-label">three-tier cache architecture</span>' },
|
||
{ type: 'text', value: ' lookup.', display: ' lookup.', expanded: ' lookup.' },
|
||
];
|
||
|
||
// token weights: how many "equivalent" tokens each piece represents
|
||
const TOKEN_WEIGHTS = {
|
||
'init': 0,
|
||
'schema': 1,
|
||
'text': 1,
|
||
'code': 1,
|
||
'label-ref': 1,
|
||
'delta': 1,
|
||
'pause': 0,
|
||
'newline': 0,
|
||
};
|
||
const EXPANDED_WEIGHTS = {
|
||
'schema': 2,
|
||
'text': 1,
|
||
'code': 2,
|
||
'label-ref': 4,
|
||
'delta': 7,
|
||
'pause': 0,
|
||
'newline': 0,
|
||
};
|
||
|
||
let demoState = { playing: false, idx: 0, timer: null, leftTokens: 0, rightTokens: 0, paused: false };
|
||
|
||
function resetDemoDisplay() {
|
||
document.getElementById('leftPanel').innerHTML = '';
|
||
document.getElementById('rightPanel').innerHTML = '';
|
||
document.getElementById('leftCount').textContent = '0';
|
||
document.getElementById('rightCount').textContent = '0';
|
||
document.getElementById('ratioBadge').textContent = 'Ratio: —';
|
||
demoState.leftTokens = 0;
|
||
demoState.rightTokens = 0;
|
||
demoState.idx = 0;
|
||
}
|
||
|
||
function demoPlay() {
|
||
if (demoState.playing) return;
|
||
demoState.playing = true;
|
||
demoState.paused = false;
|
||
document.getElementById('demoPlay').disabled = true;
|
||
document.getElementById('demoPause').disabled = false;
|
||
scheduleNext();
|
||
}
|
||
|
||
function demoPause() {
|
||
if (!demoState.playing) return;
|
||
demoState.playing = false;
|
||
demoState.paused = true;
|
||
clearTimeout(demoState.timer);
|
||
document.getElementById('demoPlay').disabled = false;
|
||
document.getElementById('demoPause').disabled = true;
|
||
document.getElementById('demoPlay').textContent = 'Resume';
|
||
}
|
||
|
||
function demoRestart() {
|
||
clearTimeout(demoState.timer);
|
||
demoState.playing = false;
|
||
demoState.paused = false;
|
||
document.getElementById('demoPlay').disabled = false;
|
||
document.getElementById('demoPlay').textContent = 'Play';
|
||
document.getElementById('demoPause').disabled = true;
|
||
resetDemoDisplay();
|
||
}
|
||
|
||
function scheduleNext() {
|
||
if (!demoState.playing) return;
|
||
if (demoState.idx >= DEMO_SCRIPT.length) {
|
||
demoState.playing = false;
|
||
document.getElementById('demoPlay').disabled = true;
|
||
document.getElementById('demoPause').disabled = true;
|
||
return;
|
||
}
|
||
const item = DEMO_SCRIPT[demoState.idx];
|
||
const delay = item.type === 'pause' ? 800 : item.type === 'newline' ? 400 :
|
||
60 + Math.random() * 40;
|
||
demoState.timer = setTimeout(() => {
|
||
processItem(item);
|
||
demoState.idx++;
|
||
scheduleNext();
|
||
}, delay);
|
||
}
|
||
|
||
function processItem(item) {
|
||
const left = document.getElementById('leftPanel');
|
||
const right = document.getElementById('rightPanel');
|
||
|
||
if (item.type === 'init') {
|
||
const badge = document.createElement('div');
|
||
badge.innerHTML = '<span class="sco-init-badge">sco-init</span> <span style="font-size:0.72rem; color:var(--muted);">codebook + schema + HMAC verified</span><br><br>';
|
||
left.appendChild(badge);
|
||
const rbadge = document.createElement('div');
|
||
rbadge.innerHTML = '<span style="font-size:0.72rem; color:var(--muted); font-style:italic;">Session established — decompressor ready</span><br><br>';
|
||
right.appendChild(rbadge);
|
||
return;
|
||
}
|
||
if (item.type === 'pause') return;
|
||
if (item.type === 'newline') {
|
||
left.appendChild(document.createElement('br'));
|
||
right.appendChild(document.createElement('br'));
|
||
return;
|
||
}
|
||
|
||
// Left panel
|
||
const lspan = document.createElement('span');
|
||
lspan.className = 'tok-new';
|
||
if (item.type === 'schema') lspan.className += ' tok-schema';
|
||
else if (item.type === 'code') lspan.className += ' tok-code';
|
||
else if (item.type === 'label-ref' || item.type === 'label-def') lspan.className += ' tok-label-def';
|
||
else if (item.type === 'delta') lspan.className += ' tok-delta';
|
||
lspan.innerHTML = item.display;
|
||
left.appendChild(lspan);
|
||
|
||
// Right panel
|
||
const rspan = document.createElement('span');
|
||
rspan.className = 'tok-new';
|
||
rspan.innerHTML = item.expanded || item.display;
|
||
right.appendChild(rspan);
|
||
|
||
// Counters
|
||
demoState.leftTokens += TOKEN_WEIGHTS[item.type] || 0;
|
||
demoState.rightTokens += EXPANDED_WEIGHTS[item.type] || 0;
|
||
document.getElementById('leftCount').textContent = demoState.leftTokens;
|
||
document.getElementById('rightCount').textContent = demoState.rightTokens;
|
||
|
||
// Ratio
|
||
if (demoState.rightTokens > 0) {
|
||
const pct = Math.round((demoState.leftTokens / demoState.rightTokens) * 100);
|
||
document.getElementById('ratioBadge').textContent = 'Ratio: ' + pct + '%';
|
||
}
|
||
|
||
// Scroll into view
|
||
left.scrollTop = left.scrollHeight;
|
||
right.scrollTop = right.scrollHeight;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
TABS
|
||
═══════════════════════════════════════════════════════════════ */
|
||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||
function showTab(n) {
|
||
document.querySelectorAll('.tab-panel').forEach((p, i) => {
|
||
p.classList.toggle('active', i === n);
|
||
});
|
||
tabBtns.forEach((b, i) => b.classList.toggle('active', i === n));
|
||
// Animate gain bar
|
||
setTimeout(() => animateGainBar(n), 50);
|
||
}
|
||
function animateGainBar(n) {
|
||
const pcts = [50, 27, 38, 20]; // midpoints of ranges
|
||
const fill = document.getElementById('gainBar' + n);
|
||
const label = document.getElementById('gain' + n);
|
||
if (!fill) return;
|
||
fill.style.width = pcts[n] + '%';
|
||
const ranges = ['40–60%', '20–35%', '10–20%', '15–25%'];
|
||
label.textContent = ranges[n];
|
||
}
|
||
// init first tab
|
||
setTimeout(() => animateGainBar(0), 300);
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
LAYER DEMOS
|
||
═══════════════════════════════════════════════════════════════ */
|
||
function runLayer0() {
|
||
const el = document.getElementById('l0after');
|
||
el.style.color = 'var(--muted)';
|
||
el.style.fontStyle = 'italic';
|
||
el.innerHTML = 'Expanding…';
|
||
setTimeout(() => {
|
||
el.style.fontStyle = 'normal';
|
||
el.style.color = 'var(--ink)';
|
||
el.innerHTML = '<span style="color:var(--green)">Action: Validated schema · Result: Pass (3 of 3) · Issues: None · Next: Deploy to staging</span>';
|
||
}, 500);
|
||
}
|
||
|
||
function runLayer2() {
|
||
const el = document.getElementById('l2expand');
|
||
el.style.color = 'var(--muted)';
|
||
el.innerHTML = 'Expanding…';
|
||
setTimeout(() => {
|
||
el.innerHTML = '<span style="color: #7A3A00; font-style: italic;">↳ “three-tier cache architecture”</span>';
|
||
}, 400);
|
||
}
|
||
|
||
function runLayer3() {
|
||
const el = document.getElementById('l3expand');
|
||
el.innerHTML = '<span style="color: var(--muted)">Loading from Execution Cache…</span>';
|
||
setTimeout(() => {
|
||
el.style.fontStyle = 'normal';
|
||
el.innerHTML = '';
|
||
const text = 'the compiled step package from Stage 1';
|
||
let i = 0;
|
||
function burst() {
|
||
if (i <= text.length) {
|
||
el.innerHTML = '<span style="color:#4A1A6B; font-style:italic">' + text.slice(0, i) + '</span>';
|
||
i += 2;
|
||
setTimeout(burst, 30);
|
||
}
|
||
}
|
||
burst();
|
||
}, 500);
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
STATE MACHINE
|
||
═══════════════════════════════════════════════════════════════ */
|
||
const SM_SEQUENCE = [
|
||
{ tok: 'The', state: 'NORMAL', action: 'Pass through to output buffer' },
|
||
{ tok: ' ', state: 'NORMAL', action: 'Pass through to output buffer' },
|
||
{ tok: '[', state: 'CODE_FRAME', action: 'Opening bracket — enter code frame mode' },
|
||
{ tok: '§', state: 'CODE_FRAME', action: 'Label sigil detected — awaiting label name' },
|
||
{ tok: 'ARCH', state: 'CODE_FRAME', action: 'Accumulating label identifier…' },
|
||
{ tok: ']', state: 'NORMAL', action: 'Frame closed — resolve [§ARCH] → "three-tier cache architecture"' },
|
||
{ tok: ' is ', state: 'NORMAL', action: 'Pass through to output buffer' },
|
||
{ tok: '[', state: 'CODE_FRAME', action: 'Opening bracket — enter code frame mode' },
|
||
{ tok: 'Δ', state: 'CODE_FRAME', action: 'Delta sigil detected — awaiting step ID' },
|
||
{ tok: 'step_1', state: 'CODE_FRAME', action: 'Accumulating step identifier…' },
|
||
{ tok: ']', state: 'BURST', action: 'Frame closed — [Δstep_1] → burst-emit cached step output' },
|
||
{ tok: '(end)', state: 'NORMAL', action: 'Burst complete — return to normal stream' },
|
||
];
|
||
|
||
let smIdx = -1;
|
||
let smTimer = null;
|
||
let smPlaying = false;
|
||
|
||
function smPlay() {
|
||
if (smPlaying) return;
|
||
smPlaying = true;
|
||
document.getElementById('smPlayBtn').disabled = true;
|
||
smNext();
|
||
}
|
||
|
||
function smReset() {
|
||
clearTimeout(smTimer);
|
||
smPlaying = false;
|
||
smIdx = -1;
|
||
document.getElementById('smPlayBtn').disabled = false;
|
||
document.getElementById('smTokVal').textContent = '—';
|
||
document.getElementById('smTokAction').textContent = 'Press Animate to begin';
|
||
document.getElementById('smLog').innerHTML = '<div style="color: var(--faint); font-style: italic;">State machine log will appear here…</div>';
|
||
['NORMAL','CODE_FRAME','LABEL_DEFINE','PASSTHROUGH','BURST'].forEach(s => {
|
||
document.getElementById('sm-' + s).classList.remove('active');
|
||
});
|
||
}
|
||
|
||
function smNext() {
|
||
smIdx++;
|
||
if (smIdx >= SM_SEQUENCE.length) {
|
||
smPlaying = false;
|
||
document.getElementById('smPlayBtn').disabled = true;
|
||
return;
|
||
}
|
||
const step = SM_SEQUENCE[smIdx];
|
||
// Update current token display
|
||
document.getElementById('smTokVal').textContent = step.tok;
|
||
document.getElementById('smTokAction').textContent = step.action;
|
||
// Update states
|
||
['NORMAL','CODE_FRAME','LABEL_DEFINE','PASSTHROUGH','BURST'].forEach(s => {
|
||
document.getElementById('sm-' + s).classList.remove('active');
|
||
});
|
||
document.getElementById('sm-' + step.state).classList.add('active');
|
||
// Log
|
||
const log = document.getElementById('smLog');
|
||
if (smIdx === 0) log.innerHTML = '';
|
||
const entry = document.createElement('div');
|
||
entry.className = 'sm-log-entry';
|
||
entry.innerHTML = '<span>[' + step.state + ']</span> "' + step.tok + '" → ' + step.action;
|
||
log.appendChild(entry);
|
||
log.scrollTop = log.scrollHeight;
|
||
// Schedule next
|
||
smTimer = setTimeout(smNext, step.state === 'BURST' ? 900 : 700);
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
COMPRESSION CHART (scroll-triggered)
|
||
═══════════════════════════════════════════════════════════════ */
|
||
let chartAnimated = false;
|
||
const chartData = [
|
||
{ id: 'bar0', prompt: 100, tuned: 100 },
|
||
{ id: 'bar1', prompt: 58, tuned: 45 },
|
||
{ id: 'bar2', prompt: 42, tuned: 32 },
|
||
{ id: 'bar3', prompt: 33, tuned: 24 },
|
||
{ id: 'bar4', prompt: 25, tuned: 18 },
|
||
];
|
||
|
||
function animateChart() {
|
||
if (chartAnimated) return;
|
||
chartAnimated = true;
|
||
chartData.forEach((row, i) => {
|
||
setTimeout(() => {
|
||
const pb = document.getElementById(row.id + 'p');
|
||
const tb = document.getElementById(row.id + 't');
|
||
if (pb) pb.style.width = row.prompt + '%';
|
||
if (tb) tb.style.width = row.tuned + '%';
|
||
const pctEl = document.getElementById('pct' + i);
|
||
if (pctEl && i > 0) {
|
||
const redP = 100 - row.prompt;
|
||
pctEl.textContent = redP + '% reduction';
|
||
}
|
||
}, i * 150);
|
||
});
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
DELTA PATH ANIMATION
|
||
═══════════════════════════════════════════════════════════════ */
|
||
function highlightPath(pathNum) {
|
||
// Clear existing
|
||
for (let p = 1; p <= 2; p++) {
|
||
document.getElementById('deltaPath' + p).classList.remove('highlighted');
|
||
for (let s = 1; s <= 5; s++) {
|
||
const step = document.getElementById('dp' + p + 's' + s);
|
||
if (step) step.classList.remove('lit');
|
||
}
|
||
}
|
||
document.getElementById('deltaPath' + pathNum).classList.add('highlighted');
|
||
for (let s = 1; s <= 5; s++) {
|
||
(function(stepNum) {
|
||
setTimeout(() => {
|
||
const step = document.getElementById('dp' + pathNum + 's' + stepNum);
|
||
if (step) step.classList.add('lit');
|
||
}, stepNum * 200);
|
||
})(s);
|
||
}
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
INTERSECTION OBSERVER — reveal + chart trigger
|
||
═══════════════════════════════════════════════════════════════ */
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('visible');
|
||
}
|
||
});
|
||
}, { threshold: 0.1 });
|
||
|
||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||
|
||
// Chart-specific observer
|
||
const chartObserver = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) animateChart();
|
||
});
|
||
}, { threshold: 0.2 });
|
||
|
||
const chartWrap = document.getElementById('chartWrap');
|
||
if (chartWrap) chartObserver.observe(chartWrap);
|
||
</script>
|
||
</body>
|
||
</html>
|