A silent-movie title card: sepia frame, serif caps, a weaving gate, uneven brightness, jumping scratches and dust. Scratches and dust are background layers repositioned in steps.
Also known as: vintage film text effect, silent movie title card, film grain flicker.
<div class="txt-old-film-text"><span class="txt-old-film-text-title">THE END</span></div>
.txt-old-film-text {
--c: var(--txt-color, #3b82f6);
font-size: var(--txt-size, 32px);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
position: relative;
display: inline-block;
overflow: hidden;
padding: 0.6em 1.3em;
background: radial-gradient(ellipse at center, color-mix(in srgb, var(--c) 10%, #4a3f33), #15110d 85%);
box-shadow: inset 0 0 0 0.14em #15110d, inset 0 0 0 0.18em color-mix(in srgb, var(--c) 10%, #cbbd9f);
color: color-mix(in srgb, var(--c) 10%, #f3ead6);
font-family: Georgia, "Times New Roman", ui-serif, serif;
font-weight: 400;
letter-spacing: 0.24em;
animation: txt-old-film-text-exposure 0.9s steps(1, end) infinite;}
.txt-old-film-text-title {
display: inline-block;
margin-right: -0.24em;
animation: txt-old-film-text-weave 0.48s steps(1, end) infinite;
}
/* two scratches */
.txt-old-film-text::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgb(243 234 214 / 0.55), rgb(243 234 214 / 0.55)) 22% 0 / 0.03em 100% no-repeat,
linear-gradient(rgb(243 234 214 / 0.3), rgb(243 234 214 / 0.3)) 71% 0 / 0.02em 100% no-repeat;
animation: txt-old-film-text-scratch 0.64s steps(1, end) infinite;
}
/* dust */
.txt-old-film-text::after {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(circle, rgb(243 234 214 / 0.8) 0.025em, transparent 0.04em) 15% 30% / 0.1em 0.1em no-repeat,
radial-gradient(circle, rgb(0 0 0 / 0.7) 0.03em, transparent 0.045em) 80% 70% / 0.1em 0.1em no-repeat,
radial-gradient(circle, rgb(243 234 214 / 0.6) 0.02em, transparent 0.035em) 55% 15% / 0.1em 0.1em no-repeat;
animation: txt-old-film-text-dust 0.36s steps(1, end) infinite;
}
@keyframes txt-old-film-text-exposure {
0%, 100% { opacity: 1; }
22% { opacity: 0.86; }
45% { opacity: 0.96; }
67% { opacity: 0.9; }
}
@keyframes txt-old-film-text-weave {
0%, 100% { transform: translate(0, 0); }
25% { transform: translate(0.01em, -0.02em); }
50% { transform: translate(-0.01em, 0.01em); }
75% { transform: translate(0, -0.01em); }
}
@keyframes txt-old-film-text-scratch {
0%, 100% { background-position: 22% 0, 71% 0; }
25% { background-position: 64% 0, 12% 0; }
50% { background-position: 35% 0, 88% 0; }
75% { background-position: 80% 0, 47% 0; }
}
@keyframes txt-old-film-text-dust {
0%, 100% { background-position: 15% 30%, 80% 70%, 55% 15%; opacity: 1; }
33% { background-position: 70% 80%, 25% 20%, 90% 55%; opacity: 0.6; }
66% { background-position: 40% 60%, 60% 35%, 10% 85%; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.txt-old-film-text, .txt-old-film-text *, .txt-old-film-text::before, .txt-old-film-text::after, .txt-old-film-text *::before, .txt-old-film-text *::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.