Text effects

Six type treatments that stop short of a Las Vegas marquee. Two of them (@property tricks) are Chromium-lineage for now and degrade gracefully elsewhere; the rest work everywhere.

1. Gradient shimmer

One bright band sliding through clipped text — a glint, not a rainbow.

Ship it with confidence

View CSS
.shimmer-text {
  background: linear-gradient(110deg,
    #F1F5F9 38%, #38BDF8 50%, #F1F5F9 62%);
  background-size: 220% 100%;
  background-clip: text;
  color: transparent;
  animation: shimmer 3s linear infinite;
}
@keyframes shimmer {
  from { background-position: 120% 0; }
  to   { background-position: -120% 0; }
}

2. Typewriter with caret

steps(28) = one jump per character; alternate deletes it back.

Zero dependencies, pure CSS.

View CSS
/* The string is exactly 28 chars — mono font makes
   1ch = 1 character, so width in ch clips cleanly. */
.typewriter {
  font-family: monospace;
  white-space: nowrap;
  overflow: hidden;
  width: 28ch;
  border-right: 2px solid #38BDF8;   /* the caret */
  animation:
    typing 3.2s steps(28) infinite alternate,
    caret 0.8s step-end infinite;
}
@keyframes typing { from { width: 0; } to { width: 28ch; } }
@keyframes caret  { 50% { border-color: transparent; } }

3. Word-by-word reveal

clip-path opens per word; delays persist across loops.

Every word earns its entrance.

View CSS
<span class="word" style="--i: 0">Every</span>
<span class="word" style="--i: 1">word</span> ...

.reveal .word {
  display: inline-block;
  clip-path: inset(0 100% 0 0);      /* clipped from the right */
  animation: word-in 5s ease infinite;
  animation-delay: calc(var(--i) * 0.18s);
}
@keyframes word-in {
  0%       { clip-path: inset(0 100% 0 0); }
  10%, 78% { clip-path: inset(0); opacity: 1; }  /* hold */
  88%      { clip-path: inset(0); opacity: 0; }
  100%     { clip-path: inset(0 100% 0 0); opacity: 0; }
}

4. Blur-in stagger

Out of focus, then sharp. Keep the blur under ~10px.

Soft focus, sharp finish.

View CSS
.blur-in .word {
  display: inline-block;
  filter: blur(8px);
  opacity: 0;
  animation: blur-in 5s ease infinite;
  animation-delay: calc(var(--i) * 0.15s);
}
@keyframes blur-in {
  0%       { filter: blur(8px); opacity: 0;
             transform: translateY(6px); }
  12%, 80% { filter: blur(0); opacity: 1; transform: none; }
  100%     { filter: blur(6px); opacity: 0; }
}

5. Animated counter

@property interpolates the integer; counter() prints it.

commits and counting
View CSS
@property --n {
  syntax: '<integer>';   /* registered = interpolable */
  initial-value: 0;
  inherits: false;
}
.counter {
  animation: count-up 4s cubic-bezier(.16,1,.3,1) infinite;
}
.counter::after {
  counter-reset: n var(--n);   /* integer -> rendered text */
  content: counter(n);
}
@keyframes count-up {
  0%        { --n: 0; }
  60%, 100% { --n: 2048; }
}

6. Subtle two-layer glitch

Hidden ~90% of the loop — restraint is the effect.

system.online

View CSS
.glitch { position: relative; }
.glitch::before, .glitch::after {
  content: attr(data-text);     /* same string, two ghosts */
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 100% 0); /* hidden between bursts */
}
.glitch::before { color: #38BDF8;
  animation: glitch-a 4s steps(1) infinite; }
.glitch::after  { color: #2563EB;
  animation: glitch-b 4s steps(1) infinite; }
@keyframes glitch-a {
  0%, 86%, 94%, 100% { clip-path: inset(0 0 100% 0); }
  88% { clip-path: inset(18% 0 55% 0);
        transform: translateX(-2px); }
  91% { clip-path: inset(62% 0 12% 0);
        transform: translateX(2px); }
}