Back to the library
Token stream, bursty
Variant: Bursty
The same model on the same 175ms beat, but delivery runs through a four-slot buffer: tokens queue, the buffer flushes, four words land at once. Production never pauses — the queue filling underneath is the proof — so the stall you feel is transport, not the model. Use it to explain chunked transfer, batched SSE flushes, or why a proxy makes a fast model feel jerky.
diagramllminfrastructure
The source
A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="animation-knobs" content="speed: 0.7-1.3" />
<title>Token stream, bursty</title>
<style>
:root {
/* Neutrals — fixed, never remixed. */
--ivory: #faf9f5;
--paper: #ffffff;
--oat: #e3dacc;
--slate: #141413;
/* Knobs — the remix API. A knob exists iff declared here and used below. */
--hue: 38.8;
--lift: 0;
--chroma: 1;
--round: 1;
--speed: 1;
/* Accents — derived from the knobs with the family's offsets baked in,
so one hue roll moves them together. Defaults reproduce the original hex. */
--clay: oklch(calc(0.6724 + var(--lift)) calc(0.1308 * var(--chroma)) var(--hue));
--clay-d: oklch(calc(0.5797 + var(--lift)) calc(0.127 * var(--chroma)) var(--hue));
--olive: oklch(
calc(0.6118 + var(--lift)) calc(0.0713 * var(--chroma)) calc(var(--hue) + 88.3)
);
--rust: oklch(
calc(0.5408 + var(--lift)) calc(0.1357 * var(--chroma)) calc(var(--hue) - 10.3)
);
--_text: color-mix(in srgb, var(--slate) 34%, transparent);
/* Clock — same 4.2s loop and same 175ms token period as the steady
variant, production is identical. Four tokens per chunk, so the buffer
cycles every 700ms and fits the loop exactly six times. All three
divide by the same --speed, which preserves the ratios under any pace
roll. */
--cycle: 4.2s;
--t: calc(var(--cycle) / var(--speed));
--_tok: calc(175ms / var(--speed));
--_chunk: calc(700ms / var(--speed));
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: var(--ivory);
font-family:
system-ui,
-apple-system,
'Segoe UI',
Roboto,
sans-serif;
color: var(--slate);
}
.sys {
width: 192px;
display: grid;
justify-items: start;
}
/* --- prompt ------------------------------------------------------ */
.prompt {
position: relative;
width: 192px;
height: 30px;
display: grid;
align-content: center;
gap: 5px;
padding: 0 10px;
border-radius: calc(7px * var(--round));
background: var(--oat);
}
.prompt b {
display: block;
height: 4px;
border-radius: 2px;
background: var(--_text);
}
.prompt b:nth-of-type(1) {
width: 128px;
}
.prompt b:nth-of-type(2) {
width: 86px;
}
.prompt .ring {
border-radius: calc(7px * var(--round));
animation: sent var(--t) infinite;
}
.ring {
position: absolute;
inset: -1.5px;
border: 1.5px solid var(--clay);
opacity: 0;
pointer-events: none;
}
/* --- channels ---------------------------------------------------- */
.chan {
position: relative;
width: 192px;
}
.chan.a {
height: 16px;
}
.chan.b {
height: 26px;
}
.rail {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 1.5px;
margin-left: -0.75px;
background: var(--oat);
}
.pkt {
position: absolute;
top: 0;
left: 50%;
width: 7px;
height: 7px;
margin-left: -3.5px;
border-radius: 50%;
background: var(--clay);
opacity: 0;
animation: send var(--t) infinite;
}
/* --- buffer ------------------------------------------------------ */
.bufgate {
position: absolute;
inset: 0;
opacity: 0;
animation: gate var(--t) steps(1, end) infinite;
}
/* Four slots at 17px rather than 11px: this is the element that explains
the whole variant, so it has to survive being shrunk to a card. */
.slots,
.fill {
position: absolute;
top: 3px;
left: 50%;
width: 72px;
height: 6px;
margin-left: -36px;
background: repeating-linear-gradient(90deg, currentColor 0 17px, transparent 17px 18px);
}
.slots {
color: var(--oat);
}
/* Four slots fill on the same 175ms beat the steady variant emits on —
the queue is what clumps, not the model. */
.fill {
color: var(--clay);
animation: buffer var(--_chunk) infinite;
}
.flush {
position: absolute;
top: 12px;
left: 50%;
width: 7px;
height: 7px;
margin-left: -3.5px;
border-radius: 50%;
background: var(--clay);
opacity: 0;
animation: deliver var(--_chunk) infinite;
}
/* --- model ------------------------------------------------------- */
.model {
position: relative;
width: 192px;
height: 36px;
border: 1.5px solid var(--oat);
border-radius: calc(9px * var(--round));
animation: receive var(--t) infinite;
}
.model .ring {
border-radius: calc(9px * var(--round));
animation: live var(--t) infinite;
}
.beat {
position: absolute;
inset: 0;
display: grid;
place-items: center;
opacity: 0;
animation: gate var(--t) steps(1, end) infinite;
}
.core {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--clay);
animation: tick var(--_tok) infinite;
}
/* --- completion -------------------------------------------------- */
.out {
width: 192px;
display: grid;
gap: 9px;
overflow: hidden;
animation: hold var(--t) infinite;
}
.line {
position: relative;
height: 7px;
}
.l1 {
width: 288px;
}
.l2 {
width: 192px;
}
.row {
position: absolute;
inset: 0;
display: flex;
}
.row span {
flex: 0 0 24px;
}
.row span::before {
content: '';
display: block;
height: 7px;
border-radius: 3.5px;
background: currentColor;
}
.ink {
color: var(--_text);
}
.hot {
color: var(--clay);
}
.l1 span:nth-child(1)::before {
width: 18px;
}
.l1 span:nth-child(2)::before {
width: 12px;
}
.l1 span:nth-child(3)::before {
width: 20px;
}
.l1 span:nth-child(4)::before {
width: 14px;
}
.l1 span:nth-child(5)::before {
width: 16px;
}
.l1 span:nth-child(6)::before {
width: 12px;
}
.l1 span:nth-child(7)::before {
width: 19px;
}
.l1 span:nth-child(8)::before {
width: 15px;
}
.l1 span:nth-child(n + 9)::before {
display: none;
}
.l2 span:nth-child(1)::before {
width: 13px;
}
.l2 span:nth-child(2)::before {
width: 20px;
}
.l2 span:nth-child(3)::before {
width: 16px;
}
.l2 span:nth-child(4)::before {
width: 11px;
}
.l2 span:nth-child(n + 5)::before {
display: none;
}
.l1 .ink {
animation: ink-a var(--t) infinite;
}
.l1 .hot {
animation: hot-a var(--t) infinite;
}
.l2 .ink {
animation: ink-b var(--t) infinite;
}
.l2 .hot {
animation: hot-b var(--t) infinite;
}
/* --- timeline ---------------------------------------------------- */
@keyframes sent {
0%,
1% {
opacity: 0;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
4% {
opacity: 1;
}
11%,
100% {
opacity: 0;
}
}
@keyframes send {
0%,
4% {
opacity: 0;
transform: translateY(-4px);
}
5% {
opacity: 1;
transform: translateY(-4px);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
11.5% {
opacity: 1;
transform: translateY(14px);
}
12%,
100% {
opacity: 0;
transform: translateY(14px);
}
}
@keyframes receive {
0%,
11.4% {
transform: scale(1);
}
11.5% {
transform: scale(0.99);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
15%,
100% {
transform: scale(1);
}
}
@keyframes live {
0%,
11.5% {
opacity: 0;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
15%,
66.667% {
opacity: 1;
}
71%,
100% {
opacity: 0;
}
}
@keyframes gate {
0% {
opacity: 0;
}
16.667%,
66.6% {
opacity: 1;
}
66.667%,
100% {
opacity: 0;
}
}
@keyframes tick {
0% {
transform: scale(1.3);
opacity: 0.95;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
55%,
100% {
transform: scale(1);
opacity: 0.5;
}
}
/* Discrete steps, not a slide: a queue holds whole tokens. It empties the
instant the chunk leaves, then starts refilling — production never stops. */
@keyframes buffer {
0% {
clip-path: inset(0 75% 0 0);
animation-timing-function: steps(1, end);
}
25% {
clip-path: inset(0 50% 0 0);
animation-timing-function: steps(1, end);
}
50% {
clip-path: inset(0 25% 0 0);
animation-timing-function: steps(1, end);
}
75% {
clip-path: inset(0 0 0 0);
animation-timing-function: steps(1, end);
}
76%,
100% {
clip-path: inset(0 100% 0 0);
}
}
@keyframes deliver {
0%,
75% {
opacity: 0;
transform: translateY(0);
}
76% {
opacity: 1;
transform: translateY(0);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
90% {
opacity: 1;
transform: translateY(12px);
}
90.5%,
100% {
opacity: 0;
transform: translateY(12px);
}
}
/* Two steps across the first line instead of eight: four tokens land
together, then nothing for 700ms. Same total, worse to read. */
@keyframes ink-a {
0%,
31.667% {
clip-path: inset(0 288px 0 0);
animation-timing-function: steps(2, end);
}
65%,
100% {
clip-path: inset(0 96px 0 0);
}
}
@keyframes hot-a {
0% {
clip-path: inset(0 192px 0 0);
opacity: 0;
animation-timing-function: steps(1, end);
}
31.667% {
clip-path: inset(0 192px 0 0);
opacity: 1;
animation-timing-function: steps(2, end);
}
65%,
100% {
clip-path: inset(0 0 0 192px);
opacity: 1;
}
}
@keyframes ink-b {
0%,
65% {
clip-path: inset(0 192px 0 0);
animation-timing-function: steps(1, end);
}
81.667%,
100% {
clip-path: inset(0 96px 0 0);
}
}
@keyframes hot-b {
0% {
clip-path: inset(0 96px 0 0);
opacity: 0;
animation-timing-function: steps(1, end);
}
65% {
clip-path: inset(0 96px 0 0);
opacity: 1;
animation-timing-function: steps(1, end);
}
81.667%,
100% {
clip-path: inset(0 0 0 96px);
opacity: 1;
}
}
@keyframes hold {
0%,
88% {
opacity: 1;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
94%,
100% {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.pkt,
.flush {
animation: none;
opacity: 0;
}
.model {
animation: none;
}
.core {
animation: none;
opacity: 0.75;
}
/* The buffer is what makes this variant different from the steady one,
so it stays — held full, which is the state a bursty consumer sits in
between flushes. Nothing about it travels. */
.bufgate {
animation: none;
opacity: 1;
}
.fill {
animation: none;
clip-path: inset(0 0 0 0);
}
}
</style>
</head>
<body>
<div
class="sys"
role="img"
aria-label="A prompt enters a model; tokens queue in a four-slot buffer and are delivered to the completion four at a time"
>
<div class="prompt">
<b></b><b></b>
<i class="ring"></i>
</div>
<div class="chan a">
<i class="rail"></i>
<i class="pkt"></i>
</div>
<div class="model">
<i class="ring"></i>
<div class="beat"><i class="core"></i></div>
</div>
<div class="chan b">
<i class="rail"></i>
<div class="bufgate">
<i class="slots"></i>
<i class="fill"></i>
<i class="flush"></i>
</div>
</div>
<div class="out">
<div class="line l1">
<div class="row ink">
<span></span><span></span><span></span><span></span><span></span><span></span
><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="row hot">
<span></span><span></span><span></span><span></span><span></span><span></span
><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</div>
<div class="line l2">
<div class="row ink">
<span></span><span></span><span></span><span></span><span></span><span></span
><span></span><span></span>
</div>
<div class="row hot">
<span></span><span></span><span></span><span></span><span></span><span></span
><span></span><span></span>
</div>
</div>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.