/* ==========================================================================
   ANALOGUE — the design system.
   Every colour, size and space on the site comes from this file. Change a
   value here and it changes everywhere. Do not write colours or font sizes
   into a page; add them here and use the name.
   ========================================================================== */

:root {
  /* ---- Colour. Four inks on paper, and a hairline. ---- */
  --paper:  #F3F1ED;
  --ink:    #16150F;
  --stone:  #8A8375;
  --red:    #D9451F;
  --hair:   rgba(22, 21, 15, .12);
  --wash:   rgba(22, 21, 15, .04);   /* the faint tint behind product photographs */

  /* ---- Type. Two families, and nothing else. ----
     Meno Display ships ONE weight (300 Light). There is no bold. Never let a
     browser invent one — font-synthesis:none below is what stops it. */
  --display: "meno-display", "Instrument Serif", Georgia, serif;
  --body:    "Century Gothic", "Jost", "Questrial", "URW Gothic", "Avant Garde", sans-serif;

  /* ---- The section rhythm. The site was deliberately compacted; these are
     the compacted numbers. Do not re-inflate them. ---- */
  --gap-section: 22px;   /* desktop top and bottom of every section */
  --gap-side:    24px;   /* desktop left and right */
  --measure:     1500px; /* the widest READING content ever gets */
  --gap-tile:    6px;    /* between pictures — a collage gutter, not a margin */

  /* One easing for everything that arrives. Slow out, no overshoot — the
     difference between a page that feels cinematic and one that feels springy. */
  --ease:   cubic-bezier(0.22, 1, 0.36, 1);
  --ease-ui: cubic-bezier(.44, 0, .56, 1);   /* hovers and small state changes */
  --measure-narrow: 480px; /* forms and single columns: cart, checkout, account */
}

@media (max-width: 1199.98px) {
  :root { --gap-section: 20px; --gap-side: 18px; }
}
@media (max-width: 809.98px) {
  :root { --gap-section: 16px; --gap-side: 12px; --gap-tile: 4px; }
}

/* ==========================================================================
   Reset — only what is needed, nothing fashionable.
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

/* Keyboard users must always be able to see where they are. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* ==========================================================================
   Type scale.
   ========================================================================== */

h1, h2, h3, .display-xl, .display-l, .display-m {
  font-family: var(--display);
  font-weight: 300;             /* the only weight that exists */
  font-style: normal;
  font-synthesis: none;         /* no faked bold, no faked italic */
  text-transform: uppercase;
  letter-spacing: .005em;
  line-height: 1.1;
  margin: 0;
}

/* Sizes carry the price tag. A headline under 40px on a desktop reads as a
   blog post; these are the premium ranges — 72-96 display, 40-56 section.
 *
 * TRACKING TIGHTENS AS SIZE GROWS, and this is the single thing worth taking
 * from the reference. Letter-spacing is optical, not absolute: the gaps that
 * make 11px legible make 88px look like it is falling apart. One value across
 * the whole scale — .005em, as this had — is therefore right in exactly one
 * place and slightly wrong everywhere else.
 *
 * The numbers are gentler than the reference's because that system is
 * sentence-case sans and this is uppercase serif. Capitals have no descenders
 * and flat sidebearings, so they need more air than lowercase at the same
 * size; taken to -0.022em they touch. The PRINCIPLE transfers, the values
 * could not.
 *
 * Line height closes with it: 1.1 is right for a section heading and loose for
 * an 88px one, where the two lines drift apart instead of reading as a block. */
.display-xl { font-size: clamp(40px, 6.4vw, 88px); letter-spacing: -.018em; line-height: 1.04; }
.display-l  { font-size: clamp(30px, 4.2vw, 52px); letter-spacing: -.010em; line-height: 1.07; }
.display-m  { font-size: clamp(24px, 3.1vw, 36px); letter-spacing: -.004em; line-height: 1.10; }
/* For a heading that names a group inside a page — a collection in the shop —
   rather than the page itself. Still the display face; a shop's collections
   are not sub-sub-sections. */
.display-s  { font-size: clamp(20px, 2.2vw, 26px); letter-spacing: 0; line-height: 1.14; }

/* The eyebrow — the small capital label above almost every section. */
.eyebrow {
  font-family: var(--body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--stone);
  margin: 0;
}
.eyebrow--ink { color: var(--ink); }

/* Body goes 16 → 17 and picks up a little negative tracking.
 *
 * The reference sets body at 17 with -0.016em and is right about why: the
 * precision people read as "considered" is the tracking, not the size. At 16
 * with none, Century Gothic's wide round forms read a touch loose and a touch
 * small next to display type this large.
 *
 * Small sizes keep more of their air — the same rule as the headings, running
 * the other way. */
.body     { font-size: 17px; line-height: 1.58; letter-spacing: -.011em; margin: 0; }
.body-lg  { font-size: 19px; line-height: 1.52; letter-spacing: -.014em; margin: 0; }
.body-sm  { font-size: 14px; line-height: 1.55; letter-spacing: -.006em; margin: 0; }
.stone    { color: var(--stone); }
.numeric  { font-variant-numeric: tabular-nums; letter-spacing: .02em; }

.measure  { max-width: 62ch; }

/* An underlined text link, used for "Open the collections →" and its kin. */
.link {
  border-bottom: 1px solid var(--hair);
  padding-bottom: 2px;
  transition: border-color .3s var(--ease-ui);
}
.link:hover { border-bottom-color: var(--ink); }

/* ==========================================================================
   Layout.
   ========================================================================== */

.section {
  padding: var(--gap-section) var(--gap-side);
}
.section--hair { border-top: 1px solid var(--hair); }

/* Rhythm from a surface shift, not from a gap.
 *
 * The reference builds its pulse by alternating canvas tones between sections
 * and letting scale do the rest — no dividers, no cards, no elevation. That
 * idea survives the translation intact even though none of its colours do:
 * the shift here is paper against the same 4% ink wash already used behind
 * photographs, so it is the site's own tone rather than an imported grey.
 *
 * It matters MORE at this density than it would at the reference's spacing.
 * With 22px between sections there is not enough emptiness to separate them,
 * so the boundary has to be carried by the surface instead — and a hairline
 * under a tight stack reads as a table, which is what this replaces. */
.section--wash {
  background: var(--wash);
  /* A tint needs a little more air than a hairline does, or it reads as a
     highlighter stripe rather than a panel. 1.6x, not 3x: the page stays
     compact, the block just stops touching its own edges. */
  padding-top:    calc(var(--gap-section) * 1.6);
  padding-bottom: calc(var(--gap-section) * 1.6);
}
/* A washed section next to a hairline would draw both. The surface change is
   already the boundary; the line is then just a line. */
.section--wash + .section--hair,
.section--wash.section--hair { border-top-color: transparent; }
.wrap   { max-width: var(--measure); margin: 0 auto; width: 100%; }
/* The single-column pages — cart, checkout, account, order received.
   They line up on the SAME left edge as every other page rather than floating
   in the middle of the screen. A form that starts in a different place to the
   nav above it looks like a mistake, because it is one. */
.narrow {
  max-width: var(--measure-narrow); width: 100%;
  margin-left: max(0px, calc((100% - var(--measure)) / 2));
  margin-right: auto;
}

.stack     { display: flex; flex-direction: column; align-items: flex-start; }
.stack > * + * { margin-top: var(--s, 14px); }
.row       { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.row--between { justify-content: space-between; }

/* minmax(0, 1fr), NOT 1fr.
   A grid track's default minimum is `auto`, which means "as wide as my widest
   unbreakable child". One long product name in one card was therefore pushing
   the whole two-column grid to 449px inside a 390px phone, and the page
   scrolled sideways — on /collections and /lifestyle, the two with the longest
   names. minmax(0, …) lets the track be narrower than its content and the text
   wrap, which is what everybody means by 1fr in the first place. */
/* Tightened from 28/24. Measured on a form page, the old figures put a
   282px card in 388px of vertical space and left the eye travelling between
   pieces instead of across them. A catalogue reads better dense. */
/* A collage gutter, not a margin. 20px between pictures reads as a list of
   things; 6px reads as one surface with seams in it, which is the whole
   difference between a catalogue page and a moodboard. The pictures do the
   work — the white between them was doing none. */
.grid { display: grid; gap: var(--gap-tile); }

/* Pictures reach the edge of the screen; words do not.
 *
 * This is the line the brief drew: "make the images fill the whole page" and
 * "don't forget we are a website". A paragraph running 1500px edge to edge is
 * unreadable, so .wrap keeps its measure and only the picture grids break out.
 * The negative margin is exactly the padding .section just applied, so the
 * grid lands flush against the viewport without anybody having to know what
 * that padding currently is. */
.bleed {
  width: 100vw;
  max-width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}
/* Inside a .section the horizontal padding has to be cancelled too, or the
   bleed sits under it and overflows instead of filling. */
.section > .bleed,
.section > .wrap > .bleed { margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); }
/* The collage grid. Fixed column counts were written for a 1200px measure; at
   full bleed on a wide screen four columns make four enormous pictures with a
   lot of nothing under them. auto-fill takes as many columns as fit at a
   sensible picture size, so the same markup is a dense wall on a monitor and
   two columns on a phone, and the pictures stay the size a picture wants to be
   rather than the size the container happens to be. */
/* auto-FIT, and a ceiling on how wide a tile may grow.
 *
 * auto-fill kept the empty columns. With three pins in a row built for six,
 * the three sat hard left at their minimum width and the other half of the
 * band was a hole — which is the second half of what was wrong here, and the
 * reason it looked broken rather than merely tight.
 *
 * auto-fit throws the empty tracks away and hands their width back to the
 * tiles that exist, so a short row fills its band and keeps its left edge on
 * the heading above it. A full grid is unaffected — with enough tiles to fill
 * every track there is nothing to collapse, and the shop lays out exactly as
 * it did.
 *
 * A definite ceiling on the track (rather than on the tile) was tried first
 * and is wrong: the repetition count is computed from the track's MAXIMUM, so
 * the ceiling quietly removed a whole column from the shop and left an uneven
 * margin down both sides. The cap belongs on the tile. */
.grid--tiles {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
/* A row with two pins in it should not put them at either end of the screen.
   The cap only bites when auto-fit has collapsed the grid down to one or two
   very wide tracks; anywhere the row is genuinely full, the tracks are already
   narrower than this and nothing happens. */
.grid--tiles > * { max-width: 520px; }

/* Even out the last row.
 *
 * auto-fit takes five columns on a wide page, so six pieces land 5 + 1 and
 * seven land 5 + 2: a full row with an orphan under it, and a band of nothing
 * beside the orphan. Six wants to be 3 + 3.
 *
 * Counting children is a :has() trick, not a script — :nth-child(6):last-child
 * can only match when there are exactly six. It re-evaluates live, so a grid
 * the catalogue fills in from the feed lands on the right shape the moment its
 * cards arrive. Only the counts that actually strand something are named;
 * beyond nine the last row is long enough to read as a row. */
@media (min-width: 810px) {
  .grid--tiles:has(> :nth-child(6):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--tiles:has(> :nth-child(7):last-child),
  .grid--tiles:has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* A picture may touch the edge of the screen. A PRICE may not.
 *
 * The tile grid is not a moodboard, whatever it looks like from across the
 * room: every tile carries a name and a price under it, and a price sitting on
 * the left edge of the glass reads as a page that failed to load. The heading
 * above it lives in .wrap and does have a gutter, so the two disagreed by
 * exactly the section padding — the eye reads that as breakage, not as bleed.
 *
 * So a tile grid gets back the inset .wrap would have given it, and lands on
 * the same vertical line as its own heading at every width. It is still the
 * widest thing on the page and the tiles are still 6px apart; it simply starts
 * where the words start.
 *
 * Bands, the ring, the film and the customise stage keep their true bleed.
 * They are photographs with nothing written at their edge, and touching the
 * edge is the whole point of them.
 *
 * Written as three selectors because .bleed's own margin rules are scoped to
 * .section and would otherwise outrank this by specificity alone. */
.grid--tiles.bleed,
.section > .grid--tiles.bleed,
.section > .wrap > .grid--tiles.bleed {
  /* 100%, not auto. Most of these grids sit inside a .stack, which is a
     column flex box with align-items:flex-start — under `auto` the grid
     shrink-wraps to its max-content and auto-fit lays out six 230px tracks
     whatever the container is, which is 50px WIDER than the page and gave the
     whole site a horizontal scrollbar. align-self undoes the flex-start for
     this one child. */
  width: 100%;
  align-self: stretch;
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
}

@media (max-width: 809.98px) {
  .grid--tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
}

.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 1199.98px) {
  .grid--4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 809.98px) {
  .grid--4, .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--2 { grid-template-columns: minmax(0, 1fr); }
}

/* Said aloud but not shown. For a heading a design leads with a photograph
   instead of words — the page still needs one, for search engines and for
   anyone listening to it rather than looking. */
.u-said-aloud {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.rule { height: 1px; background: var(--hair); border: 0; margin: 0; width: 100%; }

/* ==========================================================================
   Buttons.
   ========================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 52px; padding: 0 28px;
  font-family: var(--body); font-size: 11px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  border: 1px solid var(--ink); border-radius: 0; cursor: pointer;
  background: var(--ink); color: var(--paper);
  /* Hover moves the ink, not the opacity.
   *
   * Fading a filled button to .82 lets whatever is behind it come through, so
   * on the wash it goes muddy and over a photograph it goes transparent. The
   * pair inverts instead: the filled one empties, the outlined one fills. They
   * become each other, which is the same trick .btn--on-dark already used on
   * the bands, now true everywhere. */
  transition: background-color .28s var(--ease-ui),
              color .28s var(--ease-ui),
              border-color .28s var(--ease-ui),
              transform 100ms ease-out;
}
.btn:hover { background: transparent; color: var(--ink); }
.btn--ghost { background: none; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); }
.btn[disabled], .btn[disabled]:hover {
  opacity: .35; cursor: not-allowed;
  background: var(--ink); color: var(--paper);
}
.btn--ghost[disabled], .btn--ghost[disabled]:hover { background: none; color: var(--ink); }

/* ==========================================================================
   Forms — no boxes. A hairline underline and nothing else.
   ========================================================================== */

.field { display: block; width: 100%; }
.field input, .field textarea, input.field, textarea.field {
  width: 100%; display: block;
  background: none; border: 0; border-bottom: 1px solid var(--hair);
  border-radius: 0; padding: 16px 0;
  font-family: var(--body); font-size: 15px; color: var(--ink);
  outline: none;
}
.field textarea, textarea.field { resize: none; min-height: 92px; }
.field input::placeholder, .field textarea::placeholder,
input.field::placeholder, textarea.field::placeholder { color: var(--stone); }
.field input:focus, .field textarea:focus,
input.field:focus, textarea.field:focus { border-bottom-color: var(--ink); }

select.field {
  width: 100%; background: none; border: 0; border-bottom: 1px solid var(--hair);
  border-radius: 0; padding: 16px 0; font-family: var(--body); font-size: 15px;
  color: var(--ink); outline: none; appearance: none; cursor: pointer;
}

/* ==========================================================================
   The chrome — nav and footer. Shared by every page.
   ========================================================================== */

.nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; padding: 18px var(--gap-side);
  position: sticky; top: 0; z-index: 10;

  /* A floating layer, not an opaque strip.
   *
   * An opaque bar takes 70px of the page and keeps them, and the content
   * stops dead underneath it. A translucent one lets the page keep running
   * beneath the type, which is what tells you there is more above — the bar
   * reads as glass laid on the paper rather than a lid closing it off.
   * .72 with a wide blur is the point where the wordmark still has enough
   * ground under it to stay legible over a photograph. */
  background: rgba(243, 241, 237, .72);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  transition: background-color .3s var(--ease-ui);
}

/* No blur, no illusion — go solid rather than showing type over a photograph. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav { background: var(--paper); }
}

/* The scroll edge.
 *
 * The hairline that used to sit here was drawn whether or not anything was
 * behind it — a rule under a bar floating over blank paper, dividing nothing.
 * This is the same separation, made only where content actually passes under
 * the chrome: a short fall of shadow that arrives when the page starts moving
 * and is not there at rest. */
.nav::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 16px;
  background: linear-gradient(to bottom, rgba(22, 21, 15, .07), rgba(22, 21, 15, 0));
  opacity: 0; pointer-events: none;
  transition: opacity .3s var(--ease-ui);
}
.nav.is-floating::after { opacity: 1; }
.nav__wordmark { height: 34px; width: auto; }
.nav__seal     { height: 44px; width: auto; }   /* smaller and the rings merge */
.nav__links { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.nav__link {
  font-size: 11px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink); white-space: nowrap;
  transition: opacity .3s var(--ease-ui);
}
.nav__link:hover { opacity: .55; }
.nav__link[aria-current="page"] { color: var(--stone); }

@media (max-width: 809.98px) {
  .nav { flex-wrap: wrap; justify-content: center; gap: 12px; }
  .nav__links { justify-content: center; gap: 14px 18px; }
  .nav__seal { display: none; }
}

.foot { border-top: 1px solid var(--hair); padding: var(--gap-section) var(--gap-side); }
.foot__lockup { height: 68px; width: auto; }
.foot__links { display: flex; gap: 20px; flex-wrap: wrap; }
.foot__link {
  font-size: 11px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--stone);
}
.foot__link:hover { color: var(--ink); }
.foot__letter { display: grid; grid-template-columns: 1fr auto; gap: 24px; align-items: end; }
@media (max-width: 809.98px) { .foot__letter { grid-template-columns: 1fr; } }

/* ==========================================================================
   Product cards and grids — filled by catalogue.js from the Ops feed.
   ========================================================================== */

.card { display: block; }
.card__frame {
  aspect-ratio: 1 / 1; background: var(--wash);   /* only ever seen while a picture loads */
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* COVER, and the frame carries no tint of its own.
   
   Contain was right while the photography was a mix of 4:3 and 3:4: it showed
   the whole frame rather than a crop. But it letterboxed every one of them
   into a pale grey box, and a grid of pieces each sitting in its own visible
   rectangle is exactly what "the squares are still visible" means. The
   photography is being made square, and a square photograph in a square frame
   crops nothing — cover simply fills it, edge to edge, with no box behind.
   Anything not yet square is cropped from its centre until it is reshot. */
.card__frame img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  transform: scale(var(--card-zoom, 1));
  transition: transform .5s var(--ease);
}
.card:hover .card__frame img { transform: scale(calc(var(--card-zoom, 1) * 1.04)); }

/* Cropping into the photograph — front page only.
 *
 * The pieces were shot small in their own frames, so scaling them up inside
 * the tile closes the studio's own margin and the row reads as one dense
 * surface. On the front page that is the job: a handful of pieces chosen to
 * look like an image.
 *
 * On a collection it is the wrong job and it looked it. A collection is
 * somebody comparing eleven cups, and a crop that flatters one photograph
 * crops another one badly — the pieces stop being shown on equal terms, which
 * is the whole point of putting them in a grid. Left at 1 everywhere else. */
.grid--zoom { --card-zoom: 1.18; }
.card__name {
  font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  /* 14px above the name and a 1.45 line-height added up to 106px of text under
     every card — more than a third of the card's own height, on a page whose
     job is to show pieces. */
  margin: 8px 0 2px; line-height: 1.3;
}
/* Every line under a card is a <p>, and a <p> carries the body margin — 13px
   above AND below the price, 11px under the flag. Nobody had asked for it and
   nobody could see it: it just made every card 37px taller than its content,
   which on a grid of eleven pieces is most of a screen of nothing. The frame
   and the name were already explicit; these two were not. */
.card__price { font-size: 13px; color: var(--stone); margin: 0; }
.card__flag  { font-size: 11px; font-weight: 700; letter-spacing: .14em;
               text-transform: uppercase; color: var(--red); margin: 3px 0 0; }
/* "Pre-order" is not a refusal, so it is not red. */
.card__flag--preorder { color: var(--ink); }
.card__opts  { margin: 2px 0 0; }
.card:hover .card__frame img { opacity: .86; }
.card__frame img { transition: opacity .4s var(--ease-ui); }

/* --------------------------------------------------------------------------
   The product page gallery: one photograph large, the rest small beneath it.
   catalogue.js writes this markup and swaps the main <img>'s src on a
   thumbnail click. Six to a row at most; a piece with more photographs than
   that wraps to a second row rather than shrinking the thumbnails into
   nothing. The chosen one is marked with an ink hairline, not a colour —
   the colour on this page belongs to the piece.
   -------------------------------------------------------------------------- */
/* The page: pictures on the left, words on the right. The photograph used to
   take half the width and the words the other half in 14px, and the piece
   read as a poster with a caption. The pictures now take two fifths and are
   capped, and the words are set a size up, so the two halves weigh the same. */
.product {
  display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: 56px; align-items: start;
}
.product__pictures { width: 100%; max-width: 480px; }
.product__words .body-sm { font-size: 15px; }
.product__price { font-size: 22px; line-height: 1.4; margin: 0; }
.product__words .btn { font-size: 12px; height: 56px; }
@media (max-width: 809.98px) {
  .product { grid-template-columns: 1fr; gap: 28px; }
  .product__pictures { max-width: none; }
}
.gallery { width: 100%; }
/* The big photograph on a product page fills its square for the same reason —
   no grey bars either side of a bottle. */
.gallery__main img { object-fit: cover; object-position: center; }
/* The thumbnails touch — no gutter, no gap under the main picture: the
   photographs read as one block, and the chosen one is told by its hairline. */
.gallery__thumbs {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; margin-top: 0;
}
.gallery__thumb {
  appearance: none; -webkit-appearance: none; border: 0; padding: 0; margin: 0;
  background: var(--wash); cursor: pointer; display: block;
  aspect-ratio: 1 / 1; overflow: hidden;
  outline: 1px solid transparent; outline-offset: -1px;
  transition: outline-color .25s var(--ease-ui), opacity .25s var(--ease-ui);
}
.gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; opacity: .82; }
.gallery__thumb:hover img { opacity: 1; }
.gallery__thumb.is-on { outline-color: var(--ink); cursor: default; }
.gallery__thumb.is-on img { opacity: 1; }
.gallery__thumb:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (max-width: 559.98px) {
  .gallery__thumbs { grid-template-columns: repeat(5, 1fr); }
}

/* --------------------------------------------------------------------------
   The shop's filter bar and its collection groups. catalogue.js draws the
   family tabs into .shopbar__families and the groups into the region.
   -------------------------------------------------------------------------- */
.shopbar { border-top: 1px solid var(--hair); border-bottom: 1px solid var(--hair); padding: 14px 0 12px; }
.shopbar__families { display: flex; flex-wrap: wrap; gap: 4px 22px; margin-bottom: 14px; }
.shopbar__tab {
  appearance: none; -webkit-appearance: none; background: none; border: 0; padding: 8px 0 6px; margin: 0;
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--stone); cursor: pointer; border-bottom: 1px solid transparent;
  transition: color .25s var(--ease-ui), border-color .25s var(--ease-ui);
}
.shopbar__tab:hover { color: var(--ink); }
.shopbar__tab.is-on { color: var(--ink); border-bottom-color: var(--ink); cursor: default; }
.shopbar__n { color: var(--stone); font-weight: 400; letter-spacing: .04em; margin-left: 4px; }
.shopbar__row { display: flex; flex-wrap: wrap; gap: 14px 24px; align-items: flex-end; }
.shopbar__field { flex: 0 1 200px; min-width: 150px; }
.shopbar__field--grow { flex: 1 1 240px; }
.shopbar__field .eyebrow { display: block; margin-bottom: 4px; }
.shopbar__check { display: inline-flex; align-items: center; gap: 8px; padding-bottom: 8px; cursor: pointer; }
.shopbar__check input { width: 14px; height: 14px; accent-color: var(--ink); margin: 0; }
.shopbar__clear { padding-bottom: 8px; }
.shopbar__result { margin: 12px 0 0; min-height: 1.2em; }
select.field { appearance: none; -webkit-appearance: none; background: transparent; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--stone) 50%), linear-gradient(135deg, var(--stone) 50%, transparent 50%);
  background-position: calc(100% - 12px) 55%, calc(100% - 7px) 55%; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
  padding-right: 24px; }

.shopgroup { padding: 28px 0 8px; }
.shopgroup + .shopgroup { border-top: 1px solid var(--hair); }
.shopgroup__head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.shopgroup__head h2 { margin: 0; }
.shopgroup__head .eyebrow { margin: 0; }
@media (max-width: 559.98px) {
  .shopbar__field, .shopbar__field--grow { flex: 1 1 100%; }
}

/* Empty and loading states are content, not accidents. */
.note { color: var(--stone); font-size: 15px; }

/* ==========================================================================
   Cart lines. commerce.js writes this markup — the class names are its
   contract. Renaming one here breaks the cart.
   ========================================================================== */

.ana-line { display: flex; gap: 20px; align-items: flex-start; padding: 22px 0;
            border-bottom: 1px solid var(--hair); }
.ana-line img { width: 88px; height: 88px; object-fit: cover; flex: 0 0 auto; background: var(--wash); }
.ana-line__body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 6px; }
.ana-line__name { font-size: 15px; letter-spacing: .01em; }
.ana-line__meta { font-size: 13px; color: var(--stone); }
.ana-line__qty  { display: flex; align-items: center; gap: 14px; margin-top: 6px; }
.ana-line__qty button { all: unset; cursor: pointer; font-size: 15px; line-height: 1; padding: 4px 6px; }
.ana-line__qty button:hover { opacity: .6; }
.ana-line__price { font-size: 15px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.ana-line__rm { all: unset; cursor: pointer; font-size: 12px; letter-spacing: .14em;
                text-transform: uppercase; color: var(--stone); }
.ana-line__rm:hover { opacity: .6; }
.ana-field--bad input, .ana-field--bad textarea,
input.ana-field--bad, textarea.ana-field--bad { border-bottom-color: var(--red) !important; }

/* ==========================================================================
   The coverflow ring.
   ========================================================================== */

.cf {
  /* Card width is the only measurement. Pitch, depth and perspective are all
     derived from it, so the whole rake scales with one number. */
  --cf-card: clamp(190px, 26vw, 380px);
  width: 100%;
}
.cf__rel { position: relative; }
.cf__frame {
  /* Vertical padding keeps the cards clear of the overflow clip. */
  padding: 32px 0;
  overflow: hidden;
  outline: none;
  cursor: grab;
  touch-action: pan-y;      /* horizontal drag is ours; the page keeps scrolling */
}
.cf__frame:active { cursor: grabbing; }
.cf__frame:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.cf__track {
  position: relative;
  height: var(--cf-card);
  transform-style: preserve-3d;
  user-select: none;
}
.cf__card {
  position: absolute; left: 50%; top: 0;
  width: var(--cf-card); aspect-ratio: 1 / 1;
  overflow: hidden; background: var(--wash);
  will-change: transform, opacity;
  backface-visibility: hidden;
}
.cf__card img { width: 100%; height: 100%; object-fit: cover; user-select: none; }

.cf__nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 200;
  all: unset; cursor: pointer; padding: 12px; color: var(--stone);
  transition: color .3s var(--ease-ui);
}
.cf__nav:hover { color: var(--ink); }
.cf__nav--prev { left: 12px; }
.cf__nav--next { right: 12px; }

.cf__caption { text-align: center; margin-top: 8px; }
.cf__title {
  font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  margin: 0; color: var(--ink);
}
.cf__subtitle { font-size: 12px; color: var(--stone); margin: 6px 0 0; letter-spacing: .04em; }
.cf__meta {
  margin: 22px auto 0; max-width: 230px; font-size: 12px;
}
.cf__meta > div {
  display: flex; justify-content: space-between; padding: 5px 0;
}
.cf__meta dt { color: var(--stone); margin: 0; }
.cf__meta dd { margin: 0; font-variant-numeric: tabular-nums; }

/* No dots. Twenty-six of them under the ring read as clutter, and the
   caption already names the piece you are looking at. */

/* The ring as a whole page: edge to edge, and tall enough to be the first
   thing and the only thing. */
.hero-ring {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--gap-section) 0;
}
/* The card has to answer to the height as well as the width, or on a short
   laptop screen the ring pushes the rest of the hero off the bottom. */
.hero-ring .cf { --cf-card: clamp(180px, min(28vw, 34svh), 420px); }
@media (max-width: 809.98px) {
  .hero-ring { min-height: auto; padding-bottom: var(--gap-section); }
  .cf__nav { display: none; }   /* on a phone the ring is dragged, not clicked */
}

/* ==========================================================================
   The D’HiPPO reveal. Hover cycles the glaze; the silhouette never moves.
   A centred composition on purpose — the one place the page leaves its left
   margin, so everything in it is centred together rather than half and half.
   ========================================================================== */

.dh {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; gap: 22px; text-align: center;
}

/* A fixed square. Every glaze is laid into exactly the same box, so the
   figure cannot drift as the colour changes. */
.dh__stage {
  position: relative; width: min(100%, 460px); aspect-ratio: 1 / 1;
}
.dh__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; object-position: center;
  opacity: 0; transition: opacity var(--dh-fade, 320ms) var(--ease-ui);
  user-select: none; -webkit-user-drag: none;
}
.dh__img.is-on { opacity: 1; }

.dh__meta { min-height: 64px; }
.dh__name { margin: 0; }
.dh__stock { margin: 6px 0 0; }

.dh__dots { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.dh__dot {
  all: unset; cursor: pointer; width: 11px; height: 11px; border-radius: 50%;
  border: 1px solid var(--stone); position: relative;
  transition: background .3s var(--ease-ui), border-color .3s var(--ease-ui);
}
.dh__dot:hover, .dh__dot.is-on { background: var(--ink); border-color: var(--ink); }
/* Struck through as well as faded — colour alone is not a signal every reader
   can use. */
.dh__dot.is-out { opacity: .4; }
.dh__dot.is-out::after {
  content: ""; position: absolute; left: -2px; right: -2px; top: 50%;
  height: 1px; background: var(--stone); transform: rotate(-45deg);
}

/* When a whole section is centred, its heading and its links centre with it. */
.centred { text-align: center; }
.centred.stack, .centred .stack { align-items: center; }
.centred .measure { margin-left: auto; margin-right: auto; }

/* ==========================================================================
   Families — the four sections on /collections.
   ========================================================================== */

.fam { max-width: var(--measure); margin: 0 auto; width: 100%; }
.fam + .fam { margin-top: calc(var(--gap-section) * 1.4); padding-top: var(--gap-section);
              border-top: 1px solid var(--hair); }
.fam__head {
  display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: end;
}
.fam__blurb { justify-self: end; text-align: right; max-width: 34ch; }
.fam__grid { margin-top: 32px; }
@media (max-width: 809.98px) {
  .fam__head { grid-template-columns: 1fr; }
  .fam__blurb { justify-self: start; text-align: left; }
}

/* ==========================================================================
   Motion. Transform and opacity only — anything else forces the browser to
   re-lay-out the page mid-animation and the frame rate collapses.
   ========================================================================== */

.reveal {
  opacity: 0;
  transform: translate3d(0, 24px, 0);
  transition: opacity .7s var(--ease) var(--reveal-delay, 0ms),
              transform .7s var(--ease) var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.reveal.is-in { opacity: 1; transform: none; }
/* Let go of the compositor layer once the element has arrived. */
.reveal.is-in { will-change: auto; }

/* The first headline, word by word. */
main h1 .word { display: inline-block; overflow: hidden; vertical-align: bottom; }
main h1 .word > span {
  display: inline-block;
  transform: translate3d(0, 105%, 0);
  transition: transform .85s var(--ease);
  transition-delay: calc(var(--w, 0) * 55ms);
}
main h1.is-in .word > span { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal, main h1 .word > span { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ==========================================================================
   Utilities. A short list on purpose.
   ========================================================================== */

.hide { display: none !important; }
.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.mt-xs { margin-top: 6px }  .mt-s { margin-top: 14px }
.mt-m  { margin-top: 28px } .mt-l { margin-top: 44px }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ---------------------------------------------------------------- the way in
   The entrance: the two marks held large in the middle of an empty screen,
   then travelling to their places in the header.

   Everything here describes the RESTING state only. The movement itself is
   done in JavaScript with the Web Animations API, on transforms alone, because
   the distance each mark travels depends on where the header happens to be at
   this window size and cannot be written down in advance.

   No rule in this block affects the page when the script does not run: the
   overlay element does not exist in the HTML, so a failed script leaves an
   ordinary working site rather than a blank screen. */

.intro {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: var(--paper);
  /* It answers a click, so it must RECEIVE one — and it swallows it rather
     than letting it fall on a link the visitor cannot see yet. The pointer
     says the screen is waiting for them. It is removed the moment the marks
     land, and nothing is in the way after that. */
  pointer-events: auto; cursor: pointer; user-select: none;
  opacity: 1;
}

.intro__stack {
  display: flex; flex-direction: column; align-items: center;
  /* The gap is what makes the pair read as one lockup rather than two pictures
     that happen to be near each other. */
  gap: clamp(14px, 2.2vw, 30px);
}

/* Each mark is moved on its own, so each is its own layer. `will-change` is
   the promise to the browser that this is about to move — without it the first
   frame of the flight is where a cheap phone drops the animation. */
.intro__mark {
  display: block; width: auto;
  will-change: transform, opacity;
  /* They arrive already visible, then fade UP over the first breath, so the
     screen is never a hard cut from nothing to a logo. */
  opacity: 0;
  transition: opacity .3s var(--ease-ui);
}
.intro.is-lit .intro__mark { opacity: 1; }
/* The seal first and the wordmark a breath later — the pair assembles rather
   than switching on. A tenth of a second is enough to read as sequence and too
   short to read as a wait. */
.intro.is-lit .intro__seal { transition-delay: 0s; }
.intro.is-lit .intro__word { transition-delay: .1s; }

/* While the marks are in flight the page beneath must not scroll away under
   them — the header they are aiming at would move mid-journey. */
html.intro-running, html.intro-running body { overflow: hidden; }

/* Somebody who has told their system that motion makes them unwell gets none
   of this. The script checks the same thing and does not even build the
   overlay; this is the second lock on the same door. */
@media (prefers-reduced-motion: reduce) {
  .intro { display: none; }
  html.intro-running, html.intro-running body { overflow: auto; }
}

/* ── The film hero ────────────────────────────────────────────────────────
   A full-bleed band that plays a film when Ops holds one and shows its
   poster photograph when it does not. Height is capped in viewport units so
   it never eats a phone screen whole, and object-fit keeps the frame filled
   at any ratio rather than letterboxing it against the paper. */
.hero-film { width: 100%; background: var(--wash); overflow: hidden; }
.hero-film__media {
  display: block;
  width: 100%;
  height: clamp(280px, 62vh, 620px);
  object-fit: cover;
  object-position: center;
}
@media (prefers-reduced-motion: reduce) {
  /* A looping film is motion nobody asked for. The poster still shows. */
  .hero-film__media { animation: none; }
}

/* A film with words burned into the frame cannot be cropped. The SIN piece
   carries its own titles, and the band above — a fixed height filled by
   object-fit: cover — would cut the top line off on any ordinary laptop.
   This variant takes the full width and keeps the film's own 16:9 instead of
   a height, so nothing is ever cut and there are no black bars at the sides.
   It makes a tall opening on a laptop. That is the trade, and for a page that
   opens on a film it is the right way round. */
.hero-film--full {
  background: #000;
}
.hero-film--full .hero-film__media {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* A frame that carries one wide picture where a grid of small ones used to
   be. 4:3 rather than square: a family portrait is wider than it is tall. */
.card__frame--wide { aspect-ratio: 4 / 3; }

/* ── Customise: the commission under the cursor ───────────────────────────
   Every commissioned piece is stacked in the same box. The one under the
   pointer is revealed through a circle clipped to it, so the figure appears
   painted where the hand is. --cz-x/--cz-y follow the pointer; --cz-r is the
   size of the spotlight.

   The layers are cut off their seamless and registered to one baseline, so
   they are transparent and they line up: `contain`, never `cover`, or the
   stage would crop the ears off a tall one and the registration — the whole
   trick — would be lost. The stage carries the ground instead. */
.cz__stage {
  position: relative;
  width: 100%;
  /* Wide, and it fills the band it sits in. It was 3/4 inside a 560px column,
     which made the one section whose subject is a monumental piece the
     smallest picture on the page. */
  aspect-ratio: 16 / 9;
  min-height: clamp(420px, 74vh, 900px);
  overflow: hidden;
  background: #fff;
  /* The OS arrow is hidden and the smoke IS the cursor — see .cz__smoke. */
  cursor: none;
  --cz-x: 50%; --cz-y: 50%;
  --cz-rx: 26%; --cz-ry: 34%;
}
@media (max-width: 809.98px) {
  .cz__stage { aspect-ratio: 4 / 5; min-height: 0; cursor: default; }
}
.cz__layer {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain; object-position: center bottom;
  opacity: 0;
}
/* A layer that has not been given its address yet stands in as its own
   average colour, so a fast sweep shows the piece arriving rather than a
   hole. The moment it has a src the tint is dropped — otherwise it would
   paint a coloured rectangle behind the cut-out figure for ever. */
.cz__layer:not([src]) { background: var(--cz-tint, var(--wash)); }
/* The base piece holds the whole frame; the chosen one is cut to the
   spotlight. Both sit still — only the clip moves. */
.cz__layer[data-n="0"] { opacity: 1; }

/* Smoke, not a spotlight.
 *
 * clip-path: circle() has one hard edge and it looked like a torch being shone
 * at the piece — a shape sitting ON the photograph rather than paint bleeding
 * INTO it. A mask does the same job with a gradient instead of a boundary, so
 * the commission fades in over about a third of its radius and there is no
 * line anywhere to see.
 *
 * The ellipse is taller than it is wide because the figure is, and it drifts
 * — cz-wobble moves the two radii against each other on different periods, so
 * the edge is never the same shape twice and never obviously an ellipse. The
 * SVG turbulence filter (#cz-smoke, inlined in the page) breaks the gradient's
 * remaining smoothness into something with grain in it.
 */
.cz__layer.is-on {
  opacity: 1;
  -webkit-mask-image: radial-gradient(
    ellipse var(--cz-rx) var(--cz-ry) at var(--cz-x) var(--cz-y),
    #000 0%, #000 38%, rgba(0,0,0,.86) 54%, rgba(0,0,0,.45) 72%,
    rgba(0,0,0,.14) 87%, transparent 100%);
          mask-image: radial-gradient(
    ellipse var(--cz-rx) var(--cz-ry) at var(--cz-x) var(--cz-y),
    #000 0%, #000 38%, rgba(0,0,0,.86) 54%, rgba(0,0,0,.45) 72%,
    rgba(0,0,0,.14) 87%, transparent 100%);
  filter: url(#cz-smoke);
  animation: cz-wobble 7s ease-in-out infinite;
}
@keyframes cz-wobble {
  0%   { --cz-rx: 26%; --cz-ry: 34%; }
  33%  { --cz-rx: 30%; --cz-ry: 30%; }
  66%  { --cz-rx: 24%; --cz-ry: 36%; }
  100% { --cz-rx: 26%; --cz-ry: 34%; }
}
@property --cz-rx { syntax: "<percentage>"; inherits: true; initial-value: 26%; }
@property --cz-ry { syntax: "<percentage>"; inherits: true; initial-value: 34%; }

.cz__layer[data-n="0"].is-on { -webkit-mask-image: none; mask-image: none; filter: none; animation: none; }
/* Away from the pointer the smoke opens to the whole frame, so the section
   reads as one clean photograph when nobody is touching it. */
.cz__stage:not(.is-live) .cz__layer.is-on {
  -webkit-mask-image: none; mask-image: none; filter: none; animation: none;
}

/* The pointer itself: a soft smudge of the piece's own colour, drawn by us
   because the OS arrow is a hard little triangle and the brief asked for
   neither round nor straight. It lags the true pointer slightly (customise.js
   eases it), which is what reads as weight. */
.cz__smoke {
  position: absolute; left: 0; top: 0;
  width: 190px; height: 190px;
  margin: -95px 0 0 -95px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 260ms var(--ease-ui);
  background: radial-gradient(circle at 50% 50%,
    rgba(255,255,255,.55) 0%, rgba(255,255,255,.22) 34%, rgba(255,255,255,0) 68%);
  filter: url(#cz-smoke) blur(6px);
  animation: cz-smudge 5.5s ease-in-out infinite;
  will-change: transform;
}
.cz__stage.is-live .cz__smoke { opacity: 1; }
@keyframes cz-smudge {
  0%   { border-radius: 58% 42% 46% 54% / 52% 48% 52% 48%; transform: rotate(0deg)   scale(1); }
  50%  { border-radius: 44% 56% 62% 38% / 43% 61% 39% 57%; transform: rotate(180deg) scale(1.12); }
  100% { border-radius: 58% 42% 46% 54% / 52% 48% 52% 48%; transform: rotate(360deg) scale(1); }
}

.cz__hint {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
  font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  color: var(--stone); background: var(--paper);
  padding: 6px 12px; pointer-events: none;
  transition: opacity .3s var(--ease);
}
.cz__stage.is-live .cz__hint { opacity: 0; }

/* The name of whatever is under the hand, in the studio's red, sitting ON the
   piece rather than under it — at this size there is room, and a caption
   below a full-bleed picture is a caption nobody reads. */
.cz__name {
  position: absolute; left: 50%; bottom: 8%;
  transform: translateX(-50%);
  margin: 0; text-align: center;
  color: var(--red);
  pointer-events: none;
  text-wrap: balance;
  transition: opacity 200ms var(--ease-ui);
}
.cz { position: relative; }
@media (max-width: 809.98px) {
  .cz__name { position: static; transform: none; margin-top: 14px; }
}

.cz__swatches { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 14px; }
.cz__swatch {
  width: 42px; height: 58px; padding: 0;
  border: 1px solid var(--hairline);
  /* `contain`: a swatch is a small portrait of the piece, not a crop of its
     belly. The average colour sits behind it until the thumbnail decodes. */
  background-size: contain; background-position: center bottom;
  background-repeat: no-repeat;
  cursor: pointer; opacity: .55;
  transition: opacity .2s var(--ease), border-color .2s var(--ease);
}
.cz__swatch:hover, .cz__swatch:focus-visible, .cz__swatch.is-on { opacity: 1; border-color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  /* No spotlight and no drift: the swatches below are the whole interface. */
  .cz__layer.is-on { clip-path: none !important; transition: none; }
  .cz__stage { cursor: default; }
}


/* ==========================================================================
   The press.

   Every rule below exists for one reason: the answer to a touch must arrive
   on the way DOWN. A button that waits for the finger to lift before it
   acknowledges anything is a button that feels broken on a slow connection
   and merely dead on a fast one — the lag is the whole difference between an
   interface that responds and one that reports.

   The movement is small on purpose. 3% is enough to read as give; more and a
   button starts behaving like a toy, which is the wrong register for a shop
   selling objects made by hand.
   ========================================================================== */

.btn, .card, .cf__nav, .cz__swatch, .nav__link, .foot__link, .reel__card {
  /* No 300ms wait while the browser decides whether this was a double-tap,
     and no blue rectangle over feedback we are already giving ourselves. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.card, .cf__nav, .cz__swatch {
  transition: transform 100ms ease-out, opacity .25s var(--ease-ui);
}
.nav__link, .foot__link { transition: opacity .3s var(--ease-ui), color .3s var(--ease-ui); }

.btn:active:not([disabled]) { transform: scale(.97); }
.cf__nav:active,
.cz__swatch:active        { transform: scale(.94); }
.card:active              { transform: scale(.985); }
.nav__link:active,
.foot__link:active        { opacity: .45; }

/* ==========================================================================
   When the reader has asked for less.

   Reduced motion is not no feedback — it is feedback that does not travel.
   The press still answers; it answers in opacity instead of movement.
   Reduced transparency and raised contrast both trade the glass for paper,
   because a material nobody can read through is only a legibility problem.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .btn, .card, .cf__nav, .cz__swatch { transition: none; }
  .btn:active:not([disabled]),
  .cf__nav:active, .cz__swatch:active, .card:active { transform: none; opacity: .7; }
  .nav::after { transition: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .nav {
    background: var(--paper);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}

@media (prefers-contrast: more) {
  .nav {
    background: var(--paper);
    -webkit-backdrop-filter: none; backdrop-filter: none;
    border-bottom: 1px solid var(--ink);
  }
  .nav::after { display: none; }
}

/* ── Membership ───────────────────────────────────────────────────────────
   A ladder of five, read top to bottom on a phone and across on a desk. The
   cards are deliberately plain: the tier is a threshold and a list of what it
   carries, and any ornament here would read as the shop working hard to make a
   discount sound like an honour.

   `is-yours` is the only visual state, and it is a hairline and a flag rather
   than a colour flood — the reader needs to find their own rung at a glance
   without the other four being greyed into irrelevance. */

.mem-ladder {
  display: grid;
  gap: 1px;
  background: var(--hair);
  border-block: 1px solid var(--hair);
}
@media (min-width: 900px) {
  .mem-ladder { grid-template-columns: repeat(5, 1fr); }
}

.mem-tier {
  background: var(--paper);
  padding: 28px 22px 32px;
  display: flex;
  flex-direction: column;
}
.mem-tier.is-yours { background: #fff; }

.mem-tier__you { margin: 0 0 10px; }

.mem-tier__head { display: flex; align-items: baseline; gap: 10px; }
.mem-tier__dot {
  width: 8px; height: 8px; border-radius: 50%;
  flex: 0 0 auto;
  /* A dot on its own is decoration; against the hairline it is a key back to
     the guest's own row in the account page and in Ops. */
  box-shadow: 0 0 0 1px rgba(22, 21, 15, .18);
}
.mem-tier__name { margin: 0; font-size: clamp(20px, 2.2vw, 26px); }
.mem-tier__unlock { margin: 10px 0 0; color: var(--ink); }

.mem-perks { list-style: none; margin: 0; padding: 0; }
.mem-perk {
  padding: 10px 0;
  border-top: 1px solid var(--hair);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.mem-perk:first-child { border-top: 0; }
.mem-perk__label { font-size: 14px; }
.mem-perk__detail { font-size: 13px; color: var(--stone); }

/* The reader's own progress. One bar, no numbers inside it — the figures are
   written underneath in words, where they can be read aloud. */
.mem-bar {
  position: relative;
  height: 2px;
  width: min(560px, 100%);
  background: var(--hair);
  overflow: hidden;
}
.mem-bar__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: var(--ink);
  transition: width 900ms cubic-bezier(.22, 1, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .mem-bar__fill { transition: none; }
}

.mem-notes {
  display: grid;
  gap: 22px 40px;
}
@media (min-width: 720px) {
  .mem-notes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.mem-notes p { max-width: 46ch; margin: 0; }

/* The standing strip on /account. Rules above and below rather than a card:
   it belongs to the page it sits in, and a boxed panel here would read as an
   advertisement inside somebody's own records. */
.acct-mem {
  padding: 18px 0;
  border-block: 1px solid var(--hair);
}
