Back to the library
Token stream, thinking pause
Variant: Thinking pause
The prompt lands and then nothing: empty output, empty channel, for 1.19s while a bar sweeps the model box. It commits, collapses to a point, and streams at 105ms a token — faster than the steady variant and still finishing later. That trade is the whole diagram. Use it when documenting time-to-first-token on a reasoning model, where the silence is the part people misread.
diagramllm
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, thinking pause</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 — 1.19s of deliberation, then exactly the same 175ms token
period as the steady variant. Deliberation buys time-to-first-token;
it does not make the tokens that follow arrive any faster, and
pretending it did would be the one claim in this set that is not true
of real models. 4.9s is 28 token periods, so the metronome still never
drifts against the restart — both divide by the same --speed, which
preserves that ratio under any pace roll. */
--cycle: 4.9s;
--t: calc(var(--cycle) / var(--speed));
--_tok: calc(175ms / 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: 22px;
}
.rail {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 1.5px;
margin-left: -0.75px;
background: var(--oat);
}
.pkt,
.flow {
position: absolute;
top: 0;
left: 50%;
width: 7px;
height: 7px;
margin-left: -3.5px;
border-radius: 50%;
background: var(--clay);
opacity: 0;
}
.pkt {
animation: send var(--t) infinite;
}
.flow {
width: 5px;
height: 5px;
margin-left: -2.5px;
}
/* The channel is empty for the whole pause. Nothing in flight is the
honest picture of latency to first token, and it is what sells it. */
.flowgate {
position: absolute;
inset: 0;
opacity: 0;
animation: gate var(--t) steps(1, end) infinite;
}
.flow:nth-child(1) {
animation: drift calc(var(--_tok) * 2) linear infinite;
}
.flow:nth-child(2) {
animation: drift calc(var(--_tok) * 2) linear calc(var(--_tok) * -1) 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;
}
.sweep {
position: absolute;
top: 50%;
left: 50%;
width: 44px;
height: 4px;
margin: -2px 0 0 -22px;
border-radius: 2px;
background: var(--clay);
opacity: 0;
animation: search 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%,
0.9% {
opacity: 0;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
3.4% {
opacity: 1;
}
9.4%,
100% {
opacity: 0;
}
}
@keyframes send {
0%,
3.4% {
opacity: 0;
transform: translateY(-4px);
}
4.3% {
opacity: 1;
transform: translateY(-4px);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
9.9% {
opacity: 1;
transform: translateY(14px);
}
10.3%,
100% {
opacity: 0;
transform: translateY(14px);
}
}
@keyframes receive {
0%,
9.8% {
transform: scale(1);
}
9.9% {
transform: scale(0.99);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
12.9%,
100% {
transform: scale(1);
}
}
@keyframes live {
0%,
9.9% {
opacity: 0;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
12.9%,
81.4% {
opacity: 1;
}
85.5%,
100% {
opacity: 0;
}
}
@keyframes gate {
0% {
opacity: 0;
}
38.6%,
81.3% {
opacity: 1;
}
81.4%,
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;
}
}
@keyframes drift {
0% {
opacity: 0;
transform: translateY(0);
}
16% {
opacity: 1;
}
76% {
opacity: 1;
}
100% {
opacity: 0;
transform: translateY(20px);
}
}
/* Three passes, ease-in-out, because this is travel across the box and
not an entrance. It collapses to a point at the commit — the pause ends
in a decision, not a fade. */
@keyframes search {
0%,
14.3% {
opacity: 0;
transform: translateX(-58px) scaleX(1);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
15.9% {
opacity: 1;
transform: translateX(-58px) scaleX(1);
animation-timing-function: cubic-bezier(0.77, 0, 0.175, 1);
}
21.9% {
transform: translateX(58px) scaleX(1);
animation-timing-function: cubic-bezier(0.77, 0, 0.175, 1);
}
28% {
transform: translateX(-58px) scaleX(1);
animation-timing-function: cubic-bezier(0.77, 0, 0.175, 1);
}
34.1% {
opacity: 1;
transform: translateX(58px) scaleX(1);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
38.6%,
100% {
opacity: 0;
transform: translateX(0) scaleX(0.12);
}
}
/* Nothing at all until 38.6% of the loop — 1.89s in — then eight even
175ms steps, the same pitch as the steady variant. The empty output
during the pause is the whole lesson; the cadence after it is not. */
@keyframes ink-a {
0%,
38.6% {
clip-path: inset(0 288px 0 0);
animation-timing-function: steps(8, end);
}
67.1%,
100% {
clip-path: inset(0 96px 0 0);
}
}
@keyframes hot-a {
0% {
clip-path: inset(0 264px 0 0);
opacity: 0;
animation-timing-function: steps(1, end);
}
38.6% {
clip-path: inset(0 264px 0 0);
opacity: 1;
animation-timing-function: steps(8, end);
}
67.1%,
100% {
clip-path: inset(0 72px 0 192px);
opacity: 1;
}
}
@keyframes ink-b {
0%,
67.1% {
clip-path: inset(0 192px 0 0);
animation-timing-function: steps(4, end);
}
81.4%,
100% {
clip-path: inset(0 96px 0 0);
}
}
@keyframes hot-b {
0% {
clip-path: inset(0 168px 0 0);
opacity: 0;
animation-timing-function: steps(1, end);
}
67.1% {
clip-path: inset(0 168px 0 0);
opacity: 1;
animation-timing-function: steps(4, end);
}
81.4%,
100% {
clip-path: inset(0 72px 0 96px);
opacity: 1;
}
}
@keyframes hold {
0%,
90% {
opacity: 1;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
96%,
100% {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.pkt,
.flow,
.flowgate {
animation: none;
opacity: 0;
}
.model {
animation: none;
}
.core {
animation: none;
opacity: 0.75;
}
/* Keep the pause readable without the sweep: the bar sits still and
breathes for exactly as long as the model is thinking. */
.sweep {
animation: waiting var(--t) ease infinite;
}
@keyframes waiting {
0%,
14.3% {
opacity: 0;
}
17.1% {
opacity: 0.9;
}
25.7% {
opacity: 0.35;
}
34.3% {
opacity: 0.9;
}
38.6%,
100% {
opacity: 0;
}
}
}
</style>
</head>
<body>
<div
class="sys"
role="img"
aria-label="A prompt enters a model, which deliberates with an empty output for over a second before committing and then streaming the completion at an ordinary token cadence"
>
<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>
<i class="sweep"></i>
<div class="beat"><i class="core"></i></div>
</div>
<div class="chan b">
<i class="rail"></i>
<div class="flowgate"><i class="flow"></i><i class="flow"></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.