/* ==========================================================================
   design-v2.css — design layer for heyheath.com
   ==========================================================================

   PRESENTATION ONLY. This file is loaded AFTER style.css and changes nothing
   but colour, type, space, and motion. It adds no markup, removes no markup,
   and touches no template logic. Every JSON-LD node, canonical, hreflang
   alternate, and machine-readable endpoint is therefore untouched by
   construction, not by promise.

   Delete the one <link> tag that loads this file and the site is exactly as
   it was.

   The values below were measured on 21 August 2026 from 17 production
   high-end real estate sites and the commercial design system that 16 of
   them run on. Where a value is a direct measurement it is marked MEASURED.
   Where it is adapted for Heath's context it is marked ADAPTED, with the
   reason.

   Heath is not a luxury agent. He sells new construction at roughly a
   $414K average to first-time and repeat buyers. The goal here is to borrow
   the CRAFT of those sites (restraint, whitespace, type discipline, image
   treatment) without borrowing the POSITIONING.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Playfair Display at weight 400 and 500.

   The four Playfair woff2 files already on disk are VARIABLE fonts covering
   the whole 400-900 range, but style.css only declares them at 700, 800 and
   900. Google serves the identical files for weight 400, so declaring the
   lighter weights here costs zero additional bytes downloaded.

   This matters because the single clearest measured signal in the premium
   corpus is that display type runs at weight 300-400, never 700. Emphasis
   comes from size, not from thickness.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url(/fonts/playfair-nuFiD-vYSZviVYUb_rj3ij__anPXDTzYgA.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url(/fonts/playfair-nuFiD-vYSZviVYUb_rj3ij__anPXDTLYgFE_.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url(/fonts/playfair-nuFiD-vYSZviVYUb_rj3ij__anPXDTPYgFE_.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
                 U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
                 U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url(/fonts/playfair-nuFiD-vYSZviVYUb_rj3ij__anPXDTjYgFE_.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}


/* --------------------------------------------------------------------------
   2. Tokens.

   style.css themes the site by redefining the SAME custom property names
   under [data-theme="light"]. So redefining those names here re-skins every
   existing component with no per-component rules. That is the whole reason
   this file can be short.
   -------------------------------------------------------------------------- */

:root {
  /* ---- Motion. MEASURED: three easing curves carry the entire corpus. ---- */
  --ease-standard: cubic-bezier(0.645, 0.045, 0.355, 1);   /* 24 uses */
  --ease-out-soft: cubic-bezier(0.23, 1, 0.32, 1);         /* 18 uses */
  --ease-dramatic: cubic-bezier(0.835, 0, 0.19, 1);        /* 16 uses */
  --duration-fast: .2s;                                    /* 146 uses */
  --duration-base: .3s;                                    /* 163 uses */
  --transition: var(--duration-base) var(--ease-standard);

  /* ---- Type scale.
     ADAPTED. The measured system runs h1 up to 14.25rem, which suits pages
     carrying six words and a photograph. This is a content-dense site with a
     blog, city pages and a market-data page, so the upper bounds are pulled
     in hard. The WEIGHTS and LINE-HEIGHTS are kept at the measured values,
     because those are the part that reads as expensive.                    */
  --size-display: clamp(2.75rem, 6vw, 5rem);
  --size-h1:      clamp(2.25rem, 4.6vw, 3.75rem);
  --size-h2:      clamp(1.875rem, 3.4vw, 2.75rem);
  --size-h3:      clamp(1.375rem, 2.2vw, 1.875rem);
  --size-h4:      clamp(1.125rem, 1.4vw, 1.375rem);
  --size-eyebrow: .75rem;
  --size-body:    clamp(1rem, .55vw, 1.0625rem);

  --weight-display: 400;   /* MEASURED: 400, never 700 */
  --weight-heading: 400;
  --weight-body:    400;

  --leading-display: 1.06; /* MEASURED: 1.03 to 1.17 across h1-h3 */
  --leading-heading: 1.18;
  --leading-body:    1.65; /* ADAPTED up from the measured 1.38. That figure
                              suits pages with two sentences on them. This
                              site has long-form guides, and the measured
                              point is that the GAP between display and body
                              leading should be wide, which 1.06/1.65 keeps. */

  --track-eyebrow: .18em;  /* MEASURED: 1.5px-3px on small uppercase labels */
  --track-display: 0;      /* MEASURED: heading tokens set letter-spacing:normal */

  /* ---- Space. MEASURED shape, ADAPTED magnitude.
     The corpus uses a 15rem (240px) extra-large gap. That is tuned for a
     page with six sections. This site's homepage has fifteen, so the
     rhythm is set to a still-generous but survivable scale.               */
  --gap-xs: .5rem;
  --gap-sm: 1rem;
  --gap-md: 1.5rem;
  --gap-lg: 3.875rem;                            /* MEASURED verbatim */
  --gap-xl: clamp(3rem, 6vw, 7.5rem);

  --section-y:    clamp(3.5rem, 7vw, 7rem);
  --section-y-lg: clamp(4.5rem, 9vw, 9.5rem);

  --container:    1200px;                        /* unchanged from style.css */
  --container-wide: 1440px;                      /* MEASURED: --lp-container-large */
  --gutter: clamp(1.25rem, 4vw, 2.5rem);

  --radius: 2px;      /* ADAPTED down from 4px. The corpus is near-square. */
  --radius-lg: 3px;

  /* ---- Colour, default (light) theme.
     MEASURED: grounds are bone, not white (#f1f0eb, #eeede9, #fbfbf9,
     #e4dfd8). Ink is charcoal, not black (#1a1a1a, #403e3f). One accent.  */
  --ground:        #f4f2ed;
  --ground-raised: #fbfaf7;
  --ground-sunken: #e8e5dd;
  --rule-soft:     #e0dcd2;

  /* These are the names style.css already consumes. Redefining them re-skins
     every existing component without a single per-component override. */
  --black:      #f4f2ed;   /* page ground */
  --dark:       #efece5;   /* alternating band */
  --dark-2:     #e8e5dd;   /* card ground */
  --dark-3:     #ded9cd;   /* raised chrome */
  --white:      #1c1b19;   /* primary ink (name is legacy, role is ink) */
  --off-white:  #fbfaf7;
  --text:       #2e2c29;
  --text-muted: #696660;
  --border:     #ddd8cc;

  /* One accent, three shades. MEASURED family: old gold / clay.
     Heath's existing brand gold was #c9a94e; these keep that identity while
     landing in the muted register the corpus uses.
     --gold is used for TEXT on the bone ground, so it is darkened to clear
     WCAG AA. --gold-deco is the decorative shade for rules and hairlines,
     which carry no text and are exempt from the text contrast rule. */
  --gold:       #6f5719;   /* text-safe on bone */
  --gold-light: #5a4614;   /* text-safe, stronger */
  --gold-deco:  #9f7e3a;   /* rules, borders, non-text marks only */
  --gold-wash:  rgba(179, 146, 78, .14);
}

/* ---- Dark theme, refined rather than removed.
   MEASURED: the corpus inks are #1a1a1a / #141414 / #403e3f, never pure
   black. Heath's previous dark was #0a0a0a, which is colder and flatter
   than anything in the corpus. This warms it and keeps his gold. */
[data-theme="dark"] {
  --ground:        #14130f;
  --ground-raised: #1c1a15;
  --ground-sunken: #100f0c;
  --rule-soft:     #302c22;

  --black:      #14130f;
  --dark:       #1a1813;
  --dark-2:     #211e18;
  --dark-3:     #2a261e;
  --white:      #f4f2ed;
  --off-white:  #faf8f3;
  --text:       #e6e2d9;
  --text-muted: #a09b8e;
  --border:     #332f26;

  --gold:       #cdae63;   /* text-safe on warm charcoal */
  --gold-light: #ddc384;
  --gold-deco:  #b3924e;
  --gold-wash:  rgba(205, 174, 99, .12);
}

/* The theme init script only sets data-theme when there is a saved
   preference or when the OS prefers LIGHT. A visitor whose OS prefers dark
   and who has never toggled therefore lands with no attribute at all, which
   would fall through to the bone :root above. This honours that preference.
   Guarded on :not([data-theme="light"]) so an explicit light choice still
   wins over the OS. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:        #14130f;
    --ground-raised: #1c1a15;
    --ground-sunken: #100f0c;
    --rule-soft:     #302c22;

    --black:      #14130f;
    --dark:       #1a1813;
    --dark-2:     #211e18;
    --dark-3:     #2a261e;
    --white:      #f4f2ed;
    --off-white:  #faf8f3;
    --text:       #e6e2d9;
    --text-muted: #a09b8e;
    --border:     #332f26;

    --gold:       #cdae63;
    --gold-light: #ddc384;
    --gold-deco:  #b3924e;
    --gold-wash:  rgba(205, 174, 99, .12);
  }
}

/* The site ships an explicit [data-theme="light"] palette from style.css.
   Point it at the same bone tokens so a saved 'light' preference and the
   new default render identically. */
[data-theme="light"] {
  --black:      #f4f2ed;
  --dark:       #efece5;
  --dark-2:     #e8e5dd;
  --dark-3:     #ded9cd;
  --white:      #1c1b19;
  --off-white:  #fbfaf7;
  --text:       #2e2c29;
  --text-muted: #696660;
  --border:     #ddd8cc;
  --gold:       #6f5719;
  --gold-light: #5a4614;
  --gold-deco:  #9f7e3a;
  --gold-wash:  rgba(179, 146, 78, .14);
}


/* --------------------------------------------------------------------------
   3. Type.
   -------------------------------------------------------------------------- */

body {
  font-size: var(--size-body);
  line-height: var(--leading-body);
  font-weight: var(--weight-body);
  background: var(--black);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6,
.hero h1, .hero-title, .section-title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  letter-spacing: var(--track-display);
  color: var(--white);
  text-wrap: balance;              /* stops a one-word orphan on line two */
}

h1, .hero h1 { font-size: var(--size-h1); line-height: var(--leading-display); }
h2           { font-size: var(--size-h2); line-height: var(--leading-display); }
h3           { font-size: var(--size-h3); line-height: var(--leading-heading); }
h4, h5, h6   { font-size: var(--size-h4); line-height: var(--leading-heading); }

/* Prose measure. MEASURED behaviour rather than a measured number: every
   site in the corpus constrains running text well below the container width.
   65ch is the readability convention, and it is a large improvement on
   full-width paragraphs at 1200px. */
.section p,
.prose p,
article p {
  max-width: 68ch;
  text-wrap: pretty;
}

/* Eyebrows. MEASURED: this is the ONE place the corpus tracks type out.
   Display type is left at normal tracking. */
.section-label,
.eyebrow,
.section > .container > .label {
  font-family: var(--font-body);
  font-size: var(--size-eyebrow);
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--gold);
}


/* --------------------------------------------------------------------------
   4. Rhythm.
   -------------------------------------------------------------------------- */

.section {
  padding-top: var(--section-y);
  padding-bottom: var(--section-y);
}

/* MEASURED: the corpus does not alternate band colours every section. It
   holds one ground and lets whitespace do the separating. Dropping the
   every-other-section flip is most of what makes this read calmer. */
.section-dark {
  background: var(--dark);
}
.section-dark + .section-dark {
  padding-top: 0;
  background: var(--dark);
}

.container {
  max-width: var(--container);
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* A hairline instead of a colour change, for sections that need a break
   without a band. MEASURED: the corpus separates with rules and space. */
.section + .section:not(.section-dark) {
  border-top: 1px solid var(--rule-soft);
}


/* --------------------------------------------------------------------------
   5. Hero.
   -------------------------------------------------------------------------- */

.hero {
  min-height: clamp(520px, 78vh, 760px);   /* MEASURED shape: clamp(400px,70vh,600px) */
}

.hero h1 {
  font-size: var(--size-display);
  line-height: var(--leading-display);
  font-weight: var(--weight-display);
}

/* The hero type in this markup is CENTRED, not bottom-anchored, so the scrim
   has to be strongest through the middle band rather than weighted to one
   edge. Getting this wrong puts white type on the palest part of the photo.

   style.css already ships a light-theme hero (white scrim, dark title). This
   keeps that logic and warms it: the scrim is bone rather than pure white so
   it matches the page ground, and the photograph is allowed to actually show
   through rather than sitting at 30% opacity behind a wash. */
.hero-bg img { opacity: .88; }

.hero-overlay {
  background: linear-gradient(
    180deg,
    rgba(244, 242, 237, .52) 0%,
    rgba(244, 242, 237, .80) 38%,
    rgba(244, 242, 237, .86) 68%,
    rgba(244, 242, 237, .96) 100%
  );
}

.hero-title,
.hero .hero-content h1 { color: var(--white); }
.hero-subtitle          { color: var(--text); }
.hero-eyebrow           { color: var(--gold); }

/* Dark theme inverts the whole treatment. Two selectors because the theme
   can arrive either as an explicit attribute or as an OS preference with no
   attribute set at all. */
[data-theme="dark"] .hero-bg img { opacity: .55; }
[data-theme="dark"] .hero-overlay {
  background: linear-gradient(
    180deg,
    rgba(12, 11, 9, .42) 0%,
    rgba(12, 11, 9, .70) 40%,
    rgba(12, 11, 9, .80) 70%,
    rgba(12, 11, 9, .92) 100%
  );
}
[data-theme="dark"] .hero-title,
[data-theme="dark"] .hero .hero-content h1 { color: #f7f5f0; }
[data-theme="dark"] .hero-subtitle { color: #ded9cd; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero-bg img { opacity: .55; }
  :root:not([data-theme="light"]) .hero-overlay {
    background: linear-gradient(
      180deg,
      rgba(12, 11, 9, .42) 0%,
      rgba(12, 11, 9, .70) 40%,
      rgba(12, 11, 9, .80) 70%,
      rgba(12, 11, 9, .92) 100%
    );
  }
  :root:not([data-theme="light"]) .hero-title,
  :root:not([data-theme="light"]) .hero .hero-content h1 { color: #f7f5f0; }
  :root:not([data-theme="light"]) .hero-subtitle { color: #ded9cd; }
}


/* --------------------------------------------------------------------------
   6. Imagery.

   MEASURED: the corpus shows FEW images LARGE, at a consistent ratio, with a
   slow scale on hover. Heath's LGI model photography is 1600x1067, which is
   3:2, so the cards are set to 3:2 rather than the arbitrary heights the
   current cards use. Matching the card ratio to the source ratio removes
   every crop decision.
   -------------------------------------------------------------------------- */

.card img,
.city-card img,
.community-card img,
.plan-card img,
.blog-card img {
  aspect-ratio: 3 / 2;
  object-fit: cover;
  width: 100%;
  height: auto;
  display: block;
  background: var(--dark-2);       /* prevents a white flash before decode */
}

.card,
.city-card,
.community-card,
.blog-card {
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--dark-2);
  border: 1px solid var(--border);
  box-shadow: none;                /* MEASURED: the corpus does not drop-shadow cards */
}

/* Portrait block. Heath's headshot on disk is only 200x200, so it must NOT
   be scaled into a large portrait slot. Capping it keeps it sharp and stops
   the layout writing a cheque the asset cannot cash. */
.headshot,
.about-photo img,
img[src*="heath-headshot"] {
  max-width: 200px;
  height: auto;
  border-radius: 50%;
  border: 1px solid var(--gold-deco);
}


/* --------------------------------------------------------------------------
   6b. Inherited dark-ground assumptions.

   A few components stripe or tint themselves with a hardcoded
   rgba(0,0,0,0.18) rather than a theme token. At 18% black that is a barely
   visible sheen on a near-black ground and a heavy grey band on bone. These
   are inline styles in the template, so !important is the only lever, and
   overriding here is preferable to editing the markup (which would put a
   presentation change into a file that also emits structured data).
   -------------------------------------------------------------------------- */

.lgi-discount-board tbody tr,
.lgi-discount-compact tbody tr {
  background: transparent !important;
}
.lgi-discount-board tbody tr:nth-child(even),
.lgi-discount-compact tbody tr:nth-child(even) {
  background: var(--gold-wash) !important;
}
.lgi-discount-board tbody tr td,
.lgi-discount-compact tbody tr td {
  padding-top: .7rem;
  padding-bottom: .7rem;
}

/* Restore the intended subtlety in dark mode, where the original value was
   actually correct. */
[data-theme="dark"] .lgi-discount-board tbody tr:nth-child(even),
[data-theme="dark"] .lgi-discount-compact tbody tr:nth-child(even) {
  background: rgba(255, 255, 255, .04) !important;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lgi-discount-board tbody tr:nth-child(even),
  :root:not([data-theme="light"]) .lgi-discount-compact tbody tr:nth-child(even) {
    background: rgba(255, 255, 255, .04) !important;
  }
}


/* --------------------------------------------------------------------------
   7. Motion. Three curves, two durations, and an off switch.
   -------------------------------------------------------------------------- */

a, button, .btn, .card, .city-card, .community-card, .blog-card, img {
  transition:
    color var(--duration-base) var(--ease-standard),
    background-color var(--duration-base) var(--ease-standard),
    border-color var(--duration-base) var(--ease-standard),
    transform var(--duration-base) var(--ease-out-soft),
    opacity var(--duration-fast) var(--ease-standard);
}

/* MEASURED: `(hover: hover) and (pointer: fine)` appears 47 times in the
   corpus. Ungated hover styles stick after a tap on touch devices. */
@media (hover: hover) and (pointer: fine) {
  .card:hover,
  .city-card:hover,
  .community-card:hover,
  .blog-card:hover {
    border-color: var(--gold-deco);
    transform: translateY(-2px);
  }
  .card:hover img,
  .city-card:hover img,
  .community-card:hover img,
  .blog-card:hover img {
    transform: scale(1.03);
  }
  .card, .city-card, .community-card, .blog-card { will-change: transform; }
}

/* Card images need their own containing block for the scale to clip. */
.card > a, .city-card > a, .community-card > a, .blog-card > a { display: block; overflow: hidden; }

/* The corpus is not uniformly good about this. Be better than the reference. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .card:hover img, .city-card:hover img,
  .community-card:hover img, .blog-card:hover img { transform: none !important; }
}


/* --------------------------------------------------------------------------
   8. Controls.
   -------------------------------------------------------------------------- */

.btn,
.nav-cta,
button.btn {
  font-family: var(--font-body);
  font-size: .8125rem;
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  border-radius: var(--radius);
  padding: 1rem 2rem;
  min-height: 48px;                /* tap target */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Visible focus everywhere, in both themes. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: var(--radius);
}


/* --------------------------------------------------------------------------
   9. Header.

   MEASURED: the corpus header is a thin, quiet bar. Heath's is a three-level
   dropdown nav, which is genuinely more useful for a site with 106 pages, so
   it stays. What changes is weight and tracking.
   -------------------------------------------------------------------------- */

.site-header {
  background: color-mix(in srgb, var(--black) 92%, transparent);
  -webkit-backdrop-filter: saturate(1.6) blur(10px);
  backdrop-filter: saturate(1.6) blur(10px);
  border-bottom: 1px solid var(--rule-soft);
}

.nav-logo {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: .04em;
  color: var(--white);
}
.nav-logo span { color: var(--gold); }

.nav-links a {
  font-family: var(--font-body);
  font-size: .8125rem;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
}


/* --------------------------------------------------------------------------
   10. Footer.

   TRAP, carried over from the existing codebase: style.css forces the footer
   dark in BOTH themes with [data-theme=light] .site-footer{background:#1a1a1a}.
   So anything inside the footer must use FIXED colours, never theme tokens,
   or it washes out in day mode. That is why the values below are literals.
   -------------------------------------------------------------------------- */

.site-footer {
  background: #14130f;
  border-top: 1px solid #2a261e;
  color: #cfc9bb;
}
.site-footer h3, .site-footer h4, .site-footer .footer-heading {
  font-family: var(--font-display);
  font-weight: 400;
  color: #f4f2ed;
}
.site-footer a { color: #cfc9bb; }
@media (hover: hover) and (pointer: fine) {
  .site-footer a:hover { color: #cdae63; }
}


/* --------------------------------------------------------------------------
   11. Mobile.

   Mobile is the majority of realtor traffic, so this is not "the desktop
   design, smaller". These are the things that are DIFFERENT.
   -------------------------------------------------------------------------- */

@media (max-width: 768px) {
  :root {
    /* Whitespace that reads as generous at 1440px reads as a broken page at
       390px. The clamps already collapse; this pulls them further. */
    --section-y:    clamp(2.5rem, 9vw, 3.5rem);
    --section-y-lg: clamp(3rem, 11vw, 4.5rem);
    --leading-body: 1.6;
  }

  /* Full-bleed the hero image and shorten it. A 78vh hero on a phone pushes
     every piece of content below the fold. */
  .hero { min-height: 64vh; }

  /* One column, and let the images stay large rather than shrinking to
     thumbnails. MEASURED principle: few and large beats many and small. */
  .card img, .city-card img, .community-card img, .blog-card img {
    aspect-ratio: 16 / 10;
  }

  /* Prose measure is a desktop concern; on a phone the viewport is the
     measure and the max-width just adds an awkward ragged edge. */
  .section p, .prose p, article p { max-width: none; }

  /* Tap targets. */
  .nav-links a, .btn, .nav-cta { min-height: 48px; }

  /* Nothing may scroll the page sideways. */
  html, body { overflow-x: hidden; }
  img, video, table, pre { max-width: 100%; }
}

@media (max-width: 480px) {
  :root {
    --size-display: clamp(2rem, 9vw, 2.5rem);
    --size-h1:      clamp(1.75rem, 8vw, 2.125rem);
    --size-h2:      clamp(1.5rem, 6.5vw, 1.875rem);
    --gutter: 1.125rem;
  }
}


/* --------------------------------------------------------------------------
   12. Print. Cheap to get right and occasionally the thing a buyer actually
   hands to a lender.
   -------------------------------------------------------------------------- */

@media print {
  .site-header, .site-footer, .nav-toggle, .cta-band, #cookie-consent { display: none !important; }
  body { background: #fff; color: #000; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; }
}


/* ==========================================================================
   13. Interaction and reveal motifs.

   These are the three patterns the premium corpus actually uses to feel
   alive, extracted from the saved production CSS:

   1. A "wiping" underline. Built identically on several of the sites: a
      full-width hairline sits under a zero-width bar that grows to 100% on
      hover. It reads more considered than a plain text-decoration because
      the line has direction.
   2. Reveal-on-scroll, fade plus a short upward translate, staggered across
      a grid. Every site in the corpus does this; most pay for a JS library
      to get it. It is doable in pure CSS now.
   3. A slow scale-down on the hero image so the photograph settles rather
      than snapping into place.

   All three are motion, so all three are off under prefers-reduced-motion.
   ========================================================================== */

/* ---- 1. Wiping underline ------------------------------------------------ */

.nav-links > li > a:not(.nav-cta),
.prose a:not(.btn),
.section p a:not(.btn),
article p a:not(.btn) {
  position: relative;
  text-decoration: none;
}

.nav-links > li > a:not(.nav-cta)::after,
.prose a:not(.btn)::after,
.section p a:not(.btn)::after,
article p a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -.18em;
  height: 1px;
  width: 0;
  background: currentColor;
  transition: width var(--duration-base) var(--ease-standard);
}

/* Prose links keep a resting hairline so they are still visibly links
   without hover. Nav links get the line only on hover, which is what keeps
   a header quiet. */
.prose a:not(.btn),
.section p a:not(.btn),
article p a:not(.btn) {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: 0 calc(100% - .02em);
  color: var(--gold);
}

@media (hover: hover) and (pointer: fine) {
  .nav-links > li > a:not(.nav-cta):hover::after,
  .nav-links > li > a[aria-current="page"]::after,
  .prose a:not(.btn):hover::after,
  .section p a:not(.btn):hover::after,
  article p a:not(.btn):hover::after {
    width: 100%;
  }
  .prose a:not(.btn):hover,
  .section p a:not(.btn):hover,
  article p a:not(.btn):hover {
    background-size: 0% 1px;          /* resting line wipes out as the new one wipes in */
    transition: background-size var(--duration-base) var(--ease-standard);
  }
}

/* ---- 2. Reveal on scroll ------------------------------------------------
   Scroll-driven CSS animation, no JavaScript and no library.

   The @supports guard matters: opacity is only ever set to 0 inside a block
   that the browser has confirmed it can animate on a view() timeline. A
   browser without support (Firefox at time of writing) never sees the
   opacity:0 rule at all and renders everything normally. A reveal that can
   strand content invisible is worse than no reveal.                        */

@keyframes reveal-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    .section > .container > h2,
    .section > .container > .section-label,
    .section > .container > .section-title,
    .card, .city-card, .community-card, .blog-card, .plan-card,
    .stat, .stat-item {
      animation: reveal-up linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }

    /* Diagonal stagger across a three-up grid: each column starts its reveal
       slightly later in the scroll range than the one before it. */
    .card:nth-child(3n+2), .city-card:nth-child(3n+2),
    .community-card:nth-child(3n+2), .blog-card:nth-child(3n+2),
    .plan-card:nth-child(3n+2) { animation-range: entry 6% entry 61%; }

    .card:nth-child(3n+3), .city-card:nth-child(3n+3),
    .community-card:nth-child(3n+3), .blog-card:nth-child(3n+3),
    .plan-card:nth-child(3n+3) { animation-range: entry 12% entry 67%; }
  }
}

/* ---- 3. Hero settle ----------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .hero-bg img {
    animation: hero-settle 1.8s var(--ease-out-soft) both;
  }
}
@keyframes hero-settle {
  from { transform: scale(1.05); }
  to   { transform: none; }
}


/* ==========================================================================
   14. Editorial details.
   ========================================================================== */

/* Pull-quotes and testimonials. The corpus sets an oversized display-face
   quote mark and hangs it outside the text column. */
blockquote {
  position: relative;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--size-h3);
  line-height: var(--leading-heading);
  color: var(--white);
  border-left: 0;
  padding-left: 0;
  margin-block: var(--gap-lg);
  max-width: 34ch;
  text-wrap: balance;
}
blockquote::before {
  content: "\201C";
  position: absolute;
  left: -.5em;
  top: -.28em;
  font-family: var(--font-display);
  font-size: 3.2em;
  line-height: 1;
  color: var(--gold-deco);
  opacity: .5;
  pointer-events: none;
}
blockquote cite,
blockquote footer {
  display: block;
  margin-top: var(--gap-md);
  font-family: var(--font-body);
  font-size: .8125rem;
  font-style: normal;
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Numerals. Playfair has genuine oldstyle-capable figures; tabular alignment
   keeps a stat row from jittering and lining figures read as more deliberate
   at display size. */
.stat-value, .stat-number, .by-the-numbers .value {
  font-family: var(--font-display);
  font-weight: 400;
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: -.01em;
}

/* Hairline rules instead of boxes, wherever the markup already draws one. */
hr {
  border: 0;
  border-top: 1px solid var(--rule-soft);
  margin-block: var(--section-y);
}

/* Selection colour picked from the accent rather than the browser default. */
::selection { background: var(--gold-wash); color: var(--white); }


/* ==========================================================================
   15. Inverted surface: closing CTA band + footer.

   The corpus consistently ends a light page with ONE dark full-bleed moment
   before the footer, rather than a saturated brand-colour band. The existing
   CTA band is #9a7b2e, a fairly olive gold, which on a bone page reads muddy
   rather than considered. Making the CTA band and the footer a single
   continuous dark surface gives the page a proper close.

   These selectors carry [data-theme] because style.css sets its footer and
   CTA colours at that specificity; a bare class selector loses to it. That is
   why the earlier plain `.site-footer` rule in section 10 had no effect.
   ========================================================================== */

:root {
  --inv-surface:   #17150f;
  --inv-surface-2: #201d16;
  --inv-ink:       #efeae0;   /* 15.2:1 on --inv-surface */
  --inv-ink-2:     #c0b9ab;   /*  9.4:1 */
  --inv-ink-3:     #948c7e;   /*  5.5:1 — floor for legal text */
  --inv-rule:      #322e26;
  --inv-brand:     #c9a94e;   /*  8.1:1 — Heath's original brand gold, kept */
}

.cta-band,
[data-theme="light"] .cta-band,
[data-theme="dark"] .cta-band {
  background: var(--inv-surface);
  padding-top: var(--section-y);
  padding-bottom: var(--section-y);
}
.cta-band h2,
[data-theme="light"] .cta-band h2 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: var(--leading-display);
  color: var(--inv-ink);
}
.cta-band p,
[data-theme="light"] .cta-band p { color: var(--inv-ink-2); }

.cta-band .btn-primary,
[data-theme="light"] .cta-band .btn-primary {
  background: var(--inv-brand);
  color: #14130f;                 /* 8.2:1 */
  border-color: var(--inv-brand);
}
@media (hover: hover) and (pointer: fine) {
  .cta-band .btn-primary:hover,
  [data-theme="light"] .cta-band .btn-primary:hover {
    background: #ddc384;
    color: #14130f;
    border-color: #ddc384;
  }
}

/* Footer, matched to the band so the two read as one surface. */
.site-footer,
[data-theme="light"] .site-footer,
[data-theme="dark"] .site-footer {
  background: var(--inv-surface);
  border-top: 1px solid var(--inv-rule);
  color: var(--inv-ink-2);
}
[data-theme="light"] .site-footer .footer-logo,
.site-footer .footer-logo { color: var(--inv-ink); font-family: var(--font-display); font-weight: 400; }
[data-theme="light"] .site-footer .footer-col h3,
.site-footer .footer-col h3 {
  font-family: var(--font-body);
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--inv-brand);
}
[data-theme="light"] .site-footer .footer-col p,
[data-theme="light"] .site-footer .footer-col a,
.site-footer .footer-col p,
.site-footer .footer-col a { color: var(--inv-ink-2); }
[data-theme="light"] .site-footer .footer-bottom p,
.site-footer .footer-bottom p { color: var(--inv-ink-3); }   /* was #777 = 4.0:1, now 5.5:1 */


/* ==========================================================================
   16. Accessibility fixes the redesign exposed.
   ========================================================================== */

/* WCAG 1.4.11 requires non-text UI components, which includes input borders,
   to reach 3:1 against their background. The inherited border token resolves
   to #ddd8cc on the bone ground, which measures 1.27:1. An input whose edge
   you cannot see is a real problem on a site whose primary conversion is a
   contact form. */
input, select, textarea {
  border-color: #8e8576;          /* 3.25:1 on --paper, 3.0:1 on the card ground */
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--gold);
}
[data-theme="dark"] input,
[data-theme="dark"] select,
[data-theme="dark"] textarea { border-color: #6a6459; }

/* Status colour for builder price cuts. The inherited green was chosen for a
   near-black ground and drops to roughly 4:1 on the bone card. These clear
   4.5:1 on every ground they actually appear on. */
:root { --pos: #267449; }          /* 5.1:1 on ground, 4.5:1 on card */
[data-theme="dark"] { --pos: #4caf6a; }  /* 6.1:1 on the dark card */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --pos: #4caf6a; }
}


/* ==========================================================================
   17. Rhythm normalisation.

   Three sections on the homepage carry inline `padding:48px 20px` or
   `padding:72px 32px 16px`, which predate the spacing scale and break the
   vertical rhythm against the 100px sections around them. Inline styles, so
   !important is the only lever available without editing the template.
   ========================================================================== */

@media (min-width: 769px) {
  main > section[style*="padding:48px"],
  main > section[style*="padding: 48px"] {
    padding-top: var(--section-y) !important;
    padding-bottom: var(--section-y) !important;
  }
  main > section[style*="padding:72px 32px 16px"] {
    padding-top: var(--section-y) !important;
    padding-bottom: var(--space-md, 1.5rem) !important;
  }
}

/* The bare element selector in section 16 is outranked: style.css sets these
   borders via `.form-group select` and `[data-theme="light"] .form-group
   select`, both more specific. Matching that specificity here.

   --border stays where it is on purpose. It is doing a second job as a card
   and divider hairline, where a 3:1 edge would read as heavy boxing. Inputs
   get their own token instead. */
:root { --border-input: #8e8576; }
[data-theme="dark"] { --border-input: #6a6459; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --border-input: #6a6459; }
}

.form-group input,
.form-group select,
.form-group textarea,
.newsletter-form input[type="email"],
[data-theme="light"] .form-group input,
[data-theme="light"] .form-group select,
[data-theme="light"] .form-group textarea,
[data-theme="light"] .newsletter-form input[type="email"] {
  border-color: var(--border-input);
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.newsletter-form input[type="email"]:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--gold-wash);
}


/* ==========================================================================
   18. Corrections and refinements, round 3.
   ========================================================================== */

/* ---- 18a. Undo a self-inflicted readability regression -------------------
   Section 14 gave every <blockquote> the display-serif pull-quote treatment.
   That is right for a one-line pull-quote and wrong for a Google review: the
   review cards on this page carry blockquotes up to ~760 characters, and they
   were rendering at 30px Playfair, which is close to unreadable.

   Pull-quote styling is for pull-quotes. A testimonial is body copy with an
   attribution.                                                            */

.testimonial-card blockquote,
.review-card blockquote,
.google-review blockquote {
  font-family: var(--font-body);
  font-size: .9375rem;
  font-weight: 400;
  line-height: 1.7;
  color: var(--text);
  max-width: none;
  margin-block: 0;
  text-wrap: initial;
}
.testimonial-card blockquote::before,
.review-card blockquote::before,
.google-review blockquote::before {
  content: none;                  /* the cards already draw their own mark */
}

/* ---- 18b. Decorative emoji ----------------------------------------------
   Seven colour emoji ship as numeric HTML entities. Full-colour pictographs
   are the loudest thing on a bone-and-charcoal page and none of the premium
   corpus uses them.

   The credential band is a typographic list whose labels ("US ARMY",
   "LGI HOMES") already carry the meaning, so the marks come out entirely and
   the letterspaced type stands on its own. That is what the corpus does with
   a credential row.

   The three service cards keep an anchor mark, because a 3-up row reads
   better with one, but desaturated and sized down so it sits in the palette
   instead of fighting it. :has() lets this skip the sibling cards whose
   .feature-icon already contains a proper stroked SVG.                    */

.credibility-band div[style*="font-size:2rem"],
section div[style*="font-size:2rem"]:not(:has(svg)) {
  display: none;
}

.feature-icon:not(:has(svg)) {
  font-size: 1.5rem !important;
  filter: grayscale(1) contrast(.9);
  opacity: .45;
  display: block;
  margin-bottom: var(--gap-sm);
}

/* The credential row loses its icons, so it needs its own rhythm back. */
section div[style*="display:flex"][style*="gap:48px"] > div[style*="text-align:center"] {
  padding-inline: var(--gap-sm);
  border-left: 1px solid var(--rule-soft);
}
section div[style*="display:flex"][style*="gap:48px"] > div[style*="text-align:center"]:first-child {
  border-left: 0;
}

/* ---- 18c. Chips ---------------------------------------------------------
   The service-area chips are pure #fff, which on a bone ground reads as a
   brighter hole rather than a raised surface. Match them to the palette and
   square them off in line with everything else.                           */

.area-chip, .city-chip,
[data-theme="light"] .area-chip, [data-theme="light"] .city-chip {
  background: var(--ground-raised);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
}
@media (hover: hover) and (pointer: fine) {
  .area-chip:hover, .city-chip:hover { border-color: var(--gold-deco); }
}

/* ---- 18d. Portrait ------------------------------------------------------
   A circular crop with a gold ring is a 2010s realtor convention. The corpus
   uses a plain rectangular portrait and lets the photograph be a photograph.
   The asset is only 200x200, so it stays small either way.                */

.headshot,
.about-photo img,
img[src*="heath-headshot"] {
  border-radius: var(--radius);
  border: 1px solid var(--rule-soft);
  box-shadow: none;
}

/* ---- 18e. Details/summary -----------------------------------------------
   FAQ rows and the "expand for more photos" toggles. Give the marker a
   consistent treatment and a real tap target.                             */

details > summary {
  cursor: pointer;
  padding-block: var(--gap-sm);
  min-height: 44px;
  font-weight: 500;
}
details > summary::marker { color: var(--gold-deco); }
details[open] > summary { border-bottom: 1px solid var(--rule-soft); }


/* ==========================================================================
   19. Surface cohesion.

   style.css sets a number of light-theme surfaces to pure #fff. Against the
   original near-white ground that was invisible; against a bone ground a
   pure-white card reads as a hole punched in the page rather than a surface
   sitting on it. The corpus never does this: its cards are the same warm
   family as the ground, one step brighter.

   These selectors carry the [data-theme="light"] prefix because that is the
   specificity style.css set them at.
   ========================================================================== */

[data-theme="light"] .feature-card,
[data-theme="light"] .testimonial-card,
[data-theme="light"] .blog-card,
[data-theme="light"] .linkedin-placeholder,
[data-theme="light"] .linkedin-embed-wrapper,
[data-theme="light"] .share-btn,
[data-theme="light"] .nav-sub,
.feature-card, .testimonial-card, .blog-card {
  background: var(--ground-raised);
  border-color: var(--rule-soft);
}

/* Inputs stay the brightest surface on the page so a form field still reads
   as the thing you type into, but warm rather than clinical. */
[data-theme="light"] .form-group input,
[data-theme="light"] .form-group select,
[data-theme="light"] .form-group textarea,
[data-theme="light"] .newsletter-form input[type="email"] {
  background: #fdfcfa;
  color: var(--white);
}

/* The dropdown panel needs a little separation from the header behind it. */
[data-theme="light"] .nav-sub {
  box-shadow: 0 10px 30px rgb(28 27 25 / .10);
  border-radius: var(--radius);
}


/* ==========================================================================
   20. Two small things the section-18 rules got slightly wrong.
   ========================================================================== */

/* The credential row wraps onto a second line, and :first-child only clears
   the divider on the very first item, so the first item of the second row
   kept a leading hairline. There is no ":first-in-row" selector, so the
   divider moves to the RIGHT edge and is cleared on the last item instead,
   which wraps correctly. */
section div[style*="display:flex"][style*="gap:48px"] > div[style*="text-align:center"] {
  border-left: 0;
  border-right: 1px solid var(--rule-soft);
}
section div[style*="display:flex"][style*="gap:48px"] > div[style*="text-align:center"]:last-child {
  border-right: 0;
}

/* Give the wrapped credential row a consistent vertical gap. */
section div[style*="display:flex"][style*="gap:48px"] {
  row-gap: var(--gap-md) !important;
}


/* ==========================================================================
   21. Interior pages.

   The homepage got the light display treatment early, but ~100 of the 106
   pages are blog posts, city guides and utility pages rendered through
   `.page-content`, and those kept weight 800 at 48px. Put a post next to the
   homepage and they read as two different sites.

   `.page-content h1` is specificity (0,1,1), which outranks the bare element
   selectors in section 3 and the (0,1,0) class selectors alongside them.
   That is why nothing so far had reached them.
   ========================================================================== */

.page-content h1,
.page-content h2,
.page-content h3,
.bio-text h2,
article h1,
article h2,
article h3,
.a11y-dialog-header h2 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  letter-spacing: var(--track-display);
  color: var(--white);
  text-wrap: balance;
}

.page-content h1,
article h1 {
  font-size: var(--size-h1);
  line-height: var(--leading-display);
  margin-bottom: var(--gap-md);
}

.page-content h2,
article h2,
.bio-text h2 {
  font-size: var(--size-h2);
  line-height: var(--leading-display);
  margin-top: var(--gap-xl);
  margin-bottom: var(--gap-sm);
}

.page-content h3,
article h3 {
  font-size: var(--size-h3);
  line-height: var(--leading-heading);
  margin-top: var(--gap-lg);
  margin-bottom: var(--gap-xs);
}

/* Prose measure. The post template constrains to ~720px, but the city pages
   and the blog index let paragraphs run the full container, which is well
   past a comfortable line length at 1440px. */
.page-content p,
.page-content li,
article p,
article li {
  max-width: 68ch;
  font-size: var(--size-body);
  line-height: var(--leading-body);
  text-wrap: pretty;
}

/* A lede: the first paragraph after an h1 carries slightly more weight, the
   way the corpus opens an editorial page. */
.page-content h1 + p,
article h1 + p,
.post-meta + p {
  font-size: 1.125rem;
  line-height: 1.6;
  color: var(--text);
}

/* ---- Square the inline-rounded imagery ----------------------------------
   Templates set `border-radius:12px` inline on hero and inline images. The
   corpus is square to 2px everywhere; a 12px radius is the single most
   dating detail on an otherwise restrained page. Inline styles, so
   !important is the only lever short of editing ~30 templates. */
.page-content img[style*="border-radius"],
article img[style*="border-radius"],
main img[style*="border-radius:12px"],
main img[style*="border-radius: 12px"] {
  border-radius: var(--radius) !important;
}

/* Give interior heroes the same 3:2 discipline the cards use, and stop the
   layout shifting while they decode. */
.page-content > img:first-of-type,
article > img:first-of-type {
  background: var(--ground-sunken);
}

/* Post meta line. */
.post-meta,
.page-content .meta {
  font-family: var(--font-body);
  font-size: .8125rem;
  letter-spacing: .04em;
  color: var(--text-muted);
}


/* ==========================================================================
   22. Long-form prose.

   Roughly a hundred of this site's pages are long-form guides, so the reading
   experience below the fold matters more than anything on the homepage.
   ========================================================================== */

/* Sub-headings were rendering in the pre-existing #9a7b2e, which measures
   3.58:1 on the bone ground. That clears the large-text minimum but reads
   washed next to 12:1 body copy. The accent token is 6.15:1 and is the same
   gold used everywhere else. */
.page-content h3,
article h3 { color: var(--gold); }

.page-content h2,
article h2,
.bio-text h2 { color: var(--white); }

/* Lists were inheriting the MUTED token, so a bulleted list read a full step
   lighter than the paragraph above it. A list is body copy. */
.page-content ul, .page-content ol, .page-content li,
article ul, article ol, article li {
  color: var(--text);
}

.page-content ul, .page-content ol,
article ul, article ol {
  padding-left: 1.15em;
  margin-block: var(--gap-md);
}
.page-content li, article li {
  margin-bottom: .5em;
  padding-left: .35em;
}
.page-content ul li::marker,
article ul li::marker { color: var(--gold-deco); }
.page-content ol li::marker,
article ol li::marker {
  color: var(--gold);
  font-family: var(--font-display);
  font-variant-numeric: lining-nums;
}

/* A hairline before each major section gives long guides a scannable spine
   without adding chrome. */
.page-content h2::before,
article h2::before {
  content: "";
  display: block;
  width: 3rem;
  height: 1px;
  background: var(--gold-deco);
  margin-bottom: var(--gap-md);
}
.page-content h2:first-child::before,
article h2:first-child::before { display: none; }

/* Tables inside guides. Match the price-board treatment so the site has one
   table idiom rather than two, and keep them scrollable on a phone. */
.page-content table,
article table {
  width: 100%;
  border-collapse: collapse;
  margin-block: var(--gap-lg);
  font-size: .9375rem;
}
.page-content table th,
article table th {
  text-align: left;
  font-family: var(--font-body);
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
  border-bottom: 1px solid var(--gold-deco);
  padding: .65rem .75rem;
}
.page-content table td,
article table td {
  padding: .7rem .75rem;
  border-bottom: 1px solid var(--rule-soft);
}
.page-content table tbody tr:nth-child(even),
article table tbody tr:nth-child(even) { background: var(--gold-wash); }

/* Figures and captions. */
.page-content figure, article figure { margin: var(--gap-lg) 0; }
.page-content figcaption, article figcaption {
  font-size: .8125rem;
  color: var(--text-muted);
  margin-top: var(--gap-xs);
  max-width: 60ch;
}

/* Pull-quotes inside a guide keep the display treatment from section 14, but
   they need a measure that suits the 68ch column they sit in. */
.page-content blockquote,
article blockquote { max-width: 30ch; margin-inline: auto; }

/* Horizontal rules inside prose should be quieter than a section divider. */
.page-content hr, article hr {
  margin-block: var(--gap-xl);
  border-top-color: var(--rule-soft);
}

/* Reveal-on-scroll for long-form too, one heading at a time. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .page-content h2, article h2,
    .page-content figure, article figure,
    .page-content table, article table {
      animation: reveal-up linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 45%;
    }
  }
}


/* ==========================================================================
   23. Systematic remap of the legacy light-theme palette.

   style.css contains 64 `[data-theme="light"] …` rules that hardcode literal
   colours rather than tokens: #9a7b2e (the old gold), #1a1a1a, #444, #666,
   #777, #ccc, #fff. Because they are written at (0,2,x) they outrank
   everything this file has done through tokens, which is why the same class
   of bug kept resurfacing one component at a time (footer, then inputs, then
   prose headings).

   This block remaps them in one pass at matching specificity. The literals
   being replaced and their measured problems:

     #9a7b2e  old gold, 3.58:1 on the bone ground. Clears the large-text
              minimum, fails the 4.5:1 body minimum, and reads washed beside
              12:1 body copy. Replaced with --gold (6.15:1).
     #444     body/list ink, replaced with --text for one ink value sitewide.
     #666     muted, replaced with --text-muted (5.11:1, was 4.6:1).
     #1a1a1a  heading ink, replaced with --white so headings track the theme.
     #fff     surfaces, replaced with --ground-raised (see section 19).
   ========================================================================== */

/* ---- Ink ---- */
[data-theme="light"] .nav-logo,
[data-theme="light"] .hero-title,
[data-theme="light"] .section-title,
[data-theme="light"] .bio-text h2,
[data-theme="light"] .feature-card h3,
[data-theme="light"] .blog-card h3,
[data-theme="light"] .page-content h1,
[data-theme="light"] .page-content h2,
[data-theme="light"] .page-content strong { color: var(--white); }

[data-theme="light"] .page-content p,
[data-theme="light"] .page-content li { color: var(--text); }

[data-theme="light"] .nav-links a,
[data-theme="light"] .stat-label,
[data-theme="light"] .section-subtitle,
[data-theme="light"] .a11y-size-btn { color: var(--text-muted); }

[data-theme="light"] .nav-links a:hover,
[data-theme="light"] .nav-links a[aria-current="page"] { color: var(--white); }

[data-theme="light"] .nav-toggle span { background: var(--white); }

/* ---- Accent ---- */
[data-theme="light"] .hero-eyebrow,
[data-theme="light"] .stat-number,
[data-theme="light"] .page-content h3,
[data-theme="light"] .a11y-dialog-header h2,
[data-theme="light"] .a11y-control-label,
[data-theme="light"] .a11y-floating-btn { color: var(--gold); }

[data-theme="light"] .testimonial-stars { color: var(--gold-deco); }

[data-theme="light"] .feature-card:hover,
[data-theme="light"] .city-card:hover { border-color: var(--gold-deco); }

/* ---- Buttons ---- */
[data-theme="light"] .page-content .btn-primary {
  background: var(--gold);
  color: var(--ground-raised);
  border-color: var(--gold);
}
[data-theme="light"] .btn-outline {
  background: transparent;
  color: var(--gold);
  border-color: var(--gold-deco);
}
[data-theme="light"] .btn-outline:hover {
  background: var(--gold);
  color: var(--ground-raised);
  border-color: var(--gold);
}

/* ---- Surfaces ---- */
[data-theme="light"] .city-card,
[data-theme="light"] .mobile-cta-bar,
[data-theme="light"] .a11y-floating-btn {
  background: var(--ground-raised);
  border-color: var(--rule-soft);
}
[data-theme="light"] .city-card { color: var(--text) !important; }
[data-theme="light"] .city-card:hover { color: var(--gold) !important; }

[data-theme="light"] .mobile-cta-btn svg { stroke: var(--gold); }

[data-theme="light"] .segment-pill {
  background: var(--gold-wash);
  border-color: var(--gold-deco);
  color: var(--gold) !important;
}
[data-theme="light"] .segment-pill:hover {
  background: var(--gold);
  color: var(--ground-raised) !important;
  border-color: var(--gold);
}


/* ==========================================================================
   24. Primary buttons.

   MEASURED FAILURE: `.btn-primary` was white text on #9a7b2e, which is
   4.0:1. WCAG AA needs 4.5:1 for normal text. That is every primary call to
   action on the site, which is the worst place on a page to have a contrast
   problem.

   Fixing it by darkening the gold would work (white on --gold is 6.88:1),
   but there is a better answer. The accent was already carrying the
   eyebrows, the stat numerals, the rules, the links and the hover states.
   Putting it on every button too spends the one-accent budget the whole
   design rests on, which is exactly the failure mode the corpus avoids.

   So: charcoal is the action colour, gold stays the accent. That gives a
   clear two-level hierarchy (ink = do this, gold = notice this) and takes
   the buttons to 16.5:1. The gold button survives in one place, the closing
   CTA band, where it sits on the dark surface and reads as the single
   loudest moment on the page.
   ========================================================================== */

.btn-primary,
[data-theme="light"] .btn-primary,
[data-theme="light"] .page-content .btn-primary {
  background: var(--white);
  color: var(--ground-raised);
  border-color: var(--white);
}
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover,
  [data-theme="light"] .btn-primary:hover,
  [data-theme="light"] .page-content .btn-primary:hover {
    background: #35312c;                 /* 12.4:1 */
    color: var(--ground-raised);
    border-color: #35312c;
  }
}

/* The CTA band keeps its gold button. Re-stated after the rule above so it
   is not caught by it. */
.cta-band .btn-primary,
[data-theme="light"] .cta-band .btn-primary {
  background: var(--inv-brand);
  color: #14130f;
  border-color: var(--inv-brand);
}

/* Dark theme: the ink is bone, so an ink-filled button would be a bone
   button. Use the raised surface with a gold edge instead. */
[data-theme="dark"] .btn-primary {
  background: var(--inv-brand);
  color: #14130f;
  border-color: var(--inv-brand);
}
[data-theme="dark"] .cta-band .btn-primary {
  background: var(--inv-brand);
  color: #14130f;
}

/* ---- Remaining legacy stragglers ---------------------------------------- */

[data-theme="light"] .hero-lang-badge,
.hero-lang-badge {
  background: var(--gold-wash);
  border-color: var(--gold-deco);
  color: var(--gold);
}
[data-theme="light"] .hero-lang-badge a { color: var(--gold); }

[data-theme="light"] .theme-toggle,
.theme-toggle {
  background: var(--ground-raised);
  border-color: var(--rule-soft);
  color: var(--gold);
}

/* A stray form heading still resolving to the legacy ink literal. */
[data-theme="light"] .form-cta-card h3,
[data-theme="light"] .contact-form h3,
.form-cta-card h3,
.contact-form h3 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  color: var(--white);
}


/* ==========================================================================
   25. Header CTA.

   `.nav-cta` fills with `var(--gold)` and sets near-black text, both with
   !important. That worked when --gold was a light brand gold. This design
   darkened --gold to #6f5719 so it could be used as TEXT on the bone ground
   at 6.15:1, which turned the header button into dark-on-dark at 2.88:1.

   That is the cost of one token serving two roles. The fix is to stop the
   header button depending on the accent at all and make it the same
   ink-filled primary the rest of the site now uses, so "Let's Talk" reads
   identically wherever it appears.
   ========================================================================== */

.nav-cta,
[data-theme="light"] .nav-cta {
  background: var(--white) !important;      /* charcoal on light */
  color: var(--ground-raised) !important;   /* 16.5:1 */
  border: 1px solid var(--white) !important;
}
@media (hover: hover) and (pointer: fine) {
  .nav-cta:hover,
  [data-theme="light"] .nav-cta:hover {
    background: #35312c !important;
    color: var(--ground-raised) !important;
    border-color: #35312c !important;
  }
}
[data-theme="dark"] .nav-cta {
  background: var(--inv-brand) !important;
  color: #14130f !important;
  border-color: var(--inv-brand) !important;
}
[data-theme="dark"] .nav-cta:hover {
  background: #ddc384 !important;
  color: #14130f !important;
  border-color: #ddc384 !important;
}


/* ==========================================================================
   26. The rest of the one-token-two-roles fallout, plus wrong-context tokens.

   A full-page contrast sweep (with correct alpha compositing, which matters:
   measuring a translucent background as if it were opaque reports failures
   that are not real) turned up 68 failures across four pages. They reduce to
   four causes.

   CAUSE 1 — --gold used as a FILL behind near-black text. Same root cause as
   the header button in section 25. Anything filled with the accent now uses
   --gold-fill, which is light enough for dark text (4.89:1) while --gold
   stays dark enough to be TEXT on bone (6.15:1). One token cannot do both.
   ========================================================================== */

:root  { --gold-fill: #9f7e3a; --on-gold-fill: #14130f; }  /* 4.89:1 */
[data-theme="dark"] { --gold-fill: #c9a94e; --on-gold-fill: #14130f; }  /* 8.2:1 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --gold-fill: #c9a94e; --on-gold-fill: #14130f; }
}

.skip-link,
[data-theme="light"] .skip-link {
  background: var(--white) !important;
  color: var(--ground-raised) !important;
}

#cookie-consent button,
#cookie-consent .btn,
[data-theme="light"] #cookie-consent button {
  background: var(--white);
  color: var(--ground-raised);
  border-color: var(--white);
}
#cookie-consent button + button,
#cookie-consent .btn-secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--rule-input);
}

/* CAUSE 2 — a translucent sticky header makes text contrast depend on
   whatever happens to be scrolling underneath it, which is not something you
   can verify. Give it a solid ground; the backdrop-filter stays for the
   subtle depth but no longer decides legibility. */
.site-header,
[data-theme="light"] .site-header {
  background: var(--ground);
}
[data-theme="dark"] .site-header { background: var(--ground); }

/* CAUSE 3 — dark-theme tokens leaking into light-theme contexts. The tel
   link resolves to the dark gold #c9a94e, which is 2.03:1 on bone. This is
   a phone number on the contact page, so it is one of the highest-intent
   elements on the site. */
[data-theme="light"] a[href^="tel:"],
a[href^="tel:"] { color: var(--gold); }
[data-theme="dark"] a[href^="tel:"] { color: var(--inv-brand); }

/* CAUSE 4 — legacy literals the section-23 sweep did not name. */
.trust-trio,
[data-theme="light"] .trust-trio { color: var(--text-muted); }

/* The footer is a dark surface in BOTH themes, so anything inside it needs
   the inverted ink scale, not the page scale. --text-muted (#696660) is
   tuned for bone and measures 3.19:1 down there. */
.site-footer .footer-license,
[data-theme="light"] .site-footer .footer-license,
.footer-license {
  color: var(--inv-ink-3);           /* 5.5:1 on the footer surface */
}

/* A brand chip in the profile directory sits at 4.41:1, just under. Nudge
   the label to full weight-500 white on a slightly deepened brand ground. */
.pd-mono { color: #fff; text-shadow: 0 0 1px rgb(0 0 0 / .55); }


/* ==========================================================================
   27. Inline-styled literals.

   The last contrast failures are hardcoded in `style=""` attributes in the
   templates, so nothing in the cascade can reach them. !important is the
   only lever short of editing the templates, and these are presentation
   values, so they belong here rather than in files that also emit
   structured data.

     color:#c9a94e on a phone number  -> 2.03:1 on bone (dark-theme gold in a
                                          light context; the contact page's
                                          highest-intent element)
     color:#999    on the form trust line -> 2.55:1
     background:var(--gold);color:#0a0a0a on the cookie buttons -> 2.88:1
   ========================================================================== */

a[href^="tel:"][style*="#c9a94e"],
a[href^="mailto:"][style*="#c9a94e"],
[style*="color:#c9a94e"] { color: var(--gold) !important; }

[data-theme="dark"] a[href^="tel:"][style*="#c9a94e"],
[data-theme="dark"] a[href^="mailto:"][style*="#c9a94e"],
[data-theme="dark"] [style*="color:#c9a94e"] { color: var(--inv-brand) !important; }

/* The dark footer and CTA band legitimately want the bright gold. */
.site-footer [style*="color:#c9a94e"],
.cta-band [style*="color:#c9a94e"] { color: var(--inv-brand) !important; }

p[style*="color:#999"],
[style*="color:#999"] { color: var(--text-muted) !important; }
.site-footer [style*="color:#999"] { color: var(--inv-ink-3) !important; }

#cookie-consent button[style],
#cookie-consent a[style] {
  background: var(--white) !important;
  color: var(--ground-raised) !important;
  border: 1px solid var(--white) !important;
}
#cookie-consent button[style]:last-of-type {
  background: transparent !important;
  color: var(--text) !important;
  border: 1px solid var(--rule-input) !important;
}


/* ==========================================================================
   28. Dark-context exceptions.

   Section 27 rewrote every inline `color:#c9a94e` to the darker --gold so it
   would be legible on bone. That was right for the light page and WRONG
   anywhere the element sits on a dark ground: it turned the credential band
   (inline `background:#0a0a0a`) into dark-on-dark at 2.88:1.

   A blanket colour override has to enumerate its dark contexts, because CSS
   cannot ask "what is the background behind me". These are all of them.
   ========================================================================== */

.cred-band [style*="color:#c9a94e"],
[style*="background:#0a0a0a"] [style*="color:#c9a94e"],
[style*="background:#14130f"] [style*="color:#c9a94e"],
.site-footer [style*="color:#c9a94e"],
.cta-band [style*="color:#c9a94e"] {
  color: var(--inv-brand) !important;      /* 8.1:1 on a near-black ground */
}

/* Same reasoning for the wordmark. The header sits on bone and takes the
   dark accent; the footer wordmark sits on the inverted surface and needs
   the bright one. Section 15 coloured `.footer-logo` but not the <span>
   inside it that carries the second half of the name. */
.site-footer .footer-logo span,
[data-theme="light"] .site-footer .footer-logo span {
  color: var(--inv-brand);
}

/* The credential band's own hairline and ground, restated in tokens so it
   stops depending on inline literals. */
.cred-band,
[style*="background:#0a0a0a"].cred-band {
  background: var(--inv-surface) !important;
  border-bottom-color: var(--inv-brand) !important;
}
.cred-band, .cred-band a, .cred-band span:not([style*="color:#c9a94e"]) {
  color: var(--inv-ink-2);
}

/* Third-party brand chips in the profile directory carry the other party's
   brand colour, so the ground is not ours to change much. Deepening each by
   a few points takes white text from 4.41:1 to comfortably over 4.5 without
   the chip ceasing to read as that brand. */
.pd-ico[style*="#d92228"] { background: #b81c22 !important; }
.pd-ico[style*="#0a8a4f"] { background: #07713f !important; }


/* ==========================================================================
   29. The price table.

   The zebra wash from section 6b composites over the BOARD's card ground
   (#e8e5dd), not the page ground, landing at #e1d9c9. Against that, the
   muted ink and the status green both fall to ~4.07:1.

   Lightening the wash is not enough on its own (it tops out around 4.34), so
   the inks are darkened for this table only. Scoped rather than global,
   because --text-muted at 5.11:1 is correct everywhere else and darkening it
   sitewide would flatten the hierarchy it exists to create.
   ========================================================================== */

.lgi-discount-board table td,
.lgi-discount-compact table td,
.lgi-discount-board table td span,
.lgi-discount-compact table td span {
  color: #625f59;                 /* 4.54:1 on the wash, 5.05:1 on the card */
}
.lgi-discount-board table td strong,
.lgi-discount-compact table td strong,
.lgi-discount-board table td b,
.lgi-discount-compact table td b {
  color: var(--white);
}

/* Status green, same treatment. */
.lgi-discount-board table td [style*="color:#"],
.lgi-discount-compact table td [style*="color:#"] { color: #1f6d42 !important; }

[data-theme="dark"] .lgi-discount-board table td,
[data-theme="dark"] .lgi-discount-compact table td { color: var(--text); }
[data-theme="dark"] .lgi-discount-board table td [style*="color:#"],
[data-theme="dark"] .lgi-discount-compact table td [style*="color:#"] { color: #4caf6a !important; }


/* ==========================================================================
   30. The board ground, fixed at the root instead of per element.

   The LGI board nests three darkening layers: the section and the board are
   both --dark-2 (#e8e5dd), and the inner panel is --dark-3 (#ded9cd), with
   the zebra wash on top of that for table rows. Muted ink on #ded9cd is
   4.06:1, which is why `p`, `th` and `td` inside the board all failed while
   the identical tokens passed everywhere else on the page.

   Darkening every text element inside the board was the wrong instinct (I
   tried it in section 29 and it only moved the `td` cases). The ground is
   what is wrong, so raise the ground once and everything on it clears.

   --dark-3 #ded9cd -> #ebe7de : muted goes 4.06 -> 4.64
   zebra .14 -> .10            : keeps the row banding readable as banding
                                 while leaving headroom for the status green
   ========================================================================== */

:root,
[data-theme="light"] { --dark-3: #ebe7de; }

.lgi-discount-board tbody tr:nth-child(even),
.lgi-discount-compact tbody tr:nth-child(even) {
  background: rgba(179, 146, 78, .10) !important;
}

/* With the ground raised, the board's own body copy can go back to the
   standard muted token rather than a bespoke one. */
.lgi-discount-board p[style*="--text-muted"],
.lgi-discount-compact p[style*="--text-muted"] { color: var(--text-muted); }


/* ==========================================================================
   31. Inline gold FILLS.

   The same one-token-two-roles fault, expressed inline this time:
   `background:var(--gold);color:#0a0a0a` on the plan-card "Tour with Heath"
   buttons (and their ES/ZH equivalents) and the cookie buttons. With --gold
   darkened to be legible as text on bone, these became dark-on-dark at
   2.88:1 across all three locales.

   One rule covers every current and future instance of the pattern.
   ========================================================================== */

[style*="background:var(--gold)"] {
  background: var(--gold-fill) !important;   /* #9f7e3a light / #c9a94e dark */
  color: var(--on-gold-fill) !important;     /* 4.89:1 light, 8.2:1 dark      */
  border-color: var(--gold-fill) !important;
}
@media (hover: hover) and (pointer: fine) {
  a[style*="background:var(--gold)"]:hover,
  button[style*="background:var(--gold)"]:hover {
    background: var(--white) !important;
    color: var(--ground-raised) !important;
    border-color: var(--white) !important;
  }
}
[data-theme="dark"] [style*="background:var(--gold)"] {
  background: var(--inv-brand) !important;
  color: #14130f !important;
  border-color: var(--inv-brand) !important;
}

/* The board's beds/sf column sat at 4.3:1 after the ground was raised.
   A few more points of ink clears it with margin. */
.lgi-discount-board table td,
.lgi-discount-compact table td,
.lgi-discount-board table td span,
.lgi-discount-compact table td span { color: #5a5750; }


/* ==========================================================================
   32. Inline --text-muted inside the board.

   The beds/sf cells carry `color:var(--text-muted)` as an inline style, so
   the class rule in section 31 never applied. On the zebra row that token
   lands at 4.3:1. Inside this board specifically it needs a darker ink;
   everywhere else --text-muted is correct and is left alone.
   ========================================================================== */

.lgi-discount-board [style*="color:var(--text-muted)"],
.lgi-discount-compact [style*="color:var(--text-muted)"] {
  color: #5a5750 !important;          /* 4.9:1 on the zebra row */
}
[data-theme="dark"] .lgi-discount-board [style*="color:var(--text-muted)"],
[data-theme="dark"] .lgi-discount-compact [style*="color:var(--text-muted)"] {
  color: var(--text-muted) !important;
}

/* The price-drop badge and the language pill both put the accent on a tinted
   ground. Give them the fill treatment so the accent becomes the background
   and the text becomes ink, which is legible either way round. */
.lang-switch-pill,
[data-theme="light"] .lang-switch-pill {
  background: var(--gold-wash);
  border: 1px solid var(--gold-deco);
  color: var(--gold) !important;
}
[data-theme="dark"] .lang-switch-pill {
  background: rgba(205, 174, 99, .12);
  border-color: var(--inv-brand);
  color: var(--inv-brand) !important;
}


/* ==========================================================================
   33. The price-drop badge.

   Inline `color:#4caf50`, a bright green picked for the original near-black
   theme, sitting on the light card ground at 2.21:1. It is the label that
   announces a price cut, so it is the one number on the card a buyer is
   looking for.
   ========================================================================== */

[style*="color:#4caf50"],
[style*="color:#4CAF50"] { color: #1f6d42 !important; }   /* 5.0:1 on the card */

[data-theme="dark"] [style*="color:#4caf50"],
[data-theme="dark"] [style*="color:#4CAF50"] { color: #4caf6a !important; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) [style*="color:#4caf50"],
  :root:not([data-theme="light"]) [style*="color:#4CAF50"] { color: #4caf6a !important; }
}


/* ==========================================================================
   34. Cascade-order fix for --dark-3.

   Section 30 raised the board ground with `:root, [data-theme="light"] {
   --dark-3: #ebe7de }`. `:root` and `[data-theme="dark"]` have the SAME
   specificity (0,1,0), so when the dark attribute is set both selectors
   match the same element and source order decides. Section 30 sits after the
   dark palette in section 2, so it won, and dark mode got a bone-coloured
   board with bone-coloured text on it: 1.1:1.

   Re-asserting the dark value after the fact is the fix. The general lesson:
   a late `:root` token declaration silently overrides every earlier
   theme block, so theme overrides have to be restated below it.
   ========================================================================== */

[data-theme="dark"] { --dark-3: #2a261e; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --dark-3: #2a261e; }
}


/* ==========================================================================
   35. Dark-theme board ink, completed.

   Section 31 darkened `.lgi-discount-board table td` AND `td span` for the
   light theme, but its dark override only restated `td`. The span is the
   more specific selector, so in dark mode the light ink (#5a5750) kept
   winning on the percentage labels: 1.85:1.
   ========================================================================== */

[data-theme="dark"] .lgi-discount-board table td span,
[data-theme="dark"] .lgi-discount-compact table td span { color: var(--text-muted); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lgi-discount-board table td,
  :root:not([data-theme="light"]) .lgi-discount-compact table td,
  :root:not([data-theme="light"]) .lgi-discount-board table td span,
  :root:not([data-theme="light"]) .lgi-discount-compact table td span { color: var(--text-muted); }
}


/* ==========================================================================
   36. The remaining dark-theme status colors.

   Section 33 caught the price-drop green (#4caf50). A sweep of the pages that
   had not been audited yet found the same class of problem on the market-data
   page: #e57373, a light red picked for the original near-black theme, sitting
   on the bone card at 2.37:1. It is a percentage-change indicator, so it is
   exactly the number a reader is looking for.

   Measured on the card ground (#e8e5dd), which is the worst case:
     #e57373  2.37    #4caf50  2.21    #66bb6a  1.88    #ef5350  2.77
   All of them are tuned for a dark ground and none clear 4.5:1 on a light one.
   Mapped to accessible equivalents, and restored to the bright originals in
   dark mode where they were always correct.
   ========================================================================== */

[style*="color:#e57373"], [style*="color:#E57373"],
[style*="color:#ef5350"], [style*="color:#EF5350"] { color: #b24040 !important; }   /* 4.51:1 card, 5.07:1 bone */

[style*="color:#66bb6a"], [style*="color:#66BB6A"],
[style*="color:#81c784"], [style*="color:#81C784"] { color: #1f6d42 !important; }

[data-theme="dark"] [style*="color:#e57373"],
[data-theme="dark"] [style*="color:#E57373"],
[data-theme="dark"] [style*="color:#ef5350"],
[data-theme="dark"] [style*="color:#EF5350"] { color: #e57373 !important; }
[data-theme="dark"] [style*="color:#66bb6a"],
[data-theme="dark"] [style*="color:#66BB6A"],
[data-theme="dark"] [style*="color:#81c784"],
[data-theme="dark"] [style*="color:#81C784"] { color: #4caf6a !important; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) [style*="color:#e57373"],
  :root:not([data-theme="light"]) [style*="color:#ef5350"] { color: #e57373 !important; }
  :root:not([data-theme="light"]) [style*="color:#66bb6a"],
  :root:not([data-theme="light"]) [style*="color:#81c784"] { color: #4caf6a !important; }
}


/* ==========================================================================
   37. Mobile nav tap targets.

   Section 11 set `min-height: 48px` on `.nav-links a` inside the mobile
   breakpoint. That did nothing: an <a> is inline by default, and min-height
   has no effect on a non-replaced inline box. Measured in the open drawer:
   14 of 24 links were under 44px tall.

   This is the whole navigation of the site on the device where most of its
   traffic is, so it matters more than anything else left on the list.
   ========================================================================== */

@media (max-width: 768px) {
  .nav-links a,
  .nav-links > li > a,
  .nav-sub a {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding-block: .35rem;
    width: 100%;
    box-sizing: border-box;
  }

  /* The wiping underline is absolutely positioned against the link box; with
     the link now full-width it would run the whole screen. Constrain it to
     the text. */
  .nav-links > li > a:not(.nav-cta)::after { display: none; }
  .nav-links > li > a[aria-current="page"] {
    box-shadow: inset 0 -1px 0 currentColor;
  }

  /* Separate the sub-menu rows so adjacent 48px targets are distinguishable
     rather than one continuous slab. */
  .nav-sub a + a,
  .nav-sub li + li a { border-top: 1px solid var(--rule-soft); }

  .nav-links > li { width: 100%; }
}

/* Section 37 did not take: style.css declares
   `.nav-sub a { display:block !important; padding:8px 14px !important; min-height:0 !important }`.
   Three !important declarations, so the rule above lost silently and the
   sub-menu links stayed at 38px. Matching the weapon. */
@media (max-width: 768px) {
  .nav-sub a,
  .nav-links .nav-sub a,
  .nav-links > li.nav-has-sub > .nav-sub a {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-height: 48px !important;
    padding: .5rem 1rem !important;
    width: 100% !important;
    box-sizing: border-box !important;
  }
}


/* ==========================================================================
   38. The last four small tap targets.

   Measured on a 390px viewport: theme toggle 42x42, accessibility button
   42x42, and the two cookie-consent buttons at 33px tall. WCAG 2.5.8 asks for
   24x24 as a minimum and 44x44 is the widely-used comfortable target; these
   are close but under, and two of them are the consent controls, which is a
   poor place to make someone aim.
   ========================================================================== */

.theme-toggle,
.a11y-floating-btn {
  min-width: 44px;
  min-height: 44px;
}

#cookie-consent button,
#cookie-consent .btn,
#cookie-consent button[style] {
  min-height: 44px !important;
  padding-block: .6rem !important;
}
