A camcorder viewfinder: REC blinks in red, the date sits in the corner and the clock ticks every second, while a tracking band rolls through and jolts the OSD. A stepped integer and gradients.
Also known as: vhs timestamp css, camcorder rec text, vhs effect css, retro video overlay text.
<div class="txt-camcorder-rec-overlay" aria-label="Recording, Oct 28 1996, 11:58 PM">
<span class="txt-camcorder-rec-overlay-osd" aria-hidden="true">
<span class="txt-camcorder-rec-overlay-rec"><i></i>REC</span>
<span class="txt-camcorder-rec-overlay-sp">SP <i></i></span>
<span class="txt-camcorder-rec-overlay-date">OCT 28 1996</span>
<span class="txt-camcorder-rec-overlay-time"></span>
</span>
<span class="txt-camcorder-rec-overlay-band" aria-hidden="true"></span>
</div>@property --txt-camcorder-rec-overlay-s {
syntax: "<integer>";
initial-value: 7;
inherits: false;
}
.txt-camcorder-rec-overlay {
--c: var(--txt-color, #3b82f6);
font-size: calc(var(--txt-size, 32px) * 0.5);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
position: relative;
display: inline-block;
width: 27em;
height: 12.5em;
overflow: hidden;
border-radius: 0.4em;
background:
radial-gradient(60% 70% at 70% 40%, rgb(255 190 120 / 0.28), transparent 70%),
radial-gradient(50% 60% at 20% 80%, color-mix(in srgb, var(--c) 30%, transparent), transparent 70%),
linear-gradient(160deg, #3a3f3c, #1d211f 55%, #121413);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-weight: 700;
color: #f4f4f4;
--t: 5s;}
/* scanlines over the picture */
.txt-camcorder-rec-overlay::after {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(rgb(0 0 0 / 0.22) 0 1px, transparent 1px 0.2em);
pointer-events: none;
}
.txt-camcorder-rec-overlay-osd {
position: absolute;
inset: 1em 1.3em;
display: grid;
grid-template: auto 1fr auto / 1fr auto;
font-size: 1.35em;
letter-spacing: 0.08em;
text-shadow: 0.08em 0.08em 0 rgb(0 0 0 / 0.7), -0.05em 0 rgb(255 60 60 / 0.45), 0.05em 0 rgb(60 200 255 / 0.45);
animation: txt-camcorder-rec-overlay-jolt var(--t) infinite;
}
.txt-camcorder-rec-overlay-rec {
display: flex;
align-items: center;
gap: 0.35em;
}
.txt-camcorder-rec-overlay-rec i {
width: 0.7em;
height: 0.7em;
border-radius: 50%;
background: #ff3b30;
box-shadow: 0 0 0.4em #ff3b30;
animation: txt-camcorder-rec-overlay-blink 1s steps(1) infinite;
}
.txt-camcorder-rec-overlay-sp {
display: flex;
align-items: center;
gap: 0.4em;
}
/* a battery icon */
.txt-camcorder-rec-overlay-sp i {
width: 1.2em;
height: 0.6em;
border: 0.1em solid currentColor;
background: linear-gradient(90deg, currentColor 66%, transparent 0) 0.08em 0.08em / calc(100% - 0.16em) calc(100% - 0.16em) no-repeat;
box-shadow: 0.08em 0.08em 0 rgb(0 0 0 / 0.7);
}
.txt-camcorder-rec-overlay-date { grid-area: 3 / 1; align-self: end; }
.txt-camcorder-rec-overlay-time {
grid-area: 3 / 2;
align-self: end;
counter-reset: txt-camcorder-rec-overlay-s var(--txt-camcorder-rec-overlay-s);
animation: txt-camcorder-rec-overlay-s 60s steps(60) -7s infinite;
}
.txt-camcorder-rec-overlay-time::before {
content: "PM 11:58:" counter(txt-camcorder-rec-overlay-s, decimal-leading-zero);
}
/* the tracking band */
.txt-camcorder-rec-overlay-band {
position: absolute;
left: -5%;
right: -5%;
top: 0;
height: 1.8em;
background:
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.85) 0 3.1em, transparent 0 3.6em, rgb(255 255 255 / 0.5) 0 4.4em, transparent 0 6.9em) 0 0.35em / 100% 0.12em no-repeat,
repeating-linear-gradient(90deg, transparent 0 1.2em, rgb(255 255 255 / 0.7) 0 5.8em, transparent 0 6.5em, rgb(255 255 255 / 0.35) 0 8em) 0 0.8em / 100% 0.1em no-repeat,
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.4) 0 1.5em, transparent 0 2.3em, rgb(255 255 255 / 0.75) 0 4.1em, transparent 0 5.4em) 0 1.25em / 100% 0.14em no-repeat,
linear-gradient(transparent, rgb(255 255 255 / 0.14) 40%, rgb(255 255 255 / 0.2) 60%, transparent);
filter: blur(0.03em);
mix-blend-mode: screen;
opacity: 0;
animation: txt-camcorder-rec-overlay-band var(--t) linear infinite;
}
@keyframes txt-camcorder-rec-overlay-blink {
50% { opacity: 0; }
}
@keyframes txt-camcorder-rec-overlay-s {
from { --txt-camcorder-rec-overlay-s: 0; }
to { --txt-camcorder-rec-overlay-s: 60; }
}
@keyframes txt-camcorder-rec-overlay-band {
0%, 60% { opacity: 0; transform: translateY(-2em); }
61% { opacity: 1; transform: translateY(-1.5em); }
80% { opacity: 1; transform: translateY(12em); }
81%, 100% { opacity: 0; transform: translateY(13em); }
}
@keyframes txt-camcorder-rec-overlay-jolt {
0%, 64%, 78%, 100% { transform: none; }
66% { transform: translateX(0.25em); }
69% { transform: translateX(-0.15em) skewX(-4deg); }
72% { transform: translateX(0.1em); }
75% { transform: translate(-0.05em, 0.05em); }
}
@media (prefers-reduced-motion: reduce) {
.txt-camcorder-rec-overlay, .txt-camcorder-rec-overlay *, .txt-camcorder-rec-overlay *::before, .txt-camcorder-rec-overlay *::after { animation: none !important; }
}
@media (prefers-reduced-motion: reduce) {
.txt-camcorder-rec-overlay, .txt-camcorder-rec-overlay *, .txt-camcorder-rec-overlay::before, .txt-camcorder-rec-overlay::after, .txt-camcorder-rec-overlay *::before, .txt-camcorder-rec-overlay *::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.