@font-face{font-family:'Archivo';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/archivo-400-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Archivo';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/archivo-400.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Archivo';font-style:normal;font-weight:500;font-display:swap;src:url(../fonts/archivo-500-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Archivo';font-style:normal;font-weight:500;font-display:swap;src:url(../fonts/archivo-500.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/ibm-plex-mono-400-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/ibm-plex-mono-400.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(../fonts/ibm-plex-mono-500-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(../fonts/ibm-plex-mono-500.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

/* The Archive tokens: docs/redesign/contracts.md section 3, and DESIGN.md. Every part reads
   these; nothing restates a value. One breakpoint, 700px. */

:root {
  --paper: #FAFAFA;
  --ink: #111111;
  --ink-2: #555555;
  /* Replaces the mock's #888, which measures 3.40:1 on --paper and fails AA at 12px. */
  --ink-3: #737373;
  --placeholder: #EEEEEE;
  --veil: rgba(250, 250, 250, 0.82);
  --hairline: 1px solid #111111;
  --focus: 2px solid #111111;
  --font-sans: 'Archivo', system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;
  --track: 0.04em;
  --t-10: 10px;
  --t-11: 11px;
  --t-12: 12px;
  --t-13: 13px;
  --t-14: 14px;
  --t-16: 16px;
  --sp-2: 2px;
  --sp-4: 4px;
  --sp-6: 6px;
  --sp-10: 10px;
  --sp-12: 12px;
  --sp-14: 14px;
  --sp-16: 16px;
  --sp-18: 18px;
  --sp-20: 20px;
  --sp-22: 22px;
  --sp-28: 28px;
  --gutter: 16px;
  --head-h: 48px;
  --filter-h: 42px;
  --dur-state: 200ms;
}
@media (min-width: 700px) {
  :root {
    --gutter: 20px;
    --head-h: 52px;
    --filter-h: 44px;
  }
}
*, *::before, *::after { box-sizing: border-box; }

html { background: var(--paper); -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--t-14)/1.35 var(--font-sans);
}
@media (max-width: 699.98px) {
  body { font-size: var(--t-13); line-height: 1.3; }
}

img, picture { display: block; max-width: 100%; }
img { height: auto; }

a { color: inherit; text-decoration: none; }
a:hover { opacity: 0.7; }
.read a { text-decoration: underline; text-underline-offset: 3px; }

/* Reading text: the 16px floor, so iOS does not zoom into a form and prose stays readable. */
.read { font-size: var(--t-16); line-height: 1.55; max-width: 68ch; }

input, select, textarea, button { font: inherit; }
input, select, textarea { font-size: var(--t-16); }

.mono { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: var(--track); }

:focus-visible { outline: var(--focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* In the accessibility tree, out of the visual one. Used for the lookbook's list
   heading, which the lead paragraph beneath it already says in words. Its size is a declared
   step too, so an invisible heading does not render at the browser's own 2em. */
.sr-only {
  position: absolute;
  font-size: var(--t-12);
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* ---------- header and menu ----------

   One header on every site document. The base rule is the phone's; from 700px the header is
   a three-column grid with the pages centred. Type is mono, uppercase and tracked, as every
   label is; the wordmark alone keeps its own case. */

.site-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-16);
  height: var(--head-h);
  padding: 0 var(--gutter);
  border-bottom: var(--hairline);
  font: 500 var(--t-11)/1 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--track);
}

.wordmark { margin-right: auto; text-transform: none; }

.nav { display: flex; gap: var(--sp-16); }

.nav-wide { display: none; }

/* The underline is text-decoration, so the link's box is no taller than the words beside it
   and align-items: center leaves every label on one baseline. */
.site-head .nav [aria-current='page'] {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* A native <details>: it opens with scripts off and reports its own state. The panel is
   positioned against the header, so opening it moves nothing on the page. */
.menu > summary {
  list-style: none;
  cursor: pointer;
}
.menu > summary::-webkit-details-marker { display: none; }

.menu-panel {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  min-width: 208px;
  margin: 0;
  padding: var(--sp-6) 0;
  list-style: none;
  background: var(--paper);
  border: var(--hairline);
}
.menu-panel a {
  display: block;
  padding: var(--sp-12) var(--gutter);
}

@media (min-width: 700px) {
  .site-head {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    height: var(--head-h);
    padding: 0 var(--gutter);
    border-bottom: var(--hairline);
    font-size: var(--t-12);
  }
  .wordmark { margin-right: 0; justify-self: start; }
  .nav { gap: var(--sp-28); }
  .nav-wide { display: inline; }
  .head-dm { justify-self: end; }
  .nav-narrow { display: none; }
}
/* ---------- the shop, option 1a ----------

   Below 700px the page is the phone layout; from 700px it is the desktop one. Every gap is a
   --sp-* token, --gutter, 0 or a 1px hairline (docs/redesign/contracts.md section 3). */

/* Eight tiles, four to a row: her four pieces and a second view of each. */
.shop { margin: 0; }

.pieces {
  display: grid;
  /* minmax(0, 1fr), not 1fr: a photograph's intrinsic width would otherwise size the columns. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.piece { margin: 0; min-width: 0; }
.piece-alt { display: none; }

.tile {
  display: block;
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--placeholder);
  border-right: var(--hairline);
  border-bottom: var(--hairline);
}
.tile:hover { opacity: 1; }
.tile:focus-visible { outline-offset: -2px; }
.tile picture { display: block; width: 100%; height: 100%; }
.tile img { width: 100%; height: 100%; object-fit: cover; }

/* The name and the price, over the photograph. Hidden until the pointer or the keyboard is
   on the tile; the link's accessible name carries the same words for everyone else. */
.tile-veil {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--sp-16);
  background: var(--veil);
  opacity: 0;
  transition: opacity var(--dur-state) ease;
}
@media (hover: hover) {
  .tile:hover .tile-veil { opacity: 1; }
}
.tile:focus-visible .tile-veil { opacity: 1; }

.tile-name {
  font: 500 var(--t-13)/1.35 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--track);
}
.tile-price { font: 400 var(--t-13)/1.35 var(--font-mono); }
.tile-cta {
  margin-top: var(--sp-10);
  font: 400 var(--t-11)/1.35 var(--font-mono);
  text-decoration: underline;
}

/* One line under the tiles, and the same shape again as the row into the lookbook. */
.brand-row, .lookbook-row {
  display: flex;
  justify-content: space-between;
  margin: 0;
  padding: var(--sp-12) var(--gutter);
  border-bottom: var(--hairline);
  font: 400 var(--t-10)/1.35 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--track);
}
.brand-city { display: none; }

/* Four steps, and her yarn in the second. */
.howto { margin: 0; padding: 0; list-style: none; }

.howto-step {
  display: flex;
  gap: var(--sp-14);
  padding: var(--sp-12) var(--gutter);
  border-bottom: var(--hairline);
}
.howto-n { font: 400 var(--t-10)/1.3 var(--font-mono); }
.howto-text { font: 400 var(--t-13)/1.3 var(--font-sans); }

/* Step 02 stacks its label row above its cones. */
.howto-step#colours { flex-wrap: wrap; row-gap: var(--sp-10); }
.howto-cones {
  flex: 1 1 100%;
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.howto-cones li { margin: 0; min-width: 0; }

/* Three photographs into the lookbook; a phone shows the first. */
.teaser {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
  border-bottom: var(--hairline);
}
.teaser li { margin: 0; min-width: 0; aspect-ratio: 3 / 4; background: var(--placeholder); }
.teaser li:not(:first-child) { display: none; }
.teaser a, .teaser picture { display: block; width: 100%; height: 100%; }
.teaser img { width: 100%; height: 100%; object-fit: cover; }

.lookbook-row { font-size: var(--t-10); }

@media (min-width: 700px) {
  .pieces { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .piece-alt { display: block; }

  .brand-row, .lookbook-row {
    padding: var(--sp-14) var(--gutter);
    font-size: var(--t-12);
  }
  .brand-city { display: inline; }

  .howto { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-bottom: var(--hairline); }
  .howto-step {
    flex-direction: column;
    gap: var(--sp-12);
    padding: var(--sp-18) var(--sp-20) var(--sp-28);
    border-bottom: 0;
    border-right: var(--hairline);
  }
  .howto-step:last-child { border-right: 0; }
  .howto-n { font-size: var(--t-11); }
  .howto-text { font-size: var(--t-14); line-height: 1.35; }
  .howto-step#colours { flex-wrap: nowrap; row-gap: var(--sp-12); }
  .howto-cones { flex: 0 0 auto; grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: var(--sp-6); }

  .teaser { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .teaser li:not(:first-child) { display: block; }
  .teaser li:not(:last-child) { border-right: var(--hairline); }
}
/* A cone swatch (tools/site/cone.mjs): the derivative is already masked to a truncated cone
   on a transparent ground, so the box is square-cornered and the picture is contained. */
.swatch img { width: 100%; height: auto; aspect-ratio: 3 / 4; object-fit: contain; object-position: bottom; border-radius: 0; }
/* ---------- the lookbook, option 1c: a filter bar and a dense grid of look tiles ---------- */

.filter-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: var(--filter-h);
  padding: 0 var(--gutter);
  border-bottom: var(--hairline);
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: var(--track);
}

.filters {
  display: flex;
  gap: var(--sp-18);
  min-width: 0;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
  /* A row that overflows fades out at its right edge rather than being cut mid-letter. The
     padding is the fade's own width, so the last label is clear of it once scrolled to the end. */
  padding-right: var(--sp-16);
  -webkit-mask-image: linear-gradient(to right, var(--ink) calc(100% - var(--sp-16)), transparent);
  mask-image: linear-gradient(to right, var(--ink) calc(100% - var(--sp-16)), transparent);
}
.filters::-webkit-scrollbar { display: none; }
.filters[hidden] { display: none; }

/* A pressed label is ink with an ink underline and an unpressed one ink-3 with a transparent
   one of the same width, so a label does not move when it is pressed. */
.filters button {
  all: unset;
  cursor: pointer;
  padding: var(--sp-4) 0;
  color: var(--ink-3);
  border-bottom: 1px solid transparent;
}
.filters button[aria-pressed='true'] { color: var(--ink); border-bottom: 1px solid var(--ink); }
.filters button:focus-visible { outline: var(--focus); outline-offset: 2px; }

.look-count { margin: 0; flex: none; padding-left: var(--sp-18); color: var(--ink-2); }

/* Two across on a phone and five from 700px, in hairlines and with no gap. */
.looks {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A look the filter hides has no box, which is what lets it leave every ring of the window. */
.look { min-width: 0; }
.look[hidden] { display: none; }

.look-link {
  display: block;
  position: relative;
  aspect-ratio: 3 / 4;
  border-right: var(--hairline);
  border-bottom: var(--hairline);
  background: var(--placeholder);
  overflow: hidden;
}
.look-main, .look-main img { display: block; width: 100%; height: 100%; }
.look-main img { object-fit: cover; }

.provenance {
  margin: 0;
  padding: var(--sp-14) var(--gutter);
  border-top: var(--hairline);
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--t-11);
  color: var(--ink-2);
}

@media (min-width: 700px) {
  .filter-row { font-size: var(--t-12); }
  .filters { gap: var(--sp-22); padding-right: 0; -webkit-mask-image: none; mask-image: none; }
  .looks { grid-template-columns: repeat(5, 1fr); }
}
/* ---------- the lookbook's hover alternate: one other view of the same look ----------
   The alternate is absolutely placed over the primary inside `.look-link` (40-lookbook.css).
   Its script only runs where the device can hover, so on touch the class below is never set. */
.look-alt { position: absolute; inset: 0; opacity: 0; transition: opacity var(--dur-state) ease; }
.look.is-alt .look-alt { opacity: 1; }
.look-alt img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Instant: the swap is meant to be exactly none, whatever else sets a duration on `*`. */
@media (prefers-reduced-motion: reduce) {
  .look-alt { transition: none !important; }
}
/* ---------- a look page: lookbook/<look>/ ----------

   Every photograph of one post, whole: full width, height from the photograph's own ratio,
   so nothing is cropped, one under the next with a hairline between and no gap. Then the
   credit and the call to order. */

.look-page { display: block; }

.look-frames { list-style: none; margin: 0; padding: 0; }

.look-frames li + li { border-top: var(--hairline); }

.look-frames figure { margin: 0; }

.look-frames picture { display: block; }

.look-frames img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--placeholder);
}

.look-credit {
  margin: 0;
  padding: var(--sp-14) var(--gutter);
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--t-11);
  color: var(--ink-2);
}

.look-cta {
  display: block;
  padding: var(--sp-14) var(--gutter);
  border-top: var(--hairline);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  text-decoration: underline;
  color: var(--ink);
}
/* ---------- footer ----------

   Three parts on every site document, at every width: her routes, the pages, the copyright.
   Phone stacks them; from 700px they sit in one row. */

.site-foot {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  margin-top: var(--sp-28);
  padding: var(--sp-14) var(--gutter);
  font: 400 var(--t-11)/1.4 var(--font-mono);
  color: var(--ink-2);
}
.site-foot p { margin: 0; }
.foot-withdraw { font-weight: 500; }

@media (min-width: 700px) {
  .site-foot {
    flex-direction: row;
    justify-content: space-between;
    gap: 0;
  }
}
/* ---------- about her ----------

   Her page. Reading text is `.read` (16px, 68ch); her five headings are Archivo 500 at the
   reading size and never uppercased. The photograph is not a link and is not a grid cell: the
   portrait is 4:5 and is set at its own ratio, never cropped by a cell. */

.about {
  display: grid;
  gap: var(--sp-20);
  padding: var(--sp-20) var(--gutter) var(--sp-28);
}

.about-frame { margin: 0; max-width: 480px; }
.about-frame picture { display: block; width: 100%; height: auto; }
.about-frame img { width: 100%; height: auto; }

.about-text { min-width: 0; }
.about-name { font: 500 var(--t-16)/1.3 var(--font-sans); margin: 0 0 var(--sp-12); }
.about-text .lead, .about-band .lead { margin: 0; }

/* Her own lines, marked as hers by the hairline down the left. Her line breaks are hers too. */
.bio, .quotes blockquote, .her-story {
  margin: 0;
  padding: 0 0 0 var(--sp-16);
  border-left: var(--hairline);
}
.bio { margin-top: var(--sp-20); white-space: pre-wrap; color: var(--ink-2); }

.about-story, .about-band { padding: var(--sp-28) var(--gutter) 0; }

.her-story p { margin: var(--sp-12) 0 0; }

.story-head {
  font: 500 var(--t-16)/1.3 var(--font-sans);
  margin: var(--sp-28) 0 0;
}
.her-story > .story-head:first-child { margin-top: 0; }

h2.sub-head { font: 500 var(--t-16)/1.3 var(--font-sans); margin: 0 0 var(--sp-12); }

.quotes { list-style: none; margin: var(--sp-20) 0 0; padding: 0; display: grid; gap: var(--sp-12); }
.quotes blockquote { color: var(--ink-2); }

.about-link { margin: var(--sp-20) 0 0; font-size: var(--t-16); }
.about-link a { text-decoration: underline; text-underline-offset: 3px; }

@media (min-width: 700px) {
  .about {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: var(--sp-28);
    align-items: center;
  }
  .about-frame { max-width: none; }
}
/* ---------- legal pages ---------- */

.legal { padding: var(--sp-28) var(--gutter); }
.legal h1 { font: 500 var(--t-16)/1.3 var(--font-sans); margin: 0 0 var(--sp-20); }
.legal h2 { font: 500 var(--t-16)/1.3 var(--font-sans); margin: var(--sp-28) 0 var(--sp-6); }
.legal p { margin: 0 0 var(--sp-12); }
/* ---------- the look modal ----------

   Created by site.js and never present in the served HTML, so the lookbook still renders zero
   buttons with scripts off. DESIGN.md, *The look modal*. The shell's rules are here; the
   portrait mode's follow; a landscape mode brings its own part. */

html.viewer-open { overflow: hidden; }

/* While the modal is open the page under it is ink and paints nothing. Safari 26 and
   Instagram's in-app browser do not paint the top layer - this dialog and its backdrop - below
   their floating bottom toolbar, but they do paint the document there, so a band of the
   lookbook showed under every photograph on an iPhone. No size of the dialog reaches that band;
   the page it would reveal is made ink instead. tools/checks/viewer-fill.check.mjs. */
html.viewer-open,
html.viewer-open body { background: var(--ink); }
html.viewer-open body > :not(.look-modal) { visibility: hidden; }

.look-modal {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--ink);
  color: var(--paper);
  overflow: hidden;
}

.look-modal::backdrop { background: var(--ink); }

/* The stage is the whole screen. No touch behaviour is declared here or on the track, and
   that is deliberate and load-bearing: a declaration would reach into the edge strips the swipe
   hands back to the browser and disarm its own back and sheet-dismiss recognisers. */
.look-modal .viewer-stage {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* ---- the controls sit over the photograph ----
   Close at the top right, the credit at the bottom left, the call to order at the bottom
   right, each on the veil and each at least a tap target tall. The bar between the credit
   and the call lets touches through to the stage, so it takes no swipe away. */
.modal-close,
.modal-step,
.modal-credit,
.modal-cta {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: 0;
  padding: var(--sp-6) var(--sp-10);
  border: 0;
  border-radius: 0;
  background: var(--veil);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-11);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: var(--track);
  text-transform: uppercase;
  text-decoration: none;
}

.modal-close,
.modal-step,
.modal-cta { cursor: pointer; }

.modal-close {
  position: absolute;
  top: calc(var(--gutter) + env(safe-area-inset-top, 0px));
  right: var(--gutter);
}

.modal-bar {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: calc(var(--gutter) + env(safe-area-inset-bottom, 0px));
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-6);
  pointer-events: none;
}

.modal-bar > * { pointer-events: auto; }

/* The credit may wrap; the call to order never does. */
.modal-credit { flex: 0 1 auto; min-width: 0; }
.modal-cta { flex: none; white-space: nowrap; }

/* Her month and the photographers' handles are names, and a name is not uppercased. */
.modal-keep { text-transform: none; }

.modal-credit a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Previous and next are for a pointer that can hover; a hand swipes, and the keys are
   everyone's. */
.modal-step { display: none; }

@media (hover: hover) {
  .modal-step {
    display: inline-flex;
    justify-content: center;
    position: absolute;
    top: 50%;
    min-width: 44px;
    transform: translateY(-50%);
  }
  .modal-prev { left: var(--gutter); }
  .modal-next { right: var(--gutter); }
  .modal-step:disabled { opacity: 0.4; cursor: default; }
  .modal-close:hover,
  .modal-step:not(:disabled):hover,
  .modal-cta:hover { background: var(--paper); }
}

/* ---- portrait: the photograph covers the screen ----
   The stage is a window onto a track of three photographs - the one being looked at and one
   either side of it - so a swipe reveals the next rather than vacating the page. site.js
   writes the track's offset in pixels, because a slide is one stage wide. */
.viewer-track {
  display: flex;
  flex-wrap: nowrap;
  width: 100%;
  height: 100%;
}

.viewer-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  min-width: 0;
  overflow: hidden;
  background: var(--ink);
}

.viewer-slide picture {
  display: block;
  width: 100%;
  height: 100%;
}

.viewer-slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The same three tiers as everything else, and no fourth signal is read. */
@keyframes viewer-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes viewer-rise { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: none; } }

html[data-motion='full'] .look-modal[open] .viewer-stage {
  animation: viewer-rise var(--dur-state) ease both;
}
html[data-motion='lite'] .look-modal[open] .viewer-stage {
  animation: viewer-fade var(--dur-state) ease both;
}

/* The settling: the transition the track uses to go back where it was, or to carry the next
   photograph into the middle. A class rather than a standing rule, because during the drag the
   track must follow the finger with no transition at all. `full` moves the track; `lite` fades
   the photograph being looked at; `still` gets neither. The reduced-motion rule in
   10-base.css is the second, independent guarantee: it flattens every duration whether or not
   the script has resolved a tier. */
html[data-motion='full'] .viewer-track.settling {
  transition: transform var(--dur-state) ease;
}
html[data-motion='lite'] .viewer-slide picture.settling {
  transition: opacity var(--dur-state) ease;
}
/* ---------- the look modal, landscape ----------

   One horizontal list of whole photographs, each the full height of the screen and as wide as
   its own shape makes it, so nothing is cropped and nothing sits between two of them. The set
   is always wider than the screen. No `touch-action` is declared here or on the stage, as in
   the portrait mode: the strip scrolls natively and the browser's own gestures stay armed. */

.look-modal .viewer-strip {
  position: relative;
  display: flex;
  width: 100%;
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}

.look-modal .viewer-strip::-webkit-scrollbar { display: none; }

.look-modal .viewer-strip .viewer-frame {
  display: block;
  flex: none;
  height: 100%;
  scroll-snap-align: start;
}

.look-modal .viewer-strip img {
  display: block;
  height: 100vh;
  height: 100dvh;
  width: auto;
  max-width: none;
  margin: 0;
  border: 0;
}
/* The order pages (tools/site/pages/order-piece.mjs): order/<piece>/ and order/choose/.
   Tokens are the redesign's (contracts section 3). Photographs are 3:4 cover tiles in hairlines;
   the form is a stack of steps separated by top hairlines. One breakpoint, 700px. */

.order { display: block; }
.order-photos { list-style: none; margin: 0; padding: 0; }
.order-photos li { margin: 0; background: var(--placeholder); aspect-ratio: 3 / 4; }
.order-photos picture,
.choose-pieces picture { width: 100%; height: 100%; }
.order-photos img,
.choose-pieces img { width: 100%; height: 100%; object-fit: cover; }

/* Phone: one row of tiles that scrolls inside itself, then the form. */
.order-photos {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  border-bottom: var(--hairline);
}
.order-photos li {
  flex: 0 0 78vw;
  width: 78vw;
  scroll-snap-align: start;
  border-right: var(--hairline);
}

.order-form { padding: var(--sp-20) var(--gutter); min-width: 0; }
.order-name,
.order-legend,
.order-form legend {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  margin: 0;
  padding: 0;
}
.order-price {
  margin: var(--sp-6) 0 var(--sp-18);
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--t-13);
}
.order-price q { font-family: var(--font-sans); text-transform: none; }
.order-note,
.order-noscript,
.order-fact { margin: 0 0 var(--sp-14); }
.order-noscript a { text-decoration: underline; text-underline-offset: 3px; }

.order-step {
  display: block;
  min-width: 0;
  margin: 0;
  border: 0;
  border-top: var(--hairline);
  padding: var(--sp-14) 0;
}
.order-pending { display: flex; flex-wrap: wrap; gap: var(--sp-6) var(--sp-16); align-items: baseline; }
.order-form legend { float: left; width: 100%; margin-bottom: var(--sp-12); }
.order-form legend + * { clear: both; }

/* Radios: the input is out of sight and still focusable, and its label is the visible control. */
.order-form input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
}
.order-form label { position: relative; cursor: pointer; }
.order-size label,
.order-length label:not(.order-cm),
.order-material label {
  display: inline-flex;
  flex-direction: column;
  min-width: 48px;
  margin: 0 var(--sp-6) var(--sp-6) 0;
  padding: var(--sp-10) var(--sp-14);
  border: var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
}
.order-fibre { text-transform: none; font-size: var(--t-12); color: var(--ink-2); }
.order-size label:has(input:checked),
.order-length label:has(input:checked),
.order-material label:has(input:checked) { background: var(--ink); color: var(--paper); }
.order-material label:has(input:checked) .order-fibre { color: var(--paper); }
.order-form label:not(.order-cm):has(input:focus-visible) { outline: var(--focus); outline-offset: 2px; }
.order-cm { display: flex; align-items: center; gap: var(--sp-10); margin-top: var(--sp-10); font-size: var(--t-16); }
.order-cm[hidden] { display: none; }
.order-cm input { width: 96px; padding: var(--sp-6) var(--sp-10); border: var(--hairline); border-radius: 0; font-size: var(--t-16); }
.order-size .size-guide { margin-top: var(--sp-12); }

/* Four cones across, always: a family of six is 4 + 2 and never 5 + 1, whatever the width. */
.family {
  display: grid;
  grid-template-columns: repeat(4, 48px);
  gap: var(--sp-10);
  margin-bottom: calc(var(--sp-14) + var(--sp-10));
}
.family-name {
  grid-column: 1 / -1;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: var(--track);
}
.family label { display: block; width: 48px; margin: 0; }
.order-colour input:checked + .swatch { outline: var(--focus); outline-offset: 2px; }
.order-colour input:focus-visible + .swatch { outline: var(--focus); outline-offset: 2px; }

.order-missing { margin: var(--sp-10) 0; font-size: var(--t-14); color: var(--ink-2); }
.order-add {
  display: block;
  width: 100%;
  margin: var(--sp-12) 0 var(--sp-20);
  padding: var(--sp-14) var(--sp-16);
  border: 0;
  border-radius: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  cursor: pointer;
}
.order-add:disabled { background: var(--ink-3); cursor: not-allowed; }

/* order/choose/ */
.order-choose { padding: var(--sp-20) var(--gutter); }
.order-choose .order-name { margin-bottom: var(--sp-18); }
.choose-pieces {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-14) var(--sp-10);
}
.choose-pieces a { display: block; }
.choose-pieces picture { display: block; aspect-ratio: 3 / 4; height: auto; background: var(--placeholder); border: var(--hairline); }
.choose-pieces picture img { height: 100%; }
.choose-name,
.choose-price {
  display: block;
  margin-top: var(--sp-6);
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--track);
}
.choose-name { font-weight: 500; font-size: var(--t-12); }
.choose-price { font-size: var(--t-13); }

@media (min-width: 700px) {
  .order { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  .order-photos {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    overflow: visible;
    border-bottom: 0;
    scroll-snap-type: none;
  }
  .order-photos li { flex: none; width: auto; border-right: 0; }
  .order-photos li:nth-child(odd) { border-right: var(--hairline); }
  .order-photos li:nth-child(n+3) { border-top: var(--hairline); }
  .order-form { padding: var(--sp-20) var(--gutter); }
  .choose-pieces { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* The custom branch of the order page (src/order/custom.js, S9b): the Version choice, the ideas,
   the note and the lookbook photographs the reader came from. Tokens are the redesign's
   (contracts section 3). It follows 60-order.css in the cascade and borrows its choice tiles. */

.order-version label {
  display: inline-flex;
  min-width: 48px;
  margin: 0 var(--sp-6) var(--sp-6) 0;
  padding: var(--sp-10) var(--sp-14);
  border: var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
}
.order-version label:has(input:checked) { background: var(--ink); color: var(--paper); }
.order-version label:has(input:focus-visible) { outline: var(--focus); outline-offset: 2px; }

.order-balance { display: block; }

.custom-branch { border-top: var(--hairline); padding: var(--sp-14) 0; min-width: 0; }
.custom-branch[hidden] { display: none; }
.custom-branch .order-legend { margin-bottom: var(--sp-10); }
.custom-branch .read { margin: 0 0 var(--sp-14); }

.custom-ideas { list-style: none; margin: 0 0 var(--sp-14); padding: 0; }
.custom-ideas li { display: flex; gap: var(--sp-10); align-items: center; margin-bottom: var(--sp-10); }
.custom-ideas .read { margin: 0; }
.custom-ideas img,
.custom-refs img { flex: none; width: 72px; height: 96px; object-fit: cover; background: var(--placeholder); }

.custom-note { display: block; margin: 0 0 var(--sp-14); font-size: var(--t-14); }
.custom-note textarea {
  display: block;
  width: 100%;
  margin-top: var(--sp-6);
  padding: var(--sp-10);
  border: var(--hairline);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-16);
  resize: vertical;
}
.custom-note textarea:focus-visible { outline: var(--focus); outline-offset: 2px; }

.custom-refs { list-style: none; margin: 0 0 var(--sp-10); padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-10); }
.custom-refs li { display: flex; flex-direction: column; gap: var(--sp-6); align-items: flex-start; }
.custom-refs button {
  min-height: 44px;
  padding: var(--sp-6) 0;
  border: 0;
  background: none;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.custom-add { font-size: var(--t-14); text-decoration: underline; text-underline-offset: 3px; }
.size-guide summary {
  font-family: var(--font-mono);
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  cursor: pointer;
}
.size-guide table {
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: var(--t-12);
}
.size-guide caption { text-align: left; padding: var(--sp-6) 0; }
.size-guide th,
.size-guide td {
  border: 1px solid var(--ink);
  padding: var(--sp-6);
  text-align: left;
}
/* The care and making-time block (tools/site/partials/care.mjs). Her sentences are never
   uppercased; only the labels are. The tokens are the redesign's (contracts §2); the
   fallbacks are their declared values so the part holds until every token is on the page. */

.care {
  margin: 0;
  padding: var(--sp-12) 0;
  border-top: var(--hairline);
}

.care dt {
  font-family: var(--font-mono);
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: var(--track);
}

.care > div + div { margin-top: var(--sp-20); }

.care dd { margin: 0; margin-top: var(--sp-12); }

.care blockquote {
  margin: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--t-16);
  line-height: 1.55;
  text-transform: none;
}
/* The review page and the thanks page (tools/site/pages/order-review.mjs, src/order/review.js).
   One column at 720px, lines separated by hairlines, the thumbnail 3:4 at 72px beside the text
   at both widths. Her qualifier is never uppercased; only the mono labels are. The tokens are
   the redesign's (contracts §3); the fallbacks are their declared values so the part holds
   until every token is on the page. */

.review,
.thanks {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--sp-28) var(--gutter);
  font-size: var(--t-16);
  line-height: 1.55;
}

.review [hidden],
.thanks [hidden] { display: none; }

.review .order-name,
.thanks .order-name {
  margin: 0 0 var(--sp-20);
  font-family: var(--font-mono);
  font-size: var(--t-14);
  font-weight: 500;
  line-height: 1.35;
  text-transform: uppercase;
  letter-spacing: var(--track);
}

.review p,
.thanks p { margin: 0 0 var(--sp-12); }

.review-error {
  padding: var(--sp-12);
  border: var(--hairline);
}

.review-lines {
  margin: 0;
  padding: 0;
  list-style: none;
  border-bottom: var(--hairline);
}

.review-line {
  display: grid;
  grid-template-columns: 72px max-content max-content 1fr;
  column-gap: var(--sp-14);
  align-items: start;
  padding: var(--sp-16) 0;
  border-top: var(--hairline);
}

.review-line.is-invalid {
  /* The mark sits in the gutter, so the row's content does not move when it appears. */
  padding-left: var(--sp-10);
  margin-left: calc(-1 * (var(--sp-10) + 2px));
  border-left: 2px solid var(--ink);
}

.review-line > img {
  grid-column: 1;
  grid-row: 1 / span 8;
  width: 72px;
  height: 96px;
  object-fit: cover;
  background: var(--placeholder);
}

.review-line > :not(img) { grid-column: 2 / -1; margin: 0; }

.review-name {
  font-family: var(--font-mono);
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
}

.review-summary,
.review-note,
.review-price,
.review-balance { margin-top: var(--sp-4); }

.review-note { overflow-wrap: anywhere; }

.review-price q { text-transform: none; }

.review-refs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  margin: var(--sp-10) 0 0;
  padding: 0;
  list-style: none;
}

.review-refs img {
  width: 72px;
  height: 96px;
  object-fit: cover;
  background: var(--placeholder);
}

.review-line > .review-edit,
.review-line > .review-remove {
  grid-column: auto;
  margin-top: var(--sp-10);
  padding: var(--sp-10) 0;
  font-family: var(--font-mono);
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: var(--track);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.review-line > .review-edit { grid-column: 2; }
.review-line > .review-remove { grid-column: 3; }

.review-remove {
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

.review-totals { margin-top: var(--sp-20); }
.review-totals p { margin-bottom: var(--sp-4); }

.review .review-reply { margin-top: var(--sp-20); }

.review-send { margin-bottom: var(--sp-20); }

.review .review-empty { margin-top: var(--sp-20); }
.review-empty a { text-decoration: underline; text-underline-offset: 3px; }

.review .order-noscript { margin-top: var(--sp-20); }
.order-noscript a { text-decoration: underline; text-underline-offset: 3px; }

.review .care { margin-top: var(--sp-28); }

.review-send-mail { display: inline-block; text-decoration: underline; text-underline-offset: 3px; }
.review-mail-help, .review-copied { margin-top: var(--sp-10); }
.review-order-text {
  margin: var(--sp-20) 0 var(--sp-12);
  padding: var(--sp-12);
  font: inherit;
  font-size: max(16px, 1rem);
  color: var(--ink);
  background: var(--paper);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.review-copy {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* Checkout mode: each line's withdrawal position as a hairline row with its line number as a
   mono label, the two legal links underlined with a separator, and the order button in the
   site's one filled button style (.order-add in 60-order.css). */
.review-withdrawals {
  margin: 0 0 var(--sp-12);
  padding: 0;
  list-style: none;
  border-bottom: var(--hairline);
}
.review-withdrawal {
  display: flex;
  gap: var(--sp-10);
  align-items: baseline;
  padding: var(--sp-10) 0;
  border-top: var(--hairline);
}
.review-withdrawal-line {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-12);
  letter-spacing: var(--track);
}
.review-legal > a {
  font-family: var(--font-mono);
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.review-legal > a + a::before {
  content: '/';
  display: inline-block;
  margin: 0 var(--sp-6) 0 var(--sp-4);
  text-decoration: none;
}
.review-checkout button {
  display: block;
  width: 100%;
  margin: var(--sp-20) 0;
  padding: var(--sp-14) var(--sp-16);
  border: var(--hairline);
  border-radius: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--t-12);
  text-transform: uppercase;
  letter-spacing: var(--track);
  cursor: pointer;
}
.review-checkout button:focus-visible { outline: var(--focus); outline-offset: 2px; }
/* The selling pages and the withdrawal function: terms/, withdrawal/, withdraw/, withdraw/sent/.
   Reading text is `.read` at 16px; headings are mono uppercase 12px; the fields are 16px so a
   phone does not zoom; hairlines separate. */

.selling h1:not(.order-name) { font: 500 var(--t-12)/1.3 var(--font-mono); text-transform: uppercase; letter-spacing: var(--track); margin: 0 0 var(--sp-20); }
.selling h2 { font: 500 var(--t-12)/1.3 var(--font-mono); text-transform: uppercase; letter-spacing: var(--track); margin: var(--sp-28) 0 var(--sp-6); padding-top: var(--sp-14); border-top: var(--hairline); }
.selling .order-name { font: 500 var(--t-16)/1.3 var(--font-sans); margin: 0 0 var(--sp-20); }
.selling form { display: grid; gap: var(--sp-14); max-width: 68ch; }
.selling label { display: grid; gap: var(--sp-4); font: 500 var(--t-12)/1.3 var(--font-mono); text-transform: uppercase; letter-spacing: var(--track); }
.selling input, .selling textarea { font: 400 var(--t-16)/1.4 var(--font-sans); text-transform: none; letter-spacing: 0; color: var(--ink); background: var(--paper); border: var(--hairline); border-radius: 0; padding: var(--sp-10); width: 100%; box-sizing: border-box; }
.selling input:focus-visible, .selling textarea:focus-visible, .selling button:focus-visible { outline: var(--focus); outline-offset: 2px; }
.selling button { font: 500 var(--t-12)/1 var(--font-mono); text-transform: uppercase; letter-spacing: var(--track); color: var(--paper); background: var(--ink); border: var(--hairline); border-radius: 0; padding: var(--sp-14) var(--sp-18); justify-self: start; cursor: pointer; }
#missing-email { margin: 0 0 var(--sp-14); padding: var(--sp-10) 0; border-top: var(--hairline); border-bottom: var(--hairline); }
#missing-email[hidden] { display: none; }
