/* ==========================================================================
   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; }

/* --- 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; }
.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; }
.spec .v { font-size: 1.4rem; font-weight: 500; }
.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; }
.lead { 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; }
.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; }
.landing-head .term-kin { color: var(--on-muted); font-size: .95rem; margin-top: 14px; max-width: 64ch; }
.landing-head .term-kin a { color: var(--on); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--cherry); text-underline-offset: 3px; }
.landing-head .term-kin .fam { display: inline-flex; align-items: center; gap: 6px; }

/* --- 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; }
.chip { border: 1px solid var(--hair); border-radius: var(--radius-pill); padding: 11px 22px; font-size: 1.05rem; color: var(--ink); }
.chip:hover { border-color: var(--cherry); text-decoration: none; }
.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; }
.roaster-links { margin-top: 14px; display: flex; gap: 20px; }
.roaster-links a { color: var(--on-muted); font-variant: small-caps; letter-spacing: .06em; font-size: .95rem; }
.roaster-links a:hover { color: var(--on); text-decoration: none; }
.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; }
.roaster-card .cf { margin-top: 10px; }

/* --- 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 honest scope line, and the botanical stamp that closes every surface. */
.report .scope { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-top: 50px; padding-top: 20px; border-top: 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; }

@media (pointer: coarse) {
  .report .mini { gap: 4px; }
  .report .mrow { min-height: 44px; }
  .report .mrow .nm a { display: inline-block; padding-block: 11px; }
  .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; }
}

@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; }
  .report .scope { flex-direction: column; }
}

/* --- 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: 760px) {
  .grid { grid-template-columns: 1fr; }
  .atlas-body { grid-template-columns: 1fr; }
  .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); }
  .nav { display: none; }
}
