/* Shell components: the cart button, and the drawer it opens -- chrome,
 * line items, quantities, subtotal and the route to checkout (Task 5).
 *
 * RULING R2: every custom property here is --<token> (--gold, --board, ...),
 * matching what render_tokens_css actually emits -- never --c-<token>.
 *
 * RETONE 2026-08-31 (spec 2026-08-31-storefront-rebuild-design.md, S13/S14,
 * 4A.4): the cart button lives in the header, which keeps --board (it reads
 * as the book's own spine). The drawer itself sits on --band -- the ONE
 * warmer tinted band the light system uses to separate a card from the
 * cream page (`--page`) it floats over, replacing the old --surface-1 dark
 * step. --gold-text is now legal on --board ONLY (10-11:1); on --band it
 * measures well under AA, so every gold label/link in the drawer uses
 * --gold-label instead (>=4.5:1 on every light ground --
 * tests/test_art_direction.py). Reading text in the drawer is
 * --ink/--ink-dim, the light-ground pair -- not --text/--text-dim, which is
 * now reserved for the one dark band (--board) only. RULING R20 (fix round
 * 1, still true): --body/--body-dim are the covers' own muted chrome tone,
 * exposed in tokens() only because tokens() mirrors pipeline/cover.py, and
 * stay banned from this stylesheet
 * (test_site_stylesheets_never_reach_for_the_covers_chrome_tones).
 *   - Gold never sets body text. A line item's name, meta and quantity are
 *     body text, so they are --ink (dark ink), not --gold-label. Gold
 *     stays on the title, the subtotal's overline label, and hairlines
 *     only.
 *   - Surfaces separate with a hairline rule (--rule/--rule-strong, both
 *     built from --gold-text via color-mix in tokens.base.css), never a
 *     shadow -- a shadow reads muddy either way.
 *   - Any transition here uses the mandated two-value motion budget
 *     (spec 4A.3/4A.8): --dur-color/--ease-color for colour, --dur-
 *     transform/--ease-transform for transform. Nothing over 400ms, and
 *     never the banned overshoot curve. prefers-reduced-motion drops every
 *     transition to instant rather than merely disabling a named
 *     "animation".
 *   - The drawer itself does not slide or fade in: an instant, hard-cut
 *     open/close is the deliberate choice, not an oversight -- S4A.3
 *     names "near-zero motion as a feature" (the NOMOS precedent) as
 *     exactly the right register for a purchase surface on scripture, and
 *     an accessible slide-in would need to defeat the `hidden` attribute's
 *     display:none, which is also what removes the closed drawer from the
 *     accessibility tree correctly. Not worth the trade for one drawer.
 *   - No hex/rgba literal below for a PRODUCT colour: every colour is one
 *     of the tokens render_tokens_css emits (composed with color-mix where
 *     an alpha step is needed), or `transparent`. The scrim's
 *     rgba(0, 0, 0, 0.4) is a universal dimmer, not a brand colour, and
 *     stays literal for that reason.
 */

/* FIX ROUND 2, CRITICAL 3 (2026-08-31): --gold-text is proven legible on
 * --board only (10-11:1; it FAILS AA on every light ground, ~1.65:1 on
 * --page -- test_gold_text_fails_on_the_light_grounds_so_gold_label_is_
 * not_decorative). This button sits in the header, which used to be the
 * one dark band and now sits on --page -- ink is the correct pair. */
.cart-button {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--ink) 35%, transparent);
  border-radius: var(--r-md);
  color: var(--ink);
  font-family: var(--t-ui);
  padding: var(--s-2) var(--s-3);
  cursor: pointer;
}
.cart-button:hover { border-color: var(--ink); }
.cart-button { transition: border-color var(--dur-color) var(--ease-color); }

.cart-button__icon {
  width: 1rem;
  height: 1rem;
  display: inline-block;
  border: 1.5px solid currentColor;
  border-radius: var(--r-sm);
}

.cart-button__count { font-size: var(--t-ui-sm); min-width: 1ch; text-align: center; }

/* ---- drawer -------------------------------------------------------------- */
.cart-drawer {
  position: fixed;
  inset-block: 0;
  right: 0;
  width: min(100%, 22rem);
  background: var(--band);
  color: var(--ink);
  --focus-color: var(--gold-label);
  padding: var(--s-6);
  overflow-y: auto;
  z-index: 60;
  /* Hairline, not a shadow -- shadows read muddy on a dark ground (S4A.4). */
  border-left: 1px solid var(--rule-strong);
  display: flex;
  flex-direction: column;
}
.cart-drawer[hidden] { display: none; }

.cart-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s-4);
}
.cart-drawer__title {
  font-family: var(--t-display);
  color: var(--gold-label);
  font-size: var(--t-d-title);
  letter-spacing: var(--track-display);
  margin: 0;
}
.cart-drawer__close {
  background: none;
  border: none;
  color: var(--gold-label);
  font-size: var(--t-d-title);
  line-height: 1;
  cursor: pointer;
  padding: var(--s-1);
}

.cart-drawer__items {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cart-drawer__empty {
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--ink-dim);
}

/* ---- line items ----------------------------------------------------------
 * Name, meta and running total are body text -- cream (--text), never gold
 * (S4A.5). A hairline (not a shadow) separates one line from the next.
 */
.cart-drawer__item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "info remove" "qty total";
  column-gap: var(--s-3);
  row-gap: var(--s-2);
  padding-block: var(--s-4);
  border-top: 1px solid var(--rule);
}
.cart-drawer__items > .cart-drawer__item:first-child { border-top: none; }

.cart-drawer__item-info { grid-area: info; align-self: start; }
.cart-drawer__item-name {
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--ink);
  margin: 0;
}
.cart-drawer__item-meta {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--ink-dim);
  margin: var(--s-1) 0 0;
}

.cart-drawer__item-total {
  grid-area: total;
  justify-self: end;
  align-self: end;
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--ink);
  margin: 0;
  white-space: nowrap;
}

/* Quantity stepper: two buttons either side of the count, sized for a
 * thumb (44px is the accepted minimum touch target) rather than a bare
 * number input, which is fiddly to hit and to type into on a phone. */
.cart-drawer__item-qty {
  grid-area: qty;
  align-self: end;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.cart-drawer__qty-btn {
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--ink) 35%, transparent);
  border-radius: var(--r-md);
  color: var(--ink);
  font-family: var(--t-ui);
  font-size: var(--t-ui-md);
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur-color) var(--ease-color);
}
.cart-drawer__qty-btn:hover:not(:disabled) { border-color: var(--gold-label); color: var(--gold-label); }
.cart-drawer__qty-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.cart-drawer__qty-value {
  min-width: 2ch;
  text-align: center;
  color: var(--ink);
  font-size: var(--t-ui-sm);
}

.cart-drawer__item-remove {
  grid-area: remove;
  justify-self: end;
  align-self: start;
  background: none;
  border: none;
  color: var(--ink-dim);
  font-size: var(--t-ui-lg);
  line-height: 1;
  cursor: pointer;
  padding: var(--s-1);
  transition: color var(--dur-color) var(--ease-color);
}
.cart-drawer__item-remove:hover { color: var(--gold-label); }

/* ---- subtotal and checkout ------------------------------------------------
 * "Subtotal" is a two-word overline label, not body copy -- S4A.6 names
 * overlines/small caps as the one place small gold is allowed. The amount
 * itself is cream, tabular, same as every line total above it.
 */
.cart-drawer__foot {
  border-top: 1px solid var(--rule-strong);
  padding-top: var(--s-4);
  /* auto, not a fixed value: pins the subtotal/checkout to the bottom of
     the flex column when there is leftover space below the line items,
     without forcing the (possibly empty) items list itself to stretch. */
  margin-top: auto;
}
.cart-drawer__foot[hidden] { display: none; }

.cart-drawer__subtotal {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 var(--s-4);
}
.cart-drawer__subtotal-label {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--gold-label);
  text-transform: uppercase;
  letter-spacing: var(--track-overline);
}
.cart-drawer__subtotal .num { font-size: var(--t-ui-lg); color: var(--ink); }

.cart-drawer__checkout {
  display: block;
  text-align: center;
  background: var(--cta);
  border: 1px solid var(--cta);
  border-radius: var(--r-md);
  color: var(--page);
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  letter-spacing: 0.02em;
  text-decoration: none;
  padding: var(--s-3) var(--s-4);
  transition: background-color var(--dur-color) var(--ease-color);
}
.cart-drawer__checkout:hover {
  background: color-mix(in srgb, var(--cta) 85%, var(--ink-dim) 15%);
}

/* FIX ROUND 2, CRITICAL 1 (2026-08-31) -- the NOMOS mid-tone graft this
 * comment used to describe is OVERRIDDEN: measured on this site's own warm
 * cream, `#D9D2C0` on `#f5efe2` is too small a luminance step (0.87->0.67)
 * to read as a primary action -- it read as a disabled control. The
 * primary CTA is now a SOLID DARK WARM INK fill (`--cta` = `cover.INK`,
 * see the tokens() comment in web/context.py) with a REVERSED cream label
 * (`--page`) -- Aesop's own answer to the same problem (`#333333` on
 * cream, 12.46:1, "unambiguous"). Measured here at 13.44:1
 * (--page on --cta, test_the_primary_cta_label_clears_aa_on_the_cta_
 * surface). `--cta` stays strictly lighter than `--board` (#000000) so
 * the book cover still wins the darkest pixel on the page by construction
 * (4A.1) -- it is just no longer SO much lighter that a primary action
 * disappears into the page. Renamed from .btn--gold-solid, which this
 * treatment never was. --gold-ink stays in tokens() (context.py) for a
 * future solid-gold fill on the one dark band; unused here. */
.btn--primary {
  display: inline-block;
  text-align: center;
  background: var(--cta);
  border: 1px solid var(--cta);
  border-radius: var(--r-md);
  color: var(--page);
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  letter-spacing: 0.02em;
  text-decoration: none;
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  transition: background-color var(--dur-color) var(--ease-color);
}
.btn--primary:hover { background: color-mix(in srgb, var(--cta) 85%, var(--ink-dim) 15%); }

.cart-drawer__scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 50;
}
.cart-drawer__scrim[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cart-button,
  .cart-drawer__qty-btn,
  .cart-drawer__item-remove,
  .cart-drawer__checkout,
  .btn--primary {
    transition: none;
  }
}

/* ===========================================================================
 * THE PRODUCT PAGE (Task 6, gauntlet) -- and the signature-resolve
 * component (spec 4A.2), which lives HERE rather than in a page-specific
 * file because Task 8 reuses it verbatim on the home hero (ruling R22):
 * this stylesheet already loads on every page via _base.html.j2, so a
 * second template can include the same markup/JS and get the same rules
 * with no new <link>.
 *
 * RETONE 2026-08-31 ground rules (R20/4A.4/4A.5), restated for whoever
 * edits this section next: reading text on the light page/band is
 * --ink/--ink-dim ONLY (never --body/--body-dim, never --text/--text-dim,
 * which are now reserved for the one dark band); gold labels/links on a
 * light ground are --gold-label, never --gold-text (legal on --board
 * only) or the raw --gold; gold never sets a paragraph, list item or
 * definition; separation is a hairline (--rule/--rule-strong), never a
 * drop shadow; every transition uses the two-value motion budget
 * (--dur-color/--ease-color, --dur-transform/--ease-transform), never a
 * literal ms/s value or the banned overshoot curve.
 * ========================================================================= */

/* ---- generic button + overline primitives -------------------------------- */

.btn {
  cursor: pointer;
  border-radius: var(--r-md);
  font-family: var(--t-ui);
}

.btn--outline {
  display: inline-block;
  text-align: center;
  background: transparent;
  border: 1px solid var(--gold-label);
  color: var(--gold-label);
  font-size: var(--t-ui-sm);
  letter-spacing: 0.02em;
  text-decoration: none;
  padding: var(--s-3) var(--s-4);
  transition: background-color var(--dur-color) var(--ease-color);
}
.btn--outline:hover {
  background: color-mix(in srgb, var(--gold-label) 12%, transparent);
}

/* Small-caps section labels -- the one place small gold is allowed
 * (4A.5/4A.6). Never on a heading that is also display type. --gold-label,
 * not --gold-text: every eyebrow in this system sits on a light ground
 * (page or band), and --gold-text is legal on --board only. */
.eyebrow {
  display: block;
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--gold-label);
  text-transform: uppercase;
  letter-spacing: var(--track-overline);
  margin: 0 0 var(--s-4);
}

/* ===========================================================================
 * THE OBJECT, TURNED -- the product page's own live book (Task 5, 2026-09-08
 * batch). AMENDED direction contract: the hero is the home surface's own
 * book3d.js mesh, mounted per volume, not four cross-faded stills -- so this
 * is markup+motion glue around a <canvas>, not a gallery.
 *
 * FIRST VIEWPORT: the book fills the stage, closed, cover to camera. Name
 * and number left, price and the one gold control right, both above the
 * fold. `.book-hero__pin` sits on `--page` -- this site's own dark room
 * ground since the 2026-09-07 retone (not a light "PDP" surface) -- so the
 * object keeps the darkest, warmest pixel on the page by construction,
 * exactly as the home hero does.
 *
 * TRAVEL: 400svh, more than the home hero's one-band 230svh, because here
 * the hero IS the whole page and four stages of copy (closed/turning/
 * opening/open) each want room to be read rather than flashing past.
 * `.book-hero__pin` is the sticky child that actually holds the object
 * still while that height scrolls underneath it -- product-book.js reads
 * `.book-hero`'s own (tall) bounding box for scroll progress, exactly as
 * vitrine.js reads `#scene` rather than `#stage`.
 * ========================================================================= */
.book-hero {
  position: relative;
  /* 200svh, was 400. Bilal, 2026-09-08: "i want the scroll to open to be
   * shorter on every page." Four screens of travel to open one cover made
   * the object feel stuck; the home page's own section came down from 230svh
   * to 150svh in the same pass. Two screens leaves the settle-then-open
   * sequence its beats without holding the visitor in one section. */
  height: 200svh;
  background: var(--page);
}
.book-hero__pin {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: center;
  gap: var(--s-12);
  padding-inline: var(--s-8);
  background: var(--page);
  overflow: hidden;
}
.book-hero__stage {
  position: relative;
  height: 100%;
  display: grid;
  place-items: center;
}
/* THE SAME OBJECT AT THE SAME SIZE AS THE HOME PAGE, AND UNDER THE SAME LIGHT.
 *
 * Bilal, 2026-09-08, on the first served build: "it doesn't feel like the
 * scroll on the home page. it's bigger, without the golden light behind it."
 * Both were true and both were measured: the canvas here laid out at 764x900
 * against the home page's 540x676, and vitrine.html.j2's `.stage::before`
 * -- the warm pool the object sits in -- had no counterpart on this surface.
 *
 * `--book-w` and the 0.8 ratio are the home page's own numbers. They are
 * repeated rather than shared because the vitrine template is deliberately
 * standalone and loads none of this stylesheet (DESIGN.md records that split
 * and that the two will drift until the commerce layer folds into the vitrine
 * shell). tests/test_site_turn.py asserts the two stay equal so the drift is
 * caught rather than discovered. */
.book-hero__stage canvas,
.book-hero__poster {
  position: relative;   /* so it can sit above the glow -- see ::before below */
  z-index: 1;
  width: min(var(--book-w, 560px), 100%);
  height: auto;
  aspect-ratio: 0.8;
}
.book-hero__stage::before {
  content: "";
  position: absolute;
  /* BEHIND the object, not over it. An absolutely-positioned pseudo-element
   * paints above non-positioned in-flow siblings, so without this the warm
   * pool sat IN FRONT of the book and tinted the board gold -- reported by
   * Bilal on the served build, and only on these pages: the home page's own
   * glow escapes it because the book there lives in `.book3d`, which is
   * `position:relative`, so it wins on DOM order instead. Making the stacking
   * explicit on both layers is what stops that being an accident of markup. */
  z-index: 0;
  left: 8%;
  top: 6%;
  width: 52%;
  height: 88%;
  background: radial-gradient(ellipse at 42% 34%,
    rgba(196, 162, 76, .16), rgba(196, 162, 76, .05) 42%, transparent 68%);
  filter: blur(28px);
  pointer-events: none;
}
/* `[hidden]` is a UA rule and an author `display` declaration on either
 * child would outrank it -- see the identical note on `.viewport [hidden]`
 * in vitrine.html.j2, the same contract reused here: the canvas ships
 * `hidden` and product-book.js reveals it only when supportsRealtime()
 * says so, so no-WebGL, reduced motion, JS disabled or a throw during setup
 * all show the poster, never a blank box. */
.book-hero [hidden] { display: none !important; }
/* Sizing lives in the `--book-w` rule below, which is the home page's own
 * box. This one only makes the canvas a block; it used to also set
 * width/height 100% and, sitting later in the file, silently beat the
 * sizing rule on source order. */
.book-hero__stage canvas { display: block; }
.book-hero__poster {
  display: block;
  max-width: 100%;
  max-height: 80svh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--r-md);
}
.book-hero__margin {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  max-width: var(--w-text);
  color: var(--ink);
}
.book-hero__title { color: var(--ink); margin: 0 0 var(--s-4); }
/* Every stage's note lives in the SAME grid cell (annotation, never card --
 * the direction contract's own component language): only opacity says
 * which is current, so the column's height -- and therefore the price and
 * CTA below it -- never jumps as the book turns. */
.book-hero__notes {
  display: grid;
  margin-bottom: var(--s-4);
}
.book-hero__note {
  grid-area: 1 / 1;
  opacity: 0;
  /* The two-value motion budget, not a literal -- see this file's own
     rule against literal ms values, and test_art_direction's 400ms ceiling. */
  transition: opacity var(--dur-color) var(--ease-color);
}
[data-book-hero][data-stage="closed"] [data-annotation="closed"],
[data-book-hero][data-stage="turning"] [data-annotation="turning"],
[data-book-hero][data-stage="opening"] [data-annotation="opening"],
[data-book-hero][data-stage="open"] [data-annotation="open"] {
  opacity: 1;
}
.book-hero__overline {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--gold-label);
  text-transform: uppercase;
  letter-spacing: var(--track-overline);
  margin: 0 0 var(--s-1);
}
.book-hero__line { color: var(--ink); margin: 0; font-family: var(--t-body); }
.book-hero__detail { color: var(--ink-dim); margin: var(--s-1) 0 0; font-size: var(--t-ui-sm); }
.book-hero__price { font-size: var(--t-d-title); color: var(--ink); margin: 0 0 var(--s-2); }
.book-hero__cta { align-self: flex-start; }
.book-hero__exit { color: var(--ink-dim); font-size: var(--t-ui-xs); margin: var(--s-1) 0 0; }

@media (max-width: 900px) {
  .book-hero__pin {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(0, 1fr) auto;
    padding-inline: var(--s-4);
    padding-block: var(--s-6);
  }
  .book-hero__poster,
  .book-hero__stage canvas { max-height: 46svh; }
}

@media (prefers-reduced-motion: reduce) {
  .book-hero__note { transition: none; }
}

/* ---- THE READING TAIL -----------------------------------------------------
 * What a buyer still needs after the object has turned open. The critique's
 * finding against the old page was FIVE CONSECUTIVE text-only bands, not
 * text as such -- so this stays at three sections, one of them a second
 * visual (the signature-resolve demo), not five. */
.tail { padding-block: var(--s-12); }
.tail__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-12);
  align-items: center;
}
.tail__grid--reverse { grid-template-columns: 1fr 1fr; }
.tail__grid--reverse .tail__copy { order: 1; }
.tail__copy p { color: var(--ink-dim); max-width: var(--w-text); margin: 0 0 var(--s-4); }
.tail__copy p:last-child { margin-bottom: 0; }
.tail__figure { margin: 0; }
.tail__figure figcaption {
  margin-top: var(--s-2);
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--ink-dim);
}
.tail__faq-more { margin: var(--s-4) 0 0; }
.tail--series .tail__series-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  flex-wrap: wrap;
}
.tail--series .tail__series-row p { color: var(--ink-dim); margin: var(--s-2) 0 0; }

@media (max-width: 900px) {
  .tail__grid,
  .tail__grid--reverse { grid-template-columns: 1fr; gap: var(--s-6); }
  .tail__grid--reverse .tail__copy { order: 0; }
}

/* ---- shared prose/answer primitives -------------------------------------
 * Renamed from their old `pdp-*` names (2026-09-08 batch: the buybox and
 * gallery partials that coined those names are gone, but other pages were
 * quietly depending on the same rules -- CLAUDE.md rule 8, re-derive before
 * deleting). A concurrent round moved about/faq/returns/terms onto their
 * own `_document_style.html.j2`/`document.css`, so today `.prose` survives
 * on thanks.html.j2 and `.page-subtitle` has no current template user;
 * `.faq__list`/`.faq__item` are used by the product page's own reading
 * tail (Task 10) regardless of what the four Read pages do with them. */

.page-subtitle {
  font-family: var(--t-body);
  color: var(--ink-dim);
  max-width: var(--w-text);
  margin: 0 0 var(--s-4);
}
/* Task 9: the FAQ page's subtitle carries a link to Delivery & returns --
   the first use of a link inside this element. */
.page-subtitle a { color: var(--gold-label); }

/* Trust row (cart page only, now the sole survivor of a wider PDP trust
 * row that used to sit under the buy button: secure checkout,
 * dispatch/delivery, refund -- the three things a buyer checks before they
 * trust a stranger's checkout). */
.trust-row {
  list-style: none;
  margin: 0 0 var(--s-4);
  padding: var(--s-3) 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
/* Fix round 1, IMPORTANT: these used to carry a hollow-ring icon (border,
   no fill) -- on a row of guarantees ("Secure checkout", "14-day refund")
   that reads as an unchecked checklist, which works against the row's
   whole purpose on a page convincing a stranger to trust an unfamiliar
   seller. Replaced with a middot separator between items -- neutral
   punctuation, never a box. */
.trust-row__item {
  display: flex;
  align-items: center;
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--ink-dim);
}
/* Named with an explicit class rather than a bare list-row element
   selector: the art-direction gate (test_no_stylesheet_sets_body_copy_in_gold)
   flags a gold colour set on that element type on the assumption it is
   reading text -- correctly, everywhere else in this file. Here it is
   punctuation (a middot separator), not body copy, so the fix is a
   selector the gate can tell apart, not a weakened gate. */
.trust-row__item:not(:first-child)::before {
  content: "\00B7";
  color: var(--gold-label);
  margin-right: var(--s-2);
  font-size: var(--t-ui-md);
  line-height: 1;
}

/* FIX ROUND 2, CRITICAL 2 (2026-08-31): this class used to carry
 * `max-width: var(--w-text)` itself -- and every template pairs it directly
 * on the SAME element as `.inner` (`<div class="inner prose">`,
 * product/about/faq/returns/terms/thanks), so the measure cap was
 * overriding `.inner`'s own 1200px container width, not narrowing prose
 * inside it. The section collapsed to a 440px column and `.inner`'s own
 * `margin-inline: auto` then CENTRED that narrow column in the full
 * viewport -- exactly the "content hugs a narrow strip, half the page is
 * blank" defect (spec 4A.3: container is 1200px, the measure cap belongs
 * to paragraphs, not sections). Fix: this class no longer sets a width at
 * all -- the div stays at `.inner`'s full 1200px -- and the cap moves to
 * the `p` selector below, where it always belonged. */
.prose p {
  max-width: var(--w-text);
  color: var(--ink);
  margin: 0 0 var(--s-4);
}
.prose p:last-child { margin-bottom: 0; }
/* Task 9: the about/returns/terms/faq pages are the first to put a link
   INSIDE this prose block (a mailto:, or a link to another policy page).
   Unstyled it falls back to the browser's default blue -- gold is the
   inline-link colour everywhere else in this system (.faq__more a,
   .tail__faq-more a, .site-footer nav a). */
.prose a { color: var(--gold-label); }

.faq__list { margin: 0; max-width: var(--w-text); }
.faq__item {
  padding-block: var(--s-4);
  border-top: 1px solid var(--rule);
}
.faq__item:first-child { border-top: none; padding-top: 0; }
.faq__item dt {
  font-family: var(--t-ui);
  font-size: var(--t-ui-md);
  color: var(--ink);
  margin-bottom: var(--s-2);
}
.faq__item dd {
  margin: 0;
  color: var(--ink-dim);
  font-family: var(--t-body);
}
.faq__more a,
.tail__faq-more a { color: var(--gold-label); }

/* ---- THE SIGNATURE MOMENT (spec 4A.2, R21/R22) ---------------------------
 * Grey resolves to ink, driven by assets/signature.js setting --resolve
 * (0..1) on [data-resolve] as it crosses the viewport once, forward only.
 *
 * FIX ROUND 2, CRITICAL 2 -- this used to be two mix-blend-mode:color washes
 * (--trace fading out, --ink fading in) over one desaturated image. Measured
 * with Playwright at --resolve:0 and :1 on the real render, that technique
 * cannot do what the copy beside it promises ("watch the grey settle into
 * ink"): mix-blend-mode:color PRESERVES THE BACKDROP OWN LUMINANCE and only
 * swaps in the wash's hue/saturation, so a light-grey glyph (the trace tint
 * measures ~190/255) stayed just as light at --resolve:1 -- it only took on
 * --ink's brown HUE, which reads as a warm gold/tan, not ink. That is the
 * exact thing R21 already corrected once ("grey into gold... depicts
 * something the product does not do"): the old implementation reintroduced
 * the same defect one layer down, invisible to every gate because nothing
 * checked "is the resolved frame actually darker" (CLAUDE.md rule 7/8).
 *
 * Fix: two copies of the SAME image, cross-faded by opacity (which DOES
 * change with no luminance floor). .resolve__from is the plain grey trace
 * render. .resolve__to is the identical image run through an SVG gamma
 * transfer (#resolve-ink-curve, in the macro markup) that maps the trace
 * grey (~0.75 in 0..1) down toward the real --ink token's own luminance
 * (~0.13) while leaving true white and true black fixed points unmoved --
 * so paper stays paper and the page's black furniture stays black, and only
 * the traced glyphs actually darken. tests/test_site_product.py samples
 * pixel luminance at --resolve:0 and :1 and asserts it drops.
 *
 * Reduced motion is handled upstream, in tokens.base.css, which forces
 * --resolve: 1 on [data-resolve] -- this file never overrides that value,
 * only reads it (.resolve__to's opacity IS --resolve, so reduced motion
 * renders already-inked with no JS involved). */
.resolve {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-md);
  isolation: isolate;
}
.resolve__from,
.resolve__to {
  display: block;
  width: 100%;
  height: auto;
}
.resolve__from {
  filter: grayscale(1) contrast(1.05);
}
.resolve__to {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) url(#resolve-ink-curve) sepia(0.5) contrast(1.05) saturate(1.3);
  opacity: var(--resolve, 0);
  transition: opacity var(--dur-color) var(--ease-color);
}

@media (max-width: 640px) {
  .eyebrow { margin-bottom: var(--s-2); }
  /* RETONE 2026-08-31: title and price mobile overrides removed -- both
   * now render at --t-d-title (24px) on every width, since the Cinzel
   * scale collapsed to two sizes total (spec 4A.3) and 24px was already
   * the smaller of the two. A same-value override is dead weight. */
  .page-subtitle { font-size: var(--t-ui-sm); margin-bottom: var(--s-2); }
  .trust-row { gap: var(--s-1) var(--s-3); padding-block: var(--s-2); }
}

@media (prefers-reduced-motion: reduce) {
  .btn--outline,
  .resolve__to {
    transition: none;
  }
}
/* ===========================================================================
 * THE SERIES PAGE (Task 7, gauntlet) -- judged on scorecard dimensions 3
 * (a stranger who does not know what a juz is finds a gift for an
 * eight-year-old beginner in under three clicks) and 9 (five seconds: one
 * product line in many editions, not 31 unrelated products). No researched
 * competitor solves either.
 *
 * DENSITY AS A HIERARCHY DEVICE (4A.7): the hero and entry-points bands are
 * OPEN -- they carry the "one series" statement and the need-based
 * navigation, and both need room to be read in five seconds. The 30-tile
 * grid below is deliberately DENSE -- a tight, ordered shelf, the register
 * that suits scripture, not a shop trying to look spacious over 30
 * identical "In production" tiles.
 *
 * Ground rules restated (R20/4A.4/4A.5, same as the rest of this file):
 * reading text on the light page/band is --ink/--ink-dim only, gold labels
 * on a light ground are --gold-label (never --gold-text, legal on --board
 * only), gold never sets a paragraph or list item, separation is a
 * hairline never a shadow, every transition uses the two-value motion
 * budget (--dur-color/--ease-color, --dur-transform/--ease-transform),
 * never a literal ms/s value or the banned overshoot curve.
 * ========================================================================= */

.series-hero { padding-top: var(--s-8); }
.series-hero__lede {
  max-width: var(--w-text);
  font-family: var(--t-body);
  color: var(--ink-dim);
  margin: 0;
}

/* ---- entry points: navigate by WHO IT IS FOR, above the numbered grid,
 * never instead of it (spec 3.3). This is the ground no researched
 * competitor has taken -- given the open, generous treatment, not folded
 * into the dense grid below. */
/* FIX ROUND 2, CRITICAL 2 (2026-08-31): `repeat(auto-fit, minmax(15rem,
 * 1fr))` is the classic auto-fit trap with a FIXED, small item count (this
 * list is always exactly two entry points, context.py's `entry_points`).
 * Auto-fit computes how many 15rem tracks the 1170px container could hold
 * (four), lays the free space out across all four, and only THEN collapses
 * the two empty ones -- taking their share of the free space with them.
 * The two real cards end up sized for a four-up grid, leaving roughly half
 * the container blank on the right (the exact defect the fix brief names
 * for /series/). Flexbox has no such trap: two items with flex-grow share
 * 100% of the row, full stop, and still wrap to one column under their own
 * basis on narrow viewports with no extra media query needed. */
.entry-points__grid {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
}
.entry-points__grid .entry-point { flex: 1 1 15rem; }
.entry-point {
  background: var(--band);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.entry-point__label {
  font-family: var(--t-ui);
  font-size: var(--t-ui-lg);
  color: var(--ink);
  margin: 0;
}
.entry-point__blurb {
  font-family: var(--t-body);
  color: var(--ink-dim);
  margin: 0;
  flex: 1 1 auto;
}
.entry-point__cta { align-self: flex-start; }

/* ---- the 30-volume grid: a shelf, not a showroom ------------------------- */

.series-grid-band__note {
  max-width: var(--w-text);
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--ink-dim);
  margin: 0 0 var(--s-6);
}

.series-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
  gap: var(--s-3);
}

.series-tile {
  /* RETONE 2026-08-31 (task 4 of the retone -- "check that and simplify if
   * the mount is now redundant"): FIX ROUND 1 (superseded) gave every tile
   * a --surface-2 MOUNT because a --board (#000000) cover filling the box
   * edge-to-edge sat on a near-black #121010 page with nothing to visibly
   * sit ON -- black on black, reading as an empty rectangle. The page is
   * cream now (--page). A black-and-gold cover on cream is already the
   * darkest thing on screen with no mount required -- that IS the
   * mechanism 4A.1 names. The mount is gone; a hairline (--rule) is enough
   * to give the tile a boundary, and real padding keeps the grid's own
   * gap from touching each cover directly. */
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: var(--s-1);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  transition: border-color var(--dur-color) var(--ease-color);
}
/* The one buyable tile among 30 -- a gold hairline is enough to mark it out
 * of the shelf without competing with the grid's own restraint. */
.series-tile--available { border-color: var(--gold-label); }

.series-tile__link {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  color: inherit;
  text-decoration: none;
}
.series-tile__link:hover .series-tile__name,
.series-tile__link:focus-visible .series-tile__name { color: var(--gold-label); }

.series-tile__thumb {
  display: block;
  aspect-ratio: 521 / 700;
  background: var(--board);
  /* A hairline frame tight to the artwork itself -- its own edge, gold
   * lattice and folio number now have a boundary to read against, on top
   * of the mount's own lighter ground. */
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.series-tile__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.series-tile__meta {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.series-tile__n {
  font-size: var(--t-ui-xs);
  color: var(--gold-label);
}
.series-tile__name {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  line-height: 1.25;
  color: var(--ink);
  transition: color var(--dur-color) var(--ease-color);
}
.series-tile__status {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--ink-dim);
}
.series-tile__status--buy { color: var(--gold-label); }

.series-tile__notify {
  align-self: flex-start;
  margin-top: 0.15rem;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--gold-label);
  text-decoration: underline;
  cursor: pointer;
}
.series-tile__notify:hover { color: var(--ink); }

/* ---- the companion volume: its own card, deliberately NOT in the
 * 30-tile grid -- it is not one of the 30 juz, and folding it in would
 * read as a 31st juz that does not exist. */
.series-companion__row {
  display: flex;
  align-items: center;
  gap: var(--s-8);
}
.series-companion__thumb {
  /* RETONE 2026-08-31: same simplification as .series-tile above and for
   * the same reason -- a --board cover on the now-cream --page needs no
   * mount to be visible, so the --surface-2 padded mount is gone. The
   * hairline frame stays tight to the artwork itself. */
  flex: none;
  width: 8rem;
  aspect-ratio: 521 / 700;
}
.series-companion__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--board);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
}
.series-companion__copy { max-width: var(--w-text); }
.series-companion__name { margin: 0 0 var(--s-2); }
.series-companion__copy p { color: var(--ink-dim); margin: 0 0 var(--s-4); }

/* ---- the shared notify panel: one implementation for every "Notify me"
 * button on the page, opened with the volume it was clicked from. Same
 * scrim + raised-surface language as the cart drawer (components.css
 * above), so it reads as this shop's one modal idiom, not a second one. */
.notify-panel {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  place-items: center;
  padding: var(--s-4);
}
.notify-panel[hidden] { display: none; }
.notify-panel__scrim {
  position: fixed;
  inset: 0;
  /* A universal dimmer, not a brand colour -- literal for that reason, same
   * exception the cart drawer's scrim already takes. */
  background: rgba(0, 0, 0, 0.5);
}
/* RETONE 2026-08-31: pops OVER the scrim, so it wants to read as clearly
 * elevated above the band it sits on -- --band-deep, the one step further
 * than --band, rather than the flat --band every other card in this file
 * uses. Still light, still --ink/--ink-dim/--gold-label, never the dark
 * board pair. */
.notify-panel__card {
  position: relative;
  width: min(100%, 24rem);
  background: var(--band-deep);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-md);
  padding: var(--s-6);
  color: var(--ink);
}
.notify-panel__close {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  background: none;
  border: none;
  color: var(--gold-label);
  font-size: var(--t-d-title);
  line-height: 1;
  cursor: pointer;
  padding: var(--s-1);
}
.notify-panel__vol {
  font-family: var(--t-ui);
  font-size: var(--t-ui-md);
  color: var(--ink);
  margin: 0 0 var(--s-4);
}
.notify-panel [data-notify-form] { display: grid; gap: var(--s-3); }
.notify-panel__label {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--ink-dim);
  text-transform: uppercase;
  letter-spacing: var(--track-overline);
}
.notify-panel input[type="email"] {
  display: block;
  width: 100%;
  background: var(--band);
  border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent);
  border-radius: var(--r-sm);
  color: var(--ink);
  font-family: var(--t-ui);
  font-size: var(--t-ui-md);
  padding: var(--s-3);
}
.notify-panel__submit { width: 100%; }
.notify-panel__status {
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--ink);
  margin: 0;
}

@media (max-width: 640px) {
  .series-hero { padding-top: var(--s-4); }
  .series-grid { grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr)); gap: var(--s-2); }
  .series-companion__row { gap: var(--s-4); }
  .series-companion__thumb { width: 5.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  .series-tile,
  .series-tile__name {
    transition: none;
  }
}

/* ===========================================================================
 * TASK 9 -- about, FAQ, cart, delivery-and-returns, terms, thanks.
 *
 * No new system: every one of these is a stack of .band sections built from
 * primitives the earlier pages already read for reading copy --
 * .prose, .faq__list, .eyebrow, .trust-row,
 * .btn--primary/.btn--outline. Only what those primitives do not already
 * cover is added below.
 * =========================================================================== */

/* ---- about / returns / terms / faq: the answer box ------------------------
 * Scorecard dimension 6 (the returns-desk test): the refund answer and the
 * seller identity sit in a raised card in the FIRST band of
 * delivery-and-returns/, not two paragraphs down a wall of prose. */
.policy-answer {
  margin-top: var(--s-6);
  padding: var(--s-6);
  background: var(--band);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-md);
  max-width: var(--w-text);
}
.policy-answer__lede {
  font-family: var(--t-body);
  color: var(--ink);
  font-size: var(--t-ui-lg);
  line-height: var(--leading-body);
  margin: 0 0 var(--s-3);
}
.policy-answer__contact {
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--ink-dim);
  margin: 0;
}
.policy-answer__contact a { color: var(--gold-label); }

/* ---- cart page --------------------------------------------------------------
 * The cart page IS the drawer at full scale (see _base.html.j2 -- the
 * drawer markup is left out of this page, not duplicated). cart.js renders
 * real .cart-drawer__item lines into [data-cart-items] wherever it finds
 * it, including here; this only widens that line back out for a full-width
 * page row instead of a 22rem drawer. */
.cart-page__items {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  max-width: var(--w-text);
}
.cart-page__items .cart-drawer__item { padding-block: var(--s-6); }

.cart-page__empty {
  margin-top: var(--s-6);
  font-family: var(--t-ui);
  color: var(--ink-dim);
}
.cart-page__empty a { color: var(--gold-label); }

.cart-page__foot {
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--rule-strong);
  max-width: var(--w-text);
}
.cart-page__foot[hidden] { display: none; }

.cart-page__trust { border-top: none; padding-top: 0; margin-bottom: var(--s-6); }

.cart-page__subtotal {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 var(--s-4);
}
.cart-page__subtotal-label {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--gold-label);
  text-transform: uppercase;
  letter-spacing: var(--track-overline);
}
.cart-page__subtotal .num { font-size: var(--t-d-title); color: var(--ink); }

.cart-page__checkout { width: 100%; font-size: var(--t-ui-md); padding-block: var(--s-4); }

.cart-page__error {
  margin-top: var(--s-3);
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
  color: var(--gold-label);
}
.cart-page__error[hidden] { display: none; }

.cart-page__continue {
  margin-top: var(--s-4);
  font-family: var(--t-ui);
  font-size: var(--t-ui-sm);
}
.cart-page__continue a { color: var(--ink-dim); }
.cart-page__continue a:hover { color: var(--gold-label); }

/* ---- thanks page ------------------------------------------------------------ */
.thanks__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
  margin: var(--s-4) 0 var(--s-6);
  padding: var(--s-4) 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.thanks__facts[hidden] { display: none; }
.thanks__fact dt {
  font-family: var(--t-ui);
  font-size: var(--t-ui-xs);
  color: var(--ink-dim);
  text-transform: uppercase;
  letter-spacing: var(--track-overline);
  margin: 0 0 var(--s-1);
}
.thanks__fact dd { margin: 0; color: var(--ink); font-size: var(--t-ui-lg); }

@media (max-width: 640px) {
  .policy-answer { padding: var(--s-4); }
  /* .cart-page__subtotal .num mobile override removed: --t-d-title is
     24px at every width after the retone, so this was a same-value
     override. */
}

/* The receiving half of the shelf's cover morph -- see the note in
   vitrine.html.j2. Both documents must opt in for a cross-document
   transition to run. */
@view-transition { navigation: auto; }
/* Reduced motion gets a FADE, not nothing. Turning the navigation off
   entirely was wrong twice over: it is not what the preference asks for
   (less movement, not less feedback), and on Windows "Show animations: off"
   maps to this query -- so anyone with that setting saw no transition at all
   and no way to tell whether the feature was broken or disabled. Measured
   2026-09-07: under reduced_motion=reduce the `navigation: none` rule
   applied, which is exactly what Bilal was seeing. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 120ms !important;
    animation-timing-function: linear !important;
  }
  ::view-transition-group(*) { animation-name: none !important; }
}
/* .62s, not .34s. Measured with Bilal watching on 2026-09-07: at .34s he
   could not tell the transition had happened at all, and only saw it when the
   duration was temporarily raised to 2.5s. A cover travelling most of the
   viewport needs long enough to be read as one object moving rather than as a
   page swap. Everything else about the feature was already correct -- both
   documents opted in, the names matched, and pageswap carried a
   viewTransition -- so the defect was perceptual, not structural. */
::view-transition-group(*) { animation-duration: .62s; }
::view-transition-old(*), ::view-transition-new(*) {
  animation-timing-function: cubic-bezier(.22,.61,.36,1);
}
