Skip to content
LandingPagePrompts
Landing Pagestealth/space-bunny-alphaverified 20/20partial cross-check

Margin

Two counter-scrolling columns of procedural deadstock garment plates frame a static marketplace message.

The exact prompt

A. BUILD TARGET

Create one complete, framework-free index.html for the Second Skin marketplace landing page. Set the document title to "Second Skin — Deadstock, kept in motion" and set lang="en". Load the only permitted network resource, the Open Sans stylesheet from fonts.googleapis.com, followed by fonts.gstatic.com, with a system fallback of Arial, Helvetica, sans-serif; if the request fails, all typography must remain legible. No other network request is allowed.

At viewport widths of 901px and wider, the document must use height: 100dvh, min-height: 700px, and overflow: hidden. The page itself is a fixed, full-viewport editorial marketplace scene and must not scroll. At widths of 900px and below, replace that rule with height: auto, min-height: 100dvh, overflow-x: hidden, and normal vertical document scrolling. This is not a scroll-driven concept: the counters run continuously rather than responding to page scroll.

Use semantic header, nav, main, section, article, figure, and footer elements. Include exactly one h1. Every visible link, button, nav item, and size label must have an accessible name. Decorative SVGs and texture layers must use aria-hidden="true". Add :focus-visible outlines using a 2px solid #B4674E outline with a 3px #F4F1EA offset.

Do NOT build a product grid, shopping cart, account modal, filter drawer, checkout flow, video background, image carousel, external image, photograph, WebGL scene, Three.js canvas, scroll-linked reveal, horizontal page scroll, text marquee, or mobile pin. Do not include a second h1, a cookie notice, a newsletter modal, autoplay sound, a cursor follower, or a JavaScript animation library. Do not use Tailwind, React, GSAP, Lottie, MP4, WebM, GIF, remote raster media, CDN-hosted scripts, base64 imagery, or data-URI photography.

B. THE BACKDROP

The page background is a flat #F4F1EA bone field. Place one fixed decorative canvas behind all content at z-index 0. It is not a garment renderer: it is a low-cost ambient weave that prevents the large bone areas from reading as empty. The canvas is pointer-events: none and has the same dimensions as the viewport.

Create the canvas at devicePixelRatio, capped at 1.5. In JavaScript, multiply window.innerWidth and window.innerHeight by the capped ratio, assign those values to canvas.width and canvas.height, and set the CSS size to 100% by 100%. This is the canvas resolution trick. Redraw it only on resize, not on every animation frame.

The static canvas background is transparent. Draw a 3px lattice in two passes. First draw horizontal lines at y = 0, 3, 6 through height using rgba(26,24,21,0.018). Then draw vertical lines at x = 0, 3, 6 through width using rgba(26,24,21,0.012). The resulting average composite cost is under 0.7 fill rectangles at a 1.5 DPR cap. Do not call ctx.filter, ctx.shadowBlur, ctx.drawImage, or any per-item blur operation. The visible garment cards supply the detailed texture; the canvas is only a page-level paper grain.

The primary moving backdrop is the pair of column tracks. Each column contains 12 article elements: 6 authored garment plates followed by an exact DOM duplicate of those 6 articles. Each card is 320px wide by 440px high on the desktop scale. The 12-card track uses 16px row gaps and is 6,768px tall including the six repeated cards and eleven inter-card gaps. The first and second columns are identical in dimensions but use different authored plate order and opposite motion direction. Their transform animation travels exactly 50% of the duplicated track, 3,384px, over 34 seconds with linear timing. The duplicated boundary contains the same 16px gap as every internal boundary, so the loop is visually continuous.

Each card has a flat #E8E4DC field. Inside every card, render one inline SVG with viewBox="0 0 320 440", width 320px, height 440px. The SVG must contain exactly three overlapping garment-body path shapes: a left shoulder and sleeve path, a main body path, and a right shoulder and sleeve path. All three paths use the same muted dye color selected for that card, a 1.5px #1A1815 stroke, and a solid fill. Over the SVG, place a 3px repeating-linear-gradient weave layer with background-image repeating-linear-gradient(0deg, rgba(26,24,21,0.04) 0px, rgba(26,24,21,0.04) 1px, transparent 1px, transparent 3px), plus a second repeating-linear-gradient rotated 90 degrees at the same opacity.

Use six dye colors in this exact order: #7C8B7A, #9B9A78, #A97862, #6F7D82, #B4674E, and #8A786D. Cycle those six colors through the first authored six cards in each column, then repeat the colors in the duplicated six. Use these silhouettes in the SVG, with the path coordinates measured in the 320 by 440 viewBox: left path "M80 112 C54 118 38 141 28 177 L45 245 L80 224 L88 137 Z"; body path "M88 118 C108 101 134 101 158 118 L177 345 C153 360 96 360 72 345 Z"; right path "M158 112 C184 118 200 141 210 177 L193 245 L158 224 L150 137 Z". Apply the same geometry to all cards and vary the SVG group transform by card: identity for the first row, translateY(0) for all rows, and use a 0.92 to 1.04 scale range across the six cards. The variation must not alter the 320 by 440 card bounds.

Add one hang-tag rectangle inside each SVG. Center it at x=250, y=86, width 54px, height 78px, with a 1.5px #1A1815 outline, a small 4px circular punch hole at x=250 y=99, and a 0.35 opacity #F4F1EA diagonal fold line. Set the tag rectangle group to transform-origin 250px 86px and scale(0.6). Render the size text at x=250, y=133, using the card's own selected font stack, font-size 15px, font-weight 700, text-anchor middle, fill #1A1815, and the exact size string assigned to that plate. Alternate the size strings XS, S, M, L, XL, and 2XL by card index.

The apparent travel of each fade region is 900px: each column is covered by a 128px top gradient and a 128px bottom gradient. The gradients are static and do not animate. They are #F4F1EA at 0% opacity 1, #F4F1EA at 58% opacity 0.86, transparent at 100%, with the bottom gradient mirrored by reversing its stop sequence. Place them inside each column, outside the moving track, with pointer-events: none.

C. THE LAYER STACK

The stacking order is explicit and must not be changed.

z-index 0: the ambient weave canvas, fixed to the viewport, pointer-events none.

z-index 1: the page bone background on body and the full-width hero shell.

z-index 2: the left marquee column, at the left edge of the central desktop grid.

z-index 3: the left column's top and bottom fade overlays.

z-index 4: the center message panel, positioned in the 40vw horizontal gap.

z-index 5: the right marquee column.

z-index 6: the right column's top and bottom fade overlays.

z-index 7: the header navigation, which remains above all moving material.

z-index 8: the footer metadata, which remains above all moving material.

z-index 9: a single 1px transparent focus-ring utility, pointer-events none, used only as a visual stacking helper when needed.

The columns must not overlap the center panel on desktop. The center panel is 40vw wide at widths from 901px through 1920px, capped at 640px. The two column cards remain 320px wide. At 1440px, use a centered three-column grid with 320px, minmax(320px, 40vw), 320px and 40px gaps. The grid occupies 1,296px at 1440px, leaving 72px of outer breathing room. At 1920px, the 640px center cap creates 400px of intentional page margin on each side of the full composition.

D. TOKEN TABLE

Define every value used by the page in :root:

--bg: #F4F1EA
--ink: #1A1815
--plate: #E8E4DC
--sage: #7C8B7A
--rust: #B4674E
--white-muted: rgba(244,241,234,0.72)
--line: rgba(26,24,21,0.18)
--line-strong: rgba(26,24,21,0.34)
--grain: rgba(26,24,21,0.04)
--dye-1: #7C8B7A
--dye-2: #9B9A78
--dye-3: #A97862
--dye-4: #6F7D82
--dye-5: #B4674E
--dye-6: #8A786D
--page-min: 700px
--header-height: 76px
--footer-height: 66px
--grid-gap: 40px
--card-width: 320px
--card-height: 440px
--card-gap: 16px
--fade-height: 128px
--center-width: 40vw
--center-max: 640px
--gutter: 32px
--radius-pill: 999px
--radius-tag: 4px
--type-display: clamp(3.5rem, 6.2vw, 7.75rem)
--type-title: clamp(1.75rem, 3.15vw, 4rem)
--type-body: 16px
--type-small: 12px
--type-label: 11px
--leading-display: 0.88
--leading-title: 0.98
--leading-body: 1.45
--track-label: 0.14em
--motion-counter: 34s
--motion-hover: 220ms
--motion-tag: 400ms
--ease-hover: cubic-bezier(.2,.8,.2,1)
--ease-entrance: cubic-bezier(.25,.1,.25,1)

E. THE COMPOSITION

Build the document in this order.

First create body and the semantic page wrapper. Body has margin 0, background var(--bg), color var(--ink), font-family Arial, Helvetica, sans-serif, overflow-x hidden, and -webkit-font-smoothing: antialiased. Set the header height to var(--header-height), display grid with grid-template-columns 1fr auto 1fr, align-items center, padding 0 var(--gutter), and border-bottom 1px solid var(--line). Place the wordmark link at the left with exact visible copy "SECOND SKIN", font-size 14px, font-weight 800, letter-spacing 0.18em, and accessible label "Second Skin home". Place the nav in the center with two links, exact visible copy "SHOP DEADSTOCK" and "HOW IT WORKS", both 11px, uppercase, 700, letter-spacing 0.12em. Place the right-side link at the right with exact visible copy "SELL A PIECE", border-bottom 1px solid var(--ink), and accessible label "Sell a piece". Header z-index is 7.

Then create main and the single hero section. The hero is width 100%, height calc(100dvh - var(--header-height) - var(--footer-height)), min-height 560px, and display grid. The desktop grid is grid-template-columns var(--card-width) minmax(320px, var(--center-width)) var(--card-width), column-gap var(--grid-gap), align-items stretch, justify-content center, padding 0 32px. The center block has z-index 4, display flex, flex-direction column, justify-content center, align-items flex-start, padding 0 28px, and text-align left.

Create the center message with the following exact copy. The eyebrow is "THE DEADSTOCK EDIT / 06". The h1 is "Wear the / in-between." The h1 uses max-width 560px, font-size var(--type-display), line-height var(--leading-display), font-weight 500, letter-spacing -0.075em, margin 0, and wraps on the literal slash using a line break. The paragraph is "Discontinued shapes, unworn surplus, and hard-to-find layers. Browse pieces that no longer make it to the shelf." Its max-width is 390px, margin 26px 0 0, font-size var(--type-body), line-height var(--leading-body), color rgba(26,24,21,0.72). The primary action is an accessible link with visible copy "EXPLORE THE EDIT" and aria-label "Explore the deadstock edit". The secondary action is a text link with visible copy "VIEW SELLING GUIDE" and aria-label "View the selling guide". The action row uses margin-top 32px, display flex, gap 18px, align-items center, flex-wrap wrap.

Below the center message, add a compact inventory line with exact copy "2,481 pieces rescued" and a 6px sage dot. It sits 44px below the action row, with font-size 12px, letter-spacing 0.08em, and color rgba(26,24,21,0.62). This line is informational, not a control.

Create the left and right marquee columns as two section elements with aria-label "Discontinued garment plates". Each section is 320px wide, height 100%, overflow hidden, position relative. Inside each section, create a .track containing 12 .plate articles. Set track position absolute at top 0 and left 0, width 320px, display flex, flex-direction column, gap 16px, and will-change transform. Animate the left track with keyframes columnUp from translateY(0) to translateY(-3384px), and the right track with keyframes columnDown from translateY(-3384px) to translateY(0). Both animations are 34s linear infinite, with animation-delay 0s on the left and -17s on the right. Do not pause either track on hover.

Each plate article has position relative, width 320px, height 440px, flex 0 0 440px, overflow hidden, background var(--plate), border 1px solid rgba(26,24,21,0.08), and transform-origin center. Add a figure element with margin 0 and an SVG filling the article. Add a small plate index in the upper-left corner at 16px 16px, font-size 10px, font-weight 700, letter-spacing 0.12em, and copy "SS / 01" through "SS / 12" by authored row. The index must not obscure the garment body.

Place the exact top fade in each column with position absolute, inset 0 0 auto, height 128px, background linear-gradient(to bottom, #F4F1EA 0%, rgba(244,241,234,0.86) 58%, transparent 100%), z-index 3. Place the bottom fade with position absolute, inset auto 0 0, height 128px, background linear-gradient(to top, #F4F1EA 0%, rgba(244,241,234,0.86) 58%, transparent 100%), z-index 3.

Create the footer as a grid with height var(--footer-height), padding 0 var(--gutter), border-top 1px solid var(--line), display grid, grid-template-columns 1fr auto 1fr, align-items center, font-size 11px, letter-spacing 0.08em. Left copy is "CURATED IN LONDON / 2025". Center copy is "NO SEASON. NO SAMPLE SALE.". Right copy is "SCROLL LESS. FIND MORE." and is right-aligned. Footer is not sticky on mobile.

F. THE UI LANGUAGE

The primary action is a 48px high pill with padding 0 22px, border-radius 999px, background linear-gradient(135deg, #1A1815 0%, #34302A 100%), color #F4F1EA, font-size 11px, font-weight 800, letter-spacing 0.13em, and border 1px solid #1A1815. Its hover state is background linear-gradient(135deg, #34302A 0%, #1A1815 100%), transform translateY(-2px), and box-shadow 0 6px 0 rgba(180,103,78,0.36). The primary action must return to its original position in 180ms when focus or hover ends.

The secondary action has no filled background, a 1px bottom border, padding 0 0 4px, and color var(--ink). Its hover state is color var(--rust), border-color var(--rust), and transform translateX(3px). The selling link is not a pill. Header links use color var(--ink) and a 2px bottom border that changes from transparent to var(--rust) on hover and focus-visible. No link changes layout on hover.

Plate hover is 220ms with var(--ease-hover): transform translateY(-6px) scale(1.03), border-color var(--line-strong), and a subtle box-shadow 0 12px 24px rgba(26,24,21,0.08). Because the card is inside a moving track, apply hover transform to the article and never stop the parent track. The SVG tag group swings from rotate(-2deg) to rotate(4deg) over 400ms with ease-out, using transform-origin 250px 86px. Return to rotate(0deg) on pointer leave over 400ms. All hover motion is disabled under reduced motion.

G. MOTION

The entrance grammar is opacity 0 to 1 plus translateY(20px) to translateY(0), duration 620ms, using var(--ease-entrance), fill mode both. Apply .rise once on load.

Use this exact stagger:

header .wordmark: rise, delay 0ms.
header nav: rise, delay 80ms.
header .sell-link: rise, delay 120ms.
center .eyebrow: rise, delay 180ms.
center h1: rise, delay 260ms.
center p: rise, delay 380ms.
center .actions: rise, delay 480ms.
center .inventory: rise, delay 560ms.
footer .footer-left: rise, delay 620ms.
footer .footer-center: rise, delay 680ms.
footer .footer-right: rise, delay 740ms.

Define rise as opacity 0 and transform translateY(20px) at 0%, opacity 1 and transform translateY(0) at 100%. Define columnUp and columnDown as the two exact transform tracks described in the composition. Define tagSwing as rotate(0deg) at 0%, rotate(4deg) at 55%, and rotate(0deg) at 100%. Use only transform and opacity in animated CSS. The marquee uses transform only. Entrance uses transform and opacity only. The tag uses transform only.

H. RESPONSIVE TABLE

At 1440px by 900px and 1920px by 1080px, retain the desktop values. The composition remains one viewport, the center width is min(40vw, 640px), and the two 320px columns remain visible. At a viewport height of 760px, override --header-height to 64px, --footer-height to 54px, --gutter to 24px, --type-display to clamp(3rem, 5.7vw, 6rem), and set the hero minimum height to 520px. At a viewport height of 680px, override --header-height to 58px, --footer-height to 48px, --gutter to 20px, --type-display to clamp(2.6rem, 5.2vw, 5rem), set the center paragraph max-width to 330px, and reduce the action row margin-top to 22px.

At 901px through 1023px, set --card-width: 280px, --card-height: 386px, --grid-gap: 24px, --gutter: 24px, --fade-height: 104px, and --center-width: 40vw. Render the cards at 280 by 386 in CSS, but keep the SVG viewBox at 0 0 320 440 so the silhouettes do not distort. Update the track's exact half travel to 2,976px using the duplicated six-card geometry and 14px card gap. The 34s duration remains unchanged.

At 768px by 1024px, switch to the stacked mobile composition. Set body and the document to normal vertical flow. Set header height 64px, footer height 58px, gutter 24px, and hero height auto. Use a single-column main grid with grid-template-rows: auto 520px; row-gap 24px. Put the center message first, then place both columns in a two-column subgrid with grid-template-columns 1fr 1fr and column-gap 16px. Each mobile column is width 100%, max-width 360px, and height 520px. Set cards to 100% width, 440px high, and use the same SVG viewBox with preserveAspectRatio="xMidYMid slice". Reduce the card gap to 14px and set the mobile track half travel to 2,964px. The mobile fade height is 96px. The center text uses a max-width of 520px, text-align left, and padding 0 24px.

At 390px by 844px, set header height 60px, footer height auto with min-height 92px, gutter 16px, row-gap 18px, grid-template-rows: auto 420px, and grid-template-columns 1fr 1fr. Set the mobile columns to 171px wide, cards to 171px wide and 300px high, card gap 12px, fade height 78px, and mobile track half travel to 1,872px. The SVG uses preserveAspectRatio="xMidYMid slice". The center message has padding 0 8px, h1 font-size 48px, line-height 0.9, paragraph max-width 340px, and action row gap 12px. Reduce the inventory line margin-top to 28px. Keep the two-column counter-scroll; do not hide either column.

At max-width 900px, header navigation keeps both links but reduces the gap to 14px, the sell link becomes text-only, and the header uses grid-template-columns 1fr auto. Put the sell link in the second row of the nav? No: move the sell link to the footer by duplicating no controls. Hide the header sell link with display:none, which removes it from the accessibility tree, and keep the two center nav links. The footer becomes a 2-row layout with the left and right metadata on the first row and the center statement on the second row. At 390px, footer padding is 14px 16px and all footer text is 9px.

I. FAILURE GUARDS

Set every animated entrance element's resting opacity to 1 and resting transform to translateY(0). Do not leave opacity 0 if JavaScript fails. The plate tracks have their authored content in the DOM before animation begins. If CSS animation is unavailable, cards remain visible in a static, usable column. If fonts fail, the fallback stack renders all copy without layout collapse. If SVG path rendering fails, the plate field, index, and size tag remain readable.

Use a two-requestAnimationFrame fallback to set the telemetry ready flag: first rAF schedules the second rAF, and the second rAF sets ready true. The telemetry object must exist before either frame is scheduled. If reduced motion is enabled, cancel the marquee animations by setting animation: none, show the first six cards in each column at opacity 1, and do not call requestAnimationFrame continuously. Read the media query once at startup and again on its change event.

J. THE EXACT JS

Use this JavaScript at the end of body:

const telemetry = {
  name: "margin",
  ready: false,
  frames: 0,
  effects: [],
  errors: []
};
window.__DEMO__ = telemetry;

window.addEventListener("error", event => {
  telemetry.errors.push(String(event.message || "window error"));
});

const reduceMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const canvas = document.querySelector("[data-weave]");
const context = canvas && canvas.getContext("2d", { alpha: true });
const ratio = Math.min(window.devicePixelRatio || 1, 1.5);
let running = true;
let previous = 0;

function drawWeave() {
  if (!canvas || !context) return;
  const width = window.innerWidth;
  const height = window.innerHeight;
  canvas.width = Math.max(1, Math.floor(width * ratio));
  canvas.height = Math.max(1, Math.floor(height * ratio));
  canvas.style.width = width + "px";
  canvas.style.height = height + "px";
  context.setTransform(ratio, 0, 0, ratio, 0, 0);
  context.clearRect(0, 0, width, height);
  context.fillStyle = "rgba(26,24,21,0.018)";
  for (let y = 0; y < height; y += 3) {
    context.fillRect(0, y, width, 1);
  }
  context.fillStyle = "rgba(26,24,21,0.012)";
  for (let x = 0; x < width; x += 3) {
    context.fillRect(x, 0, 1, height);
  }
}

function markReady() {
  telemetry.ready = true;
  requestAnimationFrame(frame);
}

function frame(time) {
  if (!running) return;
  if (previous === 0) previous = time;
  const delta = Math.min(50, time - previous);
  previous = time;
  telemetry.frames += 1;
  if (telemetry.frames === 1) telemetry.effects.push("ambient-weave");
  if (telemetry.frames === 1) telemetry.effects.push("counter-scroll");
  if (telemetry.frames === 1) telemetry.effects.push("rise");
  if (delta > 40) telemetry.effects.push("frame-recovery");
  requestAnimationFrame(frame);
}

function stopForReducedMotion() {
  running = false;
  telemetry.effects.push("reduced-motion");
}

drawWeave();
window.addEventListener("resize", drawWeave, { passive: true });
if (reduceMotionQuery.matches) {
  stopForReducedMotion();
} else {
  requestAnimationFrame(() => requestAnimationFrame(markReady));
}
reduceMotionQuery.addEventListener("change", event => {
  if (event.matches) stopForReducedMotion();
});

window.__DEMO__ = telemetry;

The implementation must keep the rAF loop at one request per frame, increment frames exactly once inside that callback, avoid getBoundingClientRect entirely, avoid layout reads after initial setup, and produce no console output.

---

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 page contains exactly one h1, semantic header, nav, main, and footer, and document.documentElement.scrollHeight does not exceed window.innerHeight at 1440x900.
  2. At 1440x900, both marquee sections contain 12 plate articles, each plate measures 320 by 440 CSS pixels, and the two tracks use opposite 34-second linear animations.
  3. Every plate contains an inline SVG with exactly three garment paths, a 3px repeating-linear-gradient texture layer, a centered 0.6-scale hang-tag group, and one readable size label.
  4. At 390x844, document.documentElement.scrollWidth is no greater than window.innerWidth, both counter-scrolling columns remain visible, and the hero switches to the stacked responsive composition.
  5. window.__DEMO__ exists with name margin, ready becomes true after first paint, frames increases inside the rAF loop, effects contains the named effects, and the page has no uncaught browser errors.
  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