A live server log that never stops: each new request types in on the bottom row, then the feed scrolls up a line. The first rows repeat at the end, so the 4.5s loop has no seam.
Also known as: live log animation css, hacker terminal text, console output scrolling.
GET /api/users 200 12ms
POST /api/login 201 88ms
GET /api/feed 200 31ms
GET /health 200 2ms
PUT /api/cart 200 45ms
<div class="txt-scrolling-terminal-log">
<div class="txt-scrolling-terminal-log-head" aria-hidden="true"><i></i>tail -f access.log</div>
<div class="txt-scrolling-terminal-log-view">
<div class="txt-scrolling-terminal-log-feed">
<p>GET /api/users <b>200</b> 12ms</p>
<p>POST /api/login <b>201</b> 88ms</p>
<p>GET /api/feed <b>200</b> 31ms</p>
<p class="txt-scrolling-terminal-log-new" style="--k: 0">GET /health <b>200</b> 2ms</p>
<p class="txt-scrolling-terminal-log-new" style="--k: 1">PUT /api/cart <b>200</b> 45ms</p>
<p class="txt-scrolling-terminal-log-new" style="--k: 2" aria-hidden="true">GET /api/users <b>200</b> 12ms</p>
<p class="txt-scrolling-terminal-log-new" style="--k: 3" aria-hidden="true">POST /api/login <b>201</b> 88ms</p>
<p class="txt-scrolling-terminal-log-new" style="--k: 4" aria-hidden="true">GET /api/feed <b>200</b> 31ms</p>
<p class="txt-scrolling-terminal-log-new txt-scrolling-terminal-log-next" aria-hidden="true">GET /health <b>200</b> 2ms</p>
</div>
</div>
</div>.txt-scrolling-terminal-log {
--c: var(--txt-color, #3b82f6);
font-size: var(--txt-size, 32px);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
display: inline-block;
overflow: hidden;
border-radius: 0.3em;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: calc(var(--txt-size, 32px) * 0.48);
font-weight: 500;
text-align: left;
background: color-mix(in srgb, currentColor 5%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 15%, transparent);}
.txt-scrolling-terminal-log-head {
display: flex;
align-items: center;
gap: 0.6em;
padding: 0.5em 1em;
font-size: 0.85em;
color: color-mix(in srgb, currentColor 55%, transparent);
box-shadow: inset 0 -1px 0 color-mix(in srgb, currentColor 12%, transparent);
}
.txt-scrolling-terminal-log-head i {
width: 0.5em;
height: 0.5em;
border-radius: 50%;
background: var(--c);
animation: txt-scrolling-terminal-log-live 1.5s ease-in-out infinite;
}
/* four rows tall; older rows fade out at the top */
.txt-scrolling-terminal-log-view {
height: 6em;
margin: 0.4em 1em 0.5em;
overflow: hidden;
-webkit-mask-image: linear-gradient(transparent, #000 35%);
mask-image: linear-gradient(transparent, #000 35%);
}
.txt-scrolling-terminal-log-feed {
animation: txt-scrolling-terminal-log-scroll 4.5s infinite;
}
.txt-scrolling-terminal-log p {
margin: 0;
line-height: 1.5;
white-space: pre;
}
.txt-scrolling-terminal-log b {
color: var(--c);
}
/* row 4+k types during 0.9s beat k of the loop, then stays until the loop wraps */
.txt-scrolling-terminal-log-new {
animation: txt-scrolling-terminal-log-type 4.5s calc(var(--k) * 0.9s) infinite;
}
.txt-scrolling-terminal-log-next {
clip-path: inset(0 100% 0 0);
animation: none;
}
@keyframes txt-scrolling-terminal-log-type {
0% { clip-path: inset(0 100% 0 0); animation-timing-function: steps(24); }
12%, 100% { clip-path: inset(0 0 0 0); }
}
/* hold while a row types, then scroll up one row; 100% equals 0% */
@keyframes txt-scrolling-terminal-log-scroll {
0%, 17% { transform: translateY(0); animation-timing-function: cubic-bezier(0.3, 0.7, 0.3, 1); }
20%, 37% { transform: translateY(-1.5em); animation-timing-function: cubic-bezier(0.3, 0.7, 0.3, 1); }
40%, 57% { transform: translateY(-3em); animation-timing-function: cubic-bezier(0.3, 0.7, 0.3, 1); }
60%, 77% { transform: translateY(-4.5em); animation-timing-function: cubic-bezier(0.3, 0.7, 0.3, 1); }
80%, 97% { transform: translateY(-6em); animation-timing-function: cubic-bezier(0.3, 0.7, 0.3, 1); }
100% { transform: translateY(-7.5em); }
}
@keyframes txt-scrolling-terminal-log-live {
50% { opacity: 0.3; }
}
@media (prefers-reduced-motion: reduce) {
.txt-scrolling-terminal-log, .txt-scrolling-terminal-log *, .txt-scrolling-terminal-log::before, .txt-scrolling-terminal-log::after, .txt-scrolling-terminal-log *::before, .txt-scrolling-terminal-log *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.