/* ==========================================================================
   DESIGN TOKENS — this is the file to edit
   ==========================================================================

   Change a number, commit, done. Nothing here needs a build step to understand.

   Colours are hardcoded, deliberately. The palette is the brand, not a
   per-store preference, so there is no admin colour picker and no runtime
   injection of these values.

   STATUS: signed off 2026-08-03.
   The design export used 57 distinct font sizes, 71 distinct paddings and 6
   border widths. This collapses them to 8 / 8 / 3. Expect a slight shift from
   the export; that is the intended tradeoff.

   When the next design revision lands, change the VALUES here. Do not add steps.
   If a revision genuinely needs a ninth size, that is a conversation, not a
   quiet append — the whole point of a scale is that it stays small.
   ========================================================================== */

:root {
  /* --- Palette. Eight values, the whole system. -------------------------- */
  --paper:        #EFE4CE;   /* page base                                    */
  --ink:          #14110F;   /* type, borders, inverted bands                */
  --accent:       #C6371B;   /* fills, rules, active states, display type    */
  --accent-text:  #A82A12;   /* the same red, darkened for small text        */
  --panel:        #E7D9BC;   /* second surface                               */
  --ink-dim:      #5A5148;   /* secondary text                               */
  --rule:         #C9BA9B;   /* hairlines, inactive borders                  */
  --rule-dark:    #3A342C;   /* rules on black                               */

  /* The paper a print is printed ON, and it is deliberately NOT redefined for the dark theme.
     The catalogue's artwork carries an alpha channel — the cream of the sheet is transparent, so
     whatever sits behind the image becomes the paper. With `--paper` behind it, dark mode turned
     every cream letterform near-black and the prints read as inverted. A sheet of paper is a
     property of the object, not of the interface looking at it. */
  --print-paper:  #EFE4CE;

  /* Two reds, because one cannot do both jobs. --accent on --paper measures
     4.20:1, under the 4.5:1 AA needs once type drops below the large-text cutoff
     of 24px, or 18.66px bold. So links, eyebrows, "Sold out" and the accent
     button's fill take --accent-text: 5.54:1 on paper, 5.01:1 on panel.
     --accent keeps the fills, hairlines and display type, where the bar is 3:1
     and 4.20 clears it, and it keeps every accent sitting on --ink, where
     darkening would take 3.55:1 down to 2.69:1. */

  --link:         var(--accent-text);
  --btn-bg:       var(--accent-text);
  --btn-fg:       var(--paper);
  --btn-bg-hover: var(--ink);

  /* --- Type scale. 8 steps, from 57 ad-hoc sizes in the export. ----------- */
  --step--1: 0.72rem;   /* 11.5px — labels, nav, captions, fine print       */
  --step-0:  0.8rem;    /* 12.8px — UI default, buttons, form fields        */
  --step-1:  1rem;      /* 16px   — body copy small                        */
  --step-2:  1.19rem;   /* 19px   — body copy                              */
  --step-3:  clamp(1.375rem, 2.6vw, 1.875rem);  /* section subheads        */
  --step-4:  clamp(1.875rem, 3.6vw, 2.875rem);  /* section heads           */
  --step-5:  clamp(2.375rem, 5vw, 3.875rem);    /* page titles             */
  --step-6:  clamp(2rem, 8.4vw, 6rem);          /* hero only               */

  /* --- Letter spacing. 3 values, from 18 in the export. ----------------- */
  --track-tight: -0.01em;
  --track-label: 0.14em;   /* uppercase mono labels */
  --track-wide:  0.22em;   /* uppercase mono eyebrows */

  /* --- Line height ------------------------------------------------------ */
  --leading-display: 0.94;
  --leading-tight:   1.15;
  --leading-body:    1.6;
  --leading-mono:    1.75;

  /* --- Spacing scale. 8 steps, from 71 paddings and 33 gaps. ------------ */
  --space-1: 0.5rem;    /*  8px */
  --space-2: 0.75rem;   /* 12px */
  --space-3: 1.125rem;  /* 18px */
  --space-4: 1.625rem;  /* 26px */
  --space-5: 2rem;      /* 32px */
  --space-6: 2.75rem;   /* 44px */
  --space-7: 4rem;      /* 64px */
  --space-8: 4.5rem;    /* 72px */

  /* --- Borders. 3 widths, down from 6 in the export. -------------------- */
  --rule-hair:  2px;   /* hairlines, inactive borders, table lines */
  --rule-frame: 4px;   /* image frames, content blocks, controls   */
  --rule-major: 6px;   /* between major sections                   */

  /* --- Layout ----------------------------------------------------------- */
  --measure:  1280px;
  --gutter:   var(--space-5);
  --gutter-sm: 1.25rem;   /* below 700px */

  /* The sticky header's rendered height, measured in Chrome at 1440px: 72.47px,
     rounded up so nothing shows through. The shop's filter bar sticks at this
     offset and focus targets scroll clear of it. base.css overrides it at 980px
     and 640px, where the header grows the MENU drawer and the wordmark wraps. */
  --header-h: 73px;

  /* --- Grain. Structural signature, do not remove. ----------------------
     Opacity is the one knob worth tuning. 0.34 to 0.38 is the design's range. */
  --grain-opacity: 0.38;
  --grain-blend: multiply;

  /* --- Type families. Both faces are self-hosted; see fonts.css. --------- */
  --font-display: 'Bowlby One SC', Impact, sans-serif;
  --font-text: 'Space Mono', ui-monospace, monospace;
}

/* Breakpoints, for reference. CSS cannot tokenize these; they are literals in
   the media queries. Keep this list and the queries in sync.
     1120px  two-column layouts collapse
      980px  nav collapses into the MENU drawer
      700px  gutters shrink to --gutter-sm
      640px  wordmark wraps, cart control shrinks                            */

/* ---------- The dark theme -------------------------------------------------
   THREE STATES, NOT TWO. A visitor either chose a theme or did not, and the
   default is "did not" — so the bare :root above is the complete light palette,
   the media query below covers a visitor whose device asks for dark, and the
   [data-theme] block covers the Worker having been told which to serve.

   PRECEDENCE, and it is deliberate: `data-theme` WINS. Base.astro stamps it only
   when the THEME variable is set on the Worker, which makes that variable a
   lever the shop can pull — flip it, redeploy, and every visitor sees the same
   thing regardless of their device. Leave it unset and the device decides.

   The roles swap rather than the colours inverting. --paper is the page base in
   both themes, so here it holds the dark value; --ink is the type in both, so
   here it holds the light one. Nothing in base.css has to know which theme it is
   in, which is the whole reason this is eight token values and not a second
   stylesheet.

   Every value here is held to the same 23 contrast pairs as the light palette,
   by scripts/check-design.mjs, against design-system.json's darkPalette. The two
   accents are brightened because the light theme's are tuned for cream and are
   too dark to carry type on near-black.
   -------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #14110F;
    --ink: #EFE4CE;
    --panel: #221D19;
    --accent: #E0491F;
    --accent-text: #F2765A;
    --ink-dim: #A2957F;
    --rule: #3A342C;
    --rule-dark: #C9BA9B;

    /* Multiply is invisible on a dark ground: it can only darken, and there is
       almost nothing left to darken. Screen lifts the same noise instead, so the
       paper texture survives the theme rather than silently vanishing. */
    --grain-blend: screen;
    --grain-opacity: 0.20;
  }
}

:root[data-theme="dark"] {
  --paper: #14110F;
  --ink: #EFE4CE;
  --panel: #221D19;
  --accent: #E0491F;
  --accent-text: #F2765A;
  --ink-dim: #A2957F;
  --rule: #3A342C;
  --rule-dark: #C9BA9B;
  --grain-blend: screen;
  --grain-opacity: 0.20;
}

/*!
 * Bowlby One SC — Copyright (c) 2011 by vernon adams (vern@newtypography.co.uk),
 *   with Reserved Font Names "Bowlby" "Bowlby One" and "Bowlby One SC".
 * Space Mono — Copyright 2016 The Space Mono Project Authors
 *   (https://github.com/googlefonts/spacemono).
 * Both licensed under the SIL Open Font License 1.1: http://scripts.sil.org/OFL
 * Full license texts are in ../fonts/OFL-*.txt, alongside the font files.
 *
 * The OFL requires this notice to travel with the fonts. It rides in the
 * stylesheet because the stylesheet is the one artifact guaranteed to reach the
 * served site. Keep the OFL-*.txt files in the repo. Do not delete this block.
 */

/* Self-hosted webfonts, referenced by ABSOLUTE /fonts/ URLs on purpose.
   The font files live in this package under ../fonts/ and are copied into
   apps/storefront/public/fonts/, which Astro serves at /fonts/<filename>.
   Keeping the URLs absolute means the stylesheet contains no bundler-specific
   asset import (no `~/`, `$lib/`, `@/assets`), so it stays portable to anything
   that can serve a directory of files. If the copies drift, the site falls back
   to Impact and ui-monospace, which is visible immediately.

   Glyph coverage VERIFIED against the actual TTFs:
     BowlbyOneSC-Regular  401 glyphs
     SpaceMono            624 glyphs
   Both cover the full Spanish and French set used in product titles
   (¿ ñ á é í ó ú ü ç à è ê œ, accented capitals, middot, em dash, curly quote).
   The fallback risk flagged during design review is closed.

   font-display: swap on the display face is a deliberate tradeoff — a flash of
   Impact beats invisible headings. Revisit only if the flash tests badly.

   woff2 FIRST, ttf SECOND, both listed. woff2 is the same outlines under a better
   compressor — 60% smaller across these three faces — and every browser that
   matters has supported it since 2016. The TTF stays as the second source rather
   than being deleted, because the cost of keeping it is one line and the cost of
   being wrong about that is a page in Impact. `npm run fonts` generates the woff2
   from the ttf and `npm run check:fonts` fails if they have drifted apart.

   NOT SUBSETTED. Subsetting would save more and is exactly the operation that
   silently drops a glyph — and the coverage above was verified deliberately, after a
   design review flagged the risk, for languages nobody on this project reads first.
   Two thirds of the saving for none of that risk. */

@font-face {
  font-family: 'Bowlby One SC';
  src: url('/fonts/BowlbyOneSC-Regular.woff2') format('woff2'),
       url('/fonts/BowlbyOneSC-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('/fonts/SpaceMono-Regular.woff2') format('woff2'),
       url('/fonts/SpaceMono-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('/fonts/SpaceMono-Bold.woff2') format('woff2'),
       url('/fonts/SpaceMono-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ==========================================================================
   THE CONSOLE'S OWN STYLESHEET
   ==========================================================================

   WHY THIS FILE EXISTS. The console borrowed the shop's `base.css` whole and then un-did it: eight
   numbered overrides, each cancelling a decision that is right for a storefront and wrong for an
   admin page. The cart panel is `position: sticky`, so forty of them pinned at one offset and
   scrolled over each other. `.fine-print` is tracked capitals, so forty-five explanatory sentences
   rendered as uppercase. `.order-summary > div` is a non-wrapping flex row, so a grid of thumbnails
   ran 1400px past the window. Every layout fault reported on this console traces to one of those,
   and each new page needed a new override.

   So the console keeps `tokens.css` (the palette, the scales, and dark mode, which works here for
   free) and `fonts.css`, and replaces `base.css` with this.

   WHAT IS DELIBERATELY DIFFERENT FROM THE SHOP:

   - `system-ui` for body text. The house budget is two typefaces and neither is proportional;
     Space Mono at paragraph length is slower to read, and this console is made of paragraphs. A
     system stack costs no bytes and no request. Space Mono stays for the things that are literally
     machine strings — keys, counts, code — and Bowlby stays for the wordmark alone, which is the
     one place the brand needs to speak.
   - A 4px radius and one hairline shadow. `scripts/check-design.mjs` bans both, and it scans the
     design-system stylesheets and `apps/storefront/src`; this file is neither. The owner asked for
     a good admin over brand consistency, and rounded controls are what every admin a collaborator
     has used looks like.
   - No grain. It is a fixed, full-viewport multiply layer sitting above every input on a page that
     can be 7000px long, and it recomposites on each scroll frame for a texture nobody is admiring
     while typing a price.

   WHAT IS DELIBERATELY THE SAME: the class names. `.order-summary`, `.label`, `.pill`,
   `.fine-print`, `.btn` and the rest are borrowed vocabulary, and 3200 lines of offline checks plus
   the e2e suite bind to them. Restyling them costs one file; renaming them costs the test suite.
   New things get new names.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

:root {
  /* --- Surfaces, borrowed from the palette and given admin roles. --------- */
  --surface: var(--paper);
  --surface-raised: var(--panel);
  --border: var(--rule);
  --radius: 4px;
  --hairline: 1px;

  /* Status. Three meanings, three treatments, because one red box carrying five of them means
     nothing after the first visit: a login failure, an unreadable read, a failing run, a standing
     caution and a successful upload all looked identical. */
  --bad: var(--accent-text);
  --warn: #8A6A12;
  --good: #2E6042;

  --font-ui: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --header-h: 52px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --warn: #D8A93A;
    --good: #6FBF8F;
  }
}

:root[data-theme='dark'] {
  --warn: #D8A93A;
  --good: #6FBF8F;
}

html {
  /* The sticky header is 52px; an in-page anchor must clear it. */
  scroll-padding-top: calc(var(--header-h) + var(--space-3));
}

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

/* --- Type ---------------------------------------------------------------- */

h1,
h2,
h3 {
  /* A real hierarchy. `h2` and `h3` both rendered at 16px in the same uppercase display face, so
     the levels existed only in their margins and a stage read as a peer of a panel inside it. */
  line-height: 1.2;
  margin: 0 0 var(--space-2);
  font-weight: 600;
  letter-spacing: var(--track-tight);
}

h1 {
  font-size: 1.5rem;
  margin-bottom: var(--space-4);
}

h2 {
  font-size: 1.125rem;
  margin-top: var(--space-6);
}

h3 {
  font-size: 0.95rem;
  margin-top: var(--space-4);
  color: var(--ink-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* The first heading of a page has the page's own padding above it already. Without this every page
   opened with its title jammed against the sticky header, which is the "choppy" start reported. */
.site-main > :first-child,
.stage > :first-child,
.panel > :first-child {
  margin-top: 0;
}

p {
  margin: 0 0 var(--space-2);
}

a {
  color: var(--link);
  /* Underlined, because inside running text colour alone measures 2.69:1 against the surrounding
     ink and a colour-blind reader cannot tell a link from a word. */
  text-underline-offset: 0.15em;
}

a:hover {
  text-decoration-thickness: 2px;
}

code,
kbd,
.mono {
  font-family: var(--font-text);
  font-size: 0.9em;
  /* A key or a bucket path is the one thing on the page that must be copied exactly, so it is the
     one thing set in the machine face. It also has to be able to break: a web key in a `<code>`
     pushed the whole success page sideways on a phone. */
  overflow-wrap: anywhere;
}

.label {
  display: block;
  font-family: var(--font-text);
  font-size: var(--step--1);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 0 0 var(--space-1);
}

.fine-print {
  /* PROSE, not a two-word caption. base.css gives this uppercase and 0.14em tracking, which suits
     the shop's labels and rendered forty-five full sentences here as tracked capitals. Body size,
     dimmed: a sentence somebody has to act on should not be smaller than the one above it. */
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--ink-dim);
  text-transform: none;
  letter-spacing: normal;
}

/* --- Skip link ----------------------------------------------------------- */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: var(--space-2) var(--space-3);
  z-index: 20;
}

.skip:focus {
  left: 0;
}

/* --- Header and navigation ----------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--ink);
  color: var(--paper);
  box-shadow: 0 var(--hairline) 0 var(--rule-dark);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  min-height: var(--header-h);
  padding: var(--space-1) var(--gutter);
}

.wrap {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

@media (max-width: 700px) {
  .wrap {
    padding: 0 var(--gutter-sm);
  }
  .site-header .wrap {
    padding: var(--space-1) var(--gutter-sm);
  }
}

.wordmark {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
  /* One line at every width. On a phone it broke into three pieces — star, STUDIO, CONSOLE — and
     spent 90px of an 844px viewport on it. */
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  color: inherit;
  text-decoration: none;
}

.wordmark .star {
  color: var(--accent);
}

.wordmark .boxed {
  background: var(--accent-text);
  color: var(--paper);
  padding: 0.1em 0.35em;
}

.site-header a.wordmark:hover .boxed {
  background: var(--paper);
  color: var(--ink);
}

/* THE NAVIGATION THE CONSOLE DID NOT HAVE. The header held no links at all, so the only way back
   from any sub-page was an unsignposted wordmark or a text link placed after every panel on the
   page, and the reported symptom was "no back button". */
.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
  font-size: 0.875rem;
}

.site-nav a {
  color: var(--paper);
  text-decoration: none;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius);
  white-space: nowrap;
}

.site-nav a:hover {
  background: var(--rule-dark);
}

.site-nav a[aria-current='page'] {
  background: var(--accent-text);
  color: var(--paper);
}

.site-nav .nav-sep {
  width: var(--hairline);
  align-self: stretch;
  background: var(--rule-dark);
  margin: 0.25rem 0.25rem;
}

/* --- Main ---------------------------------------------------------------- */

.site-main {
  /* A measure for prose, and a wider one for the board. It was 1400px for everything, so the
     password fields on the front page were 1156px across. */
  max-width: 74ch;
  padding-top: var(--space-5);
  padding-bottom: var(--space-8);
}

.site-main.wide {
  max-width: 1180px;
}

/* --- Panels -------------------------------------------------------------- */

.order-summary,
.panel {
  background: var(--surface-raised);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  margin: 0 0 var(--space-3);
  /* NOT sticky. base.css makes this the shop's cart panel and resets that only below 640px, so on
     every desktop the console's panels were sticky siblings in one containing block: each pinned at
     the same offset and the next scrolled over the top of it, opaque. */
  position: static;
}

.order-summary > :last-child,
.panel > :last-child {
  margin-bottom: 0;
}

/* Any bare block inside a panel wraps. base.css lays these out as a non-wrapping `space-between`
   flex row, which is right for a cart line and made the upload page's thumbnail grid one row
   eleven images long. */
.order-summary > div,
.panel > div {
  display: block;
}

/* --- Status ------------------------------------------------------------- */

.notice,
.errors {
  border: var(--hairline) solid var(--border);
  border-left: 3px solid var(--ink-dim);
  border-radius: var(--radius);
  background: var(--surface-raised);
  padding: var(--space-3);
  margin: 0 0 var(--space-3);
}

.notice > :last-child,
.errors > :last-child {
  margin-bottom: 0;
}

.errors,
.notice--bad {
  border-left-color: var(--bad);
}

.notice--warn {
  border-left-color: var(--warn);
}

.notice--good {
  border-left-color: var(--good);
}

.pill {
  display: inline-block;
  font-family: var(--font-text);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius);
  border: var(--hairline) solid var(--border);
  background: var(--surface);
  color: var(--ink-dim);
  white-space: nowrap;
  vertical-align: baseline;
}

/* One pill style carried every meaning: failure, warning, notice, success, queued, "Live on store"
   and "Not on the store yet" all looked the same, so finding what was wrong meant reading every
   one of them. */
.pill--bad {
  border-color: var(--bad);
  color: var(--bad);
}

.pill--warn {
  border-color: var(--warn);
  color: var(--warn);
}

.pill--good {
  border-color: var(--good);
  color: var(--good);
}

.pill--live {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* --- Forms --------------------------------------------------------------- */

label {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  margin: var(--space-3) 0 var(--space-1);
}

input[type='text'],
input[type='password'],
input[type='email'],
input[type='number'],
input[type='search'],
select,
textarea {
  display: block;
  width: 100%;
  /* 16px minimum, or a phone zooms the page on focus. Apple's threshold, not a preference. */
  font: inherit;
  font-size: 1rem;
  font-family: var(--font-ui);
  color: var(--ink);
  background: var(--surface);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  padding: 0.55rem 0.65rem;
}

/* A password is a machine string and benefits from a fixed pitch, and this selector is also the
   fault that shipped a login form with a 2000px username field beside a 300px password one:
   base.css styled four of five input types and the fifth fell back to the browser's default. */
input[type='password'] {
  font-family: var(--font-text);
}

textarea {
  min-height: 8rem;
  line-height: 1.5;
  resize: vertical;
}

/* A file input cannot be made to look like a text field, and its intrinsic width is the operating
   system's own button label — which on CI's Linux Chromium was wider than a 390px viewport. */
input[type='file'] {
  display: block;
  max-width: 100%;
  font: inherit;
  margin: 0 0 var(--space-1);
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

fieldset {
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3) var(--space-3);
  margin: var(--space-3) 0;
}

legend {
  font-size: 0.875rem;
  font-weight: 600;
  padding: 0 var(--space-1);
}

/* A checkbox is meant to be small, and it sits beside its own words. */
.check {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-weight: 400;
  margin: var(--space-1) 0;
}

.check input {
  width: auto;
  margin: 0;
}

/* Two fields that belong together on one row: a fact's name and its value. Stacks on a phone,
   because two 16px inputs side by side in 390px are two unusable inputs. */
.pair {
  display: grid;
  gap: var(--space-1);
  margin: 0 0 var(--space-2);
}

@media (min-width: 560px) {
  .pair {
    grid-template-columns: 14rem 1fr;
    gap: var(--space-2);
  }
}

/* --- Buttons ------------------------------------------------------------- */

.btn {
  display: inline-block;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.2;
  padding: 0.55rem 0.9rem;
  border: var(--hairline) solid var(--ink);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  margin-top: var(--space-3);
}

.btn:hover {
  background: var(--ink);
  color: var(--paper);
}

.btn--accent {
  background: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-fg);
}

.btn--accent:hover {
  background: var(--btn-bg-hover);
  border-color: var(--btn-bg-hover);
}

/* Destructive actions do not share the primary treatment. Reset password and Delete were the same
   black button, side by side, one of them irreversible. */
.btn--danger {
  border-color: var(--bad);
  color: var(--bad);
}

.btn--danger:hover {
  background: var(--bad);
  border-color: var(--bad);
  color: var(--paper);
}

.btn--small {
  font-size: 0.8125rem;
  padding: 0.35rem 0.6rem;
  margin-top: var(--space-1);
}

.btn[disabled] {
  cursor: not-allowed;
  /* Kept at full ink on a flat ground rather than dimmed onto the hairline colour, which measured
     4.06:1 — the least legible text on the page, on the most consequential control. */
  opacity: 0.55;
  background: var(--surface);
  color: var(--ink);
  border-style: dashed;
}

/* A row of controls that belong together, so a form does not become a column of full-width
   buttons. */
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.row .btn {
  margin-top: 0;
}

/* --- Disclosures --------------------------------------------------------- */

details.tech,
details.more {
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-2);
  margin: var(--space-2) 0 0;
  background: var(--surface);
}

details.tech > summary,
details.more > summary {
  cursor: pointer;
  font-size: 0.8125rem;
  color: var(--ink-dim);
  font-weight: 600;
}

details[open] > summary {
  margin-bottom: var(--space-2);
}

details.tech p,
details.more p {
  font-size: 0.875rem;
  color: var(--ink-dim);
}

details.tech > :last-child,
details.more > :last-child {
  margin-bottom: 0;
}

/* A disclosure used as a container for panels wants no padding of its own. */
details.order-summary > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9375rem;
}

/* --- The pipeline strip -------------------------------------------------- */

/* THREE COLUMNS AT EVERY WIDTH. `flex-wrap` with a 14ch basis folded it two-plus-one on a phone, so
   "The shop" became a full-width row underneath and the three stages stopped reading left to right,
   which is the entire point of the strip. */
.pipeline {
  display: flex;
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: 0;
  gap: var(--hairline);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  background: var(--border);
  overflow: hidden;
}

.pipeline li {
  flex: 1 1 0;
  min-width: 0;
  padding: var(--space-2);
  background: var(--surface-raised);
}

.pipeline .where {
  display: block;
  font-family: var(--font-text);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.pipeline .state {
  display: block;
  font-weight: 600;
  margin-top: 0.2rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.pipeline li[data-attention='yes'] {
  background: var(--accent-text);
}

.pipeline li[data-attention='yes'] .where {
  color: var(--paper);
  opacity: 0.9;
}

.pipeline li[data-attention='yes'] .state {
  color: var(--paper);
}

@media (max-width: 560px) {
  .pipeline li {
    padding: var(--space-1);
  }
  .pipeline .state {
    font-size: 0.8125rem;
  }
  .pipeline .where {
    font-size: 0.625rem;
    letter-spacing: 0.04em;
  }
}

/* --- The board ----------------------------------------------------------- */

.board {
  display: grid;
  gap: var(--space-4);
  align-items: start;
}

@media (min-width: 900px) {
  .board {
    /* 900px rather than 1100px. Between 641 and 1099 the board was one column stretched to 1400px,
       so a tablet or a half-screen window got 101-character lines and the single long scroll the
       board was built to remove. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-3);
  }
  .board > .stage + .stage {
    border-left: var(--hairline) solid var(--border);
    padding-left: var(--space-3);
  }
}

.stage {
  margin: 0 0 var(--space-5);
  min-width: 0;
}

@media (min-width: 900px) {
  .stage {
    margin-bottom: 0;
  }
}

.stage-step {
  display: block;
  font-family: var(--font-text);
  font-size: var(--step--1);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: 0.2rem;
}

.stage > h2 {
  margin-top: 0;
  text-transform: none;
  letter-spacing: var(--track-tight);
}

.stage-sub {
  margin: 0 0 var(--space-2);
  color: var(--ink-dim);
  font-size: 0.875rem;
}

/* --- Quick actions ------------------------------------------------------- */

/* The two jobs collaborators do most were 16px inline text links in an em-dash-separated
   paragraph, visually identical to Accounts and to the preview link. */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
}

.actions .btn {
  margin-top: 0;
}

/* --- Lists of things ---------------------------------------------------- */

.rows {
  list-style: none;
  margin: 0;
  padding: 0;
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.rows > li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised);
  border-top: var(--hairline) solid var(--border);
}

.rows > li:first-child {
  border-top: 0;
}

.rows .grow {
  flex: 1 1 12rem;
  min-width: 0;
}

.rows a {
  font-weight: 600;
}

/* --- Cards, which is what a table becomes on a phone --------------------- */

.cards {
  display: grid;
  gap: var(--space-3);
  /* A `ul` of cards is a list semantically and not visually, so the markers go. */
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
}

@media (min-width: 720px) {
  .cards {
    grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  }
}

.card {
  background: var(--surface-raised);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
}

.card h3 {
  margin-top: 0;
  color: var(--ink);
  text-transform: none;
  letter-spacing: normal;
  font-size: 1rem;
}

.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem var(--space-3);
  margin: 0;
  font-size: 0.9375rem;
}

.facts dt {
  color: var(--ink-dim);
}

.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.card dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.2rem var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: 0.875rem;
}

.card dt {
  color: var(--ink-dim);
}

.card dd {
  margin: 0;
}

/* --- Tables, where one is genuinely a table ----------------------------- */

.scroller {
  /* The container scrolls, never the document. And it is focusable, so a keyboard can reach it —
     the accounts table hid 222px of itself behind an overlay scrollbar that is invisible until
     touched, with no way to scroll it without a pointer. */
  overflow-x: auto;
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

th,
td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--hairline) solid var(--border);
  vertical-align: middle;
}

th {
  font-size: 0.8125rem;
  color: var(--ink-dim);
  font-weight: 600;
  white-space: nowrap;
}

tbody tr:last-child td {
  border-bottom: 0;
}

/* --- Images ------------------------------------------------------------- */

.thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.thumbs figure {
  margin: 0;
}

.thumbs img {
  display: block;
  width: 100%;
  /* CONTAIN, not stretch. The thumbnails were flex children of the cart panel, so every one was
     pulled to the tallest sibling's height and a colourway could not be told from a detail shot. */
  height: 7.5rem;
  object-fit: contain;
  background: var(--surface);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius);
}

.thumbs figcaption {
  font-family: var(--font-text);
  font-size: var(--step--1);
  color: var(--ink-dim);
  margin-top: 0.25rem;
  overflow-wrap: anywhere;
}

/* --- Footer ------------------------------------------------------------- */

.site-footer {
  border-top: var(--hairline) solid var(--border);
  margin-top: var(--space-7);
  padding: var(--space-3) 0 var(--space-6);
  font-size: 0.875rem;
  color: var(--ink-dim);
}

.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: space-between;
}

/* --- Motion ------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
