Retro Grid Horizon
A glowing grid floor rolls endlessly toward you under a horizon, giving a hero section a sense of forward travel.
Next stop
Create a retro grid horizon background in pure CSS. Inside a container with overflow hidden, add a floor element positioned at the bottom half, wider than the container (200% width, left -50%), with two repeating-linear-gradients forming 40px grid lines in the accent colour at 50% opacity. Tilt it with transform: perspective(300px) rotateX(60deg) and transform-origin at the top. Animate background-position-y from 0 to 40px over 1.2s linear infinite so the lines roll toward the viewer seamlessly. Fade the far edge with a mask-image linear-gradient so the grid melts into the horizon, and add a soft accent glow line at the horizon. Put the headline above the horizon. Pause off screen, and under prefers-reduced-motion keep the grid still. The headline reads "Next stop".
Source
'use client'
import { useEffect, useRef } from 'react'
import s from './demo.module.css'
export default function RetroGrid({ labels }: { labels: Record<string, string> }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const io = new IntersectionObserver(([e]) => el.toggleAttribute('data-paused', !e.isIntersecting))
io.observe(el)
return () => io.disconnect()
}, [])
return (
<div ref={ref} className={s.scene}>
<p className={s.title}>{labels.title}</p>
<div className={s.floor} aria-hidden="true" />
</div>
)
}
.scene {
position: absolute;
inset: 0;
overflow: hidden;
background: radial-gradient(ellipse 70% 40% at 50% 52%, color-mix(in oklch, var(--stage-accent) 22%, transparent), transparent), var(--stage-bg);
}
.scene::after {
content: '';
position: absolute;
left: 0;
right: 0;
top: 52%;
height: 1px;
background: var(--stage-accent);
box-shadow: 0 0 16px 2px var(--stage-accent);
}
.title {
position: absolute;
left: 0;
right: 0;
top: 20%;
margin: 0;
text-align: center;
font: 700 var(--text-2xl) / 1 var(--font-display);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--stage-ink);
}
.floor {
--line: color-mix(in oklch, var(--stage-accent) 50%, transparent);
position: absolute;
left: -50%;
width: 200%;
top: 52%;
height: 60%;
background:
repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 40px),
repeating-linear-gradient(0deg, var(--line) 0 1px, transparent 1px 40px);
transform: perspective(300px) rotateX(60deg);
transform-origin: top;
-webkit-mask-image: linear-gradient(transparent, black 40%);
mask-image: linear-gradient(transparent, black 40%);
animation: roll 1.2s linear infinite;
}
.scene[data-paused] .floor { animation-play-state: paused; }
@keyframes roll {
from { background-position: 0 0; }
to { background-position: 0 40px; }
}
@media (prefers-reduced-motion: reduce) {
.floor { animation: none; }
}