/* DirectoryEngine base stylesheet — the editorial "Gallery" design system (epic-017 task-108).
 *
 * GALLERY: a credible magazine, not an auto-generated directory. White paper, near-black ink,
 * oversized tight Playfair Display headlines, Inter body at a comfortable reading measure,
 * hairline (0.5px) rules instead of card chrome on the content feed, and the accent carried
 * almost entirely by small letter-spaced UPPERCASE kickers, links, and ONE filled pill CTA —
 * never large color fills. Generous whitespace does the structural work.
 *
 * Theming model (preserved from epic-011): every color/space/radius/type value is a --de-* token.
 * A directory re-themes by overriding ONE seed, --de-color-brand (the accent seed); the accent
 * ramp (-strong/-tint/-wash) and every accented surface derive from it via color-mix(), so a
 * single hex change re-skins the whole site with no per-call-site colors. The shell emits a tiny
 * per-directory :root override (ThemeStyle.kt themeStyle) — nothing here or at call sites is a
 * hardcoded color. task-109 widened that one seed into a full per-vertical THEME bundle (accent +
 * paper tint + display voice + imagery — the same override seam; see DESIGN-SYSTEM.md).
 *
 * Epic-010's mobile-first behavior and the min-width:64rem desktop compaction are preserved
 * (bottom of file). Self-hosted fonts (task-107) are declared first; never any third-party origin. */

/* ── Self-hosted web fonts (epic-017 task-035 / task-107) ───────────────────
 * Editorial "Gallery" type: Playfair Display (display) + Inter (body), both the
 * Google-Fonts Latin subset, served first-party from /static/fonts — ZERO
 * third-party font requests at runtime (no fonts.googleapis.com / fonts.gstatic.com).
 * Each bundled woff2 is a variable face covering its weight axis; we expose only the
 * weights the design uses (Playfair 600/700, Inter 400/500) per Google's own
 * multiple-face-per-file pattern. Relative url() resolves against this file's served
 * path (/static/css/app.css) -> /static/fonts/*, so it inherits the same immutable,
 * fingerprint-cached /static route as every other first-party asset.
 * font-display:swap paints text immediately in the metric-matched fallback (below),
 * then swaps — no invisible text, minimal layout shift. Licensing: see static/fonts/OFL.md. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(../fonts/inter-latin.woff2) format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(../fonts/inter-latin.woff2) format("woff2");
}
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(../fonts/playfair-latin.woff2) format("woff2");
}
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(../fonts/playfair-latin.woff2) format("woff2");
}

/* Metric-matched fallback faces (CLS control): a fallback stack alone leaves a
 * visible reflow when the real face swaps in, because the system fallback has
 * different glyph metrics. These local() "Fallback" faces re-map a common system
 * font (Arial/Times) onto the SAME box as the web font via size-adjust +
 * ascent/descent overrides, so the swap from fallback -> web font is near-imperceptible.
 * The override values are the Google-Fonts-published fallback metrics for each face. */
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  ascent-override: 90%;
  descent-override: 22.43%;
  line-gap-override: 0%;
  size-adjust: 107.64%;
}
@font-face {
  font-family: "Playfair Display Fallback";
  src: local("Times New Roman");
  ascent-override: 96.75%;
  descent-override: 22.36%;
  line-gap-override: 0%;
  size-adjust: 112.55%;
}

:root {
  /* ── Accent seed + derived ramp ──────────────────────────────────────────
   * --de-color-brand is the ONE per-directory/theme seed (overridden by the shell).
   * The accent is intentionally restrained in the Gallery system: kickers, links, one
   * pill CTA, and small marks. The whole ramp derives from the seed via color-mix so a
   * theme only sets a single hex. --de-accent is the semantic alias call sites read. */
  --de-color-brand: #0b6b53;            /* default accent: a quiet editorial green */
  --de-color-brand-contrast: #ffffff;
  --de-accent: var(--de-color-brand);
  /* A darker accent for hover/active and AA-contrast link text on white. */
  --de-accent-strong: color-mix(in oklab, var(--de-color-brand), #000 16%);
  /* The kicker color: nudged for AA at ~10px on white (small text needs more contrast). */
  --de-accent-ink: color-mix(in oklab, var(--de-color-brand), #000 22%);
  /* A 10% tint for the one quiet accented fill (pill hover, count chip). */
  --de-accent-tint: color-mix(in oklab, var(--de-color-brand) 10%, var(--de-color-surface));
  /* A faint 5% wash — the strongest "fill" the Gallery system ever uses on a large field. */
  --de-accent-wash: color-mix(in oklab, var(--de-color-brand) 5%, var(--de-color-surface));
  /* Accent-tinted hairline for the few surfaces that need an accented edge. */
  --de-accent-border: color-mix(in oklab, var(--de-color-brand) 28%, var(--de-color-hairline));

  /* Back-compat aliases: page-specific classes written for epic-011 still read the old
   * --de-color-brand-* names. They now resolve to the Gallery accent ramp, so legacy
   * pages re-skin without per-file edits (no hidden coupling — one source of truth). */
  --de-color-brand-strong: var(--de-accent-strong);
  --de-color-brand-tint: var(--de-accent-tint);
  --de-color-brand-wash: var(--de-accent-wash);
  --de-color-brand-border: var(--de-accent-border);

  /* ── Ink ramp (Gallery: near-black ink on white, calm grays) ─────────────── */
  --de-color-ink: #141414;              /* headline + body ink */
  --de-color-text: var(--de-color-ink); /* alias the rest of the system reads */
  --de-color-text-muted: #666666;       /* secondary text (dek, meta) — AA on white */
  --de-color-text-subtle: #999999;      /* tertiary labels, large/decorative only */

  /* ── Surfaces ─────────────────────────────────────────────────────────────
   * Gallery is overwhelmingly white. "paper" is the per-theme tintable page surface
   * (DEFAULT = pure white); -alt/-sunk are the rare quiet panels (footer, code wells). */
  --de-color-paper: #ffffff;            /* per-theme tintable; task-109 overrides this */
  --de-color-surface: var(--de-color-paper);
  --de-color-surface-alt: #fafafa;      /* footer / quiet section wash */
  --de-color-surface-sunk: #f4f4f4;     /* code, inset wells, count chips */

  /* ── Hairlines (the signature structural device) ─────────────────────────── */
  --de-color-hairline: #ededed;         /* 0.5px feed/section rules */
  --de-color-border: var(--de-color-hairline);
  --de-color-border-strong: #dcdcdc;    /* inputs / the few real boxes that remain */
  --de-hairline: 0.5px solid var(--de-color-hairline);

  /* ── Semantic colors ─────────────────────────────────────────────────────── */
  --de-color-danger: #b42318;
  --de-color-danger-tint: color-mix(in oklab, var(--de-color-danger) 8%, var(--de-color-surface));
  --de-color-success: #197a4b;
  --de-color-star: #c98a04;             /* rating stars — gold, never accent-colored */

  /* ── Typography ──────────────────────────────────────────────────────────
   * Display = Playfair (oversized, tight). Body = Inter. Tokens declared in task-107;
   * task-108 owns the full scale. The system stack is the final fallback. */
  --de-font-display: "Playfair Display", "Playfair Display Fallback", Georgia,
    "Times New Roman", serif;
  --de-font-body: "Inter", "Inter Fallback", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  /* Legacy alias (a few call sites referenced --de-font-sans). */
  --de-font-sans: var(--de-font-body);
  --de-font-mono: ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono",
    Menlo, Consolas, monospace;
  --de-font-size-base: 1.0625rem;       /* 17px body — comfortable reading */
  --de-line-height: 1.65;

  /* Modular type scale (~1.26 ratio), tuned for oversized Playfair display headlines
   * down through a clear step to small UI text. Display sizes are large but fluid via
   * clamp() so a 40-44px desktop hero stays readable at 360px. */
  --de-text-xs: 0.6875rem;              /* 11px — kicker / fine print */
  --de-text-sm: 0.8125rem;              /* 13px — meta, captions */
  --de-text-md: 1rem;
  --de-text-lg: 1.1875rem;              /* dek / sub-lead */
  --de-text-xl: clamp(1.4rem, 1.1rem + 1.4vw, 1.75rem);    /* section heads */
  --de-text-2xl: clamp(1.9rem, 1.4rem + 2.4vw, 2.5rem);    /* page H1 */
  --de-text-3xl: clamp(2.3rem, 1.5rem + 3.6vw, 3.25rem);   /* hero headline (~40-52px) */

  --de-leading-display: 1.06;           /* oversized headlines: tight */
  --de-leading-snug: 1.18;              /* smaller headings */
  --de-tracking-display: -0.012em;      /* the -0.01em display tightening */
  --de-tracking-tight: -0.012em;        /* legacy alias */

  /* ── Per-theme display voice (epic-017 task-109) ──────────────────────────
   * The display TYPEFACE treatment is a theme override seam, not just color. h1/h2 read
   * these instead of hardcoding weight/tracking, so a theme re-skins the headline voice
   * (heavier+tighter for grounded trades, lighter+airier for health/beauty, restrained
   * for professional) with NO markup change — the same single-seam model as the accent.
   * DEFAULT keeps the task-108 base (700 / display tracking). */
  --de-display-weight: 700;             /* h1/h2 weight; theme-overridable */
  --de-display-tracking: var(--de-tracking-display);  /* h1/h2 letter-spacing; theme-overridable */
  --de-tracking-kicker: 0.2em;          /* the signature kicker tracking */
  --de-tracking-label: 0.14em;          /* smaller uppercase labels */
  --de-measure: 64ch;                   /* comfortable long-form reading measure */

  /* ── Spacing rhythm ───────────────────────────────────────────────────────
   * A generous scale — the Gallery look is built on air, not chrome. */
  --de-space-1: 0.25rem;
  --de-space-2: 0.5rem;
  --de-space-3: 1rem;
  --de-space-4: 1.5rem;
  --de-space-5: 2.5rem;
  --de-space-6: 4rem;
  --de-space-7: 6rem;                   /* major editorial section breaks */

  /* ── Layout, radius ───────────────────────────────────────────────────────
   * Tighter content column than a listings grid wants — editorial pages read narrow. */
  --de-content-max-width: 64rem;
  --de-radius-sm: 0.25rem;              /* inputs */
  --de-radius: 0.375rem;                /* the few remaining boxes */
  --de-radius-lg: 0.5rem;
  --de-radius-pill: 999px;              /* the accent CTA + kicker chips */

  /* Elevation is nearly absent in Gallery (no card chrome). One whisper shadow remains
   * for the rare real panel (map, claim form) so it lifts a hair off the paper. */
  --de-shadow-sm: 0 1px 2px rgb(20 20 20 / 0.04);
  --de-shadow-md: 0 1px 3px rgb(20 20 20 / 0.05), 0 6px 16px rgb(20 20 20 / 0.05);
  --de-shadow-lg: var(--de-shadow-md);  /* legacy alias — Gallery doesn't go heavier */

  /* ── Imagery treatment ────────────────────────────────────────────────────
   * Real photos are wired later (epic-018); here the system + placeholder treatment.
   * Placeholders are an accent-tinted paper wash (never a gray box), and images sit in
   * fixed aspect ratios so the feed never reflows when photos load. */
  --de-image-radius: var(--de-radius);
  --de-image-placeholder: linear-gradient(135deg,
    var(--de-accent-wash), color-mix(in oklab, var(--de-color-brand) 12%, var(--de-color-surface)));
  --de-aspect-hero: 16 / 9;             /* lead/hero photo */
  --de-aspect-thumb: 4 / 3;             /* feed thumbnail */

  /* ── Focus ring (a11y: visible on every interactive control) ──────────────── */
  --de-focus-ring: 0 0 0 3px color-mix(in oklab, var(--de-color-brand) 38%, transparent);

  /* Minimum touch-target size (Mobile NFR): 44px on every interactive control. */
  --de-tap-size: 2.75rem;
}

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

body {
  margin: 0;
  font-family: var(--de-font-body);
  font-size: var(--de-font-size-base);
  line-height: var(--de-line-height);
  color: var(--de-color-text);
  background: var(--de-color-paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "kern" 1, "liga" 1;
}

/* ── Display headings: oversized, tight Playfair (the Gallery voice) ─────────── */
h1, h2, h3, h4 {
  color: var(--de-color-ink);
  font-family: var(--de-font-display);
  /* Theme-overridable display voice (task-109): DEFAULT resolves to 700 / display tracking. */
  font-weight: var(--de-display-weight);
  line-height: var(--de-leading-snug);
  letter-spacing: var(--de-display-tracking);
  text-wrap: balance;
  margin: 0 0 var(--de-space-3);
}

h1 {
  font-size: var(--de-text-2xl);
  line-height: var(--de-leading-display);
}

h2 {
  font-size: var(--de-text-xl);
}

h3 {
  font-size: var(--de-text-lg);
  font-weight: 600;
}

h4 {
  /* The one heading on the body font — small section labels read better in Inter. */
  font-family: var(--de-font-body);
  font-size: var(--de-text-md);
  font-weight: 600;
  letter-spacing: 0;
}

/* Counts, ratings, prices line up cleanly with tabular numerals. */
.city-row-count,
.listing-rating,
.sponsor-price,
.trust-line {
  font-variant-numeric: tabular-nums;
}

/* Default link treatment: accent ink, underlined with a calm offset — never raw blue. */
a {
  color: var(--de-accent-strong);
  text-underline-offset: 0.2em;
  text-decoration-thickness: from-font;
  text-decoration-color: color-mix(in oklab, currentColor 30%, transparent);
}

a:hover {
  text-decoration-color: currentColor;
}

/* A11y: one consistent, visible focus ring on every focusable control; never removed. */
:focus-visible {
  outline: 2px solid transparent;
  box-shadow: var(--de-focus-ring);
  border-radius: var(--de-radius-sm);
}

/* Respect reduced-motion globally (a11y); transitions collapse to instant. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── KICKER — the signature editorial primitive ──────────────────────────────
 * A small, letter-spaced, uppercase accent label that sits above headlines and on
 * feed items (category, section eyebrow). ~11px / .2em / Inter 500 / accent ink.
 * Used as both a standalone .de-kicker and the shared look for category labels. */
.de-kicker {
  display: inline-block;
  font-family: var(--de-font-body);
  font-size: var(--de-text-xs);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--de-tracking-kicker);
  text-transform: uppercase;
  color: var(--de-accent-ink);
  margin: 0 0 var(--de-space-2);
}

/* Accessibility: skip link is visually hidden until focused */
.skip-link {
  position: absolute;
  left: var(--de-space-2);
  top: var(--de-space-2);
  display: inline-flex;
  align-items: center;
  min-height: var(--de-tap-size);
  padding: var(--de-space-2) var(--de-space-3);
  background: var(--de-color-ink);
  color: #ffffff;
  border-radius: var(--de-radius);
  transform: translateY(-200%);
  transition: transform 0.15s ease-in-out;
  z-index: 100;
}

.skip-link:focus {
  transform: translateY(0);
}

/* ── Editorial masthead (the shared chrome) ──────────────────────────────────
 * A quiet, centered-feeling magazine masthead: a hairline-ruled band with the
 * wordmark and a small uppercase nav. No sticky blur, no fill — just paper and a rule. */
.site-header {
  border-bottom: var(--de-hairline);
  background: var(--de-color-paper);
}

.site-nav {
  max-width: var(--de-content-max-width);
  margin: 0 auto;
  padding: var(--de-space-4) var(--de-space-3);
  display: flex;
  align-items: center;
  gap: var(--de-space-4);
  flex-wrap: wrap;
}

/* Wordmark: the directory name set in Playfair, tight — reads as a publication title. */
.site-brand {
  font-family: var(--de-font-display);
  font-weight: 700;
  font-size: 1.375rem;
  letter-spacing: var(--de-tracking-display);
  color: var(--de-color-ink);
  text-decoration: none;
  white-space: nowrap;
  /* Wordmark left; the nav items (Cities, Blog) group flush right, vertically centered. */
  margin-right: auto;
}

.site-brand:hover {
  color: var(--de-color-ink);
  text-decoration: none;
}

/* Narrow screens: the wordmark takes its own row so the nav items (Cities, Blog) group
 * together on the next line instead of Cities-right / Blog-orphaned-left. */
@media (max-width: 34rem) {
  .site-brand {
    flex: 1 1 100%;
    margin-right: 0;
  }
}

/* Nav links: small uppercase Inter, calm — the magazine section row. */
.site-nav > a:not(.site-brand) {
  font-size: var(--de-text-xs);
  font-weight: 500;
  letter-spacing: var(--de-tracking-label);
  text-transform: uppercase;
  color: var(--de-color-text-muted);
  text-decoration: none;
}

.site-nav > a:not(.site-brand):hover {
  color: var(--de-accent-ink);
}

.site-main {
  max-width: var(--de-content-max-width);
  margin: 0 auto;
  padding: var(--de-space-6) var(--de-space-3) var(--de-space-7);
  min-height: 60vh;
}

.site-footer {
  border-top: var(--de-hairline);
  background: var(--de-color-surface-alt);
  color: var(--de-color-text-muted);
  text-align: center;
  padding: var(--de-space-6) var(--de-space-3);
  font-size: var(--de-text-sm);
}

.site-footer > span {
  display: block;
  margin-top: var(--de-space-3);
  color: var(--de-color-text-subtle);
}

/* Error pages (404/410/500) rendered through the shell */
.error-page {
  text-align: center;
  padding: var(--de-space-6) var(--de-space-3);
}

.error-page h1 {
  color: var(--de-color-ink);
}

.error-page code {
  background: var(--de-color-surface-sunk);
  padding: var(--de-space-1) var(--de-space-2);
  border-radius: var(--de-radius-sm);
}

/* ── Nav dropdown (Cities) — kept JS-free, restyled to the editorial palette ──── */
.nav-dropdown {
  position: relative;
}

/* The Cities disclosure reads as a plain nav item — identical size/weight/color to the
 * Blog link (.site-nav > a) — with a small chevron. No box: WebAwesome CSS (which boxed
 * the native summary) is no longer loaded on public pages. */
.nav-dropdown > summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: var(--de-space-1);
  font-size: var(--de-text-xs);
  font-weight: 500;
  letter-spacing: var(--de-tracking-label);
  text-transform: uppercase;
  color: var(--de-color-text-muted);
}

.nav-dropdown > summary:hover {
  color: var(--de-accent-ink);
}

.nav-dropdown > summary::after {
  content: "\25BE";
  font-size: 0.72em;
  color: var(--de-color-text-subtle);
}

/* Mobile-first: the open panel flows inline below the summary — a plain details
 * disclosure that grows the header instead of overflowing a 360px viewport.
 * From 48rem up it becomes the anchored popover the desktop layout shows. */
.nav-dropdown-panel {
  z-index: 50;
  margin: var(--de-space-2) 0 0;
  padding: var(--de-space-2);
  list-style: none;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--de-color-paper);
  border: 1px solid var(--de-color-hairline);
  border-radius: var(--de-radius);
  box-shadow: var(--de-shadow-md);
}

.nav-dropdown[open] {
  flex: 1 1 100%;
}

@media (min-width: 48rem) {
  .nav-dropdown[open] {
    flex: none;
  }

  .nav-dropdown-panel {
    position: absolute;
    right: 0;
    min-width: 16rem;
  }
}

.nav-dropdown-panel a {
  display: flex;
  align-items: center;
  min-height: var(--de-tap-size);
  padding: var(--de-space-1) var(--de-space-2);
  color: var(--de-color-text);
  text-decoration: none;
  border-radius: var(--de-radius-sm);
}

.nav-dropdown-panel a:hover {
  background: var(--de-color-surface-alt);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--de-space-2) var(--de-space-4);
  justify-content: center;
  margin-bottom: var(--de-space-3);
}

.footer-links a {
  color: var(--de-color-text-muted);
  font-size: var(--de-text-sm);
}

/* Breadcrumb */
.breadcrumb {
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
  margin-bottom: var(--de-space-4);
}

/* Narrows + centers the post-page breadcrumb to the article's reading measure (task-124) —
 * without this, .article-body's centering (below) leaves the breadcrumb hanging at the wider
 * .site-main edge instead of lining up with the headline it sits above. */
.article-breadcrumb {
  max-width: var(--de-measure);
  margin-left: auto;
  margin-right: auto;
}

.breadcrumb a {
  color: var(--de-color-text-muted);
  text-decoration-color: transparent;
}

.breadcrumb a:hover {
  color: var(--de-accent-ink);
  text-decoration-color: currentColor;
}

.breadcrumb-sep {
  margin: 0 var(--de-space-2);
  color: var(--de-color-text-subtle);
}

/* ── Buttons ──────────────────────────────────────────────────────────────────
 * Two intents in the Gallery system: ONE accent pill (filled, rounded — the single
 * place a solid accent fill is allowed) and a quiet secondary (hairline, square-ish).
 * Ghost stays as a borderless text-button. Real <a>/<button> — SEO links stay links.
 * Mobile-first 44px touch height; desktop compacts at the file bottom. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--de-space-2);
  min-height: var(--de-tap-size);
  padding: var(--de-space-2) var(--de-space-4);
  border: 1px solid var(--de-color-border-strong);
  border-radius: var(--de-radius);
  color: var(--de-color-ink);
  font: inherit;
  font-weight: 500;
  font-size: var(--de-text-sm);
  letter-spacing: 0.005em;
  text-decoration: none;
  white-space: nowrap;
  background: var(--de-color-paper);
  cursor: pointer;
  transition: border-color 0.14s ease, color 0.14s ease, background 0.14s ease,
    transform 0.14s ease;
}

/* Quiet secondary hover: the hairline tints toward the accent — no fill. */
.button:hover {
  border-color: var(--de-color-ink);
  color: var(--de-color-ink);
  text-decoration: none;
}

.button:active {
  transform: translateY(0.5px);
}

/* THE accent pill: the one filled, rounded CTA per the Gallery rules. */
.button-primary {
  background: var(--de-accent);
  border-color: var(--de-accent);
  color: var(--de-color-brand-contrast);
  border-radius: var(--de-radius-pill);
  padding-inline: var(--de-space-5);
  font-weight: 600;
}

.button-primary:hover {
  background: var(--de-accent-strong);
  border-color: var(--de-accent-strong);
  color: var(--de-color-brand-contrast);
}

/* Ghost: a quiet inline text action ("All posts") — underline-on-hover, no box. */
.button-ghost {
  border-color: transparent;
  background: transparent;
  color: var(--de-accent-strong);
  padding-inline: var(--de-space-2);
}

.button-ghost:hover {
  border-color: transparent;
  background: transparent;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Icon inside a button or inline affordance: inherits text color, sized to the cap. */
.de-icon {
  width: 1.1em;
  height: 1.1em;
  flex: none;
  fill: currentColor;
  vertical-align: -0.16em;
}

/* ── Imagery primitives ──────────────────────────────────────────────────────
 * Fixed-ratio frames with the accent-tint placeholder. Apply .de-media (+ a ratio
 * modifier) to the image wrapper; real <img> wired in epic-018 fills the frame. */
.de-media {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: var(--de-image-radius);
  background: var(--de-image-placeholder);
  aspect-ratio: var(--de-aspect-thumb);
}

.de-media-hero {
  aspect-ratio: var(--de-aspect-hero);
}

.de-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── Shared primitives: badges, form controls, callouts, WA theming ──────────── */

/* Eyebrow / pill badge primitive used by FEATURED, claimed, and small status chips. */
.de-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--de-space-1);
  font-size: var(--de-text-xs);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--de-tracking-label);
  text-transform: uppercase;
  padding: 0.4em 0.7em;
  border-radius: var(--de-radius-pill);
}

/* Native form controls — token-driven, AA focus, 16px font (no iOS zoom). */
.de-field {
  display: block;
  margin-bottom: var(--de-space-3);
}

.de-field > label,
.de-label {
  display: block;
  font-size: var(--de-text-sm);
  font-weight: 600;
  color: var(--de-color-ink);
  margin-bottom: var(--de-space-1);
}

.de-field input,
.de-field select,
.de-field textarea {
  width: 100%;
  min-height: var(--de-tap-size);
  padding: var(--de-space-2) var(--de-space-3);
  font: inherit;
  font-size: 1rem;
  color: var(--de-color-ink);
  background: var(--de-color-paper);
  border: 1px solid var(--de-color-border-strong);
  border-radius: var(--de-radius-sm);
}

.de-field textarea {
  min-height: 6rem;
  resize: vertical;
}

.de-field input:hover,
.de-field select:hover,
.de-field textarea:hover {
  border-color: var(--de-color-ink);
}

/* WebAwesome controls on MERCHANT surfaces inherit the accent through CSS variables so the
 * claim form matches the design system. Public pages ship no WebAwesome JS (epic-017 NFR). */
:root {
  --wa-color-brand-fill-loud: var(--de-accent);
  --wa-color-brand-fill-loud-active: var(--de-accent-strong);
  --wa-color-brand-border-loud: var(--de-accent);
  --wa-border-radius-m: var(--de-radius);
  --wa-color-success-fill-quiet: var(--de-accent-wash);
}

/* ── City page ───────────────────────────────────────────────────────────────
 * Restyled to the editorial palette; the blog-led IA restyle is task-114. */
.trust-line {
  color: var(--de-color-text-muted);
  margin-top: calc(-1 * var(--de-space-2));
}

.site-main > h1 + .trust-line {
  font-size: var(--de-text-md);
  font-weight: 400;
}

/* The city intro is a SECTION lead-in, not long-form body copy (Paulo, 2026-06-30): it takes
 * the wider section measure (matching .home-intro's 52rem), deliberately wider than the narrow
 * `--de-measure` reading column. The listing-detail body keeps the normal measure (task-114). */
.city-intro {
  max-width: 52rem;
  color: var(--de-color-text);
  margin-bottom: var(--de-space-6);
}

.city-intro p {
  margin: var(--de-space-3) 0 0;
}

.city-map-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--de-space-4);
  margin: var(--de-space-6) 0;
}

@media (min-width: 48rem) {
  .city-map-row:has(.featured-card) {
    grid-template-columns: 3fr 2fr;
    align-items: stretch;
  }
}

/* The featured (paid) slot must read unmistakably as PREMIUM yet stay within the Gallery
 * restraint: an accent-washed panel with an accent top-rail and a clear FEATURED label
 * (FTC/Google disclosure, mandatory) — the one place a tinted panel is justified. */
.featured-card {
  display: flex;
  flex-direction: column;
  gap: var(--de-space-2);
  position: relative;
  overflow: hidden;
  padding: var(--de-space-4);
  background: linear-gradient(var(--de-accent-wash), var(--de-color-surface) 70%);
  border: 1px solid var(--de-accent-border);
  border-radius: var(--de-radius-lg);
}

.featured-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--de-accent);
}

.featured-card .listing-card-head h3 {
  font-size: var(--de-text-lg);
}

.featured-card .listing-card-head h3 a {
  color: var(--de-color-ink);
}

.featured-label {
  align-self: flex-start;
  font-size: var(--de-text-xs);
  font-weight: 600;
  letter-spacing: var(--de-tracking-label);
  text-transform: uppercase;
  color: var(--de-color-brand-contrast);
  background: var(--de-accent);
  border-radius: var(--de-radius-pill);
  padding: 0.4em 0.8em;
}

.featured-card .listing-card-actions {
  margin-top: auto;
  padding-top: var(--de-space-2);
}

.featured-card .listing-card-actions .button {
  flex: 1 1 100%;
}

.listings-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--de-space-2) var(--de-space-3);
  padding: var(--de-space-3) 0;
  margin-bottom: var(--de-space-3);
  border-bottom: var(--de-hairline);
}

.listings-header h2 {
  font-size: var(--de-text-xl);
  margin: 0;
}

/* The sort control reads as a quiet segmented group. */
.sort-control {
  display: flex;
  align-items: center;
  gap: var(--de-space-1);
  font-size: var(--de-text-sm);
  padding: 0.2rem;
  background: var(--de-color-surface-sunk);
  border-radius: var(--de-radius-pill);
}

.sort-label {
  color: var(--de-color-text-muted);
  padding-left: var(--de-space-2);
}

.sort-option {
  display: inline-flex;
  align-items: center;
  min-height: var(--de-tap-size);
  font-weight: 500;
  color: var(--de-color-text-muted);
  text-decoration: none;
  padding: var(--de-space-1) var(--de-space-3);
  border-radius: var(--de-radius-pill);
}

.sort-option:hover {
  color: var(--de-accent-ink);
}

.sort-option[aria-current="true"] {
  background: var(--de-color-paper);
  color: var(--de-color-ink);
  box-shadow: var(--de-shadow-sm);
}

/* ── Listing cards — hairline-divided rows, NO card chrome (Gallery feed) ──────
 * Epic-011 boxed each listing; the Gallery feed uses whitespace + a 0.5px rule as
 * the divider. No border box, no shadow, no fill — the editorial feed device. */
.listing-cards {
  display: grid;
  gap: 0;
}

.listing-card {
  background: transparent;
  border: none;
  border-bottom: var(--de-hairline);
  border-radius: 0;
  padding: var(--de-space-4) 0;
}

.listing-card:last-child {
  border-bottom: none;
}

.listing-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--de-space-2) var(--de-space-3);
}

.listing-card-head h3 {
  margin: 0;
  flex: 1;
  min-width: 12rem;
  font-weight: 600;
}

.listing-card-head h3 a {
  color: var(--de-color-ink);
  text-decoration: none;
}

.listing-card-head h3 a:hover {
  color: var(--de-accent-ink);
}

/* The compact "★4.8 (52)" badge never breaks mid-figure (inline span only); the
 * listing-detail rating line is a wrapping <p> (handled below). */
.listing-rating {
  font-weight: 600;
  color: var(--de-color-ink);
}

span.listing-rating {
  white-space: nowrap;
}

span.listing-rating::first-letter {
  color: var(--de-color-star);
  font-size: 1.05em;
}

/* "✓ Claimed" — a quiet verified chip. */
.claimed-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--de-text-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--de-color-success);
  background: color-mix(in oklab, var(--de-color-success) 9%, var(--de-color-surface));
  border: 1px solid color-mix(in oklab, var(--de-color-success) 28%, var(--de-color-hairline));
  border-radius: var(--de-radius-pill);
  padding: 0.2em 0.6em;
}

.listing-card-meta {
  color: var(--de-color-text-muted);
  font-size: var(--de-text-sm);
  margin: var(--de-space-2) 0 var(--de-space-3);
}

.listing-card-meta a {
  color: var(--de-color-text-muted);
}

.listing-card-meta a:hover {
  color: var(--de-accent-ink);
}

.listing-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--de-space-2);
}

.listing-card-actions .button {
  flex: 1;
}

.site-main > .listing-card-actions .button {
  flex: 1 1 100%;
}

@media (min-width: 30rem) {
  .listing-card-actions .button {
    flex: 0 1 auto;
  }
}

@media (min-width: 48rem) {
  .site-main > .listing-card-actions .button {
    flex: 0 1 auto;
  }
}

/* ── FAQ (details/summary disclosure) — hairline rows, custom +/- marker ──────── */
.city-faq {
  max-width: var(--de-measure);
}

.city-faq > h2 {
  margin-bottom: var(--de-space-3);
}

.city-faq details {
  border-bottom: var(--de-hairline);
}

.city-faq summary {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  cursor: pointer;
  font-weight: 600;
  color: var(--de-color-ink);
  padding: var(--de-space-3) 0;
  list-style: none;
}

.city-faq summary::-webkit-details-marker {
  display: none;
}

.city-faq summary::marker {
  content: "";
}

.city-faq summary:hover {
  color: var(--de-accent-ink);
}

/* An accent "+" that rotates 45deg to a "×" on open; transform-only, reduced-motion safe. */
.city-faq summary::after {
  content: "";
  margin-left: auto;
  width: 1.1rem;
  height: 1.1rem;
  flex: none;
  background: var(--de-accent);
  transition: transform 0.18s ease;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.city-faq details[open] summary::after {
  transform: rotate(45deg);
}

.city-faq details > :not(summary) {
  margin: 0 0 var(--de-space-3);
  color: var(--de-color-text-muted);
  max-width: 62ch;
}

/* Related guides: hairline stacked list, matching the home guides device. */
.related-guides {
  margin-top: var(--de-space-7);
}

.related-guides > h2 {
  margin-bottom: var(--de-space-3);
}

.related-guides ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.related-guides li a {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  padding: var(--de-space-3) 0;
  color: var(--de-color-ink);
  text-decoration: none;
  border-bottom: var(--de-hairline);
  font-weight: 500;
}

.related-guides li a:hover {
  color: var(--de-accent-ink);
}

/* ════════════════════════════════════════════════════════════════════════════
 * task-114 — City + listing editorial IA restyle
 * Brings the two "money pages" fully onto the Gallery editorial system already
 * shipped on the home + blog pages: an uppercase accent KICKER over the oversized
 * Playfair H1 (the kicker-over-headline cover device), ruled section heads
 * (.section-rule) on the FAQ / related blocks, and a wider intro measure. The
 * page-class colour/spacing tokens were already migrated in task-108; this section
 * adds ONLY the editorial framing — no other rule above is reformatted.
 * Responsive + a11y: the kicker is plain inline text (no fixed width), so it never
 * forces horizontal scroll 360–430px; all interactive targets keep the ≥44px and
 * focus-ring rules defined globally below.
 * ════════════════════════════════════════════════════════════════════════════ */

/* The cover kicker sits tight above the H1 so the two read as one masthead block.
 * The H1's own top margin would otherwise push it away, so we pull the heading up. */
.city-kicker,
.listing-kicker {
  margin-bottom: var(--de-space-1);
}

.city-kicker + h1,
.listing-kicker + .listing-title-row {
  margin-top: 0;
}

/* ── Directory home — Gallery hero (the blog-led restyle is task-112) ──────────
 * No tinted wash, no box: an airy centered hero, oversized Playfair headline, lots of
 * air above the search. Bleeds to the viewport edges past the content padding. */
.home-hero {
  text-align: center;
  padding: var(--de-space-6) var(--de-space-4) var(--de-space-6);
  margin: calc(-1 * var(--de-space-6)) calc(-1 * var(--de-space-3)) var(--de-space-5);
  border-bottom: var(--de-hairline);
}

.home-hero h1 {
  max-width: 18ch;
  margin: 0 auto var(--de-space-5);
  font-size: var(--de-text-3xl);
  line-height: var(--de-leading-display);
}

/* Mobile-first: search input full width on phones, input + button on one row from 30rem up. */
.city-search {
  display: flex;
  flex-direction: column;
  gap: var(--de-space-2);
  justify-content: center;
  max-width: 32rem;
  margin: 0 auto;
}

@media (min-width: 30rem) {
  .city-search {
    flex-direction: row;
  }
}

.city-search input {
  flex: 1;
  min-height: var(--de-tap-size);
  box-sizing: border-box;
  padding: var(--de-space-2) var(--de-space-3) var(--de-space-2) 2.6rem;
  font-size: 1rem;
  color: var(--de-color-ink);
  background-color: var(--de-color-paper);
  /* Phosphor magnifying-glass, inline data-uri (no network, no JS) — decorative. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23999999'%3E%3Cpath d='M229.66 218.34l-50.07-50.06a88.11 88.11 0 1 0-11.31 11.31l50.06 50.07a8 8 0 0 0 11.32-11.32ZM40 112a72 72 0 1 1 72 72 72.08 72.08 0 0 1-72-72Z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 0.85rem center;
  background-size: 1.1rem;
  border: 1px solid var(--de-color-border-strong);
  border-radius: var(--de-radius);
}

.city-search input::placeholder {
  color: var(--de-color-text-subtle);
}

.city-search input:focus-visible {
  border-color: var(--de-accent);
}

.city-search .button {
  font-size: var(--de-text-md);
  cursor: pointer;
}

/* Mobile-first: one column on phones, two from 30rem. */
.city-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

@media (min-width: 30rem) {
  .city-grid {
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--de-space-4);
  }
}

.city-grid a {
  color: var(--de-color-ink);
}

.city-grid a:hover {
  color: var(--de-accent-ink);
}

.no-match {
  color: var(--de-color-text-muted);
}

/* ── Listing detail ──────────────────────────────────────────────────────────── */
.listing-title-row {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  flex-wrap: wrap;
}

.listing-title-row h1 {
  margin-bottom: 0;
}

p.listing-rating {
  margin-top: var(--de-space-2);
  color: var(--de-color-ink);
}

p.listing-rating::first-letter {
  color: var(--de-color-star);
}

.rating-asof {
  color: var(--de-color-text-muted);
  font-weight: 400;
}

.listing-detail-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--de-space-4);
  margin: var(--de-space-6) 0;
}

@media (min-width: 48rem) {
  .listing-detail-row:has(.listing-map) {
    grid-template-columns: 5fr 4fr;
    align-items: start;
  }
}

/* Contact block reads as a clean definition panel beside the map. */
.contact-block {
  display: flex;
  flex-direction: column;
  gap: var(--de-space-2);
  padding: var(--de-space-4);
  background: var(--de-color-surface-alt);
  border: 1px solid var(--de-color-hairline);
  border-radius: var(--de-radius);
}

.contact-block p {
  margin: 0;
}

/* Address/phone/website each get a leading icon so the panel scans at a glance. */
.contact-address,
.contact-block p:has(> a[href^="tel:"]),
.contact-block p:has(> a[target="_blank"]) {
  display: flex;
  align-items: baseline;
  gap: var(--de-space-2);
}

.contact-address::before,
.contact-block p:has(> a[href^="tel:"])::before,
.contact-block p:has(> a[target="_blank"])::before {
  content: "";
  width: 1rem;
  height: 1rem;
  flex: none;
  align-self: center;
  background: var(--de-color-text-subtle);
}

.contact-address::before {
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 16a88.1 88.1 0 0 0-88 88c0 75.3 80 132.17 83.41 134.55a8 8 0 0 0 9.18 0C136 236.17 216 179.3 216 104a88.1 88.1 0 0 0-88-88Zm0 56a32 32 0 1 1-32 32 32 32 0 0 1 32-32Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 16a88.1 88.1 0 0 0-88 88c0 75.3 80 132.17 83.41 134.55a8 8 0 0 0 9.18 0C136 236.17 216 179.3 216 104a88.1 88.1 0 0 0-88-88Zm0 56a32 32 0 1 1-32 32 32 32 0 0 1 32-32Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.contact-block p:has(> a[href^="tel:"])::before {
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M222.37 158.46l-47.11-21.11-.13-.06a16 16 0 0 0-15.17 1.4 8.12 8.12 0 0 0-.75.56L134.87 160c-15.42-7.49-31.34-23.29-38.83-38.51l20.78-24.71c.2-.25.39-.5.57-.77a16 16 0 0 0 1.32-15.06v-.12L97.54 33.64a16 16 0 0 0-16.62-9.52A56.26 56.26 0 0 0 32 80c0 79.4 64.6 144 144 144a56.26 56.26 0 0 0 55.88-48.92 16 16 0 0 0-9.51-16.62Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M222.37 158.46l-47.11-21.11-.13-.06a16 16 0 0 0-15.17 1.4 8.12 8.12 0 0 0-.75.56L134.87 160c-15.42-7.49-31.34-23.29-38.83-38.51l20.78-24.71c.2-.25.39-.5.57-.77a16 16 0 0 0 1.32-15.06v-.12L97.54 33.64a16 16 0 0 0-16.62-9.52A56.26 56.26 0 0 0 32 80c0 79.4 64.6 144 144 144a56.26 56.26 0 0 0 55.88-48.92 16 16 0 0 0-9.51-16.62Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.contact-block p:has(> a[target="_blank"])::before {
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 24a104 104 0 1 0 104 104A104.12 104.12 0 0 0 128 24Zm87.63 96h-39.4c-1.41-31-12-58.49-29.49-77.46A88.19 88.19 0 0 1 215.63 120ZM96.34 136h63.32c-2.16 30.39-13.61 57.6-31.66 75.92-18-18.32-29.47-45.53-31.66-75.92Zm0-16C98.5 89.61 110 62.4 128 44.08c18.05 18.32 29.5 45.53 31.66 75.92ZM109.26 42.54C91.74 61.51 81.13 89 79.77 120h-39.4a88.19 88.19 0 0 1 68.89-77.46ZM40.37 136h39.4c1.36 31 12 58.49 29.49 77.46A88.19 88.19 0 0 1 40.37 136Zm106.37 77.46c17.52-19 28.13-46.44 29.49-77.46h39.4a88.19 88.19 0 0 1-68.89 77.46Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 24a104 104 0 1 0 104 104A104.12 104.12 0 0 0 128 24Zm87.63 96h-39.4c-1.41-31-12-58.49-29.49-77.46A88.19 88.19 0 0 1 215.63 120ZM96.34 136h63.32c-2.16 30.39-13.61 57.6-31.66 75.92-18-18.32-29.47-45.53-31.66-75.92Zm0-16C98.5 89.61 110 62.4 128 44.08c18.05 18.32 29.5 45.53 31.66 75.92ZM109.26 42.54C91.74 61.51 81.13 89 79.77 120h-39.4a88.19 88.19 0 0 1 68.89-77.46ZM40.37 136h39.4c1.36 31 12 58.49 29.49 77.46A88.19 88.19 0 0 1 40.37 136Zm106.37 77.46c17.52-19 28.13-46.44 29.49-77.46h39.4a88.19 88.19 0 0 1-68.89 77.46Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.hours {
  margin: var(--de-space-1) 0 0;
  padding-top: var(--de-space-2);
  border-top: 1px dashed var(--de-color-hairline);
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--de-space-1) var(--de-space-4);
  font-size: var(--de-text-sm);
}

.hours dt {
  color: var(--de-color-text-muted);
  font-weight: 500;
}

.hours dd {
  margin: 0;
}

.listing-description {
  max-width: var(--de-measure);
  color: var(--de-color-text);
}

/* Claim CTA — a confident, accent-washed invitation, clearly the page's owner action. */
.claim-callout {
  margin-top: var(--de-space-7);
  padding: var(--de-space-5);
  background: linear-gradient(var(--de-accent-wash), var(--de-color-surface) 80%);
  border: 1px solid var(--de-accent-border);
  border-radius: var(--de-radius-lg);
}

.claim-callout h2 {
  margin-top: 0;
  margin-bottom: var(--de-space-2);
}

.claim-callout p {
  margin: 0 0 var(--de-space-4);
  color: var(--de-color-text-muted);
  max-width: 48ch;
}

/* Related listings (city-funnel): a wrapped row of name + gold-rating chips. */
.related-list {
  list-style: none;
  padding: 0;
  margin: var(--de-space-3) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--de-space-2) var(--de-space-3);
}

/* task-114: the block now carries a ruled .section-rule head (its own hairline), so the
 * block's old top border + padding would double the rule — dropped, the Gallery way. */
.related-listings {
  margin-top: var(--de-space-7);
}

.related-list li {
  display: inline-flex;
  align-items: baseline;
  gap: var(--de-space-1);
}

/* ── Articles / posts — real long-form reading typography (Gallery measure) ──────
 * The blog/post restyle is task-113; here the system gives long-form a comfortable
 * Inter measure, Playfair body headings, and accented inline marks. */
.article-body {
  max-width: var(--de-measure);
  margin-left: auto;
  margin-right: auto;
  font-size: 1.1875rem;
  line-height: 1.75;
}

.article-body > h1 {
  font-size: var(--de-text-2xl);
  line-height: var(--de-leading-display);
  margin-bottom: var(--de-space-2);
}

/* The "Updated {month}" stamp sits just under the title as a quiet dateline. */
.article-body > h1 + .trust-line {
  margin: 0 0 var(--de-space-6);
  padding-bottom: var(--de-space-4);
  border-bottom: var(--de-hairline);
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
}

.article-body h2 {
  font-size: var(--de-text-xl);
  margin: var(--de-space-6) 0 var(--de-space-2);
}

.article-body h3 {
  font-size: var(--de-text-lg);
  margin: var(--de-space-5) 0 var(--de-space-2);
}

.article-body p {
  margin: 0 0 var(--de-space-3);
}

.article-body ul,
.article-body ol {
  margin: 0 0 var(--de-space-3);
  padding-left: var(--de-space-4);
}

.article-body li {
  margin-bottom: var(--de-space-2);
}

.article-body li::marker {
  color: var(--de-accent);
}

.article-body a {
  color: var(--de-accent-strong);
  font-weight: 500;
}

/* Pull-quote: a hairline-led editorial quote, no heavy fill. */
.article-body blockquote {
  margin: var(--de-space-5) 0;
  padding: var(--de-space-1) var(--de-space-4);
  border-left: 2px solid var(--de-accent);
  color: var(--de-color-text-muted);
  font-family: var(--de-font-display);
  font-style: italic;
  font-size: var(--de-text-lg);
}

.article-body blockquote p:last-child {
  margin-bottom: 0;
}

.article-body code {
  font-family: var(--de-font-mono);
  font-size: 0.9em;
  background: var(--de-color-surface-sunk);
  padding: 0.1em 0.35em;
  border-radius: var(--de-radius-sm);
}

.article-body pre {
  background: var(--de-color-surface-sunk);
  padding: var(--de-space-3);
  border-radius: var(--de-radius);
  margin: 0 0 var(--de-space-3);
}

.article-body pre code {
  background: none;
  padding: 0;
}

.article-body hr {
  border: none;
  border-top: var(--de-hairline);
  margin: var(--de-space-6) 0;
}

.article-body img {
  max-width: 100%;
  height: auto;
  border-radius: var(--de-image-radius);
  margin: var(--de-space-4) 0;
}

/* ── Blog post header — kicker + dateline + cover (task-113) ──────────────────
 * The post leads with a category kicker over the Playfair headline, a quiet dateline
 * (updated stamp · computed reading time), then a fixed-ratio cover (real photo or the
 * accent-tint placeholder). The header sits at the article reading measure. */
.article-body > .de-kicker {
  margin-bottom: var(--de-space-2);
}

/* The dateline replaces the old bare "Updated" stamp — same quiet-rule treatment, but it
 * no longer closes the header (the cover follows it), so it keeps only its top breathing room. */
.article-body > h1 + .article-dateline {
  margin: 0 0 var(--de-space-4);
  padding-bottom: 0;
  border-bottom: none;
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
}

/* Post cover: .de-media supplies the 16/9 frame + accent-tint placeholder, so an absent
 * photo is a tinted frame, never a broken img. A hairline below separates it from the body. */
.article-hero {
  width: 100%;
  margin: 0 0 var(--de-space-6);
  padding-bottom: var(--de-space-6);
  border-bottom: var(--de-hairline);
}

/* ── Blog index — a single category-filterable feed (task-113) ────────────────
 * NO kind grouping: one newest-first feed of hairline-divided rows (no card chrome),
 * each a fixed-ratio thumb + category kicker + Playfair headline + dateline meta. */

/* Category chip row: plain links that re-render the feed server-side (no JS framework). */
.blog-filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--de-space-2);
  margin: var(--de-space-4) 0 var(--de-space-5);
  padding-bottom: var(--de-space-4);
  border-bottom: var(--de-hairline);
}

.blog-chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--de-tap-size);
  padding: 0 var(--de-space-3);
  border: var(--de-hairline);
  border-radius: var(--de-radius-pill);
  font-size: var(--de-text-sm);
  font-weight: 500;
  color: var(--de-color-text-muted);
  text-decoration: none;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.blog-chip:hover {
  color: var(--de-accent-ink);
  border-color: var(--de-accent-border);
  text-decoration: none;
}

/* The active filter: filled accent pill (the one rationed accent fill, like the CTA). */
.blog-chip[aria-current="true"] {
  background: var(--de-accent);
  border-color: var(--de-accent);
  color: var(--de-color-brand-contrast);
}

.blog-feed {
  margin-bottom: var(--de-space-6);
}

.blog-feed ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Each feed row: thumb left / copy right from 36rem, stacked on phones. Hairline-divided. */
.blog-feed-item {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--de-space-3);
  padding: var(--de-space-5) 0;
  border-bottom: var(--de-hairline);
}

@media (min-width: 36rem) {
  .blog-feed-item {
    grid-template-columns: 14rem 1fr;
    gap: var(--de-space-5);
    align-items: start;
  }
}

.blog-feed-thumb {
  width: 100%;
}

.blog-feed-copy {
  min-width: 0;
}

.blog-feed-title {
  margin: 0 0 var(--de-space-2);
  font-family: var(--de-font-display);
  font-size: var(--de-text-xl);
  font-weight: var(--de-display-weight);
  line-height: var(--de-leading-snug);
  letter-spacing: var(--de-display-tracking);
}

.blog-feed-title a {
  color: var(--de-color-ink);
  text-decoration: none;
}

.blog-feed-title a:hover {
  color: var(--de-accent-ink);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

.blog-feed-meta {
  margin: 0;
  color: var(--de-color-text-muted);
  font-size: var(--de-text-sm);
}

/* Related-posts block below the post body (reuses the home .post-grid hairline cards). */
.blog-related {
  margin-top: var(--de-space-7);
  padding-top: var(--de-space-5);
  border-top: var(--de-hairline);
}

/* City-funnel footer block under each article. */
.article-city-links {
  margin-top: var(--de-space-7);
  border-top: var(--de-hairline);
  padding-top: var(--de-space-4);
}

.article-city-links > .button {
  margin-top: var(--de-space-3);
}

/* task-125: the this-post's-city callout, an accent-washed box (mirrors .claim-callout) that
 * outranks the generic all-cities list below it — a city post reader gets ONE obvious next step. */
.article-city-cta {
  margin-bottom: var(--de-space-5);
  padding: var(--de-space-4) var(--de-space-5);
  background: linear-gradient(var(--de-accent-wash), var(--de-color-surface) 80%);
  border: 1px solid var(--de-accent-border);
  border-radius: var(--de-radius-lg);
}

/* Ad slot — sits in its own breathing room, visually separated from editorial content. */
.ad-slot {
  display: block;
  margin: var(--de-space-6) 0;
  min-height: 0;
}

/* ── Home sections — ruled section heads + hairline stacked lists ─────────────── */
.home-cities,
.home-guides {
  margin-top: var(--de-space-7);
}

/* Ruled section heading: a uppercase Inter label left, a hairline rule to the right edge —
 * the editorial "section divider" device. The label reads like a kicker, not a headline. */
.home-cities > h2,
.home-guides > h2 {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  margin: 0 0 var(--de-space-4);
  font-family: var(--de-font-body);
  font-size: var(--de-text-xs);
  font-weight: 500;
  letter-spacing: var(--de-tracking-label);
  text-transform: uppercase;
  color: var(--de-color-text-muted);
}

.home-cities > h2::after,
.home-guides > h2::after {
  content: "";
  flex: 1;
  height: 0;
  border-top: var(--de-hairline);
}

/* Guides: a hairline stacked list with a leading guide glyph instead of a raw bullet. */
.home-guides ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}

.home-guides li a {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  padding: var(--de-space-3) var(--de-space-3) var(--de-space-3) var(--de-space-2);
  color: var(--de-color-ink);
  text-decoration: none;
  border-bottom: var(--de-hairline);
  font-weight: 500;
}

.home-guides li:last-child a {
  border-bottom: none;
}

.home-guides li a::before {
  content: "";
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  background: var(--de-accent);
  /* Phosphor article/book glyph, masked to the accent color. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M208 24H72A32 32 0 0 0 40 56v168a8 8 0 0 0 8 8h144a8 8 0 0 0 0-16H56a16 16 0 0 1 16-16h136a8 8 0 0 0 8-8V32a8 8 0 0 0-8-8Zm-8 152H72a31.8 31.8 0 0 0-16 4.31V56a16 16 0 0 1 16-16h128Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M208 24H72A32 32 0 0 0 40 56v168a8 8 0 0 0 8 8h144a8 8 0 0 0 0-16H56a16 16 0 0 1 16-16h136a8 8 0 0 0 8-8V32a8 8 0 0 0-8-8Zm-8 152H72a31.8 31.8 0 0 0-16 4.31V56a16 16 0 0 1 16-16h128Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.home-guides li a:hover {
  color: var(--de-accent-ink);
}

.home-guides > .button {
  margin-top: var(--de-space-3);
}

/* Home intro prose — the directory band's editorial lead-in (Paulo, 2026-06-30): a lead-in,
 * NOT article body copy, so it is NOT clamped to the narrow long-form `--de-measure`. It takes
 * the section width (a generously wider measure) so it reads as a wide intro, not a column. */
.home-intro {
  max-width: 52rem;
  margin-top: var(--de-space-4);
  color: var(--de-color-text);
}

.home-intro p {
  margin: var(--de-space-3) 0 0;
}

.home-intro p:first-child {
  margin-top: 0;
}

/* Guide cards carry a meta-description excerpt under the title; the row hairline moves
 * to the card so title + excerpt read as one block. */
.home-guides .guide-card {
  border-bottom: var(--de-hairline);
  padding-bottom: var(--de-space-3);
}

.home-guides .guide-card:last-child {
  border-bottom: none;
}

.home-guides .guide-card a {
  border-bottom: none;
}

.guide-excerpt {
  margin: var(--de-space-1) 0 0 calc(1.05rem + var(--de-space-3) + var(--de-space-2));
  color: var(--de-color-text-muted);
  line-height: 1.55;
}

/* ── Blog-led home (task-112) ─────────────────────────────────────────────────
 * The home page now LEADS with the blog feed: a focal lead post, then a hairline
 * latest-posts grid, then the DEMOTED city directory band. No card chrome — hairlines,
 * whitespace, the accent kicker, and oversized Playfair do the work. Mobile-first;
 * verified with no horizontal scroll 360–430px and ≥44px tap targets. */

/* Lead post — the page's focal point. Single column on phones; image left / copy right
 * from 48rem so the hero photo and the oversized headline share the fold on desktop. */
.home-lead {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--de-space-4);
  align-items: center;
  margin-bottom: var(--de-space-6);
  padding-bottom: var(--de-space-6);
  border-bottom: var(--de-hairline);
}

@media (min-width: 48rem) {
  .home-lead {
    grid-template-columns: 1.15fr 1fr;
    gap: var(--de-space-6);
  }
}

/* The hero frame: .de-media already supplies the fixed ratio + accent-tint placeholder,
 * so a missing photo is a tinted frame, never a broken img. The link is decorative
 * (aria-hidden, tabindex -1) — the headline link is the real, named target. */
.home-lead-media {
  width: 100%;
}

.home-lead-copy {
  min-width: 0;
}

.home-lead-title {
  margin: 0 0 var(--de-space-3);
  font-size: var(--de-text-3xl);
  line-height: var(--de-leading-display);
}

.home-lead-title a {
  color: var(--de-color-ink);
  text-decoration: none;
}

.home-lead-title a:hover {
  color: var(--de-accent-ink);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

/* The dek is a lead-in, not body copy: a comfortably wide measure, not the tight one. */
.home-lead-dek {
  max-width: 44ch;
  margin: 0 0 var(--de-space-4);
  color: var(--de-color-text-muted);
  font-size: var(--de-text-md);
  line-height: 1.55;
}

/* "Read" — an accent text action with a trailing arrow; a real ≥44px tap target. */
.home-lead-read {
  display: inline-flex;
  align-items: center;
  gap: var(--de-space-1);
  min-height: var(--de-tap-size);
  font-weight: 500;
  color: var(--de-accent-ink);
  text-decoration: none;
}

.home-lead-read::after {
  content: "→";
  transition: transform 0.15s ease-in-out;
}

.home-lead-read:hover {
  text-decoration: underline;
  text-underline-offset: 0.14em;
}

.home-lead-read:hover::after {
  transform: translateX(0.15rem);
}

/* Ruled section heading shared by Latest / the directory band — uppercase Inter label with a
 * hairline rule running to the right edge (the same device as .home-cities > h2). */
.section-rule {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  margin: 0 0 var(--de-space-4);
  font-family: var(--de-font-body);
  font-size: var(--de-text-xs);
  font-weight: 500;
  letter-spacing: var(--de-tracking-label);
  text-transform: uppercase;
  color: var(--de-color-text-muted);
}

.section-rule::after {
  content: "";
  flex: 1;
  height: 0;
  border-top: var(--de-hairline);
}

.home-latest {
  margin-bottom: var(--de-space-7);
}

/* The latest-posts grid: hairline-divided, NO card chrome. One column on phones, two from
 * 36rem, three from 60rem. Each post is a kicker + headline + meta line. */
.post-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

@media (min-width: 36rem) {
  .post-grid {
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--de-space-5);
  }
}

@media (min-width: 60rem) {
  .post-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.post-card {
  border-top: var(--de-hairline);
}

.post-card-link {
  display: block;
  padding: var(--de-space-4) 0;
  text-decoration: none;
}

.post-card-title {
  margin: 0 0 var(--de-space-2);
  font-family: var(--de-font-display);
  font-size: var(--de-text-lg);
  font-weight: var(--de-display-weight);
  line-height: var(--de-leading-snug);
  letter-spacing: var(--de-display-tracking);
  color: var(--de-color-ink);
}

.post-card-link:hover .post-card-title {
  color: var(--de-accent-ink);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

.post-card-meta {
  margin: 0;
  color: var(--de-color-text-muted);
  font-size: var(--de-text-sm);
  line-height: 1.5;
}

.home-view-all {
  margin-top: var(--de-space-4);
}

/* The DEMOTED directory band: clearly secondary (below the content), separated by a hairline,
 * but still scannable. Its heading uses the ruled-section device, not a hero. */
.home-directory {
  margin-top: var(--de-space-7);
  padding-top: var(--de-space-6);
  border-top: var(--de-hairline);
}

/* Inside the band the search and city list follow the intro with editorial spacing; the
 * cities section's own top margin (.home-cities) is neutralized here since it lives in-band. */
.home-directory .city-search {
  margin: var(--de-space-5) 0 0;
}

.home-directory .home-cities {
  margin-top: var(--de-space-5);
}

/* The home trust line: a quiet centered stamp, hairline-separated. */
.home-trust {
  margin-top: var(--de-space-7);
  padding-top: var(--de-space-4);
  border-top: var(--de-hairline);
  text-align: center;
  color: var(--de-color-text-muted);
  font-size: var(--de-text-sm);
}

/* City list rows: name left, ACTIVE listing count right — hairline-divided (no boxes). */
.city-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--de-space-2);
  min-height: var(--de-tap-size);
  padding: var(--de-space-3) 0;
  font-weight: 500;
  color: var(--de-color-ink);
  text-decoration: none;
  border-bottom: var(--de-hairline);
}

.city-row:hover {
  color: var(--de-accent-ink);
}

/* The city name carries a small location pin so the row reads as a place, not a label. */
.city-row-name {
  display: inline-flex;
  align-items: center;
  gap: var(--de-space-2);
}

.city-row-name::before {
  content: "";
  width: 0.85rem;
  height: 0.85rem;
  flex: none;
  background: currentColor;
  opacity: 0.5;
  /* Phosphor map-pin, masked so it inherits the row's (accent-on-hover) color. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 16a88.1 88.1 0 0 0-88 88c0 75.3 80 132.17 83.41 134.55a8 8 0 0 0 9.18 0C136 236.17 216 179.3 216 104a88.1 88.1 0 0 0-88-88Zm0 56a32 32 0 1 1-32 32 32 32 0 0 1 32-32Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 16a88.1 88.1 0 0 0-88 88c0 75.3 80 132.17 83.41 134.55a8 8 0 0 0 9.18 0C136 236.17 216 179.3 216 104a88.1 88.1 0 0 0-88-88Zm0 56a32 32 0 1 1-32 32 32 32 0 0 1 32-32Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.city-row-count {
  flex: none;
  min-width: 1.9rem;
  text-align: center;
  font-size: var(--de-text-sm);
  font-weight: 600;
  color: var(--de-accent-ink);
  background: var(--de-accent-tint);
  border-radius: var(--de-radius-pill);
  padding: 0.15rem var(--de-space-2);
}

/* ── Map component ──────────────────────────────────────────────────────────── */
.listing-map {
  height: 340px;
  border: 1px solid var(--de-color-border-strong);
  border-radius: var(--de-radius-lg);
  box-shadow: var(--de-shadow-sm);
  overflow: hidden;
  isolation: isolate;
  z-index: 0;
}

@media (min-width: 48rem) {
  .city-map-row:has(.featured-card) .listing-map {
    height: 100%;
    min-height: 340px;
  }
}

.map-highlight {
  outline: 2px solid var(--de-accent);
  outline-offset: 2px;
}

/* ── Legal / About long-form — readable measure, hairline section rhythm ──────── */
.legal-page,
.legal-page > p {
  max-width: var(--de-measure);
}

.legal-page > p {
  color: var(--de-color-text);
  margin: 0 0 var(--de-space-3);
}

.legal-operator {
  color: var(--de-color-text-subtle);
  font-size: var(--de-text-sm);
}

.legal-page > section {
  margin-top: var(--de-space-6);
}

.legal-page > section > h2 {
  font-size: var(--de-text-lg);
  margin-bottom: var(--de-space-2);
  padding-bottom: var(--de-space-2);
  border-bottom: var(--de-hairline);
}

.legal-page > section > p {
  color: var(--de-color-text);
  margin: 0 0 var(--de-space-3);
}

/* ── Sponsor page — a confident conversion surface within Gallery restraint ────── */
.sponsor-page {
  max-width: 48rem;
}

.sponsor-hero {
  padding: var(--de-space-5) 0;
  margin-bottom: var(--de-space-5);
  border-bottom: var(--de-hairline);
}

.sponsor-hero h1 {
  font-size: var(--de-text-2xl);
}

.sponsor-pitch {
  margin: var(--de-space-3) 0 0;
  font-size: var(--de-text-lg);
  color: var(--de-color-text-muted);
  max-width: 46ch;
}

/* Benefits as check-marked value points (accent check glyph, no emoji). */
.sponsor-benefits {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--de-space-5);
  display: grid;
  gap: var(--de-space-3);
}

.sponsor-benefits li {
  display: flex;
  align-items: flex-start;
  gap: var(--de-space-3);
  font-weight: 500;
  margin-bottom: 0;
}

.sponsor-benefits li::before {
  content: "";
  width: 1.35rem;
  height: 1.35rem;
  flex: none;
  margin-top: 0.05rem;
  background: var(--de-accent);
  /* Phosphor check-circle, masked to accent. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm45.66 85.66l-56 56a8 8 0 0 1-11.32 0l-24-24a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm45.66 85.66l-56 56a8 8 0 0 1-11.32 0l-24-24a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Price line: the figure reads big and confident; the honest cancel terms read calmly. */
.sponsor-price {
  margin: 0 0 var(--de-space-5);
  padding: var(--de-space-4);
  font-size: var(--de-text-xl);
  font-weight: 600;
  line-height: 1.4;
  color: var(--de-color-ink);
  background: var(--de-color-surface-alt);
  border: 1px solid var(--de-color-hairline);
  border-left: 3px solid var(--de-accent);
  border-radius: var(--de-radius);
  text-wrap: balance;
}

.sponsor-cta {
  display: grid;
  gap: var(--de-space-3);
}

@media (min-width: 36rem) {
  .sponsor-cta {
    grid-template-columns: 1fr 1fr;
  }
}

.sponsor-cta-path {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--de-space-3);
  padding: var(--de-space-4);
  background: var(--de-color-paper);
  border: 1px solid var(--de-color-hairline);
  border-radius: var(--de-radius);
}

.sponsor-cta-path h2 {
  margin: 0;
  font-size: var(--de-text-lg);
}

.sponsor-cta-path .button {
  width: 100%;
}

/* ── Claim form — a calm, labelled conversion form ────────────────────────────── */
.claim-form {
  max-width: 34rem;
}

.claim-back {
  margin: 0 0 var(--de-space-3);
  font-size: var(--de-text-sm);
}

.claim-back a {
  color: var(--de-color-text-muted);
  text-decoration: none;
  font-weight: 500;
}

.claim-back a:hover {
  color: var(--de-accent-ink);
}

.claim-form > h1 {
  margin-bottom: var(--de-space-2);
}

.claim-form > p {
  color: var(--de-color-text-muted);
  max-width: 46ch;
}

#claim-box {
  margin-top: var(--de-space-4);
}

#claim-box form {
  display: grid;
  gap: var(--de-space-1);
  padding: var(--de-space-5);
  background: var(--de-color-paper);
  border: 1px solid var(--de-color-hairline);
  border-radius: var(--de-radius-lg);
  box-shadow: var(--de-shadow-sm);
}

#claim-box wa-input,
#claim-box wa-select,
#claim-box wa-textarea {
  margin-bottom: var(--de-space-3);
  --wa-form-control-label-font-weight: 600;
}

#claim-box wa-button[type="submit"] {
  margin-top: var(--de-space-2);
}

/* Inline validation errors read as helpful, not alarming. */
.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--de-space-2);
  margin: calc(-1 * var(--de-space-2)) 0 var(--de-space-3);
  padding: var(--de-space-2) var(--de-space-3);
  font-size: var(--de-text-sm);
  font-weight: 500;
  color: var(--de-color-danger);
  background: var(--de-color-danger-tint);
  border: 1px solid color-mix(in oklab, var(--de-color-danger) 26%, var(--de-color-hairline));
  border-radius: var(--de-radius-sm);
}

.field-error::before {
  content: "";
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  margin-top: 0.05rem;
  background: var(--de-color-danger);
  /* Phosphor warning-circle, masked to danger. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm-8 56a8 8 0 0 1 16 0v56a8 8 0 0 1-16 0Zm8 104a12 12 0 1 1 12-12 12 12 0 0 1-12 12Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm-8 56a8 8 0 0 1 16 0v56a8 8 0 0 1-16 0Zm8 104a12 12 0 1 1 12-12 12 12 0 0 1-12 12Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.form-hint {
  margin: var(--de-space-3) 0 0;
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
}

/* WA callouts (claim confirmation/duplicate/already-claimed, 429 fragment). */
wa-callout {
  --wa-color-brand-fill-quiet: var(--de-accent-wash);
  border-radius: var(--de-radius);
}

/* ── Mobile-first responsive pass (Mobile NFR, 360px+) ──────────────────────────
 * Base rules size every interactive control to the 44px touch floor and keep content
 * reflowing inside its own container. The single min-width:64rem block at the end
 * restores compact desktop metrics. No content is hidden per viewport (SEO parity). */

.site-nav > a,
.site-nav > .nav-dropdown > summary {
  min-height: var(--de-tap-size);
  display: inline-flex;
  align-items: center;
}

.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: var(--de-tap-size);
}

.city-faq summary {
  min-height: var(--de-tap-size);
  align-content: center;
}

.portal-picker {
  list-style: none;
  padding: 0;
}

.portal-picker a {
  display: flex;
  align-items: center;
  min-height: var(--de-tap-size);
}

.article-group li a,
.home-guides li a,
.related-guides li a,
.related-list li a {
  display: inline-flex;
  align-items: center;
  min-height: var(--de-tap-size);
}

/* Inline action links: hit area grows to the touch floor via block padding; the matching
 * negative margin cancels layout shift so the page renders identically. */
.breadcrumb a,
.contact-block p a,
.portal-status a,
.claim-form a,
.listing-card-head h3 a {
  display: inline-block;
  padding-block: calc((var(--de-tap-size) - 1lh) / 2);
  margin-block: calc((1lh - var(--de-tap-size)) / 2);
}

/* WebAwesome buttons (merchant surfaces): pin the touch floor on the rendered base part. */
wa-button::part(base) {
  min-height: var(--de-tap-size);
}

/* Leaflet zoom controls ship 30x30 — too small for touch. Sized up on coarse pointers
 * and narrow viewports. Selector depth beats leaflet.css's later-loaded rule. */
@media (pointer: coarse), (max-width: 48rem) {
  .site-main .listing-map .leaflet-control-zoom a {
    width: var(--de-tap-size);
    height: var(--de-tap-size);
    line-height: var(--de-tap-size);
    font-size: 1.4rem;
  }
}

/* Content reflow guards: long URLs / unbroken tokens wrap inside their own container. */
.article-body,
.city-intro,
.listing-description,
.contact-block,
.portal-pending {
  overflow-wrap: break-word;
}

/* Markdown code blocks scroll within themselves, never the page. */
.site-main pre {
  overflow-x: auto;
  max-width: 100%;
}

/* Merchant portal chrome: the "Your listing · Sign out" header row, and mobile-sticky submit. */
.portal-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--de-space-2);
}

@media (max-width: 48rem) {
  #edit-form-box wa-button[type="submit"] {
    position: sticky;
    bottom: var(--de-space-2);
    z-index: 5;
  }
}

/* ── Desktop compaction ─────────────────────────────────────────────────────────
 * From 64rem up (mouse-driven layouts) the touch floor relaxes back to compact metrics. */
@media (min-width: 64rem) {
  .site-nav > a,
  .site-nav > .nav-dropdown > summary {
    min-height: 0;
  }

  .nav-dropdown-panel a {
    display: block;
    min-height: 0;
  }

  .footer-links a {
    display: inline;
    min-height: 0;
  }

  .sort-option {
    display: inline;
    min-height: 0;
  }

  .article-group li a,
  .home-guides li a,
  .related-guides li a,
  .related-list li a {
    display: inline;
    min-height: 0;
  }

  .breadcrumb a,
  .contact-block p a,
  .portal-status a,
  .claim-form a,
  .listing-card-head h3 a {
    display: inline;
    padding-block: 0;
    margin-block: 0;
  }

  .button {
    display: inline-flex;
    min-height: 0;
  }

  wa-button[size="small"]::part(base) {
    min-height: 0;
  }
}
