Back to the library

Token stream, steady

Variant: Steady

A prompt drops into a model and the completion writes itself back one token at a time, 175ms apart, with the token that just landed held in clay so the leading edge reads as a caret. The evenness is the only claim this diagram makes, and it is the baseline the other two are measured against. Reach for it when documenting a streaming endpoint that behaves.

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, steady</title>
    <style>
      :root {
        --ivory: #faf9f5;
        --slate: #141413;
        --clay: #d97757;
        --oat: #e3dacc;
        --text: rgba(20, 20, 19, 0.34);
        /* 4.2s loop and a 175ms token period: the period divides the loop
           exactly 24 times, so the metronome never drifts against the restart. */
        --loop: 4.2s;
        --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;
      }

      /* One dot leaves every 175ms — the same period as one token. Linear,
         because a packet in transit does not accelerate. */
      .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;
      }

      .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;
      }

      /* Settled tokens are text; the one that just landed is clay. The clay
         window is one cell wide and steps forward, so it reads as a caret. */
      .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%,
        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;
        }
      }

      @keyframes drift {
        0% {
          opacity: 0;
          transform: translateY(0);
        }
        16% {
          opacity: 1;
        }
        76% {
          opacity: 1;
        }
        100% {
          opacity: 0;
          transform: translateY(20px);
        }
      }

      /* Eight equal steps over 33.33% of the loop — one token every 175ms,
         no clustering. The evenness is the entire point of this variant. */
      @keyframes ink-a {
        0%,
        16.667% {
          clip-path: inset(0 288px 0 0);
          animation-timing-function: steps(8, end);
        }
        50%,
        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);
        }
        16.667% {
          clip-path: inset(0 264px 0 0);
          opacity: 1;
          animation-timing-function: steps(8, end);
        }
        50%,
        100% {
          clip-path: inset(0 72px 0 192px);
          opacity: 1;
        }
      }

      @keyframes ink-b {
        0%,
        50% {
          clip-path: inset(0 192px 0 0);
          animation-timing-function: steps(4, end);
        }
        66.667%,
        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);
        }
        50% {
          clip-path: inset(0 168px 0 0);
          opacity: 1;
          animation-timing-function: steps(4, end);
        }
        66.667%,
        100% {
          clip-path: inset(0 72px 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,
        .flow,
        .flowgate {
          animation: none;
          opacity: 0;
        }

        .model {
          animation: none;
        }

        .core {
          animation: none;
          opacity: 0.75;
        }
      }
    </style>
  </head>
  <body>
    <div
      class="sys"
      role="img"
      aria-label="A prompt enters a model and the completion streams back one token at a time, at an even 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>
        <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.