/* ==========================================================================
   Kofineur — components (V1: masthead, atlas index, specimen page)
   Tokens live in application.css / DESIGN.md. No utility framework.
   ========================================================================== */

/* --- Cover (wood-green hero band; used by index + specimen) ------------- */
.cover { background: var(--cover); color: var(--on); }
.cover a { color: inherit; }
.cover a:hover { text-decoration: none; }

/* --- Masthead ---------------------------------------------------------- */
.masthead { display: flex; justify-content: space-between; align-items: baseline; padding: 30px 0; }
.brand { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 1.5rem; letter-spacing: .02em; }
.brand svg { color: var(--on-cherry); }
.nav { display: flex; gap: 28px; }
.nav a { color: var(--on-muted); font-variant: small-caps; letter-spacing: .1em; font-weight: 500; font-size: .96rem; }
.nav a.on { color: var(--on); }
.nav a:hover { color: var(--on); }
.edn { color: var(--on); font-size: .86rem; }
/* Phone-only nav toggle; the 760px block turns it on and wires its panel. */
.mnav { display: none; }

/* --- Index intro ------------------------------------------------------- */
.atlas-intro { padding: 36px 0 40px; }
.atlas-intro h1 { font-weight: 300; font-size: 2.6rem; color: var(--on); letter-spacing: -.01em; }
.atlas-intro p { color: var(--on-muted); font-size: 1.1rem; margin-top: 9px; max-width: none; }
.stats { display: flex; gap: 34px; margin-top: 24px; flex-wrap: wrap; }
.stat { display: flex; align-items: baseline; gap: 8px; }
.stat b { font-size: 1.3rem; font-weight: 500; color: var(--on); }
.stat span { font-size: .82rem; color: var(--on-muted); font-variant: small-caps; letter-spacing: .08em; }

/* --- Atlas grid -------------------------------------------------------- */
.atlas { padding-block: 44px 96px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 22px; }

/* --- Specimen card ----------------------------------------------------- */
.card { background: var(--surface); border-radius: var(--radius-card); padding: 24px 26px 22px; position: relative; display: flex; flex-direction: column; }
.card .stamp { position: absolute; top: 20px; right: 22px; color: var(--ink); opacity: .32; }
.card .stamp .cherry { fill: var(--cherry); stroke: none; opacity: .85; }
.num { font-size: .8rem; color: var(--faint); font-variant: small-caps; letter-spacing: .1em; }
.card .name { font-size: 1.42rem; font-weight: 500; line-height: 1.12; margin: 6px 0 0; max-width: 82%; }
.card .name a { color: inherit; }
.card .name a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.o { font-size: .95rem; color: var(--muted); font-variant: small-caps; letter-spacing: .04em; margin-top: 9px; }
.by { font-size: .86rem; color: var(--faint); font-variant: small-caps; letter-spacing: .06em; margin-top: 2px; }
/* Archive-only freshness caption (out-of-stock / delisted). Live cards never
   render it — only the roaster archive grid shows non-in-stock coffees. */
.archived-note { font-size: .8rem; color: var(--faint); font-variant: small-caps; letter-spacing: .05em; margin-top: 9px; padding-left: 10px; border-left: 2px solid var(--hair); }
.vp { font-size: 1rem; color: var(--faint); margin-top: 9px; }
.notes { color: var(--muted); font-size: .96rem; margin-top: 9px; }

/* flavor families */
.fams { display: flex; flex-wrap: wrap; gap: 13px; margin-top: 13px; }
.fams .fam { font-style: normal; display: flex; align-items: center; gap: 7px; font-size: .92rem; }
.fam-dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; flex: none; }

/* sensory scales */
.scales { display: flex; gap: 20px; margin-top: 15px; padding-top: 14px; border-top: 1px solid var(--hair); }
.sci { font-size: .74rem; color: var(--muted); }
.sci .lab { font-variant: small-caps; letter-spacing: .05em; display: block; margin-bottom: 5px; }
.dots { display: inline-flex; gap: 3px; }
.dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--hair); display: inline-block; }
.dots i.on { background: var(--ink); }

.cf { display: flex; justify-content: flex-end; margin-top: 16px; }
.ver { font-size: .98rem; color: var(--cherry); font-variant: small-caps; letter-spacing: .06em; border-bottom: 1px solid var(--cherry); padding-bottom: 2px; }
.ver:hover { text-decoration: none; }

/* --- Specimen page: hero --------------------------------------------- */
.coffee .hero { display: grid; grid-template-columns: 1fr 240px; gap: 60px; align-items: center; padding: 18px 0 60px; }
.hero .kick { font-size: 1rem; color: var(--on-cherry); margin-bottom: 20px; }
.hero h1 { font-weight: 500; font-size: clamp(2.6rem, 5vw, 4.2rem); line-height: 1.01; color: var(--on); letter-spacing: -.012em; }
.hero .origin { font-weight: 400; font-size: 1.4rem; color: var(--on-muted); margin-top: 18px; font-variant: small-caps; letter-spacing: .05em; }
/* The region's landing link inside the origin line. On the cover, `color:
   inherit` plus the stripped hover underline leaves a link with no affordance
   at all — the same trap the landing crumb documents below — so the quiet
   cherry underline is what says this word goes somewhere. */
.hero .origin a { text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--on-cherry-quiet); text-underline-offset: 3px; transition: text-decoration-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out); }
.hero .origin a:hover { color: var(--on); text-decoration-color: var(--on-cherry); }
.hero .plate { color: var(--on); text-align: center; }
.hero .plate svg { opacity: .78; margin-inline: auto; }
.hero .plate .cherry { fill: var(--on-cherry); stroke: none; }
.hero .plate .lc { margin-top: 12px; font-size: .84rem; color: var(--on-muted); font-variant: small-caps; letter-spacing: .1em; }

/* --- Specimen page: dossier ------------------------------------------ */
.dossier { padding-block: 60px 100px; }
.specs { display: flex; flex-wrap: wrap; gap: 26px 56px; }
.spec { display: flex; flex-direction: column; }
.spec .k { font-size: .7rem; color: var(--muted); font-variant: small-caps; letter-spacing: .09em; margin-bottom: 6px; }
/* `pretty`, not the browser default: a spec value is a short piece of prose in
   a narrow column, and the default greedy wrap leaves runts. Measured across
   the 18 variety fact blocks — 13 of 96 values ended on a last line under 30%
   of the column, the worst being "1343" alone under "Caturra × Híbrido de
   Timor CIFC". It changes no line counts, so nothing reflows and no height
   moves; it only decides where the break lands. Global on `.spec` rather than
   scoped to the term article, because the rule is true wherever this component
   is narrow — the ficha's values are short today and simply don't trigger it. */
.spec .v { font-size: 1.4rem; font-weight: 500; text-wrap: pretty; }
.spec .v a, .fam-link { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 4px; }
.spec .v a:hover, .fam-link:hover { color: var(--cherry); }
.spec .sug { font-size: 1.05rem; font-weight: 400; color: var(--muted); }
.spec .sug a { text-decoration-color: var(--hair); }
.spec .sug em { font-style: normal; font-variant: small-caps; letter-spacing: .06em; font-size: .82rem; color: var(--faint); }
.spec .stated { display: block; margin-top: 5px; font-size: .82rem; font-style: italic; color: var(--faint); }
.drow { margin-top: 64px; }
.drow > .h { display: block; font-size: .95rem; color: var(--muted); font-variant: small-caps; letter-spacing: .12em; margin-bottom: 18px; }
.drow > .h--pick { color: var(--cherry); font-weight: 500; }
/* The site's one long-form prose setting: the ficha's editorial paragraph and
   the term landing's article are the same voice at the same size, so they share
   the declaration rather than each carrying its own near-miss of it. */
.lead, .term-note .prose p, .term-note .prose li { font-size: 1.1rem; line-height: 1.6; max-width: var(--measure); }
.notes-lg { font-weight: 300; font-size: 2rem; line-height: 1.25; max-width: none; }
.notes-lg span { color: var(--hair); }
.dossier .fams { gap: 24px; }
.dossier .fams .fam { font-size: 1.1rem; }
.dossier .scales { border-top: 0; padding-top: 0; gap: 36px; }

.foot { margin-top: 64px; }
.foot .cta { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.foot .cta form { margin: 0; }
.src-note { font-size: .8rem; color: var(--faint); font-variant: small-caps; letter-spacing: .06em; }
.buy { font-size: 1rem; font-weight: 600; font-family: inherit; color: var(--paper); background: var(--cherry); border: 0; border-radius: var(--radius-button); padding: 14px 24px; font-variant: small-caps; letter-spacing: .06em; display: inline-block; cursor: pointer; transition: background-color var(--dur) var(--ease-out); }
/* The one solid button on the site had no pointer feedback at all — it only
   suppressed the underline. Ripening toward ink keeps it the same cherry and
   raises the label's contrast rather than lowering it. */
.buy:hover { background: color-mix(in oklch, var(--cherry) 88%, var(--ink)); text-decoration: none; }
.buy:active { background: color-mix(in oklch, var(--cherry) 78%, var(--ink)); }

/* --- Roaster badge kit (F4.1): quiet, secondary, below the fold ------- */
.badge-kit { margin-top: 56px; padding-top: 28px; border-top: 1px solid var(--hair); max-width: 560px; }
.badge-kit .h { display: block; font-size: .85rem; color: var(--muted); font-variant: small-caps; letter-spacing: .08em; }
.badge-kit p { color: var(--faint); font-size: .95rem; margin: 6px 0 20px; max-width: 46ch; }

/* One self-contained embed unit: live badge + copy action in the header, the
   code below. Contained because it's a control, not a content card. */
.badge-kit .embed { border: 1px solid var(--hair); border-radius: var(--radius-card); overflow: hidden; background: var(--surface); }
.badge-kit .embed-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px; padding: 14px 16px; background: var(--paper); border-bottom: 1px solid var(--hair); }
.badge-kit .preview { display: inline-flex; line-height: 0; }
.badge-kit .preview a { text-decoration: none; }
.badge-kit .copy { display: inline-flex; align-items: center; gap: 7px; font-family: inherit; font-size: .85rem; font-variant: small-caps; letter-spacing: .06em; color: var(--ink); background: transparent; border: 1px solid var(--muted); border-radius: var(--radius-button); padding: 8px 16px; cursor: pointer; transition: color .15s ease, background-color .15s ease, border-color .15s ease; }
.badge-kit .copy svg { width: 15px; height: 15px; flex: none; }
.badge-kit .copy:hover { color: var(--paper); background: var(--ink); border-color: var(--ink); }
.badge-kit .code { display: block; width: 100%; box-sizing: border-box; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .76rem; line-height: 1.6; color: var(--muted); background: transparent; border: 0; padding: 13px 16px; }
.badge-kit .code:focus-visible { outline: 2px solid var(--cherry); outline-offset: -2px; }

/* --- Atlas body: rail + main ----------------------------------------- */
.atlas-body { display: grid; grid-template-columns: 196px 1fr; gap: 48px; }
.atlas-main { min-width: 0; }
.listhead { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 22px; }
/* DESIGN.md sets the label at 500; these were inheriting the body's 400, so
   the hub's <h2> section labels (which take 500 from the base heading rule)
   would otherwise have read heavier than the identical label one click away. */
.listhead .h { font-size: .85rem; font-weight: 500; color: var(--muted); }

/* --- Browse rail ----------------------------------------------------- */
.rail .fh { font-size: .82rem; color: var(--muted); margin-bottom: 11px; margin-top: 28px; }
.rail .fh:first-child { margin-top: 0; }
.rail .fh a { color: inherit; }
.rail .fh a:hover { color: var(--cherry); text-decoration: none; }
.rail ul { list-style: none; }
.rail-link { display: flex; align-items: center; gap: 9px; padding: 5px 0; font-size: 1rem; color: var(--ink); }
.rail-link:hover { color: var(--cherry); text-decoration: none; }
.rail-link.on .rail-name { font-weight: 600; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 4px; }
.rail-link em { margin-left: auto; font-style: normal; color: var(--faint); font-size: .82rem; font-variant: small-caps; }

/* --- Landing header (taxonomy pages) --------------------------------- */
.landing-head { padding: 32px 0 40px; }
.landing-head .crumb { color: var(--on-muted); font-size: .9rem; margin-bottom: 16px; }
.landing-head .crumb b { color: var(--on); font-weight: 500; }
/* The crumb's parent link is the way back up, and it had no rest state at all:
   the global `a { text-decoration: none }` stripped the underline and `.cover a
   { color: inherit }` gave it the same --on-muted as the "›" beside it, so the
   only affordance was a hover underline — nothing on touch, nothing for a
   reader who is not already pointing at it. The link was in the markup and in
   the sitemap and invisible on the page, which is a back-link a crawler can
   follow and a person cannot.
   The text stays --on-muted on purpose: the crumb's hierarchy is parent quieter
   than current page, and brightening it to --on would flatten it against the
   <b>. The underline is what carries the affordance. */
.landing-head .crumb a { text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--on-cherry-quiet); text-underline-offset: 3px; transition: text-decoration-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out); }
.landing-head .crumb a:hover { color: var(--on); text-decoration-color: var(--on-cherry); }
.landing-head h1 { font-weight: 400; font-size: clamp(2.4rem, 4vw, 3.6rem); color: var(--on); letter-spacing: -.015em; }
.landing-head .lede { color: var(--on-muted); font-size: 1.15rem; margin-top: 14px; max-width: 56ch; font-weight: 300; }
/* The two things that sit under a lede on this cover: a term's kin, and a gloss
   on one of the page's own words (the *marcas* bridge on /tostadores). Both are
   the lede's quieter neighbour, so they share one rule rather than two that
   drift. Weight stays at the inherited 400: the lede above may be 300 because
   it is display-adjacent, but this is body copy at .95rem on the dark cover,
   where light type reads lighter still (DESIGN §Readable-Weight). */
.landing-head .term-kin,
.landing-head .gloss { color: var(--on-muted); font-size: .95rem; margin-top: 14px; max-width: 64ch; }
/* `on-cherry-quiet`, not `cherry`: this rule has always lived on the wood-green
   cover, where plain cherry is 2.4:1 and the underline it draws is the only
   thing telling a reader these words are links — a boundary, not a separator,
   so it takes the ≥3:1 the Hairline-on-Green Rule asks for. Rest and hover are
   the same two steps the crumb and .roaster-links use, so the cover has one
   link idiom rather than three rules each inventing a cherry. */
.landing-head .term-kin a { color: var(--on); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--on-cherry-quiet); text-underline-offset: 3px; transition: text-decoration-color var(--dur) var(--ease-out); }
.landing-head .term-kin a:hover { text-decoration-color: var(--on-cherry); }
.landing-head .term-kin .fam { display: inline-flex; align-items: center; gap: 6px; }

/* --- Term editorial (the curated text on a landing) ------------------- */
/* An article between the cover and the browse tool: the prose on its measure,
   the declared facts beside it. The facts reuse the ficha's `.specs` /
   `.spec .k` / `.spec .v` exactly — a variety's lineage is the same kind of
   statement as a coffee's proceso, so it is the specimen spec in a column, not
   a new component. The hairline is the one genuine separation on the page:
   above it the atlas says what the term is, below it the tool for browsing it. */
/* The hairline closes the article, so it sits nearer the prose above it than
   the tool below — the separation is what it ends, not what it introduces. */
.term-note { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-md) var(--space-lg); padding-bottom: var(--space-md); margin-bottom: var(--space-lg); border-bottom: 1px solid var(--hair); }
/* One row that wraps, not a grid with a breakpoint: the facts stay beside the
   prose while the prose can still hold a readable measure, and drop underneath
   the moment it cannot. On a tablet that is the difference between a 46-character
   column and a full one, and there is no breakpoint to keep in sync. */
.term-note .prose { flex: 1 1 480px; }
.term-note .term-facts { flex: 1 1 240px; max-width: 260px; }
/* Paragraph size and measure come from the .lead rule above, which the list
   items share: a list here is the same voice at the same size, set as a list
   because what it holds is parallel. */
.term-note .prose p + p { margin-top: var(--space-md); }
/* A list where the prose has three parallel things to say (the three beneficio
   families). No disc and no rule: structure from air and a hanging term
   (DESIGN §Overview — "not from boxes and rules"), which is also why the term
   is the link, since it is both the word you scan for and the way out.
   `list-style-type: ""` rather than `none`: an empty marker keeps the list
   semantics VoiceOver drops on `list-style: none`, and there is no `role="list"`
   to add — this markup comes from a markdown file. */
.term-note .prose ul { list-style-type: ""; padding: 0; }
/* Tighter between siblings than between paragraphs: the three read as one
   group, which is the whole reason they left the paragraph. */
.term-note .prose li + li { margin-top: var(--space-sm); }
.term-note .prose p + ul, .term-note .prose ul + p { margin-top: var(--space-md); }
/* The article's emphasis weight. `<strong>` takes the browser's `bolder` (700)
   and this system has no 700: DESIGN.md sets display 300, body 400, and 500 for
   both the title and the label, so 500 is what "heavier" means here. It is the
   weight the term leading each list item carries, and the one on the
   *Variedad o varietal* lead-in one dimension over. */
.term-note .prose strong { font-weight: 500; }
.term-note .prose a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 4px; transition: color var(--dur) var(--ease-out); }
.term-note .prose a:hover { color: var(--cherry); }
/* Stacked, not wrapped: one fact per row reads as a spec sheet down the side,
   where the ficha's row of specs runs across the full width. */
.term-note .specs { flex-direction: column; gap: 18px; }
/* The ficha's 1.4rem is set for a row across the full width; in a 260px column
   it wraps every second value. 1.15rem is the size .roaster-specs already
   settled on for the same block in a tighter place. */
.term-note .spec .v { font-size: 1.15rem; line-height: 1.35; }
/* V1b — the disclosure that collapses the article on a phone. Hidden here and
   shown only inside the phone breakpoint below, so with JavaScript off (no
   `is-clamped`) and on every desktop it does not exist for the reader. */
.term-note .prose-more { display: none; }
/* V3 — the same article on a taxonomy index, where nothing sits beside it: a
   dimension has a definition, not a fact column. One item makes the flex row a
   no-op and would leave the source line trying to share it, so the section is a
   block; the paragraphs hold their measure from the .lead rule either way, and
   every phone rule below keys off `.term-note .prose`, so the clamp comes with
   it unchanged. */
.term-note--dimension { display: block; }
.term-note .src-note { display: block; margin-top: var(--space-md); }
.term-note .src-note a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--hair-strong); text-underline-offset: 3px; transition: color var(--dur) var(--ease-out), text-decoration-color var(--dur) var(--ease-out); }
.term-note .src-note a:hover { color: var(--cherry); text-decoration-color: var(--cherry); }

/* --- Notas index (grouped by family) ---------------------------------- */
.note-group { margin-bottom: 44px; }
.note-group h2 { font-size: .95rem; color: var(--muted); font-weight: 500; letter-spacing: .06em; margin-bottom: 16px; }
.note-group h2 a { color: inherit; display: inline-flex; align-items: center; gap: 8px; }
.note-group h2 a:hover { color: var(--cherry); text-decoration: none; }

/* --- El paseo (similar coffees, each card says why) ------------------- */
.paseo-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 18px; }
.paseo-card { background: var(--surface); border-radius: var(--radius-card); padding: 20px 22px 18px; display: flex; flex-direction: column; }
.paseo-card .why { font-size: .74rem; letter-spacing: .09em; color: var(--cherry); font-weight: 600; }
.paseo-card .name { font-size: 1.25rem; font-weight: 500; line-height: 1.15; margin-top: 9px; }
.paseo-card .name a { color: inherit; }
.paseo-card .name a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.paseo-card .by { margin-top: 4px; }
.paseo-card .cf { margin-top: auto; padding-top: 16px; }

/* --- Explorar -------------------------------------------------------- */
/* The cover and the coffees it describes need air between them; the grid used
   to start hard against the cover's edge. */
.explore { padding-block: 38px 88px; }
.ask { text-align: center; padding: 72px 0 16px; }
.ask h1 { font-weight: 300; font-size: clamp(2.4rem, 5vw, 3.6rem); letter-spacing: -.015em; }
/* `.ask` only ever sits inside the wood-green cover, so its lede takes the
   cover's secondary ink. It was --muted, which is tuned against paper and
   measures about 2:1 on the green — under any text floor. Same correction the
   --faint pass made, one surface over. */
.ask p { color: var(--on-muted); font-size: 1.25rem; margin-top: 14px; font-weight: 300; max-width: none; }

/* The way in for a reader who cannot answer "¿qué buscás?" yet — the mirror of
   the "cruzá características" line, which is for the reader who already can.
   Cherry on the cover (the brightened accent), used once per page. */
.quiz-entry {
  display: inline-block; margin-top: 20px; color: var(--on-cherry);
  font-variant: small-caps; letter-spacing: .06em; font-size: .98rem;
  border-bottom: 1px solid color-mix(in oklch, var(--on-cherry) 55%, transparent);
  padding-bottom: 2px;
  transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}
.quiz-entry:hover { color: var(--on); border-bottom-color: var(--on); text-decoration: none; }
.explore-section { padding: 40px 0 8px; }
.explore-section .lbl { font-size: .85rem; color: var(--muted); text-align: center; margin-bottom: 26px; }
.intents { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
/* The taste-first choice card (DESIGN.md `intent-card`), shared by /explorar's
   doors and the quiz's options: the same affordance doing the same job, so it
   has one set of metrics. Only the shell is shared — a door is a link, an
   option is a radio, and their states differ below. */
.intent, .qopt { background: var(--surface); border-radius: 6px; padding: var(--space-md); color: var(--ink); display: flex; flex-direction: column; gap: 8px; }
.intent .t, .qopt .t { font-size: 1.5rem; font-weight: 400; line-height: 1.15; }
.intent .d, .qopt .d { font-size: .98rem; color: var(--muted); }
.intent:hover { text-decoration: none; }
.intent:hover .t { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.intent .bar { width: 34px; height: 5px; border-radius: 3px; }
.intent .bar--surprise { background: linear-gradient(90deg, var(--fam-frutos-rojos), var(--fam-citricos), var(--fam-florales)); }
.intent .t { margin-top: 6px; }
.intent .go { margin-top: auto; padding-top: 14px; font-variant: small-caps; letter-spacing: .08em; color: var(--cherry); font-size: .95rem; }
/* --- The filter: its headline, its facets (Slice 2) -------------------------------------------
   The query is the page's headline; a strip of pills below it mirrors that
   headline word for word, and clicking one opens its options underneath. Every
   option is a link, so the control works with no JavaScript — the pills are
   simply hidden then and all six rows stay open. `filter--live` is the switch.
   Chips follow DESIGN.md: pill, 1px hairline, transparent fill, quiet. Borders
   use --on-hair, the cover's answer to --hair, so a boundary on the green
   clears 3:1 instead of dissolving into it. */
.filter { display: block; }
.filter-headline {
  font-weight: 300; font-size: clamp(1.45rem, 4.6vw, 2.8rem); line-height: 1.28;
  letter-spacing: -.015em; color: var(--on); max-width: 24ch; padding-top: 10px;
}
.filter-headline .noun { color: var(--on-muted); }

.pills { display: none; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 20px; }
.filter--live .pills { display: flex; }
.pills .lbl { font-size: .72rem; color: var(--on-muted); margin-right: 6px; }

/* The pill is the frame; the button inside carries the padding, so the whole
   label is clickable rather than sitting in dead space the span owned. */
.pill {
  display: inline-flex; align-items: center;
  border: 1px solid var(--on-hair); border-radius: var(--radius-pill);
  color: var(--on-muted); white-space: nowrap;
  transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.pill button {
  display: inline-flex; align-items: baseline; gap: 8px;
  font: inherit; color: inherit; background: none; border: 0;
  padding: 7px 15px; cursor: pointer;
}
.pill .noun { font-size: .74rem; font-variant: small-caps; letter-spacing: .09em; }
/* The ring belongs to the pill, not to the button inside it: on the button it
   drew a rectangle around a rounded shape. */
.pill button:focus-visible { outline: none; }
.pill:has(button:focus-visible) { outline: 2px solid var(--on-cherry); outline-offset: 2px; }
.pill.set { border-color: var(--on-cherry); color: var(--on); }
.pill.set .noun { color: var(--on-muted); }
.pill.add { border-style: dashed; }
/* Hover after the state classes, so a chosen pill answers the pointer too —
   it kept its accent border and looked inert before. */
.pill:hover, .pill:focus-within { color: var(--on); border-color: var(--on); }
.pill.set:hover, .pill.set:focus-within { border-color: var(--on-cherry); background: color-mix(in oklch, var(--on) 10%, transparent); }
.pill:active { background: color-mix(in oklch, var(--on) 16%, transparent); }
.pill.open, .pill.open:hover { background: var(--on); color: var(--cover); border-color: var(--on); }
.pill.open .noun, .pill.open .x { color: var(--cover); }
/* The × is its own control: give it a real hit area (44px on touch) and pull
   the extra back with negative margin so the pill keeps its height. */
.pill .x {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; min-height: 26px; margin: -4px 2px -4px -6px;
  color: var(--on-cherry); font-size: .95rem; line-height: 1;
}
.pill .x:hover { color: var(--on); text-decoration: none; }

/* One dimension's options. Open by default — that is the no-JavaScript page. */
.panel {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid color-mix(in oklch, var(--on-muted) 28%, transparent);
}
.filter--live .panel { display: none; }
.filter--live .panel.is-open { display: flex; animation: panel-in var(--dur) var(--ease-out) both; }
@keyframes panel-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.panel .lbl { font-size: .72rem; color: var(--on-muted); margin-right: 6px; }
.opt {
  display: inline-flex; align-items: baseline; gap: 7px;
  border: 1px solid var(--on-hair-strong); border-radius: var(--radius-pill);
  padding: 6px 13px; font-size: .93rem; white-space: nowrap; color: var(--on-muted);
  transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.opt:hover { border-color: var(--on); color: var(--on); background: color-mix(in oklch, var(--on) 8%, transparent); text-decoration: none; }
.opt:active { background: color-mix(in oklch, var(--on) 16%, transparent); }
/* The chosen option carries a check as well as the accent: colour alone must
   never be the signal (PRODUCT.md), and the check also says "click to undo". */
.opt.now { border-color: var(--on-cherry); color: var(--on); }
.opt.now::before { content: "✓"; font-size: .8em; color: var(--on-cherry); margin-right: -1px; }
.opt .c { font-size: .76rem; color: color-mix(in oklch, var(--on-muted) 85%, var(--cover)); font-variant: small-caps; }
.opt:hover .c, .opt.now .c { color: var(--on-muted); }

.filter-answer { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 16px; margin-top: 20px; padding-bottom: 32px; font-size: .8rem; max-width: none; }
.filter-answer .n { color: var(--on-cherry); }
.filter-answer .quiet { color: var(--on-muted); border-bottom: 1px solid color-mix(in oklch, var(--on-muted) 40%, transparent); transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out); }
.filter-answer .quiet:hover { color: var(--on); border-bottom-color: var(--on); text-decoration: none; }

/* Touch: every control clears the 44px target the hand needs. */
@media (pointer: coarse) {
  .pill button { padding-block: 11px; }
  .pill .x { min-width: 44px; min-height: 44px; margin-block: -11px; }
  .opt { padding-block: 10px; }
  /* The inline action link ("Ver ficha →", "Seguir el paseo →", "Responder de
     nuevo →") sits at 19px, which is a thumb's worth of missing. The hit area
     is extended with a pseudo-element rather than padding: padding would push
     the cherry underline down off the text, and this shifts no layout at all. */
  .ver { position: relative; }
  .ver::after { content: ""; position: absolute; inset: -13px -10px; }
  /* The vocabulary hub's term lists are the one dense set of links a phone
     actually gets — the rail, which is just as dense, is hidden below 760px —
     so the rows open up and each term carries its own vertical padding. Inline
     text links are exempt from the target-size rule; a list this tightly packed
     still misses often enough to be worth the extra leading. */
  .vocab-terms { line-height: 2.6; }
  .vocab-terms a { display: inline-block; line-height: 1.25; padding-block: 10px; }
}

/* The reader who skips the taste door entirely. */
.explore-cross { margin-top: 44px; padding-top: 24px; border-top: 1px solid var(--hair); text-align: center; color: var(--muted); max-width: none; }

/* Nothing crosses everything (IF3). */
.no-match { padding: 8px 0 8px; }
.no-match .h { display: block; font-size: .85rem; color: var(--muted); margin-bottom: 14px; }
.no-match .lead { margin-top: 10px; }

.chips { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
/* --hair-strong, not --hair: the pill's outline is the only thing that says
   this is a control (the label is plain ink, no underline), so it is a
   component boundary at 3:1 rather than a separator. And a chip answers press,
   not only hover (DESIGN §Motion) — the same colour-mix fill .buy, .pill, .opt
   and .qopt already answer with. */
/* `background: transparent` belongs here rather than on a variant: a chip may be
   a <button>, whose UA background is buttonface, and normalizing it at the
   component keeps :active from being outranked by a more specific variant
   selector that had to restate the fill. */
.chip { border: 1px solid var(--hair-strong); border-radius: var(--radius-pill); padding: 11px 22px; font-size: 1.05rem; color: var(--ink); background: transparent; transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out); }
.chip:hover { border-color: var(--cherry); text-decoration: none; }
.chip:active { background: color-mix(in oklch, var(--cherry) 10%, transparent); }
.explore-paseo { margin-top: 48px; padding-top: 26px; border-top: 1px solid var(--hair); text-align: center; }
.explore-paseo .l { font-size: .85rem; color: var(--muted); margin-bottom: 14px; }
.explore-paseo a { font-size: 1.15rem; color: var(--ink); }
.explore-paseo a:hover { text-decoration: underline; text-decoration-color: var(--cherry); text-underline-offset: 3px; }

/* --- The quiz (QZ4) --------------------------------------------------- */
/* One question at a time when JavaScript is there, all three at once when it
   is not — `quiz--live` is the switch, and like the filter's it only ever
   hides what the markup already showed. The options are DESIGN.md's intent
   card at option scale: a surface tint, a serif label, an optional descriptor,
   no borders. The radio itself is hidden from sight but not from the
   accessibility tree, so the whole card is the target and the keyboard still
   gets a real radio group. */
/* One question per screen, sitting in the middle of it. Without this the
   question hangs off the cover with the rest of the viewport empty below, which
   reads as content that ran out rather than as a question asked on its own.
   `safe center` because the no-JavaScript page stacks all three questions: past
   one screen the block must flow from the top, not centre itself and clip. */
.quiz { display: grid; grid-template-rows: auto 1fr; min-height: 100svh; }
/* `width: 100%` because .wrap centres itself with `margin-inline: auto`, and an
   auto margin on a grid item beats the default stretch — without it the wrap
   collapses to its content width and the option grid drops to one column. */
.quiz-body { width: 100%; display: flex; flex-direction: column; justify-content: safe center; padding-block: var(--space-lg) var(--space-xl); }

/* The scroll margins are what the controller's scrollIntoView lands against:
   a question arrives with air above it rather than flush to the top edge, and
   the button clears the bottom edge by the same amount. */
.q { border: 0; padding: 0; margin-bottom: var(--space-xl); scroll-margin-top: var(--space-lg); }
.q legend { display: block; padding: 0; margin-bottom: 18px; }
.q .step { display: block; font-size: .78rem; color: var(--faint); margin-bottom: var(--space-sm); }
/* A `<legend>` is the group's accessible name, so the prompt is a span inside
   it rather than a heading — which also puts it outside the global h1–h3
   balance rule, hence the declaration here. */
.q .prompt { display: block; font-weight: 400; font-size: clamp(1.7rem, 3.4vw, 2.4rem); line-height: 1.14; letter-spacing: -.015em; text-wrap: balance; }
.quiz--live .q { display: none; }
.quiz--live .q.is-open { display: block; animation: panel-in var(--dur) var(--ease-out) both; }

.opts { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 18px; }
/* Shell and type come from `.intent` above; what is added here is what a
   control needs and a link does not — a pointer, and the states. The border is
   transparent at rest so checking one cannot shift the layout by a pixel. */
.qopt {
  cursor: pointer; border: 1px solid transparent;
  transition: border-color var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}
/* Clipped rather than hidden: `display:none`, `visibility:hidden` and even
   `opacity:0` drop the input out of the accessibility tree or out of the tab
   order, and then the question has no control at all. Clipped, it is a real
   radio group — arrow keys move through it, the label is the target, and the
   ring below draws on the card. */
.qopt input {
  position: absolute; width: 1px; height: 1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
/* The engraved mark sits where the intent card's colour bar sits: first, above
   the title. Quiet by default so the words still lead; the taste question
   overrides the colour inline with its family's own. */
.qopt .mark { display: block; color: var(--muted); margin-bottom: 2px; }
.qopt:hover .mark { color: var(--ink); }
.qopt:has(input:checked) .mark { color: var(--cherry); }
.qopt:hover { border-color: var(--hair); background: color-mix(in oklch, var(--surface) 92%, var(--ink)); }
/* Press feedback, the same gesture `.opt` and `.pill` answer with in the
   filter — a card you can click is a card that should react to being clicked. */
.qopt:active { background: color-mix(in oklch, var(--surface) 86%, var(--ink)); }
.qopt:has(input:checked) { border-color: var(--cherry); }
.qopt:has(input:focus-visible) { outline: 2px solid var(--cherry); outline-offset: 2px; }

/* The commitment, and the page's single cherry gesture. Always there without
   JavaScript; with it, held back until the third answer and then brought in on
   the same rise the questions use, so it reads as the flow arriving somewhere
   rather than a control that was hiding. */
/* Right-aligned, the way every "Ver ficha →" sits at the end of its card (.cf):
   it is what comes *after* the options, and the eye leaves a row of cards on
   the right. */
.qgo { display: flex; justify-content: flex-end; margin-top: var(--space-md); scroll-margin-bottom: var(--space-lg); }
.quiz--live .qgo { display: none; }
.quiz--live .qgo.is-ready { display: flex; animation: panel-in var(--dur) var(--ease-out) both; }

/* --- The quiz result -------------------------------------------------- */
/* Presented as a specimen: it borrows the ficha's cover, hero and dossier
   wholesale, and adds only the two things a recommendation has that a ficha
   does not — the why, and the facets it matched. */
.quiz-result .hero .prov { font-size: 1.02rem; color: var(--on-muted); margin-top: 10px; }
.quiz-result .hero .why { font-size: 1.2rem; line-height: 1.5; color: var(--on-muted); margin-top: 22px; max-width: 46ch; font-weight: 300; }
/* The dossier opens on a row here, where the ficha opens on its spec block — so
   the row's own 64px would land on top of the section's 60px. Whichever row
   comes first: with nothing matched there are no facets and the click-out
   leads, and that gap was the same double one. */
.quiz-result .dossier > :first-child { margin-top: 0; }
.qfacets { display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-md); }
.qfacet { display: inline-flex; align-items: baseline; gap: 8px; font-size: 1.25rem; font-weight: 500; }
.qfacet .noun { font-size: .74rem; font-weight: 400; color: var(--muted); font-variant: small-caps; letter-spacing: .09em; }
.qfacet a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 4px; }
.qfacet a:hover { color: var(--cherry); }
.qspec .o { font-size: 1.05rem; color: var(--muted); font-variant: small-caps; letter-spacing: .04em; }
.qspec .vp { font-size: 1.05rem; color: var(--faint); margin-top: 4px; }
.qficha { margin-top: var(--space-md); }
/* The closing block, on the ficha's badge-kit shape: a hairline to separate it
   from the walk above, a line of copy, one action. It was a bare grey link
   floating in the whitespace under the alternates, which read as something left
   behind rather than the way back into the quiz. */
.qagain { margin-top: var(--space-xl); padding-top: var(--space-md); border-top: 1px solid var(--hair); }
.qagain .l { display: block; font-size: .85rem; color: var(--muted); margin-bottom: var(--space-sm); }

/* --- Taxonomy index (term cards) -------------------------------------- */
.term-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px; }
.term-card { background: var(--surface); border-radius: var(--radius-card); padding: 24px 26px; color: var(--ink); display: flex; flex-direction: column; gap: 8px; }
.term-card:hover { text-decoration: none; }
.term-card .name { font-size: 1.5rem; font-weight: 500; display: flex; align-items: center; gap: 10px; }
.term-card:hover .name { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.term-card .d { font-size: .98rem; color: var(--muted); max-width: none; }
/* Grid rows stretch, so a card whose name or description wraps one line short
   used to float its footer mid-card while its neighbour's sat at the bottom.
   Pinned like the paseo card's: `auto` takes the slack, the padding keeps the
   24px it always had when there is none to take. */
.term-card .cf { justify-content: space-between; align-items: baseline; margin-top: auto; padding-top: 16px; }
.term-card .count { font-size: .82rem; color: var(--faint); }
.empty-note { color: var(--muted); }

/* --- Comparison hub (S3.2) -------------------------------------------- */
/* One term grid per pair type. Air between the sections, not a rule: the
   listhead label is the only separation they need. */
.hub-section + .hub-section { margin-top: var(--space-xl); }
/* A cross card carries a name and a count, where a term card also carries a
   descriptive line — and here the rail takes 196px the taxonomy index does not
   spend. A narrower track buys back the third column, so the hub keeps the same
   grid rhythm as /variedades instead of running 34 crosses down two. */
.hub-section .term-grid { grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); }

/* --- Vocabulary hub (M1) ---------------------------------------------- */
/* Six flat plates, one per dimension — the herbarium grid, not cards inside
   cards: the terms are inline links on the plate itself. A wider track than the
   term grid, because a plate carries a sentence and a term list rather than a
   name and a count.

   `min(340px, 100%)` rather than a bare 340px: an auto-fill track keeps its
   minimum even when the container is narrower, so the plate ran 28px past the
   viewport on a 360px phone. The min() lets the track collapse to the width
   that actually exists. */
.vocab-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: 18px; }
.vocab-plate { background: var(--surface); border-radius: var(--radius-card); padding: 26px 28px; display: flex; flex-direction: column; gap: 12px; }
/* Name over its measure, the way a herbarium label is written — not name and
   count on one line. Sharing the line cost the heading ~85px, which wrapped
   "Métodos de preparación" onto a second line and pushed that plate's whole
   body a line below its neighbours'. Stacked, all six heads are one line, and
   every blurb, term list and link sits at the same height across a row. */
.vocab-plate .name { font-size: 1.5rem; font-weight: 500; }
.vocab-plate .name a { color: var(--ink); }
.vocab-plate .name a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.vocab-plate .count { display: block; margin-top: 5px; font-size: .82rem; color: var(--faint); }
.vocab-plate .d { font-size: .98rem; color: var(--muted); max-width: none; }
/* The terms read as a line of the atlas's own words, so they are separated by
   the same middle dot the ficha and the rail use — not chips, which would make
   six lists of pills compete with each other down the page. */
.vocab-terms { color: var(--faint); line-height: 1.9; }
/* Inline, not inline-flex: a flex box is an atomic inline, and the non-breaking
   space that glues the "·" to the term before it does not hold across one — the
   list wraps with the separator leading the next line. `nowrap` keeps a family's
   dot with its own label instead. */
.vocab-terms a { color: var(--ink); white-space: nowrap; }
.vocab-terms a:hover { color: var(--cherry); text-decoration: none; }
.vocab-terms .fam-dot { margin-right: 6px; }
.vocab-plate .ver { align-self: flex-start; margin-top: auto; padding-top: 4px; }
.vocab-floor { color: var(--faint); font-size: .92rem; margin-top: 32px; }

/* --- Roaster profiles ------------------------------------------------ */
.roaster-specs { margin-bottom: 48px; }
.roaster-specs .v { font-size: 1.15rem; }
.listhead--archive { margin-top: 64px; }
/* The roaster's own channels, on the cover. Padding rather than margin carries
   the gap, so the tap target is ~40px tall instead of the 15px the text alone
   gave; the row sits where it always did. */
.roaster-links { margin-top: 4px; display: flex; flex-wrap: wrap; column-gap: 26px; }
.roaster-links a { display: inline-flex; align-items: center; gap: 8px; padding-block: 10px; color: var(--on-muted); font-variant: small-caps; letter-spacing: .06em; font-size: .95rem; transition: color var(--dur) var(--ease-out); }
/* Underlined at rest, not only on hover: these are plain small-caps text on the
   cover, so nothing else says "link" to a reader who cannot hover — the mark
   identifies the channel, it does not signal that the words are clickable
   (DESIGN §Separator-vs-Boundary). Cherry brightened for the green, per
   §Buy/links and the Hairline-on-Green Rule — through --on-cherry-quiet, which
   is where the 60% knock-back that used to be written here went. Brightening to
   --on-cherry and then taking 60% of it composited to 2.4:1 over --cover: the
   same number plain --cherry gives, so the correction this comment describes
   was being spent and handed straight back. */
.roaster-links .lbl { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; text-decoration-color: var(--on-cherry-quiet); transition: text-decoration-color var(--dur) var(--ease-out); }
.roaster-links a:hover { color: var(--on); text-decoration: none; }
.roaster-links a:hover .lbl { text-decoration-color: var(--on-cherry); }
/* Nudged up half a pixel: the flex box centres on the full font box, but
   small-caps sits high in it, so a geometrically centred mark reads low. */
.roaster-links svg { width: 15px; height: 15px; flex: 0 0 auto; opacity: .72; transform: translateY(-.5px); transition: opacity var(--dur) var(--ease-out); }
.roaster-links a:hover svg, .roaster-links a:focus-visible svg { opacity: 1; }
/* Plain --cherry falls to 2.4:1 on the cover; the ring follows .pill's lead. */
.roaster-links a:focus-visible { outline-color: var(--on-cherry); outline-offset: 3px; border-radius: 2px; }
.roaster-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px; }
.roaster-card { background: var(--surface); border-radius: var(--radius-card); padding: 24px 26px; color: var(--ink); display: flex; flex-direction: column; gap: 5px; }
.roaster-card:hover { text-decoration: none; }
.roaster-card .name { font-size: 1.5rem; font-weight: 500; }
.roaster-card:hover .name { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.roaster-card .o { color: var(--muted); font-variant: small-caps; letter-spacing: .04em; }
/* V5/SH3 — the shipping reach, sharing the place's one line. `faint` keeps the
   place primary and stops an identical phrase on 14 of 19 cards drumming down
   the grid; it is the tertiary-metadata token and still clears 4.5:1 on both
   surface and paper. `nowrap` because at 320px the two longest lines wrap, and
   the phrase has to move whole rather than orphan "país" on its own row. */
.roaster-card .o .reach { color: var(--faint); white-space: nowrap; }
.roaster-card .cf { margin-top: 10px; }
/* PL2 — the uncatalogued card. Not a specimen in the collection, so it does
   not sit on `surface`: the tonal step down to bare paper is the mark, per the
   air-before-borders rule — no dashed border, no badge. The reason is body
   text in the quiet voice, and the only links go out to the roaster itself. */
.roaster-card--uncatalogued { background: transparent; }
.roaster-card--uncatalogued .gap { font-size: .95rem; line-height: 1.5; color: var(--muted); margin-top: 2px; max-width: 34ch; }
.roaster-card--uncatalogued .cf { display: flex; gap: 18px; }
.roaster-card--uncatalogued .cf a:hover { text-decoration: none; }

/* --- Province landing (V4/PL4) --------------------------------------- */
/* One block per roaster: the listhead names it, the grid shows its stock.
   Rhythm from spacing alone, the same 64px step the archive listhead takes. */
.province-roaster + .province-roaster .listhead,
.province-also .listhead { margin-top: 64px; }

/* --- Featured editorial card (index) -------------------------------- */
.feat { background: var(--surface); border-radius: var(--radius-card); padding: 28px 30px; display: grid; grid-template-columns: 1fr 130px; gap: 24px; margin-bottom: 22px; align-items: center; }
.feat .lbl { color: var(--cherry); font-weight: 500; font-size: .82rem; }
.feat .name { font-size: 1.9rem; font-weight: 500; line-height: 1.1; margin-top: 6px; }
.feat .name a { color: inherit; }
.feat .name a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cherry); }
.feat .blurb { font-size: 1.08rem; line-height: 1.55; margin-top: 12px; max-width: 60ch; }
.feat .meta { font-size: .9rem; color: var(--muted); font-variant: small-caps; letter-spacing: .04em; margin-top: 12px; }
.feat .fams { margin-top: 12px; }
.feat .read { display: inline-block; margin-top: 16px; color: var(--cherry); font-variant: small-caps; letter-spacing: .06em; border-bottom: 1px solid var(--cherry); padding-bottom: 2px; }
.feat .plate { color: var(--ink); opacity: .5; text-align: center; }
.feat .plate svg { width: 110px; height: auto; }
.feat .plate .cherry { fill: var(--cherry); stroke: none; opacity: .85; }

/* --- Estado del café: the census (S2.3) ------------------------------- */
/* The locked layout: a «Portada» headline on the cover — one giant cherry
   number, the leading origin, because that is the axis people search — over a
   «Herbario» grid of flat surface plates. No borders, no nested cards; the
   plates are told apart by tint and air, and the only hairlines are the ones
   that genuinely separate (a finding from the next, the report from its
   footer). Every bar and the one histogram are hand-authored CSS: there is no
   charting library on this site and there is not going to be. */
.report-hero { display: grid; grid-template-columns: 1.55fr .85fr; gap: 34px; align-items: center; padding: 36px 0 60px; }
.report-hero .kick { color: var(--on-cherry); font-size: .86rem; font-weight: 500; letter-spacing: .16em; }
/* Longer than the mockup's line, because the H1 carries the page's keyword
   verbatim (it is the <title> minus the brand). The measure is set so it
   balances onto two lines at the display size rather than three. */
.report-hero h1 { font-size: clamp(2.3rem, 4.4vw, 3.6rem); font-weight: 300; line-height: 1.03; letter-spacing: -.017em; color: var(--on); margin-top: 16px; max-width: 22ch; }
.report-hero .sub { color: var(--on-muted); font-size: 1.14rem; font-weight: 300; margin-top: 18px; max-width: 52ch; }
.report-hero .dated { color: var(--on-muted); font-size: .8rem; margin-top: 20px; max-width: none; }
.report-hero .dated b { color: var(--on); font-weight: 500; }
.report-headline { text-align: right; }
.report-headline .big { font-size: clamp(3.4rem, 8vw, 6rem); font-weight: 300; line-height: .84; letter-spacing: -.03em; color: var(--on-cherry); font-variant-numeric: tabular-nums; }
.report-headline .cap { display: block; color: var(--on-muted); font-size: .9rem; margin-top: 14px; }
/* The cover suppresses link underlines site-wide; the headline's is the one
   statistic that has to read as a link, so it keeps one in the bright accent
   and sits at full strength against the caption around it. */
.report-headline .cap a { color: var(--on); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--on-cherry); text-underline-offset: 3px; transition: color var(--dur) var(--ease-out); }
.report-headline .cap a:hover { color: var(--on-cherry); }

main.report { padding-block: clamp(38px, 5vw, 60px) 90px; }
.report .lead { font-size: 1.18rem; font-weight: 300; color: var(--muted); max-width: 62ch; }
.report .lead b { color: var(--ink); font-weight: 500; }

/* Section labels: cherry for the findings (the one place it earns the accent),
   muted for the grid that follows. */
.report .seclabel { display: block; font-size: .82rem; font-weight: 500; letter-spacing: .14em; color: var(--cherry); margin: 52px 0 6px; }
.report .seclabel.plain { color: var(--muted); }

/* The caveat that governs the grid, set as a caption under its heading. */
.report .secnote { color: var(--faint); font-size: .92rem; max-width: 68ch; margin-top: 10px; }

/* Titulares — editorial big numbers in ink, not cherry: three findings, each
   with the sentence that says what it means and links what backs it. */
.report .findings { display: flex; flex-direction: column; }
/* The number's column takes 8.5rem or the widest figure, whichever is more: a
   split reads "51/41", which is five glyphs of display type and ran into the
   label at the mockup's fixed width. */
/* The figure centres against the whole block it belongs to, not against the
   label's baseline: a finding with a split bar under it is three or four lines
   tall, and a number pinned to the first of them read as if it had slipped. */
.report .finding { display: grid; grid-template-columns: minmax(8.5rem, max-content) 1fr; gap: 34px; align-items: center; padding: 26px 0; border-top: 1px solid var(--hair); }
.report .finding .big { font-size: clamp(2.7rem, 5.2vw, 4rem); font-weight: 300; line-height: .86; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.report .finding .k { color: var(--muted); font-size: .82rem; font-weight: 500; letter-spacing: .13em; max-width: none; }
.report .finding .body { font-size: 1.1rem; font-weight: 300; margin-top: 10px; max-width: 56ch; }
.report .finding .body a { color: var(--ink); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 3px; transition: color var(--dur) var(--ease-out); }
.report .finding .body a:hover { color: var(--cherry); }
.report .split { display: flex; height: 12px; border-radius: 2px; overflow: hidden; margin-top: 16px; max-width: 520px; }
.report .split > i { display: block; }
.report .splabel { display: flex; justify-content: space-between; max-width: 520px; margin-top: 8px; color: var(--muted); font-size: .8rem; }

/* Por dimensión — the plate grid. */
.report .board { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(310px, 100%), 1fr)); gap: 26px; margin-top: 20px; }
.report .plate { background: var(--surface); border-radius: var(--radius-card); padding: 26px 28px 24px; display: flex; flex-direction: column; }
.report .plate.wide { grid-column: 1 / -1; }
.report .plate h3 { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; color: var(--muted); font-size: .88rem; font-weight: 500; letter-spacing: .11em; font-variant: small-caps; margin-bottom: 18px; }
.report .plate h3 .n { color: var(--faint); font-size: .76rem; text-align: right; }
/* A measure, not the plate's full width: the histogram's caption is two
   sentences, and across a wide plate they ran past 140 characters a line. */
.report .plate .pfoot { color: var(--faint); font-size: .88rem; margin-top: 16px; max-width: 74ch; }
.report .plate .pfoot a { color: var(--cherry); transition: color var(--dur) var(--ease-out); }
.report .plate .pfoot a:hover { color: var(--ink); }
/* An axis with nothing in it: the note sits in the middle of the plate the
   grid stretched, rather than under the heading with a void beneath it. */
.report .plate .pfoot.empty { margin-block: auto; }

/* One share: term, bar, percent. The bar is ink unless the term carries a
   categorical colour of its own, and the colour never travels without its
   label (the Label-Always Rule). */
.report .mini { display: flex; flex-direction: column; gap: 11px; }
.report .mrow { display: grid; grid-template-columns: 8rem 1fr 2.6rem; gap: 12px; align-items: center; font-size: .96rem; }
.report .mrow .nm a { color: var(--ink); transition: color var(--dur) var(--ease-out); }
.report .mrow .nm a:hover { color: var(--cherry); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 3px; }
.report .mrow .nm .dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 7px; }
.report .mtrack { height: 7px; background: color-mix(in oklch, var(--hair) 62%, transparent); border-radius: 2px; position: relative; }
.report .mtrack > i { position: absolute; inset: 0 auto 0 0; background: var(--ink); border-radius: 2px; transition: background-color var(--dur) var(--ease-out); }
/* Cherry, not a mix: interpolating ink toward cherry in oklch takes the short
   way round the hue circle and lands on olive. The pointer's bar is the "current
   node" the way el paseo marks one, and it is a transient state, so the
   Scarce-Cherry Rule (which governs the resting screen) still holds. */
.report .mrow:hover .mtrack:not(.mtrack--fam) > i { background: var(--cherry); }
.report .mrow .pct { text-align: right; font-variant-numeric: tabular-nums; font-size: .94rem; }

/* The report's one ordinal ink ramp. Every ranked scale on the page draws from
   it — the belt map, the cross-tab, the budget tiers, the process split — so a
   darker tone means "more" wherever it appears, and no ranked scale has to
   borrow the flavour-family colours, which name something else entirely. */
.report .s1 { background: color-mix(in oklch, var(--ink) 26%, var(--surface)); }
.report .s2 { background: color-mix(in oklch, var(--ink) 42%, var(--surface)); }
.report .s3 { background: color-mix(in oklch, var(--ink) 58%, var(--surface)); }
.report .s4 { background: color-mix(in oklch, var(--ink) 78%, var(--surface)); }
.report .s5 { background: var(--ink); }

/* A partitioned axis: one divided bar, and the rows below it as its legend.
   What the segments leave unfilled is the coffees that declare no process, so
   the bar carries the track behind it rather than sitting on the plate. */
.report .partition > .split { background: color-mix(in oklch, var(--hair) 62%, transparent); margin-top: 2px; }
.report .legend { margin-top: 14px; }
.report .lrow { display: grid; grid-template-columns: 12px 1fr 2.6rem; gap: 12px; align-items: center; font-size: .96rem; }
.report .lrow .sw { width: 12px; height: 12px; border-radius: 2px; }
.report .lrow .nm a { color: var(--ink); transition: color var(--dur) var(--ease-out); }
.report .lrow .nm a:hover { color: var(--cherry); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 3px; }
.report .lrow .pct { text-align: right; font-variant-numeric: tabular-nums; font-size: .94rem; }

/* Origen × proceso. A table, because a cross-tab is one: the tint carries the
   comparison and the figure carries the fact, so neither has to be guessed. */
.report .xscroll { overflow-x: auto; margin-top: 4px; }
/* Fixed layout: in a heat table the tint's area is part of the reading, and
   content-sized columns made "Descafeinado" twice the cell of "Honey" for the
   same count. Equal cells, so only the tone varies. */
.report .crosstab { border-collapse: collapse; width: 100%; table-layout: fixed; min-width: 30rem; }
.report .crosstab tbody th { width: 9rem; }
.report .crosstab caption { caption-side: bottom; text-align: left; margin-top: 16px; }
.report .crosstab th { font-weight: 400; font-size: .9rem; text-align: left; padding: 7px 12px; white-space: nowrap; }
.report .crosstab thead th { color: var(--muted); font-variant: small-caps; letter-spacing: .06em; text-align: center; }
.report .crosstab th a { color: var(--ink); transition: color var(--dur) var(--ease-out); }
.report .crosstab th a:hover { color: var(--cherry); }
.report .crosstab td { padding: 7px 12px; text-align: center; font-variant-numeric: tabular-nums; font-size: .96rem; border-radius: 2px; transition: background-color var(--dur) var(--ease-out); }
.report .crosstab td a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: color-mix(in oklch, currentColor 45%, transparent); text-underline-offset: 3px; }
.report .crosstab td.zero { color: var(--faint); }
/* Same five steps as the map, and the same reason for five. The two darkest
   carry paper-coloured figures, since ink on ink is not a number. */
.report .crosstab .s4, .report .crosstab .s5 { color: var(--paper); }
/* Pointer and keyboard get the same answer: the ring alone marked the link
   inside the cell, while the cell it belongs to stayed inert. */
.report .crosstab td:hover, .report .crosstab td:has(a:focus-visible) { background: var(--cherry); color: var(--paper); }
.report .crosstab td:has(a:focus-visible) a { outline: none; }

/* Altura — the one real chart. Altitude is the only continuous variable, so it
   is the only one a histogram tells the truth about; the median marker is the
   plate's single cherry gesture. */
.report .hist { position: relative; display: flex; align-items: flex-end; gap: 4px; height: 130px; margin-top: 12px; }
.report .hist .bar { flex: 1; min-width: 0; background: var(--ink); border-radius: 2px 2px 0 0; transition: background-color var(--dur) var(--ease-out); }
.report .hist .bar:hover { background: var(--cherry); }
.report .hist .med { position: absolute; top: 2px; bottom: 0; width: 2px; background: var(--cherry); z-index: 2; }
.report .hist .med b { position: absolute; top: -15px; left: 50%; transform: translateX(-50%); font-weight: 400; font-size: .72rem; font-variant-numeric: tabular-nums; color: var(--cherry); white-space: nowrap; background: var(--surface); padding: 0 3px; }
.report .histbase { height: 1px; background: var(--hair); }
.report .histx { position: relative; display: flex; justify-content: space-between; margin-top: 8px; color: var(--faint); font-size: .76rem; }
.report .histx .tick { position: absolute; top: 0; transform: translateX(-50%); }

/* De dónde viene — the coffee belt. The whole plate is one engraving: countries
   are separated by a hairline of the plate's own surface rather than by an ink
   outline, so the drawing reads as tone, which is what the data is.

   Five steps of ink, scaled to the leading origin, and nothing else: colour on
   this map would compete with the flavour families, which are the one place
   categorical colour means something. */
.report .map { width: 100%; height: auto; margin-top: 4px; }
.report .map path { fill: color-mix(in oklch, var(--ink) 7%, var(--surface)); stroke: var(--surface); stroke-width: .7; }
.report .map a path { transition: fill var(--dur) var(--ease-out); }
/* The first step clears the silhouette by a wide margin: most origins sit at
   the bottom of the scale, and a country in the catalog has to read as in it
   before it reads as small. */
.report .map .s1 { fill: color-mix(in oklch, var(--ink) 26%, var(--surface)); }
.report .map .s2 { fill: color-mix(in oklch, var(--ink) 42%, var(--surface)); }
.report .map .s3 { fill: color-mix(in oklch, var(--ink) 58%, var(--surface)); }
.report .map .s4 { fill: color-mix(in oklch, var(--ink) 78%, var(--surface)); }
.report .map .s5 { fill: var(--ink); }
/* The same gesture the share bars and the histogram answer with: the country
   under the pointer is the current node, in cherry, for as long as it is. */
.report .map a:hover path, .report .map a:focus-visible path { fill: var(--cherry); }
.report .map a:focus-visible { outline: none; }
.report .map a:focus-visible path { stroke: var(--cherry); stroke-width: 1.6; }

/* Sensory: one small histogram per axis over the five steps, with the average
   marked underneath. Room to be read — the bars carry the whole reading, so
   they get height, width and a baseline instead of sharing twenty pixels with
   a second encoding of the same scale. A step with no coffees draws nothing;
   its empty slot is the fact. */
/* Small multiples: the three axes side by side, same scale and same height, so
   the shapes are compared in one pass instead of scanned down a column. */
.report .sens { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); gap: 26px 40px; }
.report .scale { display: flex; flex-direction: column; gap: 10px; }
.report .scale .head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.report .scale .lab { color: var(--muted); font-size: .86rem; letter-spacing: .09em; }
.report .scale .n { color: var(--faint); font-size: .78rem; white-space: nowrap; }
.report .plot { position: relative; padding-bottom: 24px; }
.report .plot .ends { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; color: var(--faint); font-size: .7rem; }
.report .plot .ends b { font-weight: 400; }
/* Tall enough that the shape is the message: at a third of this width the bars
   were strips, and at this width a short chart makes them squares. */
.report .spread { display: flex; align-items: flex-end; gap: 12px; height: 78px; }
.report .spread i { flex: 1; background: color-mix(in oklch, var(--ink) 62%, var(--surface)); border-radius: 2px 2px 0 0; }
.report .plot .base { display: block; height: 1px; background: var(--hair); }
/* The average, in the same cherry the altitude median uses and for the same
   reason: it is the one mark on the chart that is not a count. */
.report .plot .avg { position: absolute; bottom: 4px; width: 2px; height: 7px; background: var(--cherry); transform: translateX(-50%); }

/* Presupuesto: the catalog's three thirds, and where each variety sits inside
   them. Never a peso figure — the bars are the whole of the price answer.

   A tier is an ordered step, so the three read as one ink ramp rather than
   three categorical colours; the glyph on every label is what names each one.
   Terciles are uniform by construction, which is exactly why the catalog-wide
   bar is a thin strip and the per-variety bars are the point of the plate. */
.report .plate .split { max-width: none; }
.report .plate .splabel { max-width: none; gap: 12px; flex-wrap: wrap; }
.report .splabel .g .off { color: var(--hair); }
.report .xtab { margin-top: 26px; }
.report .xtab .l { display: block; color: var(--muted); font-size: .8rem; letter-spacing: .1em; margin-bottom: 12px; max-width: none; }
.report .xrow { display: grid; grid-template-columns: 9rem 1fr 5rem; gap: 14px; align-items: center; padding: 5px 0; }
.report .xrow .nm a { color: var(--ink); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 3px; transition: color var(--dur) var(--ease-out); }
.report .xrow .nm a:hover { color: var(--cherry); }
.report .xrow .split { margin-top: 0; height: 10px; }
.report .xrow .c { color: var(--faint); font-size: .82rem; text-align: right; }
.report .pnote { margin-top: 18px; color: var(--muted); font-size: .98rem; max-width: 64ch; }

/* Tostadores y geografía: plain facts, set as figures with their caption. */
.report .facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: 22px 34px; }
.report .fact .v { font-size: 1.5rem; font-weight: 300; font-variant-numeric: tabular-nums; }
.report .fact .v small { font-size: .95rem; color: var(--muted); }
.report .fact .k { color: var(--muted); font-size: .8rem; letter-spacing: .09em; margin-top: 6px; }
.report .fact .k a { color: var(--cherry); transition: color var(--dur) var(--ease-out); }
.report .fact .k a:hover { color: var(--ink); }
/* The geography plate's places are a *run* of destinations, where the rule
   above was written for one clickable word inside a line of muted prose
   ("métodos declarados · 95% sugeridos"). Cherry earns that job because the
   words around it are not cherry; spend it on a whole line and the accent
   becomes the label row's colour (DESIGN §Scarce Cherry) with nothing plain
   beside it to say which words are links. So a run reads the way every other
   run of term links on the site reads — ink, with the cherry spent on a
   hairline underline. `nowrap` for the reason .vocab-terms carries it: the
   line breaks between provinces, never through "Buenos Aires". */
.report .fact .k.places a { color: var(--ink); white-space: nowrap; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 3px; }
.report .fact .k.places a:hover { color: var(--cherry); }

/* The foot: the honest scope line, the botanical stamp that closes every
   surface, and the ways to pass the report on. One --surface plate, the same
   flat tint as the grid above — the page closes on the shape it was set in. */
.report .foot { background: var(--surface); border-radius: var(--radius-card); padding: 26px 28px 24px; margin-top: 52px; }
.report .scope { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; padding-bottom: 20px; border-bottom: 1px solid var(--hair); }
.report .scope p { color: var(--faint); font-size: .92rem; max-width: 64ch; }
.report .scope .fmt { color: var(--cherry); transition: color var(--dur) var(--ease-out); }
.report .scope .fmt:hover { color: var(--ink); }
.report .stamp { flex: 0 0 auto; color: var(--ink); opacity: .85; }
.report .stamp .cy { fill: var(--cherry); stroke: none; opacity: .9; }

/* S2b.1 — the share block. Two labelled rows of the site's own chip: a
   hairline pill, transparent fill, ink text, cherry on hover, exactly as the
   taste doors and the ficha's badge-kit copy button wear it. No branded social
   buttons: their colours would be the one place on the page where someone
   else's identity outranks the atlas's, and the Scarce-Cherry Rule leaves no
   room for five of them. */
.report .share { display: flex; flex-direction: column; gap: 16px; margin-top: 26px; }
.report .grp { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
/* A fixed label column, so the two rows of chips start on the same line and
   read as one block rather than as two unrelated groups. Same size and voice as
   .xtab .l, which names a sub-block inside a plate exactly like this does. */
.report .glabel { flex: 0 0 auto; width: 8.5rem; color: var(--muted); font-size: .8rem; letter-spacing: .1em; }
/* line-height: inherit, because a <button> takes the UA's `normal` while the
   sibling <a>s inherit the body's 1.6 — which left the copy chip 33px tall in a
   row of 40px pills, and under the 44px touch target once coarse padding
   applied. */
.report .share .chip { display: inline-flex; align-items: center; gap: 8px; padding: 8px 17px; font-family: inherit; font-size: .9rem; line-height: inherit; font-variant: small-caps; letter-spacing: .06em; cursor: pointer; }
.report .share .chip:hover { color: var(--cherry); border-color: var(--cherry); }
.report .share .chip svg { width: 13px; height: 13px; flex: 0 0 auto; opacity: .7; }
.report .share .chip .go { color: var(--faint); font-size: .95em; transition: color var(--dur) var(--ease-out); }
.report .share .chip:hover .go { color: inherit; }
.report .ainote { color: var(--faint); font-size: .86rem; max-width: 66ch; }

@media (pointer: coarse) {
  .report .mini { gap: 4px; }
  .report .mrow { min-height: 44px; }
  .report .mrow .nm a { display: inline-block; padding-block: 11px; }
  /* The one link run in the facts row, opened up the way .vocab-terms is — the
     same shape (a dot-separated line of term links) and the same 40px box
     .roaster-links and .vocab-terms both land on. The padding is 12px rather
     than their 10px only because this line sets at .8rem: the target is the
     house 40px, not a new number. The run's own line-height is what keeps two
     wrapped lines of provinces off each other's boxes. */
  .report .fact .k.places { line-height: 3.2; }
  .report .fact .k.places a { display: inline-block; line-height: 1.25; padding-block: 12px; }
  .report .xrow { padding-block: 8px; }
  /* A cross-tab cell is a link with a number in it; at desktop padding a thumb
     lands between two of them. */
  .report .crosstab td, .report .crosstab th { padding-block: 13px; }
  .report .lrow { min-height: 40px; }
  .report .share .chip { padding-block: 12px; }
}

@media (max-width: 760px) {
  .report-hero { grid-template-columns: 1fr; gap: 22px; }
  /* The axis keeps its two ends; the thousand-metre marks are a wide-plate
     affordance, and on a phone they run into the end labels. */
  .report .histx .tick { display: none; }
  .report-headline { text-align: left; }
  .report .finding { grid-template-columns: 1fr; gap: 6px; }
  .report .mrow { grid-template-columns: 6rem 1fr 2.4rem; gap: 10px; }
  /* Name over its bar on a phone, with the count beside the name. */
  .report .xrow { grid-template-columns: 1fr auto; row-gap: 6px; }
  .report .xrow .split { grid-column: 1 / -1; }
  /* The stamp stays a corner mark. Stacked, it used to close the page; now the
     share block follows it, and left-aligned under the paragraph it read as an
     orphan in the middle of the foot rather than as the plate's flourish. */
  .report .scope { flex-direction: column; }
  .report .scope .stamp { align-self: flex-end; }
  .report .foot { padding: 22px 20px 20px; }
  /* The label takes its own line rather than squeezing the chips into a
     column beside it. */
  .report .glabel { width: 100%; }
}

/* --- Site footer ------------------------------------------------------- */
/* The wood-green bookend. It reuses the cover's palette rather than the .cover
   class itself: .cover forces `color: inherit` on links and kills their hover
   underline, which is right for a masthead sitting on a hero and wrong for a
   column of links whose whole job is to be links.
   `margin-top: auto` is what pins it to the bottom of a short page — see the
   body rule in application.css. */
.foot { background: var(--cover); color: var(--on); margin-top: auto; }
/* Hairline-on-Green: plain cherry falls to 2.4:1 here, under the 3:1 a focus
   ring needs. Same swap .cover makes, for the same reason. */
.foot :focus-visible { outline-color: var(--on-cherry); }
/* Asymmetric on purpose: the band opens wide off the paper above it and closes
   on a step, but not on the 24px that left the signature sitting against the
   bottom edge of the screen. */
.foot .wrap { padding-block: var(--space-xl) var(--space-lg); }
/* The about column is the wide one because it carries the only sentence; the
   two link columns are equal so their labels align.
   `baseline` because the three columns open on type of three different sizes:
   box-aligned, the 1.3rem wordmark sat 11px below two .82rem labels that were
   supposed to be its peers. One line, and the row reads as one row. */
.foot-body { display: grid; grid-template-columns: minmax(0, 1.7fr) repeat(2, minmax(0, 1fr)); gap: var(--space-lg); align-items: baseline; }
/* .brand is already the global wordmark rule (gap, weight, tracking); down
   here it only answers the masthead quieter, so only the two things that
   differ are restated. It was a full second copy of the rule until polish. */
.foot .brand { color: var(--on); font-size: 1.3rem; }
.foot .brand:hover { text-decoration: none; }
.foot-about p { color: var(--on-muted); font-size: .95rem; margin-top: 14px; max-width: 48ch; }
/* `.h` is the site's section label (.listhead, .drow, .no-match all use it);
   this is the same label on green, at the size .stat span takes there. */
.foot-links .h { font-size: .82rem; font-weight: 500; color: var(--on-muted); }
.foot-links ul { list-style: none; padding: 0; margin-top: 14px; display: flex; flex-direction: column; gap: 9px; }
.foot-links a { color: var(--on); font-size: .96rem; transition: color var(--dur) var(--ease-out); }
.foot-links a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--on-cherry); }
/* A full spacing step below the list, not the list's own 9px rhythm: at the
   tighter gap it read as a third link that happened to wrap. */
.foot-formats { color: var(--on-muted); font-size: .84rem; margin-top: var(--space-md); max-width: 32ch; }

/* The signature line. Deliberately not a flex row: it is one sentence with two
   marks set inside it, and flex would turn every text node between them into a
   sibling that wraps on its own. The reset gives every svg `display: block`, so
   the two icons have to be put back inline by hand. */
/* Set to the right edge: the signature is the last thing on the page and it is
   an aside, not a heading — left-aligned it started a fourth column under the
   three above it and read as one more thing to look at. Against the right rule
   it closes the band instead, the way a colophon closes a book. */
.foot-made { margin-top: var(--space-lg); padding-top: var(--space-md); border-top: 1px solid var(--on-hair); color: var(--on-muted); font-size: .92rem; max-width: none; text-align: right; }
.foot-made svg { display: inline-block; }
.foot-made .i-heart { width: 14px; height: 14px; color: var(--on-cherry); vertical-align: -1px; }
/* Bigger than the heart and a touch brighter than the sentence around it: the
   heart is a filled shape and the cup is three thin lines, so matching them by
   number would have left the cup dissolving into the green. */
.foot-made .i-coffee { width: 17px; height: 17px; color: var(--on); vertical-align: -3px; }
/* The underline is the affordance (the link sits in running text at faint
   weight), and it ripens to cherry on hover the way .buy does. */
/* `nowrap` because at 320px the line wraps and it was breaking the name in
   half, underline and all: a person's name is one word for this purpose, so
   the wrap happens before it instead. */
.foot-made .author { white-space: nowrap; color: var(--on); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; text-decoration-color: var(--on-hair-strong); transition: text-decoration-color var(--dur) var(--ease-out); }
.foot-made .author:hover { text-decoration-color: var(--on-cherry); }

@media (pointer: coarse) {
  /* The house 40px box, the way .roaster-links and .vocab-terms get it: the
     padding grows the target, the reduced gap keeps the column the same height. */
  .foot-links ul { gap: 0; }
  .foot-links li a { display: inline-block; padding-block: 10px; }
}

/* --- Responsive ------------------------------------------------------- */
/* The masthead is one line only while one line fits. Six nav items wanted
   1075px of the 1084px usable — ~9px of slack, i.e. full — which is why M1
   collapsed Variedades · Procesos · Familias into one "Vocabulario" item. Four
   items leave the row with room again (enough for "Informe" when the report
   lands, and for the quiz beside "Explorar" after that), so what stays below is
   the safety net rather than a rule the common case relies on: the nav takes a
   row of its own before it can run past the wrap, and wraps again if even that
   is not enough. */
@media (max-width: 1120px) {
  .masthead { flex-wrap: wrap; row-gap: 18px; }
  .nav { order: 3; flex-basis: 100%; flex-wrap: wrap; row-gap: 10px; }
}

@media (max-width: 1120px) {
  /* Three columns want ~1.7fr + two readable link columns; below the masthead's
     own breakpoint the two link columns pair up under the sentence. */
  .foot-body { grid-template-columns: 1fr 1fr; row-gap: var(--space-lg); }
  .foot-about { grid-column: 1 / -1; }
}

@media (max-width: 760px) {
  .grid { grid-template-columns: 1fr; }
  .foot-body { grid-template-columns: 1fr; gap: var(--space-md); }
  .foot .wrap { padding-block: var(--space-lg); }
  .foot-about p, .foot-formats { max-width: none; }
  .atlas-body { grid-template-columns: 1fr; }
  /* V1b — on a phone the article and its facts sit between the reader and the
     coffees, stacked, and the article alone ran more than a screen and a half.
     The prose collapses to the lines that carry the definition (the opening
     sentence stays readable without tapping; only the elaboration hides), and
     the facts go back to the row the ficha's .specs already wraps in, two per
     line. The prose keeps every word in the DOM — this clips, it never removes.
     `is-clamped` is set by the controller, so no JavaScript means no clamp. */
  .term-note .prose.is-clamped .prose-text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 6; overflow: hidden; }
  .term-note .prose.is-clamped.is-open .prose-text { display: block; }
  /* The site's inline-action idiom (.ver), with the padding that buys a 44px
     tap target; the underline is drawn by text-decoration so the padding never
     pushes it away from the word. */
  .term-note .prose.is-clamped .prose-more { display: inline-block; padding-block: 14px; font-family: inherit; font-size: .98rem; color: var(--cherry); background: none; border: 0; cursor: pointer; font-variant: small-caps; letter-spacing: .06em; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; transition: color var(--dur) var(--ease-out); }
  /* The same ripening toward ink the buy button answers a pointer with. */
  .term-note .prose.is-clamped .prose-more:hover { color: color-mix(in oklch, var(--cherry) 88%, var(--ink)); }
  .term-note .term-facts { max-width: none; }
  .term-note .specs { flex-direction: row; flex-wrap: wrap; gap: var(--space-md); }
  /* Two per row where two fit, one where they do not: at 320px a half column is
     124px, which puts "Mutación natural de Bourbon" on three lines of thirteen
     characters. The basis wraps them back to one column there without a second
     breakpoint, the same way the article and its facts wrap one level up. */
  .term-note .spec { flex: 1 1 140px; min-width: 0; }
  .rail { display: none; }
  /* The filter is the page's only control and lives in the cover, so it is
     never hidden — it just wraps onto more lines. The cover is trimmed instead:
     a three-facet query used to fill a phone screen on its own, which put the
     coffees the query is about entirely below the fold. */
  .filter-headline { max-width: none; }
  .pills { margin-top: 16px; }
  .filter-answer { margin-top: 16px; padding-bottom: 24px; }
  .coffee .hero { grid-template-columns: 1fr; gap: 30px; }
  .hero .plate { display: none; }
  .intents { grid-template-columns: 1fr; }
  .feat { grid-template-columns: 1fr; }
  .feat .plate { display: none; }
  .wrap { padding-inline: var(--space-md); }
  /* The nav hides behind a literal "Menú" disclosure instead of disappearing.
     The <details> holds only the toggle; the panel it opens is the .nav itself
     (sibling selector), so one set of links serves both widths and a Turbo
     visit closes it by re-rendering the body — no JS. The auto margin pulls
     the toggle over to sit beside "Explorar" rather than floating mid-row. */
  .nav { display: none; }
  .mnav { display: block; margin-left: auto; margin-right: var(--space-md); }
  /* Padding + negative margin buys the 44px tap target without moving the
     baseline; "Explorar" gets the same treatment below (inline, so its
     padding never affects layout). */
  .mnav summary { list-style: none; cursor: pointer; color: var(--on); font-size: .86rem; font-weight: 500; padding: var(--space-sm); margin: calc(-1 * var(--space-sm)); }
  .mnav summary::-webkit-details-marker { display: none; }
  .mnav[open] summary { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; text-decoration-color: var(--on-cherry); }
  .edn { padding-block: var(--space-sm); }
  .mnav[open] ~ .nav { display: flex; flex-direction: column; gap: 0; animation: panel-in var(--dur) var(--ease-out) both; }
  .mnav[open] ~ .nav a { padding-block: 14px; border-top: 1px solid var(--on-hair); }
  .mnav[open] ~ .nav a:last-child { border-bottom: 1px solid var(--on-hair); }
}
