Skip to content
LandingPagePrompts
Landing Pagekimi-k3verified 20/20confirmed cross-check

Grid Collapse

A perspective grid collapsing toward a vanishing point under terminal type

The exact prompt

Build one complete, self-contained `index.html` for **Vector** — a developer platform landing page. Document title: `Vector — Grid Collapse`. `<html lang="en">`. ONE file only: plain HTML + CSS + JS in `<style>`/`<script>`. No frameworks, no bundlers, no external media of any kind (no images, no video, no fonts other than Google Fonts, no CDN scripts). Every visual pixel is generated by CSS gradients, SVG, or `<canvas>`.

**Viewport rule:** on viewports `>= 901px` wide the page is a single frame: `html, body { height:100%; overflow:hidden }` and `.page { height:100dvh; overflow:hidden }`. NO SCROLL on desktop. On viewports `<= 900px` the page may scroll vertically. No horizontal overflow at any width.

**Do NOT build:** no video, no WebGL, no Three.js, no Lottie, no GSAP, no jQuery, no Tailwind, no React, no images (remote or base64), no audio, no forms, no pricing tables, no testimonials, no extra sections beyond the specified header / hero / footer, no footer links beyond the three specified, no cookie banner, no scroll indicator, no hamburger menu on desktop (menu only on `<=900px`).

---

## A. WHAT IS BEING BUILT

A single-viewport landing page for a developer platform called **Vector**. A full-bleed `<canvas>` renders an infinite perspective grid — a floor plane of hairlines receding toward a vanishing point at the vertical centre of the upper third, continuously accelerating toward the viewer (classic infinite-tunnel). The grid fades to pure black in the distance via a radial mask. Overlaid on top: monospace terminal-style type — a small prompt line, a large geometric-sans H1, a single-line muted "output" line. A thin horizontal scanning line sweeps the viewport top-to-bottom every 4 seconds. The aesthetic is black-terminal, precision-engineered, cinematic.

---

## B. THE BACKDROP — procedural canvas grid (the ONLY visual effect besides the scanline)

A single `<canvas id="grid">` fills the viewport: `position:fixed; inset:0; width:100%; height:100%; z-index:0; display:block`. It is repainted every animation frame via `requestAnimationFrame`. It is NEVER a static image.

**Projection maths (canvas 2D, no libraries):**

- Camera at origin looking down -Z. Screen centre `(cx, cy)` where `cx = w/2`, `cy = h * 0.42` (vanishing point sits at 42% of viewport height — the upper-third anchor).
- Focal length `f = h * 0.9`.
- World floor plane at `y = 1` (camera height 1 unit).
- A world point `(x, 1, z)` with `z > 0` projects to screen:
  `sx = cx + (x * f) / z`
  `sy = cy + (f * 1) / z`   (floor projects below the horizon line `cy` because screen y grows downward and the camera looks level; the "+1" camera height pushes floor lines down)
- Depth range drawn: `z` from `zNear = 0.6` to `zFar = 80`.

**Longitudinal lines (the rails converging at the vanishing point):**

- `N = 26` lines, world x positions `x_i = (i - (N-1)/2) * 1.6` for `i = 0..25` (symmetric fan from `-20` to `+20` in world units, spacing 1.6).
- Each line is drawn as a polyline of 60 segments sampling z exponentially from `zNear` to `zFar`: `z_k = zNear * (zFar/zNear)^(k/59)`. Exponential sampling keeps segment density high near the camera where perspective stretches.
- Stroke colour: `rgba(110, 231, 168, alpha)` — the accent `#6ee7a8` — where alpha per segment is `0.34 * (1 - z_k/zFar)^1.6`. LineWidth 1px. Near segments are brightest; the far ones die out.

**Transverse lines (the rungs rushing toward the viewer):**

- Rungs sit at world depths `z_j = zNear * step^j` with `step = 1.14`, for all `j` with `z_j <= zFar` (≈ 38 rungs).
- Each rung is a horizontal line across the floor at depth `z_j`: project `(-22, 1, z_j)` and `(22, 1, z_j)` to screen and connect.
- Motion: a continuous phase `p = (t * speed) % 1` where `t` is seconds since start and `speed = 0.55`. Effective rung depth is `z_j' = z_j / step^p` — i.e. every rung's depth divides by the step as phase advances, so rungs accelerate toward the camera and wrap seamlessly (the classic infinite-corridor trick: when `p` wraps, rung `j` takes the exact place rung `j+1` held).
- Rung alpha: `0.30 * (1 - z_j'/zFar)^1.5`, same accent colour, 1px.

**Radial distance mask:** after all grid strokes, paint a radial gradient overlay centred at `(cx, cy)`: `rgba(0,0,0,0)` at radius 0 → `rgba(0,0,0,0)` at `r1 = min(w,h)*0.28` → `rgba(0,0,0,0.55)` at `r2 = min(w,h)*0.62` → `rgba(0,0,0,0.96)` at `r3 = max(w,h)*0.95`. This kills the grid at the edges and far distance so the frame fades to black.

**Vignette:** a second full-canvas radial gradient centred at screen centre: transparent to `rgba(0,0,0,0.42)` at the corners, drawn last, to deepen the cinematic frame.

**Frame budget:** total strokes per frame ≈ 26 polylines × 60 segments + ~38 rungs ≈ 1600 line segments. Use a single `ctx.beginPath()` per alpha bucket (bucket alphas into 8 quantised levels, one path per level) to keep draw calls under 10 per frame. Cap the device pixel ratio at `Math.min(window.devicePixelRatio, 2)`. Target 60fps; the loop must drop nothing — if `document.hidden`, skip painting but keep the rAF alive.

**Resize:** on window resize, set `canvas.width = innerWidth * dpr`, `canvas.height = innerHeight * dpr`, `ctx.setTransform(dpr,0,0,dpr,0,0)`, repaint immediately.

**Scanline (CSS, separate element):** a `div.scanline`, `position:fixed; left:0; right:0; height:1px; top:0; z-index:3; pointer-events:none; background: linear-gradient(90deg, transparent, rgba(110,231,168,0.55) 18%, rgba(110,231,168,0.55) 82%, transparent); box-shadow: 0 0 12px rgba(110,231,168,0.35)`. Keyframes `scan`: `0% { transform: translateY(-2vh); opacity:0 } 6% { opacity:1 } 92% { opacity:1 } 100% { transform: translateY(102vh); opacity:0 }`, `animation: scan 4s linear infinite`.

---

## C. LAYER STACK (back → front)

1. `html, body` — pure black `#000000`, forced twice (see Failure Guards).
2. `canvas#grid` — z-index 0, the perspective grid + radial masks + vignette.
3. `.scrim` — z-index 1, a fixed `linear-gradient(180deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.15) 34%, rgba(0,0,0,0.12) 62%, rgba(0,0,0,0.72) 100%)` guaranteeing text contrast top and bottom.
4. `.page` — z-index 2, `position:relative; display:grid; grid-template-rows: auto 1fr auto; min-height:100dvh` (height:100dvh inside the desktop lock).
5. `.scanline` — z-index 3, above content (it sweeps over everything, terminal-style).
6. `.grain` — z-index 4, `position:fixed; inset:-50%; width:200%; height:200%; pointer-events:none; opacity:0.05`, an inline SVG `feTurbulence` noise (`<svg><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" stitchTiles="stitch"/><feColorMatrix type="saturate" values="0"/></filter><rect width="100%" height="100%" filter="url(#n)"/></svg>` as a data-URI background, tile size 160px), animated with `grain 0.9s steps(4) infinite` jittering `transform: translate` among 4 offsets `(0,0) (-2%,1%) (1%,-2%) (-1%,2%)`.

---

## D. TOKEN TABLE (defaults at ~1440px)

```
--bg:            #000000
--text:          #f2f2f2
--muted:         #8f8f8f
--dim:           #5c5c5c
--accent:        #6ee7a8
--accent-dim:    rgba(110,231,168,0.55)
--accent-line:   rgba(110,231,168,0.28)
--border:        rgba(255,255,255,0.14)
--border-soft:   rgba(255,255,255,0.09)
--logo:          15px
--logo-mark:     20px
--nav:           13.5px
--nav-h:         38px
--btn:           13.5px
--btn-h:         42px
--prompt:        13px
--h1:            56px
--output:        14px
--foot:          12.5px
--header-y:      24px
--header-x:      40px
--hero-x:        40px
--hero-y:        48px
--copy-max:      880px
--prompt-max:    560px
--mono:          "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
--sans:          "Inter", system-ui, -apple-system, "Segoe UI", sans-serif
```

Fonts (Google Fonts ONLY, with fallbacks as listed): `Inter:wght@400;500;600` and `JetBrains+Mono:wght@400;500`, loaded with one `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>` and one stylesheet link with `display=swap`.

---

## E. THE COMPOSITION — exact build order

**.page** is the grid container with three rows: `<header>`, `<main>`, `<footer>`.

**HEADER** — `padding: var(--header-y) var(--header-x); display:flex; align-items:center; justify-content:space-between; gap:24px`.

- Left: `a.brand` (href `#`, accessible name "Vector home"): an inline SVG mark 20×20 — a right-pointing chevron built from two strokes: `<path d="M4 3 L16 10 L4 17" fill="none" stroke="#6ee7a8" stroke-width="2.2" stroke-linecap="square"/>` — followed by the word `vector` in `var(--mono)`, `font-size: var(--logo); font-weight:500; color:var(--text); letter-spacing:0.02em`. The underscore cursor `_` after the wordmark, `color: var(--accent)`, blinking: keyframes `blink` `0%,49% {opacity:1} 50%,100% {opacity:0}`, `animation: blink 1.06s steps(1) infinite`.
- Centre: `nav` with exactly 4 links: `docs`, `runtime`, `pricing`, `changelog` — all `href="#"`, mono, `font-size: var(--nav); color: var(--muted)`, each preceded by `./` in `var(--dim)` (so they read `./docs` etc.). Height `var(--nav-h)`, `display:flex; align-items:center; gap:30px`. Hover: `color: var(--accent)` over 0.18s ease; the `./` stays dim.
- Right: `a.cta` (href `#`) — the header button, text `$ npx vector init`, mono, `font-size: var(--btn); height: var(--btn-h); padding: 0 18px; display:inline-flex; align-items:center; border:1px solid var(--accent-line); border-radius:6px; color:var(--accent); background: rgba(110,231,168,0.06)`. Hover: `background: rgba(110,231,168,0.14); border-color: var(--accent-dim); box-shadow: 0 0 16px rgba(110,231,168,0.18)`, 0.2s ease.

**MAIN (hero)** — `display:flex; flex-direction:column; justify-content:center; align-items:flex-start; padding: var(--hero-y) var(--hero-x); max-width: var(--copy-max); margin: 0 auto; width:100%`. Left-aligned, vertically centred within the 1fr row — NOT bottom-anchored, NOT right-aligned.

1. `.prompt` — mono, `font-size: var(--prompt); color: var(--dim); max-width: var(--prompt-max)`. Exact copy: `hongvin@vector:~$ deploy --grid --watch`. The `hongvin@vector` part is `color: var(--accent)`; `:~$` is `var(--dim)`; the command is `var(--text)`. After the command, a block cursor `▉` (U+2589) in `var(--accent)` with the same `blink` animation.
2. `h1` — EXACTLY ONE `<h1>`, `var(--sans)`, `font-weight:600; font-size: var(--h1); letter-spacing:-0.04em; line-height:1.06; color:var(--text); margin: 22px 0 0`. Two masked lines (see Motion), exact copy:
   Line 1: `Ship to the grid.`
   Line 2: `Collapse the distance.`
   Line 2 contains `<em>distance</em>` styled `font-family: var(--mono); font-style:normal; font-weight:400; font-size:0.92em; color: var(--accent); letter-spacing:-0.02em`.
3. `.output` — mono, single line, `font-size: var(--output); color: var(--muted); margin-top:26px; white-space:nowrap`. Exact copy: `[ok] 214 nodes online · median latency 11ms · build 0.9.4`. The `[ok]` token is `color: var(--accent)`. On narrow screens it must shrink (see responsive), never wrap, never overflow.

**FOOTER** — `padding: 22px var(--header-x); display:flex; align-items:center; justify-content:space-between; gap:20px; border-top:1px solid var(--border-soft)`.

- Left: mono `var(--foot)`, `color: var(--dim)`: `© 2026 Vector Systems`.
- Centre: mono `var(--foot)`, `color: var(--dim)`: `status: all systems nominal` with a 7px square `background: var(--accent); box-shadow: 0 0 8px var(--accent-dim)` inline before it (a status pixel, `display:inline-block; margin-right:8px`).
- Right: exactly 3 links `github`, `x`, `irc` — mono, `var(--foot)`, `color: var(--muted)`, `gap:22px`, href `#`, hover `color: var(--text)`.

---

## F. THE UI LANGUAGE

There is exactly ONE button style in the page (the header CTA) — the terminal-chip: 6px radius, 1px `var(--accent-line)` border, 6% accent fill, mono text, accent glow on hover as specified above. Nav links are plain mono text with the `./` prefix convention — no pills, no backgrounds, no underline ever; hover swaps colour to accent. Footer links: same treatment in `var(--foot)`. Every interactive element gets `:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px }`. Selection: `::selection { background: rgba(110,231,168,0.28); color: #fff }`. No element on the page ever shows a default blue focus ring or a text underline.

---

## G. MOTION — entrance stagger + named keyframes

All entrance animations: `animation-duration: 0.95s; animation-fill-mode: both; animation-timing-function: cubic-bezier(0.16,1,0.3,1); animation-delay: var(--d, 0.08s)`. The `.appear` class is applied to each animated element with a modifier class selecting keyframes and an inline `--d`.

Stagger table (element → modifier → --d):
```
brand          appear--fade   0.08s
nav link 1     appear--fade   0.16s
nav link 2     appear--fade   0.24s
nav link 3     appear--fade   0.32s
nav link 4     appear--fade   0.40s
header cta     appear--chip    0.46s
prompt line    appear--type    0.55s
h1 line 1      appear--mask    0.70s
h1 line 2      appear--mask    0.86s
output line    appear--soft    1.06s  (duration 1.2s)
footer left    appear--fade    1.22s
footer centre  appear--fade    1.30s
footer right   appear--fade    1.38s
```

Named keyframes (all end at opacity 1 / identity transform):
```
in-fade   from { opacity: 0 }                                          to { opacity: 1 }
in-soft   from { opacity: 0; transform: translateY(12px) }             to { opacity: 1; transform: none }
in-mask   from { opacity: 0; transform: translateY(105%) }             to { opacity: 1; transform: none }
in-chip   from { opacity: 0; transform: translateY(10px) scale(0.96) } to { opacity: 1; transform: none }
in-type   from { opacity: 0; clip-path: inset(0 100% 0 0) }            to { opacity: 1; clip-path: inset(0 -5% 0 0) }
```

The H1 masking: each line of the H1 is wrapped in `<span class="line"><span class="line-inner appear appear--mask">…</span></span>` where `.line { display:block; overflow:hidden; padding: 0.04em 0.1em 0.10em }` and `.line-inner { display:inline-block }`. The `in-mask` translateY(105%) is clipped by the parent's overflow — the type rises out of a hard edge, terminal-style.

`in-type` on the prompt line reveals it left-to-right via clip-path, like a command being echoed.

The canvas grid, scanline, blink, and grain animations run continuously, independent of the entrance stagger.

---

## H. RESPONSIVE TABLE — token overrides per breakpoint (exact)

```
>= 1600px:
  --h1: 72px  --logo: 16px  --nav: 14.5px  --nav-h: 42px  --btn: 14.5px
  --btn-h: 46px  --prompt: 14px  --output: 15px  --foot: 13px
  --header-y: 28px  --header-x: 56px  --hero-x: 56px  --copy-max: 1020px

>= 1920px:
  --h1: 84px  --header-x: 72px  --hero-x: 72px  --copy-max: 1140px
  --output: 16px  --prompt: 15px

1280px – 1599px:
  --h1: 62px  --header-x: 48px  --hero-x: 48px  --copy-max: 920px

901px – 1279px:
  --h1: 46px  --nav-h: 34px  --header-x: 32px  --hero-x: 32px
  --copy-max: 760px  --output: 13px

>= 901px AND max-height 850px:
  --h1: 42px  --hero-y: 28px  --header-y: 18px  (h1 margin-top 16px, output margin-top 18px)

>= 901px AND max-height 720px:
  --h1: 36px  --hero-y: 18px  --header-y: 14px  --prompt: 12px  --output: 12.5px

>= 901px (desktop lock, all heights):
  html, body { height:100%; overflow:hidden }  .page { height:100dvh; overflow:hidden }

<= 900px:
  no vh lock; page may scroll; nav becomes a full-viewport overlay menu:
  - header keeps brand + burger button (<button class="burger" aria-label="Menu" aria-expanded="false">, three 18×1.5px bars in var(--text), gap 5px)
  - .menu { position:fixed; inset:0; z-index:10; background: rgba(0,0,0,0.92); backdrop-filter: blur(14px); display:flex; flex-direction:column; align-items:flex-start; justify-content:center; padding: 0 32px; gap: 26px; transform: translateY(-100%); transition: transform 0.45s cubic-bezier(0.16,1,0.3,1) }
  - .menu.open { transform: none }
  - menu shows the 4 nav links stacked at --nav: 22px plus the CTA chip; Escape key, link click, and resize >900px all close it
  --h1: 40px  --header-x: 24px  --hero-x: 24px  --copy-max: 100%

<= 560px:
  --h1: 32px  --prompt: 11.5px  --output: 11.5px  --header-x: 20px  --hero-x: 20px
  footer becomes flex-direction:column; align-items:flex-start; gap:10px
  header CTA is hidden (it lives inside the menu on mobile)
```

Canvas constants also scale: below 901px the vanishing point `cy = h * 0.40`, longitudinal line count `N = 18`, rung step unchanged. The `.output` line at <=560px drops the `· build 0.9.4` segment via a `<span class="opt">` hidden at that breakpoint (so the line never overflows).

---

## I. FAILURE GUARDS — the page is never blank, never broken

1. **Black-first:** the FIRST CSS rule is `html, body { background:#000000 !important; color:#f2f2f2 }`; the body tag carries `style="background:#000;color:#f2f2f2"`; a later rule restates `html, body { background:#000000; background: var(--bg, #000000) }`.
2. **Resting opacity is 1:** every `.appear` element's un-animated computed style is its final state (opacity 1, no transform). Animations use `fill-mode: both` only to hold the FROM state during the delay. If CSS animations fail or JS never runs, the page renders complete and readable.
3. **rAF entrance fallback:** after two `requestAnimationFrame` ticks, if `el.getAnimations()` reports nothing running or finished for any `.appear`, JS adds `.is-in` to every `.appear` (`.is-in { animation:none !important; opacity:1 !important; transform:none !important; clip-path:none !important }`).
4. **Canvas fallback:** if `canvas.getContext('2d')` returns null, JS adds class `.no-canvas` to `<html>` and CSS paints a static CSS-only grid: `.no-canvas body { background-image: linear-gradient(var(--border-soft) 1px, transparent 1px), linear-gradient(90deg, var(--border-soft) 1px, transparent 1px); background-size: 48px 48px; }` — a flat hairline grid so the page still reads as "grid", statically.
5. **Reduced motion:** inside `@media (prefers-reduced-motion: reduce)`: `*, *::before, *::after { transition:none !important; animation:none !important }`; force `.appear, .line-inner, .prompt, .output` to `opacity:1 !important; transform:none !important; clip-path:none !important`; the canvas loop still runs but with `speed = 0` (grid drawn once, static) and the scanline is `display:none`.
6. **No console output** in the happy path: no `console.log`, no thrown exceptions; every canvas call guarded.

---

## J. THE EXACT JS (only this, vanilla, one IIFE at end of body)

1. **Telemetry first, before anything else:**
   `window.__DEMO__ = { name: 'grid-collapse', ready: false, frames: 0, effects: [], errors: [] };`
   Push `'perspective-grid'` and `'scanline'` into `effects` once implemented. Add `window.addEventListener('error', e => window.__DEMO__.errors.push(String(e.message || e)))`.
2. **Canvas loop:** get `#grid`, get 2d context (guard null → `.no-canvas` path), implement the projection maths from section B verbatim (26 lines, 60 segments, exponential z sampling, step 1.14, speed 0.55, 8 alpha buckets). Each frame: increment `window.__DEMO__.frames` by exactly 1, clear to `#000`, draw lines, rungs, radial mask, vignette. On the FIRST completed paint set `window.__DEMO__.ready = true`. Loop with `requestAnimationFrame`; skip painting while `document.hidden` but keep the loop alive.
3. **Resize handler** (debounced via rAF flag): resize canvas backing store with dpr cap 2, repaint.
4. **Entrance fallback:** two nested rAFs after DOMContentLoaded, then per `.appear`: if `el.getAnimations` exists and returns no running/finished animations, add `.is-in` to all `.appear` elements.
5. **Menu (mobile only):** burger click toggles `.menu.open` and `aria-expanded`; Escape closes; any menu link click closes; window resize to >900px closes.
6. **prefers-reduced-motion:** read `matchMedia('(prefers-reduced-motion: reduce)')` once at startup; if true, set grid speed to 0 (paint one static frame, still set ready, still count that 1 frame).

No other JS. No libraries. No console calls.

---

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)

  1. exactly one <h1> split into exactly 2 masked line elements (.line > .line-inner) with text 'Ship to the grid.' and 'Collapse the distance.'
  2. canvas#grid paints non-blank content and keeps repainting (pixel hash changes across frames) at 1440x900
  3. window.__DEMO__.ready becomes true, window.__DEMO__.frames increments in the rAF loop, effects contains 'perspective-grid' and 'scanline', errors stays empty
  4. a .scanline element sweeps the viewport with a 4s linear infinite animation, and exactly 4 nav links + 3 footer links exist with accessible names
  5. no horizontal overflow at 390px, 768px, 1440px, or 1920px; at >=901px body has overflow:hidden and .page height is 100dvh
  6. window.__DEMO__.ready becomes true within 5s
  7. window.__DEMO__.frames advances (>= 20 frames in 1.2s)
  8. window.__DEMO__.errors is empty
  9. zero console errors and zero page errors
  10. zero failed network requests
  11. exactly one <h1> with non-empty text
  12. header, main, footer landmarks present
  13. no horizontal overflow at 390 / 768 / 1440 widths
  14. no horizontal overflow after 2s of animation
  15. prefers-reduced-motion: reduce leaves the page usable and errors empty

More landing page prompts