---
name: basic-ux-design
description: Design and build websites and web apps that people find clear and good-looking and that AI agents (web agents, browser agents, computer-use agents) can read and operate. Turns the five BASIC UX principles (Beauty, Accessibility, Simplicity, Intuitiveness, Consistency) into build rules for both users, with code patterns for semantic HTML, accessible names, llms.txt, JSON-LD, robots.txt and WebMCP. Use when building, restyling or reviewing any page, site, component or web app; when asked to make something "look good", "modern", "AI native", "agent-ready" or "work with AI agents"; and together with the basic-ux-audit skill, which measures the result.
license: MIT
compatibility: Single-file edition of basic-ux-design for tools that take one Markdown file, such as Figma's agent and Figma Make. Same rules as the folder edition, with its references inlined.
---

# BASIC UX design

BASIC UX (basicux.com, first published in UX Magazine in 2016) is five
principles with questions to ask under each. A site now has two kinds of user.
People look at the screen. AI agents read the accessibility tree, the HTML and
any machine paths the site offers. This skill turns the five principles into
build rules for both, so the page you ship is usable by people and agent-ready
in one build.

People come first. The agent rules never outrank the people rules, and there
is never a separate version for agents: same content, same prices, same labels.
Nearly every agent rule is also an accessibility rule, so doing it right helps
both.

## How it works with basic-ux-audit

1. **Design** with this skill. If a `brand.json` sits beside it, use its
   colours, fonts and notes as the style guide.
2. **Build** the page.
3. **Audit** it with the basic-ux-audit skill. From that skill's folder, run
   `node scripts/assess.mjs <url>`. It checks the page at desktop width and at
   a 390px phone width, scores each principle twice, for people and for
   agents, and lists every failed check.
4. **Fix** each finding with the matching rule below. Every audit finding is
   named by principle, and each principle here has both halves.
5. **Re-run** until both the people and the agent score are 90 or above, and
   the audit's judgement steps (screenshots and `agent-view.yaml`) pass.

## Beauty

People: is it aesthetically pleasing? Agents: is it well formed?

- One concept for the page in a sentence, before any styling. It picks the
  typeface, the accent and the imagery.
- Two typefaces at most, on a modular fluid scale. Body text 16px or larger,
  line height 1.5 to 1.7, 60 to 75 characters a line.
- One spacing scale on a 4 or 8px base. Every gap is a token.
- Mostly neutrals and one accent. Contrast 4.5:1 for text, 3:1 for large
  text and controls.
- One focal point per screen.
- Agents: one `h1`, no skipped heading levels, a unique `title`, a meta
  description, `lang`, a canonical URL and valid JSON-LD.
- Agents: a stable layout (CLS under 0.1), nothing covering a control, and
  visual order that matches source order.
- Depth: the Visual craft section below for craft and the generic AI-design
  tells to avoid; the Agent-ready patterns section below for the markup.

## Accessibility

People: can everyone use it? Agents: can an agent read and use it?

- Keyboard reachable, a visible `:focus-visible` style, alt text on every
  meaningful image, `prefers-reduced-motion` respected.
- Tap targets 24px at least, 44px for primary touch actions.
- Text a reader with ADHD, dyslexia or another learning difference can
  read, at a 6th grade reading level or below. See "Write for every reader".
- Nothing hidden from an automated browser. If content animates in on
  scroll, start it visible when JavaScript is off or `navigator.webdriver` is
  true.
- Real elements for controls: `<button>` for actions, `<a href>` for
  navigation. Never a clickable `div`.
- Every control has a name in the accessibility tree. An icon-only button
  gets an `aria-label`.
- Every field has a `<label>`. A placeholder is not a label.
- The content is in the HTML before JavaScript runs. Render on the server or
  at build time.
- robots.txt lets user-directed agents in (ChatGPT-User, Claude-User,
  Perplexity-User). No CAPTCHA in front of a public task. A sitemap exists.

## Simplicity

People: does it make life easier? Agents: does it take the fewest steps?

- One primary action per view. Few choices on the first screen.
- Cut every sentence that does not carry a fact. No filler copy.
- Short text: about 15 words a sentence or fewer, none over 25. One idea
  per sentence. Paragraphs of 1 to 3 sentences. An intro of 1 or 2.
- Agents: a `/llms.txt`, JSON-LD for the facts on the page, and alternate
  formats (`<link rel="alternate">` to Markdown or JSON) where they exist.
- Agents: WebMCP tools for the page's main tasks where it fits, one job per
  tool, named with a verb. A plain HTML form can become a tool with the
  `toolname` and `tooldescription` attributes. Add `toolautosubmit` only to
  forms that read (search, filter, look up); a form that books, buys, sends
  or contacts someone waits for the person to press submit.
- Agents: keep the accessibility tree short. No duplicated menus or hidden
  off-canvas copies of the page.

## Intuitiveness

People: is it easy to learn? Agents: does it say what it does?

- Familiar patterns: logo top left links home, navigation where people look
  for it, underlined or clearly styled links, buttons that look pressable.
- Link text names the destination. Button text is a verb and an object. No
  "click here", "learn more" or "read more" on their own.
- Text means what it says on the first read. Headings of 8 words or fewer
  that say what is under them. Every term explained the first time.
- Every form has a real submit button.
- After an action, the page says what happened in text that stays
  (`role="status"` or `role="alert"`), not a toast that disappears. The
  region is in the page from the start, empty, so screen readers announce
  the change. Never `display:none` it while it waits.
- Anything that cannot be undone is marked and confirmed.

## Consistency

People: does it match the system? Agents: does it act the same every time?

- One navigation model per menu. A menu either scrolls within the current
  page or opens other pages, never both. A link like `/#book` from another
  page counts as a page link, so on a multi-page site give "Book" its own
  page or put the form where the menu lands.
- Header, footer and page title block in the same place on every page.
- Reuse components and tokens. Forms share labels, validation and messages.
- Agents: people and agents get the same content, prices and labels. An
  `aria-label` contains the words on screen.
- Agents: the same name always leads to the same place. Stable URLs, filters
  and searches reflected in the URL.
- Agents: the API, the JSON-LD and llms.txt say what the screen says.

## Write for every reader

Write as if the reader has ADHD, dyslexia or another learning difference,
reads on a phone, and had the least schooling of anyone who might visit.
Many readers are like this at some point: tired, busy, or reading in a
second language. The basic-ux-audit skill measures the first four rules.

- Reading grade 6 or below.
- About 15 words a sentence on average. None over 25.
- Paragraphs of 1 to 3 sentences. A page or section intro of 1 or 2.
- Headings of 8 words or fewer, saying what is under them.
- One idea per sentence. Put the main fact first.
- Short, common words: use, not utilize; help, not facilitate.
- Literal words. No idioms or figures of speech ("move the needle", "under
  the hood"). Some readers take every word at face value.
- Explain each term the first time, in the same sentence. Then use it the
  same way every time. A new word for the same thing reads as a new thing.
- Digits for numbers a reader compares: 3 steps, 16 weeks, $9 a month.
- Lists for steps and choices, not long paragraphs.
- Describe what the thing does. Do not sell it: no "powerful", "seamless",
  "unlock" or "elevate".

Agents gain too. Short, literal text is easier for a model to read correctly
and costs fewer tokens.

Sources: W3C, "Making Content Usable for People with Cognitive and Learning
Disabilities" (https://www.w3.org/TR/coga-usable/), and WCAG 2.2 success
criteria 3.1.3 Unusual Words, 3.1.4 Abbreviations and 3.1.5 Reading Level.

## When the product has its own agent

For an AI-native app, where an agent acts for the user (books, buys, sends,
edits), add these. The audit cannot measure them, so check each by hand.

- Beauty: the agent's plan and results can be checked at a glance.
- Accessibility: the person can stop it, take over or say no at any moment.
- Simplicity: checking its work is easier than doing it, and it asks only
  when the answer matters.
- Intuitiveness: it shows the plan before it acts, and actions can be undone.
- Consistency: it always asks before anything permanent, paid or private,
  and always says it is an AI.

## Avoid the generated look

These mark a page as default AI output. Details in
the Visual craft section below.

- Em dashes and long arrows in UI copy. Emoji used as icons.
- A centered hero with a badge above the H1 and two buttons side by side.
- Purple-to-blue gradients, gradient-clipped headings, glow blobs.
- Inter or a framework default as the whole type identity; untouched
  shadcn or Tailwind tokens.
- The same card grid, padding and radius on every section; a coloured stripe
  on the left or top of rounded cards; a Lucide icon in a tinted tile on
  every feature card.
- Fade-in-up on every element.
- Copy like "Empower your workflow" or "Seamlessly". Say the specific thing.

## Before calling it done

- Run basic-ux-audit. Both the people and the agent score 90 or above.
- Read `agent-view.yaml` and name the control an agent would use for the
  page's main task. If you cannot, fix that first.
- View the page at 390px (the audit saves `mobile-view.png`). The navigation
  collapses into a real menu and nothing scrolls sideways. The hero is
  reworked, not shrunk: restack or crop its picture for the narrow screen and
  keep one clear headline and one action in the first screen.
- `curl` the URL and check the main content, the title, the description and
  the canonical are in the response.
- Check every visible string: buttons, empty states, errors, success messages.
- Read the page as the reader in "Write for every reader". The audit reports
  the reading grade, long sentences, long paragraphs and long headings.

Every BASIC UX question for both users: https://basicux.com/principles.json

## Agent-ready patterns

The agent half of every BASIC UX principle, as code. Web agents use a page in
three ways: they look at screenshots, they read the browser's accessibility
tree (each element's role, name and state), and they call whatever machine
paths the site offers (APIs, llms.txt, structured data, WebMCP tools). Build
for all three. Almost every pattern here is also an accessibility fix, so
people who use screen readers, keyboards and zoom gain from it too.

Never build a separate version for agents. Same content, same prices, same
labels for both users. An `aria-label` that says something the screen does
not is the failure mode: in one 2026 test an agent clicked Cancel instead of
Save in 25 of 25 runs because the hidden labels were swapped.

### Beauty: well formed and stable

```html
<!doctype html>
<html lang="en">
<head>
  <title>Pricing | Acme</title>                       <!-- unique, under 60 characters -->
  <meta name="description" content="Plans start at $9 a month. ...">  <!-- 50 to 160 -->
  <link rel="canonical" href="https://acme.com/pricing">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <header>...</header>
  <main id="main">
    <h1>Pricing</h1>                                    <!-- exactly one -->
    <h2>Plans</h2> ... <h3>Team</h3>                    <!-- never skip a level -->
  </main>
  <footer>...</footer>
</body>
</html>
```

- Reserve space for anything that loads late: `width` and `height` on every
  `<img>`, `aspect-ratio` on embeds, fixed heights on ad and banner slots.
  Keep cumulative layout shift under 0.1. A control that moves between a
  screenshot and a click is a misclick.
- Nothing invisible on top of a control. Transparent overlays, cookie
  walls that do not block visually, and full-width click catchers all
  intercept an agent's click. Test: `document.elementFromPoint` at the centre
  of each button returns that button.
- Visual order matches source order. Do not reorder with CSS `order` or
  absolute positioning in a way that makes the DOM read differently from the
  screen.
- Unique `id`s. A duplicate id breaks `label for` and `aria-labelledby`.

### Accessibility: an agent can read and operate it

Use the real element. A `div` with a click handler is invisible as a control
in the accessibility tree.

```html
<!-- no -->
<div class="btn" onclick="save()">Save</div>
<a onclick="next()">Next</a>
<button><svg>...</svg></button>

<!-- yes -->
<button type="button" onclick="save()">Save</button>
<a href="/checkout/shipping">Next: shipping</a>
<button type="button" aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
```

Label every field. A placeholder is not a label: it disappears when typing
starts and many agents ignore it.

```html
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required>
```

Put the content in the HTML. Agents that fetch a page without running
JavaScript (most AI search and many assistant fetches) see only the server
response. Render on the server or at build time: Next.js server components,
Astro, Remix loaders, plain HTML. Test: `curl` the page and read it.

Do not hide content from automated browsers. Scroll reveals, lazy text and
"load more" for core content leave an agent's snapshot empty. If you animate
content in, start it visible when JavaScript is off or when
`navigator.webdriver` is true.

Let user-directed agents in. These fetch a page because a person asked their
assistant to. Blocking them blocks that person.

```text
# robots.txt
User-agent: ChatGPT-User
Allow: /

User-agent: Claude-User
Allow: /

User-agent: Perplexity-User
Allow: /

# Training crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot ...) are a
# separate policy choice. Decide it on purpose.

Sitemap: https://acme.com/sitemap.xml
```

No CAPTCHA in front of a public task such as reading a price, searching or
booking. Rate-limit instead, and return a plain 429 with `Retry-After`.

### Simplicity: the fewest steps

Offer a short path beside the page.

`/llms.txt`, a Markdown index for language models (llmstxt.org). Only the H1
is required; the summary and links do the work.

```markdown
# Acme

> Acme makes invoicing software for freelancers. Plans from $9 a month.

## Docs
- [Pricing](https://acme.com/pricing.md): plans, limits and what each includes
- [API reference](https://acme.com/docs/api.md): REST endpoints and auth
```

Structured data, so facts do not have to be scraped:

```html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Acme Team",
  "offers": { "@type": "Offer", "price": "29.00", "priceCurrency": "USD" },
  "dateModified": "2026-10-15"
}
</script>
```

Alternate formats a tool can find from the page:

```html
<link rel="alternate" type="text/markdown" href="/pricing.md">
<link rel="alternate" type="application/json" href="/api/plans">
```

WebMCP tools for the page's main tasks. One tool per job, named with a verb,
described in a sentence. The draft standard exposes the API on
`document.modelContext`; early previews used `navigator.modelContext`, so
check both.

```js
const mc = document.modelContext || navigator.modelContext;
if (mc && mc.registerTool) {
  mc.registerTool({
    name: "search_products",
    description: "Search the catalogue by keyword. Returns name, price and URL for up to 20 products.",
    inputSchema: {
      type: "object",
      properties: { query: { type: "string", description: "What to search for" } },
      required: ["query"]
    },
    annotations: { readOnlyHint: true },
    execute: async ({ query }) => JSON.stringify(await (await fetch(`/api/search?q=${encodeURIComponent(query)}`)).json())
  });
}
```

Or turn an existing form into a tool with no script (the declarative API).
`toolautosubmit` lets the agent submit without the person pressing the
button, so use it only on forms that read: search, filter, look up. Leave it
off any form that books, buys, sends a message or contacts someone.

```html
<form action="/search" toolname="search_products"
      tooldescription="Search the catalogue by keyword" toolautosubmit>
  <label for="q">Search</label>
  <input id="q" name="q" type="search" toolparamdescription="What to search for">
  <button type="submit">Search</button>
</form>
```

If script handles the submit, tell the agent the result instead of only
updating the screen. `agentInvoked` is true when an agent filled the form:

```js
form.addEventListener("submit", (e) => {
  e.preventDefault();
  const done = fetch(form.action, { method: "POST", body: new FormData(form) })
    .then((r) => r.ok ? "Request sent. We reply within one working day." : "The request failed. Try again or call us.");
  done.then((text) => { document.querySelector("#form-status").textContent = text; });
  if (e.agentInvoked && e.respondWith) e.respondWith(done);
});
```

Keep the accessibility tree short. Every decorative wrapper, duplicated mega
menu and hidden off-canvas copy costs an agent tokens on every step.

### Intuitiveness: it says what it does

- Link text names the destination: "Read the pricing FAQ", never "click
  here", "learn more" or "read more" on their own.
- Button text is a verb and an object: "Add to cart", "Send message",
  "Delete invoice 1042".
- Every form has a real submit button.
- After an action, say what happened in text that stays on the page. A
  toast that vanishes in three seconds is gone before an agent's next
  snapshot.

```html
<!-- in the page from the start, empty; script fills it after the action -->
<p id="form-status" role="status" aria-live="polite"></p>
<p id="form-error" role="alert"></p>

<!-- after a submit -->
<p id="form-status" role="status" aria-live="polite">Saved. Invoice 1042 was sent to sam@example.com.</p>
```

Do not hide the empty region with `display:none` or `:empty { display:none }`.
Screen readers only announce changes to a region that was already rendered.

- Mark what cannot be undone. Put a confirmation step in front of delete,
  pay and send, and say so in the label ("Delete permanently"). In WebMCP,
  set `annotations: { consequentialHint: true }` on such tools.

### Consistency: the same every time

- Same name, same destination. Two links called "Pricing" that go to
  different pages confuse an agent the way they confuse a person.
- The accessible name contains the visible words (WCAG 2.5.3). If a button
  shows "Save", its `aria-label` starts with "Save".
- Stable URLs and identifiers. Filters and searches change the URL so a state
  can be linked to and returned to. No session ids in paths.
- The API does what the screen does. A price, stock level or policy is the
  same in the HTML, the JSON-LD, the API and llms.txt. Timestamp anything
  that changes (`dateModified`).

### When the product has its own agent

If the product runs an AI agent for its users (an assistant that books,
buys, sends or edits), the people half adds five requirements:

- Beauty: the agent's plan and its results are laid out so a person can check
  them at a glance.
- Accessibility: a person can stop it, take over or say no at any moment.
- Simplicity: checking its work is less effort than doing the work, and it
  only asks when the answer matters.
- Intuitiveness: it shows what it will do before it acts, and every action
  can be undone or is listed in a plain record.
- Consistency: it always asks before anything that cannot be undone, costs
  money or shares personal data, and it always says it is an AI.

## Visual craft: looking designed, not generated

The people half of Beauty, in depth. Read it when starting any site, page or
component. Two halves: the tells that mark a page as default AI output, and
the craft that reads as considered. Distilled from the 2025 to 2026 "AI slop"
design discourse and award-tier studio practice.

Root cause: an LLM asked for "a modern landing page" returns the average of
its training data, which is saturated with Tailwind UI, shadcn/ui, and v0
output. Avoiding the look means deviating from that average on purpose. The
finish test for any element: can you say what decision put it there? If yes
for all of them, it reads as designed.

### Part 1: the tells to avoid

#### Layout
- Centered hero: eyebrow badge, centered H1, centered subhead, two buttons
  side by side (Get Started / Learn More), mockup floating below. The literal
  default of v0, Bolt, and Lovable. Left-align the hero, give it one dominant
  visual and an asymmetric composition.
- A pill badge floating above the H1 ("Introducing X", "Now with AI", a dot
  or sparkle icon). Fold any announcement into the nav or drop it.
- The universal stack: hero, three feature cards, stat banner, testimonial
  carousel, pricing table, CTA band, fat footer, every section the same
  max-width and vertical padding. The rhythm itself is the tell. Break the
  meter: one full-bleed band, one deliberately short single-line band, one
  off-grid asymmetric block.
- Bento grid on everything, tiles sized to fit content rather than by
  importance. Use bento only when tile size encodes real hierarchy.
- Reflexive "1-2-3" step rows and horizontal stat strips (10k+ users, 99.9%
  uptime, 24/7 support) as filler. Keep only if the numbers are real and
  load-bearing, and fold them into the narrative.

#### Color
- Purple-to-blue or violet gradient hero (Tailwind violet-600 #7C3AED,
  indigo-500 #6366F1). The signature "we used AI" palette.
- "Tailwind blue" (#3B82F6) as the reflexive CTA color. Reads as untouched
  default.
- Gradient fills on background, buttons, and headings all at once; pink-to-
  orange CTA gradients; gradient-clipped heading text (bg-clip-text).
- Default dark mode with a neon accent regardless of brand, usually failing
  AA contrast on body text.
- Purple or blue glow blobs in the corners, colored box-shadows, every shadow
  set to exactly 0.1 opacity.

Instead: one accent derived from the brand or the subject, neutrals carrying
the layout, a gradient only as a rare accent or a real photo, never as the
default fill.

#### Type
- Inter or Roboto as the whole identity, most tellingly on a centered hero
  headline. The Helvetica of the LLM era.
- The LLM font rotation: Geist, Space Grotesk, Manrope, Satoshi, Instrument
  Serif shipped as-is because they are the fashionable free defaults. Geist
  specifically signals "built on v0 or Vercel."
- A single hero word in italic serif on an otherwise all-Inter page. A copied
  Stripe and Vercel gesture now, not an idea.
- All-caps letter-spaced eyebrows on every block.

#### Icons and imagery
- A thin-stroke Lucide glyph centered in a roughly 48px rounded square with a
  soft gradient tint, one per feature card. The universal AI feature-card
  shape.
- Emoji standing in for real icons in lists and nav.
- AI-generated illustration (too smooth, too symmetrical, a plastic quality),
  faceless 3D characters holding glowing orbs, unDraw / Storyset / Saly
  filler, the stock "diverse team around a laptop" scene.
- Fake testimonial avatars and greyed placeholder logo bars.

Instead: real product screenshots, real team photos with real names,
commissioned or single-grid custom icons, graded stock only when nothing
better exists.

#### Spacing, motion, components
- Uniform everything: one border-radius on every element, one padding value,
  one card height, identical roughly 140px band padding top and bottom. Vary
  radius and padding by component role; vary the vertical rhythm.
- Over-rounded corners on literally everything. Commit to a radius language
  (sharp 0, soft 1rem, or full pills) with intent.
- Fade-in-up-on-scroll with a stagger on every element (the Framer Motion
  whileInView default). Keep only motion that teaches: state changes,
  count-ups, meaningful reveals. Cut decorative fades.
- Typewriter hero headline. Skip it unless the typing is genuinely the
  product story.
- The identical three-column feature triplet (small icon, h3, two lines).
  Colored left-border cards (a 3 to 4px accent stripe), as reliable an AI
  tell as em dashes. Glassmorphism cards and backdrop-blur on everything
  (dated and automated at once). "Powered by AI", "Built with love", and
  "Made with [tool]" badges.

#### Framework tells (the source of the sameness)
- The shadcn fingerprint: slate or zinc neutrals, 0.5rem radius everywhere,
  Inter at default sizes, 1px slate-200 (#E2E8F0) card borders, muted-indigo
  primary buttons, DataTable and Dialog styled exactly like the docs. Highest
  leverage fix: edit five tokens and keep the library while losing the look.
  Swap the neutral ramp (try a warm bone), change the radius decisively,
  replace Inter with a real pairing, pick one saturated accent, and ship one
  signature token (grain, a custom shadow stack, an easing curve, a border
  treatment).
- Raw Tailwind palette (blue-500, slate, zinc) and default scale shipped
  unmodified. Override the theme tokens before building anything.
- The v0 and Geist pole: #171717 on #ffffff, Geist, a big bold black hero,
  #6B7280 grey body, a single centered roughly 1200px column, a soft orange-
  to-teal blob. Fine as inspiration, but change the accent, the symmetry, and
  the font or "built with v0" is the first read.

### Part 2: what reads as considered

#### Have a concept first
State the idea in one sentence before designing ("Lando Norris as an
experience you drive through", "infrastructure built by engineers who care
about craft"). The concept picks the typeface, the accent (drawn from the
brand or subject), the motion, and the imagery. Coherence across every layer
is what award-winning actually means, not one flashy feature. Design from the
client's real facts, never lorem ipsum.

#### Typography (most of the "expensive" feeling)
- Pair a characterful display face with a clean text face, or use a
  superfamily (Freight Text with Freight Sans, Questa with Questa Sans) for
  guaranteed harmony. A canonical editorial pairing: Neue Montreal with
  Editorial New (Pangram Pangram).
- Free route: Google Fonts and Fontshare both carry well-made families. Pick
  past the overused defaults listed above, and test the pairing at body size.
- Paid route: real foundries rather than the Google default list: Klim (Domaine,
  Founders Grotesk, Signifier, Tiempos), Grilli Type (GT Sectra, GT America,
  GT Walsheim), Pangram Pangram, Displaay. Study Typewolf and Fonts In Use
  before choosing.
- Modular scale, one ratio (1.25 for dense UI, 1.333 or 1.5 for expressive
  marketing). Make it fluid with clamp() using a vw plus rem term so browser
  zoom still works: clamp(2.5rem, 5vw + 1rem, 6rem). Scale line-height and
  section spacing fluidly too.
- Tighten tracking as size grows (roughly -0.01 to -0.03em on display,
  +0.05em on small caps). Line-height inverse to size (body about 1.5,
  headlines 1.0 to 1.15). Measure 60 to 75ch.
- Build hierarchy with weight and color, not only size (Refactoring UI:
  de-emphasize by lowering contrast, emphasize by raising weight).

#### Color
- Restraint is the signal: mostly neutrals with one accent doing the work.
  Three accents means none.
- Work in OKLCH, not HSL. HSL lightness lies, so accessible steps are
  unreliable in it; OKLCH is perceptually uniform. Never pure #000 on #fff;
  use a dark desaturated ink. Keep neutrals near zero chroma, leaning warm
  (paper, stone) if they lean at all: a ramp tinted toward the brand hue reads
  as a colour cast, not as branding. Put the brand in the accent. 60/30/10
  distribution as a starting discipline.
- Dark mode is a redesign, not an invert: base surface around #121212, step
  elevated surfaces up 2 to 4% lightness each, express elevation through
  lightness because shadows barely read, desaturate and lighten the accents,
  set body text off-white (rgba(255,255,255,0.87)), never pure white.

#### Layout
- Design on a 12-column grid, then break it on purpose: a headline pushed off
  center, an image bleeding past the column, generous negative space on one
  side. The grid is what makes the break read as confident rather than broken.
- Whitespace is structure, always more than feels necessary. Vary the rhythm
  down the page (wide hero, then a two-column split, then an offset image,
  then a tight text column). Give the hero one job.

#### Motion (restraint plus correct physics; Emil Kowalski is the reference)
- Under 300ms for UI (buttons 100 to 160ms, dropdowns 150 to 250ms, modals
  and drawers 200 to 500ms). Never ease-in for entrances; ease-out for enter
  and exit, ease-in-out for on-screen morphs, linear only for constant motion.
- Use strong custom easing, not browser defaults:
  `--ease-out: cubic-bezier(0.23, 1, 0.32, 1);`
  `--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);`
  `--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);`
- Animate transform and opacity only. Enter from scale(0.95) plus opacity 0,
  not scale(0). Prefer CSS transitions (interruptible) over keyframes for UI.
  Stagger 30 to 80ms. Skip animation on high-frequency and keyboard actions.
- Scroll storytelling: tie motion to scroll softly and sparingly (GSAP
  ScrollTrigger with scrub, Lenis for smooth scroll), one hero sequence, and
  let the rest breathe. Reduced-motion strips transform motion but keeps
  opacity so comprehension survives.

#### Art direction and craft
- Real, art-directed imagery graded to one treatment. A light film grain adds
  tactility and hides gradient banding. Let key images bleed past the grid.
- Design every state (hover, active, focus, disabled, loading, empty, error),
  not just the resting one. Press feedback: scale(0.97) on :active.
- Focus states are non-negotiable and should be on-brand: use :focus-visible,
  never a bare outline:none. Layer several shadows for real depth, not one
  flat drop shadow on everything. Get the small type details right: true
  apostrophes and quotes, non-breaking spaces so prices and names never wrap,
  tabular figures in tables.
- Custom cursors only when they carry interaction meaning, and never on touch.

#### The build sequence
1. One-sentence concept. Derive the accent and the type voice from it.
2. Two typefaces (distinctive display plus clean text, or one superfamily);
   modular fluid scale.
3. Neutrals with a temperature plus one accent, in OKLCH; design light and
   dark as separate systems.
4. 12-column grid on an 8px scale, broken asymmetrically with generous air.
5. Motion last: under 300ms, ease-out, transform and opacity, reduced-motion.
6. Art-direct and grade the imagery to one look; add subtle grain.
7. Finish every state, beautiful focus rings, layered shadows, type details.
