Landing PageMiniMaxAI/MiniMax-M3partial cross-check
Chrome Bloom
A liquid-chrome metaball bloom drifts behind a conic-sheen italic wordmark
The exact prompt
Build one complete, self-contained `index.html` for **Bloom** — a generative brand studio landing page. Document title: `Bloom — Generative Brand Studio`. `<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, inline SVG, and absolutely-positioned CSS circles composited through a `filter: blur() contrast()` chain. No `<canvas>`, no WebGL, no Three.js.
**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 `<canvas>`, 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 more than 3 nav links, no more than 3 footer items, 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 generative brand studio called **Bloom**. Behind a large italic serif wordmark sits a liquid-chrome form: exactly **6 circles** on independent orbital paths, blurred and composited through a single `filter: blur(28px) contrast(18)` chain so they read as ONE metaball mass rather than 6 separate blobs. The wordmark itself is a serif italic at large size with a conic-gradient chrome sheen applied via `background-clip: text`. A fine SVG grain overlay sits on top. A slow ~40-second hue drift rotates the chrome's tint. The frame is moody, expensive, liquid-metal — the studio calls itself a brand foundry and the page should feel like one. Header: small wordmark + 3 nav links. Hero: eyebrow chip, italic wordmark, single muted lede, one CTA. Footer: a three-item strip.
---
## B. THE BACKDROP — the chrome bloom (the ONLY animated visual besides the grain jitter)
The chrome bloom is a fixed-size composited layer, NOT a full-viewport element. It is one `<div class="bloom">` at `position:fixed; left:50%; top:50%; width: min(82vw, 1180px); height: min(82vw, 1180px); transform: translate(-50%, -50%); z-index: 1; pointer-events: none; filter: blur(28px) contrast(18); mix-blend-mode: screen; will-change: transform; contain: paint;`. Its transform is animated by JS; the **filter is set once in CSS and never re-applied per frame**. Because the bloom sits over a near-black field, the `contrast(18)` amplifies blurred edges into hard chrome rims and the adjacent circles merge into one body.
**The 6 circles.** Inside `.bloom`, six `<span class="orb orb--N">` elements where N = 1..6. Each is `position:absolute; left:50%; top:50%; width: var(--orb-size, 38%); height: var(--orb-size, 38%); margin-left: calc(var(--orb-size)/-2); margin-top: calc(var(--orb-size)/-2); border-radius: 50%;`. Each circle is filled with a **radial-gradient chrome body** so even before compositing it looks like a polished bead. The gradient (the same family for every orb, different stop offsets) is:
```
background:
radial-gradient(circle at 32% 30%,
#ffffff 0%,
#f6f0e1 8%,
#e9d8a6 18%,
#b08a3a 38%,
#2a1d0a 62%,
#0a0703 88%,
#000000 100%);
```
The 6 orbs have these sizes and starting offsets (orb-size is a percentage of the bloom container; x/y are the centerpoint offset as a percentage of container width/height; the JS orbit path will then add to these):
```
orb--1 size 42% x -18% y -8% (large, upper-left mass)
orb--2 size 34% x 22% y -14% (mid, upper-right)
orb--3 size 30% x 10% y 18% (mid, lower-centre)
orb--4 size 26% x -24% y 14% (small, lower-left)
orb--5 size 22% x 26% y 6% (small, right)
orb--6 size 18% x -6% y -22% (tiny, top-centre)
```
**Orbits.** JS reads `--t` (seconds since start) and sets each orb's `transform` to `translate(calc(baseX + r * cos(theta)), calc(baseY + r * sin(theta)))` once per animation frame. The baseX / baseY are the offsets above; the radius `r` is a per-orb percentage of container size (so the path is responsive). The angle `theta = omega * t + phase`. Six named per-orb motion rules, each fully independent:
```
orb--1 r 9% omega 0.105 rad/s phase 0.00
orb--2 r 7% omega 0.146 rad/s phase 1.10
orb--3 r 6% omega 0.182 rad/s phase 2.31
orb--4 r 5% omega 0.218 rad/s phase 3.47
orb--5 r 4.5% omega 0.260 rad/s phase 4.62
orb--6 r 4% omega 0.314 rad/s phase 5.78
```
Frequencies are deliberately non-commensurate so the bloom never visibly repeats. Each orb is also given a faint `filter: brightness(0.92..1.08)` via its inline `--bri` custom property, set once in CSS and varied per orb to break perfect symmetry.
**Performance budget for the filter.** The `filter: blur(28px) contrast(18)` chain is expensive; therefore the bloom container is:
- a fixed pixel size that does NOT re-flow on scroll or resize-recalc,
- promoted to its own composited layer via `will-change: transform`,
- has `contain: paint`,
- the filter is declared in CSS **once** — the rAF loop animates `transform` on the `.bloom` container (a slow 40s hue-rotation drift) and per-orb `transform` (the orbit positions). Filter is never re-applied per frame.
- on `<= 900px` the bloom is shrunk: `width: min(120vw, 720px); height: min(120vw, 720px);` and `filter: blur(22px) contrast(16)` to keep mobile paint under budget.
**Hue drift.** A second slow animation is applied to the `.bloom` container itself: `animation: hue-drift 40s linear infinite` where `hue-drift` keyframes go `0% { filter: blur(28px) contrast(18) hue-rotate(0deg); } 50% { filter: blur(28px) contrast(18) hue-rotate(-14deg); } 100% { filter: blur(28px) contrast(18) hue-rotate(0deg); }`. The hue-rotate sits at the end of the filter chain so the chrome mass subtly tints warmer / cooler without changing position. The filter is set in keyframes, not per frame, so the compositor can keep caching it.
**Grain overlay.** A `.grain` element: `position:fixed; inset:-50%; width:200%; height:200%; pointer-events:none; z-index:5; opacity:0.07; mix-blend-mode: overlay;`. Background is an inline SVG `feTurbulence` rendered as a data-URI: `<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.92' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>`, `background-size: 180px 180px;`. Animation: `grain 1.1s steps(4) infinite` with keyframes jittering `transform: translate` among `(0,0) (-2%,1%) (1.5%,-2%) (-1%,2%)`. This is the second effect layer.
---
## C. LAYER STACK (back → front)
1. `html, body` — near-black `#0a0a0c`, forced twice (see Failure Guards).
2. `.vignette` — z-index 0, `position:fixed; inset:0; pointer-events:none;`, `background: radial-gradient(ellipse at 50% 50%, rgba(10,10,12,0) 0%, rgba(10,10,12,0) 55%, rgba(0,0,0,0.55) 90%, rgba(0,0,0,0.85) 100%);` — a deep edge falloff so the chrome bloom appears to float in a void.
3. `.bloom` — z-index 1, the 6-orb liquid-chrome composited layer (per Section B).
4. `.scrim` — z-index 2, a fixed `linear-gradient(180deg, rgba(10,10,12,0.55) 0%, rgba(10,10,12,0.12) 28%, rgba(10,10,12,0.10) 58%, rgba(10,10,12,0.78) 100%)` guaranteeing text contrast at the top and bottom of the frame.
5. `.page` — z-index 3, `position:relative; display:grid; grid-template-rows: auto 1fr auto; min-height:100dvh` (height:100dvh inside the desktop lock).
6. `.wordmark` sheen — z-index 4, the conic-gradient chrome sheen applied via `background-clip: text` on the h1 (described in section E).
7. `.grain` — z-index 5, the SVG feTurbulence overlay.
---
## D. TOKEN TABLE (defaults at ~1440px)
```
--bg: #0a0a0c
--bg-2: #14141a
--text: #f4ecdc
--text-soft: #d6c9a8
--muted: #8b8474
--dim: #56524a
--accent: #e9d8a6
--accent-warm: #f6e2a0
--accent-deep: #a07a2a
--line: rgba(244,236,220,0.16)
--line-soft: rgba(244,236,220,0.08)
--chrome-hi: #ffffff
--chrome-mid: #e9d8a6
--chrome-lo: #2a1d0a
--logo: 15px
--nav: 13.5px
--nav-h: 36px
--btn: 13.5px
--btn-h: 44px
--eyebrow: 12px
--h1: clamp(64px, 9.4vw, 156px)
--h1-min: 44px
--h1-max: 168px
--lede: 17px
--foot: 12.5px
--header-y: 26px
--header-x: 44px
--hero-x: 44px
--hero-y: 48px
--copy-max: 1080px
--lede-max: 560px
--radius-pill: 999px
--radius-soft: 14px
--focus-w: 2px
--focus-offset: 4px
--bloom-blur: 28px
--bloom-contrast:18
--orb-1: 42%
--orb-2: 34%
--orb-3: 30%
--orb-4: 26%
--orb-5: 22%
--orb-6: 18%
--ease-out: cubic-bezier(0.16,1,0.3,1)
--enter-dur: 0.95s
--soft-dur: 1.15s
--drift-dur: 40s
--grain-dur: 1.1s
--serif: "Cormorant Garamond", "EB Garamond", Georgia, "Times New Roman", serif
--sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif
```
Fonts (Google Fonts ONLY, with the fallbacks listed): `Cormorant+Garamond:ital,wght@1,400;1,500;1,600` and `Inter:wght@400;500;600`, 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) 0; display:flex; align-items:center; justify-content:space-between; gap:24px`.
- Left: `a.brand` (href `#`, accessible name "Bloom home"). It contains an inline SVG monogram 22×22 — a small chrome droplet: `<svg viewBox="0 0 22 22" width="22" height="22" aria-hidden="true"><defs><radialGradient id="bm" cx="35%" cy="30%" r="70%"><stop offset="0%" stop-color="#ffffff"/><stop offset="22%" stop-color="#f6e2a0"/><stop offset="55%" stop-color="#a07a2a"/><stop offset="100%" stop-color="#0a0703"/></radialGradient></defs><circle cx="11" cy="11" r="9" fill="url(#bm)"/></svg>` — followed by the wordmark text `bloom` in `var(--serif)`, italic, `font-size: var(--logo); font-weight:500; color: var(--text); letter-spacing:0.01em; font-style: italic`.
- Centre: `nav` with exactly 3 links: `work`, `studio`, `journal`. Each `href="#"`, `var(--sans)`, `font-size: var(--nav); color: var(--muted); font-weight:500; letter-spacing: 0.02em;`. Height `var(--nav-h)`, `display:flex; align-items:center; gap: 32px;`. Hover: `color: var(--text); transition: color 0.22s var(--ease-out);`.
- Right: `a.cta` (href `#`, accessible name "Start a project"), text `Start a project`. Style: `display:inline-flex; align-items:center; height: var(--btn-h); padding: 0 22px; border-radius: var(--radius-pill); font-family: var(--sans); font-size: var(--btn); font-weight: 500; letter-spacing: 0.01em; color: #14110a; background: linear-gradient(180deg, #fbf2cf 0%, var(--accent) 48%, var(--accent-deep) 100%); border: 1px solid rgba(255,255,255,0.18); box-shadow: 0 18px 44px rgba(233,216,166,0.18), inset 0 1px 0 rgba(255,255,255,0.55);`. Hover: `transform: translateY(-1px); box-shadow: 0 22px 56px rgba(233,216,166,0.26), inset 0 1px 0 rgba(255,255,255,0.7); background: linear-gradient(180deg, #ffffff 0%, #f6e2a0 50%, #c89a3a 100%);` over 0.24s `var(--ease-out)`.
**MAIN (hero)** — `display:flex; flex-direction:column; justify-content:center; align-items:center; padding: var(--hero-y) var(--hero-x) calc(var(--hero-y) + 24px); width:100%; max-width: var(--copy-max); margin: 0 auto; text-align:center; min-width:0;`. Vertically centred within the 1fr row.
1. `.eyebrow` — `var(--sans)`, `font-size: var(--eyebrow); color: var(--accent); letter-spacing: 0.22em; text-transform: uppercase; font-weight: 500; margin: 0;`. Exact copy: `Generative Brand Foundry · Est. 2024`. The middle dot is `color: var(--dim)`.
2. `h1` — EXACTLY ONE `<h1>`, `var(--serif)`, italic, `font-weight: 500; font-size: var(--h1); line-height: 0.96; letter-spacing: -0.025em; color: var(--text); margin: 24px 0 0; text-wrap: balance;`. The conic-gradient chrome sheen is applied to the h1 element via `background: conic-gradient(from 220deg at 50% 50%, #fbf2cf 0deg, #e9d8a6 70deg, #b08a3a 140deg, #f6e2a0 220deg, #ffffff 290deg, #a07a2a 340deg, #fbf2cf 360deg); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: transparent;`. The text is also given `text-shadow: 0 1px 0 rgba(0,0,0,0.35);` for legibility on bright bloom moments. The conic gradient is slowly rotated via `@keyframes sheen { from { background-position: 50% 50%; } to { background-position: 50% 50%; } }` — the sheen does NOT visibly animate (the wordmark stays reading as a single chrome surface); rotation is implicit because the conic is drawn around the text bounding box, not the viewport. Exact copy, a single line, max 2 lines on narrow screens: `Brands, in liquid metal.`
3. `.lede` — `var(--sans)`, single paragraph, `font-size: var(--lede); line-height: 1.55; color: var(--text-soft); margin: 22px auto 0; max-width: var(--lede-max); font-weight: 400; letter-spacing: 0.002em;`. Exact copy: `Bloom designs living identity systems — marks, type, motion and chrome — for studios that ship faster than the brand book can keep up.`
**FOOTER** — `padding: 18px var(--header-x) 22px; display:flex; align-items:center; justify-content:space-between; gap: 20px; border-top: 1px solid var(--line-soft); background: linear-gradient(180deg, rgba(10,10,12,0) 0%, rgba(10,10,12,0.55) 100%);`.
- Left: mono-styled `var(--foot)`, `color: var(--muted); font-family: var(--sans); font-weight: 500; letter-spacing: 0.01em;`. Exact copy: `© 2026 Bloom Studio`.
- Centre: a single `span.foot-mark` — `color: var(--muted); font-size: var(--foot); font-family: var(--sans); font-weight: 500;` with a 6×6 `display:inline-block; border-radius: 50%; background: var(--accent); box-shadow: 0 0 10px rgba(233,216,166,0.55); margin-right: 8px; vertical-align: 1px;` and the text `Now booking · Q2 2026`.
- Right: exactly 3 links `dribbble`, `instagram`, `are.na`. `var(--sans)`, `var(--foot)`, `color: var(--muted); font-weight: 500; letter-spacing: 0.01em;`, `gap: 22px`, `href="#"`. Hover: `color: var(--text); transition: color 0.22s var(--ease-out);`.
---
## F. THE UI LANGUAGE
There is exactly ONE button style in the page (the header CTA) — the chrome-pill: 999px radius, 1px `rgba(255,255,255,0.18)` border, a 3-stop linear gradient `#fbf2cf → #e9d8a6 → #a07a2a`, dark text `#14110a`, outer accent glow, inner top highlight. Hover: lift -1px, swap to a brighter gradient, deepen the glow over 0.24s `var(--ease-out)`. Nav links are plain sans text — no pills, no backgrounds, no underline ever; hover swaps colour to `var(--text)`. Footer links: same treatment in `var(--foot)`. Eyebrow is a tracking-heavy small-caps accent line. Selection: `::selection { background: rgba(233,216,166,0.30); color: #14110a; }`. Every interactive element gets `:focus-visible { outline: var(--focus-w) solid var(--accent); outline-offset: var(--focus-offset); border-radius: 6px; }`. 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: var(--enter-dur); animation-fill-mode: both; animation-timing-function: var(--ease-out); 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 work appear--fade 0.16s
nav link 2 studio appear--fade 0.22s
nav link 3 journal appear--fade 0.28s
header cta appear--lift 0.34s
eyebrow appear--soft 0.50s
h1 appear--bloom 0.66s (duration 1.20s)
lede appear--soft 0.96s (duration 1.10s)
footer left appear--fade 1.18s
footer centre appear--fade 1.24s
footer right appear--fade 1.30s
```
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-lift from { opacity: 0; transform: translateY(10px) scale(0.97) } to { opacity: 1; transform: none }
in-bloom from { opacity: 0; transform: translateY(18px) scale(0.985); filter: blur(6px); }
to { opacity: 1; transform: none; filter: blur(0); }
```
The H1 `in-bloom` rise mirrors the way a molten form settles — the type enters with a faint blur that resolves into the chrome sheen. The CTA `in-lift` carries the only transform:translateY + scale on a button.
Continuous keyframes:
- `hue-drift` — see Section B. 40s, linear, infinite. Sits on `.bloom`. The filter is declared in keyframes (so it is cached by the compositor), not re-applied by JS.
- `grain` — see Section B. 1.1s, steps(4), infinite. Sits on `.grain`. Only `transform` is animated.
- `sheen` — declared on the h1 so the conic gradient's `background-position` is animated; in practice the conic is fixed relative to the text bounding box so the visible effect is a stable chrome surface with a barely-perceptible highlight drift. `0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } ` over 18s linear infinite. If this risks distracting from the wordmark it may be removed; the wordmark still reads as chrome without it.
The orb orbits, the hue-drift, the grain, and the sheen run continuously, independent of the entrance stagger. The orb orbit positions are written by JS per rAF tick (see Section J).
---
## H. RESPONSIVE TABLE — exact token overrides per breakpoint
```
>= 1600px:
--h1-max: 188px --logo: 16px --nav: 14px --nav-h: 38px
--btn: 14px --btn-h: 46px --lede: 18px --foot: 13px
--header-y: 32px --header-x: 60px --hero-x: 60px
--copy-max: 1240px --lede-max: 620px
>= 1920px:
--h1-max: 220px --header-x: 76px --hero-x: 76px
--copy-max: 1380px --header-y: 38px --lede: 19px
1280px – 1599px:
--h1-max: 168px --header-x: 48px --hero-x: 48px
--copy-max: 1120px
901px – 1279px:
--h1-min: 56px --h1-max: 132px --nav-h: 34px
--header-x: 32px --hero-x: 32px
--copy-max: 920px --lede: 16px --lede-max: 520px
>= 901px AND max-height 850px:
--h1-min: 52px --h1-max: 120px --hero-y: 28px
--header-y: 18px --lede: 15px (h1 margin-top 16px, lede margin-top 16px)
>= 901px AND max-height 720px:
--h1-min: 44px --h1-max: 96px --hero-y: 18px
--header-y: 14px --eyebrow: 11px --lede: 13.5px --btn-h: 38px
>= 901px (desktop lock, all heights):
html, body { height:100%; overflow:hidden } .page { height:100dvh; overflow:hidden }
.bloom { width: min(82vw, 1180px); height: min(82vw, 1180px);
filter: blur(28px) contrast(18); }
<= 900px:
no vh lock; page may scroll; nav becomes a full-viewport overlay menu:
- header keeps brand + burger button (<button class="burger" aria-label="Open navigation" aria-expanded="false">, three 18×1.5px bars in var(--text), gap 5px)
- .menu { position:fixed; inset:0; z-index:50; background: rgba(10,10,12,0.94);
backdrop-filter: blur(14px); -webkit-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 var(--ease-out); }
- .menu.open { transform: none }
- menu shows the 3 nav links stacked at --nav: 22px plus the CTA pill
- Escape key, link click, and resize >900px all close it
--h1-min: 44px --h1-max: 96px --header-x: 24px --hero-x: 24px
--copy-max: 100% --lede: 16px
.bloom { width: min(120vw, 720px); height: min(120vw, 720px);
filter: blur(22px) contrast(16); }
.bloom { top: 56%; } /* bias bloom down so wordmark reads on top of it */
<= 560px:
--h1-min: 38px --h1-max: 80px --eyebrow: 11px
--lede: 15px --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)
.bloom { width: min(160vw, 560px); height: min(160vw, 560px);
filter: blur(20px) contrast(15); }
```
The orb sizes and orbital radii are CSS custom properties on the `.bloom` container; on <=900px they shrink to `--orb-1: 36%, --orb-2: 30%, --orb-3: 26%, --orb-4: 22%, --orb-5: 18%, --orb-6: 16%` and the JS orbit radii are divided by 1.3 so the metaball mass stays compact.
---
## I. FAILURE GUARDS — the page is never blank, never broken
1. **Black-first:** the FIRST CSS rule is `html, body { background:#0a0a0c !important; color:#f4ecdc }`; the body tag carries `style="background:#0a0a0c;color:#f4ecdc"`; a later rule restates `html, body { background:#0a0a0c; background: var(--bg, #0a0a0c) }`.
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. The 6 orbs sit at their base offsets in CSS (no animation needed) and JS only adjusts `transform`. If JS never runs, the bloom is still visible.
3. **rAF entrance fallback:** after two `requestAnimationFrame` ticks, for every `.appear` element check `el.getAnimations({ subtree: false })`; if none has `playState` `"running"` or `"finished"`, add `.is-in` to every `.appear` element. `.is-in { animation: none !important; opacity: 1 !important; transform: none !important; filter: none !important; clip-path: none !important }`. If the browser lacks `getAnimations`, perform the same `.is-in` fallback after the two rAFs.
4. **Bloom fallback (no filter support):** if `CSS.supports('filter', 'blur(28px) contrast(18)')` returns false, JS adds `.no-bloom` to `<html>`. CSS must then show 6 absolutely-positioned CSS circles (no blur/contrast) with `opacity: 0.85; background: var(--accent-deep); border-radius: 50%;` so the wordmark still sits on a recognisable warm form. Page remains readable; telemetry `errors` receives a short string.
5. **Reduced motion:** inside `@media (prefers-reduced-motion: reduce)`: `*, *::before, *::after { transition: none !important; animation: none !important; }`; force `.appear` to `opacity:1 !important; transform: none !important; filter: none !important; clip-path: none !important`; the JS orbit loop still runs once (paints the initial frame, sets `ready = true`, increments `frames` by 1) and then `cancelAnimationFrame`s itself; the `.bloom` filter keyframe `hue-drift` is disabled by the universal rule; the grain animation is hidden. The static frame must still show the 6 orbs at their base offsets so the page reads as designed.
6. **No console output** in the happy path: no `console.log`, no thrown exceptions; every filter and animation property read is guarded.
---
## J. THE EXACT JS (only this, vanilla, one IIFE at end of body)
1. **Telemetry first, before anything else:**
`window.__DEMO__ = { name: 'chrome-bloom', ready: false, frames: 0, effects: [], errors: [] };`
Push `'liquid-chrome'`, `'conic-sheen'`, `'hue-drift'`, `'grain-texture'`, and `'mobile-menu'` into `effects` once implemented (in that order). Add `window.addEventListener('error', e => window.__DEMO__.errors.push(String(e.message || e)))`.
2. **Cache DOM references once:** `document.documentElement`, `.bloom`, all 6 `.orb` nodes (in order), `.grain`, `.burger`, `.menu`, all `.menu` links, and the result of `matchMedia('(prefers-reduced-motion: reduce)')`. Never call `getBoundingClientRect` inside the rAF loop. Cache the bloom container's `clientWidth` / `clientHeight` once at startup and recompute on resize only.
3. **Orbit data table** (read-only constant in JS): an array of 6 objects `{ baseXPct, baseYPct, radiusPct, omega, phase, sizePct }` matching the orb sizes and starting offsets in Section B. On resize, divide every `radiusPct` by the orb size factor appropriate to the current breakpoint (1.0 desktop, 1.3 mobile) so the metaball mass stays compact.
4. **rAF loop:** `requestAnimationFrame` reads `t = performance.now() / 1000`; for each of the 6 orbs, compute `x = baseX + radius * Math.cos(omega * t + phase)` and `y = baseY + radius * Math.sin(omega * t + phase)`; assign `orb.style.transform = `translate(${x.toFixed(2)}%, ${y.toFixed(2)}%)``. The bloom container transform is left to CSS keyframes. On the FIRST completed paint, set `window.__DEMO__.ready = true`. Increment `window.__DEMO__.frames` by exactly 1 each rAF tick. If `document.hidden`, skip the assignment and the paint, but keep the rAF alive. If `prefersReduced.matches`, paint exactly one frame then `cancelAnimationFrame` and return.
5. **Resize handler:** one `resizeQueued` boolean; on `window.resize` set the flag and call `requestAnimationFrame` once; in the rAF callback recompute the cached bloom `clientWidth` / `clientHeight`, recompute breakpoint, update orb radius factors. No timers, no `getBoundingClientRect` per frame.
6. **Entrance fallback:** after two nested `requestAnimationFrame` callbacks following `DOMContentLoaded`, iterate every `.appear` and check `el.getAnimations`; if no animation is running or finished, add `.is-in` to every `.appear`. If `getAnimations` is missing, add `.is-in` to every `.appear` after the same two rAFs.
7. **Mobile menu:** burger click toggles `.menu.open` and `aria-expanded`; `Escape` key closes; any menu link click closes; `window.resize` to width > 900px closes and clears `aria-expanded`.
8. **Feature support check:** at startup, if `CSS.supports('filter', 'blur(28px) contrast(18)')` is false, add `.no-bloom` to `<html>` and skip the rAF loop entirely; push a string into `errors` like `'filter-blur-contrast-unsupported'`.
No other JS. No libraries. No console calls. No `setTimeout` / `setInterval`. All motion is `requestAnimationFrame` or CSS keyframes.
---
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)
- exactly one <h1> with the exact text 'Brands, in liquid metal.' rendered in an italic serif (Cormorant Garamond) with a conic-gradient chrome sheen via background-clip: text
- a fixed .bloom container holds exactly 6 absolutely-positioned .orb circles whose transforms are updated per rAF; the container applies filter: blur(28px) contrast(18) once in CSS and is animated with transform only
- window.__DEMO__ is initialized with name 'chrome-bloom', ready becomes true after the first painted orbit frame, frames increments once per rAF tick, effects contains 'liquid-chrome', 'conic-sheen', 'hue-drift', 'grain-texture', and 'mobile-menu', and errors stays empty in the happy path
- exactly 3 nav links, 1 CTA, 1 lede paragraph and 3 footer items exist with accessible names; every anchor and button has a visible :focus-visible ring and the page is single-viewport (height:100dvh; overflow:hidden) at >=901px
- no horizontal overflow at 390px, 768px, 1440px, or 1920px; the prefers-reduced-motion reduce block keeps the page readable and static; the entire index.html is under 90 KB and uses Google Fonts as the only network request
- 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