Landing Pagestealth/space-bunny-alphaverified 18/18confirmed cross-check
Cardinal
A precision compass rose with 72 tick marks seeking a wandering bearing with spring damping, behind three pulsing magnetic-field rings.
The exact prompt
A. WHAT IS BEING BUILT
This document specifies the complete build order for the "CARDINAL" single-viewport cinematic landing page. The page is a high-precision, dark-mode navigation interface. The document title must be exactly `<title>CARDINAL — Precision Navigation</title>`. The HTML tag must be `<html lang="en">`. The viewport rule is `<meta name="viewport" content="width=device-width, initial-scale=1.0">`. The body must enforce a single viewport on desktop: `height: 100dvh; overflow: hidden;` for screens >=901px, and allow native scrolling (`overflow-y: auto; overflow-x: hidden;`) for screens <=900px.
Do NOT build: any multi-page routing; any WebGL, Three.js, or canvas-based 3D rendering; any React, Vue, Svelte, or framework code; any Tailwind or CSS frameworks; any GSAP, Lottie, or anime.js; any external raster images (jpg/png/gif), external SVGs, video files, or audio files; any base64 encoded media blobs; any custom font files (woff/woff2/ttf); any horizontal scrollbars at 390px, 768px, 1440px, or 1920px; any scroll-jacking or smooth-scroll libraries.
B. THE BACKDROP
The base background is a deep black: `--bg: #050505`. A subtle radial glow sits at z-index 0: `radial-gradient(circle at 50% 50%, rgba(248, 113, 113, 0.08) 0%, rgba(5, 5, 5, 1) 60%)` applied to a pseudo-element covering 100vw and 100vh.
The magnetic field rings consist of 3 SVG `<circle>` elements inside an SVG with a `0 0 100 100` viewBox. The radii are 30, 40, and 50 user units (which map to 30vmin, 40vmin, 50vmin when the SVG is scaled). The stroke is `var(--accent)` at `0.3` user units wide, with `fill="none"`. They pulse outward using the `pulse-ring` keyframe: scale from 0.8 to 1.2, opacity from 0.8 to 0. Durations are 4s, 5s, and 6s. Delays are 0s, 1.3s, and 2.6s. The entire ring container drifts with a `drift` animation: `transform: translate(-50%, -50%) rotate(360deg)` over 180s linear infinite.
The canvas resolution trick (`canvas.width = canvas.offsetWidth * devicePixelRatio`) is explicitly bypassed because we are using SVG and CSS gradients instead of `<canvas>`. This guarantees the per-frame cost budget with zero rasterization overhead. The per-frame cost budget is strictly 2 DOM writes: the needle's `transform` and the readout's `textContent`. Zero `getBoundingClientRect` calls are made inside the loop. Zero `ctx.filter` or `ctx.shadowBlur` operations exist.
C. THE LAYER STACK
z-index 0: `.bg-glow` (radial gradient pseudo-element).
z-index 1: `#rings` (SVG container for the 3 pulsing magnetic rings).
z-index 2: `#rose-container` (SVG container for the 72 ticks, 4 labels, and 1 needle).
z-index 3: `header` (logo and navigation).
z-index 4: `.hero` (the H1 and the live bearing readout).
z-index 5: `footer` (status indicator and action buttons).
D. TOKEN TABLE
--bg: #050505;
--accent: #f87171;
--accent-dim: rgba(248, 113, 113, 0.2);
--text-main: #f3f4f6;
--text-muted: #9ca3af;
--font-mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--rose-size: 70vmin;
--tick-length-cardinal: 2.5;
--tick-length-ordinal: 1.5;
--tick-length-minor: 0.8;
--needle-length: 60;
--h1-size: clamp(2.5rem, 6vw, 5rem);
--readout-size: clamp(1rem, 2vw, 1.5rem);
--header-footer-pad: 2rem 3rem;
E. THE COMPOSITION
Header: `display: grid; grid-template-columns: 1fr auto; padding: var(--header-footer-pad); position: absolute; top: 0; width: 100%; box-sizing: border-box; z-index: 3;`. Left side contains the text "CARDINAL" (`font-family: var(--font-sans); font-weight: 600; letter-spacing: 0.5em; font-size: 1rem; color: var(--text-main);`). Right side contains `<nav>` with three links: "TECHNOLOGY", "FLEET", "CONTACT" (`display: flex; gap: 2rem; font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; color: var(--text-muted); text-decoration: none;`).
Hero: `position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; z-index: 4; pointer-events: none;`. Contains exactly one `<h1>` with the text "NAVIGATE WITH ABSOLUTE CERTAINTY" (`font-size: var(--h1-size); font-weight: 600; letter-spacing: -0.02em; margin: 0; text-transform: uppercase;`). Below it is a `<div id="readout">` showing "BRG 000°" (`font-family: var(--font-mono); font-size: var(--readout-size); color: var(--accent); margin-top: 1rem; font-weight: 700;`).
Footer: `position: absolute; bottom: 0; width: 100%; box-sizing: border-box; padding: var(--header-footer-pad); display: grid; grid-template-columns: 1fr auto; align-items: center; z-index: 5;`. Left side: "STATUS: ONLINE" (`font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-muted); display: flex; align-items: center; gap: 0.5rem;`) followed by an 8px dot (`width: 8px; height: 8px; background: var(--accent); border-radius: 50%; animation: blink 2s infinite;`). Right side: `.btn-group { display: flex; gap: 1rem; }` containing two buttons.
F. THE UI LANGUAGE
Primary Button: `.btn-primary { background: linear-gradient(135deg, #f87171 0%, #dc2626 100%); color: #ffffff; border: none; padding: 0.75rem 1.5rem; border-radius: 999px; font-family: var(--font-mono); font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.1em; cursor: pointer; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s; }`. Hover state: `.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(248, 113, 113, 0.3); }`.
Secondary Button: `.btn-secondary { background: transparent; color: var(--text-main); border: 1px solid rgba(248, 113, 113, 0.3); padding: 0.75rem 1.5rem; border-radius: 999px; font-family: var(--font-mono); font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.1em; cursor: pointer; transition: background 0.2s, border-color 0.2s; }`. Hover state: `.btn-secondary:hover { border-color: var(--accent); background: rgba(248, 113, 113, 0.05); }`.
Focus visible: `:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }`.
G. MOTION
Stagger table:
- `header`: `fade-down`, 0.6s, delay 0.1s.
- `line` (ticks): `fade-in`, 0.5s, delay `calc(var(--i) * 0.01s)`.
- `h1`: `fade-up`, 0.8s, delay 0.2s.
- `#readout`: `fade-up`, 0.8s, delay 0.4s.
- `footer`: `fade-up`, 0.6s, delay 0.6s.
- `circle` (rings): `pulse-ring`, 4s/5s/6s, delay 0s/1.3s/2.6s.
- `#needle`: Spring damping via JS (continuous).
Named keyframes:
- `@keyframes pulse-ring { 0% { transform: scale(0.8); opacity: 0.8; } 100% { transform: scale(1.2); opacity: 0; } }`
- `@keyframes drift { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } }`
- `@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }`
- `@keyframes fade-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }`
- `@keyframes fade-down { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }`
- `@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }`
H. RESPONSIVE TABLE
Base (1440x900): `--rose-size: 70vmin;` `--header-footer-pad: 2rem 3rem;`
@media (max-width: 900px): `body { overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; min-height: 100dvh; }`, `#rose-container { position: relative; top: auto; left: auto; transform: none; margin: 10vh auto; }`, `.hero { position: relative; top: auto; left: auto; transform: none; margin-bottom: 10vh; }`, `footer { position: relative; }`.
@media (max-width: 768px): `--rose-size: 80vmin;` `--h1-size: 3rem;`, `header { grid-template-columns: 1fr; gap: 1rem; text-align: center; }`, `nav { justify-content: center; }`.
@media (max-width: 390px): `--rose-size: 90vmin;` `--h1-size: 2rem;`, `footer { grid-template-columns: 1fr; gap: 1.5rem; text-align: center; }`, `.btn-group { justify-content: center; }`.
@media (max-height: 700px): `--rose-size: 50vmin;`, `header, footer { padding: 1rem 2rem; }`.
@media (max-height: 500px): `--rose-size: 40vmin;`, `--h1-size: 1.5rem;`, `header, footer { padding: 0.5rem 1rem; }`.
I. FAILURE GUARDS
Resting opacity: All base CSS sets `opacity: 1` for text and ticks. The `fade-in` and `fade-up` animations end at `opacity: 1` with `forwards`. If JS fails to execute, CSS animations still play and leave all elements perfectly visible.
Needle resting: Initial CSS `transform: rotate(0deg)` is set on `#needle`. If JS fails, the needle remains visibly rendered at 0 degrees (North).
Two-rAF fallback: The JS loop uses `requestAnimationFrame`. If the tab is backgrounded, rAF naturally pauses and `frames` stops incrementing, which is correct. No custom polyfill is required.
Reduced-motion: The CSS includes `@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; } }`. The JS checks `window.matchMedia('(prefers-reduced-motion: reduce)').matches`. If true, it sets `currentBearing = 45`, updates the DOM exactly once, sets `ready = true`, and deliberately does NOT start the rAF loop or the `setInterval`. The navigation settles instantly and the page remains completely readable and static.
J. THE EXACT JS
```javascript
window.__DEMO__ = { name: 'cardinal', ready: false, frames: 0, effects: [], errors: [] };
window.addEventListener('error', (e) => window.__DEMO__.errors.push(e.message));
const ticksGroup = document.getElementById('ticks');
const ringsGroup = document.getElementById('rings');
const needle = document.getElementById('needle');
const readout = document.getElementById('readout');
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 1. Generate 72 ticks
for (let i = 0; i < 72; i++) {
const tick = document.createElementNS('http://www.w3.org/2000/svg', 'line');
const angle = (i * 5) * (Math.PI / 180);
const isCardinal = i % 18 === 0;
const isOrdinal = i % 9 === 0 && !isCardinal;
const length = isCardinal ? 2.5 : (isOrdinal ? 1.5 : 0.8);
const strokeWidth = isCardinal ? 0.6 : 0.3;
const rOuter = 48;
const rInner = rOuter - length;
tick.setAttribute('x1', (50 + rOuter * Math.sin(angle)).toString());
tick.setAttribute('y1', (50 - rOuter * Math.cos(angle)).toString());
tick.setAttribute('x2', (50 + rInner * Math.sin(angle)).toString());
tick.setAttribute('y2', (50 - rInner * Math.cos(angle)).toString());
tick.setAttribute('stroke', 'var(--accent)');
tick.setAttribute('stroke-width', strokeWidth.toString());
tick.setAttribute('opacity', isCardinal ? '1' : (isOrdinal ? '0.6' : '0.3'));
tick.style.setProperty('--i', i);
if (!prefersReducedMotion) {
tick.style.animation = 'fade-in 0.5s ease-out forwards';
tick.style.animationDelay = `calc(var(--i) * 0.01s)`;
}
ticksGroup.appendChild(tick);
}
// 2. Generate 4 cardinal labels
const labels = [
{ text: 'N', angle: 0 },
{ text: 'E', angle: 90 },
{ text: 'S', angle: 180 },
{ text: 'W', angle: 270 }
];
labels.forEach(l => {
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
const angle = l.angle * (Math.PI / 180);
const r = 42;
text.setAttribute('x', (50 + r * Math.sin(angle)).toString());
text.setAttribute('y', (50 - r * Math.cos(angle) + 1.5).toString());
text.setAttribute('fill', 'var(--text-main)');
text.setAttribute('font-size', '4');
text.setAttribute('font-family', 'var(--font-mono)');
text.setAttribute('text-anchor', 'middle');
text.textContent = l.text;
ticksGroup.appendChild(text);
});
// 3. Generate 3 magnetic rings
for (let i = 0; i < 3; i++) {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', '50');
circle.setAttribute('cy', '50');
circle.setAttribute('r', (30 + i * 10).toString());
circle.setAttribute('fill', 'none');
circle.setAttribute('stroke', 'var(--accent)');
circle.setAttribute('stroke-width', '0.3');
if (!prefersReducedMotion) {
circle.style.animation = `pulse-ring ${4 + i}s ease-out infinite`;
circle.style.animationDelay = `${i * 1.3}s`;
}
ringsGroup.appendChild(circle);
}
// 4. Spring physics for the needle
let currentBearing = 0;
let targetBearing = 0;
let velocity = 0;
const stiffness = 0.08;
const damping = 0.85;
if (prefersReducedMotion) {
currentBearing = 45;
targetBearing = 45;
needle.style.transform = `rotate(${currentBearing}deg)`;
readout.textContent = `BRG ${Math.round(currentBearing).toString().padStart(3, '0')}°`;
window.__DEMO__.ready = true;
} else {
window.__DEMO__.effects.push('spring-needle');
window.__DEMO__.effects.push('magnetic-pulse');
setInterval(() => {
targetBearing = Math.random() * 360;
}, 3500);
function loop() {
window.__DEMO__.frames++;
let diff = targetBearing - currentBearing;
// Normalize difference to the shortest path (-180 to 180)
diff = ((diff + 180) % 360 + 360) % 360 - 180;
const force = diff * stiffness;
velocity = (velocity + force) * damping;
currentBearing = (currentBearing + velocity + 360) % 360;
needle.style.transform = `rotate(${currentBearing}deg)`;
readout.textContent = `BRG ${Math.round(currentBearing).toString().padStart(3, '0')}°`;
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
window.__DEMO__.ready = true;
}
```
---
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)
- document.querySelectorAll('h1').length === 1 && document.querySelector('h1').textContent.trim() === 'NAVIGATE WITH ABSOLUTE CERTAINTY'
- window.__DEMO__.frames > 20 after 1200ms of runtime
- getComputedStyle(document.getElementById('needle')).transform !== 'none' and its value differs between two samples 100ms apart
- document.documentElement.scrollWidth <= document.documentElement.clientWidth at 390px, 768px, and 1440px viewport widths
- window.__DEMO__.effects.includes('spring-needle') && window.__DEMO__.effects.includes('magnetic-pulse')
- 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