<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>every-other-token: LLM token stream viewer and interceptor</title>
<meta name="description" content="Free CLI and web UI that shows per-token confidence and perplexity of an OpenAI or Anthropic stream and rewrites tokens live. Windows .exe, no API key to try.">
<link rel="canonical" href="https://every-other-token.vercel.app/">
<meta property="og:type" content="website">
<meta property="og:site_name" content="every-other-token">
<meta property="og:url" content="https://every-other-token.vercel.app/">
<meta property="og:title" content="every-other-token: watch an LLM stream token by token">
<meta property="og:description" content="See how sure the model was about every token, and rewrite every other token as it streams. OpenAI, Anthropic, or a mock with no API key.">
<meta property="og:image" content="https://every-other-token.vercel.app/assets/og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="every-other-token output: every other token reversed, with a confidence bar and percentage under each token">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="every-other-token: watch an LLM stream token by token">
<meta name="twitter:description" content="Per-token confidence and perplexity for OpenAI and Anthropic streams, with live token rewriting. Free, open source, Windows .exe.">
<meta name="twitter:image" content="https://every-other-token.vercel.app/assets/og.png">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "every-other-token",
"description": "CLI and web UI that intercepts an OpenAI or Anthropic token stream live, shows per-token confidence and perplexity from logprobs, and rewrites every other token. A mock provider runs with no API key.",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Windows, macOS, Linux",
"softwareVersion": "4.3.1",
"url": "https://every-other-token.vercel.app/",
"downloadUrl": "https://gitlab.com/mattbusel/Every-Other-Token/-/releases/permalink/latest/downloads/every-other-token-windows-x86_64.exe",
"codeRepository": "https://gitlab.com/mattbusel/Every-Other-Token",
"programmingLanguage": "Rust",
"license": "https://opensource.org/licenses/MIT",
"isAccessibleForFree": true,
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
"author": { "@type": "Person", "name": "Matthew Busel", "url": "https://gitlab.com/mattbusel" }
}
</script>
<meta name="theme-color" content="#0c0d0b">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%230c0d0b'/%3E%3Crect x='6' y='20' width='8' height='3' rx='1.5' fill='%23e6e1d3'/%3E%3Crect x='18' y='20' width='8' height='3' rx='1.5' fill='%23ff6a2b'/%3E%3Crect x='6' y='10' width='8' height='6' rx='1' fill='%23e6e1d3'/%3E%3Crect x='18' y='10' width='8' height='6' rx='1' fill='%23ff6a2b'/%3E%3C/svg%3E">
<style>
:root{
--bg:#0c0d0b;--bg2:#090a08;--card:#131411;--line:#25271f;--line2:#36382f;
--ink:#e6e1d3;--dim:#a19c8c;--faint:#6b675b;--xf:#ff6a2b;--gold:#e8c872;
--hi:#7fd69f;--mid:#e8c872;--lo:#ff5c7a;
--mono:ui-monospace,'Cascadia Code','JetBrains Mono','SF Mono',Menlo,Consolas,'Liberation Mono',monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{background:var(--bg);color:var(--ink);font-family:var(--mono);font-size:15px;line-height:1.6;-webkit-font-smoothing:antialiased}
a{color:var(--ink);text-decoration-color:var(--line2);text-underline-offset:4px}
a:hover{text-decoration-color:var(--xf)}
.wrap{max-width:1120px;margin:0 auto;padding:0 28px}
.x{color:var(--xf)}
nav{display:flex;align-items:center;justify-content:space-between;padding:22px 0;gap:16px}
.logo{font-weight:700;font-size:16px;text-decoration:none;letter-spacing:-.01em;white-space:nowrap}
nav .links{display:flex;gap:22px;font-size:13px;color:var(--dim)}
nav .links a{color:var(--dim);text-decoration:none}
nav .links a:hover{color:var(--ink)}
header.hero{padding:56px 0 28px}
h1{font-size:clamp(34px,6.2vw,68px);line-height:1.02;letter-spacing:-.035em;font-weight:700}
.lead{max-width:62ch;color:var(--dim);font-size:17px;margin-top:22px}
.lead b{color:var(--ink);font-weight:500}
.player{margin-top:40px;border:1px solid var(--line);border-radius:14px;background:var(--bg2);overflow:hidden}
.pbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid var(--line);background:var(--card)}
.pbar .lbl{font-size:12px;color:var(--faint);margin-right:4px}
.tab{font:inherit;font-size:13px;color:var(--dim);background:transparent;border:1px solid var(--line2);border-radius:6px;padding:4px 11px;cursor:pointer}
.tab:hover{color:var(--ink);border-color:var(--faint)}
.tab[aria-pressed=true]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.replay{margin-left:auto;font:inherit;font-size:13px;font-weight:700;background:var(--xf);color:#170904;border:0;border-radius:6px;padding:5px 14px;cursor:pointer}
.replay:hover{background:#ff8450}
.stage{display:grid;grid-template-columns:1fr 1fr;min-height:250px}
.lane{padding:22px 24px 26px;font-size:clamp(16px,2vw,21px);line-height:2.25;white-space:pre-wrap;word-wrap:break-word}
.lane+.lane{border-left:1px solid var(--line)}
.lane h3{font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);line-height:1;margin-bottom:14px}
.tk{text-decoration-line:underline;text-decoration-thickness:2px;text-underline-offset:8px;animation:tin .18s ease-out both}
.tk.h{text-decoration-color:var(--hi)}.tk.m{text-decoration-color:var(--mid)}.tk.l{text-decoration-color:var(--lo)}
.tk.x{color:var(--xf);font-weight:700}
.tk.del{color:var(--faint);text-decoration-line:line-through;text-decoration-color:var(--faint);font-weight:400}
.tk[data-ws]::before{content:attr(data-ws);display:inline-block;white-space:pre}
@keyframes tin{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
.caret{display:inline-block;width:.55em;height:1.05em;background:var(--xf);vertical-align:-.15em;margin-left:2px;animation:blink 1s steps(1) infinite}
@keyframes blink{50%{opacity:0}}
.pfoot{display:flex;flex-wrap:wrap;gap:8px 26px;align-items:center;padding:12px 24px;border-top:1px solid var(--line);font-size:12.5px;color:var(--faint)}
.pfoot b{color:var(--ink);font-weight:500}
.sw{display:inline-block;width:16px;height:3px;border-radius:2px;vertical-align:middle;margin-right:6px}
.legend{margin-left:auto;display:flex;gap:16px;flex-wrap:wrap}
.note{font-size:12.5px;color:var(--faint);margin-top:12px;max-width:80ch}
code{white-space:nowrap;font-family:var(--mono);font-size:.92em;color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:4px;padding:1px 5px}
section{padding:72px 0 8px}
h2{font-size:clamp(22px,3vw,30px);letter-spacing:-.02em;line-height:1.15;font-weight:700}
h2 .n{color:var(--faint);font-weight:400;margin-right:10px}
.sub{color:var(--dim);margin-top:10px;max-width:64ch}
.cmds{display:grid;gap:14px;margin-top:26px}
.cmd{border:1px solid var(--line);border-radius:10px;background:var(--card);overflow:hidden}
.cmd .hd{display:flex;justify-content:space-between;align-items:center;padding:8px 14px;border-bottom:1px solid var(--line);font-size:12px;color:var(--faint)}
.copy{font:inherit;font-size:12px;color:var(--dim);background:transparent;border:1px solid var(--line2);border-radius:5px;padding:2px 10px;cursor:pointer}
.copy:hover{color:var(--ink);border-color:var(--faint)}
.copy.ok{color:var(--hi);border-color:var(--hi)}
pre{margin:0;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:14px;line-height:1.7;color:var(--ink)}
pre .c{color:var(--faint)}
pre .p{color:var(--xf);user-select:none}
.shot{margin-top:26px}
.shot img{display:block;width:100%;height:auto;border-radius:12px;border:1px solid var(--line)}
.shot figcaption{font-size:12.5px;color:var(--faint);margin-top:10px}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-top:28px}
.cell{background:var(--bg);padding:20px 20px 22px}
.cell h3{font-size:14px;font-weight:700;margin-bottom:6px}
.cell p{font-size:13.5px;color:var(--dim)}
.cell .k{color:var(--xf)}
footer{margin-top:90px;border-top:1px solid var(--line);padding:26px 0 40px;font-size:13px;color:var(--faint);display:flex;flex-wrap:wrap;gap:12px 24px;justify-content:space-between}
footer a{color:var(--dim)}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
@media (max-width:820px){
.wrap{padding:0 16px}
.stage{grid-template-columns:1fr}
.lane+.lane{border-left:0;border-top:1px solid var(--line)}
.grid{grid-template-columns:1fr}
.shot .scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.shot .scroll img{width:680px;max-width:none}
nav .links{gap:14px}
.legend{margin-left:0}
header.hero{padding-top:28px}
}
@media (max-width:420px){nav .links .opt{display:none}}
@media (prefers-reduced-motion:reduce){.tk{animation:none}.caret{animation:none}}
.dl{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:26px}
.dl a.win{display:inline-block;background:var(--xf);color:#170904;font-weight:700;text-decoration:none;border-radius:8px;padding:12px 20px;font-size:15px}
.dl a.win:hover{background:#ff8450}
.dl a.alt{color:var(--dim);font-size:13px}
.how img{width:100%;height:auto;display:block;border-radius:14px}
.how ol{margin:18px 0 0 20px;color:var(--dim);max-width:78ch}
.how ol li{margin:6px 0}
.how ol b{color:var(--ink);font-weight:500}
</style>
</head>
<body>
<div class="wrap">
<nav>
<a class="logo" href="./">every-<span class="x">other</span>-token</a>
<div class="links">
<a href="#install">install</a>
<a class="opt" href="https://docs.rs/every-other-token">docs.rs</a>
<a class="opt" href="https://crates.io/crates/every-other-token">crates.io</a>
<a href="https://gitlab.com/mattbusel/Every-Other-Token">github</a>
</div>
</nav>
<header class="hero">
<h1>Read the stream,<br>not the <span class="x">answer</span>.</h1>
<p class="lead"><b>every-other-token</b> is a Rust CLI and web UI that sits on an OpenAI or Anthropic token stream as it arrives. It rewrites every other token (or any fraction), and it tells you how sure the model was about each one: confidence <code>exp(logprob)</code> and perplexity <code>exp(-logprob)</code>, per token, live.</p>
<div class="dl">
<a class="win" href="https://gitlab.com/mattbusel/Every-Other-Token/-/releases/permalink/latest/downloads/every-other-token-windows-x86_64.exe">Download for Windows (.exe)</a>
<a class="alt" href="#install">macOS, Linux, Scoop, Homebrew, Cargo</a>
</div>
<div class="player" id="player">
<div class="pbar" role="group" aria-label="Transform">
<span class="lbl">transform</span>
<button class="tab" data-t="reverse" aria-pressed="true">reverse</button>
<button class="tab" data-t="uppercase" aria-pressed="false">uppercase</button>
<button class="tab" data-t="mock" aria-pressed="false">mock</button>
<button class="tab" data-t="noise" aria-pressed="false">noise</button>
<button class="tab" data-t="delete" aria-pressed="false">delete</button>
<button class="replay" id="replay">replay</button>
</div>
<div class="stage">
<div class="lane"><h3>what the model sent</h3><span id="orig"></span></div>
<div class="lane"><h3>what you see</h3><span id="shown"></span></div>
</div>
<div class="pfoot">
<span><b id="n">0</b> tokens</span>
<span><b id="nx">0</b> rewritten</span>
<span>mean confidence <b id="mc">-</b></span>
<span class="legend"><span><i class="sw" style="background:var(--hi)"></i>sure ≥70%</span><span><i class="sw" style="background:var(--mid)"></i>unsure</span><span><i class="sw" style="background:var(--lo)"></i>guessing <40%</span></span>
</div>
</div>
<p class="note">Recorded from the real <code>/stream</code> endpoint of <code>every-other-token --web --provider mock</code>. The mock provider replays a fixed reply with fixed logprobs through the same interception pipeline a real model goes through, so it runs with no API key. Point it at OpenAI or Anthropic and the tokens and numbers are the model's own.</p>
</header>
<section class="how" id="how-it-works">
<h2><span class="n">00</span>How it works</h2>
<p class="sub">Four stages, shown on a real run of the mock provider with the reverse transform at rate 0.5.</p>
<img src="img/how-it-works.svg" width="1000" height="640" alt="Diagram: 1 Intercept reads the live stream, 2 Score gives each token confidence exp(logprob) and perplexity exp(-logprob), 3 Mutate reverses the odd-numbered tokens, 4 Output shows 'The kciuq brown xof jumps revo the yzal dog' in the terminal, web UI or JSON" loading="lazy">
<ol>
<li><b>Intercept.</b> It opens the model's streaming (SSE) connection itself, so it sees each chunk the moment it arrives.</li>
<li><b>Score.</b> Each token gets confidence <code>exp(logprob)</code>, perplexity <code>exp(-logprob)</code> and the top alternatives.</li>
<li><b>Mutate.</b> Every other token by default (<code>--rate</code>), or only the ones below <code>--min-confidence</code>, goes through a transform.</li>
<li><b>Output.</b> Terminal, web UI, JSON lines, CSV or an HTML heatmap.</li>
</ol>
</section>
<section id="install">
<h2><span class="n">01</span>Running in two minutes</h2>
<p class="sub">No Rust needed: <a href="https://gitlab.com/mattbusel/Every-Other-Token/-/releases/permalink/latest/downloads/every-other-token-windows-x86_64.exe">Windows .exe</a> (double-click it and the web UI opens), <a href="https://gitlab.com/mattbusel/Every-Other-Token/-/releases">macOS Apple Silicon</a>, <a href="https://gitlab.com/mattbusel/Every-Other-Token/-/releases">macOS Intel</a>, <a href="https://gitlab.com/mattbusel/Every-Other-Token/-/releases/permalink/latest/downloads/every-other-token-linux-x86_64.tar.gz">Linux x86_64</a>. Or <code>scoop install every-other-token</code> after <code>scoop bucket add mattbusel https://gitlab.com/mattbusel/scoop-bucket</code>, <code>brew install mattbusel/tap/every-other-token</code>, or <code>cargo install every-other-token</code> (Rust 1.81+).</p>
<div class="cmds">
<div class="cmd"><div class="hd"><span>install</span><button class="copy" data-copy="cargo install every-other-token">copy</button></div>
<pre><span class="p">$ </span>cargo install every-other-token</pre></div>
<div class="cmd"><div class="hd"><span>try it, no API key</span><button class="copy" data-copy="every-other-token "What is consciousness?" --provider mock --visual">copy</button></div>
<pre><span class="p">$ </span>every-other-token "What is consciousness?" --provider mock --visual</pre></div>
<div class="cmd"><div class="hd"><span>a real model, in the browser</span><button class="copy" data-copy="export OPENAI_API_KEY=sk-...
every-other-token --web">copy</button></div>
<pre><span class="p">$ </span>export OPENAI_API_KEY=sk-... <span class="c"># or ANTHROPIC_API_KEY with --provider anthropic</span>
<span class="p">$ </span>every-other-token --web <span class="c"># http://localhost:8888</span></pre></div>
</div>
</section>
<section>
<h2><span class="n">02</span>In the terminal</h2>
<p class="sub">The stream prints as it arrives. With <code>--visual</code>, rewritten tokens are highlighted; <code>--heatmap</code> colors each token by importance instead.</p>
<figure class="shot"><div class="scroll"><img src="assets/terminal-visual.svg" width="957" height="438" alt="Terminal: every-other-token with the mock provider, every other word reversed and highlighted, 22 tokens streamed, 11 transformed" loading="lazy"></div><figcaption>Real output of the command above.</figcaption></figure>
</section>
<section>
<h2><span class="n">03</span>Every token carries its numbers</h2>
<p class="sub">Each intercepted token is an event with the original text, what was shown, its index, whether it was rewritten, confidence, perplexity and the top alternatives. Consume them from the library, as JSON lines with <code>--json-stream</code>, or export them to CSV, JSONL or a self-contained HTML heatmap.</p>
<figure class="shot"><div class="scroll"><img src="assets/terminal-table.svg" width="1060" height="772" alt="Per-token table from cargo run --example mock_stream: index, original token, shown token, confidence, perplexity" loading="lazy"></div><figcaption><code>cargo run --example mock_stream</code>, from a clone of the repo.</figcaption></figure>
</section>
<section>
<h2><span class="n">04</span>The web UI</h2>
<p class="sub"><code>--web</code> serves a single page with no build step and no external scripts. Single, split, quad (four transforms at once), OpenAI vs Anthropic diff, A/B system prompts, a research dashboard, JSON and CSV export, and collaborative rooms where several people edit tokens mid-stream.</p>
<figure class="shot"><div class="scroll"><img src="assets/web-ui.png" width="1280" height="600" alt="The web UI in split view: original stream on the left, transformed on the right, each token underlined by confidence, with perplexity and confidence sparklines below" loading="lazy"></div><figcaption>Split view, mock provider. The underline under each token is its confidence.</figcaption></figure>
</section>
<section>
<h2><span class="n">05</span>What else is in the box</h2>
<div class="grid">
<div class="cell"><h3>9 transforms, chainable</h3><p><span class="k">reverse</span>, uppercase, mock, noise, chaos, scramble, delete, synonym, <span class="k">delay:N</span>, or a chain like <code>reverse,uppercase</code>.</p></div>
<div class="cell"><h3>Rate, seed, gating</h3><p><code>--rate 0.3</code> spreads rewrites evenly at any fraction. <code>--seed</code> makes random transforms reproducible. <code>--min-confidence</code> only touches tokens the model was unsure of.</p></div>
<div class="cell"><h3>Provider diff</h3><p><code>--diff-terminal</code> streams OpenAI and Anthropic side by side and compares their confidence structure.</p></div>
<div class="cell"><h3>A/B system prompts</h3><p>Run two system prompts against the same user prompt, many times, and test the confidence shift with Welch's t-test (<code>--significance</code>).</p></div>
<div class="cell"><h3>Research mode</h3><p><code>--research --runs 20</code> runs headless and writes aggregate perplexity, confidence and vocabulary stats to JSON.</p></div>
<div class="cell"><h3>Record and replay</h3><p>Record any session to JSON and replay it later, for example to spot behavior changes after a model update.</p></div>
</div>
</section>
<footer>
<span>MIT licensed. Built in Rust by <a href="https://gitlab.com/mattbusel">Matthew Busel</a>.</span>
<span><a href="https://gitlab.com/mattbusel/Every-Other-Token">source</a> <a href="https://gitlab.com/mattbusel/Every-Other-Token/-/releases">releases</a> <a href="https://docs.rs/every-other-token">API docs</a> <a href="https://gitlab.com/mattbusel/Every-Other-Token/-/issues">issues</a></span>
</footer>
</div>
<script>
const REC = {"reverse":[["The","The",0,0.887,1.127],[" quick"," kciuq",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," xof",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," revo",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," yzal",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," si",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," kcom",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," rof",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"uppercase":[["The","The",0,0.887,1.127],[" quick"," QUICK",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," FOX",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," OVER",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," LAZY",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," IS",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," MOCK",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," FOR",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"mock":[["The","The",0,0.887,1.127],[" quick"," qUiCk",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," fOx",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," oVeR",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," lAzY",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," iS",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," mOcK",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," fOr",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"noise":[["The","The",0,0.887,1.127],[" quick"," quick~",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," fox+",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," over$",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," lazy%",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".*",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," is+",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," mock*",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," for+",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":$",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"delete":[["The","The",0,0.887,1.127],[" quick"," ",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," ",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," ",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," ",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".","",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," ",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," ",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," ",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":","",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]]};
const $ = s => document.querySelector(s);
const band = c => c >= 0.7 ? 'h' : c >= 0.4 ? 'm' : 'l';
let timer = null, cur = 'reverse';
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
function span(text, cls, title) {
const s = document.createElement('span');
s.className = 'tk ' + cls;
const m = /^(\s+)(\S[\s\S]*)$/.exec(text);
if (m) { s.dataset.ws = m[1]; s.textContent = m[2]; } else s.textContent = text;
if (title) s.title = title;
return s;
}
function play(t) {
cur = t; clearTimeout(timer);
document.querySelectorAll('.tab').forEach(b => b.setAttribute('aria-pressed', b.dataset.t === t));
const o = $('#orig'), v = $('#shown'); o.textContent = ''; v.textContent = '';
const caret = document.createElement('span'); caret.className = 'caret'; caret.setAttribute('aria-hidden', 'true');
v.appendChild(caret);
const rows = REC[t]; let i = 0, sum = 0, nx = 0;
const step = () => {
const [orig, shown, x, conf, ppl] = rows[i];
const title = 'confidence ' + Math.round(conf * 100) + '%, perplexity ' + ppl.toFixed(2);
const so = span(orig, band(conf), title);
if (so.dataset.ws) { o.appendChild(document.createTextNode(so.dataset.ws)); delete so.dataset.ws; }
o.appendChild(so);
const deleted = x && shown.trim() === '';
const sv = span(deleted ? orig : shown, band(conf) + (x ? (deleted ? ' del' : ' x') : ''), title + (x ? (deleted ? ', deleted' : ', rewritten') : ''));
if (sv.dataset.ws) { v.insertBefore(document.createTextNode(sv.dataset.ws), caret); delete sv.dataset.ws; }
v.insertBefore(sv, caret);
sum += conf; nx += x; i++;
$('#n').textContent = i; $('#nx').textContent = nx; $('#mc').textContent = Math.round(sum / i * 100) + '%';
if (i < rows.length) timer = setTimeout(step, reduce ? 0 : 70 + Math.round(ppl * 45));
else caret.remove();
};
step();
}
document.querySelectorAll('.tab').forEach(b => b.addEventListener('click', () => play(b.dataset.t)));
$('#replay').addEventListener('click', () => play(cur));
document.querySelectorAll('.copy').forEach(b => b.addEventListener('click', async () => {
try { await navigator.clipboard.writeText(b.dataset.copy); b.textContent = 'copied'; b.classList.add('ok'); }
catch (_) { b.textContent = 'select + copy'; }
setTimeout(() => { b.textContent = 'copy'; b.classList.remove('ok'); }, 1400);
}));
const io = new IntersectionObserver(es => { if (es[0].isIntersecting) { play('reverse'); io.disconnect(); } });
io.observe($('#player'));
</script>
</body>
</html>