/* Public pages (WP A5). Plain CSS on the visual system's tokens (design/high-fidelity/
   ACG_VISUAL_SYSTEM.md §2-§5, §11-§12); no build step, no third-party request, no inline
   style (the site's CSP allows only its own origin).

   Follows the visitor's system light/dark setting and never overrides it. Green means
   "you can act" (buttons, links, focus) plus the two trusted words "Open" and "Confirmed".
   Type: Instrument Sans, one self-hosted variable woff2 (visual system §3, §11 "Fonts"). */

/* The only font file the site loads: Latin and Latin Extended-A, both axes (weight 400-700,
   width 75-100 %). The relative path is rewritten to the fingerprinted name in production.
   `swap` shows the text in the system font at once and switches when the file arrives. */
@font-face {
  font-family: "Instrument Sans";
  src: url("../fonts/InstrumentSans-latin-var.2c9a6add106a.woff2") format("woff2");
  font-weight: 400 700;
  font-stretch: 75% 100%;
  font-style: normal;
  font-display: swap;
}

:root {
  --c-canvas: #f5f4ef;
  --c-surface: #ffffff;
  --c-sunken: #eeede7;
  --c-text: #161a17;
  --c-text-secondary: #434840;
  --c-text-muted: #62675f;
  --c-divider: #e7e5de;
  --c-border: #dad7ce;
  --c-border-strong: #b9b5aa;
  --c-brand: #1d6644;
  --c-brand-hover: #175637;
  --c-brand-pressed: #12462d;
  --c-on-brand: #ffffff;
  --c-brand-text: #1b5e3f;
  --c-brand-tint: #e5efe7;
  --c-brand-line: #bcd5c3;
  --c-focus: #1d6644;
  --c-focus-gap: #ffffff;
  --c-aging: #b07a1e;
  --c-warning: #7a5009;
  --c-warning-bg: #faf0db;
  /* Card and freshness tokens (visual system §2 and the prototype, same names and values). */
  --c-card: #ffffff;
  --c-photo-ground: #d9d6cc;
  --c-text-on-photo: #ffffff;
  --c-border-control: #86837a;
  --c-selected: #1d6644;
  --c-status-open: #1b5e3f;
  --c-status-closed: #9a3a22;
  --c-status-unknown: #62675f;
  --c-fresh: #434840;
  --c-aging-glyph: #b07a1e;
  --c-stale: #62675f;
  --photo-scrim: linear-gradient(
    180deg,
    rgba(8, 10, 9, 0) 0%,
    rgba(8, 10, 9, 0.44) 26%,
    rgba(8, 10, 9, 0.66) 52%,
    rgba(8, 10, 9, 0.82) 100%
  );

  /* Type (visual system §3): Instrument Sans, self-hosted (the `@font-face` above), then the
     system fonts while it loads or if it cannot. The scale is in rem (16 px = 1rem at the
     browser's default size), so the browser's text-size setting reaches every size; line heights
     are unitless, so they follow. Three weights only: 400, 550, 650. */
  --font: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    sans-serif;
  --fs-place: 2.125rem; /* 34: the shop page's own name, the one size nothing else shares */
  --fs-display: 1.625rem; /* 26 */
  --fs-name: 1.25rem; /* 20: card names, the two answers */
  --lh-name: 1.2; /* below this the last line's descenders are cut by the card's clamp */
  --fs-heading: 1.1875rem; /* 19 */
  --fs-body: 1rem; /* 16 */
  --lh-body: 1.4375; /* 23 at 16 */
  --fs-ui: 0.9375rem; /* 15: buttons, the state word, the address */
  --fs-row: 0.875rem; /* 14: card rows, chips, links */
  --lh-row: 1.35;
  --fs-count: 0.8125rem; /* 13 */
  --fs-meta: 0.78125rem; /* 12.5 */
  --lh-meta: 1.36;
  --fs-small: 0.75rem; /* 12: compact labels, never smaller except the attribution */
  --fs-attr: 0.625rem; /* 10: the photo and map attribution */
  --w-regular: 400;
  --w-medium: 550;
  --w-strong: 650;
  --place-width: 88%;
  --m-press: 100ms;
  --m-quick: 150ms;

  --s2: 4px;
  --s3: 6px;
  --s4: 8px;
  --s5: 10px;
  --s6: 12px;
  --s7: 16px;
  --s8: 20px;
  --s9: 24px;
  --r-xs: 3px;
  --r-md: 8px;
  --r-lg: 12px;
  /* The map, the drawer and their floating parts (visual system §2 "Map", §5, §6, §10; A4). */
  --c-surface-raised: #ffffff;
  --c-drawer: #ffffff;
  --c-brand-tint-strong: #cfe2d4;
  --c-selected-bg: #eef5ef;
  --c-error: #a12f1d;
  --c-error-bg: #fbeae5;
  --c-error-border: #c9533e;
  --c-map-land: #eeece5;
  --c-map-label: #58544b;
  --c-marker-fill: #161a17;
  --c-marker-stroke: #ffffff;
  --c-marker-hollow: #ffffff;
  --c-marker-unknown: #7c796f;
  --c-marker-selected: #1d6644;
  --c-marker-label-bg: #12462d;
  --c-marker-label-text: #ffffff;
  --c-pill-bg: #ffffff;
  --c-pill-text: #161a17;
  --c-pill-border: #9f9b90;
  --c-pill-muted: #62675f;
  --c-pill-selected-bg: #1d6644;
  --c-pill-selected-text: #ffffff;
  --c-cluster-bg: #fbfaf7;
  --c-cluster-text: #434840;
  --c-cluster-border: #9f9b90;
  --e1: 0 1px 2px rgba(22, 26, 23, 0.16), 0 0 0 1px rgba(22, 26, 23, 0.04);
  --e2: 0 10px 24px -8px rgba(22, 26, 23, 0.28), 0 2px 6px rgba(22, 26, 23, 0.1);
  --e-drawer: 0 -1px 0 var(--c-divider), 0 -8px 24px -12px rgba(22, 26, 23, 0.3);
  --pin-shadow: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
  --hit: 44px;
  /* The side gutters: 16 px, or the device's own inset where that is wider (a landscape phone's
     notch on either side). The brand bar is `--brand-h` tall plus its 1 px rule; the pinned
     parts of the wide layout sit under it by that token, not by a copy of the number. */
  --gutter-l: max(var(--s7), env(safe-area-inset-left));
  --gutter-r: max(var(--s7), env(safe-area-inset-right));
  --brand-h: 56px;
  --site-header-h: calc(var(--brand-h) + 1px);
  --s1: 2px;
  --r-sm: 6px;
  --r-xl: 16px;
  --m-drawer: 400ms;
  --ease-drawer: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --c-canvas: #0c0e0d;
    --c-surface: #131614;
    --c-sunken: #0f1210;
    --c-text: #f0ede5;
    --c-text-secondary: #c9c5ba;
    --c-text-muted: #9c988d;
    --c-divider: #232724;
    --c-border: #2b302c;
    --c-border-strong: #3c423d;
    --c-brand: #5fb585;
    --c-brand-hover: #71c294;
    --c-brand-pressed: #4e9e72;
    --c-on-brand: #07140d;
    --c-brand-text: #7fc99e;
    --c-brand-tint: #14231a;
    --c-brand-line: #25402f;
    --c-focus: #8ad3a9;
    --c-focus-gap: #0c0e0d;
    --c-aging: #d59e3e;
    --c-warning: #e2b462;
    --c-warning-bg: #2a2011;
    --c-card: #161917;
    --c-photo-ground: #232724;
    --c-border-control: #7a776e;
    --c-selected: #6cbd90;
    --c-status-open: #7fc99e;
    --c-status-closed: #e9937a;
    --c-status-unknown: #9c988d;
    --c-fresh: #c9c5ba;
    --c-aging-glyph: #d59e3e;
    --c-stale: #9c988d;
    --c-surface-raised: #1b1f1c;
    --c-drawer: #131614;
    --c-brand-tint-strong: #1b3123;
    --c-selected-bg: #142019;
    --c-error: #f29a87;
    --c-error-bg: #2c1612;
    --c-error-border: #b4553f;
    --c-map-land: #151816;
    --c-map-label: #a19d92;
    --c-marker-fill: #f0ede5;
    --c-marker-stroke: #0c0e0d;
    --c-marker-hollow: #151816;
    --c-marker-unknown: #8a867c;
    --c-marker-selected: #6cbd90;
    --c-marker-label-bg: #6cbd90;
    --c-marker-label-text: #07140d;
    --c-pill-bg: #202421;
    --c-pill-text: #f0ede5;
    --c-pill-border: #6a6f69;
    --c-pill-muted: #a8a498;
    --c-pill-selected-bg: #6cbd90;
    --c-pill-selected-text: #07140d;
    --c-cluster-bg: #1b1f1c;
    --c-cluster-text: #c9c5ba;
    --c-cluster-border: #5d625c;
    --e1: 0 1px 2px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(240, 237, 229, 0.06);
    --e2: 0 12px 28px -8px rgba(0, 0, 0, 0.75), 0 0 0 1px rgba(240, 237, 229, 0.07);
    --e-drawer: 0 -1px 0 var(--c-border-strong), 0 -10px 28px -12px rgba(0, 0, 0, 0.8);
    --pin-shadow: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
  }
}

/* Reduced motion: every duration token is zero, on every page (it was in map.css, so only the
   search page had it, and the profile's and the lists' own transitions still ran). */
@media (prefers-reduced-motion: reduce) {
  :root {
    --m-press: 0ms;
    --m-quick: 0ms;
    --m-drawer: 0ms;
  }
}

/* The one rule for `hidden`, on every page. A component's own `display` (flex, inline-flex, grid)
   outranks the browser's `[hidden]`, so without this an element hidden until script, or hidden
   again by it, would still show. Components do not patch it one by one. */
[hidden] {
  display: none !important;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: var(--s9);
}

body {
  margin: 0;
  background: var(--c-canvas);
  color: var(--c-text);
  font: 400 var(--fs-body) / var(--lh-body) var(--font);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Form controls do not inherit the page's font by themselves. */
button,
input,
select,
textarea {
  font-family: inherit;
}

/* The browser's "bold" is 700, outside the three weights (400, 550, 650). */
h1,
h2,
h3,
h4,
h5,
h6,
b,
strong,
th {
  font-weight: var(--w-strong);
}

a {
  color: var(--c-brand-text);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 2px;
}

/* Only for a link that sets no colour of its own (a link in prose). `a:hover` had the specificity
   of an element and a pseudo-class, which beat every single-class component rule: the active chip
   turned its own text the colour of its fill when hovered (1.25:1). With `:where()` a component's
   colour wins, and each component that wants a hover colour says so. */
:where(a):hover {
  color: var(--c-brand-hover);
}

:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--c-focus-gap);
}

main:focus {
  outline: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: var(--s7);
  top: -100px;
  z-index: 100; /* above the sticky headers and the drawer (14 and 12 at most), or it is hidden on focus */
  padding: var(--s6) var(--s7);
  background: var(--c-surface);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md);
}

.skip:focus {
  top: var(--s4);
}

/* ---- header and footer ---- */

.site-header {
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-divider);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s6);
  min-height: var(--brand-h);
  padding: 0 var(--gutter-r) 0 var(--gutter-l);
  max-width: 44rem;
  margin: 0 auto;
  color: var(--c-text);
  text-decoration: none;
}

.brand-mark {
  flex: none;
  color: var(--c-brand);
}

.brand-name {
  font-weight: 650;
  font-stretch: 88%;
  font-size: var(--fs-body);
}

.site-footer {
  max-width: 44rem;
  margin: var(--s9) auto 0;
  padding: var(--s8) var(--gutter-r) calc(var(--s9) + env(safe-area-inset-bottom)) var(--gutter-l);
  border-top: 1px solid var(--c-divider);
  color: var(--c-text-secondary);
  font-size: var(--fs-row);
  line-height: 1.4;
}

.footer-notice {
  margin: 0 0 var(--s6);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--s7);
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-links a {
  display: inline-block;
  min-width: 44px;
  padding: 12px 0;
  font-weight: 550;
}

/* ---- page ---- */

.page {
  max-width: 44rem;
  margin: 0 auto;
  padding: var(--s8) var(--gutter-r) var(--s8) var(--gutter-l);
}

.page h1 {
  margin: 0 0 var(--s6);
  font-size: var(--fs-display);
  line-height: 1.15;
  font-weight: 650;
  font-stretch: 88%;
}

.page h2 {
  margin: var(--s9) 0 var(--s4);
  font-size: var(--fs-heading);
  line-height: 1.25;
  font-weight: 650;
}

.page h3 {
  margin: var(--s8) 0 var(--s4);
  font-size: var(--fs-body);
  font-weight: 650;
}

/* The default spacing of running text. It has no specificity of its own (`:where`), so a
   component's own one-class rule (`.mchip-list`, `.mrows`, `.profile-foot`, `.mcarry`,
   `.attribution`, `.hours-note`, `.mcheap-full`, ...) decides its margin, as it was written
   to. As `.page p` it outranked all of them and quietly zeroed or reset their margins (UX
   review, 5 Oct 2026, Appendix C.1). */
:where(.page :is(p, ul, ol, table)) {
  margin: 0 0 var(--s6);
}

.meta {
  color: var(--c-text-muted);
}

.meta {
  font-size: var(--fs-count);
  line-height: 1.385;
}

/* The shop page's Back (the Founder, 1 Oct 2026): a link with an arrow, quiet, 44 px tall. It
   sits in the page's top bar beside Share (see "the shop page, redesigned" below), which is
   what stays at the top of a phone while the page scrolls. */
.back {
  margin: 0 0 var(--s4);
  font-size: var(--fs-ui);
}

.back a {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--hit);
  padding-right: var(--s6);
  font-weight: var(--w-strong);
  text-decoration: none;
}

.back svg {
  flex: none;
}

@media (width < 1024px) {
  /* The category chips jump to the menu; it must not stop under the top bar above it. */
  .profile #menu {
    scroll-margin-top: calc(var(--hit) + var(--s4));
  }
}

/* ---- the search page (WP A3) ----
   Phone first: the field, the quick filters and the count sit at the top, the cards follow.
   Everything works as a plain form and plain links; A4 adds the map drawer and in-place
   updates on top of the same markup. */

.search-page {
  max-width: 44rem;
  margin: 0 auto;
  padding: var(--s6) var(--gutter-r) var(--s8) var(--gutter-l);
}

/* Visual system §11: 44 px, radius 8, sunken fill, control edge, an 18 px search icon. */
.search-field {
  position: relative;
}

.search-icon {
  position: absolute;
  left: var(--s6);
  top: 50%;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--c-text-muted);
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}

.search-input {
  width: 100%;
  min-height: 44px;
  padding: 0 var(--s6) 0 38px;
  border: 1px solid var(--c-border-control);
  border-radius: var(--r-md);
  background: var(--c-sunken);
  color: var(--c-text);
  font: inherit;
  font-size: var(--fs-body); /* 16 px: phones do not zoom into the field */
}

/* The placeholder is the field's only visible label: the muted text colour at full opacity, in
   place of the browser's own grey (3.93:1 light, 4.09:1 dark); this token is at least 4.94:1. */
.search-input::placeholder {
  color: var(--c-text-muted);
  opacity: 1;
}

/* Visual system §11: focus turns the field to the surface with a doubled brand edge. */
.search-input:focus {
  background: var(--c-surface);
  border-color: var(--c-brand);
  box-shadow: 0 0 0 1px var(--c-brand);
  outline: none;
}

/* The field and, while the search panel is open, its Cancel (UX §8; prototype `.hdr__row`). */
.search-row {
  display: flex;
  gap: var(--s4);
  align-items: center;
}

.search-row .search-field {
  flex: 1;
  min-width: 0;
}

/* The field has its own clear button (below), so the browser's never shows: one ×, not two. */
.search-input::-webkit-search-cancel-button {
  display: none;
  -webkit-appearance: none;
  appearance: none;
}

/* The clear button (the Founder, 6 Oct 2026): a small × at the right end inside the field while it
   has text. suggest.js shows it; it is hidden in markup, so without script it is not there to do
   nothing. 28 px of control inside the 44 px field, with the whole 44 px as its hit area. */
.search-clear {
  position: absolute;
  top: 50%;
  right: var(--s4);
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: 0;
  padding: 0;
  transform: translateY(-50%);
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--c-text-muted);
  cursor: pointer;
}

.search-clear::after {
  content: "";
  position: absolute;
  inset: -8px;
}

.search-clear svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}

.search-clear:hover {
  color: var(--c-text);
}

.search-clear:active {
  background: var(--c-brand-tint);
  color: var(--c-text);
}

/* The typed text stops short of the button, which is only there with text. */
.search-field:has(.search-clear:not([hidden])) .search-input {
  padding-right: var(--hit);
}

.search-cancel {
  flex: none;
  height: var(--hit);
  padding: 0 var(--s2);
  border: 0;
  background: none;
  color: var(--c-brand-text);
  font: inherit;
  font-size: var(--fs-body);
  font-weight: var(--w-medium);
  line-height: 1;
  cursor: pointer;
}

.field {
  display: grid;
  gap: var(--s2);
  font-size: var(--fs-row);
}

.field-label {
  font-weight: 550;
}

.field select {
  min-height: 44px;
  padding: 0 var(--s4);
  border: 1px solid var(--c-border-control); /* 3.8:1 light, 4.1:1 dark (WCAG 1.4.11: 3:1) */
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text);
  font: inherit;
  font-size: var(--fs-body);
}

.field-note {
  color: var(--c-text-muted);
  font-size: var(--fs-count);
}

.field-check {
  display: flex;
  align-items: center;
  gap: var(--s4);
  min-height: 44px;
}

.field-check input {
  width: 22px;
  height: 22px;
  accent-color: var(--c-brand);
}

/* The quick filters scroll sideways in their own row (UX §6.2 rule 5), never the page. */
/* One chip, everywhere (visual system §11 "Chips"): 34 px, radius 8, 14 px 550, a
   `border-strong` edge on surface, brand fill when it is on. The rows that hold them - the
   search page's quick filters, the search panel's, the menu's categories - add only their own
   scrolling and hit areas. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  padding: 0 var(--s6);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text);
  font-size: var(--fs-row);
  font-weight: 550;
  text-decoration: none;
}

.chip-on {
  background: var(--c-brand);
  border-color: var(--c-brand);
  color: var(--c-on-brand);
}

.chip:not(.chip-on):hover {
  border-color: var(--c-border-control);
}

/* The chip that is on keeps its on-fill text when hovered, wherever it is (the menu's category
   chips too), and so does the count inside it. */
.chip-on:hover {
  background: var(--c-brand-hover);
  color: var(--c-on-brand);
}

.quick-list {
  display: flex;
  gap: var(--s4);
  /* Room for the chips' 44 px hit areas, and for their focus ring (2 px + a 2 px gap, 4 px
     outside the chip) which a scrolling row clips on every side; the margin takes the sideways
     room back so the chips still line up with the field. */
  margin: var(--s4) -4px 0;
  padding: 5px 4px;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}

/* Visual system §11 and the prototype's chip rules: 34 px with a 44 px hit area; hover,
   pressed and active (brand fill) states; the row wraps on desktop. */
.quick-list .chip,
.mchip-list .chip {
  position: relative;
  flex: none;
  min-height: 34px;
  height: 34px;
  white-space: nowrap;
  transition:
    background-color var(--m-quick) linear,
    border-color var(--m-quick) linear,
    color var(--m-quick) linear,
    transform var(--m-press) linear;
}

.quick-list .chip::after,
.mchip-list .chip::after {
  content: "";
  position: absolute;
  inset: -5px -4px;
}

.quick-list .chip:hover,
.mchip-list .chip:hover {
  border-color: var(--c-border-control);
}

.quick-list .chip:active,
.mchip-list .chip:active {
  transform: scale(0.97);
  background: var(--c-sunken);
}

.quick-list .chip-on,
.mchip-list .chip-on {
  border-color: var(--c-brand);
  background: var(--c-brand);
  color: var(--c-on-brand);
}

.quick-list .chip-on:hover,
.mchip-list .chip-on:hover {
  background: var(--c-brand-hover);
  color: var(--c-on-brand);
}

.quick-list .chip-on:active,
.mchip-list .chip-on:active {
  background: var(--c-brand-pressed);
}

@media (width >= 1024px) {
  .quick-list {
    flex-wrap: wrap;
    overflow: visible;
  }
}

/* More chips than fit (UX review, 5 Oct 2026, A.2): the row scrolls sideways with no scrollbar,
   so the edge where chips are still out of sight fades out. It serves every row that scrolls its
   chips, the search page's quick filters and the shop page's categories (6 Oct 2026): the row is
   marked `data-fade-row` and the scrolling list inside it `data-fade-list`. quickfade.js says
   which edges (`data-more` on the row: "end", "start" or "both") and only while the row really overflows, so
   a row that fits, or wraps on a desktop, is never faded. It is a mask, not a colour laid over
   the page: it reads the same on the light and the dark canvas. For a visitor who asked for less
   transparency a solid 3 px bar at the edge says the same without any blending. */
[data-fade-row][data-more="end"] [data-fade-list] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
}

[data-fade-row][data-more="start"] [data-fade-list] {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 36px), transparent);
  mask-image: linear-gradient(to left, #000 calc(100% - 36px), transparent);
}

[data-fade-row][data-more="both"] [data-fade-list] {
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    #000 36px,
    #000 calc(100% - 36px),
    transparent
  );
  mask-image: linear-gradient(to right, transparent, #000 36px, #000 calc(100% - 36px), transparent);
}

@media (prefers-reduced-transparency: reduce) {
  [data-fade-row][data-more] [data-fade-list] {
    -webkit-mask-image: none;
    mask-image: none;
  }

  .quick,
  .mchips {
    position: relative;
  }

  .quick[data-more="end"]::after,
  .quick[data-more="both"]::after,
  .quick[data-more="start"]::before,
  .quick[data-more="both"]::before,
  .mchips[data-more="end"]::after,
  .mchips[data-more="both"]::after,
  .mchips[data-more="start"]::before,
  .mchips[data-more="both"]::before {
    content: "";
    position: absolute;
    top: calc(var(--s4) + 5px);
    width: 3px;
    height: 34px;
    border-radius: 2px;
    background: var(--c-border-strong);
    pointer-events: none;
  }

  .quick[data-more="end"]::after,
  .quick[data-more="both"]::after {
    right: 0;
  }

  .quick[data-more="start"]::before,
  .quick[data-more="both"]::before {
    left: 0;
  }
  /* The categories' list reaches the page's edges, wider than its row by the gutter. */
  .mchips[data-more]::before,
  .mchips[data-more]::after {
    top: 5px;
  }

  .mchips[data-more="end"]::after,
  .mchips[data-more="both"]::after {
    right: calc(-1 * var(--s7));
  }

  .mchips[data-more="start"]::before,
  .mchips[data-more="both"]::before {
    left: calc(-1 * var(--s7));
  }
}

/* The styles of the one category chosen (D15): the same chips in a second row, closer to the
   first than the first is to the field. */
.quick--styles .quick-list {
  margin-top: 0;
}

@media (prefers-reduced-motion: reduce) {
  .quick-list .chip,
  .mchip-list .chip {
    transition: none;
  }
}

/* The Filters chip, first in the row (UX §9; prototype `.chip--filters`, `.chip__n`): the sliders
   icon, and the number of filters set inside the sheet in a brand tile. */
.chip--filters {
  padding-left: var(--s5);
  gap: var(--s3);
}

/* "Near me" (D20), first in the row, before Filters: the same chip as a real button, so it takes
   the page's type back from the browser's button and shows that it can be pressed. While it is
   finding the position it says so and is dimmed (`aria-busy`); a second tap is ignored. */
.chip--near {
  padding-left: var(--s5);
  font-family: inherit;
  cursor: pointer;
}

.chip--near[aria-busy="true"] {
  opacity: 0.65;
  cursor: progress;
}

.chip--filters svg,
.chip--near svg {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chip__n {
  font-variant-numeric: tabular-nums;
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: 4px;
  background: var(--c-brand);
  color: var(--c-on-brand);
  font-size: var(--fs-small);
  font-weight: var(--w-strong);
  line-height: 1;
}

/* ---- the filter sheet (visual system §11 "Filter sheet", §5 "Modal"; prototype `.sheet`) ----
   A full surface over the page. filters.js opens it as a modal dialog; without script the
   chip's link shows it (:target). On wide screens its content keeps the search page's measure. */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 25;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--c-surface);
  color: var(--c-text);
}

.sheet[open],
.sheet:target {
  display: block;
}

.sheet::backdrop {
  background: transparent;
}

.sheet__form {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-width: 44rem;
  margin: 0 auto;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left);
}

.sheet__head {
  flex: none;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* The title on the left, where the body's own text starts; × at the right end (the Founder,
     6 Oct 2026). */
  padding: 0 var(--s2) 0 var(--s7);
  border-bottom: 1px solid var(--c-divider);
}

.sheet__head h2 {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
  line-height: 1;
}

/* The title takes the focus when a panel is opened by a tap or a click (dialog.js), so a screen
   reader says what opened. It is not a control: it never shows a ring. */
.sheet__head h2:focus,
.psheet__head h2:focus {
  outline: none;
  box-shadow: none;
}

.sheet__body {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: none;
}

.sheet__foot {
  flex: none;
  padding: var(--s6) var(--s7) calc(var(--s6) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--c-divider);
  background: var(--c-surface);
}

.sheet__foot .btn {
  width: 100%;
  font-size: var(--fs-body);
}

.fgroup {
  padding: var(--s6) var(--s7) var(--s7);
  border-bottom: 1px solid var(--c-divider);
}

.fgroup .field {
  gap: 0;
}

.fgroup .field-label {
  font-size: var(--fs-ui);
  font-weight: var(--w-strong);
  line-height: 1.3;
}

.fgroup .field-note {
  margin-top: 1px;
  color: var(--c-text-secondary);
  line-height: 1.35;
}

.fgroup .field select {
  margin-top: var(--s4);
}

.fgroup .field-check {
  gap: var(--s4);
  font-size: var(--fs-body);
  font-weight: var(--w-medium);
}

/* 44 px icon button (prototype `.icon-btn`) and the quiet text button (`.btn-quiet`). */
.icon-btn {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: var(--hit);
  height: var(--hit);
  border-radius: var(--r-md);
  color: var(--c-text);
  transition:
    background-color var(--m-press) linear,
    transform var(--m-press) linear;
}

.icon-btn svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-btn:hover {
  background: var(--c-sunken);
  color: var(--c-text);
}

.icon-btn:active {
  background: var(--c-brand-tint);
  transform: scale(0.96);
}

.btn-quiet {
  display: inline-flex;
  align-items: center;
  height: var(--hit);
  padding: 0 var(--s4);
  border-radius: var(--r-md);
  color: var(--c-brand-text);
  font-size: var(--fs-ui);
  font-weight: var(--w-strong);
  line-height: 1;
  text-decoration: none;
}

/* ---- the Price panel (UX §9 "Max price per gram", revised 1 Oct 2026; visual system §11
   "€/g popover and slider") ---- A small raised panel from the foot of the page, in the sheet's
   pattern: price.js opens it as a modal dialog; without script the chip's link shows it
   (:target). One native range, a 44 px hit at the thumb's height, and Done. */

.psheet {
  position: fixed;
  inset: auto 0 0;
  z-index: 25;
  width: 100%;
  max-width: 44rem;
  height: auto;
  /* Never taller than the screen below the status bar: on a landscape phone, or at 200% zoom
     (WCAG 1.4.4, 1.4.10), the body scrolls inside the panel and its head, its Close and its
     Done stay where they can be reached. */
  max-height: calc(100vh - max(var(--s4), env(safe-area-inset-top)));
  max-height: calc(100dvh - max(var(--s4), env(safe-area-inset-top)));
  margin: 0 auto;
  /* Beside a notch the panel keeps clear of it. */
  padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left);
  border: 1px solid var(--c-border-strong);
  border-bottom: 0;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--c-surface-raised);
  color: var(--c-text);
  box-shadow: var(--e2);
}

.psheet[open],
.psheet:target {
  display: flex;
  flex-direction: column;
}

.psheet::backdrop {
  background: rgba(8, 10, 9, 0.5);
}

.psheet__form {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
}

.psheet__head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 56px;
  padding: 0 var(--s2) 0 var(--s7);
  border-bottom: 1px solid var(--c-divider);
}

.psheet__head h2 {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
  line-height: 1;
}

.psheet__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s6) var(--s7) var(--s7);
}

/* The reading above the slider: 17 px 650, the value the slider stands at. */
.price-value {
  display: block;
  margin: 0 0 var(--s2);
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
}

/* One track, two thumbs (the Founder, 6 Oct 2026): two native ranges laid over one drawn track.
   Each is a real slider (arrow keys, Home and End, its own name and value text); the inputs take
   no touch themselves, only their thumbs do, so a thumb is grabbed wherever it is and the one on
   top is the one that has room to move. A thumb is a 44 px hit with the 28 px disc drawn inside it
   (a transparent 8 px border, the fill kept to the padding box), so the touch target is the size
   of a button and the disc the size it was. --lo and --hi are the thumbs' places on the track, 0
   to 1 (price.js). */
.price-slider {
  position: relative;
  height: var(--hit);
  --lo: 0;
  --hi: 1;
}

/* A 6 px track in `sunken` with a `border-control` edge, the brand between the thumbs. It ends
   where the discs do (8 px in from the 44 px hits), so a disc at an end sits on the track's end. */
.price-track {
  position: absolute;
  inset: calc((var(--hit) - 6px) / 2) 8px;
  border-radius: 3px;
  background: var(--c-sunken);
  box-shadow: inset 0 0 0 1px var(--c-border-control);
}

.price-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(14px + (100% - 28px) * var(--lo));
  right: calc(14px + (100% - 28px) * (1 - var(--hi)));
  border-radius: 3px;
  background: var(--c-brand);
}

/* At "Any price" the fill goes and the rings are quiet, so a price and no price never look alike.
   Without script (the left thumb is still hidden) there is no range to fill. */
.price-slider[data-any] .price-fill,
.price-slider:has(.price-range--lo[hidden]) .price-fill {
  display: none;
}

.price-range {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: var(--hit);
  margin: 0;
  padding: 0;
  background: transparent;
  accent-color: var(--c-brand); /* where the browser draws the control itself */
  -webkit-appearance: none;
  appearance: none;
  pointer-events: none;
  cursor: pointer;
}

.price-range::-webkit-slider-runnable-track {
  height: var(--hit);
  background: transparent;
}

.price-range::-moz-range-track {
  height: var(--hit);
  background: transparent;
}

.price-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  box-sizing: border-box;
  width: var(--hit);
  height: var(--hit);
  border: 8px solid transparent;
  border-radius: 50%;
  background: var(--c-surface);
  background-clip: padding-box;
  box-shadow: inset 0 0 0 2px var(--c-brand);
  pointer-events: auto;
}

.price-range::-moz-range-thumb {
  box-sizing: border-box;
  width: var(--hit);
  height: var(--hit);
  border: 8px solid transparent;
  border-radius: 50%;
  background: var(--c-surface);
  background-clip: padding-box;
  box-shadow: inset 0 0 0 2px var(--c-brand);
  pointer-events: auto;
}

.price-slider[data-any] .price-range::-webkit-slider-thumb {
  box-shadow: inset 0 0 0 2px var(--c-border-control);
}

.price-slider[data-any] .price-range::-moz-range-thumb {
  box-shadow: inset 0 0 0 2px var(--c-border-control);
}

/* Where the thumbs meet, the one with room to go is on top. */
.price-slider[data-top="lo"] .price-range--lo {
  z-index: 1;
}

.price-slider:not([data-top="lo"]) .price-range--hi {
  z-index: 1;
}

/* The focus ring goes round the disc, not round the 44 px strip the thumbs travel in. */
.price-range:focus-visible {
  outline: none;
  box-shadow: none;
}

.price-range:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--c-focus);
  outline-offset: -4px;
}

.price-range:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--c-focus);
  outline-offset: -4px;
}

.price-ticks {
  display: flex;
  justify-content: space-between;
  margin: 0 0 var(--s6);
  color: var(--c-text-muted);
  font-size: var(--fs-small);
  line-height: 1.2;
}

.psheet__foot {
  flex: none;
  padding: 0 var(--s7) calc(var(--s6) + env(safe-area-inset-bottom));
}

.psheet__foot .btn {
  width: 100%;
  font-size: var(--fs-body);
}

/* The Type panel (D15) is the Price panel with a short list of choices where the slider was:
   native radios, each a 48 px row, so a thumb finds one. */
.optlist {
  min-width: 0;
  margin: 0 0 var(--s4);
  padding: 0;
  border: 0;
}

.optlist .field-check {
  min-height: 48px;
  font-size: var(--fs-body);
}

.optlist .field-check input {
  flex: none;
}

/* ---- the search panel (UX §8; visual system §11 "Search suggestions"; prototype `.suggest`,
   `.sug`) ---- While it is open it stands where the results were; the chips row steps aside. */

.is-searching .quick,
.is-searching #results-region {
  display: none;
}

.suggest__h {
  margin: var(--s7) 0 var(--s2);
  font-size: var(--fs-count);
  font-weight: var(--w-strong);
  line-height: 1.2;
  color: var(--c-text-secondary);
}

.suggest__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  margin: 0;
  padding: 5px 0;
  list-style: none;
}

.suggest__chips .chip {
  position: relative;
  min-height: 34px;
  height: 34px;
  white-space: nowrap;
}

.suggest__chips .chip::after {
  content: "";
  position: absolute;
  inset: -5px -4px;
}

.suggest__chips .chip-on {
  border-color: var(--c-brand);
  background: var(--c-brand);
  color: var(--c-on-brand);
}

.suggest__list {
  margin: 0;
  padding: var(--s2) 0 0;
  list-style: none;
}

.sug {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s6);
  min-height: 52px;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--c-divider);
  color: var(--c-text);
  font-size: var(--fs-body);
  line-height: 1.3;
  cursor: pointer;
}

.sug mark {
  background: none;
  color: inherit;
  font-weight: var(--w-strong);
}

.sug small {
  flex: none;
  color: var(--c-text-muted);
  font-size: var(--fs-count);
  line-height: 1.2;
  text-align: right;
}

.sug--enter {
  color: var(--c-brand-text);
}

/* The row the arrow keys have reached (the field keeps the focus): the focus ring. */
.sug.is-active {
  outline: 2px solid var(--c-focus);
  outline-offset: -2px;
}

.suggest__credits {
  margin-top: var(--s7);
}

/* While the sheet is open the page behind it does not scroll. */
html:has(.sheet[open], .psheet[open]) {
  overflow: hidden;
}

.sheet,
.sheet__body {
  overscroll-behavior: contain;
}

.summary {
  margin: var(--s6) 0 var(--s4);
}

.count-line {
  margin: 0;
  font-size: var(--fs-count);
  color: var(--c-text-secondary);
}

/* ==== discovery: the Sort button, the Wheelchair chip's icon and the Sort panel (the Founder,
   2 Oct 2026) ====
   The count line and the Sort button share a row: the line takes what it needs, the button
   stands at the right. The button is a chip 32 px high with its 44 px hit area, like the quick
   filters'. The Sort panel is the `.psheet` of Price and Type; only its hint line is its own. */
.count-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
}

.count-row .count-line {
  min-width: 0;
}

.sortbtn {
  position: relative;
  flex: none;
  min-height: 32px;
  padding: 0 var(--s5);
  white-space: nowrap;
}

.sortbtn::after {
  content: "";
  position: absolute;
  inset: -6px -4px;
}

.sortbtn:hover {
  border-color: var(--c-border-control);
}

.sortbtn:active {
  background: var(--c-sunken);
}

/* The starting point, a fixed control inside the count line (UX review, 5 Oct 2026): the place
   and a caret, in the page's link colour, with the 44 px hit area the chips have. It stays on one
   line (a place's name is never broken); when the sentence is too long the whole control moves
   to the next line. */
.startctl {
  position: relative;
  display: inline-block;
  color: var(--c-brand-text);
  font-weight: var(--w-medium);
  white-space: nowrap;
  text-decoration: none;
}

.startctl::after {
  content: "";
  position: absolute;
  inset: -14px -6px;
}

.startctl:hover {
  text-decoration: underline;
}

.startctl__caret {
  margin-left: 3px;
  vertical-align: -1px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The straight-line note under the count line: small, and quiet. */
.note.start-note {
  margin: 0;
  font-size: var(--fs-count);
  color: var(--c-text-muted);
}

/* Phosphor "regular" icons are filled outlines: a 256-unit box, `currentColor`, no stroke. */
.ic-ph {
  flex: none;
  fill: currentColor;
  stroke: none;
}

/* What would make a disabled choice apply, under it and in line with its label. */
.optlist__hint {
  margin: calc(var(--s4) * -1) 0 var(--s3) calc(22px + var(--s4));
}

/* The one hint that is a link is a 44 px target. */
.optlist__hint a {
  display: inline-flex;
  align-items: center;
  min-height: var(--hit);
  color: var(--c-brand-text);
  font-weight: 550;
}

.optlist .field-check:has(input:disabled) {
  color: var(--c-text-muted);
}

.applied {
  margin: var(--s2) 0 0;
  font-size: var(--fs-row);
}

.applied-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--s6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.applied-label {
  color: var(--c-text-muted);
}

.applied-chip,
.applied-clear {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 550;
}

.applied-chip {
  color: var(--c-text);
  text-decoration: none;
}

/* Links that set their own colour name their hover colour (`:where(a):hover` does not reach
   them), as they always showed one. */
.btn-quiet:hover,
.applied-chip:hover,
.addr-map:hover {
  color: var(--c-brand-hover);
}

/* The word space in Instrument Sans is narrow; the remove mark gets a little more. */
.applied-chip [aria-hidden="true"] {
  margin-left: 0.2em;
}

.note {
  margin: var(--s2) 0 0;
  font-size: var(--fs-row);
  color: var(--c-text-secondary);
}

/* D16: "Showing Pre-rolls for “joints”." and the way back to the plain words, which is a link on
   a 44 px line of its own when the sentence leaves no room for it (the Founder, 2 Oct 2026). */
.alias-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--s4);
}

.alias-note a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 550;
}

/* D21: the second line of the note ("Within 1 km needs a starting point." with its Near me
   button) and, under an exact product name, the divider before the shops that only share its
   words. */
.alias-note--more .btn {
  min-height: 44px;
  padding: 0 var(--s7);
}

.cards-divider {
  margin: var(--s9) 0 var(--s7);
  padding-top: var(--s7);
  border-top: 1px solid var(--c-border);
}

.cards-divider__title {
  margin: 0;
  font-size: var(--fs-heading);
  font-weight: var(--w-strong);
}

.cards-divider__note {
  margin: var(--s2) 0 0;
  font-size: var(--fs-row);
  color: var(--c-text-secondary);
}

.exact-none {
  margin: 0 0 var(--s7);
}

.hits {
  margin: 0 0 var(--s6);
  font-size: var(--fs-row);
  line-height: 2rem;
}

.empty-result {
  margin: var(--s8) 0;
}

.empty-result p {
  margin: 0 0 var(--s6);
}

.empty-title {
  font-size: var(--fs-heading);
  font-weight: var(--w-strong);
}

.empty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
}

.empty-note {
  color: var(--c-text-secondary);
  font-size: var(--fs-row);
}

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  margin: var(--s8) 0 0;
}

.pager-where {
  color: var(--c-text-muted);
  font-size: var(--fs-row);
}

/* ---- coffeeshop card (visual system §8, §11) ----
   Copied from the approved prototype (design/high-fidelity/ACG_HIGH_FIDELITY.html, "Coffeeshop
   card"), token for token. A shop with a photograph shows it with only its name over it; a
   shop without one shows the identity band: brand tint, the canal-ring arcs off the right
   edge, the name in ink. "A shop without a photo is shorter, never emptier." The whole card
   is one link laid over it (`card__link`). The Map affordance of row 3 arrives with the map
   (A4); selected, hover-from-marker and pending are driven by it too. */

.cards {
  margin: 0;
  padding: 0;
  list-style: none;
}

.card {
  position: relative;
  margin-bottom: var(--s4);
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  overflow: hidden;
  transition:
    transform var(--m-press) linear,
    opacity var(--m-quick) linear;
}

.card__link {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
}

.card__link:focus-visible {
  outline-offset: -3px;
  box-shadow: none;
}

.card:has(.card__link:active) {
  transform: scale(0.985);
}

.card:has(.card__link:active)::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--c-text);
  opacity: 0.05;
  pointer-events: none;
  z-index: 2;
}

.card.is-selected {
  box-shadow: 0 0 0 2px var(--c-selected);
  border-color: var(--c-selected);
}

.card.is-hover {
  outline: 2px dashed var(--c-selected);
  outline-offset: 1px;
}

.card__media {
  position: relative;
  height: 108px;
  background: var(--c-photo-ground);
}

.card__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.card__scrim {
  position: absolute;
  inset: auto 0 0 0;
  height: 88px;
  background: var(--photo-scrim);
  pointer-events: none;
}

.card__name {
  position: absolute;
  left: var(--s6);
  right: var(--s6);
  bottom: 9px;
  margin: 0;
  color: var(--c-text-on-photo);
  font: var(--w-strong) var(--fs-name) / var(--lh-name) var(--font);
  font-stretch: var(--place-width);
  letter-spacing: -0.005em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.card__attr {
  position: absolute;
  top: var(--s4);
  right: var(--s4);
  max-width: 62%;
  margin: 0;
  padding: 3px 6px;
  border-radius: var(--r-xs);
  background: rgba(12, 14, 12, 0.62);
  color: #fff;
  font: var(--w-regular) var(--fs-attr) / 1.25 var(--font);
  text-align: right;
}

/* no-photo identity band */
.card--nophoto .card__media {
  height: 60px;
  background: var(--c-brand-tint);
}

.card--nophoto .card__name {
  color: var(--c-text);
  text-shadow: none;
  top: 50%;
  bottom: auto;
  transform: translateY(-50%);
  right: 96px;
}

.band-motif {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.band-motif path {
  fill: none;
  stroke: var(--c-brand-line);
  stroke-width: 1.25;
}

.card__body {
  padding: var(--s4) var(--s6);
}

.card__r1 {
  font: var(--w-regular) var(--fs-row) / var(--lh-row) var(--font);
  color: var(--c-text-secondary);
}

.card__r1 .d {
  color: var(--c-text);
  font-weight: var(--w-strong);
  font-variant-numeric: tabular-nums;
}

.card__r1 .hood {
  color: var(--c-text-muted);
}

/* D24: with the Wheelchair filter on, what is step-free and what is not, under row 1: the
   compact line of the shop page's tile, its two lines as two lines, behind the chip's icon. */
.card__access {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin-top: var(--s1);
  font: var(--w-regular) var(--fs-row) / var(--lh-row) var(--font);
  color: var(--c-text-secondary);
}

.card__access .ic-ph {
  flex: none;
  margin-top: 2px;
}

.card__access-line {
  display: block;
}

.st {
  font-weight: var(--w-strong);
}

.st--open {
  color: var(--c-status-open);
}

.st--closed {
  color: var(--c-status-closed);
}

.st--unknown {
  color: var(--c-status-unknown);
}

.card__r2 {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  margin-top: 3px;
  font: var(--w-regular) var(--fs-row) / var(--lh-row) var(--font);
  color: var(--c-text);
}

.card__r2 .m {
  font-weight: var(--w-strong);
}

.card__r2 .quiet {
  color: var(--c-text-muted);
}

.price {
  flex: none;
  font-weight: var(--w-strong);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.price small {
  font-size: var(--fs-meta);
  font-weight: var(--w-regular);
  color: var(--c-text-secondary);
  margin-right: 3px;
}

/* The one further tier of a product (D23) stands on a line of its own: after the headline a
   "·" read as part of it ("€7.80/g · 5 g €34"). */
.tiers {
  display: block;
  color: var(--c-text-secondary);
  font-variant-numeric: tabular-nums;
}

.cat-tag {
  display: inline-block;
  padding: 1px 5px;
  margin: 0 2px;
  border-radius: var(--r-xs);
  border: 1px solid var(--c-border-strong);
  font: var(--w-medium) var(--fs-small)/1.3 var(--font);
  color: var(--c-text-secondary);
  vertical-align: 1px;
}

.card__r3 {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s4);
  margin-top: 3px;
}

.fresh {
  margin: 0;
  min-width: 0;
  font: var(--w-regular) var(--fs-meta) / var(--lh-meta) var(--font);
  color: var(--c-text-secondary);
  font-variant-numeric: tabular-nums;
}

.unavail-tag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 6px;
  border-radius: var(--r-xs);
  background: var(--c-warning-bg);
  color: var(--c-warning);
  font: var(--w-medium) var(--fs-small)/1.35 var(--font);
  white-space: nowrap;
}

.unavail-tag svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}

.num {
  font-variant-numeric: tabular-nums;
}

/* Browse categories: two plus "+n" on phones, four on desktop (VS §11, plan Q6). */
.cats-desktop {
  display: none;
}

@media (width >= 1024px) {
  .cats-phone:has(+ .cats-desktop) {
    display: none;
  }

  .cats-desktop {
    display: inline;
  }
}

@media (width < 375px) {
  .card__media {
    height: 100px;
  }

  .card--nophoto .card__media {
    height: 60px;
  }
}

@media (width >= 1024px) {
  .card__media {
    height: 140px;
  }

  .card--nophoto .card__media {
    height: 60px;
  }

  .card:hover {
    border-color: var(--c-border-control);
  }
}

@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none;
  }
}

/* ---- profile ---- */

.place-name {
  overflow-wrap: break-word;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  margin: var(--s7) 0 var(--s4);
}

/* Buttons: `.btn` is the base (the secondary look); a variant is a `--` modifier on it
   (`.btn--primary`), and a modifier is only written when it has a rule. `.map .btn` is the
   smaller one inside the map (map.css). `.btn-quiet` is a different thing, a text button. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 var(--s7);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-brand-text);
  font: inherit;
  font-size: var(--fs-ui);
  font-weight: 650;
  text-decoration: none;
  cursor: pointer;
}

.btn--primary {
  background: var(--c-brand);
  border-color: var(--c-brand);
  color: var(--c-on-brand);
}

.btn--primary:hover {
  background: var(--c-brand-hover);
  color: var(--c-on-brand);
}

.notice {
  padding: var(--s6) var(--s7);
  border-radius: var(--r-lg);
  background: var(--c-warning-bg);
  color: var(--c-warning);
  font-size: var(--fs-ui);
}

.disclosure {
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  margin-bottom: var(--s6);
}

.disclosure summary {
  display: flex;
  justify-content: space-between;
  gap: var(--s6);
  align-items: center;
  min-height: 48px;
  padding: 0 var(--s7);
  font-weight: 650;
  cursor: pointer;
}

.hours {
  width: calc(100% - 2 * var(--s7));
  margin: 0 var(--s7) var(--s7);
  border-collapse: collapse;
  font-size: var(--fs-ui);
}

.hours th,
.hours td {
  padding: var(--s4) 0;
  text-align: left;
  border-top: 1px solid var(--c-divider);
}

.hours th {
  font-weight: 400;
  color: var(--c-text-secondary);
}

.hours .today th,
.hours .today td {
  font-weight: 650;
  color: var(--c-text);
}

/* ---- the profile, revised (visual system §11 "Profile:…", §12) ----
   Every size, weight, radius and spacing below is the approved prototype's
   (design/high-fidelity/ACG_SHOP_MENU.html, 30 Sep 2026) on this file's tokens. */

/* ==== the shop page, redesigned (the Founder, 2 Oct 2026) ====
   Everything between here and the hours disclosure below is the profile's new top: a hero behind
   the top of the page, the top bar, the name, the two answers, the address, the strip of three
   facts and the actions. It supersedes the 30 Sep layout (the two answers as small-caps
   labels, the one quiet trust line, a row of equal buttons). The mock-up is the Founder's
   (design/UX_MODEL.md, "Profile"); the Phosphor icons come in through `{% icon %}`. */

.profile {
  position: relative;
  isolation: isolate; /* the hero sits behind the page's own content, not behind the page */
  /* Glass: the translucent fill of the small boxes and round buttons that sit over the hero. */
  --c-glass: rgba(255, 255, 255, 0.62);
  --c-glass-line: rgba(22, 26, 23, 0.08);
  /* What keeps the text legible over a photograph: the page's own ground, thickening downward. */
  --c-hero-veil: linear-gradient(
    180deg,
    rgba(245, 244, 239, 0.3),
    rgba(245, 244, 239, 0.7) 60%,
    rgba(245, 244, 239, 0.92)
  );
}

/* Over a photograph the text must be legible whatever the photograph is, black or white
   (WCAG 1.4.3, 1.4.11). The first veil here (5 Oct 2026) laid 0.66 to 0.97 of the page's own
   colour over the whole hero: the text was safe and the photograph a faint tint (3 to 7 %
   visible below the name). Now the photograph is veiled lightly everywhere (`--c-hero-veil`) and
   the text block, and only it, sits on a scrim (`--c-scrim`, drawn by `.profile-head::before`):
   the name, the two answers and the address, whatever their height, because the scrim is the
   block's own box, feathered 22 px above and 18 px below so it has no hard edge. Back and Share
   stay solid pills, so the strip above the name shows the photograph almost as it is. Behind any
   text the page's colour is 0.76 over a black photograph (light) or a white one (dark), which is
   what 4.5:1 needs; the browser test measures it. A shop with no photograph keeps the lighter
   tint and has no scrim. */
.profile--photo {
  --c-hero-veil: rgba(245, 244, 239, 0.14);
  --c-scrim: rgba(245, 244, 239, 0.72);
}

.profile--photo .profile-head {
  position: relative;
}

.profile--photo .profile-head::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -22px calc(-1 * var(--gutter-r)) -18px calc(-1 * var(--gutter-l));
  background: linear-gradient(
    180deg,
    transparent,
    var(--c-scrim) 22px,
    var(--c-scrim) calc(100% - 18px),
    transparent
  );
  pointer-events: none;
}

@media (prefers-color-scheme: dark) {
  .profile {
    --c-glass: rgba(22, 25, 23, 0.55);
    --c-glass-line: rgba(240, 237, 229, 0.08);
    --c-hero-veil: linear-gradient(
      180deg,
      rgba(12, 14, 13, 0.35),
      rgba(12, 14, 13, 0.7) 60%,
      rgba(12, 14, 13, 0.92)
    );
  }

  .profile--photo {
    --c-hero-veil: rgba(12, 14, 13, 0.16);
    --c-scrim: rgba(12, 14, 13, 0.74);
  }
}

.ph {
  flex: none;
  fill: currentColor;
}

/* The hero: behind the top of the page only, faded out by the address line. A shop with no
   visible photograph gets the cards' own band (brand tint and the canal-ring arcs, §8), slightly
   blurred and frosted; a shop with one gets the photograph, blurred about 9 px and veiled. */
.hero {
  position: absolute;
  z-index: -1;
  top: 0;
  right: 0;
  left: 0;
  height: 300px;
  overflow: hidden;
  background: var(--c-brand-tint);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(#000 55%, transparent);
  mask-image: linear-gradient(#000 55%, transparent);
}

.hero-motif {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  filter: blur(1.5px);
}

.hero-motif path {
  fill: none;
  stroke: var(--c-brand-line);
  stroke-width: 2;
}

.hero-frost {
  position: absolute;
  inset: 0;
  -webkit-backdrop-filter: blur(6px) saturate(1.2);
  backdrop-filter: blur(6px) saturate(1.2);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0));
}

.hero--photo {
  height: 330px;
  background: var(--c-photo-ground);
  -webkit-mask-image: linear-gradient(#000 50%, transparent);
  mask-image: linear-gradient(#000 50%, transparent);
}

/* Over-size by the blur's reach, so the blurred edge is cropped away rather than seen. */
.hero--photo img {
  position: absolute;
  inset: -24px;
  width: calc(100% + 48px);
  max-width: none;
  height: calc(100% + 48px);
  object-fit: cover;
  object-position: 50% 30%;
  filter: blur(9px) saturate(1.1);
}

.hero-veil {
  position: absolute;
  inset: 0;
  background: var(--c-hero-veil);
}

/* The top bar: Back on the left, Share on the right. */
.profile-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
}

.profile .back {
  margin: 0;
}

/* Over a photograph, Back and Share stand on the page's own surface: a green word on a photograph
   fell to 1.04:1 over black, and glass lets the photograph through. */
.profile--photo .back a {
  padding-left: var(--s5);
  border: 1px solid var(--c-glass-line);
  border-radius: calc(var(--hit) / 2);
  background: var(--c-surface);
}

.profile--photo .round {
  background: var(--c-surface);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.round {
  position: relative;
  display: grid;
  flex: none;
  place-items: center;
  width: var(--hit);
  height: var(--hit);
  padding: 0;
  border: 1px solid var(--c-glass-line);
  border-radius: 50%;
  background: var(--c-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--c-text);
  cursor: pointer;
}

/* What the button says when it has copied the link: a small tag under it, for a few seconds. */
.round[data-said]::after {
  content: attr(data-said);
  position: absolute;
  top: calc(100% + var(--s3));
  right: 0;
  padding: var(--s2) var(--s5);
  border-radius: var(--r-md);
  background: var(--c-text);
  color: var(--c-canvas);
  font-size: var(--fs-count);
  font-weight: var(--w-strong);
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
}

/* On a phone the top bar stays at the top while the page scrolls. Over the hero it is
   transparent; as the page scrolls under it, it takes the page's own ground, driven by the
   scroll itself. A browser that cannot do that leaves it where it is rather than let the menu
   run under a see-through bar. */
@media (width < 1024px) {
  @supports (animation-timeline: scroll()) {
    .profile-top {
      position: sticky;
      top: 0;
      z-index: 5;
      isolation: isolate;
      padding-top: max(var(--s2), env(safe-area-inset-top));
      padding-bottom: var(--s2);
    }

    /* The ground is drawn by a layer the full width of the screen, not the bar itself, which
       sits inside the page's side margins: a solid bar inset 16 px from each edge read as a
       floating box over the hero (the Founder, 2 Oct 2026). Solid by default; while the page is
       still at the top, the scroll timeline fades it out so the hero shows through.
       The layer is the bar's own box, and a shadow spread far past it paints the rest of the
       width, cut to the bar's height by the clip. A 100vw box would count the width of a classic
       scrollbar and push the page sideways; a shadow takes no part in the page's width. */
    .profile-top::before,
    .profile-top::after {
      content: "";
      position: absolute;
      z-index: -1;
      inset: 0;
      background: var(--c-canvas);
      box-shadow: 0 0 0 100vmax var(--c-canvas);
      clip-path: inset(0 -100vmax);
      animation: top-bar-fills linear both;
      animation-timeline: scroll(root);
      animation-range: 0 60px;
    }

    /* The ground also reaches up past the bar. On an iPhone the page runs under the status bar
       (viewport-fit=cover), and while scrolling a stuck bar can trail the page by a few pixels:
       the hero showed through as a green band between the clock and the bar (the Founder,
       6 Oct 2026). The ground now covers 120 px above the bar too. So that it never darkens the
       site bar above while that scrolls away, it starts to fade in only once the bar is stuck,
       that is after the site bar's height. */
    .profile-top::before {
      clip-path: inset(-120px -100vmax 0);
      animation-range: var(--site-header-h) calc(var(--site-header-h) + 40px);
    }

    /* The hairline under the bar, as wide as the ground. It fades in with the ground, over the
       same stretch of scroll: drawn earlier, it crossed the hero under a still-clear bar and read
       as a light band below Back and Share (the Founder, 6 Oct 2026). */
    .profile-top::after {
      inset: auto 0 -1px;
      height: 1px;
      background: var(--c-divider);
      box-shadow: 0 0 0 100vmax var(--c-divider);
      animation-range: var(--site-header-h) calc(var(--site-header-h) + 40px);
    }
  }
}

@keyframes top-bar-fills {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.profile .place-name {
  margin: var(--s5) 0 var(--s7);
  font-size: var(--fs-place);
  line-height: 1.1;
  font-weight: var(--w-strong);
  letter-spacing: -0.01em;
  font-stretch: var(--place-width);
}

/* The two answers a visitor came for: a word or a count at 21 px 700, and what qualifies it
   beneath. Nothing else on the page is this size. */
.leads {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s6) 0;
  margin: 0 0 18px;
}

.lead + .lead {
  margin-left: var(--s8);
  padding-left: var(--s8);
  border-left: 1px solid var(--c-border-strong);
}

.lead-main {
  display: flex;
  align-items: center;
  gap: var(--s4);
  font-size: var(--fs-name);
  line-height: 1.2;
  font-weight: var(--w-strong);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.lead--open .lead-main {
  color: var(--c-status-open);
}

.lead--closed .lead-main {
  color: var(--c-status-closed);
}

/* An answer that is not a number or a verdict reads one step quieter. */
.lead--none .lead-main,
.lead--unknown .lead-main {
  font-size: var(--fs-body);
  color: var(--c-text-secondary);
}

.lead-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: currentColor;
}

.lead-sub {
  font-size: var(--fs-row);
  line-height: 1.3;
  color: var(--c-text-secondary);
}

/* The address, the neighbourhood, and the way to see it on the map. */
.addr {
  display: flex;
  align-items: center;
  gap: var(--s6);
  padding: 14px 0;
  border-top: 1px solid var(--c-glass-line);
  border-bottom: 1px solid var(--c-glass-line);
  color: var(--c-text-secondary);
}

.addr-text {
  flex: 1;
  min-width: 0;
}

.addr-1 {
  display: block;
  font-weight: var(--w-medium);
  color: var(--c-text);
}

.addr-2 {
  display: block;
  font-size: var(--fs-row);
  color: var(--c-text-muted);
}

/* Over a photograph the muted district line cannot reach 4.5:1 on a scrim that lets the photograph
   show (it would need 0.9 of the page's colour behind it, which is the heavy veil this replaces),
   so it takes a step toward the main text colour there (88 % of it, written out so that script
   and test read an ordinary rgb value). Everything else keeps its colour. */
.profile--photo .addr-2 {
  color: #313431;
}

@media (prefers-color-scheme: dark) {
  .profile--photo .addr-2 {
    color: #d5d2cb;
  }
}

.addr-map {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--s3);
  min-height: var(--hit);
  padding: 0 14px;
  border: 1px solid var(--c-glass-line);
  border-radius: var(--r-lg);
  background: var(--c-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--c-text);
  font-size: var(--fs-ui);
  font-weight: var(--w-medium);
  text-decoration: none;
}

/* On a phone or a tablet the Map button has no job left: the map preview under the facts opens
   the map (D25, the Founder, 7 Oct 2026). From 1024 px it stays, the way to the map beside the
   details (and, without script, the link to the search map). */
@media (width < 1024px) {
  .addr-map {
    display: none;
  }
}

/* ==== the map preview (D25, the Founder, 7 Oct 2026) ====
   Phones and tablets: a 140 px strip under the facts and above the actions, rounded like the
   facts, showing the shop's pin in the middle and the shops nearby. It is a picture, not a map to
   handle: nothing in it takes a touch, a wheel or the focus, so a thumb scrolling the page over
   it scrolls the page. The whole strip is one link (`.mappreview__open`, over everything): to the
   search page without script, the map sheet with it. Until shop-map.js has drawn the map (it
   fades in, at once under reduced motion) the strip is the cards' no-photo band, and its height
   is fixed, so nothing moves when the map arrives. The map's own parts that have no place in a
   picture (the controls, Directions, the loading line, a card) are left out by shop-map.css. */
.mappreview {
  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  height: 140px;
  margin: 0 0 var(--s7);
  overflow: hidden;
  border: 1px solid var(--c-glass-line);
  border-radius: var(--r-lg);
  background: var(--c-brand-tint);
}

/* Its own stacking context (z-index 0), so nothing of the map, whatever its z-index, rises over
   the strip's link. */
.mappreview__map {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--m-quick) linear;
}

.mappreview.is-drawn .mappreview__map {
  opacity: 1;
}

/* The map could not be drawn: the band stays, and one quiet line says so. */
.mappreview__off {
  position: absolute;
  bottom: var(--s5);
  left: var(--s6);
  margin: 0;
  color: var(--c-text-secondary);
  font-size: var(--fs-row);
  font-weight: var(--w-medium);
}

.mappreview__open {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: var(--s4);
  border-radius: inherit;
  color: var(--c-text);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

/* The strip clips what is outside it, so its ring is drawn inside its edge. */
.mappreview__open:focus-visible {
  outline-offset: -4px;
  box-shadow: inset 0 0 0 6px var(--c-focus-gap);
}

/* What says the strip opens the map: the old Map button's look, small, in the corner. */
.mappreview__tag {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  min-height: 32px;
  padding: 0 var(--s5);
  border: 1px solid var(--c-glass-line);
  border-radius: var(--r-md);
  background: var(--c-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  font-size: var(--fs-row);
  font-weight: var(--w-medium);
}

.mappreview__open:hover .mappreview__tag {
  color: var(--c-brand-hover);
}

@media (width >= 1024px) {
  .mappreview {
    display: none;
  }
}

/* What we hold and when we saw it: three small glass boxes. Two when menus are not shown.
   Menu and Hours side by side, of one height, and Wheelchair a full-width tile under them, at
   every width (the Founder, 7 Oct 2026, D24): its compact line reads one line per line instead of
   in a third of the width. Hours takes the row alone when menus are not shown. */
.profile .facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4);
  margin: 14px 0 var(--s7);
  padding: 0;
  list-style: none;
}

.fact {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  min-width: 0;
  min-height: 84px;
  padding: 9px var(--s5);
  border: 1px solid var(--c-glass-line);
  border-radius: var(--r-lg);
  background: var(--c-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--c-text-secondary);
}

.fact .ph {
  margin-bottom: var(--s1);
}

.fl {
  font-size: var(--fs-small);
  color: var(--c-text-muted);
}

.fv {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-row);
  font-weight: var(--w-strong);
  color: var(--c-text);
}

.fv .glyph {
  flex: none;
  margin: 0;
}

/* "Partial": only when the menu is part of the board. */
.fx {
  align-self: flex-start;
  margin-top: var(--s1);
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--c-warning-bg);
  color: var(--c-warning);
  font-size: var(--fs-small);
  font-weight: var(--w-medium);
}

/* Wheelchair access in four parts (D24, the Founder, 6 Oct 2026). The tile's second line (what
   has stairs, or what is not recorded) is the first line's size, quieter. When anything is
   recorded the whole tile is the link that opens the detail, with a small caret in its corner. */
.fv2 {
  font-size: var(--fs-small);
  font-weight: var(--w-medium);
  line-height: 1.3;
  color: var(--c-text-secondary);
}

.fact--link {
  padding: 0;
}

/* A shop's own age rule (D30, the Founder, 10 Oct 2026): one quiet `.meta` line tucked under the
   facts (8 px below the tiles, where the strip's own 16 px gap would be), then that gap again. */
.profile .age-rule {
  margin: calc(var(--s4) - var(--s7)) 0 var(--s7);
}

.profile .facts > .fact--access,
.profile .facts > .fact--hours:first-child {
  grid-column: 1 / -1;
}

.fact-link {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--s1);
  padding: 9px var(--s8) 9px var(--s5);
  border-radius: inherit;
  color: inherit;
  text-decoration: none;
}

.fact-link:hover {
  background: var(--c-glass);
}

.fact-more {
  position: absolute;
  top: 10px;
  right: var(--s4);
  display: flex;
  color: var(--c-text-muted);
}

.fact-more .ph {
  margin: 0;
}

/* The detail: one line per part. ✓ and ✕ sit in a ring of their state's colour; the dash of a
   part not recorded has no ring and no colour, so it never reads as a cross. It has no Done
   button, so its body keeps clear of the home indicator itself. */
.access-body {
  padding-bottom: calc(var(--s9) + env(safe-area-inset-bottom));
}

.access-label {
  margin: 0 0 var(--s5);
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
}

.access-parts {
  margin: 0;
  padding: 0;
  list-style: none;
}

.access-part {
  display: flex;
  align-items: center;
  gap: var(--s5);
  min-height: 40px;
  font-size: var(--fs-body);
  line-height: var(--lh-row);
}

.access-mark {
  display: grid;
  flex: none;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1.5px solid currentColor;
  border-radius: 999px;
  font-size: var(--fs-row);
  font-weight: var(--w-strong);
  line-height: 1;
}

.access-part--yes .access-mark {
  color: var(--c-status-open);
}

.access-part--no .access-mark {
  color: var(--c-status-closed);
}

.access-part--unknown {
  color: var(--c-text-secondary);
}

.access-part--unknown .access-mark {
  border-color: transparent;
  color: var(--c-text-muted);
}

.access-note {
  margin: var(--s6) 0 0;
  font-size: var(--fs-ui);
  line-height: var(--lh-body);
  color: var(--c-text);
}

.access-source {
  margin: var(--s5) 0 0;
  font: var(--w-regular) var(--fs-meta) / var(--lh-meta) var(--font);
  color: var(--c-text-muted);
}

/* The actions: Directions big, two rows tall when there are enough beside it, and whichever of
   Website, Instagram, Call and Share the shop has in as many rows as they need. An odd one out
   takes the whole row. */
.profile .actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s5);
  margin: 0 0 18px;
}

.profile .actions--dir {
  grid-template-columns: 1.1fr 2fr;
}

/* Directions alone, which is most shops (few record a website, an Instagram or a phone): the
   whole row, the icon beside the word, not a third of it beside an empty column (UX review,
   5 Oct 2026). With any of the others beside it the two columns above stand. */
.profile .actions--solo {
  grid-template-columns: 1fr;
}

.actions--solo .acts {
  display: none;
}

.actions--solo .dir {
  flex-direction: row;
  gap: var(--s5);
}

/* A row with nothing in it takes no space, rather than leaving a hole. */
.profile .actions:not(:has(a, button:not([hidden]))) {
  display: none;
}

.dir {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  min-height: 56px;
  padding: 0 var(--s4);
  border-radius: var(--r-xl);
  background: var(--c-brand);
  color: var(--c-on-brand);
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
  text-decoration: none;
}

.dir:hover {
  background: var(--c-brand-hover);
  color: var(--c-on-brand);
}

/* The sticky Directions pill (visual system §11: 48 px, level-2 elevation, 12 px above the home
   indicator, 150 ms ease-out, a 12 px rise and a fade). directions-pill.js adds `is-on` once the
   button above has scrolled off the top. Drawn but not visible until then, so without script
   the plain button is all there is; `visibility` also keeps it out of the tab order and away
   from a screen reader while hidden. Phones and tablets only: from 1024 px the page is a
   centred column and the button is a short scroll away. */
.dir-pill {
  position: fixed;
  z-index: 15;
  inset-inline: 0;
  bottom: calc(var(--s6) + env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s4);
  /* Under 80% of the screen's width, so iOS 26 Safari never takes its colour for its bottom bar:
     Safari only reads fixed elements at least 80% as wide as the screen. The transparent
     background colour below was not enough: Safari still painted the whole bar mint for a moment
     as the pill slid down past it in dark mode (the Founder's recording, 7 Oct 2026). */
  width: min(74vw, calc(44rem - 2 * var(--s7)));
  height: 48px;
  margin-inline: auto;
  border-radius: var(--r-xl);
  /* Painted as an image, with no background colour: iOS 26 Safari tints its own top and bottom
     bars with the background colour of a fixed element near the screen's edge, so the green pill
     flashed the whole bottom bar green while it slid away (the Founder, 6 Oct 2026). */
  background-color: transparent;
  background-image: linear-gradient(var(--c-brand), var(--c-brand));
  color: var(--c-on-brand);
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
  text-decoration: none;
  box-shadow: var(--e2);
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition:
    opacity var(--m-quick) linear,
    transform var(--m-quick) var(--ease-out),
    visibility 0s linear var(--m-quick);
}

.dir-pill.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

.dir-pill:hover {
  background-image: linear-gradient(var(--c-brand-hover), var(--c-brand-hover));
  color: var(--c-on-brand);
}

@media (min-width: 1024px) {
  .dir-pill {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dir-pill {
    transition: none;
  }
}

/* With the pill in play (script on), nothing a visitor must reach may end up under it: the
   scroll padding keeps a focused link clear of it, and the footer has room below its last link. */
@media (max-width: 1023.98px) {
  html.has-dir-pill {
    scroll-padding-bottom: calc(48px + 2 * var(--s6) + env(safe-area-inset-bottom));
  }

  html.has-dir-pill .site-footer {
    padding-bottom: calc(48px + 3 * var(--s6) + env(safe-area-inset-bottom));
  }
}

.acts {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: var(--s5);
}

.act {
  display: inline-flex;
  flex: 1 1 calc(50% - var(--s4));
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 0;
  min-height: 56px;
  padding: 0 var(--s2);
  border: 1px solid var(--c-border);
  border-radius: 14px;
  background: var(--c-card);
  color: var(--c-text);
  font: inherit;
  font-size: var(--fs-row);
  font-weight: var(--w-medium);
  text-decoration: none;
  cursor: pointer;
}

.act:hover {
  color: var(--c-text);
  border-color: var(--c-border-strong);
}

/* On a phone a small button's word never breaks ("Instagra-m", the Founder, 2 Oct 2026): the
   icon sits above the word, as on Directions, so the word has the button's whole width. */
.act {
  white-space: nowrap;
}

@media (width < 480px) {
  .act {
    flex-direction: column;
    gap: 3px;
    font-size: var(--fs-count);
  }
}

/* Too narrow for Directions beside two columns: it takes a row of its own, icon beside word. */
@media (width < 360px) {
  .profile .actions--dir {
    grid-template-columns: 1fr;
  }

  .dir {
    flex-direction: row;
    gap: var(--s4);
  }
}

/* ==== end of the shop page's new top ==== */

/* VS §11: the card treatment stops at the product, so the hours keep no border and no fill.
   Flush with the page's own edge; the chevron is the affordance the box used to be. */
.hours-disclosure {
  margin-top: var(--s7);
  border: 0;
  border-radius: 0;
  background: none;
}

.hours-disclosure > summary {
  justify-content: flex-start;
  padding: 0;
  list-style: none;
}

.hours-disclosure > summary::-webkit-details-marker {
  display: none;
}

.hours-disclosure > summary::marker {
  content: "";
}

.hours-disclosure > summary::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin: 0 0 3px 2px;
  border-right: 2px solid var(--c-text-secondary);
  border-bottom: 2px solid var(--c-text-secondary);
  transform: rotate(45deg);
  transition: transform var(--m-quick) linear;
}

.hours-disclosure[open] > summary::after {
  margin: 3px 0 0 2px;
  transform: rotate(-135deg);
}

@media (prefers-reduced-motion: reduce) {
  .hours-disclosure > summary::after {
    transition: none;
  }
}

.hours-disclosure .hours {
  width: 100%;
  margin: 0 0 var(--s7);
}

.hours-note {
  margin: 0 0 var(--s7);
  font-size: var(--fs-row);
  color: var(--c-text-secondary);
}

/* The age glyph, drawn by shape first (§12): solid, half, ring, dashed ring. */
.glyph {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: var(--s2);
  border-radius: 50%;
  vertical-align: baseline;
}

.glyph-fresh {
  background: var(--c-fresh);
}

.glyph-aging {
  background: linear-gradient(90deg, var(--c-aging-glyph) 50%, transparent 50%);
  border: 1px solid var(--c-aging-glyph);
}

.glyph-stale {
  border: 1.5px solid var(--c-stale);
}

.glyph-unknown {
  border: 1.5px dashed var(--c-stale);
}

/* The menu heading and its count, on one baseline. */
.mhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s6);
  margin: 26px 0 var(--s5);
}

.mhead h2 {
  margin: 0;
  font-size: var(--fs-heading);
  line-height: 1.25;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.mcount {
  color: var(--c-text-muted);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
}

/* The menu's Sort button (the Founder, 6 Oct 2026) is the search page's own chip, beside the
   count at the right of the heading's line. */
.mhead .mcount {
  margin-left: auto;
}

.mhead .sortbtn {
  align-self: center;
}

/* The menu's category chips, All first: the same chip in a row that scrolls on a phone.
   They are links with a query parameter, so the filter needs no script. */
.mchip-list {
  display: flex;
  gap: var(--s4);
  margin: 0;
  margin-inline: calc(-1 * var(--s7));
  padding: 5px var(--s7); /* room for the chips' 44 px hit areas */
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}

.mchip-list::-webkit-scrollbar {
  display: none;
}

/* From 1024 px the row wraps instead of scrolling, as the search page's quick filters do (the
   Founder, 6 Oct 2026): at a desktop's column width the last chips were cut off and out of reach
   of a mouse. The chips' own look and feedback are shared with the quick filters' (`.quick-list
   .chip`, above). */
@media (width >= 1024px) {
  .mchip-list {
    flex-wrap: wrap;
    overflow: visible;
  }
}

/* The styles of the one category in view (D15): the same chips in a second row, closer to the
   first than the first is to the heading. */
.mchips--styles .mchip-list {
  margin-top: 0;
}

.mchip-n {
  color: var(--c-text-muted);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.mchip-list .chip-on .mchip-n {
  color: inherit;
  opacity: 0.85;
}

/* One card per product (visual system §11 "Profile: the menu list", as the Founder revised
   it on 30 September 2026: the card treatment of the v2 prototype, in place of the hairline
   rows). The card edge does what the divider used to do, so there are no dividers.

   The card treatment stops here. The two leading facts, the quiet line and the hours
   disclosure keep no border and no fill: a border, a fill and a radius mark a thing as
   separate, and if everything is a card nothing is. */
.mrows {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  margin: var(--s5) 0 0;
  padding: 0;
  list-style: none;
}

/* A filled panel, not an outlined box. The card, the category pill and the weight chips are
   three rounded rectangles inside one another; at forty-six products in a column, three sets
   of edges read as clutter, so the fill does the grouping and only the pill and the chips
   keep an edge. */
.mrow {
  padding: var(--s5) var(--s6);
  border-radius: var(--r-lg);
  background: var(--c-card);
}

.mrow-top {
  display: flex;
  align-items: baseline;
  gap: var(--s4);
}

.mname {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-ui);
  line-height: 1.2;
  font-weight: 650;
  letter-spacing: -0.005em;
}

/* The category, as a pill immediately before the price. Sentence case, never shouted. */
.mpill {
  flex: none;
  align-self: center;
  padding: 2px var(--s4);
  border-radius: 999px;
  background: var(--c-sunken);
  color: var(--c-text-secondary);
  font-size: var(--fs-small);
  font-weight: 550;
  line-height: 1.5;
  white-space: nowrap;
}

.mprice {
  flex: none;
  font-size: var(--fs-body);
  font-weight: var(--w-strong);
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
}

.mprice small {
  font-size: var(--fs-small);
  font-weight: 550;
  color: var(--c-text-muted);
  letter-spacing: 0;
}

.mna {
  flex: none;
  font-size: var(--fs-meta);
  color: var(--c-text-muted);
}

.mtiers {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s3);
  margin-top: 7px;
}

.mtier {
  font-size: var(--fs-small);
  font-variant-numeric: tabular-nums;
  color: var(--c-text-secondary);
  background: var(--c-sunken);
  border-radius: var(--r-sm);
  padding: 2px 7px;
}

.mtier b {
  font-weight: 650;
  color: var(--c-text);
}

.mtier-warn {
  background: var(--c-warning-bg);
  color: var(--c-warning);
}

.mown {
  font-size: var(--fs-small);
  color: var(--c-text-muted);
}

/* The product's shared labels (D15), "Cali · Sativa": quiet text under the name. Never a box
   and never another pill; a product without labels has no such line. */
.mlabels {
  margin-top: 2px;
  font-size: var(--fs-meta);
  line-height: 1.3;
  color: var(--c-text-muted);
}

/* What follows the price of a piece or a pack (D18), "€23.33 per piece" or "2.5 g": quiet text
   under the price, on its side of the card, so a long name is not squeezed beside a long unit. */
.mnote {
  margin-top: 2px;
  font-size: var(--fs-meta);
  line-height: 1.3;
  color: var(--c-text-muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* "Also at <shop>: <price> for <size> · seen <date>" (D19). The shop and its price are what the
   eye takes; the caveat expands in place and is never repeated down the page. No fill, a 2 px
   brand rule. */
.mcheap {
  margin-top: var(--s4);
  border-left: 2px solid var(--c-brand);
  padding: 1px 0 1px 9px;
}

.mcheap > summary {
  display: block;
  cursor: pointer;
  list-style: none;
}

.mcheap > summary::-webkit-details-marker {
  display: none;
}

.mcheap > summary::marker {
  content: "";
}

.mcheap-hd {
  display: block;
  font-size: var(--fs-meta);
  font-weight: 650;
  color: var(--c-brand-text);
  font-variant-numeric: tabular-nums;
}

.mcheap-at {
  font-weight: 550;
  color: var(--c-text-secondary);
}

.mcheap-sub {
  display: block;
  margin-top: 1px;
  font-size: var(--fs-small);
  color: var(--c-text-muted);
}

.mcheap-more {
  text-decoration: underline;
  text-decoration-thickness: 1px;
}

.mcheap-full {
  margin: var(--s3) 0 0;
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--c-text-secondary);
}

.mcheap > summary:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}

.profile-foot {
  margin-top: var(--s9);
}

/* Attribution: a licence's credit, on its own quiet line, never mixed with freshness (§12). */
.attribution {
  margin: var(--s7) 0 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--c-text-muted);
}

.attribution a {
  color: inherit;
}

/* ---- legal and information pages ---- */

.prose {
  max-width: 40rem;
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-ui);
}

.prose th,
.prose td {
  padding: var(--s4) var(--s6) var(--s4) 0;
  text-align: left;
  vertical-align: top;
  border-top: 1px solid var(--c-divider);
}

/* ==== the shop page opened with the visitor's search (the Founder, 2 Oct 2026) ====
   One quiet line above the product cards: "Showing 2 products matching your search · Show full
   menu". It is the count line's voice (13 px, secondary), not a notice: no box, no fill. The link
   is the only part that is a control, so it gets the 44 px hit area through padding on the inline
   box (which does not move the line), and the line keeps clear of the chips above and the first
   card below so those hit areas do not meet. */
.mcarry {
  margin: var(--s6) 0;
  font-size: var(--fs-count);
  line-height: 1.385;
  color: var(--c-text-secondary);
}

/* A 44 px target whatever the fonts' metrics: padding alone gave 43 px on Linux (CI). */
.mcarry a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  vertical-align: middle;
  color: var(--c-text);
  font-weight: 550;
}

/* ==== the map on the shop page (the Founder, 6 Oct 2026; visual system §11 "Desktop") ====
   From 1024 px the shop's details keep their column (520 px, the results panel's width) and the
   map stands beside them, pinned while the details scroll, the hero over the details' column
   only. Nothing of the map but this empty place is in the page: shop-map.js fills it when it
   scrolls into view, and `shop-map.css`, loaded then, gives it its look. Below 1024 px the place
   stays hidden and the Map link opens a sheet instead. A shop with no map (`profile--map` is only
   set for one whose position may be shown) keeps the single centred column it had.

   The map fits the window (the Founder, 6 Oct 2026) and is a complete card (8 Oct 2026): a
   border and rounded corners on all four sides, one gap under the site bar and one gap above the
   bottom of the window, whatever the window's height; it is never taller than the window, so
   nothing in it (the nearby shop's card, the controls, the attribution) is ever cut off. The bar
   is not pinned, so as it scrolls away the map rises under it, keeping its gap below it, and
   grows by as much (scroll-driven, over the bar's own height): its foot does not move, and once
   the bar is gone it stands one gap from the top and one from the bottom. A browser without
   scroll-driven animation keeps the map where it began, the same card a bar and a gap from the
   top.

   The page's frame keeps a gutter from the window on both sides, and the map runs to the frame's
   right edge as the hero runs to its left one: the two panels stand as far from the window's
   edges (the map used to stop a gutter short of the frame, 16 px nearer the middle than the
   hero). The site bar and the footer take the same frame, so the mark lines up with the details. */
@media (width >= 1024px) {
  .has-shopmap .brand,
  .has-shopmap .site-footer,
  .profile--map {
    max-width: min(76rem, 100% - var(--gutter-l) - var(--gutter-r));
  }

  .profile--map {
    --shopmap-col: 32.5rem;
    --shopmap-gap: var(--s6);
    /* The map's height at the top of the page, and once the bar has scrolled away. Never under
       22 rem, which a window shorter than that must scroll to see whole. */
    --shopmap-h0: max(22rem, calc(100vh - var(--site-header-h) - 2 * var(--shopmap-gap)));
    --shopmap-h1: max(22rem, calc(100vh - 2 * var(--shopmap-gap)));
    display: grid;
    grid-template-columns: minmax(0, var(--shopmap-col)) minmax(0, 1fr);
    column-gap: var(--s9);
    align-items: start;
  }

  @supports (height: 1dvh) {
    .profile--map {
      --shopmap-h0: max(22rem, calc(100dvh - var(--site-header-h) - 2 * var(--shopmap-gap)));
      --shopmap-h1: max(22rem, calc(100dvh - 2 * var(--shopmap-gap)));
    }
  }

  .profile--map > :not(.hero) {
    grid-column: 1;
  }

  /* The hero stays behind the details' column and does not run under the map. */
  .profile--map .hero {
    right: auto;
    width: calc(var(--shopmap-col) + var(--gutter-l) + var(--s7));
  }

  .profile--map > .shopmap-slot {
    grid-column: 2;
    grid-row: 1 / span 40;
    position: sticky;
    top: calc(var(--site-header-h) + var(--shopmap-gap));
    align-self: start;
    box-sizing: border-box;
    height: var(--shopmap-h0);
    /* One gap under the site bar, as there is one over the window's bottom: the page's own top
       padding is replaced by the gap. To the frame's right edge (see above). */
    margin-top: calc(var(--shopmap-gap) - var(--s8));
    margin-right: calc(-1 * var(--gutter-r));
    border: 1px solid var(--c-border-strong);
    border-radius: var(--r-lg);
    background: var(--c-map-land);
    overflow: hidden;
    z-index: 1;
  }

  /* As the bar scrolls away the map, in the page's flow, rises with it (a gap below it the whole
     way) and grows by as much, so its foot stays put; once the bar is gone it is held a gap from
     the top. Only the height is animated: the card is whole throughout. */
  @supports (animation-timeline: scroll()) {
    .profile--map > .shopmap-slot {
      top: var(--shopmap-gap);
      animation: shopmap-rise linear both;
      animation-timeline: scroll(root);
      animation-range: 0 var(--site-header-h);
    }
  }
}

@keyframes shopmap-rise {
  to {
    height: var(--shopmap-h1);
  }
}
