/* Fonts are self-hosted so typography is available without a third-party
   request.  The variable Fraunces file deliberately keeps the axes used by
   entity names below (including WONK); Archivo supplies body weights 400–700.
   The metric-compatible local faces keep the short swap interval from moving
   the layout while the woff2 files arrive. */
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/fraunces-latin.8068a2ebddb2.woff2") format("woff2");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Archivo";
  src: url("../fonts/archivo-latin.2a5a65665393.woff2") format("woff2");
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces Fallback";
  src: local("Georgia");
  size-adjust: 107%;
  ascent-override: 91%;
  descent-override: 22%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Archivo Fallback";
  src: local("Arial");
  size-adjust: 97%;
  ascent-override: 93%;
  descent-override: 24%;
  line-gap-override: 0%;
}

/* finca.network — Field Atlas. Single small stylesheet, no framework
   (server-rendered). Source of truth: docs/visual_style.md. Tokens are §1,
   type roles §2, the three-colour rule §3, the top bar §4, the contour
   motif §5, page components §6. Existing component class names are kept
   (docs/design_review.md C1) so un-rebuilt templates keep working; new
   capabilities are additive classes consumed by the M4a wave-2 templates. */

:root {
  /* §1 tokens — surfaces */
  --ground:      #e9ebe6;   /* page */
  --panel:       #16241e;   /* entity block, top bar, footer */
  --panel-ink:   #e9ebe6;
  --panel-mut:   #9fb0a5;

  /* §1 tokens — text */
  --ink:         #16241e;
  --mut:         #4c5e54;
  --line:        #cfd4ca;   /* structural rules */
  --line-soft:   #dde0d9;   /* table row rules */

  /* §1 tokens — three colours, three jobs, never overlapped (§3):
     green  --live       in stock / live / just seen. Nothing else.
     amber  --hedge      crawl warnings / partial-health status. Since
                         2026-09-24 (invariant 3a) the "likely" confidence
                         band is never shown as a word or colour, so this
                         token no longer backs any entity-confidence UI.
     copper --accent     brand + structure: crumbs, active nav underline,
                         primary links, the one solid button. Never a state. */
  --accent:      #8f4f1d;   /* darkened from #b2662a for 4.5:1 body-link contrast (SITE-17) */
  --accent-panel:#e0a266;   /* copper on forest: the same job, lifted for contrast */
  --live:        #16874f;
  --live-panel:  #56c98c;   /* green: in stock, on forest */
  --hedge:       #8a6212;

  /* §1 tokens — type */
  --display:     Fraunces, "Fraunces Fallback", Georgia, serif; /* 900, WONK 1, SOFT 20 */
  --body:        Archivo, "Archivo Fallback", system-ui, sans-serif;

  /* §1 tokens — motif */
  --topo:        0.25;      /* contour opacity — settled, do not raise (§5, §7) */

  /* §5 contour ring patterns, reusable via custom properties. Spacing is part
     of the settled motif; do not tune. */
  --topo-ring: repeating-radial-gradient(circle at 50% 50%,
      transparent 0 13px, var(--accent) 13px 14.2px);
  --topo-ring-quiet: repeating-radial-gradient(circle at 50% 50%,
      transparent 0 9.5px, var(--accent) 9.5px 10.6px);

  /* Soft washes behind state chips (not §1 tokens; derived surfaces the
     pre-M4a components still need). --live-soft is the old --accent-soft
     wash: green's job is state, so the "ok" crawl chip keeps a green wash
     under a new name. */
  --live-soft:   #e7f2ec;
  --hedge-soft:  #f7ecd7;
  --danger:      #b3372f;
  --danger-soft: #f8e3e1;
  --info:        #2b5ea7;   /* crawl "running" state + legacy focus outline only */
  --info-soft:   #e3ecf8;

  /* Legacy aliases — the old token names, each now derived from a §1 token so
     this block stays the single source of truth. Un-rebuilt templates keep
     working; the wave-2 templates use the §1 names directly. */
  --bg:          var(--ground);
  --surface:     var(--ground);
  --text:        var(--ink);
  --muted:       var(--mut);
  --accent-soft: var(--live-soft);
}

* { box-sizing: border-box; }

/* §2 body text: Archivo 400, line-height 1.55. Raised from 15px to 16px in
   the 2026-09-18 layout pass: at 15px with 10.5px labels most of the page was
   small grey text. The page itself is full-bleed; .wrap is the content column. */
body {
  margin: 0;
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--ground);
  /* P0 mobile containment, belt to the braces: wide content is contained at
     its source (see .table-scroll), and clip is the last-resort guarantee
     that the page body itself never scrolls sideways. (clip, not hidden —
     it does not create a scroll container, so sticky table headers keep
     working.) */
  overflow-x: clip;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* The content column. Wider than the old 60rem so tables and the tile grid
   breathe; dark head blocks break out of it to the full viewport width. */
.wrap { max-width: 74rem; margin: 0 auto; padding-inline: 1.5rem; }
main.wrap { padding-top: 2.5rem; padding-bottom: 4rem; flex: 1 0 auto; width: 100%; }

/* §3 copper is the chrome link colour. Links that ARE data (a roaster, a lot,
   a producer inside a table, feed or fact list) stay ink — copper Fraunces at
   15px read as spindly and turned every table into a wall of orange.
   SITE-24/D-22: on touch there is no hover, so a data link needs a visible
   affordance at rest — a faint permanent underline in --line, darkening to
   --accent on hover/focus, rather than no underline until hover. */
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
table a, .feed a, .fact-value a, .alias-list a, .lot-table a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
table a:hover, .feed a:hover, .fact-value a:hover, .alias-list a:hover, .lot-table a:hover,
table a:focus-visible, .feed a:focus-visible, .fact-value a:focus-visible,
.alias-list a:focus-visible, .lot-table a:focus-visible {
  text-decoration-color: var(--accent);
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: var(--accent); color: #fff; }

/* h1/h2 stay chrome (§2: Archivo for chrome); entity names opt into Fraunces
   with .entity-name. */
h1 { font-size: 2.1rem; line-height: 1.15; letter-spacing: -0.015em; margin: 0.4rem 0 0.8rem; }
h2 { font-size: 1.2rem; line-height: 1.3; margin: 0 0 0.7rem; }

/* --- §2 type roles: serif is reserved for content, Archivo is chrome ----------
   Fraunces only ever on data — producer names, lot names, figures, prices.
   Chrome (nav, labels, buttons, captions) is always Archivo. */

.entity-name {
  font-family: var(--display);
  font-weight: 900;
  font-variation-settings: "WONK" 1, "SOFT" 20;   /* §1 display setting */
  font-size: clamp(36px, 5vw, 58px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
  margin: 0.35rem 0 0.5rem;
}

/* The line under an entity name — on the product page, the lot's own title
   (producer is the headline, the lot is the subtitle). */
.entity-sub {
  margin: 0 0 0.9rem;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(19px, 2vw, 24px);
  line-height: 1.3;
  color: var(--panel-ink);
  opacity: 0.88;
  overflow-wrap: anywhere;
}
.entity-sub .lot-name { font-size: inherit; line-height: inherit; }

.figure-value {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: 25px;
  line-height: 1.15;
  font-variant-numeric: lining-nums tabular-nums;
}

.figure-label {
  display: block;
  margin-top: 0.25rem;
  font-family: var(--body);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mut);
}
.panel-header .figure-label { color: var(--panel-mut); }

/* Copper eyebrow: geography breadcrumbs and tile geography (§6). */
.eyebrow {
  font-family: var(--body);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Neutral label: figure captions, section labels, the roaster column. */
.label {
  font-family: var(--body);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mut);
}
.panel-header .label { color: var(--panel-mut); }

/* Prices and any aligned digits (§2): Archivo 600, tabular. */
.num { font-variant-numeric: tabular-nums; font-weight: 600; }

/* §2 lot names in tables: Fraunces 400 (16px since the layout pass). */
.lot-name {
  font-family: var(--display);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.35;
}

/* --- §3 state: the live dot, live / sold-out cell styles ---------------------- */

.dot {
  display: inline-block; flex: none;
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; position: relative;
}
.dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: currentColor;
  animation: ping 2.3s cubic-bezier(0, 0, .2, 1) infinite;
}
@keyframes ping {
  0% { transform: scale(1); opacity: .5 }
  70%, 100% { transform: scale(3.4); opacity: 0 }
}
@media (prefers-reduced-motion: reduce) {
  .dot::after { animation: none; opacity: .22; transform: scale(2.2); }
}

/* Green on light ground; inside the dark panel the on-forest green takes over. */
.dot-live { color: var(--live); }
.panel-header .dot-live, .tile-media .dot-live { color: var(--live-panel); }

.state-live {
  display: inline-flex; align-items: center; gap: 0.4rem;
  color: var(--live); white-space: nowrap;
}
/* SITE-17: "Live" text reads 3.5:1 on the dark panel with --live; swap to the
   already-lifted --live-panel token (7.76:1) wherever .state-live sits inside
   a dark header block. */
.panel-header .state-live, .page-head .state-live { color: var(--live-panel); }
.state-soldout { color: var(--mut); white-space: nowrap; }

/* --- §4 top bar: --panel, not --ground; one type spec for the whole bar -------
   Full viewport width. A page's .panel-header / .page-head sits flush under
   it (main's top padding is cancelled by their negative margin) so the nav
   and the head block read as one continuous dark mass. The "finca" name is
   plain text in the bar's own type spec — a name, not a logo (§4). */
.top-bar {
  background: var(--panel); color: var(--panel-mut);
  font-family: var(--body);
  font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.top-bar-inner {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  padding-top: 1rem; padding-bottom: 0.7rem;
}
.top-bar nav { display: flex; flex-wrap: wrap; gap: 0.2rem 1.4rem; }
.top-bar a {
  display: inline-block; padding: 0.25rem 0 0.35rem;
  color: var(--panel-mut); text-decoration: none;
}
.top-bar a:hover { color: var(--panel-ink); text-decoration: none; }
.top-bar a.is-active {
  color: var(--panel-ink);
  box-shadow: inset 0 -2px var(--accent-panel);   /* §4 active underline */
}
.top-bar .brand {
  color: var(--panel-ink);
  font-size: 15px; font-weight: 700; letter-spacing: 0.02em; text-transform: none;
}

/* Phone menu (Garrett 2026-09-27): hidden above the phone breakpoint, where
   nav renders inline as always; shown and made the nav's disclosure control
   below it (see the 40rem media query further down). A 44x44 tap target
   throughout (WCAG 2.5.5/2.5.8). */
.nav-toggle {
  display: none; align-items: center; justify-content: center; gap: 0.35rem;
  min-width: 44px; min-height: 44px; padding: 0 0.7rem;
  border: 1px solid rgba(233, 235, 230, 0.3); border-radius: 6px;
  background: transparent; color: var(--panel-ink);
  font: inherit; font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer;
}
.nav-toggle:hover { border-color: rgba(233, 235, 230, 0.55); }
.nav-toggle:focus-visible { outline: 2px solid var(--accent-panel); outline-offset: 2px; }

/* Origins menu: opens on hover (pointer devices only, so a tap never leaves
   it stuck open), on keyboard focus (:focus-within, SITE-18/D-11 — stays
   matched while Tab moves focus from the toggle into the menu items, unlike
   :has(:focus-visible) which drops out for a moment between them and loses
   the user's place), and via the caret button (.is-open, a few lines of
   vanilla JS in base.html). Escape sets .is-dismissed, which wins over hover
   and focus until the pointer or focus leaves the menu. The "Origins" word
   itself still links to /origins/, which lists the same pages. */
.nav-drop { position: relative; display: inline-flex; align-items: center; }
.nav-drop-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.75rem; min-height: 1.75rem; margin: 0 0 0 0.1rem; padding: 0;
  border: 0; background: transparent; color: var(--panel-mut);
  font: inherit; font-size: 12px; cursor: pointer;
}
.nav-drop-toggle:hover, .nav-drop.is-open .nav-drop-toggle { color: var(--panel-ink); }
.nav-drop-toggle:focus-visible { outline: 2px solid var(--accent-panel); outline-offset: 1px; }
.nav-drop-menu {
  display: none; position: absolute; top: 100%; right: 0; z-index: 20;
  min-width: 12.5rem; margin: 0; padding: 0.45rem 0; list-style: none;
  background: var(--panel); border: 1px solid rgba(233, 235, 230, 0.16);
  border-radius: 8px; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.28);
}
.nav-drop-menu a {
  display: block; padding: 0.55rem 1rem; white-space: nowrap;
}
.nav-drop-menu a:hover, .nav-drop-menu a:focus-visible {
  background: rgba(233, 235, 230, 0.08); color: var(--panel-ink);
}
.nav-drop.is-open .nav-drop-menu,
.nav-drop:focus-within .nav-drop-menu { display: block; }
@media (hover: hover) {
  .nav-drop:hover .nav-drop-menu { display: block; }
}
.nav-drop.is-dismissed:not(.is-open) .nav-drop-menu { display: none; }
/* Phones: the bar wraps, so the menu opens as a two-column sheet spanning
   the bar's width, anchored to the bar rather than to the Origins item. */
@media (max-width: 40rem) {
  .top-bar-inner { position: relative; }
  .nav-drop { position: static; width: 100%; }
  .nav-drop-menu { left: 1rem; right: 1rem; top: calc(100% - 0.3rem); min-width: 0; }
  .nav-drop.is-open .nav-drop-menu,
  .nav-drop:focus-within .nav-drop-menu { display: grid; grid-template-columns: 1fr 1fr; }
  .nav-drop.is-dismissed:not(.is-open) .nav-drop-menu { display: none; }
  .nav-drop-menu a { padding: 0.85rem 1rem; min-height: 44px; box-sizing: border-box; white-space: normal; }
  .nav-drop-toggle { min-width: 44px; min-height: 44px; }

  /* Phone menu (Garrett 2026-09-27): the bar shows brand + toggle on one
     line; nav is a hidden vertical sheet until .top-bar-inner.is-nav-open
     (toggled by the plain-JS handler in base.html). Fixes the top menu
     wrapping across several cramped lines at 375px. */
  .nav-toggle { display: inline-flex; }
  .top-bar nav {
    display: none;
    flex-direction: column; align-items: stretch; gap: 0;
    width: 100%; margin-top: 0.6rem; padding-top: 0.5rem;
    border-top: 1px solid rgba(233, 235, 230, 0.16);
  }
  .top-bar-inner.is-nav-open nav { display: flex; }
  .top-bar nav > a {
    min-height: 44px; display: flex; align-items: center; padding: 0.6rem 0;
  }
  .nav-drop-top { min-height: 44px; display: flex; align-items: center; flex: 1; }
}

/* Nav search (T458): plain GET form to /search/?q=, styled to sit inline
   with the rest of the top bar. Label is visually hidden (same clip
   technique as .roaster-table thead / .lot-table-t111 thead below) --
   the placeholder carries the visible hint. */
.nav-search { display: inline-flex; align-items: center; margin-left: 0.2rem; }
.nav-search label {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.nav-search input[type="search"] {
  width: 11rem; max-width: 100%; padding: 0.3rem 0.7rem;
  border: 1px solid rgba(233, 235, 230, 0.3); border-radius: 999px;
  background: rgba(233, 235, 230, 0.08); color: var(--panel-ink);
  font: inherit; font-size: 12px; font-weight: 500;
  letter-spacing: normal; text-transform: none;
}
.nav-search input[type="search"]::placeholder { color: var(--panel-mut); }
.nav-search input[type="search"]:focus-visible {
  outline: 2px solid var(--accent-panel); outline-offset: 2px;
  border-color: transparent;
}
@media (max-width: 40rem) {
  /* Part of the phone nav sheet (.top-bar-inner.is-nav-open nav above), so
     it stretches full width there instead of the fixed desktop width. */
  .nav-search { width: 100%; margin: 0.4rem 0 0.1rem; }
  /* 16px: smaller text makes iOS Safari zoom the page on focus. */
  .nav-search input[type="search"] { width: 100%; min-height: 44px; font-size: 16px; }
}

/* --- §5 the contour motif: dark header blocks only ----------------------------
   Two clusters per §5 on .panel-header; .topo-tint is the reusable single
   cluster for other dark blocks (tile media). Contours never appear behind
   page body content (§5, §7); --topo is settled at 0.25 — do not raise. */
.panel-header, .page-head,
.topo-tint { position: relative; overflow: hidden; }
.panel-header > *, .page-head > *,
.topo-tint > * { position: relative; z-index: 1; }

.panel-header::before, .page-head::before,
.topo-tint::before {          /* primary cluster, lower right */
  content: ""; position: absolute; right: -58px; bottom: -168px;
  width: 384px; height: 384px; border-radius: 50%;
  z-index: 0; pointer-events: none;
  background: var(--topo-ring);
  opacity: var(--topo);
  mask-image: radial-gradient(circle at 50% 50%, #000 20%, transparent 74%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 20%, transparent 74%);
}
.panel-header::after, .page-head::after {   /* secondary cluster, upper left, quieter */
  content: ""; position: absolute; left: -118px; top: -116px;
  width: 262px; height: 262px; border-radius: 50%;
  z-index: 0; pointer-events: none;
  background: var(--topo-ring-quiet);
  opacity: calc(var(--topo) * 0.42);
  mask-image: radial-gradient(circle at 64% 66%, #000 12%, transparent 66%);
  -webkit-mask-image: radial-gradient(circle at 64% 66%, #000 12%, transparent 66%);
}
/* Reusable quiet-cluster variant of the tint (e.g. tile media). */
.topo-tint--tl::before {
  right: auto; bottom: auto; left: -118px; top: -116px;
  width: 262px; height: 262px;
  background: var(--topo-ring-quiet);
  opacity: calc(var(--topo) * 0.42);
  mask-image: radial-gradient(circle at 64% 66%, #000 12%, transparent 66%);
  -webkit-mask-image: radial-gradient(circle at 64% 66%, #000 12%, transparent 66%);
}

/* Rule inside the dark panel (between sub-detail and figures, §6 producer). */
.panel-rule { border: 0; border-top: 1px solid rgba(233, 235, 230, 0.16); margin: 0.95rem 0; }

.site-footer {
  background: var(--panel); color: var(--panel-mut);
  padding: 2.2rem 0 2.8rem; font-size: 0.88rem;
}
.footer-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; }
.site-footer h2 { color: var(--panel-ink); font-size: 12.5px; letter-spacing: .14em; text-transform: uppercase; margin: 0 0 .55rem; }
.site-footer section { display: flex; flex-direction: column; align-items: flex-start; gap: .25rem; }
.site-footer p { margin: .35rem 0 0; }
.site-footer a { color: var(--panel-ink); }
.footer-policy { grid-column: 1 / -1; color: var(--accent-panel); font-weight: 600; letter-spacing: .06em; }
@media (max-width: 40rem) { .footer-grid { grid-template-columns: 1fr; } }

.lede { max-width: 44rem; font-size: 1.1rem; color: var(--mut); }

/* Home masthead: the page-head at its largest. */
.hero h1 {
  max-width: 19ch;
  font-size: clamp(34px, 4.6vw, 54px); line-height: 1.05; letter-spacing: -0.025em;
  margin: 0.5rem 0 1rem;
}
.hero .lede { color: var(--panel-mut); font-size: 1.12rem; max-width: 40rem; margin: 0; }

/* T114: figures row inside the hero (same figure-value / figure-label pair as
   the producer panel) and the compact home roaster list. */
.hero-figures { margin-top: 1.6rem; }
.hero .figure-value { color: var(--panel-ink); font-variant-numeric: tabular-nums; }
.hero .figure-label { color: var(--panel-mut); }
.tile-grid--strip { grid-template-columns: repeat(auto-fill, minmax(min(10.5rem, 100%), 1fr)); margin-top: 0.8rem; }
.roaster-list {
  list-style: none; margin: 0.8rem 0 1rem; padding: 0;
  columns: 14rem; column-gap: 1.6rem; font-size: 0.95rem;
}
.roaster-list li { break-inside: avoid; padding: 0.2rem 0; overflow-wrap: anywhere; }

/* Sections are open, not boxed. The old 10px-rounded bordered .panel put
   every block on the page in the same outlined card (the "uniform rounded
   cards" §7 rules out); a section is now a rule and a heading. */
.panel {
  background: none; border: 0; border-radius: 0;
  padding: 0; margin: 0 0 2.75rem;
}
.panel > h2, .section-head h2, .cols section > h2, main > section > h2, article > section > h2 {
  padding-top: 0.75rem; border-top: 2px solid var(--ink);
}
.section-head { margin-bottom: 0.4rem; }
.section-head h2 { margin-bottom: 0.3rem; }
.section-head p { max-width: 46rem; margin: 0; }

.facts { display: flex; flex-wrap: wrap; gap: 0.4rem 1.1rem; margin: 0.2rem 0; color: var(--mut); font-size: 0.92rem; overflow-wrap: anywhere; }
.crumbs { color: var(--mut); font-size: 0.85rem; margin: 0; }
.muted { color: var(--mut); }
.small { font-size: 0.85rem; }
.warn { color: var(--hedge); }

table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
th, td { text-align: left; padding: 0.6rem 0.75rem 0.6rem 0; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
th {
  text-align: left; color: var(--mut); font-weight: 600;
  font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.14em;
  border-bottom: 1px solid var(--line);
}
tr:last-child td { border-bottom: none; }

/* --- P0 horizontal containment ------------------------------------------------
   Wide tables scroll inside their own container; the document never does.
   .col-hide-sm drops low-value internal-fingerprint columns (Platform, Site)
   on phones. --- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (max-width: 40rem) {
  .col-hide-sm { display: none; }
}

/* Sold-out archive rows: muted, never hidden (invariant 6). */
.soldout-row, .soldout-row a { color: var(--mut); }

ul.entity-list, ul.product-list { list-style: none; margin: 0; padding: 0; }
ul.entity-list li, ul.product-list li { padding: 0.45rem 0; border-bottom: 1px solid var(--line); }
ul.entity-list li:last-child, ul.product-list li:last-child { border-bottom: none; }
.attr-label { display: inline-block; min-width: 7.5rem; color: var(--mut); font-size: 0.88rem; }

/* --- confidence + price tags ---------------------------------------------------
   §3 discipline: green is state only, copper is never a state. High and
   medium confidence links render identically (invariant 3a, Garrett
   2026-09-24): the band is an internal data flag, never a visible word,
   badge, or style difference on a public page. sold out is muted (§6);
   sale is neutral (it is a price state).
   Danger red remains reserved for crawl failure. */
.tag {
  display: inline-block; padding: 0.05rem 0.5rem; border-radius: 999px;
  font-size: 0.75rem; font-weight: 600; vertical-align: 1px;
  white-space: nowrap;
}
/* High (and, since 2026-09-24, medium) confidence is "shown plainly"
   (invariant 3a): the words stay for screen readers and the curious, but
   visually they recede to a footnote. */
.tag-high { background: transparent; color: var(--mut); border: 0; padding: 0 0 0 0.25rem; font-weight: 400; opacity: 0.8; }
.crumbs .tag-high { border: 1px solid var(--line); padding: 0.05rem 0.5rem; opacity: 1; }
.tag-soldout { background: transparent; color: var(--mut); border: 1px solid var(--line); }
.tag-sale { background: transparent; color: var(--mut); border: 1px solid var(--line); }

.status-dot {
  display: inline-block; width: 0.55em; height: 0.55em; border-radius: 50%;
  background: var(--mut); vertical-align: 1px;
}
.status-ok { background: var(--live); }           /* crawl ok ≈ just seen (§3 green) */
.status-partial { background: var(--hedge); }
.status-failed, .status-blocked { background: var(--danger); }
.status-running { background: var(--info); }

/* SITE-21/D-09: the reading-width cap sits on the text children, not on
   `.prose` itself — `.prose` is also `.about-page`, whose `.page-head` uses
   the full-bleed `calc(50% - 50vw)` trick (§6 below); that trick measures
   from `.prose`'s own box, so a 45rem cap on the article stopped the dark
   header band 210px short of the viewport edge at 1440px. `.contents` gets
   its own width (full-bleed grid column below); everything else in the flow
   (lede, h2, p, ul) reads at ~72ch. */
.prose > :not(.page-head):not(.contents) { max-width: 45rem; }   /* ~72ch */
.prose h2 { margin-top: 2.2rem; }

/* Images (migration 0004): hotlinked roaster photos + CSS-only placeholders
   so pages hold their layout when a URL is absent. Nothing is re-hosted. */
.product-image { margin: 0 0 1.5rem; }
.product-image img {
  display: block; max-width: 100%; max-height: 34rem; width: auto; height: auto;
  background: #fff;
}
.product-image figcaption { margin-top: 0.5rem; }
/* The markup carries a ☕ glyph for old tests' sake; the designed empty state
   is a quiet block with a label, never an emoji. */
.img-placeholder {
  display: flex; align-items: center; justify-content: center;
  min-height: 14rem; border: 1px dashed var(--line);
  background: transparent; font-size: 0;
}
/* Detail pages with no photo: a slim strip, never a reserved empty stage. */
.img-placeholder--slim { min-height: 0; height: 4rem; }

.thumb {
  width: 2.25rem; height: 2.25rem; object-fit: contain;
  border: 1px solid var(--line-soft); border-radius: 50%;
  background: #fff; vertical-align: middle; margin-right: 0.6rem;
}
.img-placeholder-sm { min-height: 6rem; margin: 0 0 1rem; }

.thumb-placeholder {
  display: inline-block; font-size: 0; background: var(--line-soft); border-color: transparent;
}

/* Roaster head (T115): an approved logo on a light disc beside the name; a
   crawled photo is never shown here. Figures sit under the facts line. */
.roaster-headline { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.roaster-headline h1 { min-width: 0; overflow-wrap: anywhere; margin-bottom: 0.4rem; }
.roaster-logo-disc {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 4.25rem; height: 4.25rem; border-radius: 50%; background: #fff;
  overflow: hidden; margin-bottom: 0.4rem;
}
.roaster-logo-disc img { max-width: 70%; max-height: 70%; object-fit: contain; }
/* An approved logo file is a finished square badge (graph/static/graph/logos/,
   made by make_logo_badges.py): its margin and background are baked in, so it
   fills the whole circle. */
.roaster-logo-disc img.badge { width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
.thumb.badge, .roaster-table .thumb.badge { padding: 0; object-fit: cover; }
.roaster-figures { margin-top: 1.2rem; }
.page-head .roaster-figures .figure-value { color: var(--panel-ink); }
.roaster-figures .figure-value {
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* --- /shop/ browse cards: image-led grid, newest first (invariant 8: the
   chronological order is neutral — no featured slots, nothing purchasable).
   The media box reserves 4/3 space via aspect-ratio so a missing or slow
   image never shifts layout; the placeholder tile carries the same ☕ idiom
   as the detail pages. Fallback ladder: image → placeholder tile.
   Kept for the un-rebuilt shop page; the §6 4:5 tile vocabulary lives in the
   Field Atlas components section below. --- */
.card-grid {
  list-style: none; margin: 1.1rem 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(9.5rem, 100%), 1fr));
  gap: 0.85rem;
}
.product-card { display: flex; min-width: 0; }
.card-link {
  display: flex; flex-direction: column; width: 100%; min-width: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  overflow: hidden; color: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.card-link:hover, .card-link:focus-visible {
  text-decoration: none; border-color: var(--accent);
  box-shadow: 0 3px 12px rgba(22, 36, 30, 0.1); transform: translateY(-1px);
}
.card-link:hover .card-title { text-decoration: underline; }
.card-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (prefers-reduced-motion: reduce) {
  .card-link { transition: none; }
  .card-link:hover, .card-link:focus-visible { transform: none; }
}

.card-media {
  position: relative; display: block; aspect-ratio: 4 / 3; overflow: hidden;
  border-bottom: 1px solid var(--line); background: var(--bg);
}
.card-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.card-media-placeholder {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; font-size: 1.9rem; color: var(--mut);
  background: var(--bg); border: 1px dashed var(--line);
}
/* invariant 6: sold-out stays listed and clearly marked — muted, never hidden.
   The badge sits on a photo, so it keeps an opaque ground fill behind the
   muted chip. */
.card-soldout-badge {
  position: absolute; top: 0.5rem; left: 0.5rem;
  background: var(--ground);
  box-shadow: 0 1px 4px rgba(22, 36, 30, 0.18);
}
.is-soldout .card-img { filter: grayscale(0.85); opacity: 0.72; }
.is-soldout .card-title { color: var(--mut); }

.card-body {
  display: flex; flex-direction: column; gap: 0.3rem;
  padding: 0.6rem 0.7rem 0.7rem; flex: 1;
}
.card-roaster {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--mut);
}
.card-title {
  font-size: 0.92rem; font-weight: 600; line-height: 1.35; color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;  /* a long token in a title can never push the page wide */
}
.card-meta {
  margin-top: auto; padding-top: 0.15rem;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.55rem;
}
.card-price { font-weight: 700; font-size: 0.95rem; font-variant-numeric: tabular-nums; }
.card-per100, .card-noprice { font-size: 0.78rem; color: var(--mut); }
.card-foot {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.15rem 0.6rem;
  font-size: 0.8rem; margin-top: 0.1rem;
}
.card-unknown, .card-origin { color: var(--mut); }

/* --- /roasters/ health chips + sticky table header ---------------------------- */
.health-chip {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.12rem 0.6rem; border-radius: 999px;
  font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  color: var(--mut);
}
.health-chip .status-dot { background: currentColor; }
.health-chip.status-ok { background: var(--live-soft); color: var(--live); }
.health-chip.status-partial { background: var(--hedge-soft); color: var(--hedge); }
.health-chip.status-failed { background: var(--danger-soft); color: var(--danger); }
.health-chip.status-running { background: var(--info-soft); color: var(--info); }
.health-chip.status-never { background: transparent; border: 1px dashed var(--line); color: var(--mut); }

.table-sticky thead th {
  position: sticky; top: 0; z-index: 2; background: var(--surface);
  /* collapsed borders scroll away under a sticky th; the inset shadow holds
     the header's bottom rule in place while the body passes beneath. */
  box-shadow: inset 0 -1px 0 var(--line);
}

/* /roasters/: big rows and big logos (Garrett, 2026-09-26: "the images should
   be bigger. Bigger rows"). Sizing only; how the logos load is untouched. */
.roaster-table td { padding-top: 0.95rem; padding-bottom: 0.95rem; }
.roaster-table td.roaster-cell { font-size: 1.12rem; font-weight: 600; }
.roaster-table .thumb {
  width: 4.5rem; height: 4.5rem; margin-right: 1.1rem;
  box-sizing: border-box; padding: 0.3rem;   /* wide logos clear the circle's edge */
}
.roaster-table .thumb-placeholder { padding: 0; }

/* /roasters/ on narrow screens: the four-column table stacks into per-roaster
   cards, labels supplied by each td's data-label, so the page never requires
   horizontal scrolling. */
@media (max-width: 40rem) {
  .roaster-table thead {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .roaster-table, .roaster-table tbody { display: block; }
  .roaster-table tr {
    display: block; margin: 0 0 0.7rem; padding: 0.55rem 0.75rem 0.65rem;
    background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
  }
  .roaster-table tr:last-child { margin-bottom: 0; }
  .roaster-table td {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    gap: 0.25rem 1rem; padding: 0.18rem 0; border-bottom: none;
  }
  .roaster-table td::before {
    content: attr(data-label); color: var(--mut);
    font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
    flex: 0 0 auto;
  }
  .roaster-table td.roaster-cell::before { display: none; }
  /* no data-label on the roaster cell: keep thumb + name left-aligned */
  .roaster-table td.roaster-cell { justify-content: flex-start; align-items: center; gap: 0.8rem; padding-bottom: 0.45rem; }
  .roaster-table td { padding-top: 0.2rem; padding-bottom: 0.2rem; }
  .roaster-table .thumb { width: 3.75rem; height: 3.75rem; margin-right: 0; }
}


/* --- empty states (designed, not accidental) ---------------------------------- */
.empty-state {
  text-align: center; padding: 2.4rem 1rem 2.6rem; margin: 1.1rem 0 0;
  border: 1px dashed var(--line); border-radius: 10px;
  background: var(--surface); color: var(--mut);
}
.empty-state p { margin: 0 0 0.3rem; }
.empty-state .empty-glyph { font-size: 2.1rem; line-height: 1; margin: 0 0 0.55rem; }

/* --- §6 Field Atlas page components (consumed by the wave-2 templates) -------- */

/* §6 producer/entity page: the dark bar flows straight into this block —
   stack .top-bar flush above it, no gap (§4). Contours live here (§5). */
/* Full-bleed: the block breaks out of .wrap to the viewport edges while its
   content stays on the .wrap column (margin and padding cancel exactly).
   The negative top margin cancels main's padding so it meets the bar with no
   gap. .page-head is the same block for index pages (shop, roasters, home). */
.panel-header, .page-head {
  background: var(--panel); color: var(--panel-ink);
  margin: -2.5rem calc(50% - 50vw) 2.5rem;
  padding: 1.6rem calc(50vw - 50%) 2.4rem;
}
.page-head h1 { color: var(--panel-ink); margin-bottom: 0.4rem; }
.panel-header .muted, .panel-header .small, .panel-header .facts, .panel-header .crumbs,
.page-head .muted, .page-head .small, .page-head .facts, .page-head .crumbs,
.panel-header .figure-label, .page-head .label, .page-head .figure-label { color: var(--panel-mut); }
.panel-header a:not(.btn-primary), .page-head a { color: var(--accent-panel); }
.panel-header .eyebrow, .page-head .eyebrow { color: var(--accent-panel); }
.panel-header .facts, .page-head .facts { margin: 0.5rem 0 0; }
.panel-header .btn-primary { margin-top: 0.6rem; }
.panel-header .tag-soldout, .page-head .tag-soldout { color: var(--panel-mut); border-color: rgba(159,176,165,.5); }

/* The method page remains a normal article on small screens; on desktop the
   compact contents rail stays available while its linked sections scroll. */
.contents { margin: 0 0 1.8rem; padding: .8rem 1rem; border-left: 2px solid var(--accent); background: rgba(207, 212, 202, .32); }
.contents p { margin: 0 0 .3rem; }
.contents ol { margin: 0; padding-left: 1.2rem; columns: 2; }
.contents li { padding: .1rem 0; }
@media (min-width: 1024px) {
  .about-page { display: grid; grid-template-columns: minmax(0, 1fr) 13rem; column-gap: 3rem; }
  .about-page > .page-head { grid-column: 1 / -1; }
  .about-page > :not(.page-head):not(.contents) { grid-column: 1; }
  .about-page > .contents { grid-column: 2; grid-row: 2 / span 20; align-self: start; position: sticky; top: 1rem; margin: 0; }
  .contents ol { columns: 1; }
}

/* §6 producer body: two columns (1.55fr / 1fr), collapsing to one under 760px. */
.cols {
  display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: 3.5rem; align-items: start; margin-top: 0;
}
@media (max-width: 760px) {
  .cols { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* Product page body: the photo beside the facts, the roaster's own
   description below at reading width. */
.product-body {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 3.5rem; align-items: start;
}
.product-media .product-image img { width: 100%; max-height: 38rem; object-fit: contain; }
.product-description { max-width: 45rem; overflow-wrap: anywhere; }  /* T118: a long URL/token in a description cannot push the phone page wide */
@media (max-width: 760px) {
  .product-body { grid-template-columns: minmax(0, 1fr); gap: 0; }
}
.price-line { font-size: 1.02rem; margin: 0 0 0.4rem; }
.price-line strong {
  font-family: var(--display); font-weight: 600; font-size: 1.7rem;
  font-variant-numeric: tabular-nums; color: var(--ink);
}

/* §6 producer panel: a row of figures, each a Fraunces number over an
   Archivo label. Elevation and country belong here. */
.figures {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: 0.9rem 2.2rem;
}

/* §6 producer left column: the lot table. Tight rows, --line-soft rules.
   Columns: roaster (uppercase label) · lot (Fraunces) · crop year · price
   (tabular, right) · state (live dot or muted "Sold out"). */
.lot-table { width: 100%; border-collapse: collapse; font-size: 15px; line-height: 1.4; }
.lot-table th {
  padding: 0 12px 8px 0; border-bottom: 1px solid var(--line);
  text-align: left; color: var(--mut);
  font-family: var(--body); font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.lot-table td {
  padding: 9px 12px 9px 0;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: baseline;
}
.lot-table tr:last-child td { border-bottom: none; }
.lot-table th.num { text-align: right; }
.lot-table .num { text-align: right; white-space: nowrap; }

/* §6 browse: uniform 4:5 tiles regardless of source image dimensions. Tiles
   are flat on the ground (no rounded-card idiom); the media box is forest
   with the contour tint; a missing photo gets the designed dashed empty
   state, never an empty box. */
.tile-grid {
  list-style: none; margin: 1.2rem 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(12.5rem, 100%), 1fr));
  gap: 2.2rem 1.4rem;
}
.tile { min-width: 0; }
/* The shop tile carries both the old .card-link and the §6 .tile-link
   classes; strip the card idiom (border, radius, lift) so it sits flat. */
.tile-link, .tile-link.card-link {
  display: block; color: inherit; text-decoration: none;
  background: none; border: 0; border-radius: 0; overflow: visible;
  box-shadow: none; transform: none;
}
.tile-link:hover, .tile-link.card-link:hover, .tile-link.card-link:focus-visible {
  text-decoration: none; box-shadow: none; transform: none;
}
.tile-link:hover .tile-name { text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 3px; }
.tile-link:hover .tile-img { transform: scale(1.025); }
/* Light media ground: roaster product shots are mostly bags on white, and a
   forest ground turned every not-yet-loaded image into a black square. The
   contour motif stays in header blocks only (§5). */
.tile-media, .tile-media.card-media {
  position: relative; display: block; aspect-ratio: 4 / 5; overflow: hidden;
  background: var(--line-soft); border: 0;
}
.tile-media.topo-tint::before { display: none; }
.tile-img { transition: transform 0.25s ease; }
@media (prefers-reduced-motion: reduce) { .tile-img { transition: none; } }
.tile-img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* NOTE: .topo-tint > * lifts children above the contour; these absolute
   children are declared after it so their positioning wins. */
.tile-empty {
  position: absolute; inset: 0.5rem;
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 0.6rem;
  border: 1px dashed rgba(159, 176, 165, 0.6);
  color: var(--panel-mut);
  font-family: var(--body); font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.tile-live {
  position: absolute; top: 0.5rem; left: 0.5rem; z-index: 1;
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.16rem 0.5rem;
  background: rgba(22, 36, 30, 0.85); color: var(--live-panel);
  font-size: 0.75rem; font-weight: 600;
}
.tile-body { display: flex; flex-direction: column; gap: 0.2rem; padding: 0.75rem 0 0; }
.tile-body .card-roaster { font-size: 12px; letter-spacing: 0.12em; }
/* T113: a photo blends into the page ground (bag shots sit on white); the
   typographic card is the no-photo state, on tiles and on the product page
   (4:5). Contours are faint and dark-on-light, tiles being the one body
   surface that may carry them (§6 Browse). */
.tile-media .card-img, .product-media .product-image img { mix-blend-mode: multiply; }
.tile-card {
  position: relative; overflow: hidden; box-sizing: border-box;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch; gap: 0.35rem;
  width: 100%; height: 100%; padding: 1rem; text-align: left;
  background: var(--line-soft); border: 0; font-size: 1rem; color: var(--ink);
}
.tile-card::before {
  content: ""; position: absolute; right: -40%; top: -30%; width: 120%; aspect-ratio: 1;
  border-radius: 50%; pointer-events: none;
  background: repeating-radial-gradient(circle at 50% 50%,
      transparent 0 13px, rgba(22, 36, 30, 0.10) 13px 14.2px);
  mask-image: radial-gradient(circle at 50% 50%, #000 20%, transparent 74%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 20%, transparent 74%);
}
.tile-card > * { position: relative; }
.tile-card-eyebrow {
  font-family: var(--body); font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
  overflow-wrap: anywhere;
}
.tile-card-title {
  font-family: var(--display); font-weight: 600; font-size: 1.5rem; line-height: 1.15;
  color: var(--ink); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.tile-card--product { aspect-ratio: 4 / 5; height: auto; min-height: 0; padding: 1.5rem; }
.tile-card--product .tile-card-title { font-size: 2rem; }
.shop-note { margin-top: 1.5rem; }
.tile-meta .status-dot { margin-left: 0.4rem; }
.tile-name {
  font-family: var(--display); font-weight: 600; font-size: 1.08rem; line-height: 1.28;
  color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
.tile-meta { color: var(--mut); font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.is-soldout .tile-img { filter: grayscale(0.85); opacity: 0.72; }
.is-soldout .tile-name { color: var(--mut); }

/* §6 drops / home: a time series, not a catalogue. Dense first-seen rows,
   timestamps, live dots on today's rows, roaster + geography as a sub-line. */
/* Rows are grouped under a day heading, so each row carries only its time. */
.feed-day {
  margin: 2rem 0 0; padding-bottom: 0.45rem; border-bottom: 1px solid var(--line);
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--mut);
}
.feed { list-style: none; margin: 0; padding: 0; }
.feed-row {
  display: grid; grid-template-columns: 3.6rem minmax(0, 1fr);
  gap: 0 0.9rem; align-items: baseline;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.feed-row:last-child { border-bottom: none; }
.feed-time { color: var(--mut); font-size: 0.85rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.feed-main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.feed-name {
  font-family: var(--display); font-weight: 400; font-size: 17.5px; line-height: 1.35; color: var(--ink);
  overflow-wrap: anywhere;
}
.feed-name .dot { margin: 0 0.35rem 0.15em 0.1rem; vertical-align: middle; }
.feed-name a { text-decoration: none; }
.feed-name a:hover { text-decoration: underline; text-decoration-color: var(--accent); }
.feed-sub { color: var(--mut); font-size: 0.88rem; }
.feed-sub a { color: var(--mut); text-decoration-color: var(--line); }

/* §6 product page: extracted facts as a label / value / confidence list. */
.fact-list { list-style: none; margin: 0.3rem 0; padding: 0; }
.fact-list li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.8rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.fact-list li:last-child { border-bottom: none; }
.fact-label {
  flex: 0 0 auto; min-width: 7.5rem;
  font-family: var(--body); font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--mut);
}
.fact-value { overflow-wrap: anywhere; }

/* §6 producer right column: "Also known as" — names are data, so serif. */
.alias-list { list-style: none; margin: 0.2rem 0; padding: 0; }
.alias-list li {
  font-family: var(--display); font-weight: 400; font-size: 15px; line-height: 1.4;
  padding: 0.3rem 0; border-bottom: 1px solid var(--line-soft);
  overflow-wrap: anywhere;
}
.alias-list li:last-child { border-bottom: none; }

/* §6 product page: the one solid copper button (link out to the roaster). */
.btn-primary {
  display: inline-block; padding: 0.7rem 1.15rem;
  background: var(--accent); color: #fff;
  font-family: var(--body); font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  border: 0; border-radius: 2px; text-decoration: none;
}
.btn-primary:hover { text-decoration: none; color: #fff; filter: brightness(1.08); }
/* --- T110 /origins/ ---------------------------------------------------------
   Counts are lining, tabular numerals; the bar shows a count and ranks nothing. */
.origin-group { margin: 1.4rem 0 0.4rem; }
.origin-browse { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 1rem 0 0; }
.origin-browse a {
  border: 1px solid var(--line); border-radius: 999px; padding: 0.2rem 0.8rem;
  font-size: 0.9rem;
}
.origin-list, .origin-cols { list-style: none; margin: 0 0 1rem; padding: 0; }
.origin-list li {
  display: grid; grid-template-columns: minmax(8rem, 14rem) 4.5rem minmax(4rem, 1fr);
  align-items: center; gap: 0.75rem; padding: 0.3rem 0; border-bottom: 1px solid var(--line-soft);
}
.origin-count {
  font-variant-numeric: lining-nums tabular-nums; font-weight: 600; color: var(--mut);
  text-align: right;
}
.origin-cols .origin-count { margin-left: 0.3rem; text-align: left; }
.origin-bar { display: block; height: 4px; background: var(--line-soft); }
.origin-bar i { display: block; height: 100%; background: var(--accent); }
.origin-cols { columns: 14rem; column-gap: 2rem; }
.origin-cols li { break-inside: avoid; padding: 0.15rem 0; }
@media (max-width: 40rem) {
  .origin-list li { grid-template-columns: 1fr 4rem; }
  .origin-bar { grid-column: 1 / -1; }
}

/* --- T111 lot tables (§6): Lot · Roaster · Crop year · Per 100 g · State ------ */
.lot-table-t111 .lot-price { font-variant-numeric: tabular-nums; }
.lot-table-t111 .lot-year { white-space: nowrap; font-variant-numeric: tabular-nums; }
.lot-table-t111 .state-live, .lot-table-t111 .lot-state { white-space: nowrap; }
.region-chips { list-style: none; margin: 0 0 1rem; padding: 0; display: flex; flex-wrap: wrap; gap: 0.4rem; }
.region-chip {
  border: 1px solid var(--line); border-radius: 999px; padding: 0.1rem 0.7rem;
  font-size: 0.85rem; color: var(--ink);
}
.region-count { color: var(--mut); font-variant-numeric: tabular-nums; margin-left: 0.2rem; }

@media (max-width: 40rem) {
  .lot-table-t111 thead {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .lot-table-t111, .lot-table-t111 tbody { display: block; }
  .lot-table-t111 tr {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0;
    padding: 0.6rem 0; border-bottom: 1px solid var(--line-soft);
  }
  .lot-table-t111 tr:last-child { border-bottom: none; }
  .lot-table-t111 td { display: block; padding: 0; border: 0; min-width: 0; }
  .lot-table-t111 td:empty { display: none; }
  .lot-table-t111 td.lot-name { flex: 0 0 100%; overflow-wrap: anywhere; }
  .lot-table-t111 td.lot-roaster { font-size: 12.5px; }
  .lot-table-t111 td.lot-year::before,
  .lot-table-t111 td.lot-price::before,
  .lot-table-t111 td.lot-state::before { content: "\00b7"; margin: 0 0.5em; color: var(--mut); }
  .lot-table-t111 .num { text-align: left; }
}
