/*
catcrafts.net — stylesheet

Plain CSS, one file, no preprocessor. One file rather than several because the
DOM partition cannot add <link> elements (only the SSR'd head can), so extra
files would mean either extra head plumbing or a blocking @import — and a
single file compresses better anyway.

@layer is doing real work here: it makes the cascade predictable without
specificity games, so a components rule always beats a base rule regardless of
selector weight. That is the preprocessor-free way to keep a growing stylesheet
maintainable.

Design direction: this is an open-source systems shop, so the visual language
is terminal-adjacent — near-black surfaces, hairline borders, one accent, a
monospace face for structural text. Deliberately not the purple-gradient
treatment it replaces, which read as the wrong category for the work.
*/

@layer reset, tokens, base, layout, components, utilities, overrides;

/* ─── reset ─────────────────────────────────────────────────────────── */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; padding: 0; }

  img, picture, svg, video, canvas {
    display: block;
    max-width: 100%;
  }

  button, input, select, textarea { font: inherit; color: inherit; }
  ul, ol { list-style: none; }
}

/* ─── tokens ────────────────────────────────────────────────────────── */
@layer tokens {
  :root {
    color-scheme: dark;

    /* Semantic surface names, not "dark-secondary" — so a light theme later
       is a token swap rather than a rename across the file. */
    --surface-0: #0b0d10;
    --surface-1: #12151a;
    --surface-2: #181c23;
    --surface-3: #1f242c;

    --border:        color-mix(in oklab, white 9%, transparent);
    --border-strong: color-mix(in oklab, white 18%, transparent);

    --text:        #e6e8ec;
    --text-muted:  #98a1ae;
    --text-faint:  #6b7482;
    --text-on-accent: #06202b;

    --accent:       #4cc2ff;
    --accent-hover: #7ad4ff;
    --accent-dim:   color-mix(in oklab, #4cc2ff 18%, transparent);

    --ok:     #48d597;
    --warn:   #e8b339;
    --danger: #ff6b6b;

    /* Fluid type scale, 1.2 ratio. clamp() means no font-size media queries. */
    --step--1: clamp(0.84rem, 0.81rem + 0.15vw, 0.92rem);
    --step-0:  clamp(1rem, 0.96rem + 0.2vw, 1.08rem);
    --step-1:  clamp(1.2rem, 1.12rem + 0.4vw, 1.4rem);
    --step-2:  clamp(1.44rem, 1.3rem + 0.7vw, 1.8rem);
    --step-3:  clamp(1.73rem, 1.5rem + 1.15vw, 2.4rem);
    --step-4:  clamp(2.07rem, 1.7rem + 1.9vw, 3.2rem);

    /* Spacing scale — replaces the ad-hoc rem values this file used to have
       scattered through it. */
    --s-3: 0.25rem; --s-2: 0.5rem;  --s-1: 0.75rem; --s0: 1rem;
    --s1:  1.5rem;  --s2:  2rem;    --s3:  3rem;    --s4: 4.5rem;

    --radius:    10px;
    --radius-sm: 6px;
    --radius-pill: 999px;

    --measure:   68ch;
    --container: 68rem;

    --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
                 "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, "JetBrains Mono", "Cascadia Code", Menlo,
                 Consolas, monospace;

    --dur-fast: 110ms;
    --dur:      200ms;
    --ease:     cubic-bezier(0.25, 0.8, 0.25, 1);
  }

  @media (prefers-reduced-motion: reduce) {
    :root { --dur-fast: 1ms; --dur: 1ms; }
  }
}

/* ─── base ──────────────────────────────────────────────────────────── */
@layer base {
  html { -webkit-text-size-adjust: 100%; }

  body {
    font-family: var(--font-sans);
    font-size: var(--step-0);
    line-height: 1.65;
    color: var(--text);
    background: var(--surface-0);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    /* A single very soft radial instead of the old three-stop gradient: it
       reads as depth without competing with the content. */
    background-image: radial-gradient(
      ellipse 80% 50% at 50% -10%,
      color-mix(in oklab, var(--accent) 7%, transparent),
      transparent 70%);
    background-repeat: no-repeat;
  }

  h1, h2, h3 { line-height: 1.2; text-wrap: balance; }
  p { text-wrap: pretty; }

  a { color: var(--accent); text-decoration: none; }
  a:hover { color: var(--accent-hover); text-decoration: underline; }

  code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.92em; }

  code {
    background: var(--surface-2);
    border: 1px solid var(--border);
    padding: 0.1em 0.35em;
    border-radius: var(--radius-sm);
  }

  pre {
    background: var(--surface-0);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--s0);
    overflow-x: auto;
  }

  /* :focus-visible rather than :focus so a mouse click leaves no ring, and
     :where() keeps specificity at zero so components can override without
     !important. There was no focus style at all before this. */
  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
  }

  ::selection { background: var(--accent-dim); }

  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: var(--surface-1); }
  ::-webkit-scrollbar-thumb {
    background: var(--surface-3);
    border-radius: var(--radius-pill);
    border: 2px solid var(--surface-1);
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--border-strong); }
}

/* ─── layout ────────────────────────────────────────────────────────── */
@layer layout {
  #catcrafts-root {
    display: contents;
  }

  #cc-header {
    position: sticky;
    top: 0;
    z-index: 100;
    background: color-mix(in oklab, var(--surface-1) 88%, transparent);
    backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border);
  }

  .nav-container {
    max-width: var(--container);
    margin-inline: auto;
    padding: var(--s-1) var(--s1);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s1);
    flex-wrap: wrap;
  }

  main {
    flex: 1;
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding: var(--s3) var(--s1) var(--s4);
  }

  .section { margin-top: var(--s3); }

  #cc-footer {
    border-top: 1px solid var(--border);
    background: var(--surface-1);
    margin-top: auto;
  }

  .footer-content {
    max-width: var(--container);
    margin-inline: auto;
    padding: var(--s2) var(--s1);
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
  }
}

/* ─── components ────────────────────────────────────────────────────── */
@layer components {

  /* nav */
  .logo {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    font-family: var(--font-mono);
    font-size: var(--step-1);
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.01em;
  }
  .logo:hover { color: var(--text); text-decoration: none; }
  /* The mark is an inline SVG (see RenderNav): the cat face draws in
     currentColor, so this one declaration colours it. */
  .logo__mark { color: var(--accent); display: inline-flex; }
  .logo__mark svg { width: 1.4em; height: 1.4em; }
  /* The cat's mouth is the old ">_" prompt, and its underscore blinks like a
     real terminal cursor: step-end, not a fade — cursors snap. Guarded below
     for prefers-reduced-motion, which the --dur trick can't catch because this
     animation has a literal duration. */
  .logo-cursor { animation: logo-blink 1.2s step-end infinite; }
  @keyframes logo-blink { 0% { opacity: 1; } 50% { opacity: 0; } }
  @media (prefers-reduced-motion: reduce) {
    .logo-cursor { animation: none; }
  }
  /* On a narrow viewport the wordmark is the first thing worth dropping —
     the cat mark still identifies the site and the nav gets the room. */
  @media (max-width: 26rem) {
    .logo__text { display: none; }
  }

  .nav-list { display: flex; gap: var(--s-3); flex-wrap: wrap; }

  .nav-link {
    display: inline-block;
    padding: var(--s-3) var(--s-1);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: var(--step--1);
    font-weight: 500;
    transition: color var(--dur-fast) var(--ease),
                background-color var(--dur-fast) var(--ease);
  }
  .nav-link:hover {
    color: var(--text);
    background: var(--surface-3);
    text-decoration: none;
  }
  .nav-link--active { color: var(--accent); }
  .nav-link--active:hover { color: var(--accent-hover); }

  /* hero — no VISIBLE headline by choice: a slogan-shaped heading is the
     marketing register this site refuses. The mission sentence itself is the
     h1 (document outline, crawlers) but wears body-prose clothes: same size
     step, weight and rhythm as the paragraph that completes it, so the two
     render as one block of text. */
  .hero { max-width: var(--measure); }
  .hero__lede {
    font-size: var(--step-1);
    color: var(--text);
    text-wrap: balance;
  }
  h1.hero__lede {
    font-weight: 400;
    line-height: inherit;
  }
  .hero__actions {
    margin-top: var(--s1);
    display: flex;
    gap: var(--s-1);
    flex-wrap: wrap;
  }

  .section__title {
    font-size: var(--step-2);
    letter-spacing: -0.015em;
    padding-bottom: var(--s-2);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--s1);
  }

  /* buttons */
  .btn {
    display: inline-block;
    padding: var(--s-2) var(--s0);
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-strong);
    background: var(--surface-2);
    color: var(--text);
    font-size: var(--step--1);
    font-weight: 550;
    transition: background-color var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease);
  }
  .btn:hover {
    background: var(--surface-3);
    border-color: var(--accent);
    color: var(--text);
    text-decoration: none;
  }
  .btn--primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
  }
  .btn--primary:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
  }

  .link-more {
    font-size: var(--step--1);
    font-family: var(--font-mono);
  }
  .link-more::after { content: " \2192"; }

  /* featured cards on the home page */
  .mini-grid {
    display: grid;
    gap: var(--s0);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
  }
  .mini-card {
    padding: var(--s0);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color var(--dur-fast) var(--ease);
  }
  .mini-card:hover { border-color: var(--border-strong); }
  .mini-card__title { font-size: var(--step-0); font-family: var(--font-mono); }
  .mini-card__blurb {
    margin-top: var(--s-2);
    font-size: var(--step--1);
    color: var(--text-muted);
    /* Clamp so uneven blurb lengths don't make a ragged grid. */
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* recent-posts list */
  .recent { display: flex; flex-direction: column; }
  .recent__item {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--s0);
    padding: var(--s-2) 0;
    border-bottom: 1px solid var(--border);
  }
  .recent__date {
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
    white-space: nowrap;
  }

  /* page headers */
  .page-header { max-width: var(--measure); margin-bottom: var(--s2); }
  .page-header__title {
    font-size: var(--step-3);
    letter-spacing: -0.02em;
  }
  .page-header__lede {
    margin-top: var(--s-1);
    color: var(--text-muted);
  }

  /* projects — cards, deliberately shallow. The Forgejo page is the canonical
     home for each project, so a card carries a name, one sentence and a
     language chip, and the whole thing points there. */
  .project-grid {
    display: grid;
    gap: var(--s0);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
  }
  .project-card {
    display: flex;
    flex-direction: column;
    padding: var(--s0);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color var(--dur-fast) var(--ease);
  }
  .project-card:hover { border-color: var(--accent); }
  .project-card__title { font-size: var(--step-0); font-family: var(--font-mono); }
  .project-card__blurb {
    margin-top: var(--s-2);
    font-size: var(--step--1);
    color: var(--text-muted);
  }
  .project-card__lang {
    /* Pushed to the card's bottom edge so the chips align across a row even
       when blurb lengths differ. */
    margin-top: auto;
    padding-top: var(--s-1);
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
  }

  /* status badges */
  .badge {
    font-family: var(--font-mono);
    font-size: var(--step--1);
    padding: 0.1em 0.6em;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    color: var(--text-muted);
    white-space: nowrap;
  }
  .badge--active {
    color: var(--ok);
    border-color: color-mix(in oklab, var(--ok) 35%, transparent);
    background: color-mix(in oklab, var(--ok) 10%, transparent);
  }
  .badge--experiment {
    color: var(--warn);
    border-color: color-mix(in oklab, var(--warn) 35%, transparent);
    background: color-mix(in oklab, var(--warn) 10%, transparent);
  }

  /* posts */
  .post-list { display: grid; gap: var(--s0); }

  .post-card {
    padding: var(--s1);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color var(--dur-fast) var(--ease);
  }
  .post-card:hover { border-color: var(--border-strong); }

  .post-card__header { display: grid; gap: var(--s-3); }
  .post-card__title { font-size: var(--step-1); letter-spacing: -0.01em; }
  .post-card__title a { color: var(--text); }
  .post-card__title a:hover { color: var(--accent); text-decoration: none; }

  .post-card__meta {
    margin-top: var(--s-3);
    display: flex;
    gap: var(--s-1);
    align-items: baseline;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
  }
  .post-card__community { color: var(--text-muted); }

  .post-card__excerpt {
    margin-top: var(--s-1);
    color: var(--text-muted);
    max-width: var(--measure);
  }

  .post-card__link {
    margin-top: var(--s-2);
    font-family: var(--font-mono);
    font-size: var(--step--1);
    /* A pasted URL has no spaces to break at and would otherwise force
       horizontal page scroll on a phone. */
    overflow-wrap: anywhere;
  }

  /* Post media — screenshots and screen recordings of the work, mirrored to our
     own origin by tools/fetch-media.sh. */
  .post-media {
    margin-top: var(--s0);
    display: grid;
    gap: var(--s-2);
  }
  /* More than one file gets a two-up grid so a post with four screenshots does
     not become a mile of scrolling. */
  .post-media:has(> :nth-child(2)) {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  }
  .post-media__item {
    width: 100%;
    height: auto;
    /* The intrinsic size can be a 1829px-tall screenshot; cap it so one image
       cannot dominate the page, and letterbox rather than crop — these are
       screenshots where the edges carry information. */
    max-height: 26rem;
    object-fit: contain;
    background: var(--surface-0);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
  }
  video.post-media__item { max-height: 32rem; }

  .post-card__footer {
    margin-top: var(--s0);
    padding-top: var(--s-2);
    border-top: 1px solid var(--border);
    display: flex;
    gap: var(--s0);
    align-items: baseline;
    flex-wrap: wrap;
  }
  .stat {
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
  }
  .stat--link { color: var(--accent); }

  /* ── shop ─────────────────────────────────────────────────────────── */

  .product-grid {
    display: grid;
    gap: var(--s1);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  }

  .product-card {
    display: flex;
    flex-direction: column;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;   /* the media corner-clips to the card radius */
    transition: border-color var(--dur-fast) var(--ease);
  }
  .product-card:hover { border-color: var(--accent); }
  .product-card__media {
    display: block;
    background: var(--surface-2);
  }
  .product-card__img {
    display: block;
    width: 100%;
    max-height: 18rem;
    object-fit: contain;
    padding: var(--s0);
  }
  .product-card__body { padding: var(--s0); }
  .product-card__tagline { margin-top: var(--s-2); color: var(--text-muted); }
  .product-card__status { margin-top: var(--s-1); }

  /* The price block: both the EU-inclusive figure and its derived export twin
     are always in the markup — identical HTML for every visitor, so the pages
     stay cacheable and crawlers see everything. The timezone hint script tags
     <html> with cc-eu or cc-noneu when it is CONFIRMED which region applies,
     and then the other line is simply hidden: a Dutch buyer never reads about
     import charges that cannot apply to them. No JS means neither class, and
     the dual display with the EU line leading is the honest fallback. */
  .price { margin-top: var(--s-1); }

  /* The single headline number, on the card and at the top of the product
     page alike. Its text starts as the euro price and the hint script may
     swap it for a pre-rendered local-currency string — same element either
     way, so one rule covers both. */
  .price__single {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--step-1);
    font-weight: 650;
  }

  /* The supporting lines under the product-page number: always secondary,
     and the one that does not apply disappears once the region is confirmed.
     Without JS both remain — complete and honest. */
  .price__line {
    display: block;
    color: var(--text-faint);
    font-size: var(--step--1);
    max-width: var(--measure);
  }
  .price__amount { font-family: var(--font-mono); }
  .cc-eu .price__line--world { display: none; }
  .cc-noneu .price__line--eu { display: none; }

  .checkout__customs {
    max-width: var(--measure);
    color: var(--text-muted);
    font-size: var(--step--1);
    margin-bottom: var(--s1);
  }

  .price__from {
    color: var(--text-faint);
    font-size: var(--step--1);
    font-weight: 400;
  }

  /* The live total the script fills in once colour, quantity and country are
     known. It is the answer to "what will I actually pay", so it earns the
     accent — the one place on the form where a number shouts. */
  .checkout__total {
    font-family: var(--font-mono);
    font-size: var(--step-0);
    font-weight: 650;
    color: var(--accent);
    padding: var(--s-1) 0;
    border-top: 1px solid var(--border);
  }

  .product__photo {
    display: block;
    max-width: min(100%, 22rem);
    height: auto;
    margin: var(--s1) 0;
    border-radius: var(--radius);
    background: var(--surface-1);
  }

  .product__summary {
    max-width: var(--measure);
    color: var(--text-muted);
    font-size: var(--step-1);
  }

  .checkout__shipnote {
    max-width: var(--measure);
    color: var(--text-muted);
    font-size: var(--step--1);
    margin-bottom: var(--s-1);
  }

  /* Order status page. */
  .order__amount { font-family: var(--font-mono); }
  .order__vat { color: var(--text-faint); font-size: var(--step--1); margin-top: var(--s-2); }
  .order__indicative { color: var(--text-muted); font-size: var(--step--1); max-width: var(--measure); }
  .order__note { color: var(--text-muted); font-size: var(--step--1); max-width: var(--measure); }
  .order__keep {
    margin-top: var(--s2);
    color: var(--text-faint);
    font-size: var(--step--1);
    max-width: var(--measure);
  }

  .section__lede {
    max-width: var(--measure);
    margin-bottom: var(--s0);
    color: var(--text-muted);
  }

  /* The spec table.
     Wrapped in its own scroll container rather than allowed to widen the
     page: a wide table must scroll itself, never make the body scroll
     horizontally. */
  .spec-table {
    width: 100%;
    border-collapse: collapse;
    display: block;
    overflow-x: auto;
  }
  .spec-table th,
  .spec-table td {
    text-align: left;
    padding: var(--s-2) var(--s-1);
    border-bottom: 1px solid var(--border);
    vertical-align: top;
  }
  .spec-table th { color: var(--text-muted); font-weight: 500; white-space: nowrap; }

  /* ── forms ────────────────────────────────────────────────────────── */

  .checkout {
    margin-top: var(--s3);
    padding: var(--s1);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }
  .checkout__lede {
    max-width: var(--measure);
    color: var(--text-muted);
    font-size: var(--step--1);
    margin-bottom: var(--s1);
  }

  .form { display: grid; gap: var(--s1); max-width: 32rem; }

  .field { display: grid; gap: var(--s-3); }
  .field label { font-weight: 550; font-size: var(--step--1); }
  .field__req { color: var(--text-faint); font-weight: 400; }
  .field__opt { color: var(--text-faint); font-weight: 400; }

  .field input,
  .field textarea {
    /* 44px is the minimum comfortable touch target; anything less is a miss on
       a phone, which is most of this audience. */
    min-height: 44px;
    padding: var(--s-2) var(--s-1);
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    font: inherit;
    width: 100%;
  }
  .field textarea { min-height: 5rem; resize: vertical; }
  .field input:focus-visible,
  .field textarea:focus-visible { border-color: var(--accent); }

  /* :user-invalid, not :invalid — never mark a field the visitor hasn't
     touched yet as wrong. */
  .field input:user-invalid { border-color: var(--danger); }

  .field__hint { font-size: var(--step--1); color: var(--text-faint); }
  .field__error { font-size: var(--step--1); color: var(--danger); }
  .field__error:empty { display: none; }

  .notice {
    padding: var(--s-1) var(--s0);
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-strong);
    font-size: var(--step--1);
  }
  .notice--ok {
    color: var(--ok);
    border-color: color-mix(in oklab, var(--ok) 35%, transparent);
    background: color-mix(in oklab, var(--ok) 8%, transparent);
  }
  .notice--error {
    color: var(--danger);
    border-color: color-mix(in oklab, var(--danger) 40%, transparent);
    background: color-mix(in oklab, var(--danger) 10%, transparent);
  }
  /* The one-line person-company link under the hero actions. Muted: it is a
     fact, not a call to action. */
  .hero__byline {
    color: var(--text-muted);
    font-size: var(--step--1);
  }

  /* The coming-soon price list: the launch prices per colour, shown where the
     colour selector will be once orders open. */
  .checkout__colors {
    list-style: none;
    padding: 0;
    display: grid;
    gap: var(--s-2);
  }
  .checkout__colors li {
    padding: var(--s-2) var(--s-1);
    border-bottom: 1px solid var(--border);
  }

  /* The emergency-calling note on the product page. Warn colours rather than
     danger: it flags an unverified path, not a known-broken one. */
  .notice--warn {
    color: var(--warn);
    border-color: color-mix(in oklab, var(--warn) 40%, transparent);
    background: color-mix(in oklab, var(--warn) 10%, transparent);
  }

  /* Honeypot: positioned off-screen rather than display:none, because some bots
     skip hidden inputs entirely and would sail past the trap. Paired with
     aria-hidden and tabindex="-1" in the markup so no real visitor — pointer,
     keyboard or screen reader — can reach it. */
  .honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
  }

  /* ── legal pages ──────────────────────────────────────────────────── */

  /* Constrained to a readable measure. These are the longest prose on the site
     and the only pages someone reads start to finish, so line length matters
     more here than anywhere else. */
  .legal { max-width: var(--measure); }
  .legal__section { margin-top: var(--s2); }
  .legal__heading {
    font-size: var(--step-1);
    letter-spacing: -0.01em;
    padding-bottom: var(--s-3);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--s-1);
  }
  .legal__section p + p { margin-top: var(--s-1); }
  .legal__section p { color: var(--text-muted); }
  .legal__updated {
    margin-top: var(--s-1);
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
  }

  /* ── demos ────────────────────────────────────────────────────────── */

  .demo-grid {
    display: grid;
    gap: var(--s1);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  }
  .demo-card {
    padding: var(--s1);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color var(--dur-fast) var(--ease);
  }
  .demo-card:hover { border-color: var(--border-strong); }
  .demo-card__title { font-size: var(--step-1); letter-spacing: -0.01em; }
  .demo-card__blurb { margin-top: var(--s-2); color: var(--text-muted); }
  .demo-card__meta { margin-top: var(--s-1); }
  .demo-card__tech {
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
  }

  .breadcrumb {
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-faint);
    margin-bottom: var(--s-1);
  }

  .demo { margin-top: var(--s1); }

  /* Host box the Crafter.Graphics render canvas is reparented into. It needs a
     resolved height for getBoundingClientRect() — the canvas is absolutely
     positioned inside it — so the size comes from aspect-ratio, which the
     content file supplies per demo. The dark fill frames it before the first
     traced frame lands. */
  .demo-stage {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;   /* overridden inline per demo */
    border-radius: var(--radius);
    overflow: hidden;
    background: #05060a;
    border: 1px solid var(--border);
  }
  .demo__requires {
    margin-top: var(--s-2);
    font-size: var(--step--1);
    color: var(--text-faint);
    text-align: center;
  }

  .demo-facts {
    margin-top: var(--s2);
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--s-3) var(--s0);
    font-size: var(--step--1);
  }
  .demo-facts dt { color: var(--text-faint); }
  .demo-facts dd { font-family: var(--font-mono); }

  /* Host box the Crafter.Graphics render canvas is reparented into. It needs
     a resolved height for getBoundingClientRect() — the canvas is absolutely
     positioned inside it — so the size comes from aspect-ratio. The dark fill
     frames it before the first traced frame lands. */
  .webgpu-demo-canvas {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius);
    overflow: hidden;
    background: #05060a;
    border: 1px solid var(--border);
  }
  .webgpu-demo-caption {
    margin-top: var(--s-2);
    font-size: var(--step--1);
    color: var(--text-faint);
    text-align: center;
  }

  /* footer */
  .footer-tagline { font-size: var(--step--1); color: var(--text-muted); }
  .footer-links { display: flex; gap: var(--s0); flex-wrap: wrap; }
  .footer-links a { font-size: var(--step--1); font-family: var(--font-mono); }
  .footer-legal { font-size: var(--step--1); color: var(--text-faint); }

  .empty {
    padding: var(--s1);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius);
    color: var(--text-muted);
    text-align: center;
  }
}

/* ─── utilities ─────────────────────────────────────────────────────── */
@layer utilities {
  .text-muted { color: var(--text-muted); }
}

/* ─── overrides ─────────────────────────────────────────────────────── */
@layer overrides {
  /* Backstop for anything added later. Individual entrance animations are
     already opt-in via (prefers-reduced-motion: no-preference). */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }

  @media print {
    body { background: white; color: black; }
    #cc-header, #cc-footer, .hero__actions { display: none; }
  }

  /* Windows high-contrast: colour-based state must also be conveyed
     structurally, or the active nav item and status badges vanish. */
  @media (forced-colors: active) {
    .nav-link--active { text-decoration: underline; }
    .badge { border: 1px solid currentColor; }
  }
}
