Animations and transitions
CSS motion works on the grid with one honest constraint: a cell either has a glyph and colours or it doesn’t. Colours interpolate smoothly; geometry steps cell by cell; everything else switches discretely.
Animations
@keyframes with from/to or percentage stops, driven by the animation shorthand
or animation-name / animation-duration / animation-iteration-count (including infinite) / animation-timing-function:
@keyframes pulse {
0% { color: #ef4444; }
50% { color: #7f1d1d; }
}
.recording { animation: pulse 1s ease-in-out infinite; } Keyframe values resolve var() and light-dark() against the animated element when the animation starts.
Easing
animation-timing-function and transition-timing-function support linear, ease (the default, as in browsers), ease-in, ease-out, ease-in-out, cubic-bezier(x1, y1, x2, y2), steps(n[, start | end]), step-start and step-end. As in CSS, the
easing shapes progress within each keyframe segment, and non-interpolable
values switch when eased progress crosses the midpoint.
What interpolates
- Colours (
color,background) mix in RGB at ~30fps between the surrounding stops. ANSI palette names interpolate through nominal values and stay exact at the endpoints. - Single cell/ch lengths (
width,height,padding-*,margin-*, insets,gap) interpolate and round to whole cells — a 10-cell slide is ten visible steps, so give movement enough distance to read. - Everything else supported by the style system (
display,font-weight,border-*,visibility, …) applies discretely, switching at the midpoint of the segment — the CSS rule for non-interpolable values.
Layout-affecting animations re-flow every frame; colour-only animations just repaint.
Transitions
transition runs when a tracked property’s target changes — a class toggles, an
inline style updates, a :checked rule starts applying:
.preview { transition: background-color 400ms; }
.tab { transition: color 150ms, padding-left 150ms; } transition-property takes a list or all; transition-duration takes ms/s. The same interpolation rules as animations apply. The initial
style never transitions.
Transitions are configured per property, per spec: comma-separated
shorthand groups (transition: color 150ms linear, width 400ms ease)
or longhand lists paired cyclically. An interrupted transition
continues from its current blended value. A timing function declared inside a keyframe applies from that stop to the next, overriding the
element’s. Keyframe var()/light-dark() values re-resolve when the
scheme or custom properties change mid-animation, retargeting without
restarting.
Deviations from browsers
opacitydoesn’t interpolate (it applies discretely mid-animation) — animate colour toward the background for a smooth fade.transition-delay/animation-delayare not implemented.
Reduced motion
Media queries work, so honour user preference the standard way:
@media (prefers-reduced-motion: reduce) {
.recording { animation: none; }
}