Dark page where six milestone cards hang inside a glowing grid tunnel and scrolling flies you through them one by one. Pure CSS 3D and inline SVG, so every tool builds the same page.
## Goal
Design a company history landing page for Meridian, a space flight company. The hero is a 3D tunnel: six milestone cards (sixty years of missions from 1969 to 2026) hang in a glowing grid corridor, and scrolling flies the visitor forward through them one by one. Keep the tone confident and cinematic.
Use the style, layout and motion rules below exactly. Replace the sample content with copy that fits the goal, keeping six milestones in chronological order, with years or dates in the same place and similar text lengths.
## Style
Keep this exactly: it is what makes the design look the way it does.
- Page background #05060D, text #EAF0FF, muted text #8791AD, card surface rgb(12 15 30 / 0.95), hairlines rgb(255 255 255 / 0.12). Accent gradient from cyan #5CE1E6 to violet #8B7CFF (left to right; 135deg on the card icons). Font: system-ui.
- Gradient text: the accent gradient clipped to the text (`background-clip: text`, transparent color).
- Buttons are pills, 14px 26px padding, 15px semibold. Primary: accent gradient, dark text #04121A, glow 0 8px 28px rgb(92 225 230 / 0.28). Secondary: transparent with a 1px border at 18% white. The small header button is the secondary style at 10px 20px padding, 14px text.
- Every focusable element shows a 2px cyan (#5CE1E6) focus outline with 3px offset.
- No images anywhere. Everything is drawn with CSS 3D transforms and inline SVG. Do not generate, search for or link any image, and do not use WebGL, canvas or libraries.
**The tunnel.** All of it lives in one 3D scene and is built from real planes at real depths. Use `perspective: 900px` with perspective origin 60% 46% on the scene, and `transform-style: preserve-3d` on every element between the scene and the planes, cards and gates. The tunnel cross-section is 1120px wide and 640px tall (so 560px and 320px from the center line), and the vanishing point is at 60% across and 46% down the stage.
- **Four walls**, each 1120px × 3600px (the side walls are drawn 3600px × 640px), `pointer-events: none`, made of a grid: 1px lines every 130px, horizontal lines rgb(92 225 230 / 0.2) and vertical lines rgb(139 124 255 / 0.16). Floor: `transform: translateY(320px) rotateX(90deg) translateY(-1200px)`. Ceiling: `translateY(-320px) rotateX(-90deg) translateY(1200px)`. Left wall: `translateX(-560px) rotateY(90deg) translateX(1200px)`. Right wall: `translateX(560px) rotateY(-90deg) translateX(-1200px)`. Each fades out toward the far end with a `mask-image` linear-gradient from transparent to #000 at 55%.
- **Gates**: six rectangular rings, 1120px × 640px, 28px radius, 2px border rgb(92 225 230 / 0.4), glow 0 0 40px rgb(92 225 230 / 0.25) plus inset 0 0 40px rgb(139 124 255 / 0.15). They sit between the cards and fly past the camera, giving a sense of speed.
- **Milestone cards**, 460px × 300px, 20px radius, 28px padding, surface color, 1px border at 14% white, shadow 0 30px 80px rgb(0 0 0 / 0.5) plus 0 0 60px rgb(92 225 230 / 0.12). Inside, top row: the year in 46px bold gradient text (tabular figures, -0.04em letter spacing) on the left and a 64px line icon on the right (inline SVG, 2px round strokes with the accent gradient, no fill). Then a 24px bold title (18px above, 8px below, -0.02em letter spacing), a 15px muted description (line-height 1.5), and at the bottom a meta row: "01 / 06" in 12px semibold uppercase muted text with 0.12em letter spacing on the left, and a key figure in 14px bold uppercase cyan #5CE1E6 on the right.
- Behind everything on the stage, a soft radial glow: ellipse 45% × 55% at 60% 46%, #1B2350 fading to transparent by 70%.
## Layout
1. **Header,** fixed, 72px tall, a fade from the page color to transparent, 6vw side padding: logo on the left (a 12px gradient dot with a cyan glow and the brand name, 18px bold), links on the right ("Missions", "Fleet", "Careers", 14px muted, 32px apart) and the small secondary "Watch live" button.
2. **Scroll stage.** A section 320svh tall containing a sticky, `100svh` stage (`overflow: hidden`). Inside it:
- Left column, 420px wide (max 34vw), vertically centered, 6vw from the left: a pill eyebrow (13px semibold muted text, 1px border at 14% white, 6px gradient dot), an h1 (clamp(36px, 4.4vw, 62px), bold, line-height 1.02, -0.035em letter spacing) whose last words use the gradient text, an 18px muted paragraph (line-height 1.55) and a scroll hint (13px uppercase semibold muted text, 0.08em letter spacing, with a small down arrow).
- The 3D scene (see Style) filling the stage, with its vanishing point at 60% across and 46% down.
- Progress dots: six 8px circles filled with the accent gradient, 14px apart, centered at 60% across and 68px from the bottom.
- A scroll gauge centered at 60% across and 36px from the bottom: the first year, a 160px × 2px track (white at 12%) and the last year, in 11px semibold uppercase muted text, 0.16em letter spacing, 14px gaps. The track fills left to right with the accent gradient as `--p` goes from 0 to 1 (`transform: scaleX(var(--p))`, origin left).
3. **Closing call to action,** centered, padding 140px 24px 120px: a heading (clamp(34px, 4vw, 52px), bold, -0.03em, with the brand in the gradient), an 18px muted line, then the primary and secondary buttons 14px apart.
4. **Footer,** a hairline above, 13px muted text, copyright on the left and "Privacy · Terms" on the right.
Under 900px wide: the nav links hide (the button stays), the column sits at the top (96px down, full width) with the paragraph and hint hidden and the h1 at 38px, the vanishing point moves to 50% across and 64% down, cards become 300px × 250px with 20px padding (year 34px, title 20px, text 14px, icon 44px, 30px sideways offset), and the dots and gauge center at 50% across (52px and 24px from the bottom).
## Motion
Use CSS transitions, keyframes and a small vanilla script; no animation libraries. Use the easing cubic-bezier(0.22, 1, 0.36, 1) unless stated. **The fly-through is plain CSS driven by one number:** the script only sets a unitless `--p` (0 to 1) on the stage, and everything below is a `calc()` on `--p`, so the result is identical in every browser.
- **Scroll progress:** target progress = how far the sticky stage has scrolled through its section, (−section top) / (section height − viewport height), clamped to 0–1. Each animation frame, move the displayed `--p` toward the target by 12% of the remaining distance, so it eases instead of snapping, and stop updating once it is within 0.0005.
- **Depth of each item:** every card, gate and progress dot has an index `--i` and its own `--d: calc(var(--p) * 5 - var(--i))`. Cards use i = 0 to 5 (oldest first, so the oldest is in focus at `--p` 0 and the newest at `--p` 1). Gates use i = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5. Card and gate depth is `translateZ(calc(var(--d) * 520px))`: negative `--d` is farther down the tunnel, positive is behind the camera's focus plane, flying toward the viewer.
- **Card position:** cards zig-zag so those behind peek out: `translate3d(calc(var(--s) * 120px), 0, calc(var(--d) * 520px))` where `--s` is +1 for even i (0, 2, 4) and −1 for odd i (1, 3, 5). Center each card on the vanishing point.
- **Visibility:** gate opacity is `min(clamp(0, calc((0.4 - var(--d)) * 3), 1), clamp(0, calc(1 + var(--d) * 0.45), 1))`. Card opacity is `min(clamp(0, calc((0.5 - var(--d)) * 4), 1), clamp(0, calc(1 + var(--d) * 0.8), 1))`, so the card in focus stays fully solid (its 95% surface hides the cards behind it, so their text never shows through), fades out only once it is well past (gone at `--d` 0.5), and the cards farther down the tunnel stay dim until they come forward. Gates fade in gradually from the far end of the tunnel. Do not animate the opacity of any 3D ancestor, because it flattens the 3D sorting.
- **Moving walls:** the wall grids slide toward the camera as `--p` grows, 2600px over the full range: floor `background-position: 0 calc(var(--p) * 2600px)`, ceiling `0 calc(var(--p) * -2600px)`, left wall `calc(var(--p) * -2600px) 0`, right wall `calc(var(--p) * 2600px) 0`.
- **Progress dots:** dot i has opacity `clamp(0.25, calc(1.6 - max(var(--d), calc(var(--d) * -1)) * 2), 1)` and scale `calc(1 + clamp(0, calc(1 - max(var(--d), calc(var(--d) * -1))), 1) * 0.7)`, so the dot of the card in focus glows and grows.
- **Intro:** on load the whole tunnel flies in: its independent `translate` property animates from `0 0 -700px` to `0 0 0` over 1000ms. Only use `translate`, never opacity.
- **Idle sway:** the scene rotates between -0.6deg and 0.6deg on the Z axis, 7s ease-in-out, alternating, forever. Put it on its own wrapper.
- **Pointer lean:** on devices with a fine pointer, the scene leans toward the cursor over the stage (up to 4deg of rotateY and 3deg of rotateX, inverted for X), easing over 600ms, and returns flat when the cursor leaves. Put this on its own wrapper, outside the sway.
- **Scroll hint:** fades out as the scroll starts (opacity `calc(1 - var(--p) * 8)`); its arrow nudges down 5px and back every 1.8s.
- **Buttons:** the primary lifts 2px with a stronger glow (0 12px 36px rgb(139 124 255 / 0.45)) on hover, 240ms; the secondary gets a 7% white background.
- **Reduced motion:** with prefers-reduced-motion: reduce, **scrolling must still fly through the tunnel**, because it is something the visitor does, not motion the page makes on its own. Keep the 320svh section, and set `--p` straight to the target progress on every scroll and resize event (no easing and no animation loop). Turn off only the self-running motion: the intro, the idle sway, the pointer lean and the hint nudge.
## Sample content
- Brand "Meridian", a space flight company. Links "Missions", "Fleet", "Careers". Header button "Watch live".
- Eyebrow "Mission archive". Heading "Sixty years of flying forward." with "flying forward." in the gradient. Paragraph "Six milestones, one tunnel. Scroll to fly through the missions that took Meridian from a desert test stand to the edge of the belt." Hint "Scroll to fly through". Gauge "1969" and "2026".
- Milestones, in order (year, title, description, key figure, icon):
1. 1969, "First light", "Our founders fire a two-stage rocket from a desert test stand and reach 40 km.", "40 km", a rocket.
2. 1978, "Orbit achieved", "Meridian-1 completes 14 orbits and returns its capsule intact.", "14 orbits", an orbit ring around a planet with a small moon.
3. 1986, "The long eye", "A 2.4 m space telescope maps the southern sky for eleven years.", "11 years", a telescope mirror (two concentric circles with a cross).
4. 1998, "Station keeping", "Four modules dock to form the first commercial research station.", "4 modules", a space station (a square with two solar panels).
5. 2011, "Return to the Moon", "A reusable lander touches down at Shackleton Rim with 900 kg of cargo.", "900 kg", a crescent moon.
6. 2026, "Beyond the belt", "Ion-driven Meridian-9 leaves for Ceres, with ten years of travel ahead.", "10 years", a dashed trajectory between a small and a large circle.
- Closing: "Fly with Meridian" (the brand in the gradient), "Next launch window opens 14 March. Seats on the Ceres crew rotation are limited.", buttons "Join the crew" and "Read the logbook". Footer "© 2026 Meridian" and "Privacy · Terms".
- Mark the 3D scene `aria-hidden="true"` and give the stage an aria-label that lists the six milestones' range, e.g. "Scroll through six Meridian milestones, from 1969 to 2026". The cards' text is decorative inside the scene, so keep the key facts also in the left column copy.
## Images
None. This design uses no photos, no generated images and no image links: the tunnel, cards and icons are built from CSS 3D transforms and inline SVG, and the page works the same in every tool. Do not add any `<img>`, background image, canvas or WebGL.
## Output
A single self-contained HTML file with CSS in a `<style>` tag and JavaScript in one `<script>` tag, responsive down to 375px wide, using semantic HTML and visible keyboard focus states.