# Lattice

> **Category:** Landing Page
> **Effect:** A slowly rotating gyroid wireframe lattice projected to depth-faded hairlines over a thin horizon glow
> **Platform:** cmd-code (`cmdcode`)
> **Model:** `xiaomi/mimo-v2.6-pro` — Xiaomi MiMo v2.6 Pro (xiaomi/mimo-v2.6-pro)
> **License:** Free / MIT — copy, paste, ship.
> **Self-contained:** yes — one `index.html`, no build step, no external media.

---

## 🎬 Live demo

Open [`public/demos/lattice/index.html`](../public/demos/lattice/index.html) — generated by the model
above from the prompt below, with no human edits to the markup.

## ✅ Verification

<!-- VERIFICATION:START -->
_Pending — this block is written by `tools/verify.mjs` after the build run._
<!-- VERIFICATION:END -->

## 📋 Prompt

```text
Build one complete, self-contained `index.html` for **Lattice** — a materials-science company that architects gyroid-phase ceramics. Document title: `Lattice — Matter, Architected`. `<html lang="en">`. ONE file only: plain HTML + CSS + JS in `<style>` and one `<script>` at end of body. No build step, no framework, no bundler, no React, Tailwind, GSAP, Three.js, Lottie, jQuery, or external scripts. NO EXTERNAL MEDIA: no mp4/webm/gif, no remote images, no base64 blobs, no third-party CDN URLs. Google Fonts is the ONLY permitted network request (Inter + IBM Plex Mono) with system fallback stacks. Every visible pixel comes from CSS gradients, inline SVG, text, or canvas.

**Viewport rule:** at widths `>= 901px` the page is one frame: `html, body { height:100%; overflow:hidden }` and `.page { height:100dvh; overflow:hidden }`. NO SCROLL on desktop. At widths `<= 900px` the page may scroll vertically. No horizontal overflow at 390x844, 768x1024, 1440x900, or 1920x1080, ever.

**Do NOT build:** no WebGL, no video, no audio, no images, no forms, no newsletter capture, no pricing table, no testimonials, no logos wall, no case-study carousel, no blog feed, no dashboard, no cookie banner, no scroll indicator, no hamburger on desktop (burger only at `<=900px`), no sections beyond the specified header / hero / footer, no footer links beyond the three specified, no second `<h1>`, no `console.log` in the shipped file.

---

## A. WHAT IS BEING BUILT

A single-viewport landing page for **Lattice**, a materials-science company. A full-bleed `<canvas>` renders a slowly rotating 3D wireframe lattice — a cubic node grid whose vertices are Newton-projected onto the gyroid isosurface `F = 0`, so the wireframe reads as an architected minimal surface, not a plain cube. A pinhole camera projects it to 2D; hairline edges are depth-bucketed and alpha-faded so far geometry dissolves and near geometry stays crisp. Behind it a thin horizon glow: a soft band plus a 1px core line dying at both ends. Everything is monochrome — one cold near-white on near-black — precise and technical. Over the field: a compact left-aligned type block (eyebrow, one H1 of two masked lines, one lead sentence, one mono spec readout, two buttons) and a hairline footer with batch status. Composition is asymmetric: type in the left 44%, lattice right-of-centre, never covered by text on desktop.

---

## B. THE BACKDROP — procedural gyroid lattice on canvas (the ONLY visual effect besides the grain)

A single `<canvas id="lattice">` fills the viewport: `position:fixed; inset:0; width:100%; height:100%; z-index:0; display:block; pointer-events:none`. Repainted every animation frame; NEVER a static image.

**Geometry, built ONCE at startup:**

- Node grid: `i, j, k ∈ 0..8` at `(x,y,z) = ((i-4)*0.62, (j-4)*0.62, (k-4)*0.62)`. Spacing 0.62, cube half-extent 2.48, exactly 729 nodes.
- Gyroid field with `a = 0.9`: `F(p) = sin(a x)cos(a y) + sin(a y)cos(a z) + sin(a z)cos(a x)`.
- Analytic gradient: `dF/dx = a cos(a x)cos(a y) - a sin(a z)sin(a x)`; `dF/dy = -a sin(a x)sin(a y) + a cos(a y)cos(a z)`; `dF/dz = -a sin(a y)sin(a z) + a cos(a z)cos(a x)`.
- Newton projection onto `F = 0`: `p' = p - 0.9 * F(p) * grad / |grad|^2`, once per node; skip when `|grad|^2 < 0.05`; clamp displacement length to 0.34. This bends the cubic grid into a gyroid-like saddle mesh.
- Edges: `+x`, `+y`, `+z` neighbours where they exist: `3 * 8 * 9 * 9 = 1944` edges. Nodes in `Float32Array(2187)`, edge pairs in `Uint16Array(3888)`. Nothing is allocated during animation.

**Camera and projection (per frame):**

- Rotation per node, in order: yaw about Y `theta = t * (2*PI/48)` (48s per revolution), pitch about X `phi = 0.42 + 0.10 * sin(t * 2*PI/74)`, roll about Z `psi = 0.06 * sin(t * 2*PI/90)`; `t` = seconds since start.
- Pinhole projection: camera at `z = CAM_Z = 9.0` on the origin; `depth = 9.0 - zRot`; `sx = cx + xRot * f / depth`, `sy = cy - yRot * f / depth`. Focal `f = min(h, w * 1.35) * zoom` with `zoom = 1.0`, or `0.88` at desktop max-height 850px, `0.78` at max-height 720px. Centre: desktop `cx = w * 0.635, cy = h * 0.54`; `<=900px` `cx = w * 0.5, cy = h * 0.42`. At 1440x900 with `f = h` the structure spans `5.56 * 900 / 9 = 556px` = 62% of viewport height, the designed size.

**Depth sorting and fade (the core look):**

- Per node store `sx`, `sy`, `depth`, validity in preallocated `Float32Array(729)` x3 + `Uint8Array(729)`; invalid when `depth < 5.8` or `depth > 12.4` (near cull + fog cutoff).
- Per edge mean depth `dm = (d0 + d1) / 2`; drop the edge if either endpoint is invalid. Bucket `b = floor((dm - 5.8) / (12.4 - 5.8) * 12)` clamped `0..11` (b0 = nearest); push the edge index into one of 12 preallocated `Uint16Array(1944)` buffers with an integer count each — zero allocation per frame.
- Paint buckets 11 (far) → 0 (near): one `beginPath()`, the bucket's `moveTo/lineTo` pairs, one `stroke()`. Exactly 12 stroke calls per frame.
- Stroke: `lineWidth = 1`, `lineCap = 'butt'`, colour `rgba(223, 233, 243, alpha)` where `alpha[b] = 0.03 + 0.29 * pow(1 - b / 11, 1.25)` (near ≈ 0.32, far ≈ 0.03). Cache the 12 `strokeStyle` strings at startup.

**Horizon glow (2 fill primitives, gradients cached on resize):**

- Band: `fillRect(0, cy - h*0.13, w, h*0.26)` with vertical gradient stops `rgba(223,233,243,0) 0%`, `rgba(223,233,243,0.11) 50%`, `rgba(223,233,243,0) 100%`.
- Core line: `fillRect(cx - w*0.36, cy - 0.5, w*0.72, 1)` with a horizontal linear gradient stopped `rgba(223,233,243,0) 0%`, `rgba(223,233,243,0.26) 50%`, `rgba(223,233,243,0) 100%`.
- Both draw BEFORE the wireframe and are built once in `resizeCanvas()` — `createLinearGradient` is never called inside the loop.

**Vignette (1 fill primitive, gradient cached on resize):** full-canvas radial gradient centred `(w/2, h/2)`, radius `max(w,h) * 0.78`, stops `rgba(3,5,8,0) 0%`, `rgba(3,5,8,0.18) 55%`, `rgba(3,5,8,0.62) 100%`, drawn last.

**Per-frame cost budget:** 729 node rotations (~18 flops each) + 729 perspective divides + up to 1944 moveTo/lineTo pairs + 12 strokes + 3 gradient fills = **15 draw calls, ≈ 45k flops per frame**, far under the 20-frames-per-1.2s bar. NEVER call `getBoundingClientRect` in the loop; cache `document.hidden`, canvas and context handles outside it. NEVER set `ctx.filter` or `ctx.shadowBlur`. The glow's softness comes from gradient stops alone.

**Resolution trick:** `canvas.width = cssW * dpr`, `canvas.height = cssH * dpr`, then `ctx.setTransform(dpr,0,0,dpr,0,0)`, with `dpr = Math.min(devicePixelRatio, 2)` at `>=901px`, `Math.min(devicePixelRatio, 1.5)` at `<=900px`. Resize is scheduled by one boolean + rAF flag, never a timer.

**Grain (CSS element):** `.grain` fixed, `inset:-50%; width:200%; height:200%; opacity:0.04`, data-URI inline-SVG `feTurbulence` (`type="fractalNoise" baseFrequency="0.85" numOctaves="2" stitchTiles="stitch"` + `feColorMatrix saturate 0`), tile 180px, `animation: grain 0.95s steps(4) infinite` among offsets `(0,0) (-2%,1%) (1%,-2%) (-1%,2%)`.

---

## C. LAYER STACK (back → front)

1. `html, body` — `#05070a`, with a base gradient `linear-gradient(180deg, #070a0e 0%, #05070a 46%, #030508 100%)` forced twice (see Failure Guards).
2. `canvas#lattice` — z-index 0: horizon glow, 12 depth-sorted wire batches, vignette.
3. `.scrim` — z-index 1, fixed: desktop `linear-gradient(90deg, rgba(5,7,10,0.88) 0%, rgba(5,7,10,0.66) 32%, rgba(5,7,10,0.10) 60%, rgba(5,7,10,0.34) 100%)` (text-protection left, structure breathing right); at `<=900px` `linear-gradient(180deg, rgba(5,7,10,0.55) 0%, rgba(5,7,10,0.20) 40%, rgba(5,7,10,0.80) 100%)`.
4. `.page` — z-index 2, `position:relative; display:grid; grid-template-rows:auto 1fr auto; min-height:100dvh`.
5. `.grain` — z-index 5, pointer-events none.
6. `.menu` — z-index 80 when open (`<=900px` only).
Focus rings stay above local stacking contexts.

---

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

```
--bg:            #05070a
--bg-deep:       #030508
--bg-top:        #070a0e
--text:          #eef3f8
--muted:         #97a4b0
--dim:           #616e7a
--accent:        #dfe9f3
--accent-dim:    rgba(223,233,243,0.55)
--accent-line:   rgba(223,233,243,0.30)
--accent-fill:   rgba(223,233,243,0.06)
--wire:          rgba(223,233,243,1)
--border:        rgba(255,255,255,0.14)
--border-soft:   rgba(255,255,255,0.08)
--logo:          15px
--logo-mark:     22px
--nav:           13px
--nav-h:         38px
--eyebrow:       11.5px
--h1:            62px
--lead:          16px
--spec:          12.5px
--btn:           13px
--btn-h:         42px
--foot:          12px
--header-y:      26px
--header-x:      44px
--hero-x:        44px
--hero-y:        40px
--hero-max:      640px
--hero-gap:      24px
--copy-gap-h1:   22px
--copy-gap-lead: 24px
--radius-btn:    4px
--focus-w:       2px
--focus-off:     3px
--mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
--sans:  "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif
--ease-out:      cubic-bezier(0.16,1,0.3,1)
--enter-dur:     0.95s
--grain-dur:     0.95s
```

Canvas constants mirror the tokens in JS: `NODES_N 9`, `SPACING 0.62`, `GYROID_A 0.9`, `NEWTON 0.9`, `DISPLACE_MAX 0.34`, `CAM_Z 9.0`, `DEPTH_NEAR 5.8`, `DEPTH_FAR 12.4`, `BUCKETS 12`, `YAW_PERIOD 48`, `PITCH_PERIOD 74`, `ROLL_PERIOD 90`, `DPR_MAX 2`, `DPR_MAX_MOBILE 1.5`.

Fonts (Google Fonts ONLY): `Inter:wght@400;500;600` and `IBM+Plex+Mono:wght@400;500`, one `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>` plus 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 "Lattice home"): inline SVG mark 22x22 — a wireframe unit cell: `<path d="M11 2 L20 7 L20 16 L11 21 L2 16 L2 7 Z M11 2 L11 11.5 M11 11.5 L20 7 M11 11.5 L2 7" fill="none" stroke="#dfe9f3" stroke-width="1.3" stroke-linejoin="miter"/>` — then the wordmark `lattice`, `var(--sans); font-size:var(--logo); font-weight:600; letter-spacing:0.14em; text-transform:lowercase; color:var(--text)`, then a blinking underscore `_` in `var(--accent)` (`blink` keyframes, `animation: blink 1.06s steps(1) infinite`).
- Centre: `nav` with exactly 4 links — `01 materials`, `02 process`, `03 research`, `04 index` — all `href="#"`, mono, `font-size:var(--nav)`; container `height:var(--nav-h); display:flex; align-items:center; gap:28px`. Numerals sit in `<span class="num">` at `color:var(--dim)`, words at `color:var(--muted)`. Hover over 0.18s ease: word → `var(--text)`, numeral → `var(--accent)`.
- Right: `a.cta` (href `#`), text `Request data sheet` — 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:var(--radius-btn); color:var(--text); background:var(--accent-fill)`. Hover 0.2s ease: `background:rgba(223,233,243,0.13); border-color:var(--accent-dim); box-shadow:0 0 18px rgba(223,233,243,0.14); transform:translateY(-1px)`.

**MAIN (hero)** — `display:flex; flex-direction:column; justify-content:center; align-items:flex-start; padding:var(--hero-y) var(--hero-x); width:100%`; inner `.hero-copy { width:100%; max-width:var(--hero-max) }`. Left-aligned, vertically centred in the 1fr row; on desktop it never exceeds 44vw so it cannot collide with the lattice at `cx = w*0.635`.

1. `.eyebrow` — mono, `font-size:var(--eyebrow); letter-spacing:0.18em; color:var(--dim); text-transform:uppercase`. Exact copy: `LATTICE MATERIALS · ARCHITECTED SOLIDS · EST. 2021`.
2. `h1` — EXACTLY ONE `<h1>`, `var(--sans); font-weight:600; font-size:var(--h1); letter-spacing:-0.045em; line-height:1.04; color:var(--text); margin:var(--copy-gap-h1) 0 0`. Two masked lines (see Motion), exact copy:
   Line 1: `Matter, architected.`
   Line 2: `Down to the unit cell.`
   Line 2 wraps `<em>unit cell</em>` styled `font-family:var(--mono); font-style:normal; font-weight:400; font-size:0.86em; letter-spacing:-0.02em; color:var(--accent)`.
3. `p.lead` — `var(--sans); font-size:var(--lead); line-height:1.6; letter-spacing:-0.012em; color:var(--muted); margin:var(--copy-gap-lead) 0 0; max-width:520px`. Exact copy: `Lattice grows gyroid-phase ceramics whose stiffness is programmed by geometry, then verified one unit cell at a time.`
4. `.spec` — mono, `font-size:var(--spec); color:var(--dim); margin-top:18px; white-space:nowrap`. Exact copy: `G-surface · Si–O system · ρ* 0.42 g/cm³ · E*/E_s 3.1`. The `G-surface` token is `color:var(--accent)`. At `<=560px` the `· E*/E_s 3.1` tail sits in `<span class="opt">` and is hidden so the line never wraps.
5. `.actions` — `display:flex; gap:12px; flex-wrap:wrap; margin-top:30px`. `a.btn.btn--primary` text `Request the data sheet`; `a.btn.btn--ghost` text `Read the method`. Both `href="#"`; treatments in section F.

**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 Lattice Materials`.
- Centre: mono `var(--foot)`, `color:var(--dim)`: `SEM batch 07 · released 12 MAR`, preceded by a 7px square status pixel `display:inline-block; margin-right:8px; background:var(--accent); box-shadow:0 0 8px var(--accent-dim)`.
- Right: exactly 3 links `papers`, `patents`, `contact` — mono `var(--foot)`, `color:var(--muted)`, `gap:22px`, `href="#"`, hover `color:var(--text)` over 0.18s.

---

## F. THE UI LANGUAGE

Two button treatments, both squared and instrument-like. `.btn` is `display:inline-flex; align-items:center; justify-content:center; height:var(--btn-h); padding:0 20px; border-radius:var(--radius-btn); font-family:var(--mono); font-size:var(--btn); letter-spacing:0.01em; text-decoration:none; transition: transform 200ms var(--ease-out), background 200ms var(--ease-out), border-color 200ms var(--ease-out), color 200ms var(--ease-out), box-shadow 200ms var(--ease-out)`.

- `.btn--primary`: `color:#05070a; font-weight:500; border:1px solid rgba(255,255,255,0.55); background:linear-gradient(180deg, #f4f8fc 0%, #dfe9f3 55%, #c3cfdb 100%); box-shadow:0 12px 30px rgba(223,233,243,0.12), inset 0 1px 0 rgba(255,255,255,0.7)`. Hover: `background:linear-gradient(180deg, #ffffff 0%, #eaf1f8 52%, #d0dbe6 100%); transform:translateY(-1px); box-shadow:0 16px 38px rgba(223,233,243,0.20), inset 0 1px 0 rgba(255,255,255,0.8)`. Active: `transform:translateY(0)`.
- `.btn--ghost`: `color:var(--text); border:1px solid var(--border); background:rgba(255,255,255,0.03)`. Hover: `border-color:var(--accent-line); background:rgba(223,233,243,0.07); transform:translateY(-1px)`. Active: `transform:translateY(0)`.

The header CTA reuses the ghost family with `background:var(--accent-fill)`. Nav and footer links are plain mono text with numeral prefixes — no pills, no backgrounds, no underlines. Focus: every `a`/`button` gets `:focus-visible { outline:var(--focus-w) solid var(--accent); outline-offset:var(--focus-off); border-radius:2px }`; `::selection { background:rgba(223,233,243,0.22); color:#ffffff }`.

---

## G. MOTION — entrance stagger + named keyframes

All entrance animations: `animation-duration:var(--enter-dur); animation-fill-mode:both; animation-timing-function:var(--ease-out); animation-delay:var(--d, 0.08s)`. Each animated element carries `.appear`, one modifier selecting keyframes, and 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.23s
nav link 3       appear--fade   0.30s
nav link 4       appear--fade   0.37s
header cta       appear--chip   0.44s
eyebrow          appear--type   0.52s
h1 line 1        appear--mask   0.66s
h1 line 2        appear--mask   0.80s
lead             appear--soft   0.98s   (duration 1.15s)
spec line        appear--soft   1.12s
primary button   appear--chip   1.24s
ghost button     appear--chip   1.32s
footer left      appear--fade   1.44s
footer centre    appear--fade   1.52s
footer right     appear--fade   1.60s
```

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(104%) }             to { opacity: 1; transform: none }
in-chip   from { opacity: 0; transform: translateY(8px) scale(0.97) }  to { opacity: 1; transform: none }
in-type   from { opacity: 0; clip-path: inset(0 100% 0 0) }            to { opacity: 1; clip-path: inset(0 -4% 0 0) }
blink     0%,49% { opacity: 1 }  50%,100% { opacity: 0 }
grain     0% { transform: translate(0,0) }  25% { transform: translate(-2%,1%) }
          50% { transform: translate(1%,-2%) }  75% { transform: translate(-1%,2%) }
```

H1 masking: each line is `<span class="line"><span class="line-inner appear appear--mask">…</span></span>` with `.line { display:block; overflow:hidden; padding:0.03em 0.06em 0.09em }`, `.line-inner { display:inline-block }`. `in-mask` rises out of that hard edge; `in-type` reveals the eyebrow left-to-right like a typed label. Canvas rotation and grain jitter run continuously, independent of the stagger.

---

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

```
>= 1600px:
  --h1: 74px  --logo: 16px  --nav: 13.5px  --btn: 13.5px  --btn-h: 44px
  --lead: 17px  --spec: 13px  --foot: 12.5px
  --header-y: 30px  --header-x: 60px  --hero-x: 60px  --hero-max: 700px

>= 1920px:
  --h1: 86px  --header-x: 76px  --hero-x: 76px  --hero-max: 780px
  --lead: 18px  --nav-h: 42px  --btn-h: 46px

1280px – 1599px:
  --h1: 56px  --header-x: 40px  --hero-x: 40px  --hero-max: 600px

901px – 1279px:
  --h1: 44px  --nav-h: 34px  --header-x: 30px  --hero-x: 30px
  --hero-max: 520px  --lead: 15px  --spec: 12px

>= 901px AND max-height 850px:
  --h1: 46px  --hero-y: 24px  --header-y: 18px  --lead: 15px
  --copy-gap-h1: 16px  --copy-gap-lead: 18px   (spec margin-top 14px, actions margin-top 22px)
  canvas zoom: 0.88

>= 901px AND max-height 720px:
  --h1: 38px  --hero-y: 14px  --header-y: 12px  --eyebrow: 11px
  --lead: 13.5px  --spec: 11.5px  --btn-h: 36px  --foot: 11px
  canvas zoom: 0.78

>= 901px (desktop lock, all heights):
  html, body { height:100%; overflow:hidden }   .page { height:100dvh; overflow:hidden }
  .scrim uses the 90deg gradient; hero-copy max-width never exceeds 44vw

<= 900px:
  no height lock; page may scroll; nav becomes a full-viewport overlay menu:
  - header keeps brand + burger <button class="burger" aria-label="Menu" aria-expanded="false">
    (three 18x1.5px bars in var(--text), gap 5px); the header CTA hides here
  - .menu { position:fixed; inset:0; z-index:80; background:rgba(5,7,10,0.94); backdrop-filter:blur(12px);
    display:flex; flex-direction:column; align-items:flex-start; justify-content:center; padding:0 32px;
    gap:24px; transform:translateY(-100%); transition:transform 0.45s var(--ease-out) }
  - .menu.open { transform:none }; links at --nav: 22px + the CTA chip; Escape, link click, or resize >900px all close it
  canvas: cx = w*0.5, cy = h*0.42, dpr cap 1.5
  --h1: 40px  --header-x: 24px  --hero-x: 24px  --hero-max: 100%  --hero-y: 64px

<= 560px:
  --h1: 32px  --lead: 14.5px  --spec: 11px  --eyebrow: 10.5px  --header-x: 20px  --hero-x: 20px
  .spec .opt { display:none }
  footer becomes flex-direction:column; align-items:flex-start; gap:10px
```

The `min-height:100dvh` stays at every breakpoint; only the `901px+` lock adds `overflow:hidden`. No element exceeds `100vw` at the four required viewports.

---

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

1. **Black-first:** the FIRST CSS rule is `html, body { background:#05070a !important; color:#eef3f8 }`; the body tag carries `style="background:#05070a;color:#eef3f8"`; a later rule restates `html, body { background:#05070a; background-image:linear-gradient(180deg,#070a0e 0%,#05070a 46%,#030508 100%) }`.
2. **Resting opacity is 1:** every `.appear` element's un-animated computed style is its final state (opacity 1, no transform, no clip-path). `fill-mode: both` only holds the FROM state during the delay. If animations fail or JS never runs, the page renders complete.
3. **Two-rAF entrance fallback:** after two nested rAF ticks, for every `.appear` run `el.getAnimations({subtree:false})`; if none reports `playState` "running" or "finished", add `.is-in` to ALL `.appear`. If `getAnimations` is missing, apply `.is-in` after the two rAFs. `.is-in { animation:none !important; opacity:1 !important; transform:none !important; clip-path:none !important; filter:none !important }`.
4. **Canvas fallback:** if `canvas.getContext('2d')` returns null or throws, add `.no-canvas` to `<html>`, push the string `'canvas-unavailable'` into `window.__DEMO__.errors`, and CSS paints a static wire substitute: `.no-canvas body { background-image: linear-gradient(rgba(255,255,255,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.06) 1px, transparent 1px); background-size: 56px 56px; background-position: center; }` plus `.no-canvas .scrim::after` a static 1px horizon `linear-gradient(90deg, transparent, rgba(223,233,243,0.22), transparent)` at `top:54%; height:1px`.
5. **Reduced motion:** inside `@media (prefers-reduced-motion: reduce)`: `*, *::before, *::after { transition:none !important; animation:none !important }`; force `.appear, .line-inner, .eyebrow` to `opacity:1 !important; transform:none !important; clip-path:none !important`; hide `.grain` and the cursor blink; the rAF loop keeps running (`frames` increments) with `t` frozen at 7.5s, painting one identical static frame per tick — readable, static, never blank.
6. **Zero console output:** no `console.*` calls, no thrown exceptions, every canvas call guarded, the geometry builder wrapped so a maths failure degrades to the raw cubic grid.

---

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

1. **Telemetry first, before anything else:**
   `window.__DEMO__ = { name: 'lattice', ready: false, frames: 0, effects: [], errors: [] };`
   Push `'gyroid-lattice'`, `'horizon-glow'`, `'grain-texture'` into `effects` in that order, exactly once. Then `window.addEventListener('error', e => window.__DEMO__.errors.push(String(e.message || e)))`.
2. **Build geometry once:** `pts = new Float32Array(2187)`, `idx = new Uint16Array(3888)`, working `rx/ry/rz` and `sx/sy/dd` `Float32Array(729)`, `ok = new Uint8Array(729)`. Fill the 9x9x9 grid at spacing 0.62, Newton-project per section B inside try/catch (on failure keep raw grid points), build the 1944 edge pairs (`+x/+y/+z` neighbours). Allocate 12 `Uint16Array(1944)` bucket buffers + counts and 12 cached `'rgba(223,233,243,' + alpha[b] + ')'` strings with `alpha[b] = 0.03 + 0.29 * Math.pow(1 - b/11, 1.25)`.
3. **Canvas setup:** grab `#lattice`, guard the 2d context (null → `.no-canvas` + one errors push). `resizeCanvas()`: read `innerWidth/innerHeight` (never getBoundingClientRect), `dpr = Math.min(devicePixelRatio, innerWidth <= 900 ? 1.5 : 2)`, set the backing store, `ctx.setTransform(dpr,0,0,dpr,0,0)`, rebuild the three cached gradients and `cx, cy, f` per section B. Resize queues via a boolean + rAF only.
4. **Render loop:** one `requestAnimationFrame` callback. Each tick: `window.__DEMO__.frames++` once; if `document.hidden`, skip paint but keep the loop alive; `t = reduced ? 7.5 : ms/1000`; compute `theta, phi, psi` per section B (sin/cos computed 3 times per frame, not per node); rotate all 729 nodes into `rx/ry/rz`; project to `sx/sy/dd`; validity `5.8 < depth < 12.4`; reset bucket counts, bucket every edge by mean depth; `clearRect(0,0,w,h)`; paint horizon band, horizon core; for `b = 11..0`: `beginPath()`, `moveTo/lineTo` the bucket, `stroke()` at `lineWidth 1`; paint the vignette. On the FIRST completed paint set `window.__DEMO__.ready = true`. NO allocation, NO getBoundingClientRect, NO ctx.filter, NO ctx.shadowBlur, NO gradient recreation in the loop.
5. **Entrance fallback:** two nested rAFs after DOMContentLoaded, then the `.appear` `getAnimations` check and `.is-in` sweep per I.3.
6. **Mobile menu (`<=900px` only):** burger toggles `.menu.open` + `aria-expanded`; Escape, any link click, or resize to `>900px` closes.
7. **Reduced motion:** read `matchMedia('(prefers-reduced-motion: reduce)')` once at startup; when true freeze `t` at 7.5 (loop and `frames` keep running) and hide `.grain` via CSS.

No other JS. No libraries. No timers. 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".
```

---

*LandingPagePrompts Prompt Library — free, verified, model-attributed.*
