Skip to content
LandingPagePrompts
Landing Pagexiaomi/mimo-v2.6-proverified 20/20partial cross-check

Halide

A camera iris of 8 SVG aperture blades breathing open and shut inside a rotating conic lens ring, with lens bloom and chromatic fringe

The exact prompt

## A. WHAT IS BEING BUILT

Build one complete, valid index.html — a single-viewport cinematic landing page for **Halide**, a computational photography-AI company. Document title: "Halide — Night is Undeveloped Light". html lang="en". ONE file: plain HTML + one style block + one vanilla script at end of body. No build step, no framework, no bundler.

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

**Do NOT build:** no video, mp4/webm/gif, remote images, base64 photo blobs, third-party CDN URLs, WebGL, Three.js, GSAP, Lottie, jQuery, Tailwind, React, audio, forms, pricing tables, testimonials, blog, cookie banner, scroll indicator, modal dialogs, gallery grids, extra sections beyond header / hero / stats footer, footer content beyond the three specified stats, desktop hamburger (burger only <= 900px). Google Fonts is the ONLY network request.

**The single visual idea:** a black photographic darkroom, one light behind one lens. Centre-upper: a camera iris — 8 SVG aperture blades on barrel-ring pivots, breathing open and shut over 16s. A conic-gradient focus ring turns around it like a lens barrel (28s); a warm lens bloom breathes behind (9s); a 6px red/cyan chromatic fringe drifts on the blade rim (34s). Dust motes rise through the bloom on a tiny canvas. Over the optics: a bottom-anchored hero (eyebrow chip, two masked serif headline lines, lead, two buttons) and a three-item stats footer on a hairline.

---

## B. THE BACKDROP — the optical stack (counts, blur radii, gradient stops, drift periods)

Everything behind the type lives in one fixed .backdrop at inset:0, overflow:hidden, pointer-events:none, z-index:0.

1. **.room-bg** (inset:0, z-index:0) background layers, back to front: radial-gradient(ellipse 130% 85% at 50% 40%, rgba(255,180,84,0.10) 0%, rgba(255,150,60,0.045) 30%, transparent 62%); radial-gradient(circle at 50% 40%, rgba(255,240,214,0.06) 0%, transparent 26%); linear-gradient(180deg, #07060a 0%, #0b0908 38%, #060505 72%, #030202 100%).
2. **.bloom** (z-index:1) centred (left:50%, top:var(--iris-top), translate(-50%,-50%)), size calc(var(--iris) * 1.55); background:radial-gradient(circle, rgba(255,214,150,0.50) 0%, rgba(255,180,84,0.26) 24%, rgba(255,140,50,0.10) 46%, transparent 72%); filter:blur(48px); opacity:0.76; animation bloom-pulse 9s ease-in-out infinite (opacity 0.62 <-> 0.90, scale 1 <-> 1.05).
3. **.bloom-core** (z-index:2) size calc(var(--iris) * 0.5), same centring; background:radial-gradient(circle, rgba(255,240,214,0.55) 0%, rgba(255,196,120,0.18) 38%, transparent 70%); filter:blur(18px); opacity:0.8.
4. **.flare** (z-index:3) left:50%; top:var(--iris-top); width:150vw; height:3px; transform:translate(-50%,-50%) rotate(-2.4deg); background:linear-gradient(90deg, transparent 0%, rgba(122,200,255,0.20) 20%, rgba(255,220,170,0.42) 50%, rgba(255,92,122,0.18) 80%, transparent 100%); filter:blur(7px); opacity:0.42; animation flare-drift 22s ease-in-out infinite (translateX -2.5% <-> +2.5%, rotate -2.4deg <-> 1.6deg, opacity 0.34 <-> 0.58).
5. **.iris-ring** (z-index:5, the rotating conic ring) size var(--iris), centred, border-radius:50%, background:conic-gradient(from 0deg, rgba(255,180,84,0.00) 0deg, rgba(255,180,84,0.55) 40deg, rgba(255,240,214,0.85) 72deg, rgba(255,180,84,0.10) 130deg, rgba(255,180,84,0.00) 190deg, rgba(90,209,255,0.28) 250deg, rgba(255,180,84,0.42) 305deg, rgba(255,180,84,0.00) 360deg); mask plus -webkit-mask radial-gradient(circle, transparent 0 calc(50% - 26px), #000 calc(50% - 25px) 50%, transparent calc(50% + 1px)) — a 26px ring; rest rotate(24deg); animation ring-spin 28s linear infinite.
6. **.iris-ticks** (z-index:6) same box; background:repeating-conic-gradient(from 0deg, rgba(255,236,210,0.55) 0deg 0.5deg, transparent 0.5deg 7.5deg) — exactly 48 ticks; outer 8px mask ring radial-gradient(circle, transparent 0 calc(50% - 9px), #000 calc(50% - 8px) 50%, transparent calc(50% + 1px)); opacity:0.28; animation ticks-spin 40s linear infinite reverse.
7. **svg.iris** (z-index:7) viewBox "0 0 400 400" on a wrapper of size calc(var(--iris) * 0.72) centred on the axis. Exactly 8 blades. Barrel: <circle cx="200" cy="200" r="150" fill="none" stroke="rgba(255,196,120,0.22)" stroke-width="1.2"/>. Blade path, instanced 8 times: d="M200 50 A150 150 0 0 1 324.4 116.1 Z" — a circular segment: pivot corner (200,50) at the barrel top, leading corner (324.4,116.1), 56 degrees of arc clockwise. Each instance: outer <g transform="rotate(k * 45, 200, 200)"> for k = 0..7 wrapping <g class="blade"> with transform-box:view-box; transform-origin:200px 50px; transform:rotate(30deg) at rest; animation aperture-breathe 16s ease-in-out infinite. Keyframes swing rotate(10deg) (aperture radius 118 in the 400 viewBox, open) to rotate(56deg) (radius 16, nearly shut); the 8 straight leading edges form the opening. Blade fill <linearGradient id="bladeMetal" x1="0" y1="0" x2="1" y2="1"> stops offset 0 #35302a, 0.55 #1c1916, 1 #0f0d0b (fill url(#bladeMetal)); stroke="rgba(255,196,120,0.30)" stroke-width="1.2". Hairline rim <circle cx="200" cy="200" r="118" fill="none" stroke="rgba(90,209,255,0.16)" stroke-width="1"/>.
8. **.glass** (z-index:8) svg.iris box, border-radius:50%, background:radial-gradient(circle at 34% 28%, rgba(255,255,255,0.16) 0%, transparent 40%), radial-gradient(circle at 50% 50%, rgba(90,209,255,0.10) 0%, transparent 60%); box-shadow: inset 0 0 60px rgba(0,0,0,0.55).
9. **.fringe** (z-index:9, chromatic fringe) size calc(var(--iris) * 0.78), centred, border-radius:50%, background:conic-gradient(from 15deg, rgba(255,92,122,0.55) 0deg, transparent 70deg, rgba(90,209,255,0.55) 150deg, transparent 250deg, rgba(255,92,122,0.55) 360deg); 12px ring mask radial-gradient(circle, transparent 0 calc(50% - 13px), #000 calc(50% - 12px) calc(50% - 1px), transparent 50%); filter:blur(6px); opacity:0.32; mix-blend-mode:screen; animations fringe-spin 34s linear infinite reverse + fringe-breathe 7s ease-in-out infinite (opacity 0.24 <-> 0.38).
10. **canvas#dust** (z-index:10) fixed inset:0, size 100%, pointer-events:none. Exactly 34 motes at desktop, 24 at <= 900px, 16 at <= 560px. Per mote (deterministic LCG, seed 20260214): radius 0.6px–2.2px, rise 4px/s–11px/s, sway amplitude 8px–22px at 0.12Hz–0.31Hz, fill rgba(255,206,152, a) with a 0.05–0.18. Pure time function: y = (y0 - t * vy) mod (h + 60) - 30; x = x0 + sin(t * swayHz * 6.2832 + phase) * swayAmp. Cost: 34 arcs + 1 clear per frame — under 40 canvas primitives, ZERO ctx.filter, ZERO ctx.shadowBlur anywhere in the code.
11. **.grain** (z-index:30) fixed inset:-50%, size 200%, opacity:0.045; background: inline SVG feTurbulence data-URI (feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" stitchTiles="stitch" then feColorMatrix type="saturate" values="0"), tile 160px; animation grain 0.9s steps(4) infinite jittering translate among (0,0) (-2%,1%) (1%,-2%) (-1%,2%).

**Resolution trick:** dpr = Math.min(window.devicePixelRatio || 1, 2); canvas.width = cssWidth * dpr; canvas.height = cssHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0). Sizes are read once in resizeCanvas — getBoundingClientRect allowed THERE only. Resize: one boolean flag + requestAnimationFrame, no timers.

**Per-frame cost budget:** 1 clear + 34 arc fills, under 1.5ms at 1440x900. Ring, ticks, blades, fringe, bloom, flare, grain animate transform/opacity only — zero layout, zero JS paint. No DOM reads beyond cached handles in the loop. The loop sustains >= 20 frames per 1.2s at 1440x900 headless. If document.hidden, keep loop and counter alive, skip the paint.

---

## C. THE LAYER STACK (back -> front, z-index)

1. html, body — #07060a, forced twice (Failure Guards).
2. .backdrop — z-index:0, fixed inset:0, pointer-events:none, overflow:hidden. Inside it: .room-bg 0, .bloom 1, .bloom-core 2, .flare 3, .iris-stage 5 (fixed box left:50%, top:var(--iris-top), size var(--iris), transform:translate(-50%,-50%)) holding .iris-ring 5, .iris-ticks 6, svg.iris 7 (width calc(var(--iris) * 0.72), centred left:50% top:50% translate(-50%,-50%)), .glass 8, .fringe 9; canvas#dust 10; .grain 30.
3. .scrim — z-index:20, fixed inset:0, pointer-events:none, background:linear-gradient(180deg, rgba(3,2,2,0.55) 0%, rgba(3,2,2,0.10) 30%, rgba(3,2,2,0.28) 62%, rgba(3,2,2,0.86) 100%) so the bottom-anchored hero and stats always clear contrast over the bloom.
4. .page — z-index:40, position:relative; display:grid; grid-template-rows:auto minmax(0, 1fr) auto; min-height:100dvh (height:100dvh inside the desktop lock).
5. main.hero z-index:50; header.site-header and footer.stats-footer z-index:60.
6. .menu (mobile, when open) — fixed inset:0, z-index:80.

---

## D. TOKEN TABLE (defaults at 1440x900)

```
--bg:            #07060a
--bg-2:          #0b0908
--text:          #f6f1ea
--text-soft:     #d8cfc4
--muted:         #9b9287
--dim:           #6b645b
--accent:        #ffb454
--accent-hot:    #ffe3ae
--accent-soft:   rgba(255,180,84,0.55)
--accent-line:   rgba(255,180,84,0.30)
--fringe-r:      #ff5c7a
--fringe-b:      #5ad1ff
--border:        rgba(255,236,210,0.16)
--border-soft:   rgba(255,236,210,0.09)
--focus:         #ffe3ae
--serif:         "Instrument Serif", Georgia, "Times New Roman", serif
--sans:          "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif
--mono:          "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
--logo:          16px
--logo-track:    0.22em
--nav:           13.5px
--nav-h:         38px
--btn:           13.5px
--btn-h:         44px
--chip-h:        30px
--eyebrow:       12px
--h1:            78px
--lead:          18px
--stat-val:      26px
--stat-label:    12px
--header-y:      26px
--header-x:      44px
--hero-x:        44px
--hero-bottom:   56px
--copy-max:      720px
--iris:          560px
--iris-top:      42%
--radius-pill:   999px
--focus-w:       2px
--focus-offset:  3px
--bloom-blur:    48px
--fringe-blur:   6px
--grain-op:      0.045
--ease-out:      cubic-bezier(0.16,1,0.3,1)
--enter-dur:     0.95s
```

Fonts: Google Fonts ONLY — Instrument Serif 400, Inter 400;500;600, JetBrains Mono 400;500 — one preconnect to https://fonts.gstatic.com (crossorigin) and one stylesheet link with display=swap, with the fallback stacks above.

---

## E. THE COMPOSITION — exact build order

**.page** holds <header class="site-header">, <main class="hero">, <footer class="stats-footer"> in grid rows auto / 1fr / auto.

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

- a.brand (href "#", accessible name "Halide home"): inline SVG mark 22x22 — circle cx 11 cy 11 r 8.4 fill none stroke currentColor stroke-width 1.5 plus three 1px micro-iris chords in var(--accent) at 0/120/240 degrees — then "HALIDE", var(--sans), font-size var(--logo), weight 600, letter-spacing var(--logo-track), color var(--text), no underline.
- nav with exactly 3 links — "Optics", "Pipeline", "Journal" — href "#", var(--sans), font-size var(--nav), weight 500, color var(--muted), display:flex; gap:32px; height:var(--nav-h); align-items:center. Hover: color var(--text), 0.18s ease.
- a.cta (href "#", text "Request access"): height var(--btn-h); padding 0 20px; inline-flex; align-items:center; border-radius var(--radius-pill); font-size var(--btn); font-weight 600; color var(--accent-hot); border 1px solid var(--accent-line); background linear-gradient(180deg, rgba(255,180,84,0.16) 0%, rgba(255,180,84,0.05) 100%).

**MAIN (hero, bottom-anchored)** — display:flex; flex-direction:column; justify-content:flex-end; align-items:flex-start; padding:0 var(--hero-x) var(--hero-bottom); copy hugs the LEFT page padding — never centred, right-aligned or vertically centred. Inner .hero-copy: width:100%; max-width:var(--copy-max); display:flex; flex-direction:column; align-items:flex-start.

1. .chip (a <p>, not interactive): height var(--chip-h); padding 0 14px; inline-flex; align-items:center; border-radius var(--radius-pill); border 1px solid var(--border); background rgba(255,180,84,0.06); var(--mono) at var(--eyebrow), letter-spacing 0.18em, color var(--accent). Exact copy: "COMPUTATIONAL PHOTOGRAPHY".
2. h1 — EXACTLY ONE <h1>, var(--serif), font-weight 400, font-size var(--h1), line-height 0.98, letter-spacing -0.015em, color var(--text), margin: 20px 0 0. Two masked lines (Motion): line 1 "Night is"; line 2 "undeveloped light." with <em>undeveloped</em> italic, color var(--accent-hot), text-shadow 1.5px 0 rgba(255,92,122,0.35), -1.5px 0 rgba(90,209,255,0.35) (the lens fringe).
3. .lead — var(--sans), var(--lead), line-height 1.55, letter-spacing -0.012em, color var(--text-soft), max-width 560px, margin: 22px 0 0. Exact copy: "Halide rebuilds the photograph your sensor almost saw — twelve-frame multi-exposure fusion, per-channel denoise and full lens simulation, resolved in 38 milliseconds."
4. .actions — display:flex; gap:12px; flex-wrap:wrap; margin-top: 30px. Two anchors: .btn--primary href "#" text "Request early access"; .btn--ghost href "#" text "See the pipeline".

**FOOTER (three-item stats)** — display:grid; grid-template-columns:repeat(3, 1fr); gap:0; padding: 22px var(--header-x) 28px; border-top:1px solid var(--border-soft). Exactly one <ul> with exactly three <li>, nothing else. Each li: display:grid; gap:6px; padding:0 30px; li:first-child padding-left:0; li:last-child padding-right:0; li + li border-left:1px solid var(--border-soft); li:nth-child(2) text-align:center; li:nth-child(3) text-align:right. Each li holds <strong class="stat-val"> (var(--mono), var(--stat-val), weight 500, letter-spacing -0.02em, color var(--text)) and <span class="stat-label"> (var(--mono), var(--stat-label), letter-spacing 0.14em, uppercase, color var(--dim)). Exact pairs left to right: "38 ms" / "RAW fusion latency"; "+1.4 EV" / "shadow detail recovered"; "12" / "frames per capture".

---

## F. THE UI LANGUAGE

- **.btn (shared base):** inline-flex; align-items:center; justify-content:center; height var(--btn-h); padding 0 22px; border-radius var(--radius-pill); font-family var(--sans); font-size var(--btn); font-weight 600; letter-spacing 0.01em; no underline; position:relative; overflow:hidden; transition transform/border-color/background/color/box-shadow 220ms var(--ease-out).
- **.btn--primary:** color #241403; border 1px solid rgba(255,227,174,0.65); background linear-gradient(180deg, #ffe3ae 0%, #ffb454 52%, #e08f2c 100%); box-shadow 0 14px 44px rgba(255,180,84,0.22), inset 0 1px 0 rgba(255,255,255,0.62). Hover: background linear-gradient(180deg, #fff4dd 0%, #ffc272 48%, #ea9a38 100%), translateY(-1px), box-shadow 0 18px 54px rgba(255,180,84,0.32). Active translateY(0).
- **.btn--ghost:** color var(--text-soft); border 1px solid var(--border); background rgba(255,236,210,0.035). Hover: color var(--text), border-color var(--accent-line), background rgba(255,180,84,0.08), translateY(-1px).
- **Glint** on .btn and .cta: content ""; position:absolute; inset:-1px; background linear-gradient(112deg, transparent 0%, transparent 36%, rgba(255,240,214,0.20) 50%, transparent 64%, transparent 100%); transform translateX(-140%); transition transform 680ms var(--ease-out); hover drives it to translateX(140%).
- **.cta (header)** takes .btn base at padding 0 20px with section E's amber hairline look; hover: border-color var(--accent-soft), background linear-gradient(180deg, rgba(255,180,84,0.24) 0%, rgba(255,180,84,0.08) 100%), box-shadow 0 0 20px rgba(255,180,84,0.20).
- **Nav links** plain, no pills, no underline; hover color var(--text). **.chip** never reacts.
- **Focus:** every a and button gets :focus-visible { outline: var(--focus-w) solid var(--focus); outline-offset: var(--focus-offset); border-radius: var(--radius-pill) }. Selection: ::selection { background: rgba(255,180,84,0.30); color: #1a1005 }. No default blue ring, no underline anywhere.

---

## G. MOTION — entrance stagger and named keyframes

Entrance animations: animation-duration var(--enter-dur); animation-fill-mode both; animation-timing-function var(--ease-out); animation-delay var(--d, 0.08s). Each element: .appear + one modifier + inline --d. Resting state is the final state.

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
header cta       appear--chip   0.40s
iris stage       appear--open   0.18s   (duration 1.4s)
eyebrow chip     appear--pop    0.62s
h1 line 1        appear--mask   0.76s
h1 line 2        appear--mask   0.92s
lead             appear--soft   1.10s
primary button   appear--chip   1.24s
ghost button     appear--chip   1.32s
stat 1           appear--stat   1.42s
stat 2           appear--stat   1.50s
stat 3           appear--stat   1.58s
```

Named entrance keyframes (all end at opacity 1 / identity):
```
in-fade   from { opacity: 0 }                                                to { opacity: 1 }
in-soft   from { opacity: 0; transform: translateY(14px); filter: blur(4px) } to { opacity: 1; transform: none; filter: blur(0) }
in-mask   from { opacity: 0; transform: translateY(108%) }                   to { opacity: 1; transform: none }
in-chip   from { opacity: 0; transform: translateY(10px) scale(0.96) }       to { opacity: 1; transform: none }
in-pop    0% { opacity: 0; transform: scale(0.94) }  68% { opacity: 1; transform: scale(1.03) }  100% { opacity: 1; transform: none }
in-open   from { opacity: 0; transform: scale(0.86) }                        to { opacity: 1; transform: none }
```

Named continuous keyframes (the optics):
```
aperture-breathe  16s ease-in-out infinite
  0% { transform: rotate(10deg) }  44% { transform: rotate(56deg) }
  52% { transform: rotate(56deg) }  96% { transform: rotate(10deg) }
  100% { transform: rotate(10deg) }
ring-spin         28s linear infinite          from { transform: rotate(24deg) } to { transform: rotate(384deg) }
ticks-spin        40s linear infinite reverse  (360deg)
fringe-spin       34s linear infinite reverse  (360deg)
bloom-pulse       9s ease-in-out infinite  0%,100% { opacity: 0.62; transform: translate(-50%,-50%) scale(1) }  50% { opacity: 0.90; transform: translate(-50%,-50%) scale(1.05) }
fringe-breathe    7s ease-in-out infinite  0%,100% { opacity: 0.24 }  50% { opacity: 0.38 }
flare-drift       22s ease-in-out infinite  0%,100% { transform: translate(-50%,-50%) translateX(-2.5%) rotate(-2.4deg); opacity: 0.34 }  50% { transform: translate(-50%,-50%) translateX(2.5%) rotate(1.6deg); opacity: 0.58 }
grain             0.9s steps(4) infinite  translate among (0,0) (-2%,1%) (1%,-2%) (-1%,2%)
```

H1 masking: each line is <span class="line"><span class="line-inner appear appear--mask" style="--d:0.76s">…</span></span> with .line { display:block; overflow:hidden; padding: 0.06em 0.06em 0.12em } and .line-inner { display:inline-block } — type rising from a hard edge. Periods 16s/28s/34s/40s/9s/7s are non-harmonic so the optics never re-sync.

---

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

```
>= 1600px:
  --iris: 640px  --h1: 92px  --lead: 19px  --logo: 17px  --nav: 14px  --nav-h: 40px
  --btn: 14px  --btn-h: 46px  --stat-val: 30px
  --header-y: 30px  --header-x: 56px  --hero-x: 56px  --hero-bottom: 64px  --copy-max: 820px

>= 1920px:
  --iris: 720px  --h1: 106px  --lead: 20px  --stat-val: 34px  --stat-label: 12.5px
  --header-x: 72px  --hero-x: 72px  --hero-bottom: 72px  --copy-max: 900px

1280px – 1599px:
  --iris: 520px  --h1: 74px  --copy-max: 680px

901px – 1279px:
  --iris: 440px  --iris-top: 38%  --h1: 58px  --lead: 16px  --stat-val: 24px
  --nav: 12.5px  --btn: 12.5px  --header-x: 30px  --hero-x: 30px  --hero-bottom: 44px  --copy-max: 600px

>= 901px AND max-height 850px:
  --iris: 420px  --iris-top: 35%  --h1: 54px  --lead: 16px  --stat-val: 22px  --chip-h: 28px
  --header-y: 18px  --hero-bottom: 36px  (h1 margin-top 14px, lead 16px, actions 22px)

>= 901px AND max-height 720px:
  --iris: 340px  --iris-top: 32%  --h1: 44px  --lead: 15px  --eyebrow: 11px  --stat-val: 20px  --stat-label: 11px
  --chip-h: 26px  --btn-h: 38px  --header-y: 14px  --hero-bottom: 26px  (footer padding 14px top, 18px bottom)

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

<= 900px:
  no viewport lock; vertical scroll; nav + CTA move into a burger menu:
  - header keeps .brand + <button class="burger" aria-label="Open navigation" aria-expanded="false">, three 18x1.5px bars, var(--text), gap 5px
  - .menu { position:fixed; inset:0; z-index:80; background: rgba(7,6,10,0.94); 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 3 nav links at 22px + the .cta chip; Escape, any link click, resize > 900px close it
  --iris: min(78vw, 460px)  --iris-top: 34%  --h1: 46px  --lead: 16px  --stat-val: 22px
  --header-x: 24px  --hero-x: 24px  --hero-bottom: 40px  --copy-max: 100%   (dust: 24 motes)

<= 560px:
  --iris: min(86vw, 340px)  --iris-top: 30%  --h1: 36px  --lead: 15px  --eyebrow: 11px
  --stat-val: 19px  --stat-label: 10.5px  --header-x: 20px  --hero-x: 20px  --hero-bottom: 32px
  stats footer stacks: grid-template-columns:1fr; li { padding: 12px 0; text-align: left !important }; li + li { border-left: 0; border-top: 1px solid var(--border-soft) }
  .actions { width:100% }  .btn { flex: 1 1 100% }   (dust: 16 motes)
```

---

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

1. **Black-first:** the FIRST CSS rule is html, body { background:#07060a !important; color:#f6f1ea }; body carries style="background:#07060a;color:#f6f1ea"; a later rule restates html, body { background:#07060a; background: var(--bg, #07060a) }.
2. **Resting opacity is 1:** every .appear element's un-animated computed style is its final state (opacity 1, no transform, no filter); keyframes own the FROM states; fill-mode:both holds them only during the delay. If CSS animation fails or JS never runs, the page renders complete.
3. **Two-rAF entrance fallback:** after two nested requestAnimationFrame callbacks, for each .appear call el.getAnimations({ subtree:false }); if none reports playState "running"/"finished", add .is-in to ALL .appear. No getAnimations: run the sweep unconditionally after the two rAFs. .is-in forces animation:none !important; opacity:1 !important; transform:none !important; filter:none !important; clip-path:none !important.
4. **Canvas fallback:** if canvas or getContext("2d") is missing, add .no-canvas to <html>, push "canvas-unavailable" into window.__DEMO__.errors, and paint static dust on .backdrop::after with two radial-gradient dot layers (rgba(255,206,152,0.14) 0 1.5px, transparent 2px) at background-size 180px 220px and 260px 300px, offsets 40px 0 and 0 90px. Optics stay fully visible; only motes degrade.
5. **Reduced motion:** inside @media (prefers-reduced-motion: reduce): *, *::before, *::after { transition:none !important; animation:none !important }, and .appear / .appear.is-in forced to opacity:1 !important; transform:none !important; filter:none !important; clip-path:none !important. Optics freeze at mid-breath resting transforms: .blade rotate(30deg) (half-open), .iris-ring rotate(24deg), .iris-ticks rotate(-12deg), .fringe rotate(38deg) opacity 0.32, .bloom translate(-50%,-50%) scale(1) opacity 0.76, .flare translate(-50%,-50%) rotate(-2.4deg) opacity 0.42, .grain display:none. Dust paints exactly ONE static frame at t = 0 then stops; ready still becomes true, frames stops at 1. Nothing moves.
6. **Contrast floor:** h1 and stat values #f6f1ea on #07060a, lead #d8cfc4 over the scrim, labels #6b645b 12px uppercase above 4.5:1 — never drop text opacity below 0.86.
7. **Zero console output:** no console calls anywhere, no thrown exceptions, every canvas and DOM call guarded.

---

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

1. **Telemetry first:** window.__DEMO__ = { name: 'halide', ready: false, frames: 0, effects: [], errors: [] }; then push exactly these effect names once, in order: 'aperture-iris', 'lens-bloom', 'chromatic-fringe', 'dust-motes', 'film-grain'. Then window.addEventListener('error', function (e) { window.__DEMO__.errors.push(String(e.message || e)); }).
2. **Canvas dust loop:** cache document.documentElement, canvas#dust, the 2D context (null -> .no-canvas path, push "canvas-unavailable"), the 34 motes of section B ({ x0, y0, r, vy, swayAmp, swayHz, phase, alpha }), and a cached { w, h } refreshed only on resize. drawDust(t) clears and fills each mote with the time-pure formulas — one beginPath, one arc, one fill, fillStyle 'rgba(255,206,152,' + alpha + ')'. NEVER ctx.filter, ctx.shadowBlur, or getBoundingClientRect in the loop. Each rAF tick increments window.__DEMO__.frames by exactly 1 then paints; the FIRST completed paint sets ready = true. While document.hidden, skip paint, keep loop and counter.
3. **Resize:** resizeCanvas() reads canvas.getBoundingClientRect() ONCE (the only permitted call), recomputes { w, h }, sets dpr = Math.min(window.devicePixelRatio || 1, 2), canvas.width = w * dpr, canvas.height = h * dpr, ctx.setTransform(dpr, 0, 0, dpr, 0, 0), repaints. Schedule via one boolean resizeQueued + requestAnimationFrame — no timers.
4. **Entrance fallback:** the two nested rAFs, the getAnimations check, the .is-in sweep (Guard 3).
5. **Mobile menu (<= 900px):** burger click toggles .menu.open and aria-expanded; Escape, any menu link click, and resize past 900px close it.
6. **prefers-reduced-motion:** read matchMedia('(prefers-reduced-motion: reduce)') once; if it matches, drawDust(0) once, set ready on that paint, count that frame, schedule no further rAF.

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".

Acceptance criteria (15)

  1. the document contains exactly one h1 built from exactly two .line > .line-inner masked lines with the visible copy 'Night is' and 'undeveloped light.', and uses semantic header, nav, main, and footer landmarks with exactly 3 nav links and 3 stat items
  2. an svg.iris exists with exactly 8 .blade groups animating rotate(10deg) to rotate(56deg) on a 16s aperture-breathe loop, and a .iris-ring with a conic-gradient background rotating 360deg over 28s linear
  3. window.__DEMO__ is initialized with name 'halide', ready becomes true after the first painted dust frame, frames increments once per requestAnimationFrame tick (>= 20 per 1.2s at 1440x900), effects contains aperture-iris, lens-bloom, chromatic-fringe, dust-motes and film-grain, and errors stays empty in the happy path
  4. canvas#dust paints non-blank pixel content at 1440x900 and the script never calls ctx.filter, ctx.shadowBlur, or getBoundingClientRect inside the animation loop
  5. no horizontal overflow at 390px, 768px, 1440px or 1920px; at >= 901px body has overflow:hidden and .page height is 100dvh; a prefers-reduced-motion: reduce block freezes all optics and dust while leaving every text element at opacity 1
  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