Split-flap board
CSS + JSA departure board: each character is four half-height leaves, and a change drops the old top half and lands the new bottom half. JS only swaps the letters and restarts the CSS animation.
Columns of green code fall through the dark at four layers of depth, each with a bright leading glyph and a tail that dims behind it. One perspective makes the far columns small and slow and the near ones large and fast, so the rain has real depth, in pure CSS.
Your edited version
width: 1ch), holding its glyphs as a single word that word-break: break-all breaks after every letter. Forty-eight columns are forty-eight elements, and each falls with one translate3d.background-clip: text, border-box. The top layer is a steady green brightening down the column to a near-white last line, the glyph leading the fall; the bottom one is a black strip, so a near column hides the far ones behind it and its letters are always read on black. The tail dims only as far as a green still 6:1 on black: every glyph is text you can read, not a smear.translateZ from 165 units behind the screen to 45 in front, in one perspective. Perspective draws the far ones smaller and nearer the middle, so each column works out --k, how much smaller it is drawn, and multiplies its place across and its fall by it: it lands at the share of the canvas it names and falls from just above the top edge to just below the bottom one, at any depth.animation-delay, so the rain is already falling on the first frame, and every fall starts and ends out of sight: no seam.inset: 0 and a size container, so the fall is counted in cqh (shares of the canvas’ height) and the places in percentages: a tall canvas shows the same rain, not more of it. Its sizes are multiples of one base unit, --u, tied to the canvas, so a glyph is the same share of a card and of a full screen.translate3d fall at each column’s own --zbackground-clip: text, border-boxword-break: break-all, 1ch widenegative animation-delay<div class="rain" role="img" aria-label="Columns of green code falling at different depths, like digital rain">
<div class="field" aria-hidden="true">
<i style="--x:-2.1;--z:-158;--t:7.2s;--d:-3.9s">6CA+L.HM~K#~9</i>
<i style="--x:0.6;--z:24;--t:3.4s;--d:-0.6s">GXMUS8NF</i>
<i style="--x:2.3;--z:-32;--t:4.2s;--d:-3s">N~4X^S+TS</i>
<i style="--x:4.6;--z:-90;--t:5.3s;--d:-3s">#WN?R7_|5|*13~A8</i>
<i style="--x:6.6;--z:-157;--t:6.4s;--d:-5.3s">B?P2Q|Y|3~Q!</i>
<i style="--x:9;--z:30;--t:3.4s;--d:-2s;--m:-1">5W-!M$X~7~D=?CJ@%PZC</i>
<i style="--x:10.7;--z:-51;--t:4.9s;--d:-1s">70^R6@%^@^</i>
<i style="--x:14;--z:-102;--t:5.4s;--d:-4.5s;--m:-1">7S?N+4C:</i>
<i style="--x:15.2;--z:-142;--t:6.5s;--d:-4.3s">SYZ!-J!*T</i>
<i style="--x:18.6;--z:17;--t:3.3s;--d:-2.9s">F|M5ZK1SWL=0#</i>
<i style="--x:20.8;--z:-33;--t:4s;--d:-3s">3AM:CYV=8REZ#9*@</i>
<i style="--x:22.9;--z:-86;--t:5.3s;--d:-5.1s;--m:-1">7K7E1V^~L@</i>
<i style="--x:24.9;--z:-164;--t:6.5s;--d:-1.2s">V:.6_~AY+KV1Z</i>
<i style="--x:26.7;--z:27;--t:3.1s;--d:-1.8s;--m:-1">25G#62Q1$3D2YCM</i>
<i style="--x:29.5;--z:-39;--t:4.4s;--d:-2.9s;--m:-1">V#6U~Q8:</i>
<i style="--x:31;--z:-89;--t:5.3s;--d:-2.1s;--m:-1">._4RR!?C9K</i>
<i style="--x:33.3;--z:-146;--t:6.6s;--d:-6.2s">N?#DQH09</i>
<i style="--x:35.9;--z:31;--t:3.2s;--d:-1.4s">|19W_1D17DUH~~473</i>
<i style="--x:37.7;--z:-44;--t:4.2s;--d:-4.1s">:GL=|W5D?5MQ</i>
<i style="--x:40.5;--z:-109;--t:6.1s;--d:-5.9s;--m:-1">NZ6.XHMK:Z!</i>
<i style="--x:42.8;--z:-154;--t:6.5s;--d:-1.4s">ASNL3WF^HYNR</i>
<i style="--x:43.9;--z:22;--t:3.6s;--d:-2s">725KYVUM@=1SH?</i>
<i style="--x:46;--z:-53;--t:5.1s;--d:-1.8s">2+1FS0?P+8AYTU</i>
<i style="--x:48.6;--z:-95;--t:5.4s;--d:-0.7s">-2:JB_SRJJ7B</i>
<i style="--x:50.7;--z:-144;--t:5.9s;--d:-4.9s">@~:1F_C8L</i>
<i style="--x:53;--z:33;--t:3.4s;--d:-2s;--m:-1">5Y+1X^6~1</i>
<i style="--x:55.2;--z:-40;--t:4.3s;--d:-4.1s;--m:-1">^YTM^@BC$1EF=LL@@</i>
<i style="--x:58.4;--z:-82;--t:5.5s;--d:-2.9s">=RX7=6H|</i>
<i style="--x:60;--z:-154;--t:6.5s;--d:-5.2s">+^GH1#S:QV4A14VV*4</i>
<i style="--x:62.3;--z:39;--t:3.3s;--d:-0.5s;--m:-1">SH!XRBYE0!3|X</i>
<i style="--x:65;--z:-27;--t:4.4s;--d:-0.6s">WBAL@|SQU%##4U9=0V</i>
<i style="--x:67.3;--z:-109;--t:5.9s;--d:-5.3s">XU$X:3V#-</i>
<i style="--x:68.9;--z:-146;--t:7s;--d:-2.7s">M!9C#W#Q?^DMDR</i>
<i style="--x:71.3;--z:37;--t:3.3s;--d:-0.4s;--m:-1">8J2L$XF1^0HK9F+-*XG</i>
<i style="--x:72.7;--z:-29;--t:4.3s;--d:-0.3s">0S95Q9P07.L0.H3K</i>
<i style="--x:75;--z:-83;--t:5.8s;--d:-5.1s;--m:-1">_NJ*JK9B!F</i>
<i style="--x:77;--z:-152;--t:6.8s;--d:0s">P2?-8=WQ^-?E</i>
<i style="--x:79.4;--z:21;--t:3.6s;--d:-1.6s">GF8F0H$:RJ@KBJB~5</i>
<i style="--x:82.7;--z:-49;--t:5.1s;--d:-2.7s">|G4.ST^E8E#A9.X</i>
<i style="--x:83.7;--z:-87;--t:5.8s;--d:-3.5s;--m:-1">%EL.PU%|!K~Q%9$GE9</i>
<i style="--x:87.2;--z:-136;--t:5.6s;--d:-3.8s">^ZQJ1TF-</i>
<i style="--x:89.4;--z:25;--t:3.6s;--d:-2.7s;--m:-1">GKYT5Y735J^LNWM+</i>
<i style="--x:91.2;--z:-45;--t:4.8s;--d:-0.9s">+BBKJ#~:!FZ8</i>
<i style="--x:93.1;--z:-81;--t:4.9s;--d:-1.7s">TC95.J!-^%R</i>
<i style="--x:94.5;--z:-159;--t:7.2s;--d:-4.1s">D:Z0~^74+Y#</i>
<i style="--x:96.8;--z:39;--t:2.9s;--d:-2s;--m:-1">Z!RZ3|^AT=XMU=:</i>
<i style="--x:98.9;--z:-26;--t:4.5s;--d:-2.4s">*J*FNC?EQ#Q</i>
<i style="--x:101.5;--z:-99;--t:5.6s;--d:-1.1s;--m:-1">5D%9M0_Y~#</i>
</div>
</div>
.rain {
/* one base unit, tied to the canvas; the rain itself fills the canvas edge to edge, placed in
percentages of it and falling in shares of its height */
--u: 0.5vmin;
position: fixed;
inset: 0;
overflow: hidden;
container-type: size; /* inside it, 1cqh is 1% of the canvas's height */
perspective: calc(200 * var(--u));
background: #000;
}
/* the whole field sways a little about the vertical, so near columns slide past far ones */
.field {
position: absolute;
inset: 0;
transform-style: preserve-3d;
animation: sway 24s ease-in-out infinite;
}
/* a column: a black strip one character wide, its glyphs one per line. --x is where it lands, in
% of the canvas width; --z its depth in units; --k how much smaller perspective draws it there */
.field i {
--k: calc((200 - var(--z)) / 200);
position: absolute;
top: 0;
/* perspective pulls a far column towards the middle by --k, so it is placed --k further out */
left: calc(50% + (var(--x) - 50) * var(--k) * 1%);
width: 1ch;
font: 600 calc(10 * var(--u)) / 1.12 'JetBrains Mono', ui-monospace, monospace;
font-style: normal;
text-align: center;
word-break: break-all; /* one character wide, so every letter starts a line */
/* two layers, each with its own clip: a green that brightens down the column to a near-white
head, clipped to the letters, over a black strip that fills the box and hides whatever is
behind it, so the glyphs are always read on black. The darkest green, #14a64c, is 6:1 on it */
background:
linear-gradient(#14a64c, #22c55e 50%, #74ffa6 calc(100% - 1.12em), #e9fff1 calc(100% - 1.12em)),
linear-gradient(#000, #000);
-webkit-background-clip: text, border-box;
background-clip: text, border-box;
color: transparent;
animation: fall var(--t) linear var(--d) infinite;
}
/* from just above the top edge to just below the bottom one, as perspective draws it at --z:
50cqh is the vanishing point, and a column drawn --k smaller has --k as far to go each way.
--m: -1 mirrors a column's glyphs, for the look of the film */
@keyframes fall {
from { transform: translate3d(-50%, calc(50cqh * (1 - var(--k)) - 100% - 6cqh * var(--k)), calc(var(--z) * var(--u))) scaleX(var(--m, 1)); }
to { transform: translate3d(-50%, calc(50cqh * (1 + var(--k)) + 6cqh * var(--k)), calc(var(--z) * var(--u))) scaleX(var(--m, 1)); }
}
/* symmetrical easing, and the end is the start: no seam */
@keyframes sway {
0%, 100% { transform: rotateY(-6deg); }
50% { transform: rotateY(6deg); }
}