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" />
<title>Token stream, thinking pause</title>
<style>
:root {
--ivory: #faf9f5;
--slate: #141413;
--clay: #d97757;
--oat: #e3dacc;
--text: rgba(20, 20, 19, 0.34);
/* 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. */
--loop: 4.9s;
--tok: 175ms;
}
* {
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: 7px;
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: 7px;
animation: sent var(--loop) 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(--loop) 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(--loop) 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: 9px;
animation: receive var(--loop) infinite;
}
.model .ring {
border-radius: 9px;
animation: live var(--loop) 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(--loop) infinite;
}
.beat {
position: absolute;
inset: 0;
display: grid;
place-items: center;
opacity: 0;
animation: gate var(--loop) 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(--loop) 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(--loop) infinite;
}
.l1 .hot {
animation: hot-a var(--loop) infinite;
}
.l2 .ink {
animation: ink-b var(--loop) infinite;
}
.l2 .hot {
animation: hot-b var(--loop) 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(--loop) 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.