# Tessellate

> **Category:** Landing Page
> **Effect:** SVG pattern morphs between triangular, hexagonal, and herringbone tilings behind an animated rotating mask, cycling every 6s.
> **Platform:** cmd-code (`openrouter`)
> **Model:** `stealth/space-bunny-alpha` — Space Bunny Alpha (stealth/space-bunny-alpha)
> **License:** Free / MIT — copy, paste, ship.
> **Self-contained:** yes — one `index.html`, no build step, no external media.

---

## 🎬 Live demo

Open [`public/demos/tessellate/index.html`](../public/demos/tessellate/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
A. WHAT IS BEING BUILT:
A single-viewport cinematic landing page for "TESSELLATE", a generative-design studio. The document title is "TESSELLATE — Generative Design Studio". The <html> element carries lang="en". The viewport meta tag is <meta name="viewport" content="width=device-width, initial-scale=1">. The CSS locks the desktop viewport: html, body { height: 100dvh; } @media (min-width: 901px) { html, body { overflow: hidden; } }. DO NOT BUILD: multi-page routing, any <canvas> element, mp4/webm/gif media, remote images (except Google Fonts), base64 photo blobs, Tailwind CSS, React, GSAP, Three.js, Lottie, WebGL, external CDNs, horizontal scrollbars, invisible focus states, console.log statements, unhandled promise rejections, or non-vector blur (e.g., no box-shadow blur on animated elements). Every pixel must originate from CSS gradients, SVG shapes, or system fonts.

B. THE BACKDROP:
The backdrop consists of 1 parent <svg id="backdrop-svg">, 3 <pattern> definitions (#pat-tri, #pat-hex, #pat-herr), 1 <mask id="anim-mask"> containing 1 <g class="mask-shapes"> with exactly 6 vector child shapes (2 <polygon>, 2 <path>, 1 <circle>, 1 <rect>), and 3 stacked <rect> elements (#rect-tri, #rect-hex, #rect-herr) inside a <g mask="url(#anim-mask)">. Blur radii are exactly 0 — no SVG <feGaussianBlur> or CSS filter: blur() is applied to the backdrop, guaranteeing crisp vector rendering at any DPI. Gradient stops: the page background is a CSS radial-gradient(circle at 50% 40%, #1a1025 0%, #0a0610 70%, #000000 100%). Pattern fills use flat alpha: triangle paths are rgba(192,132,252,0.15) and rgba(192,132,252,0.05); hexagon uses rgba(192,132,252,0.10); herringbone uses rgba(192,132,252,0.08) and rgba(192,132,252,0.12). Drift periods: .mask-shapes rotates 360deg over 24s (linear infinite) and scales from 1 to 1.15 and back over 12s (ease-in-out infinite). The 3 rects cross-fade over a single 18s cycle (6 seconds per tiling). Canvas resolution trick: N/A — vector-only SVG (no <canvas>), so no devicePixelRatio scaling is required. The SVG uses viewBox="0 0 1440 900" and preserveAspectRatio="xMidYMid slice" to guarantee crisp scaling at any viewport size without pixelation, replacing the canvas DPR trick entirely. Per-frame cost budget: total JS execution per frame must be < 2ms. We cache the #readout-text DOM node. We NEVER call getBoundingClientRect inside the loop. CSS handles all animation (opacity and transform only). The only JS math is Math.floor(Date.now() / 6000) % 3, an O(1) operation taking < 0.01ms.

C. THE LAYER STACK (back to front, z-index values):
- z-index: 0 — #bg-gradient (a body::before pseudo-element with the radial gradient, position: fixed; inset: 0;).
- z-index: 1 — #backdrop-svg (position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none;).
- z-index: 2 — #vignette (a fixed div with background: radial-gradient(circle, transparent 40%, #000 120%); pointer-events: none;).
- z-index: 3 — <main> (position: relative; z-index: 3;).
- z-index: 10 — <header> (position: fixed; top: 0; left: 0; right: 0;).
- z-index: 10 — <footer> (position: fixed; bottom: 0; left: 0; right: 0;).
- Interactive controls (:focus-visible) receive position: relative; z-index: 100; and outline: 2px solid var(--accent); outline-offset: 4px;.
The <nav> lives inside <header>.

D. A TOKEN TABLE (--name: value) for every size and colour:
--accent: #c084fc;
--accent-glow: rgba(192, 132, 252, 0.35);
--bg-dark: #0a0610;
--bg-mid: #1a1025;
--bg-black: #000000;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--font-display: 'Space Grotesk', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
--header-h: 80px;
--footer-h: 64px;
--hero-pad-top: 40px;
--hero-pad-x: 24px;
--grid-gap: 16px;
--pill-pad-y: 10px;
--pill-pad-x: 24px;
--pill-radius: 999px;
--readout-size: 0.75rem;
--h1-size: clamp(2.5rem, 8vw, 6rem);
--h1-line: 1.05;
--z-bg: 0;
--z-backdrop: 1;
--z-vignette: 2;
--z-content: 3;
--z-header: 10;
--z-footer: 10;
(Include Google Fonts link for Space Grotesk 700 and JetBrains Mono 400 in the head).

E. THE COMPOSITION — header, hero, footer as a build order:
1. <header>: height: var(--header-h); padding: 0 24px; display: flex; align-items: center; justify-content: space-between; Contains <nav> (display: flex; gap: 24px;) with links: <a href="#work">Work</a>, <a href="#studio">Studio</a>, <a href="#contact">Contact</a>. Accessible names are provided by the text nodes 'Work', 'Studio', 'Contact'.
2. <main>: padding-top: var(--header-h); padding-bottom: var(--footer-h); padding-left: var(--hero-pad-x); padding-right: var(--hero-pad-x); display: grid; grid-template-columns: 1fr; gap: var(--grid-gap); place-items: center; text-align: center; min-height: 100dvh;
  - <h1> (the only h1 on the page): copy is 'TESSELLATE'. Style: font-family: var(--font-display); font-size: var(--h1-size); line-height: var(--h1-line); font-weight: 700; letter-spacing: -0.02em; color: var(--text-primary); margin: 0; text-transform: uppercase;
  - <p id="subtitle">: copy is 'Generative design studio engineering structural patterns for spatial interfaces.' Style: color: var(--text-secondary); font-family: var(--font-display); max-width: 600px; margin: 0;
  - <div id="readout">: contains <span id="readout-text">CURRENT TILING: TRIANGULAR</span>. Style: font-family: var(--font-mono); font-size: var(--readout-size); color: var(--accent); text-transform: uppercase; letter-spacing: 0.1em; margin-top: 8px;
3. <footer>: height: var(--footer-h); padding: 0 24px; display: flex; align-items: center; justify-content: space-between; Left side: <span>© 2024 Tessellate Studio</span> (font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-secondary);). Right side: <button id="toggle-motion" class="pill">Pause Motion</button> (accessible name 'Pause Motion' from text content).

F. THE UI LANGUAGE — button/pill treatments:
The .pill button (#toggle-motion) uses: background: linear-gradient(135deg, rgba(192,132,252,0.15), rgba(192,132,252,0.05)); border: 1px solid var(--accent-glow); color: var(--text-primary); padding: var(--pill-pad-y) var(--pill-pad-x); border-radius: var(--pill-radius); font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; cursor: pointer; transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
Hover state: background: linear-gradient(135deg, rgba(192,132,252,0.3), rgba(192,132,252,0.1)); transform: translateY(-2px); box-shadow: 0 4px 12px var(--accent-glow);
Focus-visible state: outline: 2px solid var(--accent); outline-offset: 4px; position: relative; z-index: 100;
Nav links: color: var(--text-secondary); text-decoration: none; transition: color 0.2s; Hover: color: var(--text-primary);. Focus-visible gets the same ring.

G. MOTION — a stagger table (element -> animation -> delay -> duration):
| Element | Animation | Delay | Duration |
|---|---|---|---|
| .mask-shapes | maskRotate | 0s | 24s |
| .mask-shapes | maskScale | 0s | 12s |
| #rect-tri | crossFadeTri | 0s | 18s |
| #rect-hex | crossFadeHex | 0s | 18s |
| #rect-herr | crossFadeHerr | 0s | 18s |
| h1 | fadeUp | 0.2s | 1s |
| #subtitle | fadeUp | 0.4s | 1s |
| #readout | fadeUp | 0.6s | 1s |
| #toggle-motion | fadeUp | 0.8s | 1s |
Named keyframes:
@keyframes maskRotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes maskScale { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }
@keyframes crossFadeTri { 0%, 30% { opacity: 1; } 33.33%, 96.66% { opacity: 0; } 100% { opacity: 1; } }
@keyframes crossFadeHex { 0%, 30% { opacity: 0; } 33.33%, 63.33% { opacity: 1; } 66.66%, 100% { opacity: 0; } }
@keyframes crossFadeHerr { 0%, 63.33% { opacity: 0; } 66.66%, 96.66% { opacity: 1; } 100% { opacity: 0; } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
All elements using fadeUp have animation-fill-mode: both;. .mask-shapes has transform-box: view-box; transform-origin: 50% 50%;.

H. A RESPONSIVE TABLE — token overrides per breakpoint:
1. @media (max-width: 900px) (tablet & mobile, scroll enabled): html, body { overflow: auto; } --h1-size: clamp(2rem, 10vw, 4rem); --header-h: 70px; --footer-h: 56px;
2. @media (max-width: 600px) (mobile): --header-h: 64px; --hero-pad-x: 16px; --grid-gap: 12px; --pill-pad-x: 16px;
3. @media (min-width: 901px) and (max-height: 800px) (max-height breakpoint 1): --h1-size: 4rem; --header-h: 70px; --footer-h: 56px;
4. @media (min-width: 901px) and (max-height: 700px) (max-height breakpoint 2): --h1-size: 3rem; --header-h: 60px; --footer-h: 48px; --hero-pad-top: 20px;
No horizontal overflow occurs at 390x844, 768x1024, 1440x900, or 1920x1080 because #backdrop-svg uses preserveAspectRatio="xMidYMid slice" and body { overflow-x: hidden; }.

I. FAILURE GUARDS:
1. Resting opacity 1: All fadeUp elements rest at opacity: 1; transform: none; after their animation completes (due to animation-fill-mode: both). If animations are stripped, they default to visible.
2. Two-rAF fallback: window.__DEMO__.ready = true is set inside a double requestAnimationFrame callback, guaranteeing the flag flips strictly after the browser's first paint, preventing false-positive readiness.
3. Reduced-motion: A @media (prefers-reduced-motion: reduce) block sets animation: none !important; transition: none !important; for all elements. #rect-hex and #rect-herr are set to display: none;, #rect-tri is forced to opacity: 1;. .mask-shapes is given a static transform: rotate(15deg) scale(1.05); to preserve the composition without motion. The JS still runs the rAF loop to increment window.__DEMO__.frames (so telemetry passes) but does not manipulate DOM styles. document.body.classList.add('reduced') is added for any JS conditionals.

J. THE EXACT JS:
window.__DEMO__ = { name: 'tessellate', ready: false, frames: 0, effects: [], errors: [] };
window.addEventListener('error', (e) => { window.__DEMO__.errors.push(e.message); });
['svg-pattern-crossfade', 'mask-rotate-scale', 'monospace-readout'].forEach(e => window.__DEMO__.effects.push(e));

const readouts = ['TRIANGULAR', 'HEXAGONAL', 'HERRINGBONE'];
let lastIdx = -1;
const readoutEl = document.getElementById('readout-text');

function loop() {
  window.__DEMO__.frames++;
  const idx = Math.floor(Date.now() / 6000) % 3;
  if (idx !== lastIdx) {
    lastIdx = idx;
    if (readoutEl) readoutEl.textContent = 'CURRENT TILING: ' + readouts[idx];
  }
  requestAnimationFrame(loop);
}

requestAnimationFrame(() => {
  requestAnimationFrame(() => {
    window.__DEMO__.ready = true;
    requestAnimationFrame(loop);
  });
});

document.addEventListener('DOMContentLoaded', () => {
  const btn = document.getElementById('toggle-motion');
  if (btn) {
    btn.addEventListener('click', () => {
      const paused = document.body.classList.toggle('paused');
      btn.textContent = paused ? 'Play Motion' : 'Pause Motion';
      const els = document.querySelectorAll('#rect-tri, #rect-hex, #rect-herr, .mask-shapes');
      els.forEach(el => el.style.animationPlayState = paused ? 'paused' : 'running');
    });
  }
});


---

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