:root{
  --paper:oklch(96.5% .013 78);--surface:oklch(98.6% .008 80);--ink:oklch(24% .022 55);
  --muted:oklch(47% .025 60);--faint:oklch(60% .02 65);--line:oklch(89.5% .017 78);
  --ember:oklch(56% .17 40);--ember-soft:oklch(94% .03 45);
  --drop:oklch(50% .11 155);--drop-soft:oklch(94% .04 155);
  --restock:oklch(50% .10 250);--restock-soft:oklch(94% .03 250);
  --fd:"Bricolage Grotesque",sans-serif;--fu:"Hanken Grotesk",sans-serif;
  --space-xs:4px;--space-sm:8px;--space-md:12px;--space-lg:16px;--space-xl:24px;--space-2xl:40px;--space-3xl:64px;
}
@media (prefers-color-scheme:dark){:root{
  --paper:oklch(19% .015 60);--surface:oklch(21% .015 60);--ink:oklch(95% .01 80);
  --muted:oklch(72% .015 70);--faint:oklch(65% .012 68);--line:oklch(30% .012 70);
  --ember:oklch(72% .15 45);--ember-soft:oklch(28% .05 42);
  --drop:oklch(72% .13 155);--drop-soft:oklch(26% .045 155);
  --restock:oklch(72% .11 250);--restock-soft:oklch(26% .04 250);
  --plate:oklch(92% .012 80); /* light plate under product photos */
}
/* Product imagery reads on a light plate, never on dark paper — shops shoot
   packs for white; transparent PNGs turn to sludge on dark backgrounds. */
.thumb,div.thumb{background:var(--plate)}
div.thumb{color:oklch(75% .02 75)}
.gthumb,.gthumb img{background:var(--plate)}
.relcard img,.relswatch{background:var(--plate)}
.brandlogo{background:var(--plate)}
.rpop img{background:var(--plate)}
}
*{box-sizing:border-box;margin:0}
body{background:var(--paper);color:var(--ink);font:15px/1.55 var(--fu);font-variant-numeric:tabular-nums}
main{max-width:76rem;margin:0 auto;padding:0 var(--space-lg) var(--space-2xl)}
::selection{background:var(--ember);color:var(--paper)}
/* iOS: kill the grey tap flash and the 300ms double-tap-zoom delay on every
   interactive control (the .card tile carries role=button). */
a,button,select,input,[role="button"]{-webkit-tap-highlight-color:transparent;touch-action:manipulation}

header.site{position:sticky;top:0;z-index:5;background:var(--paper);max-width:76rem;margin:0 auto;display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:var(--space-sm);padding:var(--space-xl) var(--space-lg) var(--space-md)}
.wordmark{font:800 clamp(1.9rem,4vw,2.6rem)/.95 var(--fd);letter-spacing:-.03em}
.wordmark a{color:inherit;text-decoration:none}
.wordmark span{color:var(--ember)}
.meter{font:600 12.5px/1.55 var(--fu);color:var(--muted);text-align:right;letter-spacing:.01em}
.meter b{color:var(--ink);font-weight:700}
.meter a{color:inherit}
.lsw{font:600 11.5px/1 var(--fu);color:var(--muted);margin-right:var(--space-md)}
.lsw a{color:var(--muted);text-decoration:none}
.lsw a:hover{color:var(--ink)}
.lsw b{color:var(--ink)}
.pulse{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--drop);margin-right:.45em;animation:pulse 2.4s cubic-bezier(.4,0,.2,1) infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.2}}
@media (prefers-reduced-motion:reduce){.pulse{animation:none}}

/* Header search (docs/plans/coffee-search.md "UX spec"): instrument styling,
   no thumbnails, no entrance motion — same hairline/pill grammar as .find /
   .chip. Sits between the wordmark and the meter block, per DESIGN.md's
   "wordmark left; meter block right" — search is a meter function. */
.hsearch{position:relative;flex:1 1 16rem;max-width:22rem;margin:0 var(--space-lg)}
.hq{width:100%;font:500 13.5px/1 var(--fu);color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:4px;padding:.55rem 2.1rem .55rem .75rem;min-height:38px;transition:border-color .15s ease-out}
.hq::placeholder{color:var(--faint)}
.hq:focus{outline:2px solid var(--ember);outline-offset:-1px;border-color:transparent}
.hkey{position:absolute;right:.6rem;top:50%;transform:translateY(-50%);font:600 10.5px/1 var(--fu);color:var(--faint);border:1px solid var(--line);border-radius:4px;padding:.2rem .4rem;pointer-events:none;background:var(--paper)}
.hq:focus~.hkey{display:none}
/* grouped, micro-labeled suggest panel — hairline dividers, tabular-nums
   counts, keyboard-navigable rows (.hsopt.active mirrors :hover) */
.hsuggest{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:20;background:var(--surface);border:1px solid var(--line);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.12);max-height:70vh;overflow-y:auto;padding:var(--space-xs) 0}
.hsgroup+.hsgroup{border-top:1px solid var(--line);margin-top:var(--space-xs);padding-top:var(--space-xs)}
.hsglabel{font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--faint);padding:.3rem var(--space-md)}
.hsopt{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm);width:100%;text-align:left;font:500 13px/1.4 var(--fu);color:var(--ink);background:none;border:0;padding:.5rem var(--space-md);cursor:pointer;min-height:36px}
.hsopt:hover,.hsopt.active{background:var(--paper)}
.hsopt b{font-weight:700}
.hsopt .hscount{color:var(--faint);font-variant-numeric:tabular-nums;font-weight:600;font-size:12px;flex:none}
.hsempty{font:500 13px/1.5 var(--fu);color:var(--faint);padding:.5rem var(--space-md)}
@media(max-width:640px){
.hsearch{order:2;flex-basis:100%;max-width:none;margin:var(--space-sm) 0 0}
.hq{font-size:16px;min-height:44px}
}

.rule{max-width:76rem;margin:0 auto;padding:0 var(--space-lg)}
.rule::after{content:"";display:block;height:3px;background:var(--ink);border-radius:2px}
/* task #1: read as an alert callout, not a thin full-bleed rule — taller, bolder,
   ink text on ember-soft (DESIGN.md: stale = ember-soft bg + ember border) */
.stale{max-width:76rem;margin:var(--space-md) auto 0;display:flex;align-items:center;gap:.5rem;font:700 13px/1.45 var(--fu);color:var(--ink);background:var(--ember-soft);border:1px solid var(--ember);border-radius:6px;padding:.75rem 1rem}

.toolbar{max-width:76rem;margin:0 auto;display:flex;flex-wrap:wrap;gap:var(--space-xs);align-items:center;padding:var(--space-md) var(--space-lg)}
.find{flex:1 1 15rem;min-width:12rem;font:500 13.5px/1 var(--fu);color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:4px;padding:.55rem .75rem;transition:border-color .15s ease-out}
.find::placeholder{color:var(--faint)}
.find:focus{outline:2px solid var(--ember);outline-offset:-1px;border-color:transparent}
.chip{font:600 12px/1 var(--fu);border:1px solid var(--line);background:var(--surface);color:var(--muted);border-radius:99px;padding:.45rem .75rem;cursor:pointer;transition:all .15s ease-out}
.chip:hover{border-color:var(--muted);color:var(--ink)}
.chip:focus-visible{outline:2px solid var(--ember);outline-offset:1px}
.chip[aria-pressed="true"]{background:var(--ink);color:var(--paper);border-color:var(--ink)}
/* recent searches (F6): a compact chip row under the find bar, shown only on
   focus of an empty search box — never competes with live filter results.
   Base visibility is the inline style="display:none" the template renders
   (same convention as .suggest/.hidebar) — JS toggles it to 'flex' to show,
   so an empty/collapsed panel never eats layout space it shouldn't. */
.recent{flex-basis:100%;flex-wrap:wrap;align-items:center;gap:var(--space-xs);margin-top:var(--space-xs)}
.recentlabel{font:600 11.5px/1 var(--fu);color:var(--muted)}
.recent .chip{padding:.35rem .65rem;font-size:11px}
.obtn{border-color:var(--ember);border-width:2px;color:var(--ink)}
.obtn b{font-weight:700}
.obtn:hover{border-color:var(--ember);background:var(--ember-soft)}
.obtn:focus-visible{outline:2px solid var(--ember);outline-offset:1px;border-color:var(--ember)}
.belt{max-width:76rem;margin:0 auto;padding:var(--space-md) var(--space-lg) var(--space-lg);border-bottom:1px solid var(--line)}
.beltmap{width:100%;display:block;color:var(--muted)}
.beltcap{display:flex;align-items:baseline;gap:var(--space-sm);font:500 12px/1.5 var(--fu);color:var(--muted);margin-bottom:var(--space-sm)}
.beltcap b{font:700 13px/1.3 var(--fd);color:var(--ink)}
.belthelp{position:relative;display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;border:1px solid var(--faint);color:var(--faint);font:700 10px/1 var(--fu);cursor:help}
.belthelp .tip{display:none;position:absolute;left:50%;top:135%;transform:translateX(-50%);z-index:6;width:min(16rem,calc(100vw - 2*var(--space-lg)));background:var(--ink);color:var(--paper);font:500 11.5px/1.5 var(--fu);border-radius:4px;padding:.55rem .7rem;text-align:left}
.belthelp:hover .tip,.belthelp:focus .tip{display:block}
.hastip{position:relative;cursor:help}
.hastip .tip{display:none;position:absolute;left:50%;top:135%;transform:translateX(-50%);z-index:20;width:min(16rem,calc(100vw - 2*var(--space-lg)));max-width:min(240px,70vw);white-space:normal;background:var(--ink);color:var(--paper);font:500 11.5px/1.5 var(--fu);border-radius:4px;padding:.55rem .7rem;text-align:left}
.hastip:hover .tip,.hastip:focus .tip,.hastip.tipopen .tip{display:block}
.landmass{fill:currentColor;opacity:.13;stroke:none}
.belttxt{font:600 4.5px var(--fu);text-transform:uppercase;letter-spacing:.12em;fill:var(--faint)}
/* task #12: kill the browser's default focus box on the focusable SVG dot;
   keyboard focus shown on the circle's stroke instead. Fallback to :focus for
   older browsers; @supports suppresses mouse-focus outline in modern ones. */
.beltdot{cursor:pointer;outline:none}
.beltdot circle{fill:var(--ember);opacity:.55;transition:opacity .15s ease-out,r .15s ease-out}
.beltdot:hover circle,.beltdot:focus circle{opacity:1}
.beltdot:focus circle{stroke:var(--ink);stroke-width:1.5}
.beltdot.on circle{opacity:1;stroke:var(--ink);stroke-width:1.5}
@supports selector(:focus-visible) {
  .beltdot:focus:not(:focus-visible) circle{stroke:none}
  .beltdot:hover circle,.beltdot:focus-visible circle{opacity:1}
}
.beltchips{display:flex;flex-wrap:wrap;gap:var(--space-xs);margin-top:var(--space-md)}
.bchip.on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.bcount{opacity:.65;font-weight:600;margin-left:.15em}

/* secnav: jump-nav rail for the home page's sections — pure state highlight
   (IntersectionObserver toggles .active), never an animated fill; DESIGN.md
   bans progress/scroll choreography, this is wayfinding only. Built entirely
   by site.js from whatever sections actually render, so :empty hides it
   before JS runs (and if JS never runs). */
.secnav{position:sticky;top:var(--header-h,0px);z-index:4;max-width:76rem;margin:0 auto;display:flex;gap:var(--space-lg);overflow-x:auto;padding:var(--space-sm) var(--space-lg);background:var(--paper);border-bottom:1px solid var(--line);scrollbar-width:none}
.secnav::-webkit-scrollbar{display:none}
.secnav:empty{display:none;border-bottom:0;padding:0}
.secnav a{flex:0 0 auto;font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--muted);text-decoration:none;padding-bottom:.3rem;border-bottom:2px solid transparent;white-space:nowrap}
.secnav a:hover{color:var(--ink)}
.secnav a.active{color:var(--ember);border-bottom-color:var(--ember)}
@media(max-width:640px){.secnav{display:none}}

section.feed{margin-top:var(--space-2xl)}
.feed h2{font:700 clamp(1.25rem,2.2vw,1.55rem)/1.1 var(--fd);letter-spacing:-.015em;display:flex;align-items:baseline;gap:.55rem}
.feed h2 .count{font:700 12px/1 var(--fu);color:var(--paper);background:var(--muted);border-radius:99px;padding:.25rem .5rem;letter-spacing:.02em;align-self:center}
.feed .sub{font:500 12.5px/1.5 var(--fu);color:var(--muted);margin-top:var(--space-xs)}
.feed>.cards{margin-top:var(--space-lg)}
/* Updates (news-feed MVP slice 1, #146): smaller variant of the image-card
   tile grammar — same .card surface/radius, no click-to-inspect (not a
   product), so cursor/hover lift are dropped. Source pill reuses the solid
   "count" badge pill visual (muted bg, paper text, 99px radius) rather than
   the event trio's colors — this is provenance, not a radar event. Owner
   posts render no pill (first-person voice). No new color or font. */
#updates .cards{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
.upcard{cursor:default}
.upcard:hover{transform:none;border-color:var(--line)}
.upcard .thumb{height:110px}
.upcard .body{gap:var(--space-sm)}
.srcpill{align-self:flex-start;font:700 10px/1 var(--fu);color:var(--paper);background:var(--muted);border-radius:99px;padding:.22rem .55rem;text-transform:uppercase;letter-spacing:.06em}
.updbody{font:500 13.5px/1.5 var(--fu);color:var(--ink)}
.updtime{font:600 11px/1 var(--fu);color:var(--faint);font-variant-numeric:tabular-nums;text-transform:uppercase;letter-spacing:.04em}
.updmore{font:700 12px/1 var(--fu);color:var(--ember);text-decoration:none;align-self:flex-start}
.updmore:hover{text-decoration:underline}
.updempty{grid-column:1/-1;font:500 13px/1.5 var(--fu);color:var(--muted);padding:var(--space-sm) 0}
/* featured: horizontal scroll rail (#136a) — same touch/keyboard pattern as
   .chiprail (hidden scrollbar, scroll-snap-align, -webkit-overflow-scrolling)
   instead of a grid, so the editorial band reads as its own row at any width.
   Paid/sponsored placement (#136b) is Phase 5, out of scope here. */
#featured .cards{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:var(--space-md);padding-bottom:2px;-webkit-overflow-scrolling:touch;scrollbar-width:none;scroll-snap-type:x proximity}
#featured .cards::-webkit-scrollbar{display:none}
#featured .cards .card{flex:0 0 200px;scroll-snap-align:start}
/* radar category rails: micro-label heading + hairline, cards regrouped below */
.rail{margin-top:var(--space-xl)}
.railhead{font:700 11px/1 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--muted);display:flex;align-items:center;gap:var(--space-sm)}
.railhead::after{content:"";flex:1;height:1px;background:var(--line)}
.railcount{color:var(--faint);font-weight:600;font-variant-numeric:tabular-nums}
.rail>.cards{margin-top:var(--space-md)}
/* "view all (N)" escape valve to /catalog/{id}/ — same micro-label voice as
   railcount, ember for the link cue. In a railhead it sits before the
   hairline divider; in a section h2 (.hmore) it's pushed flush right. */
.railmore{font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--ember);text-decoration:none;white-space:nowrap}
.railmore:hover{text-decoration:underline}
.feed h2 .hmore{margin-left:auto}
/* sort control: real <select> kept fully native for semantics/keyboard/SR
   behavior (F8) — only the closed-state chrome is restyled, via
   appearance:none + a CSS-drawn chevron (border-triangle trick, no SVG
   data-uri, so it inherits --muted and just works in dark mode), same
   ember-focus-ring / chip language as .bchip. The native open-menu popup
   itself is intentionally left to the OS/browser: fully re-skinning it
   isn't reliably possible with CSS and would risk the keyboard/SR
   semantics a plain <select> gives for free. */
/* never-dead-end panel: on zero matches, offer one-tap relaxations instead
   of a bare "not found" (each chip names what it unlocks + a count) */
.suggest{margin-top:var(--space-xl);border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:var(--space-lg)}
.sughead{font:600 13.5px/1.5 var(--fu);color:var(--ink)}
.sughead b{color:var(--ember)}
.sugrow{display:flex;flex-wrap:wrap;gap:var(--space-xs);margin-top:var(--space-md)}
.sugrow .chip{color:var(--ink)}
.sugmiss{margin-top:var(--space-md);font:400 12.5px/1.5 var(--fu);color:var(--muted)}
.sugmiss a{color:var(--ember)}
.sugfeat{margin-top:var(--space-lg);padding-top:var(--space-lg);border-top:1px solid var(--line)}

/* /search results page: "N readings match" voice (never "results found"),
   removable applied-facet chips, zero-result relaxations reuse .sugrow. */
.searchhead{margin-top:var(--space-lg)}
.shq{font:700 clamp(1.15rem,2.4vw,1.5rem)/1.25 var(--fd);letter-spacing:-.01em}
.shq b{color:var(--ember);font-variant-numeric:tabular-nums}
.shsub{font:500 13px/1.5 var(--fu);color:var(--muted);margin-top:.2rem}
.facetchips{display:flex;flex-wrap:wrap;gap:var(--space-xs);margin-top:var(--space-md)}
.fchip{display:inline-flex;align-items:center;gap:.4em;font:600 12px/1 var(--fu);color:var(--ink);background:var(--ember-soft);border:1px solid var(--ember);border-radius:99px;padding:.45rem .5rem .45rem .75rem;cursor:pointer;min-height:32px;transition:opacity .15s ease-out}
.fchip:hover{opacity:.8}
.fchip .x{font-weight:700;color:var(--ember)}
.sponsored{font:700 9.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.07em;color:var(--paper);background:var(--muted);border-radius:99px;padding:.25rem .45rem}
@media(max-width:640px){.fchip{min-height:44px}}
.sugfeat .cards{margin-top:var(--space-md)}
.sortwrap{margin-left:auto;display:inline-flex;align-items:center;gap:6px;font:600 11.5px/1 var(--fu);color:var(--muted);letter-spacing:.01em;position:relative}
.sortsel{font:600 12px/1 var(--fu);color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:99px;padding:.4rem 1.7rem .4rem .65rem;cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}
.sortsel:focus-visible{outline:2px solid var(--ember);outline-offset:1px}
.sortwrap::after{content:"";position:absolute;right:.7rem;top:50%;width:6px;height:6px;border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);transform:translateY(-65%) rotate(45deg);pointer-events:none}
/* €/cup at a personal dose: dose slider (DESIGN.md 2026-07-10 — replaces the
   dropdown; direct manipulation of "how much am I brewing" reads better as a
   range than a picked list, same ember-accent chip family as .sortsel),
   filled client-side from data-eurg (micro-euros/gram) — sparse bean data
   omits the whole element, never a guessed number. */
.cupline{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
.doseslider-wrap{display:flex;align-items:center;gap:.5rem;flex:0 0 auto;min-width:120px}
.doseslider{-webkit-appearance:none;appearance:none;flex:0 0 auto;width:180px;height:4px;border-radius:99px;background:linear-gradient(to right,var(--ember) 0%,var(--ember) var(--fillpct,50%),var(--line) var(--fillpct,50%),var(--line) 100%);cursor:pointer;accent-color:var(--ember)}
.doseslider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:var(--ember);border:2px solid var(--paper);box-shadow:0 0 0 1px var(--line);cursor:pointer;transition:transform .15s ease-out}
.doseslider::-webkit-slider-thumb:hover{transform:scale(1.15)}
.doseslider::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--ember);border:2px solid var(--paper);box-shadow:0 0 0 1px var(--line);cursor:pointer}
.doseslider::-moz-range-track{height:4px;border-radius:99px;background:var(--line)}
.doseslider::-moz-range-progress{height:4px;border-radius:99px;background:var(--ember)}
.doseslider:focus-visible{outline:2px solid var(--ember);outline-offset:2px}
.dosereadout{font:700 13px/1 var(--fu);font-variant-numeric:tabular-nums;color:var(--ink);min-width:3ch}
.doseshint{font:600 10.5px/1 var(--fu);letter-spacing:.03em;color:var(--faint);white-space:nowrap;flex-basis:100%;margin-top:2px}
.doseshint:empty{display:none}
.cupamt{font:700 15px/1 var(--fu);font-variant-numeric:tabular-nums;color:var(--ink)}
.cupunit{font:600 12px/1 var(--fu);color:var(--muted)}
.atdose{font:700 10.5px/1 var(--fu);letter-spacing:.04em;color:var(--faint);font-variant-numeric:tabular-nums}
@media(max-width:640px){.doseslider-wrap{flex-basis:100%}.doseslider{width:100%;flex:1 1 auto}.doseslider::-webkit-slider-thumb{width:22px;height:22px}.doseslider::-moz-range-thumb{width:22px;height:22px}}
@media (prefers-reduced-motion:reduce){.doseslider::-webkit-slider-thumb{transition:none}}
/* watch star: quiet on the thumb, ember when armed */
.star{position:absolute;top:var(--space-sm);right:var(--space-sm);width:26px;height:26px;border-radius:99px;border:1px solid var(--line);background:var(--surface);color:var(--muted);font:600 13px/1 var(--fu);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s ease-out}
.star:hover{color:var(--ember);border-color:var(--ember)}
.star:focus-visible{outline:2px solid var(--ember);outline-offset:2px}
.star[aria-pressed="true"]{color:var(--paper);background:var(--ember);border-color:var(--ember)}
/* cart add: sits beside the star, same chip language, on the .card thumb */
.cartadd{position:absolute;top:var(--space-sm);right:calc(26px + var(--space-sm) + 6px);width:26px;height:26px;border-radius:99px;border:1px solid var(--line);background:var(--surface);color:var(--muted);font:700 14px/1 var(--fu);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s ease-out}
.cartadd:hover{color:var(--ember);border-color:var(--ember)}
.cartadd:focus-visible{outline:2px solid var(--ember);outline-offset:2px}
.cartadd.added{color:var(--paper);background:var(--ember);border-color:var(--ember)}
.detail .cartadd,.foot .cartadd{position:static;width:auto;height:auto;border-radius:99px;padding:.5rem .85rem;font:700 12.5px/1 var(--fu)}
/* header cart chip: hidden at zero, mirrors .lsw's quiet weight. Now a
   <button> (was a <span>) so it can open the cart drawer (#135) — reset the
   button chrome back to the original inline-text look. Note: the sibling
   branch for #134 (interim "hide add-to-cart until the drawer ships") adds
   `.cartadd,.cartchip{display:none}` here but hasn't landed on staging yet
   — once it does, that rule should be dropped/scoped to .cartadd only,
   since this drawer is the thing #134 was waiting on. Not touched here to
   avoid fighting a not-yet-merged branch. */
.cartchip{font:700 12px/1 var(--fu);color:var(--ink);margin-right:var(--space-md);background:none;border:0;padding:0;cursor:pointer;display:inline-flex;align-items:center;gap:.3em}
.cartchip:hover{color:var(--ember)}
.cartchip:focus-visible{outline:2px solid var(--ember);outline-offset:2px;border-radius:2px}
.cartchip .ccount{font-variant-numeric:tabular-nums}

/* Universal cart drawer (#135) — side panel per DESIGN.md ("no modal, ever";
   the drawer is the one explicit exception to that rule, same footing as
   the split-view .inspect above). Slides in from the header chip, never
   centered/full-bleed. Scrim is a light dim only, not an opaque modal veil. */
.cartdrawer-scrim{position:fixed;inset:0;z-index:70;background:oklch(from var(--ink) l c h / 22%);opacity:0;pointer-events:none;transition:opacity .15s ease-out}
.cartdrawer-scrim.show{opacity:1;pointer-events:auto}
.cartdrawer{position:fixed;top:0;right:0;bottom:0;z-index:71;width:min(380px,100vw);background:var(--surface);border-left:1px solid var(--line);box-shadow:-8px 0 24px oklch(from var(--ink) l c h / 12%);display:flex;flex-direction:column;transform:translateX(100%);transition:transform .2s cubic-bezier(.22,1,.36,1)}
.cartdrawer.show{transform:none}
@media (prefers-reduced-motion:reduce){.cartdrawer-scrim,.cartdrawer{transition:none}}
.cartdrawer-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-md);padding:var(--space-lg);border-bottom:1px solid var(--line)}
.cartdrawer-head h2{font:800 15px/1 var(--fd);letter-spacing:-.01em}
.cartdrawer-close{font:700 13px/1 var(--fu);color:var(--muted);background:none;border:0;cursor:pointer;padding:.3rem}
.cartdrawer-close:hover{color:var(--ink)}
.cartdrawer-close:focus-visible{outline:2px solid var(--ember);outline-offset:2px}
.cartlist{list-style:none;overflow-y:auto;flex:1;padding:var(--space-md) var(--space-lg)}
.cartrow{display:flex;gap:var(--space-md);padding:var(--space-md) 0;border-bottom:1px solid var(--line)}
.cartrow:last-child{border-bottom:0}
.cartrow img.cartimg,.cartrow div.cartimg{width:56px;height:56px;flex:none;border-radius:5px;object-fit:cover;background:var(--plate,var(--surface))}
.cartrow div.cartimg{display:flex;align-items:center;justify-content:center;color:var(--faint);font-size:20px}
.cartrowbody{flex:1;min-width:0}
.cartwho{font:600 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
.cartbean{font:700 13.5px/1.3 var(--fd);margin:.2rem 0;overflow-wrap:anywhere}
.cartrowfoot{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm);margin-top:.3rem}
.cartprice{font:700 13px/1 var(--fu);font-variant-numeric:tabular-nums}
.qtybox{display:flex;align-items:center;gap:.4rem}
.qtybtn{width:22px;height:22px;line-height:20px;text-align:center;font:700 14px/1 var(--fu);color:var(--ink);background:var(--paper);border:1px solid var(--line);border-radius:4px;cursor:pointer;padding:0}
.qtybtn:hover{border-color:var(--ember)}
.qtyval{min-width:1.2em;text-align:center;font-variant-numeric:tabular-nums;font:600 13px/1 var(--fu)}
.cartrowactions{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm);margin-top:.4rem}
.cartbuy{font:700 12px/1 var(--fu);color:var(--ember);text-decoration:none;border-bottom:1px solid transparent}
.cartbuy:hover{border-bottom-color:var(--ember)}
.cartremove{font:600 11.5px/1 var(--fu);color:var(--faint);background:none;border:0;cursor:pointer;padding:0;text-decoration:underline}
.cartremove:hover{color:var(--ink)}
.cartrow.unavailable{opacity:.6}
.cartrow.unavailable .cartbean::after{content:" — "attr(data-unavail);color:var(--faint);font:600 11px var(--fu);text-transform:none;letter-spacing:0}
.cartempty{padding:var(--space-2xl) var(--space-lg);text-align:center;color:var(--muted);font:500 13.5px/1.5 var(--fu)}

/* wayfinding: one hairline break between the "what changed" feed (new/drops/
   restock/featured) and the full browsable catalog (radar) below it — the
   only structural cue separating the two, per the anti-decoration rule. */
.feedbreak{position:relative;margin:var(--space-3xl) 0 0;border-top:1px solid var(--line);text-align:center}
.feedbreak span{position:relative;top:-.6em;background:var(--paper);padding:0 var(--space-md);font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--faint)}

/* quiet sections collapse to one status line each — no acres of empty paper */
.quietgroup{margin-top:var(--space-xl);border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:var(--space-sm) var(--space-lg) var(--space-xs)}
/* task #2: label the collapsed group so it reads as intentional, not disabled */
.quietcap{font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--muted);padding:var(--space-xs) 0 var(--space-sm);border-bottom:1px solid var(--line)}
section.quiet{display:flex;align-items:baseline;gap:var(--space-md);padding:var(--space-sm) 0;border-bottom:1px solid var(--line)}
section.quiet:last-child{border-bottom:0}
section.quiet h2{font:700 14px/1.4 var(--fd);letter-spacing:-.01em;white-space:nowrap;min-width:8.5rem;color:var(--ink)}
section.quiet .empty{font:500 13px/1.5 var(--fu);color:var(--muted);padding:0}

.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--space-md)}
.card{position:relative;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:6px;cursor:pointer;transition:border-color .15s ease-out,transform .15s cubic-bezier(.22,1,.36,1)}
.card:hover{border-color:var(--muted);transform:translateY(-2px);z-index:2}
.card[hidden]{display:none}
.card:focus-visible{outline:2px solid var(--ember);outline-offset:2px}
.thumb{width:100%;height:auto;max-height:150px;object-fit:contain;background:var(--paper);border-radius:6px 6px 0 0}
.thumb.noimg{height:150px}
.thumb.noimg img{width:100%;height:100%;object-fit:contain;display:block}
div.thumb{display:flex;align-items:center;justify-content:center;color:var(--faint);font:700 30px/1 var(--fd)}
/* roaster-card banner: blurred cover backdrop + contained foreground, so wide
   photos go full-bleed and logos sit on a soft blur of themselves — never
   cropped, never a bare letterbox gap. Banners are og:images of mixed shapes;
   the box height comes inline (--b carries the image url). */
.rbanner{position:relative;overflow:hidden;border-radius:6px 6px 0 0;background:var(--surface)}
.rbanner::before{content:"";position:absolute;inset:0;background:var(--b) center/cover no-repeat;filter:blur(16px);transform:scale(1.15);opacity:.5}
.rbanner img{position:relative;width:100%;height:100%;object-fit:contain;display:block}
.evchip{position:absolute;top:var(--space-sm);left:var(--space-sm);font:700 10.5px/1 var(--fu);letter-spacing:.06em;text-transform:uppercase;color:var(--paper);border-radius:99px;padding:.3rem .55rem}
.ev-new .evchip{background:var(--ember)}
.ev-drop .evchip{background:var(--drop)}
.ev-restock .evchip{background:var(--restock)}
.card .body{padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-xs);flex:1;border-top:1px solid var(--line)}
.who{font:700 10.5px/1.3 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--faint)}
.bean{font:600 15.5px/1.25 var(--fd);letter-spacing:-.01em}
.tags{display:flex;gap:var(--space-xs);flex-wrap:wrap}
/* tags foreshadow the coffee-page spec-plate: uppercase micro-label type,
   tabular-nums, a hairline border on the plain (roast/variant) pill — the
   pill SHAPE stays (tile declutter decision), only the surface texture and
   the origin cell's mono country code echo the plate. No new color. */
.tag{font:700 10px/1 var(--fu);color:var(--muted);background:var(--paper);border:1px solid var(--line);border-radius:99px;padding:.22rem .55rem;text-transform:uppercase;letter-spacing:.06em;font-variant-numeric:tabular-nums}
.tag.decaf{color:var(--ember);background:var(--ember-soft);border-color:transparent}
.tag.origin{background:var(--restock-soft);color:var(--restock);border-color:transparent}
.tag.origin.t3{background:var(--drop-soft);color:var(--drop);font-weight:800}
.tag.origin.t2{background:var(--ember-soft);color:var(--ember)}
.mic{margin-left:.4em;vertical-align:-3px;color:var(--muted)}
.micon{width:15px;height:15px}
/* per-roaster hide/only (#16, F1 recoverability pass #128): quiet icon
   buttons in the tile's roaster row. --muted (not --faint) as the idle
   color plus a soft background on hover/focus reads as an actual control
   instead of decoration — the founder-flagged "too small/unclear" fix.
   On mobile the box grows to a real 44px touch target (not just the visual
   glyph), same size discipline as .hidebtn's mobile floor below. */
.rhide,.ronly{margin-left:.35em;border:0;background:none;padding:0;color:var(--muted);cursor:pointer;font-size:11px;line-height:1;vertical-align:-2px;width:18px;height:18px;border-radius:99px;display:inline-flex;align-items:center;justify-content:center;transition:color .15s ease-out,background-color .15s ease-out}
.rhide:hover,.rhide:focus-visible{color:var(--drop);background:var(--drop-soft)}
.ronly:hover,.ronly:focus-visible{color:var(--ember);background:var(--ember-soft)}
@media(max-width:640px){.rhide,.ronly{width:44px;height:44px;font-size:15px}}
/* hide/only status line: never a dead end — stays put under the toolbar as
   long as anything is hidden or an "only" is active; undo/show-all always
   one tap away. Same chip vocabulary as the rest of the toolbar. Restyled
   (#128 F1) as a bordered surface, not bare text, so the recovery action
   reads as a persistent panel rather than a line easy to miss. */
/* Host wrapper is inserted after .toolbar (a full-width sibling), so it must
   re-apply the same 76rem/auto/side-padding cage or the bar goes full-bleed on
   desktop and left-aligns to the viewport edge instead of the content column. */
.hidebars{max-width:76rem;margin:0 auto;padding:0 var(--space-lg)}
.hidebar{display:none;align-items:center;gap:var(--space-sm);flex-wrap:wrap;margin-top:var(--space-sm);padding:var(--space-sm) var(--space-md);border:1px solid var(--line);border-radius:6px;background:var(--surface);font:600 12px/1.4 var(--fu);color:var(--muted)}
.hidebtn{font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.04em;border:1px solid var(--line);background:var(--paper);color:var(--ink);border-radius:99px;padding:.35rem .7rem;cursor:pointer;transition:border-color .15s ease-out}
.hidebtn:hover{border-color:var(--muted)}
.hidebtn.primary{border-color:var(--ember);color:var(--ember)}
.hidebtn.primary:hover{background:var(--ember-soft)}
@media(max-width:640px){.hidebtn{min-height:44px}}
/* empty-state recovery panel (#128 F1): only ever shown when hiding/only
   leaves zero visible beans anywhere on the page — a blank feed must never
   be the only thing a visitor sees. Same bordered-surface family as
   .hidebar/.suggest, ember CTA matches .fetchcta's weight without reusing
   its exact class (button, not an anchor). */
.hideempty{display:none;margin-top:var(--space-sm);border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:var(--space-lg);text-align:left}
.hideemptyhead{font:700 14px/1.5 var(--fu);color:var(--ink)}
.hideemptybtn{display:inline-block;margin-top:var(--space-md);font:700 13px/1 var(--fu);color:var(--paper);background:var(--ember);border:0;border-radius:99px;padding:.7rem 1.1rem;cursor:pointer;transition:opacity .15s ease-out}
.hideemptybtn:hover{opacity:.85}
.hideemptysub{margin-top:var(--space-sm);font:500 12.5px/1.6 var(--fu);color:var(--muted)}
@media(max-width:640px){.hideemptybtn{min-height:44px}}
.spark{color:var(--ember);margin-top:var(--space-sm)}
.sparkcap{font:600 10.5px/1.6 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--faint)}
.desc{font:400 15px/1.65 var(--fu);max-width:65ch;margin-top:var(--space-lg);color:var(--ink)}
.cwrap{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,3fr);gap:var(--space-2xl);margin-top:var(--space-xl);align-items:start}
/* hero must never crop the pack shot: contain, not cover */
.cwrap .thumb{height:auto;max-height:420px;object-fit:contain;background:var(--surface);border:1px solid var(--line);border-radius:6px}
@media (prefers-color-scheme:dark){.cwrap .thumb{background:var(--plate)}}
.gstrip{display:flex;gap:var(--space-sm);margin-top:var(--space-sm);flex-wrap:wrap}
.gthumb{width:64px;height:64px;object-fit:cover;border:1px solid var(--line);border-radius:4px;cursor:pointer;background:var(--surface);padding:0;transition:border-color .15s ease-out}
.gthumb.on,.gthumb:hover{border-color:var(--ink)}
.gthumb:focus-visible{outline:2px solid var(--ember);outline-offset:1px}
.gthumb img{width:100%;height:100%;object-fit:cover;border-radius:3px;display:block}
.soldby{display:flex;align-items:center;gap:var(--space-sm);margin-top:var(--space-lg);font:500 13px/1.5 var(--fu);color:var(--muted)}
.soldby img{width:26px;height:26px;object-fit:contain;border:1px solid var(--line);border-radius:4px;padding:2px;background:var(--surface)}
.soldby a{color:var(--ember)}
@media(max-width:760px){.cwrap{grid-template-columns:1fr}}
.numbers{margin-top:auto;padding-top:var(--space-sm)}
.price{font:700 16px/1.2 var(--fu)}
.was{color:var(--faint);text-decoration:line-through;font-weight:500;font-size:12.5px;margin-right:.45em}
.delta{font:700 11.5px/1.5 var(--fu)}
.ev-drop .delta{color:var(--drop)}
.ev-restock .delta{color:var(--restock)}
.ev-new .delta{color:var(--ember)}
.norm{font:500 11.5px/1.5 var(--fu);color:var(--muted)}
.listed{font:500 11px/1.5 var(--fu);color:var(--faint)}
.buy{font:600 12.5px/1 var(--fu);color:var(--ember);text-decoration:none;border-bottom:1.5px solid currentColor;padding-bottom:1px;transition:opacity .15s ease-out}
.buy:hover{opacity:.75}
/* prominent secondary CTA: ember-outline, hard 6px edge, so the inspector's
   link to the richer full coffee page reads as an obvious affordance instead
   of the buried plain-text link it replaces (founder UX feedback, 2026-07-08) */
.fullpage{display:inline-flex;align-items:center;gap:.3rem;font:700 12.5px/1 var(--fu);color:var(--ember);text-decoration:none;border:1.5px solid var(--ember);border-radius:6px;padding:.55rem .85rem;margin-bottom:var(--space-sm);transition:background .15s ease-out,color .15s ease-out;min-height:44px}
.fullpage:hover{background:var(--ember);color:var(--paper)}

/* split view — list keeps flowing left, focused item lives in a sticky
   inspector on the right (asymmetric 5:3). no modals, no overlays. */
.layout{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-xl);align-items:start}
.layout.split{grid-template-columns:minmax(0,5fr) minmax(18rem,3fr)}
.inspect{display:none}
.layout.split .inspect{display:block;position:sticky;top:calc(var(--header-h, 0px) + var(--space-2xl) + var(--space-sm));max-height:calc(100vh - var(--header-h, 0px) - var(--space-2xl) - var(--space-xl));overflow:auto;background:var(--surface);border:1px solid var(--muted);border-radius:6px;animation:slidein .2s cubic-bezier(.22,1,.36,1)}
@keyframes slidein{from{opacity:0;transform:translateX(8px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.layout.split .inspect{animation:none}}
.inspect .thumb{height:360px;max-height:none;border-bottom:1px solid var(--line);object-fit:contain;background:var(--surface)}
.inspect .body{padding:var(--space-lg);display:flex;flex-direction:column;gap:var(--space-xs);border-top:0}
.inspect .bean{font-size:19px}
.inspect .detail{border-top:1px solid var(--line);padding:var(--space-lg);background:var(--paper);display:block}
.inspect .evchip{position:static;align-self:flex-start;margin-bottom:var(--space-xs)}
.card.sel{border-color:var(--ink);transform:none}
.card .detail{display:none}
/* ≤860px there is no split view: tapping a tile navigates to /coffee/<slug>/
   instead (the sticky inspector 8000px up-page was invisible from deep in the
   list — P0, 2026-07-05 review). The detail panel stays server-rendered for
   the coffee page's own use; it is never shown inline here. */
@media(max-width:860px){
.layout.split{grid-template-columns:1fr}
.layout.split .inspect{display:none}
}
/* chips: wrapper is layout-invisible on desktop, a scrollable rail on phones
   (10 chips stacked 3 rows deep ate a third of the first screen) */
.chiprail{display:contents}
@media(max-width:640px){
.chiprail{display:flex;flex:1 1 100%;gap:var(--space-xs);overflow-x:auto;flex-wrap:nowrap;padding-bottom:2px;-webkit-overflow-scrolling:touch;scrollbar-width:none;scroll-snap-type:x proximity}
.chiprail::-webkit-scrollbar{display:none}
.chiprail .chip{flex:0 0 auto;scroll-snap-align:start;min-height:44px}
}
/* phone toolbar, header stack and origin belt — same 640px density line as
   the chip rail above, no third breakpoint. Search goes full-width on its own
   row (16px kills iOS zoom-on-focus); the belt collapses to its chip fallback
   since the 4.5px SVG labels are unreadable at 390px; the meter drops to a
   full-width ticker line; every control clears the 44px tap floor. */
@media(max-width:640px){
.find{flex-basis:100%;font-size:16px}
.sortsel{min-height:44px}
.beltmap,.beltcap .belthelp{display:none}
.railhelp .tip{max-width:min(240px,70vw);white-space:normal}
.bchip{min-height:44px}
.meter{flex:1 1 100%;text-align:left;order:3}
.lsw a{padding:16px 12px;margin:-16px -12px}
.fullpage{min-height:44px}
}
/* ≤640px density — 2-col compact tile grid (1 column shows ~1.5 tiles at
   375px, too airy for a ticker; 2 columns keep ~4 on screen), smaller
   thumbs/stars/padding, plus per-page trims: the coffee hero can't fill the
   first screen, the vtable drops its redundant availability column (the .oos
   dimming + avail pill already carry it), roaster chrome tightens. */
@media(max-width:640px){
.cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-sm)}
#featured .cards{gap:var(--space-sm)}
#featured .cards .card{flex:0 0 150px}
.thumb{height:110px}
.card .body{padding:var(--space-sm)}
.star{width:34px;height:34px}
.cartadd{width:34px;height:34px;right:calc(34px + var(--space-sm) + 6px)}
.cwrap .thumb{max-height:48vh}
.vtable th:nth-child(2),.vtable td:nth-child(2){display:none}
.brandbar{height:120px}
.stats{gap:var(--space-lg)}
.stat b{font-size:22px}
}

.vtable{width:100%;border-collapse:collapse;font:500 12.5px/1.55 var(--fu)}
.vtable th{font:700 10.5px/1.3 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--faint);text-align:left;padding:.3rem .5rem .3rem 0;border-bottom:1px solid var(--line)}
.vtable td{padding:.4rem .5rem .4rem 0;border-bottom:1px solid var(--line)}
.vtable td:last-child,.vtable th:last-child{text-align:right;padding-right:0}
.oos{color:var(--faint)}
.detail .foot{display:flex;gap:var(--space-lg);flex-wrap:wrap;margin-top:var(--space-md);font:500 12px/1.6 var(--fu);color:var(--muted);align-items:baseline}
.detail .foot a{color:var(--ember)}
.close{font:600 11.5px/1 var(--fu);color:var(--muted);background:none;border:1px solid var(--line);border-radius:99px;padding:.35rem .65rem;cursor:pointer;margin-left:auto;transition:all .15s ease-out}
.close:hover{color:var(--ink);border-color:var(--muted)}
.empty{color:var(--faint);font:500 13px/1.6 var(--fu);padding:var(--space-sm) 0}
.nomatch{display:none;color:var(--muted);font:500 13.5px/1.6 var(--fu);padding:var(--space-lg) 0}
.nomatch a{color:var(--ember)}
.fetchcta{display:inline-block;font:700 14px/1 var(--fu);color:var(--paper)!important;background:var(--ember);border-radius:99px;padding:.7rem 1.1rem;text-decoration:none;transition:opacity .15s ease-out}
.fetchcta:hover{opacity:.85}

.crumb{font:600 12.5px/1 var(--fu);color:var(--muted);text-decoration:none;display:inline-block;margin-top:var(--space-lg);transition:color .15s ease-out}
.crumb:hover{color:var(--ink)}
/* profile brandbar: a fixed strip, always cover-cropped. Only genuinely wide
   art belongs here — site.js swaps anything squarer than 2:1 (logos, pack
   shots) for the roastery placeholder rather than butchering it. */
.brandbar{height:170px;overflow:hidden;background:var(--surface)}
.brandbar img{width:100%;height:100%;object-fit:cover;object-position:center;display:block}
.profile{display:flex;gap:var(--space-lg);align-items:center;flex-wrap:wrap;margin-top:var(--space-xl)}
.profile h1{font:800 clamp(1.7rem,3.6vw,2.4rem)/1.05 var(--fd);letter-spacing:-.025em}
.brandlogo{width:60px;height:60px;object-fit:contain;background:var(--surface);border:1px solid var(--line);border-radius:6px;padding:5px}
.profile .domain{font:600 13px/1 var(--fu);color:var(--ember);text-decoration:none;border-bottom:1.5px solid currentColor;padding-bottom:1px}
.claim{font:700 10px/1 var(--fu);text-transform:uppercase;letter-spacing:.08em;color:var(--muted);border:1px dashed var(--faint);border-radius:99px;padding:.4rem .65rem;text-decoration:none;transition:all .15s ease-out}
.claim:hover{color:var(--ink);border-color:var(--ink)}
.claim.claimed{color:var(--drop);border:1px solid var(--drop);display:inline-flex;align-items:center;gap:.35em}
.claim.manage{border-style:solid}
.beancheck{width:15px;height:15px;flex:none;overflow:visible}
.pblurb{font:400 14.5px/1.6 var(--fu);color:var(--muted);max-width:62ch;margin-top:var(--space-md)}
.pmeta{display:flex;flex-wrap:wrap;gap:var(--space-lg);font:600 12.5px/1.5 var(--fu);color:var(--muted);margin-top:var(--space-md)}
.pmeta a{color:var(--ember);text-decoration:none}
.pmeta a:hover{border-bottom:1.5px solid currentColor}
.locmeta{flex-basis:100%;order:99}
.locmeta>summary{cursor:pointer;list-style:none;color:inherit}
.locmeta>summary::-webkit-details-marker{display:none}
.locmeta>summary:hover{border-bottom:1.5px solid currentColor}
.locpanel{margin-top:var(--space-sm);padding:var(--space-md);border:1px solid var(--line);border-radius:6px;max-width:26rem}
.locmap{width:100%;height:160px;border:0;border-radius:5px;display:block}
.locnote{font:500 12px/1.5 var(--fu);color:var(--muted);margin:0}
.locactions{display:flex;flex-wrap:wrap;gap:var(--space-sm);margin-top:var(--space-sm)}
.locmap+.locactions{margin-top:var(--space-md)}
.locaction{font:700 12px/1 var(--fu);color:var(--ember);text-decoration:none;border:1.5px solid var(--ember);border-radius:99px;padding:.5rem .85rem;background:none;cursor:pointer;transition:all .15s ease-out}
.locaction:hover{background:var(--ember);color:var(--paper)}
.contactrow{display:flex;flex-wrap:wrap;gap:var(--space-sm);margin-top:var(--space-md)}
.contactrow a{font:700 12.5px/1 var(--fu);color:var(--ember);text-decoration:none;border:1.5px solid var(--ember);border-radius:99px;padding:.5rem .85rem;transition:all .15s ease-out}
.contactrow a:hover{background:var(--ember);color:var(--paper)}
.promo{margin-top:var(--space-lg);border:1px solid var(--ember);border-left:4px solid var(--ember);border-radius:6px;padding:var(--space-md) var(--space-lg);background:oklch(from var(--ember) l c h / .06);font:500 14px/1.5 var(--fu);color:var(--ink);display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-sm)}
.promocode{font:700 13px/1 var(--fm,monospace);background:var(--surface);border:1px dashed var(--ember);border-radius:4px;padding:.3rem .5rem;color:var(--ember);display:inline-flex;align-items:center;gap:.4rem;cursor:pointer}
.promocode .pcode{filter:blur(4px);user-select:none;letter-spacing:.1em}
.promocode.revealed .pcode{filter:none;user-select:text}
.promocode .plabel{font:700 10.5px/1 var(--fu);text-transform:uppercase;letter-spacing:.03em;opacity:.75}
.promo a{color:var(--ember);font-weight:700;text-decoration:none;border-bottom:1.5px solid currentColor}
.lead{margin-top:var(--space-xl);border:1px solid var(--line);border-radius:6px;padding:0 var(--space-lg)}
.lead>summary{font:700 13px/1 var(--fu);color:var(--ink);cursor:pointer;padding:var(--space-md) 0;list-style:none}
.lead>summary::-webkit-details-marker{display:none}
.lead>summary::before{content:"+ ";color:var(--ember)}
.lead[open]>summary::before{content:"– "}
.lead form{display:flex;flex-direction:column;gap:var(--space-sm);padding-bottom:var(--space-lg);max-width:34rem}
.lead label{font:600 11px/1.4 var(--fu);text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
.lead input,.lead select,.lead textarea{font:400 14px/1.4 var(--fu);color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:5px;padding:.6rem .7rem;width:100%}
.lead textarea{min-height:5rem;resize:vertical}
.lead .hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.lead button{font:700 13px/1 var(--fu);color:var(--paper);background:var(--ink);border:0;border-radius:99px;padding:.7rem 1.2rem;cursor:pointer;align-self:flex-start;transition:opacity .15s ease-out}
.lead button:hover{opacity:.85}
.lead button:disabled{opacity:.5;cursor:default}
.lead .msg{font:600 12.5px/1.5 var(--fu);margin-top:.2rem}
.lead .msg.ok{color:var(--drop)}
.lead .msg.err{color:var(--ember)}
.stats{display:flex;gap:var(--space-2xl);flex-wrap:wrap;margin:var(--space-xl) 0 var(--space-sm)}
.stat b{display:block;font:800 26px/1.1 var(--fd);letter-spacing:-.02em}
.stat span{font:700 10.5px/2 var(--fu);text-transform:uppercase;letter-spacing:.09em;color:var(--faint)}

.totop{position:fixed;right:var(--space-lg);bottom:calc(var(--space-lg) + env(safe-area-inset-bottom,0px));font:700 12px/1 var(--fu);color:var(--paper);background:var(--ink);border:0;border-radius:99px;padding:.7rem 1rem;cursor:pointer;opacity:0;pointer-events:none;transform:translateY(6px);transition:opacity .2s ease-out,transform .2s cubic-bezier(.22,1,.36,1);z-index:5}
.totop.show{opacity:.92;pointer-events:auto;transform:none}
.totop:hover{opacity:1}
.sentinel{height:1px}
/* transient status toast (watchlist cap warning, task #10) */
.toast{position:fixed;left:50%;bottom:calc(var(--space-xl) + env(safe-area-inset-bottom,0px));transform:translate(-50%,var(--space-sm));z-index:60;max-width:calc(100vw - 2*var(--space-lg));text-align:center;font:600 12.5px/1.4 var(--fu);color:var(--paper);background:var(--ink);border-radius:99px;padding:var(--space-sm) var(--space-lg);opacity:0;pointer-events:none;transition:opacity .2s ease-out,transform .2s cubic-bezier(.22,1,.36,1)}
.toast.show{opacity:.97;transform:translate(-50%,0)}
@media (prefers-reduced-motion:reduce){.toast{transition:opacity .2s ease-out}}
.card.reveal{animation:reveal .35s cubic-bezier(.22,1,.36,1) both}
@keyframes reveal{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.card.reveal{animation:none}.totop{transition:none}}
/* interim: hide add-to-cart affordance until Phase 3b cart drawer ships (#134)
   localStorage cart plumbing remains intact for #135/#142 */
.cartadd,.cartchip{display:none}

footer{font:500 12px/1.75 var(--fu);color:var(--muted);padding:var(--space-3xl) var(--space-lg) var(--space-2xl);max-width:46rem;margin:0 auto;text-align:center}
footer a{color:var(--ember)}

/* ⋯ roaster menu popover — same fixed + body-detached pattern as .sortlist.
   The JS (openRoasterMenu) reparents .rmenupop to <body> so it escapes any
   .card overflow and can be positioned with getBoundingClientRect. */
.rmenuwrap{position:relative;display:inline-block}
.rmenu{font:700 14px/1 var(--fu);color:var(--muted);background:none;border:0;padding:0 .2rem;cursor:pointer;user-select:none}
.rmenu:hover,.rmenu:focus-visible{color:var(--ink)}
.rmenu[aria-expanded="true"]{color:var(--ember)}
.rmenupop{position:fixed;z-index:1000;list-style:none;margin:0;padding:4px;min-width:150px;background:var(--surface);border:1px solid var(--line);border-radius:8px;font:600 12px/1 var(--fu);color:var(--ink);box-shadow:0 4px 12px rgba(0,0,0,.08);display:none}
.rmenupop.open{display:block}
.rmenupop .rmenuitem{display:block;width:100%;text-align:left;border:0;background:none;padding:.55rem .7rem;border-radius:5px;cursor:pointer;white-space:nowrap}
.rmenupop .rmenuitem:hover{background:var(--ember-soft)}
.rmenupop .rmenuitem:focus{background:var(--ember-soft);outline:none}

/* non-bean inventory extra cards (task 3) */
.card.extra{display:flex;flex-direction:column}
.card.extra .face{width:100%;aspect-ratio:1;object-fit:contain;background:var(--paper,#fdf8f2)}
.card.extra .body{padding:.6rem .75rem;display:flex;flex-direction:column;gap:.35rem}
.card.extra .etitle{font-size:.95rem;line-height:1.25;margin:0}
.card.extra .eprice{font-weight:700}
.card.extra .ebuy{font-size:.85rem}
.card.extra.oos{opacity:.6}
.feed.extras h2{margin-top:1.5rem}
