/* DirectoryEngine base stylesheet — the "Trusted Utility" design system
 * (design-system/design-system.md, the authoritative spec).
 *
 * TRUSTED UTILITY: the credible-directory standard (Checkatrade/Angi/Trustpilot). Navy
 * (--de-color-ink) does the structural work — trust bar, footer, headings, dark CTAs — with
 * ONE action blue reserved for interactive affordances and links, never decoration. Amber
 * appears only in star ratings, green only in verification, red only in safety/errors (the
 * 1:1 color-to-meaning mapping is what makes the palette read trustworthy). Schibsted
 * Grotesk headlines, Hanken Grotesk body, bordered white cards that lift on a blue-tinted
 * hover shadow, prose held to a 66ch measure.
 *
 * 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. The former per-vertical theme BUNDLE (paper/display-voice/imagery, task-109)
 * was retired by this redesign; the accent seed is the only override left.
 *
 * Epic-010's mobile-first behavior and the min-width:64rem desktop compaction are preserved
 * (bottom of file). Self-hosted fonts are declared first; never any third-party origin. */

/* ── Self-hosted web fonts ("Trusted Utility" design system) ─────────────────
 * Two grotesques, 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):
 *   • Schibsted Grotesk — display/headings, a confident slightly-condensed grotesque.
 *   • Hanken Grotesk    — body/UI, a highly legible humanist sans.
 * Each bundled woff2 is a VARIABLE face covering its weight axis, so ONE @font-face per
 * family exposes the whole range (Schibsted 400–900, Hanken 400–800) via font-weight
 * ranges. Relative url() resolves against this file's served path (/static/css/app.css)
 * -> /static/fonts/*, inheriting the same immutable, fingerprint-cached /static route as
 * every other first-party asset. font-display:swap paints 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: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url(../fonts/schibsted-grotesk-latin.woff2) format("woff2");
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(../fonts/hanken-grotesk-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 Arial onto roughly the SAME box as the
 * web font via size-adjust, so the swap is far less jarring. Both target faces are
 * humanist/grotesque sans close to Arial's metrics, so a single Arial-based mapping per
 * family keeps CLS low without shipping a second fallback font. */
@font-face {
  font-family: "Schibsted Grotesk Fallback";
  src: local("Arial");
  ascent-override: 92%;
  descent-override: 23%;
  line-gap-override: 0%;
  size-adjust: 102%;
}
@font-face {
  font-family: "Hanken Grotesk Fallback";
  src: local("Arial");
  ascent-override: 96%;
  descent-override: 26%;
  line-gap-override: 0%;
  size-adjust: 100%;
}

:root {
  /* ── Accent seed + derived ramp ──────────────────────────────────────────
   * --de-color-brand is the ONE per-directory seed (overridden by the shell when a
   * directory carries a validated brandColor). "Trusted Utility" uses a single action
   * BLUE — reserved for interactive/action affordances and links, never decoration. The
   * whole ramp derives from the seed via color-mix so one hex re-skins every accent. The
   * fixed literal values in the comments are the spec's blue ramp (§2), reproduced by the
   * derivation so a per-directory brandColor keeps a coherent ramp. --de-accent is the
   * semantic alias call sites read. */
  --de-color-brand: #125bd4;            /* spec --color-brand: the one action blue */
  --de-color-brand-contrast: #ffffff;
  --de-accent: var(--de-color-brand);
  /* Darker accent for hover/active and AA-contrast link text on white (~#0e49a8). */
  --de-accent-strong: color-mix(in oklab, var(--de-color-brand), #000 16%);
  /* Kicker/link ink — the same darker blue keeps small uppercase text AA on white. */
  --de-accent-ink: var(--de-accent-strong);
  /* Chip / subtle brand surface (~#eef3fb) and a fainter key-box wash (~#f4f8fe). */
  --de-accent-tint: color-mix(in oklab, var(--de-color-brand) 8%, #ffffff);
  --de-accent-tint-2: color-mix(in oklab, var(--de-color-brand) 4%, #ffffff);
  --de-accent-wash: var(--de-accent-tint-2);
  /* Accent-tinted hairline for surfaces that need an accented edge. */
  --de-accent-border: color-mix(in oklab, var(--de-color-brand) 22%, var(--de-color-border));

  /* Spec brand aliases (§2) + epic-011 back-compat: page classes read --de-color-brand-*;
   * they resolve to the derived ramp above, so a single seed re-skins everything. */
  --de-color-brand-ink: var(--de-accent-strong);
  --de-color-brand-tint: var(--de-accent-tint);
  --de-color-brand-tint-2: var(--de-accent-tint-2);
  --de-color-brand-strong: var(--de-accent-strong);
  --de-color-brand-wash: var(--de-accent-wash);
  --de-color-brand-border: var(--de-accent-border);

  /* ── Ink ramp — navy structure, calm body grays (spec §2) ─────────────────
   * Navy does triple duty: headings, the trust bar, and the footer. Body copy is never
   * pure black — always --de-color-body on white. */
  --de-color-ink: #0d1b2f;              /* headings, dark (navy) surfaces */
  --de-color-body: #26303e;             /* body copy */
  --de-color-text: var(--de-color-body); /* alias the rest of the system reads */
  --de-color-text-muted: #5c6b7e;       /* secondary text (dek, meta) — AA on white */
  --de-color-text-subtle: #8494a6;      /* tertiary labels, large/decorative only */

  /* Text ON navy surfaces (trust bar, footer). */
  --de-color-on-navy: #c9d6e8;
  --de-color-on-navy-strong: #ffffff;

  /* ── Surfaces ─────────────────────────────────────────────────────────────
   * "paper" is the (per-directory overridable, always white today) page surface;
   * -alt is the quiet section wash, -sunk the inset wells / code. */
  --de-color-paper: #ffffff;
  --de-color-surface: var(--de-color-paper);
  --de-color-surface-alt: #f6f8fb;      /* alternate/section background */
  --de-color-surface-sunk: #eef2f7;     /* code, inset wells, count chips */

  /* ── Borders / hairlines ──────────────────────────────────────────────────
   * Trusted Utility is card-based: 1px hairlines and card borders, a stronger border on
   * hover/emphasis. --de-hairline stays the shared "one rule" token. */
  --de-color-border: #e4e9f0;           /* hairlines, card borders, dividers */
  --de-color-hairline: var(--de-color-border);
  --de-color-border-strong: #c7d3e2;    /* hover borders, emphasis dividers, inputs */
  --de-hairline: 1px solid var(--de-color-border);

  /* ── Semantic colors — 1:1 color-to-meaning (spec §2) ─────────────────────
   * amber ONLY in stars, green ONLY in verification, red ONLY in safety/errors. */
  --de-color-danger: #b42318;
  --de-color-danger-bg: #fdece9;
  --de-color-danger-tint: var(--de-color-danger-bg);
  --de-color-success: #166c3e;          /* verified badge text, licensed/insured */
  --de-color-success-bg: #e7f4ec;
  --de-color-star: #f5a524;             /* filled rating stars — amber, never accent */
  --de-color-star-empty: #d9e0ea;       /* empty rating stars */
  --de-color-warning: #8a5a00;
  --de-color-warning-bg: #fdf3e2;

  /* ── Typography (spec §3) ─────────────────────────────────────────────────
   * Display = Schibsted Grotesk (confident, slightly condensed). Body = Hanken Grotesk
   * (legible humanist sans). Both self-hosted; the system stack is the final fallback. */
  --de-font-display: "Schibsted Grotesk", "Schibsted Grotesk Fallback", system-ui,
    -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --de-font-body: "Hanken Grotesk", "Hanken Grotesk Fallback", system-ui,
    -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, 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.03125rem;      /* 16.5px body — spec §3 */
  --de-line-height: 1.72;

  /* Type scale mapped to spec §3. Only the article H1 is fluid via clamp(); everything
   * else is fixed. Sizes carried on --de-* names the page CSS already references. */
  --de-text-xs: 0.75rem;                /* 12px — eyebrow / category */
  --de-text-sm: 0.8125rem;              /* 13px — small / meta */
  --de-text-md: 1rem;
  --de-text-lg: 1.1875rem;              /* 19px — lede / dek / H3-ish */
  --de-text-xl: 1.5rem;                 /* 24px — H2 section head */
  --de-text-2xl: clamp(1.875rem, 1.2rem + 2.8vw, 2.625rem);  /* 30–42px — page/article H1 */
  --de-text-3xl: clamp(2.1rem, 1.4rem + 3.2vw, 3rem);        /* hero headline */

  --de-leading-display: 1.08;           /* oversized headlines: tight */
  --de-leading-snug: 1.2;               /* smaller headings */
  --de-tracking-display: -0.02em;       /* display headline tightening */
  --de-tracking-tight: -0.02em;         /* legacy alias */

  /* ── Display voice ────────────────────────────────────────────────────────
   * The display TYPEFACE treatment (weight/tracking) is carried on tokens so h1/h2 don't
   * hardcode them. Trusted Utility keeps ONE voice for every directory (per-vertical type
   * bundles were removed): heavy Schibsted headings. */
  --de-display-weight: 800;             /* h1 weight (spec §3) */
  --de-display-tracking: var(--de-tracking-display);
  --de-tracking-kicker: 0.1em;          /* eyebrow / kicker tracking (spec §3) */
  --de-tracking-label: 0.08em;          /* smaller uppercase labels */
  --de-measure: 66ch;                   /* comfortable long-form reading measure (spec §3) */

  /* ── 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 (spec §3–4) ──────────────────────────────────────────
   * Content column ~1140px (a card grid wants room); prose stays at --de-measure. */
  --de-content-max-width: 71.25rem;
  --de-radius-sm: 6px;                  /* chips, badges, inputs */
  --de-radius: 10px;                    /* buttons, logos */
  --de-radius-lg: 12px;                 /* cards, provider rows, hero, key-box */
  --de-radius-pill: 999px;

  /* Elevation (spec §4): cards rest flat/at -sm and lift to -md on hover. The hover
   * shadow carries a faint BLUE tint (not neutral gray) so elevation ties to the brand. */
  --de-shadow-sm: 0 1px 2px rgb(13 27 47 / 0.05);
  --de-shadow-md: 0 8px 24px rgb(18 91 212 / 0.12);
  --de-shadow-lg: 0 24px 60px rgb(20 28 40 / 0.16);

  /* ── Imagery treatment ────────────────────────────────────────────────────
   * A missing image is an accent-tinted paper wash (never a gray box / broken img), and
   * images sit in fixed aspect ratios so the feed never reflows when photos load. */
  --de-image-radius: var(--de-radius-lg);
  --de-image-placeholder: linear-gradient(135deg,
    var(--de-accent-tint), color-mix(in oklab, var(--de-color-brand) 12%, var(--de-color-surface)));
  --de-aspect-hero: 2 / 1;              /* lead/hero photo (spec §5.5) */
  --de-aspect-thumb: 4 / 3;             /* feed thumbnail */

  /* ── Focus ring (a11y: visible on every interactive control, spec §8) ──────── */
  --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;
}

/* DESIGN SYSTEM — spacing convention (2026-07 spacing audit): every heading and paragraph
 * owns the space BELOW it, never above. margin-top stays 0 by default; a component only
 * earns extra top margin when it's a genuine mid-flow subsection break (see .article-body
 * h2/h3). A per-component override should mean "this heading pairs tightly with its
 * neighbor" (flex row, dateline, badge) — never a restatement of this same de-space-3
 * value; if you're about to write `margin: 0 0 var(--de-space-3)` on some selector, you
 * don't need to — this rule already covers it. */
h1, h2, h3, h4, h5, h6, p {
  margin: 0 0 var(--de-space-3);
}

/* ── Display headings: confident, tight Schibsted Grotesk (the display voice) ── */
h1, h2, h3, h4 {
  color: var(--de-color-ink);
  font-family: var(--de-font-display);
  font-weight: var(--de-display-weight);
  line-height: var(--de-leading-snug);
  letter-spacing: var(--de-display-tracking);
  text-wrap: balance;
}

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

h2 {
  font-size: var(--de-text-xl);
  font-weight: 700;                    /* spec §3: H2 700 */
  letter-spacing: -0.01em;
}

h3 {
  font-size: var(--de-text-lg);
  font-weight: 700;                    /* spec §3: H3 (provider/card name) 700 */
  letter-spacing: -0.01em;
}

/* h4-h6: small section labels — the one place headings break from the display voice
 * above. Rare in practice (h5/h6 only fire on markdown content nested 4+ levels deep,
 * Markdown.kt renderHeading) but styled so they're never on raw browser defaults. */
h4 {
  font-family: var(--de-font-body);
  font-size: var(--de-text-md);
  font-weight: 600;
  letter-spacing: 0;
}

h5, h6 {
  font-family: var(--de-font-body);
  font-size: var(--de-text-sm);
  font-weight: 600;
}

h6 {
  color: var(--de-color-text-muted);
}

/* 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: the action blue, underlined with a calm offset. */
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 (spec §8): one consistent, visible focus ring on every focusable control — a 2px
 * brand outline at 2px offset; never removed. */
:focus-visible {
  outline: 2px solid var(--de-color-brand);
  outline-offset: 2px;
  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;
  }
}

/* ── EYEBROW / KICKER (spec §3, §5.3) ─────────────────────────────────────────
 * A small, letter-spaced, uppercase accent label that sits above headlines and on
 * feed items (category, section eyebrow). 12px / 0.1em / 700 / brand. Used as both a
 * standalone .de-kicker and the shared look for category labels. Safety content flips
 * the color to danger via .eyebrow--danger where used. */
.de-kicker {
  display: inline-block;
  font-family: var(--de-font-body);
  font-size: var(--de-text-xs);
  font-weight: 700;
  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);
}

/* ── App bar (spec §5.1) ──────────────────────────────────────────────────────
 * White bar, hairline bottom border, sticky on scroll. Left: brand lockup (a rounded
 * blue tile holding the Phosphor mark + the directory name in the display font). Right:
 * the Cities dropdown + Blog links. No "get a quote" CTA (no quote funnel exists). */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: var(--de-hairline);
  background: var(--de-color-surface);
}

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

/* Brand lockup: the icon tile + the directory name in Schibsted, tight. inline-flex +
 * gap so the tile and wordmark sit on one baseline-centered row. */
.site-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--de-space-2);
  font-family: var(--de-font-display);
  font-weight: 800;
  font-size: 1.25rem;
  letter-spacing: -0.01em;
  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;
}

/* The brand MARK: the Phosphor glyph in a rounded brand-blue tile (spec §5.1), white on
 * blue. A fixed square keeps the tile crisp regardless of the glyph's own metrics. */
.site-brand .ph-fill {
  display: grid;
  place-items: center;
  width: 1.875rem;
  height: 1.875rem;
  border-radius: var(--de-radius);
  background: var(--de-color-brand);
  color: var(--de-color-brand-contrast);
  font-size: 1.125rem;
  line-height: 1;
}

.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: calm, medium-weight sentence-case links (spec §5.1). */
.site-nav > a:not(.site-brand) {
  font-size: var(--de-text-sm);
  font-weight: 500;
  color: color-mix(in oklab, var(--de-color-ink) 82%, transparent);
  text-decoration: none;
}

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

/* ── Trust bar (spec §5.2) ────────────────────────────────────────────────────
 * A full-width NAVY strip directly under the app bar, on every public template. It
 * carries ONE honest stat — the directory's total listing count — with a leading icon.
 * No average rating, no "vetted"/"verified" claims (we never fabricate trust signals). */
.trust-bar {
  background: var(--de-color-ink);
  color: var(--de-color-on-navy);
}

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

.trust-stat {
  display: inline-flex;
  align-items: center;
  gap: var(--de-space-2);
  font-size: var(--de-text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Leading mark — a masked Phosphor map-pin (the same glyph the city rows use), tinted to
 * the on-navy strong color, so the strip needs no icon font and no inline SVG in the markup. */
.trust-stat::before {
  content: "";
  width: 0.95rem;
  height: 0.95rem;
  flex: none;
  background: var(--de-color-on-navy-strong);
  -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;
}

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

.site-footer {
  background: var(--de-color-ink);
  color: var(--de-color-on-navy);
  padding: var(--de-space-6) var(--de-space-3) var(--de-space-4);
  font-size: var(--de-text-sm);
}

/* The footer content is centered in the same measure as the page body. */
.site-footer > * {
  max-width: var(--de-content-max-width);
  margin-left: auto;
  margin-right: auto;
}

/* Columns (spec §5.14): a brand + tagline column beside the grouped link columns. */
.footer-top {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--de-space-4) var(--de-space-5);
}

@media (min-width: 40rem) {
  .footer-top {
    grid-template-columns: 1.4fr 1fr;
    align-items: start;
  }
}

.footer-brand {
  display: flex;
  flex-direction: column;
  gap: var(--de-space-2);
}

.footer-brandname {
  font-family: var(--de-font-display);
  font-weight: 800;
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
  color: var(--de-color-on-navy-strong);
}

.footer-tagline {
  color: var(--de-color-on-navy);
  max-width: 38ch;
}

/* The grouped links: a wrapped set of columns, un-underlined until hover. */
.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--de-space-2) var(--de-space-5);
  align-content: start;
}

.footer-links a {
  color: var(--de-color-on-navy);
  text-decoration: none;
}

.footer-links a:hover {
  color: var(--de-color-on-navy-strong);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.site-footer > span {
  display: block;
  margin-top: var(--de-space-5);
  padding-top: var(--de-space-4);
  border-top: 1px solid color-mix(in oklab, var(--de-color-on-navy) 22%, transparent);
  color: color-mix(in oklab, var(--de-color-on-navy) 78%, transparent);
}

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

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

/* 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 (spec §5.8) ──────────────────────────────────────────────────────
 * Four intents on real <a>/<button> (SEO links stay links): PRIMARY (brand-blue fill,
 * the main conversion action) · DARK (navy, secondary emphasis / "Call now") · the base
 * .button = GHOST (transparent, hairline, ink — tertiary) · .button-ghost = LINK (brand
 * text, no box). Radius-md, weight 700. 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: 0.625rem var(--de-space-4);
  border: 1px solid var(--de-color-border);
  border-radius: var(--de-radius);
  color: var(--de-color-ink);
  font: inherit;
  font-weight: 700;
  font-size: var(--de-text-sm);
  text-decoration: none;
  white-space: nowrap;
  background: var(--de-color-surface);
  cursor: pointer;
  transition: border-color 0.14s ease, color 0.14s ease, background 0.14s ease,
    box-shadow 0.14s ease, transform 0.14s ease;
}

/* Ghost hover: the hairline strengthens — no fill. */
.button:hover {
  border-color: var(--de-color-border-strong);
  color: var(--de-color-ink);
  text-decoration: none;
}

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

/* PRIMARY — the brand-blue conversion action. */
.button-primary {
  background: var(--de-accent);
  border-color: var(--de-accent);
  color: var(--de-color-brand-contrast);
}

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

/* DARK — navy; provider "Call now" and secondary emphasis. */
.button-dark {
  background: var(--de-color-ink);
  border-color: var(--de-color-ink);
  color: var(--de-color-on-navy-strong);
}

.button-dark:hover {
  background: color-mix(in oklab, var(--de-color-ink) 92%, #fff);
  border-color: color-mix(in oklab, var(--de-color-ink) 92%, #fff);
  color: var(--de-color-on-navy-strong);
}

/* LINK — a quiet inline text action ("All posts →"), brand text, underline on hover. */
.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-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: the SAME §5.12 provider-row parts as an organic card, in a PREMIUM
 * accent-washed panel with a brand top-rail and the mandatory FEATURED label (FTC/Google
 * disclosure). It sits in the narrow map-row column, so it uses the stacked (logo | body, then
 * CTA) layout with the label spanning the top. */
.featured-card {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  grid-template-areas:
    "label label"
    "logo body"
    "cta cta";
  gap: var(--de-space-2) var(--de-space-4);
  align-items: start;
  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-logo {
  grid-area: logo;
}

.featured-card > .listing-card-body {
  grid-area: body;
}

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

/* The CTA drops to its own full-width row (location left, action right). */
.featured-card > .listing-card-cta {
  grid-area: cta;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  text-align: left;
  margin-top: var(--de-space-2);
}

.featured-label {
  grid-area: label;
  justify-self: start;
  font-size: var(--de-text-xs);
  font-weight: 700;
  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;
}

.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);
}

/* Design system exception: h2 sits inline with the sort control in one flex row, so it
 * can't carry the base default's bottom margin without breaking the row's alignment. */
.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 — the core directory unit (spec §5.12) ──────────────────────
 * Trusted Utility cards: a white bordered panel that rests at --shadow-sm and lifts to
 * the blue-tinted --shadow-md (border strengthens) on hover, so the listing scans as a
 * discrete, trustworthy row. Name (H3) + verified pill, an amber rating line, address/
 * phone meta, and the action buttons. */
.listing-cards {
  display: grid;
  gap: var(--de-space-3);
}

/* The provider row: three columns — 64px logo | details | CTA — that lifts on hover (§5.12). */
.listing-card {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) auto;
  gap: var(--de-space-4);
  align-items: center;
  background: var(--de-color-surface);
  border: 1px solid var(--de-color-border);
  border-radius: var(--de-radius-lg);
  padding: var(--de-space-4);
  box-shadow: var(--de-shadow-sm);
  transition: box-shadow 0.16s ease, border-color 0.16s ease;
}

.listing-card:hover {
  box-shadow: var(--de-shadow-md);
  border-color: var(--de-color-border-strong);
}

/* 64px logo/monogram thumb (§5.12). Image variant: a cover-fit /media photo. Monogram variant
 * (.listing-card-mono): a brand-tint tile with the business initial in ink — never a broken image. */
.listing-card-logo {
  width: 64px;
  height: 64px;
  flex: none;
  overflow: hidden;
  border-radius: var(--de-radius);
}

.listing-card-logo .listing-card-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.listing-card-mono {
  display: grid;
  place-items: center;
  background: var(--de-color-brand-tint);
  color: var(--de-color-ink);
  font-family: var(--de-font-display);
  font-weight: 800;
  font-size: 1.5rem;
  line-height: 1;
}

/* Below 760px (spec §5.12 responsive): collapse to 52px thumb | details, and drop the CTA to a
 * full-width row underneath (location left, action right). */
@media (max-width: 47.5rem) {
  .listing-card {
    grid-template-columns: 52px minmax(0, 1fr);
    grid-template-areas:
      "logo body"
      "cta cta";
    align-items: start;
  }

  .listing-card > .listing-card-logo {
    grid-area: logo;
    width: 52px;
    height: 52px;
  }

  .listing-card-body {
    grid-area: body;
  }

  .listing-card > .listing-card-cta {
    grid-area: cta;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    text-align: left;
    margin-top: var(--de-space-1);
  }
}

/* The larger cover image on the listing detail page (epic-028): same 16:9 cover treatment, full
 * content width, between the rating line and the description. Absent = nothing rendered. */
.listing-detail-image {
  margin: var(--de-space-3) 0 var(--de-space-4);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--de-radius-lg);
  background: var(--de-color-surface-sunken, var(--de-color-surface));
}

.listing-detail-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

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

/* Design system exception: same reason as .listings-header h2 — h3 is a flex sibling of
 * the verified pill in this row, so the default bottom margin has to go. min-width:0 lets the
 * name shrink and wrap (instead of overflowing) in the narrow featured column and on mobile. */
.listing-card-head h3 {
  margin: 0;
  flex: 1;
  min-width: 0;
  font-weight: 700;
}

.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 details column. */
.listing-card-body {
  min-width: 0;
}

/* A 1–2 line muted description, clamped so a long blurb never blows out the row. */
.listing-card-desc {
  margin: var(--de-space-2) 0 0;
  color: var(--de-color-text-muted);
  font-size: var(--de-text-sm);
  line-height: 1.5;
  max-width: 52ch;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The right-aligned CTA column: location line + one action button (§5.12). */
.listing-card-cta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--de-space-2);
  text-align: right;
}

.listing-card-loc {
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
}

.listing-card-cta .button {
  white-space: nowrap;
}

/* The compact "★4.8 (52)" rating text used on the listing-detail line and the related-listings
 * chips (an inline span); the fractional card stars are handled by .pv-rate / .stars 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;
}

/* The provider-row rating line: fractional stars + bold numeric + muted review count (§5.9). */
.pv-rate {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--de-space-1) var(--de-space-2);
  margin-top: var(--de-space-2);
  font-size: var(--de-text-sm);
}

.pv-rate b {
  color: var(--de-color-ink);
  font-weight: 800;
}

.pv-rate .rev {
  color: var(--de-color-text-muted);
}

/* Fractional stars (§5.9), pure CSS: an empty-star base with a filled layer clipped to --pct. */
.stars {
  --pct: 0%;
  position: relative;
  display: inline-block;
  font-size: 0.95rem;
  letter-spacing: 1px;
  line-height: 1;
  vertical-align: middle;
}

.stars::before {
  content: "★★★★★";
  color: var(--de-color-star-empty);
}

.stars i {
  position: absolute;
  inset: 0;
  width: var(--pct);
  overflow: hidden;
  white-space: nowrap;
  color: var(--de-color-star);
}

.stars i::before {
  content: "★★★★★";
}

/* "✓ Verified owner" — the green Verified pill (spec §5.10), shown ONLY for listings with
 * an APPROVED owner claim (green is reserved for verification). */
.verified-owner-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25em;
  font-size: var(--de-text-xs);
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--de-color-success);
  background: var(--de-color-success-bg);
  border-radius: var(--de-radius-sm);
  padding: 0.25em 0.55em;
}

.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);
}

/* Home FAQ sits directly under the directory band with no spacing of its own (.city-faq only
 * sets width) — give it the same ruled-section separation as its .home-directory/.home-trust
 * siblings so it doesn't read as glued to the content above. */
.home-faq {
  margin-top: var(--de-space-7);
  padding-top: var(--de-space-6);
  border-top: var(--de-hairline);
}

.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 (the same device .home-guides used before it was
 * removed as dead CSS — zero Kotlin call sites — in the 2026-07 spacing audit). */
.related-guides {
  margin-top: var(--de-space-7);
}

.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);
}

/* Design system exception: the one oversized hero headline on the site — deliberately
 * more air under it (de-space-5) than the base default gives every other heading. */
.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;
}

/* Design system exception: h1 shares this row with the claimed badge, so it can't hold
 * the base default's bottom margin either. */
.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);
}

/* Design system exception: deliberately tighter than the base default for this compact
 * callout box. If a third callout needs the same tight heading, promote this to a shared
 * utility class instead of a third one-off (flagged in the 2026-07 spacing audit). */
.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);
}

/* Get your badge (F-048 / task-143): the embed section under the claim callout — the shared
 * ruled section head, the LIVE badge preview beside a labelled readonly snippet block, the
 * copy button (quiet secondary — the accent pill stays reserved for the claim CTA), and one
 * instruction line. Wraps to a single column on narrow screens. */
.badge-embed {
  margin-top: var(--de-space-7);
}

.badge-embed-lead {
  max-width: var(--de-measure);
  color: var(--de-color-text-muted);
  margin: 0 0 var(--de-space-4);
}

.badge-embed-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--de-space-5);
  align-items: flex-start;
}

.badge-embed-preview {
  flex-shrink: 0;
}

.badge-embed-code {
  flex: 1;
  min-width: min(100%, 20rem);
}

.badge-embed-code label {
  display: block;
  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);
  margin-bottom: var(--de-space-2);
}

.badge-embed-code textarea {
  width: 100%;
  font-family: var(--de-font-mono);
  font-size: var(--de-text-sm);
  line-height: 1.5;
  color: var(--de-color-text);
  background: var(--de-color-surface-alt);
  border: 1px solid var(--de-color-border-strong);
  border-radius: var(--de-radius-sm);
  padding: var(--de-space-3);
  resize: vertical;
}

.badge-embed-actions {
  display: flex;
  align-items: center;
  gap: var(--de-space-3);
  margin-top: var(--de-space-2);
}

.badge-copy-status {
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
}

.badge-embed-instruction {
  margin-top: var(--de-space-3);
  font-size: var(--de-text-sm);
  color: var(--de-color-text-muted);
  max-width: var(--de-measure);
}

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

/* Design system exception: tighter than the base default so the title pairs closely with
 * the dateline stamp directly under it. */
.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);
}

/* Design system exception (the ONLY place top margin is earned rather than zeroed): these
 * are mid-flow subsection breaks in long-form body copy, so they deliberately get MORE
 * space before the heading than after it — the standard typographic signal for "new
 * subsection starts here." h4/h5/h6 (deeper markdown headings) fall through to the base
 * default below and don't get this same top-margin ladder; extend it here if long-form
 * content ever nests that deep. */
.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 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;
}

/* :not(.de-media img) — the hero's img lives inside .article-body too, and this rule ties
 * with .de-media img on specificity; without the exclusion, this one (later in the file)
 * wins the cascade and overrides the hero's height:100%/object-fit:cover with height:auto,
 * shrinking the img and exposing the placeholder frame's background above it. */
.article-body img:not(.de-media 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: a <figure> wrapping the .de-media frame (16/9 + accent-tint placeholder, so an
 * absent photo is a tinted frame, never a broken img) and an optional credit caption. A hairline
 * below separates it from the body; the default <figure> margin is reset — spacing is owned here. */
.article-hero {
  width: 100%;
  margin: 0 0 var(--de-space-4);
  padding-bottom: var(--de-space-3);
  border-bottom: var(--de-hairline);
}

/* Photo credit (F-015): a quiet caption tight under the image — smaller, italic, and muted so
 * it reads as a byline, not body copy. --de-space-2 keeps it close to the frame above it. */
.article-figcaption {
  margin: var(--de-space-2) 0 0;
  font-size: var(--de-text-sm);
  font-style: italic;
  color: var(--de-color-text-muted);
}

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

  /* No thumb rendered (post has no photo) — drop the reserved thumb column so copy runs full width. */
  .blog-feed-item-noimg {
    grid-template-columns: 1fr;
  }
}

.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 {
  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 {
  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 {
  content: "";
  flex: 1;
  height: 0;
  border-top: var(--de-hairline);
}

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

/* ── 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 {
  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);
}

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

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

/* Design system exception: a genuinely different component (bordered divider heading),
 * not a spacing tweak — keep as-is. */
.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);
}

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

/* Design system exception: compact CTA card heading, no room for the base bottom margin. */
.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);
}

/* Design system exception: tighter than the base default, pairing closely with the intro
 * copy right under it. */
.claim-form > h1 {
  margin-bottom: var(--de-space-2);
}

/* BUG (2026-07 spacing audit): no margin set here, and there's no site-wide <p> default to
 * fall back on — this is the one paragraph on the whole site running on the raw browser
 * default instead of the de-space-3 rhythm every other paragraph uses. Fix by adding a
 * base `p` rule (see the note on the h1-h4 rule near the top of this file). */
.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,
.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,
  .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;
  }
}
