Landing PageMiniMaxAI/MiniMax-M3verified 23/23partial cross-check
Scroll Atlas
Pinned scroll-scrubbed cinematic narrative with a progress rail
The exact prompt
DESIGN BRIEF — "Scroll Atlas"
Build a one-page scroll-driven narrative for a fictional cartography studio called
"Atlas Nine". The page must feel like scrubbing a film timeline: the user scrolls, and a
pinned stage performs choreographed, reversible motion. Nothing here should be a
"fade in on scroll" — every transform must be *driven* by scroll progress.
A. THE PINNED STAGE
- Structure: a `<section class="stage">` that is 420vh tall, containing a
`position: sticky; top: 0; height: 100vh` inner viewport. Do NOT use
position: fixed. The sticky inner viewport holds the canvas, the type, and the rail.
- Progress p = clamp(scrollTop_of_section / (sectionHeight - viewportHeight), 0, 1),
computed in the rAF loop from a cached rect (recompute the rect only on resize and
on a 250ms debounce of scroll — never call getBoundingClientRect inside rAF).
- The stage is divided into 4 beats at p = 0.00, 0.25, 0.50, 0.75. Each beat has a
title, a body line and a colour. Cross-fade beats with a 0.08-wide smoothstep
window so there is never a hard cut.
B. THE VISUAL — a drawn world, not an image
- A full-viewport <canvas> renders an abstract "atlas": a slowly rotating globe of
latitude/longitude arcs (stroke rgba(255,255,255,.14), 1px) drawn with simple
3D→2D projection maths you write yourself (no library). Only the front hemisphere
is drawn; back-facing segments are skipped.
- As p advances, the camera dollies in (radius 1.0 → 0.42), the globe tilts
(tilt 0 → -0.6 rad), and a set of 12 "survey pins" (small filled circles with a
1.5px halo) fade in one by one between p = 0.25 and 0.75, each with a thin arc
connecting it to its predecessor — a route being drawn.
- The globe stroke colour lerps between the four beat colours. Add a soft radial
bloom behind the globe whose radius is driven by p.
- Performance: precompute the sphere point set once (lat rings of 18, lon rings of 36)
into typed arrays; per frame only rotate + project. Target 60fps at 1440x900.
C. THE RAIL
- A vertical progress rail fixed to the right edge, 2px wide, 34vh tall, centred:
track rgba(255,255,255,.12), fill a gradient that grows with p. Four 6px tick
marks sit at 0/25/50/75% and the active tick grows to 10px and turns solid white.
The rail is aria-hidden and purely decorative, but the *text* beats must be real
content in the DOM.
D. TEXT
- Beat 1 — "Nine degrees of nowhere" / "We began with a blank plate and a
suspicion that the coastline was lying."
- Beat 2 — "Survey, then argue" / "Twelve soundings, four disagreements, one
corrected map."
- Beat 3 — "The route writes itself" / "Each pin is a decision. The arc is the
consequence."
- Beat 4 — "Atlas Nine" / "Maps for places that are still deciding what they are."
- Beats are absolutely stacked in the same grid cell; opacity and a 24px translateY
are driven by p (no CSS transitions on them). Text must remain selectable and
readable when p is 0.
E. FRAME
- <header> a fixed, translucent, blurred top bar (background rgba(8,8,12,.55),
backdrop-filter blur(12px), 1px bottom hairline) with the wordmark "ATLAS NINE",
links "Work", "Studio", "Journal" and a bordered pill button "Start a map".
- <main> holds the pinned stage, then one closing section (min-height 70vh) with an
<h2> "Ready to redraw something?" and a large ghost button.
- <footer> three columns: wordmark + one-line description, "Studio" link list,
"Contact" link list; bottom row with the copyright.
- Palette: base #08080c, ink #f2f2f5, muted rgba(242,242,245,.6), accent #c084fc,
secondary #67e8f9. UI font: system stack. Display font: "Instrument Serif"
(Google Fonts) for the h1/h2 only.
F. MOTION SAFETY
- With prefers-reduced-motion: reduce, the section collapses to 100vh (via CSS), the
sticky behaviour is removed, all four beats are laid out sequentially as static
blocks, and the globe renders at a fixed camera with no rotation.
---
TECHNICAL CONTRACT (non-negotiable):
1. Deliver ONE single file: a complete, valid `index.html`. No build step, no bundler,
no framework, no package install. It must open correctly by double-clicking the file
and also over http://.
2. Vanilla only. No React, no Tailwind CDN, no GSAP, no Three.js, no jQuery, no Lottie.
Plain HTML + CSS + modern JavaScript in <style> and <script> tags.
3. NO external media. No .mp4, no .webm, no .gif, no remote images, no base64 blobs of
photos. Every visual must be generated procedurally with CSS gradients, SVG, or
<canvas>. This keeps the page free to host and impossible to break with a dead link.
4. Google Fonts is the ONLY permitted network request, and it must be declared with a
<link rel="preconnect"> plus a system-font fallback stack so the page still looks
intentional offline. If you prefer, skip it and use a pure system stack.
5. Responsive and correct at 390x844 (phone), 768x1024 (tablet) and 1440x900 (desktop).
No horizontal scrollbar at any of those widths. Nothing may overflow its container.
6. Accessibility: exactly one <h1>; real <header>/<main>/<footer>/<nav> landmarks; every
interactive element is a <button> or <a> with an accessible name; visible :focus-visible
ring; body text contrast ratio >= 4.5:1 against its background.
7. Motion safety: wrap all continuous motion in
`@media (prefers-reduced-motion: reduce)` rules that reduce it to a static or
near-static state. The page must remain fully readable and usable with motion off.
8. Performance: no layout thrash. Cache DOM reads, batch writes inside a single
requestAnimationFrame loop, use transform/opacity for animation, and never allocate
inside the hot loop. Target 60fps on a 2019 laptop. Total file under 90 KB.
9. Runtime telemetry hook (used by the automated verifier — implement it exactly):
at the top of your script define
window.__DEMO__ = { name: '<slug>', ready: false, frames: 0, effects: [], errors: [] };
then set `ready = true` after the first paint of your main effect, increment
`frames` once per animation frame inside your rAF loop, and push the name of each
implemented effect into `effects`. Add a `window.addEventListener('error', ...)`
handler that pushes messages into `errors`. Do not rename these fields.
10. Zero console output in the happy path. No console.log left behind. No uncaught
exceptions, no failed network requests.
RESPONSE FORMAT:
Return ONLY the raw HTML inside a single ```html fenced code block. No explanation, no
preamble, no commentary before or after the fence. The file must be complete and
immediately runnable — never truncate, never write "// rest of code here".Acceptance criteria (15)
- the pinned stage section is taller than 200vh
- a sticky element with height ~100vh exists
- canvas renders non-blank pixels
- all four beat texts are present in the DOM
- scroll progress drives a transform: a value changes measurably after scrolling 50% of the stage
- window.__DEMO__.ready becomes true within 5s
- window.__DEMO__.frames advances (>= 20 frames in 1.2s)
- window.__DEMO__.errors is empty
- zero console errors and zero page errors
- zero failed network requests
- exactly one <h1> with non-empty text
- header, main, footer landmarks present
- no horizontal overflow at 390 / 768 / 1440 widths
- no horizontal overflow after 2s of animation
- prefers-reduced-motion: reduce leaves the page usable and errors empty