
  :root {
    --oat-light: color-mix(in srgb, var(--oat) 70%, transparent);
  }
  body::after {
    content: ''; position: fixed; inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='256' height='256' filter='url(%23g)' opacity='0.035'/%3E%3C/svg%3E");
    background-size: 256px; pointer-events: none; z-index: var(--z-grain);
  }
  .fh { font-family: var(--font-display); }
  .fb { font-family: var(--font-body); }

  /* Browser-drawn surfaces belong to the palette too — left to the default they ship the
     OS blue, which is the one colour on the page that is nobody's brand.
     Solid mint rather than a tint: selection has to read on the cream body AND on the
     charcoal cards, and a translucent mint over charcoal leaves charcoal text on a dark
     ground. Solid mint under charcoal text is 7.6:1 on either; on buynewcondo.sg it is
     white on blueberry (--on-mint). */
  ::selection { background: var(--mint); color: var(--on-mint); }
  .mr-input, .mr-select, textarea { caret-color: var(--mint-dk); }
  /* Native checkboxes and radios ship the same OS blue for their tick AND their focus
     ring. Charcoal, not mint: a mint tick on cream is ~2.6:1, under the 3:1 bar for a
     UI component. */
  input[type='checkbox'], input[type='radio'] { accent-color: var(--charcoal); }
  input[type='checkbox']:focus-visible, input[type='radio']:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

  .bg-cream     { background-color: var(--cream); }
  .bg-cream-alt { background-color: var(--cream-alt); }
  .text-mid      { color: var(--mid); }
  .text-soft     { color: var(--soft); }
  .text-charcoal { color: var(--charcoal); }
  .text-mint     { color: var(--mint); }
  .text-mint-dk  { color: var(--mint-dk); }

  .hp-over {
    display: block;
    font-family: var(--font-body);
    font-size: 0.6875rem; font-weight: 600;
    letter-spacing: var(--tracking-overline); text-transform: uppercase; color: var(--soft);
  }
  .hp-h1 {
    font-family: var(--font-display);
    font-size: clamp(2.25rem, 5.5vw, 3.75rem);
    font-weight: 600; line-height: 1.04; letter-spacing: var(--tracking-display); color: var(--charcoal);
    text-wrap: balance;
  }
  .hp-h2 {
    font-family: var(--font-display);
    font-weight: 600; line-height: 1.1; letter-spacing: var(--tracking-heading); color: var(--charcoal);
  }
  .hp-body { font-family: var(--font-body); color: var(--mid); line-height: 1.65; }

  /* ── Hero layout ── */
  .mr-hero {
    display: grid;
    grid-template-columns: 1fr;
    gap: 3rem;
    align-items: center;
  }
  @media (min-width: 900px) {
    .mr-hero { grid-template-columns: 1fr auto; gap: 4rem; }
  }
  .mr-hero-copy { max-width: 38rem; }

  /* Hero copy veil — see the partial for why it lives there. */

  /* Cream veil for hero copy that sits over artwork. Goes on the copy COLUMN, not on a
     full-width sibling, so it needs no constants at all: the pseudo-element is the column
     plus a 12rem tail, and the gradient is measured from its right edge inwards, which
     means the opaque region ends exactly where the copy ends on every page regardless of
     container and column widths.

     This replaced a hand-written gradient that every public hero carried inline, whose
     stops were percentages of the VIEWPORT while the copy is anchored to a centred
     container — the two drift apart as the window widens, and the copy lands on unveiled
     artwork. Measured glyph-by-glyph against the rendered pixels (not against an ancestor
     background-color, which reads a misleading 6.8:1), 17 of the 18 public heroes failed:
     body copy as low as 1.00:1 against a 4.5:1 bar, headlines at 1.33:1 against 3:1.
     Per-page anchors were the other candidate fix and were rejected — the heroes span
     five different container/column combinations, from a 576px column in a 1024px
     container to a 672px column in a 1152px one, so every page would carry a constant
     that silently rots the next time its layout is touched.

     `z-index: 0` on the host is load-bearing: it makes the column a stacking context so
     the `z-index: -1` pseudo-element resolves inside it, painting above the artwork (the
     copy wrapper comes after the art in DOM order) but below the copy's own text. Without
     it the pseudo-element escapes to the section and covers the words it is meant to back.

     Only from `lg` up, matching where the heroes put artwork behind the copy. Below that
     the art stacks above the copy: at 768px the column fills ~77% of the section, so a
     veil wide enough to keep the copy readable would erase the picture entirely. */
  .mr-hero-veil { position: relative; z-index: 0; }
  @media (min-width: 1024px) {
    .mr-hero-veil::before {
      content: '';
      position: absolute;
      z-index: -1;
      /* Bleeds past the copy and is clipped by the hero section's own overflow:hidden —
         the hero is taller than the copy (min-height plus vertical padding), and a veil
         that stopped at the text would draw a hard horizontal seam across the artwork.
         A hero section that CANNOT clip must bound the bleed itself via
         `--mr-veil-bleed`, or the veil paints over whatever follows it: mop-report.njk
         drops overflow:hidden on purpose so a dropdown can escape the section.

         `--mr-veil-tail` (default 12rem) is the fade width past the column's own right
         edge — set it smaller on `--mr-hero-veil` inline style when a page wants more of
         the artwork visible than the default tail leaves. Shrinking it does not touch the
         opaque region over the text itself, only how far the fade reaches into the art. */
      top: calc(-1 * var(--mr-veil-bleed, 50vh));
      bottom: calc(-1 * var(--mr-veil-bleed, 50vh));
      left: -100vw;
      right: calc(-1 * var(--mr-veil-tail, 12rem));
      background: linear-gradient(270deg,
        color-mix(in srgb, var(--cream) 0%, transparent) 0,
        color-mix(in srgb, var(--cream) 95%, transparent) var(--mr-veil-tail, 12rem),
        color-mix(in srgb, var(--cream) 97%, transparent) 100%);
      pointer-events: none;
    }
  }


  /* For an affordance that only exists with a mouse. chartjs-plugin-zoom's drag-box and
     wheel zoom are both pointer-driven — pinch needs Hammer.js, which we do not load — so
     on a touch screen the zoom hint describes something that cannot happen and the reset
     button is inert. Verified by driving a touch drag across the canvas: the axis does not
     move. Gate on the input device, not on width: a 1024px tablet is still touch-only. */
  .mr-pointer-only { display: none; }
  @media (hover: hover) and (pointer: fine) {
    .mr-pointer-only { display: flex; }
  }

  /* Trust badges */

  .mr-trust-badges {
    display: flex; flex-wrap: wrap; gap: 0.625rem;
  }
  .mr-trust-badge {
    display: inline-flex; align-items: center; gap: 0.45rem;
    background: #fff; border: var(--border-card); border-radius: var(--radius-pill);
    padding: 0.4rem 0.9rem;
    font-family: var(--font-body);
    font-size: 0.8125rem; font-weight: 500; color: var(--mid);
  }
  .mr-trust-badge svg { flex-shrink: 0; }


  /* Chart panel */
  .mr-chart-wrap {
    display: none;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.5rem;
  }
  @media (min-width: 900px) {
    .mr-chart-wrap { display: flex; }
  }
  .mr-chart-wrap svg { width: 260px; height: auto; }
  .mr-chart-label {
    font-family: var(--font-body);
    font-size: 0.7rem; font-weight: 500; letter-spacing: 0.07em;
    text-transform: uppercase; color: var(--soft);
    text-align: right;
  }

  /* ── Search card ── */
  .mr-search-card {
    background: #fff; border-radius: var(--radius-card); padding: 2rem;
  }
  .mr-select {
    width: 100%; border: var(--border-card); border-radius: 8px;
    padding: 0.65rem 0.9rem; background: var(--cream);
    font-family: var(--font-body); font-size: 0.9375rem;
    color: var(--charcoal); appearance: none; -webkit-appearance: none;
    outline: none; transition: border-color 0.15s;
  }
  /* `appearance: none` above strips the native dropdown arrow, which left every
     public-page <select> looking exactly like a text input. Draw it back. Excluded for
     [multiple], which renders as a list box with no collapsed row to point at. */
  .mr-select:not([multiple]) {
    padding-right: 2.25rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.75L6 6.25L11 1.75' stroke='%232A2825' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
  }
  /* A list box shows its options directly, so the input's own vertical padding just
     clips the last visible row in half. Pad the options instead of the box. */
  .mr-select[multiple] { padding: 0; }
  .mr-select[multiple] option { padding: 0.35rem 0.9rem; }
  .mr-select:focus { border-color: var(--mint); }
  /* Keyboard focus: the mint border alone is ~2.4:1 against cream — under the 3:1 bar
     for a focus indicator, and invisible on a control that already has a border. */
  .mr-select:focus-visible,
  .mr-input:focus-visible,
  .mr-btn-mint:focus-visible,
  .mr-btn-ghost:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
  }
  .mr-btn-ghost {
    background: transparent; border: var(--border-width) solid color-mix(in srgb, var(--charcoal) 25%, transparent);
    color: var(--charcoal); border-radius: var(--radius-pill); padding: 0.4rem 1rem;
    font-family: var(--font-body); font-size: 0.8125rem;
    font-weight: 500; cursor: pointer;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
  }
  @media (hover: hover) {
    .mr-btn-ghost:hover { border-color: var(--charcoal); background: var(--charcoal); color: #fff; }
  }
  /* A CTA row: one mint pill plus its ghost siblings, centred. The ghosts are
     sized up from the base 0.8125rem, which next to the mint's 0.9375rem reads as
     a different class of control rather than a sibling. Scoped to the row rather
     than set on .mr-btn-ghost globally — most of its ~59 uses are table and filter
     controls that want the small size — and scoped to the ROW rather than a
     per-button modifier so a new CTA row is correct without remembering to opt in. */
  .mr-cta-row { display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center; }
  .mr-cta-row .mr-btn-ghost,
  .mr-cta-row .mr-btn-wa { font-size: 0.9375rem; }
  /* The WhatsApp pill (.mr-btn-wa) is defined once in input.css — on every page. */
  .mr-btn-mint {
    background: var(--mint); color: var(--on-mint);
    border: none; border-radius: var(--radius-pill); padding: 0.65rem 1.5rem;
    font-family: var(--font-body); font-size: 0.9375rem;
    font-weight: 600; letter-spacing: var(--tracking-button); cursor: pointer;
    transition: background 0.2s, transform 0.15s, box-shadow 0.15s;
  }
  @media (hover: hover) {
    .mr-btn-mint:hover {
      background: var(--mint-dk); transform: translateY(-2px);
      box-shadow: var(--shadow-cta);
    }
  }
  /* Busy, per design.md: HTMX marks the submitting form .htmx-request. */
  form.htmx-request .mr-btn-mint { opacity: 0.55; pointer-events: none; }
  /* ── Compact filter row ──
     A toggle button and its sibling selects have to read as ONE segmented control, so
     they share a height, a border weight, a radius and a type size. The hand-rolled
     0.25rem pill this replaces sat 14px shorter than the selects beside it and carried a
     999px radius against their 8px — two controls acting on the same chart, drawn from
     two different systems. Pill radius throughout, per the segmented-control rule. */
  .mr-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
  .mr-filter-btn,
  .mr-filter-row .mr-select {
    height: 2.5rem; border-radius: var(--radius-pill); font-size: 0.875rem;
  }
  .mr-filter-btn {
    display: inline-flex; align-items: center; padding: 0 1.15rem;
    border: var(--border-card); background: var(--cream); color: var(--charcoal);
    font-family: var(--font-body); font-weight: 600;
    letter-spacing: var(--tracking-button); cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
  }
  .mr-filter-btn:hover { border-color: var(--charcoal); }
  /* The pressed state is driven off aria-pressed, not a class the script has to keep in
     step: one source of truth for what the control says and what it looks like. */
  .mr-filter-btn[aria-pressed='true'] {
    background: var(--charcoal); border-color: var(--charcoal); color: #fff;
  }
  .mr-filter-btn:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  /* Width is auto so a select sizes to its own label; the vertical padding goes because
     the shared height centres the text. Only padding is restated — the chevron lives on
     .mr-select:not([multiple])'s background-image and must survive. */
  .mr-filter-row .mr-select {
    width: auto; padding: 0 2.25rem 0 1.15rem; font-weight: 500;
  }

  /* ── Period summary band ──
     The chart's live total. White-on-white inside a .card it read as an empty outline;
     charcoal is the system's payoff surface and makes it the second thing the eye lands
     on after the heading. */
  .mr-period-band {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.85rem;
    background: var(--charcoal); border-radius: var(--radius-card); padding: 1rem 1.4rem 1.1rem;
  }
  .mr-period-label {
    flex-basis: 100%;
    font-family: var(--font-body);
    font-size: 0.6875rem; font-weight: 600; letter-spacing: var(--tracking-overline);
    text-transform: uppercase; color: var(--accent-on-dark); /* 7.6:1 on charcoal (mint; blueberry-on-dark on bnc) */
    margin-bottom: 0.15rem;
  }
  .mr-period-figure {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 4.5vw, 2.25rem); font-weight: 700;
    letter-spacing: var(--tracking-figure); line-height: 1; color: #fff;
    font-variant-numeric: tabular-nums;
  }
  .mr-period-sub { font-size: 0.875rem; color: rgba(255,255,255,0.72); }

  .mr-row { transition: background 0.1s; }
  @media (hover: hover) {
    .mr-row:hover { background: var(--cream); }
  }
  .mr-stat-card {
    background: #fff; border: var(--border-card); border-radius: var(--radius-card);
    padding: 0.75rem 1rem; text-align: center;
    /* Grid stretches every card in a row to the tallest; centre the figure so a
       one-line value beside a two-line range does not sit over a blank band. */
    display: flex; flex-direction: column; justify-content: center;
    min-width: 0; /* grid items default to min-width:auto — lets long values shrink instead of overflowing the card */
    overflow-wrap: break-word;
    /* Sizes .mr-stat-value below to THIS card rather than the viewport. */
    container-type: inline-size;
  }
  .mr-stat-label {
    display: block; font-size: 0.6875rem; font-weight: 600;
    letter-spacing: var(--tracking-label); text-transform: uppercase;
    color: var(--soft); margin-bottom: 0.5rem;
  }
  .mr-stat-value {
    display: block;
    font-family: var(--font-display);
    /* cqi (the card's own inline size), not vw: at every width where the strip is
       5-across the viewport is wide, so a vw-based clamp sat pinned at its 1.625rem
       maximum while the card itself was only ~158px — and "S$4,780,000" broke as
       "S$4,780,00 / 0". A price split mid-figure is not a smaller price, it is a
       wrong one, hence nowrap: the value shrinks to fit or it does not fit at all.
       14cqi is measured, not guessed, and sized for the LONGEST realistic value
       rather than today's data: an 11-character "S$4,780,000" needs 15.9cqi, but a
       12-character "S$25,000,000" — ordinary on the landed report, which shares this
       card — needs 14.2. cqi cannot know the string length, so the coefficient has to
       clear the worst case or the longer value silently overflows instead.
       The 1.625rem ceiling still applies wherever the card is roomy enough, so only
       the crowded 5-across desktop strip actually steps down. */
    white-space: nowrap;
    font-size: clamp(1rem, 14cqi, 1.625rem); font-weight: 700;
    letter-spacing: var(--tracking-heading); line-height: 1; color: var(--charcoal);
  }
  /* A two-figure range ("S$260,000 – S$1,375,000") is ~2× the longest single value
     the clamp above is sized for, so nowrap can only overflow the card. Let it break
     at the dash instead — each figure sits in its own nowrap span (dash kept with the
     first, so the break never lands before it), so a price still never splits. */
  .mr-stat-value--range { white-space: normal; line-height: 1.15; }
  .mr-stat-value--range > span { white-space: nowrap; }
  .mr-stat-sub {
    display: block; font-size: 0.75rem; font-weight: 500;
    color: var(--soft); margin-top: 0.35rem;
  }
  /* A unit riding inside the figure ("psf land") stays in the body face. */
  .mr-stat-value .mr-stat-sub { font-family: var(--font-body); letter-spacing: normal; }
  /* No outline on section-level cards on any public page, smh and bnc (David 16/10/2026):
     this one, .mr-search-card and the per-page section cards. Inner cards (stat cards, tiles,
     thumbnails, inputs, pills) keep theirs. */
  .mr-table-card {
    background: #fff; border-radius: var(--radius-card);
    padding: 1.5rem; overflow: hidden;
  }
  /* Stat cards as one ledger on a phone: label left, figure right, a row each. */
  @media screen and (max-width: 639px) {
    .mr-ledger { display: block; background: #fff; border-radius: var(--radius-card); padding: 0.25rem 1.25rem; }
    .mr-ledger .mr-stat-card {
      display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: 1rem;
      padding: 0.7rem 0; background: none; border: 0; border-top: 1px solid var(--oat); border-radius: 0;
      text-align: left; container-type: normal;
    }
    .mr-ledger .mr-stat-card:first-child { border-top: 0; }
    .mr-ledger .mr-stat-label { grid-area: 1 / 1; margin: 0; }
    .mr-ledger .mr-stat-sub { grid-area: 2 / 1; margin: 0; }
    .mr-ledger .mr-stat-value { grid-area: 1 / 2 / 3 / 3; font-size: 1.25rem; }
  }
  /* Phone floor, opt-in with .mr-phone on the main element: 16px fields so iOS Safari does not zoom
     on focus, 44px controls, tighter cards, and no Print button (a phone's print dialog
     is not how anyone keeps this page; print media still gets everything). `screen`,
     because a phone's print is paper-width. */
  @media screen and (max-width: 639px) {
    .mr-phone .mr-input, .mr-phone .mr-select, .mr-phone .mr-filter-row .mr-select { font-size: 1rem; }
    .mr-phone .mr-filter-btn, .mr-phone .mr-filter-row .mr-select { height: 2.75rem; }
    .mr-phone .mr-btn-ghost, .mr-phone .mr-btn-wa {
      display: inline-flex; align-items: center; justify-content: center; min-height: 2.75rem;
    }
    .mr-phone .mr-table-card { padding: 1.25rem; }
    .mr-phone [data-print-page] { display: none; }
  }
  .mr-pill {
    display: inline-block; background: var(--cream-alt); border-radius: var(--radius-pill);
    padding: 0.25rem 0.7rem; font-size: 0.725rem; font-weight: 600;
    letter-spacing: var(--tracking-pill); color: var(--mid);
    font-family: var(--font-body); white-space: nowrap;
  }
  .mr-sort-active {
    display: inline-flex; align-items: center; gap: 0.25rem;
    background: color-mix(in srgb, var(--mint) 12%, transparent); color: var(--mint-text); border-radius: 6px;
    padding: 0.2rem 0.5rem; border: none; cursor: pointer;
    font-size: 0.75rem; font-weight: 600;
    font-family: var(--font-body);
    letter-spacing: var(--tracking-pill); text-transform: uppercase;
  }
  .mr-sort-inactive {
    display: inline-flex; align-items: center; gap: 0.25rem;
    color: var(--soft); background: none; border: none; cursor: pointer;
    border-radius: 6px; padding: 0.2rem 0.5rem;
    font-size: 0.75rem; font-weight: 600;
    font-family: var(--font-body);
    letter-spacing: var(--tracking-pill); text-transform: uppercase;
    transition: background 0.15s, color 0.15s;
  }
  .mr-sort-inactive:hover { background: var(--cream-alt); color: var(--mid); }
  .mr-input {
    width: 100%; border: var(--border-card); border-radius: 8px;
    padding: 0.65rem 0.9rem; background: var(--cream);
    font-family: var(--font-body); font-size: 0.9375rem;
    color: var(--charcoal); outline: none; transition: border-color 0.15s;
  }
  .mr-input:focus { border-color: var(--mint); }
  .mr-label        { font-size: 0.8125rem; font-weight: 600; color: var(--charcoal); }
  .mr-slider-label { font-size: 0.875rem;  font-weight: 600; color: var(--charcoal); }
  .mr-range-value  { font-size: 0.875rem;  font-weight: 600; color: var(--mint-dk); }
  .mr-range-ticks  { font-size: 0.75rem; color: var(--soft); padding-inline: 7px; }
  .mr-hint         { font-size: 0.75rem; color: var(--soft); margin-top: 0.25rem; }
  .mr-bg-cream     { background-color: var(--cream); } /* colour only: a .topo-* texture layers on top */
  .mr-soft-text    { color: var(--soft); }
  /* An idle spinner is opacity:0, so it held 88px of blank page under the search card. */
  .mr-bg-cream p.htmx-indicator:not(.htmx-request) { display: none; }
  .mr-bg-cream .htmx-request p.htmx-indicator { display: block; }
  .mr-check { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; min-height: 2rem; }
  .mr-slider {
    width: 100%; cursor: pointer;
    -webkit-appearance: none; appearance: none;
    background: transparent; outline: none;
    height: 22px;
  }
  .mr-slider::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--mint); border: 2.5px solid #fff;
    box-shadow: 0 2px 8px color-mix(in srgb, var(--mint) 35%, transparent);
    margin-top: -8px; cursor: pointer;
    transition: background 0.15s, box-shadow 0.15s;
  }
  .mr-slider::-webkit-slider-thumb:hover {
    background: var(--mint-dk);
    box-shadow: 0 4px 14px color-mix(in srgb, var(--mint) 45%, transparent);
  }
  .mr-slider::-moz-range-track {
    height: 6px; border-radius: var(--radius-pill);
    background: var(--oat); border: none;
  }
  .mr-slider::-moz-range-progress {
    height: 6px; border-radius: var(--radius-pill);
    background: var(--mint);
  }
  .mr-slider::-moz-range-thumb {
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--mint); border: 2.5px solid #fff;
    box-shadow: 0 2px 8px color-mix(in srgb, var(--mint) 35%, transparent);
    cursor: pointer;
  }

  /* ── Preview tiles ── */
  .mr-preview-tiles {
    padding-bottom: 4rem;
  }
  .mr-preview-eyebrow {
    font-family: var(--font-body);
    font-size: 0.6875rem; font-weight: 600; letter-spacing: var(--tracking-overline);
    text-transform: uppercase; color: var(--soft);
    text-align: center; display: block; margin-bottom: 1.5rem;
    margin-top: 2.5rem;
  }
  .mr-preview-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
  }
  .mr-preview-tile {
    background: #fff; border: var(--border-card); border-radius: var(--radius-card);
    padding: 1.5rem;
  }
  .mr-preview-icon {
    width: 2.5rem; height: 2.5rem; border-radius: 50%;
    background: color-mix(in srgb, var(--mint) 12%, transparent);
    display: flex; align-items: center; justify-content: center;
    margin-bottom: 1rem;
    flex-shrink: 0;
  }
  .mr-preview-title {
    font-family: var(--font-display);
    font-size: 1.0625rem; font-weight: 500; letter-spacing: var(--tracking-title);
    color: var(--charcoal); margin-bottom: 0.375rem;
  }
  .mr-preview-desc {
    font-family: var(--font-body);
    font-size: 0.875rem; color: var(--soft); line-height: 1.55;
  }

  /* ── Phones ──
     `screen`, because a phone's print is paper-width and keeps the desktop page. The
     table half of this pass is .rk-stack in ranking-styles.njk. */
  @media screen and (max-width: 639px) {
    /* The hero gives way to the tool: the form was starting 660px down a 844px screen. */
    .mr-hero-tight img, .mr-hero-tight video { max-height: 11rem; }
    .mr-hero-tight > .relative { padding-block: 1.5rem; }
    .mr-hero-tight .hp-over { margin-bottom: 0.5rem; }
    .mr-hero-tight .hp-h1 { margin-bottom: 0.75rem; }
    .mr-hero-tight .hp-body { margin-bottom: 1rem; }
    .mr-hero-tight .mr-trust-badges { gap: 0.4rem; }
    .mr-hero-tight .mr-trust-badge { padding: 0.35rem 0.6rem; gap: 0.3rem; font-size: 0.75rem; white-space: nowrap; }

    .mr-search-card { padding: 1.25rem; }
    .mr-tabs { display: grid; grid-template-columns: 1.7fr 1fr; gap: 0.5rem; }
    .mr-tabs .mr-tab-active, .mr-tabs .mr-tab-inactive {
      min-height: 2.75rem; padding: 0.5rem; font-size: 0.875rem; white-space: nowrap;
    }
    /* Search is the last step, so it comes last: Date Range sat underneath it. The row
       holding the button dissolves, and only the button moves. */
    .mr-search-card form { display: flex; flex-direction: column; gap: 1.25rem; }
    .mr-search-card form > * { margin-top: 0 !important; min-width: 0; }
    .mr-submit-row { display: contents; }
    .mr-submit { order: 9; }
    .mr-submit .mr-btn-mint { min-height: 2.75rem; }
    #cmr-project-info-container:not(:has(> :not(input[type='hidden']))) { display: none; }
    #cmr-project-suggestions button { min-height: 2.75rem; }

    /* 44px controls: segmented pills, checkbox row, slider. */
    .mr-search-card [role='group'] { padding: 0.125rem; }
    .mr-search-card [role='group'] button { min-height: 2.75rem; padding-inline: 0.75rem; }
    #cmr-sale-type-toggle { display: flex; width: 100%; }
    #cmr-sale-type-toggle button { flex: 1 1 auto; padding-inline: 0.5rem; }
    .mr-check { min-height: 2.75rem; }
    .mr-check input { width: 1.25rem; height: 1.25rem; flex: none; }
    .mr-slider { height: 2.75rem; }
    .mr-slider::-webkit-slider-thumb { width: 28px; height: 28px; margin-top: -11px; }
    .mr-slider::-moz-range-thumb { width: 28px; height: 28px; }

    /* What you'll see: a row each, not three screens of cards. Printing is not a phone
       task, so neither the button nor the promise of it shows here. */
    .mr-preview-tile--print, .mr-print-row { display: none; }
    .mr-preview-tiles { padding-bottom: 2.5rem; }
    .mr-preview-eyebrow { margin-top: 1.5rem; margin-bottom: 1rem; }
    .mr-preview-grid { gap: 0.75rem; }
    .mr-preview-tile:not(.hidden) { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 0.9rem; padding: 1.1rem 1.25rem; }
    .mr-preview-icon { grid-row: 1 / 3; margin-bottom: 0; }

    /* The answer leads, then the note on how far to trust it. */
    [data-land-on-results] { display: flex; flex-direction: column; }
    [data-land-on-results] > * { min-width: 0; }
    .mr-dq { padding: 1rem; }

    /* The ledger above, with the one figure the page exists to show leading it. */
    .mr-ledger { display: flex; flex-direction: column; order: -1; }
    .mr-ledger .mr-stat-card { grid-column: auto; }
    .mr-ledger:has(.mr-stat-card--lead) .mr-stat-card:first-child:not(.mr-stat-card--lead) { border-top: 1px solid var(--oat); }
    .mr-ledger .mr-stat-card.mr-stat-card--lead { order: -1; border-top: 0; padding-block: 0.9rem; }
    .mr-ledger .mr-stat-value { text-align: right; }
    .mr-ledger .mr-stat-card:not(:has(> .mr-stat-sub)) .mr-stat-label { grid-row: 1 / 3; }
    .mr-ledger .mr-stat-card--lead .mr-stat-value { color: var(--mint-text); }
    .mr-ledger .mr-stat-card--lead .mr-stat-value { font-size: 1.75rem; letter-spacing: var(--tracking-figure); }
    .mr-ledger .mr-stat-value .mr-stat-sub { display: inline; margin: 0; }

    .mr-pager { flex-wrap: wrap; gap: 0.5rem 0.75rem; }
    .mr-pager > div { margin-left: auto; }
    .mr-pager .mr-btn-ghost { min-height: 2.75rem; padding-inline: 1.25rem; white-space: nowrap; }
    .js-radius-map { height: 18rem !important; }
  }

  /* Launch directory — card grid, filters, showflat modal.
     Included INSIDE a <style nonce> block, after mr-styles.njk (which defines the tokens).
     Mint is decorative only (button fills, focus rings) — never readable text. */
  /* Explicit columns, not auto-fill: the target is a 4-up row on desktop, and
     auto-fill would give 3 or 5 depending on the container's exact width. The
     grid section is max-w-7xl (1280px) so four cards land at ~290px each. */
  .lc-grid {
    display: grid; gap: 1.5rem;
    grid-template-columns: 1fr;
  }
  @media (min-width: 640px)  { .lc-grid { grid-template-columns: repeat(2, 1fr); } }
  @media (min-width: 960px)  { .lc-grid { grid-template-columns: repeat(3, 1fr); } }
  @media (min-width: 1200px) { .lc-grid { grid-template-columns: repeat(4, 1fr); } }
  /* While a filter request is in flight (hx-indicator="#lc-grid-wrap" in launch-filters.njk)
     the grid dims, so a slow response is visibly "loading" rather than silently stale. */
  #lc-grid-wrap { transition: opacity 0.15s; }
  #lc-grid-wrap.htmx-request { opacity: 0.55; }
  .lc-card {
    position: relative;                 /* containing block for .lc-link::after */
    display: flex; flex-direction: column;
    background: #fff; border: var(--border-card);
    border-radius: var(--radius-card); overflow: hidden;
  }
  @media (prefers-reduced-motion: reduce) { .lsm-submit { transition: none; } }
  /* No lift on hover (David 15/10/2026): only the photo zooms (.lc-media img below). */
  /* Every hero renders at the SAME 3:2 box regardless of the source image's shape
     (scraped renders range from 0.85 to 4.94 — near-square to letterbox).
     The <img> is absolutely positioned ON PURPOSE: in normal flow its height:100%
     resolves against an indefinite height, falls back to auto, and the IMAGE's
     natural height then defines the box — so aspect-ratio is silently ignored and
     a tall render makes its card taller than the rest of the row. Out of flow, the
     ratio box wins and object-fit crops. Don't "simplify" this back to static. */
  .lc-media {
    position: relative; display: block;
    background: var(--cream-alt);
    aspect-ratio: 3 / 2; overflow: hidden;
  }
  .lc-media img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover; object-position: center;
    display: block;
    transition: transform 0.3s var(--ease-out);
  }
  /* Hover / keyboard-focus zoom, 5%, clipped by .lc-media's overflow (David 09/10/2026). */
  .lc-card:hover .lc-media img, .lc-card:focus-within .lc-media img { transform: scale(1.05); }
  @media (prefers-reduced-motion: reduce) {
    .lc-media img { transition: none; }
    .lc-card:hover .lc-media img, .lc-card:focus-within .lc-media img { transform: none; }
  }
  .lc-stage {
    position: absolute; top: 0.75rem; left: 0.75rem;
    background: rgba(255,255,255,0.92); border: 1px solid var(--oat); border-radius: var(--radius-pill);
    padding: 0.3rem 0.75rem;
    font-family: var(--font-body);
    font-size: 0.75rem; font-weight: 600; color: var(--charcoal);
  }
  /* SOLD OUT corner ribbon on launch + nearby/facet cards (David 09/10/2026). Red is a
     deliberate palette exception for THIS ribbon only (root CLAUDE.md, Palette); white on
     --red-700 is 6.5:1. The host clips it: .lc-card already does, a nearby card takes
     .lp-sold-host. pointer-events: none, so the card's link still takes the click. */
  .lp-sold-host { position: relative; overflow: hidden; }
  /* A nearby card with no photo slot has text where the ribbon lies. The band narrows the line
     by ~2.75rem at the card's top edge and ~1.5rem by the name's line, so each line is kept
     clear by that much — and only there: on a photo the ribbon lies over the picture. */
  .lp-sold-host:not(:has(> .lp-nearby-img)) .lp-nearby-stage { padding-right: 3rem; }
  .lp-sold-host:not(:has(> .lp-nearby-img)) .lp-nearby-name { padding-right: 1.5rem; }
  .lp-sold-ribbon {
    position: absolute; top: 1.05rem; right: -2.55rem; z-index: 3;
    width: 9rem; padding: 0.3rem 0;
    transform: rotate(45deg);
    background: var(--red-700); color: #fff;
    font-family: var(--font-body); font-size: 0.6875rem; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase; text-align: center;
    box-shadow: 0 2px 6px color-mix(in srgb, var(--charcoal) 25%, transparent);
    pointer-events: none;
  }
  .lc-body { padding: 0.625rem 1.25rem; display: flex; flex-direction: column; flex: 1; }
  .lc-title {
    font-family: var(--font-display);
    font-size: 1.375rem; font-weight: 600; letter-spacing: var(--tracking-title);
    color: var(--charcoal); margin-bottom: 0.35rem;
  }
  /* 0.75rem (12px, David 09/10/2026): units, handover date and tenure share ONE line, which
     the narrowest 4-column card (1200px viewport) only fits at this size. */
  .lc-district, .lc-meta {
    font-family: var(--font-body);
    font-size: 0.75rem; color: var(--mid); margin-bottom: 0.35rem;
  }
  .lc-meta { margin-bottom: 0; }
  /* A fact never breaks inside itself; the line breaks between facts. The separator is
     a ::before on every fact but the first, so it wraps WITH its fact rather than being
     stranded at the end of the previous line. */
  .lc-meta { display: flex; flex-wrap: wrap; column-gap: 0.4rem; row-gap: 0.1rem; }
  .lc-fact { white-space: nowrap; }
  /* Card icons (launch-icons.njk): the text's own grey, not the accent .lp-glyph sets on the
     launch pages — this rule out-ranks it where launch-page-styles is also loaded. */
  .lc-card .lp-glyph {
    display: inline-block; width: 0.875rem; height: 0.875rem; margin-right: 0.25rem;
    vertical-align: -0.15em; color: currentColor;
  }
  .lc-fact + .lc-fact::before { content: "\00b7"; margin-right: 0.4rem; }

  /* Stretched link: the whole card opens the detail page. The <a> stays a real link
     (title text, keyboard focus, crawlable); this overlay just extends its hit area
     over the entire card. The book button must out-rank it — see .lc-btn-book. */
  .lc-title a.lc-link { color: inherit; text-decoration: none; }
  .lc-link::after {
    content: ""; position: absolute; inset: 0; z-index: 1;
  }
  .lc-card:hover .lc-title { text-decoration: underline; text-underline-offset: 3px; }
  /* Keyboard focus. The title link rings the WHOLE card (it is the whole card's link), drawn on
     .lc-card itself: an element's own outline is never clipped by its own overflow, whereas a
     ring on the link or on the ::after overlay sits inside overflow:hidden. The browser's blue
     ring on the <a> is replaced, not added to. Older engines without :has() keep the overlay
     ring. */
  .lc-link:focus-visible::after { outline: var(--focus-ring); outline-offset: -3px; }
  @supports selector(:has(a)) {
    .lc-link:focus-visible { outline: none; }
    .lc-link:focus-visible::after { outline: none; }
    .lc-card:has(.lc-link:focus-visible) { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  }
  /* The flush book button: an inset ring in its own text colour (white on blueberry, charcoal on
     mint) — the card's overflow:hidden clipped the browser's outside ring to nothing. */
  .lc-card .lc-btn-book:focus-visible { outline: var(--focus-width) solid currentColor; outline-offset: -5px; }

  /* Flush to the card's bottom edge, full-bleed left-to-right, square corners.
     margin-top:auto pins it to the bottom of the flex column (.lc-body has flex:1).
     z-index:2 lifts it above .lc-link::after, or the card link would swallow the click. */
  .lc-btn-book {
    position: relative; z-index: 2;
    margin-top: auto; width: 100%; display: block;
    background: var(--mint); color: var(--on-mint); border: none; border-radius: 0;
    padding: 0.9rem 1.1rem; cursor: pointer;
    font-family: var(--font-body); font-size: 0.875rem; font-weight: 600;
    transition: background 0.2s;
  }
  .lc-btn-book:hover { background: var(--mint-dk); }

  /* Filters */
  .lc-filters {
    display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: flex-end;
    background: #fff; border: var(--border-card); border-radius: var(--radius-card);
    padding: 1.25rem 1.5rem;
  }
  .lc-filter-label {
    display: block; font-family: var(--font-body);
    font-size: 0.6875rem; font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase;
    color: var(--charcoal); margin-bottom: 0.35rem;
  }
  /* One control height for the whole bar (inputs, selects, the stage toggle), so the labels
     and the control edges line up; 2.75rem is also the 44px touch target. */
  .lc-select, .lc-input {
    border: var(--border-card); border-radius: 8px; background: var(--cream);
    height: 2.75rem; padding: 0 0.9rem; min-width: 12rem; max-width: 100%;
    font-family: var(--font-body); font-size: 0.9375rem; color: var(--charcoal);
  }
  /* A district option ("D09 · Orchard, Cairnhill, River Valley") sizes its <select> wider
     than a phone's filter card; let the field shrink to the card instead. */
  .lc-filters > div { min-width: 0; max-width: 100%; }
  .lc-input::placeholder { color: var(--soft); }
  /* Keyboard focus: the brand ring, not the browser's blue (same rule as .mr-input / .mr-select). */
  .lc-select:focus-visible, .lc-input:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  /* The search box leads and takes the slack; the two facets keep their natural width. */
  .lc-search { flex: 1 1 16rem; }
  .lc-search .lc-input { width: 100%; }
  /* Phones: Est. TOP and Stage share one row — TOP takes what the (tightened) toggle leaves.
     Flex wraps on the HYPOTHETICAL sizes (TOP's basis + the toggle's natural width + the gap),
     before any shrinking, so the basis must leave room on the narrowest phone: at 320px the
     card's content is 254px and the toggle 182px (measured 09/10/2026). */
  @media (max-width: 639px) {
    .lc-filters { padding: 1.25rem 1rem; column-gap: 0.5rem; }
    .lc-top-field { flex: 1 1 4rem; }
    .lc-top-field .lc-select { width: 100%; min-width: 0; padding-left: 0.6rem; padding-right: 0.4rem; font-size: 0.875rem; }
    .lc-stage-field { flex: none; }
    .lc-stage-field .lc-seg-btn { padding: 0 0.5rem; font-size: 0.8125rem; }
  }
  /* 320px (iPhone SE 1st gen): the toggle shrinks to ~160px so TOP keeps a usable ~86px. */
  @media (max-width: 359px) {
    .lc-stage-field .lc-seg-btn { padding: 0 0.4rem; font-size: 0.75rem; }
  }
  /* Wider than an iPad in portrait: all filters on one row. Search and district split the
     slack; a district option's natural width would otherwise wrap Stage onto a second row.
     The district select's 12rem floor is lifted inside its 1fr cell — at exactly 1024px that
     column is ~232px, so a longer option (or a translation) would overflow into the TOP
     column instead of shrinking; TOP keeps the floor, its column is auto-sized. Column flow +
     auto implicit columns: the <noscript> Apply button (a fifth child with JS off) gets its
     own column instead of stretching across a 1fr column on a second row. */
  @media (min-width: 1024px) {
    .lc-filters { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto; grid-auto-flow: column; grid-auto-columns: auto; }
    .lc-filters .lc-select { width: 100%; }
    .lc-filters > :nth-child(2) .lc-select { min-width: 0; }
  }
  /* Segmented stage toggle — same pill/inverted-active look as the /next-move
     step-7 toggle (.nm-seg-*), but radios so it works without JS. */
  .lc-seg-toggle { display: flex; gap: 2px; height: 2.75rem; padding: 4px; background: var(--oat-light); border-radius: var(--radius-pill); }
  .lc-seg-toggle input { position: absolute; opacity: 0; pointer-events: none; }
  .lc-seg-btn {
    display: flex; align-items: center; justify-content: center;
    border-radius: var(--radius-pill); padding: 0 1.1rem;
    font-family: var(--font-body);
    font-size: 0.875rem; font-weight: 600; color: var(--charcoal); cursor: pointer; text-align: center;
    transition: background 0.15s, color 0.15s, box-shadow 0.15s;
  }
  .lc-seg-toggle input:checked + .lc-seg-btn { background: var(--charcoal); color: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
  .lc-seg-toggle input:focus-visible + .lc-seg-btn { outline: var(--focus-width) solid var(--mint); outline-offset: var(--focus-offset); }

  .lc-filter-btn {
    background: var(--charcoal); color: #fff; border: none; border-radius: var(--radius-pill);
    padding: 0.6rem 1.5rem; cursor: pointer;
    font-family: var(--font-body); font-size: 0.875rem; font-weight: 600;
  }

  /* Showflat modal. #lsm-modal itself carries position:fixed/inset:0 (not just
     its .lsm-backdrop child) — its only child is position:fixed, which takes it
     out of flow and collapses the wrapper to a zero-size box. A zero-size box is
     "hidden" to visibility checks (Playwright's toBeVisible, some AT) even
     without the .hidden class ever being applied. */
  /* Stacking band: overlays sit at 10000+, ABOVE the public header's z-header (9999)
     (header.njk). The header is 9999 only to clear the decorative page-grain
     texture at 9998; at 9000 this modal sat under it, and since the header is
     opaque and 64px tall it covered .lsm-close (panel top + 1rem on a 90vh
     panel lands at ~52px). Any new full-screen overlay belongs at 10000+ too. */
  #lsm-modal { position: fixed; inset: 0; z-index: var(--z-overlay); }
  #lsm-modal.hidden { display: none; }
  .lsm-backdrop {
    position: fixed; inset: 0; z-index: var(--z-overlay);
    background: color-mix(in srgb, var(--charcoal) 55%, transparent); backdrop-filter: blur(4px);
    display: flex; align-items: center; justify-content: center; padding: 1rem;
  }
  .lsm-panel {
    position: relative; z-index: calc(var(--z-overlay) + 1); background: #fff;
    border: var(--border-card); border-radius: var(--radius-card); padding: 2rem;
    width: 100%; max-width: 460px; max-height: 90vh; overflow-y: auto;
    box-shadow: 0 24px 64px color-mix(in srgb, var(--charcoal) 18%, transparent);
  }
  .lsm-close {
    position: absolute; top: 1rem; right: 1rem;
    background: none; border: none; cursor: pointer; color: var(--charcoal); padding: 0.25rem;
  }
  .lsm-heading {
    font-family: var(--font-display);
    font-size: 1.5rem; font-weight: 600; letter-spacing: var(--tracking-heading);
    color: var(--charcoal); margin: 0 0 0.4rem; padding-right: 2rem;
  }
  .lsm-sub {
    font-family: var(--font-body);
    font-size: 0.875rem; color: var(--mid); line-height: 1.55; margin: 0 0 1.25rem;
  }
  .lsm-fields { display: flex; flex-direction: column; gap: 0.875rem; }
  .lsm-label {
    display: block; font-family: var(--font-body);
    font-size: 0.8125rem; font-weight: 500; color: var(--charcoal); margin-bottom: 0.35rem;
  }
  .lsm-input {
    width: 100%; box-sizing: border-box;
    border: var(--border-card); border-radius: 8px; background: var(--cream);
    padding: 0.65rem 0.9rem;
    font-family: var(--font-body); font-size: 0.9375rem; color: var(--charcoal);
    outline: none; transition: border-color 0.15s;
  }
  .lsm-input:focus { border-color: var(--mint); }
  /* Salesperson card (launch-agent-card.njk) — detail page + compact in the modal. */
  .lp-agent-card { margin-top: 1.5rem; padding: 1.25rem; background: #fff; border: var(--border-card); border-radius: var(--radius-card); text-align: left; font-family: var(--font-body); font-size: 0.8125rem; line-height: 1.5; color: var(--charcoal); }
  .lp-agent-head { display: flex; align-items: center; gap: 1rem; }
  .lp-agent-photo { width: 72px; height: 72px; border-radius: var(--radius-pill); object-fit: cover; flex: none; background: var(--cream); }
  .lp-agent-text { min-width: 0; }
  /* Two classes deep on purpose: it must beat `.lp-lead-pitch p` (its font size). */
  .lp-agent-card .lp-agent-text p { margin: 0; color: var(--charcoal); font-size: inherit; }
  /* Three deep: the rule above would otherwise shrink the name to body size. */
  .lp-agent-card .lp-agent-text .lp-agent-name { font-family: var(--font-display); font-size: 1.25rem; line-height: 1.2; font-weight: 600; }
  .lp-agent-card .lp-agent-text .lp-agent-role { color: var(--soft); margin-bottom: 0.25rem; }
  .lp-agent-card a { color: var(--charcoal); text-decoration: underline; text-underline-offset: 2px; }
  /* Registration details: whose number it is on the left, the number on the right. */
  .lp-agent-ids { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: 0.5rem 1rem; margin: 1rem 0 0; padding-top: 1rem; border-top: 1px solid var(--oat); }
  .lp-agent-ids dt { color: var(--soft); font-size: 0.75rem; }
  .lp-agent-ids dd { margin: 0; }
  .lp-agent-ids dd span { display: block; }
  .lp-agent-card--compact { display: flex; align-items: center; gap: 0.75rem; margin: 0 0 1rem; padding: 0.6rem 0.8rem; border-radius: 12px; font-size: 0.75rem; }
  .lp-agent-card--compact .lp-agent-photo { width: 48px; height: 48px; }
  .lp-agent-card--compact .lp-agent-text .lp-agent-name { font-size: 0.9375rem; }
  .lsm-phone-row { display: flex; gap: 0.5rem; }
  .lsm-phone-row > .lsm-input { flex: 1; min-width: 0; }
  .lsm-cc-btn { width: auto; display: flex; align-items: center; gap: 0.35rem; white-space: nowrap; cursor: pointer; }
  .lsm-optional { font-weight: 400; color: var(--mid); }
  .lsm-choices { margin-top: 0.875rem; }
  .lsm-choices > .lsm-label { padding: 0; }
  .lsm-message { margin-top: 0.875rem; }
  .lsm-textarea { resize: vertical; min-height: 3.5rem; line-height: 1.45; }
  .lsm-wa-link { display: inline-block; margin-top: 0.875rem; font-family: var(--font-body); font-size: 0.8125rem; font-weight: 600; color: var(--charcoal); text-decoration: underline; text-underline-offset: 3px; }
  .lsm-consents { display: flex; flex-direction: column; gap: 0.625rem; margin-top: 1rem; }
  .lsm-consent {
    display: flex; align-items: flex-start; gap: 0.625rem;
    font-family: var(--font-body);
    font-size: 0.75rem; color: var(--mid); line-height: 1.5; cursor: pointer;
  }
  .lsm-consent input { margin-top: 0.2rem; flex-shrink: 0; }
  .lsm-msg { font-size: 0.8125rem; margin-top: 0.75rem; }
  .lsm-msg-err { color: var(--red-700); }
  .lsm-msg-ok { color: var(--mint-text); }
  .lsm-submit {
    display: block; width: 100%; margin-top: 1rem;
    background: var(--mint); color: var(--on-mint); border: none; border-radius: var(--radius-pill);
    padding: 0.75rem 1.5rem; cursor: pointer;
    font-family: var(--font-body); font-size: 0.9375rem; font-weight: 600;
    transition: background 0.2s, transform 0.15s;
  }
  .lsm-submit:hover { background: var(--mint-dk); transform: translateY(-1px); }
  .lsm-submit:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
  /* One form, two modes: the panel gets .lsm-mode-alert when a data-launch-alert
     trigger opened it, and the copy for the other mode is hidden. */
  .lsm-alert-only, .lsm-mode-alert .lsm-viewing-only { display: none; }
  .lsm-mode-alert span.lsm-alert-only { display: inline; }
  .lsm-mode-alert p.lsm-alert-only { display: block; }
  /* Success panel (launch-lead-form.njk). NO display rule on .lsm-done itself: this sheet
     loads after output.css, so one would beat Tailwind's .hidden and show it on page load.
     The ledes below are p-qualified so they tie `.lsm-mode-alert p.lsm-alert-only` and win
     on source order — else the alert-mode lede survives the submit. */
  .lsm-done-inner { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; padding: 1rem 0 0.5rem; text-align: center; }
  .lsm-done-icon { color: var(--mint); }
  #lsm-done .lsm-msg { margin: 0; font-size: 1rem; line-height: 1.5; }
  /* Focused by script only (role=status announces it); not a control, so no ring. */
  #lsm-done:focus { outline: none; }
  .lsm-sent p.lsm-sub, .lsm-sent p.lp-lead-lede { display: none; }

  /* Detail page (/new-launches/<slug>). The book button keeps .lc-btn-book — that
     class is the modal's click-binding contract — and .lp-book only turns the card's
     full-bleed bar into a pill. */
  .lp-back { color: var(--charcoal); text-decoration: none; position: relative; }
  /* A 44px tap target on a 16px line, without moving the layout. */
  .lp-back::after { content: ''; position: absolute; inset: -14px -8px; }
  .lp-back:hover { text-decoration: underline; text-underline-offset: 3px; }
  /* In-page anchors: clear the sticky launch header (its height is --lp-header-h, defined
     ONCE in launch-page-styles.njk) plus a breath. #lp-final's rule is owned by the
     lead-form band and reads the same variable. */
  .lp-main > [id]:not(#lp-final) { scroll-margin-top: calc(var(--lp-header-h) + 0.75rem); }
  .lp-hero {
    background: var(--cream-alt); border: var(--border-card); border-radius: var(--radius-card);
    overflow: hidden; aspect-ratio: 3 / 2; position: relative;
  }
  /* Full-bleed variant: square, borderless, edge to edge and flush under the header. */
  .lp-hero.lp-hero-bleed { border: 0; border-radius: 0; }
  /* On a wide screen 3:2 would be ~960px tall and push every stat card below the fold,
     so the height is clamped instead — and `aspect-ratio` MUST go with it: the two
     together shrink the WIDTH to height × 1.5 (1020px at 1440) rather than cropping the
     photo, which is the opposite of full bleed. Below 1024px the ratio still rules, so a
     phone gets a 3:2 hero rather than a 650px wall of image. */
  @media (min-width: 1024px) {
    .lp-hero.lp-hero-bleed { aspect-ratio: auto; height: min(72vh, 640px); }
  }
  .lp-hero-back {
    position: absolute; top: 0.9rem; left: 1rem; z-index: 2; margin: 0;
    font-family: var(--font-body); font-size: 0.8125rem; font-weight: 600;
  }
  .lp-hero-back a { color: rgba(255, 255, 255, 0.92); text-decoration: none; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); }
  .lp-hero-back a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
  /* The photo's qualifier, set like an image credit in the bottom-right corner, where the
     mask is darkest (0.58). z-index 2 keeps it above .lp-hero-copy's `inset: 0` box. */
  .lp-hero-credit {
    position: absolute; right: 1rem; bottom: 0.6rem; z-index: 2; margin: 0;
    font-family: var(--font-body); font-size: 0.6875rem; line-height: 1.4;
    color: rgba(255, 255, 255, 0.85); text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
  }
  /* Location + stage, top-left under the back link. On the photo at every width (the
     mask's top is lightest, so the shadow carries it); on cream in the no-image hero. */
  .lp-hero-loc { margin: 0; }
  .lp-hero .lp-hero-loc {
    position: absolute; top: 2.35rem; left: 1rem; right: 1rem; z-index: 2;
  }
  .lp-hero .lp-hero-loc .hp-over { color: rgba(255, 255, 255, 0.92); text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); }
  .lp-hero img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Title over the hero: a dark mask so white type holds up on ANY photo the admin
     uploads (a pale sky is the common case), plus a drop shadow for the edges the mask
     alone cannot carry. Both are needed — the mask sets the floor, the shadow the edge. */
  .lp-hero-mask {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.28) 0%, rgba(0, 0, 0, 0.58) 100%);
  }
  /* The copy block: static on cream below md (a 3:2 phone hero has no room for six lines),
     over the image from md. Text colour follows the placement, not the markup. */
  .lp-hero-copy {
    display: flex; flex-direction: column; align-items: center;
    gap: 0; text-align: center; padding: 1.75rem 1rem 2rem;
    max-width: var(--content-max); margin: 0 auto;
  }
  .lp-top-plain .lp-hero-copy { padding-top: 0.5rem; }
  @media (min-width: 768px) {
    .lp-top:not(.lp-top-plain) { position: relative; }
    .lp-top:not(.lp-top-plain) .lp-hero-copy {
      position: absolute; inset: 0; z-index: 1; justify-content: center; max-width: none;
    }
    /* Uppercased in CSS, not in the content: `launch.name` and `launch.tagline` stay as
       typed for the <title>, the meta description, the JSON-LD and the card grid. ~32
       characters a line, shrinking with the viewport so it fits the 3:2 box; a much longer
       tagline takes a third line — the admin's copy decision. Below md the title sits on
       cream, so it keeps .hp-h1's own size (2.25rem at 390px), same as the no-image hero. */
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-hero-title {
      color: #fff; text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
      text-transform: uppercase; letter-spacing: var(--tracking-button);
      max-width: 32ch; font-size: clamp(1.15rem, 3.7vw, 2.4rem);
    }
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-tagline { color: rgba(255, 255, 255, 0.92); }
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-hero-urgency,
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-fact-key,
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-fact-val {
      color: #fff; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
    }
    /* Facts over the photo: a charcoal fill under white type, no outline (an outline is
       the ghost-button look), so the box holds on any image; the fill does the contrast
       work the text-shadow did. */
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-chip {
      background: color-mix(in srgb, var(--charcoal) 62%, transparent);
    }
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-chip .lp-fact-key,
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-chip .lp-fact-val { text-shadow: none; }
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lc-btn-book:focus-visible,
    .lp-top:not(.lp-top-plain) .lp-hero-copy .lp-hero-wa:focus-visible { outline-color: #fff; }
  }
  .lp-hero-copy .hp-h1 { margin: 0; }
  /* The hero's own call to action: bigger than the in-page pills. It no longer grows on
     hover (the 1.3x scale was removed 28/09) — only the colour change every .lc-btn-book
     has. Never reintroduce a transform here below md: it extends scrollable overflow, and
     a tap's sticky :hover once left a 390px phone sideways-pannable. */
  .lc-btn-book.lp-hero-cta {
    padding: 0.85rem 1.9rem; font-size: clamp(0.95rem, 2.1vw, 1.15rem);
  }
  /* CSS-only tooltip on the handover label. Its containing block is set by the hero chips
     (launch-page-styles.njk): the chip LIST below md — hence `max-width: 100%` here, the
     list's width — and the chip itself from md, where that file re-caps it at 19rem / 90vw.
     A `visibility: hidden` absolute box still counts toward scrollable overflow, so the cap
     matters even while the pop is hidden. */
  .lp-lc { text-transform: none; }
  .lp-fact-val {
    margin: 0; font-family: var(--font-display); font-weight: 600;
    font-size: 1.25rem; line-height: 1.15; letter-spacing: var(--tracking-title); color: var(--charcoal, var(--charcoal));
    overflow-wrap: anywhere;
  }
  @media (min-width: 768px) { .lp-fact-val { font-size: 1.5rem; } }
  /* #55534e, not --soft: the lighter warm grey fails AA at this size. */
  .lp-fact-key {
    margin: 0.25rem 0 0; font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.03em;
    text-transform: uppercase; color: var(--mid);
  }
  .lp-tip { cursor: help; outline-offset: 3px; }
  .lp-tip-pop {
    position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 30;
    width: max-content; max-width: 100%; padding: 0.4rem 0.65rem;
    background: var(--charcoal, var(--charcoal)); color: #fff; border-radius: 8px;
    font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.08em; line-height: 1.35;
    text-align: left; text-transform: uppercase; white-space: normal;
    opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.15s ease;
  }
  .lp-tip:hover .lp-tip-pop, .lp-tip:focus .lp-tip-pop, .lp-tip:focus-visible .lp-tip-pop {
    opacity: 1; visibility: visible;
  }
  .lc-btn-book.lp-book {
    width: auto; margin-top: 0; border-radius: var(--radius-pill);
    padding: 0.65rem 1.5rem; font-size: 0.9375rem;
  }

  /* The lead form's <fieldset> reset (the form is on BOTH the grid pages' modal and the detail page — keep its CSS here, never in the detail-only partial). */
  .lsm-step { border: none; padding: 0; margin: 0; min-width: 0; }

  /* Lead-gen page sections for /new-launches/<slug>. Included INSIDE the page's
     <style nonce> block after mr-styles.njk + launch-styles.njk (tokens + modal).
     Mint is decorative only — button fills, borders — never readable text. */

  /* THE header height — the bar, the menu's top inset and every scroll-margin-top read it. */
  :root { --lp-header-h: 4rem; }
  /* Sticky launch header (spec 2026-09-28 §Header). 9999 clears the page grain at 9998. */
  /* Whose site this is, above the header (Google Ads misrepresentation, 17/10/2026). Not
     sticky: it scrolls away, the header stays. */
  .lp-disclosure {
    background: var(--cream-alt); color: var(--mid);
    font-size: 0.8125rem; line-height: 1.25rem; padding: 0.5rem 1rem; /* whole px: a 19.5px line left the hero CTAs sub-pixel out of view */ text-align: center;
  }
  .lp-disclosure p { max-width: var(--content-max); margin: 0 auto; }
  /* Phones: a two-line short version + More; the full sentence opens in its place. */
  .lp-disclosure-short { display: none; }
  .lp-disclosure-more { all: unset; cursor: pointer; color: var(--charcoal); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; white-space: nowrap; }
  .lp-disclosure-more:focus-visible, .lp-disclosure-full:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  @media (max-width: 640px) {
    .lp-disclosure-short { display: block; }
    .lp-disclosure:not(.is-open) .lp-disclosure-full { display: none; }
    .lp-disclosure.is-open .lp-disclosure-short { display: none; }
  }
  .lp-disclosure strong { color: var(--charcoal); font-weight: 600; }
  .lp-disclosure a { color: var(--charcoal); text-decoration: underline; text-underline-offset: 2px; }
  [data-theme='dark'] .lp-disclosure { background: var(--color-surface); color: var(--color-text-muted); }
  [data-theme='dark'] .lp-disclosure strong, [data-theme='dark'] .lp-disclosure a, [data-theme='dark'] .lp-disclosure-more { color: var(--color-text); }
  .lp-header {
    position: sticky; top: 0; z-index: var(--z-header);
    height: var(--lp-header-h);
    background: color-mix(in srgb, var(--cream) 88%, transparent);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid transparent;
    transition: background 0.2s, border-color 0.2s;
  }
  .lp-header[data-scrolled] {
    background: color-mix(in srgb, var(--cream) 94%, transparent);
    border-bottom-color: var(--oat);
  }
  [data-theme='dark'] .lp-header { background: color-mix(in srgb, var(--color-surface) 88%, transparent); }
  [data-theme='dark'] .lp-header[data-scrolled] { background: color-mix(in srgb, var(--color-surface) 94%, transparent); }
  .lp-header-inner {
    max-width: 80rem; height: 100%; margin: 0 auto; padding: 0 1rem;
    display: flex; align-items: center; gap: 1rem;
  }
  .lp-header-brand { display: flex; align-items: center; flex: none; }
  .lp-header-logo { height: 2.25rem; width: auto; object-fit: contain; max-width: 200px; }
  /* Scrolls sideways rather than overflowing the bar when a long commercial nav meets
     the pill at 1024–1279px. A THIN scrollbar, not a hidden one: it is the only mouse cue
     that more links sit off to the right, and `overflow-x: auto` draws it only while the
     nav actually overflows. WebKit/Blink take the styled pseudo-element (Chrome ignores it
     once scrollbar-width is non-auto, and its native thin bar auto-hides on macOS);
     Firefox takes scrollbar-width. Centred by auto margins, not justify-content — an
     overflowing centred flex row cannot scroll to its start. The 4px padding keeps the
     focus ring inside the scroll box. */
  .lp-nav {
    display: none; flex: 1; min-width: 0; overflow-x: auto;
    scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--mid) 45%, transparent) transparent;
  }
  @supports selector(::-webkit-scrollbar) {
    .lp-nav { scrollbar-width: auto; scrollbar-color: auto; }
    .lp-nav::-webkit-scrollbar { height: 4px; }
    .lp-nav::-webkit-scrollbar-track { background: transparent; }
    .lp-nav::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--mid) 45%, transparent); border-radius: 2px; }
  }
  .lp-nav ul, .lp-nav-menu ul { list-style: none; margin: 0; padding: 0; }
  .lp-nav ul { display: flex; gap: 1.25rem; width: max-content; margin: 0 auto; padding: 4px; }
  .lp-nav a {
    position: relative; display: inline-block; padding: 0.35rem 0; white-space: nowrap;
    font-family: var(--font-body); font-size: 0.86rem; font-weight: 500;
    color: var(--charcoal); text-decoration: none;
  }
  /* A 2px mint underline (decorative — the text stays charcoal) grows on hover, stays on the current section. */
  .lp-nav a::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
    background: var(--mint); transform: scaleX(0); transform-origin: left; transition: transform 0.2s;
  }
  .lp-nav a:hover::after, .lp-nav a[aria-current]::after { transform: scaleX(1); }
  .lp-nav a:focus-visible, .lp-nav-menu a:focus-visible,
  .lp-nav-toggle:focus-visible, .lp-header-brand:focus-visible {
    outline: var(--focus-ring); outline-offset: var(--focus-offset);
  }
  .lp-nav-toggle {
    margin-left: auto; width: 2.5rem; height: 2.5rem; border: 0; background: none;
    border-radius: 0.5rem; cursor: pointer;
    display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 0.35rem;
  }
  .lp-nav-bar {
    display: block; width: 1.5rem; height: 2px; background: var(--charcoal);
    transition: transform 0.3s, opacity 0.3s; transform-origin: center;
  }
  .lp-nav-toggle.is-open .lp-nav-bar:nth-child(1) { transform: translateY(0.475rem) rotate(45deg); }
  .lp-nav-toggle.is-open .lp-nav-bar:nth-child(2) { opacity: 0; }
  .lp-nav-toggle.is-open .lp-nav-bar:nth-child(3) { transform: translateY(-0.475rem) rotate(-45deg); }
  @media (min-width: 1024px) {
    .lp-nav { display: block; }
    .lp-nav-toggle { display: none; }
  }
  /* Full-screen section menu under the bar (below lg). A sibling of the header — see the partial. */
  .lp-nav-menu {
    position: fixed; inset: var(--lp-header-h) 0 0 0; z-index: var(--z-overlay);
    background: var(--cream); overflow-y: auto; padding: 0.5rem 1rem 2rem;
  }
  .lp-nav-menu[hidden] { display: none; }
  [data-theme='dark'] .lp-nav-menu { background: var(--color-surface); }
  .lp-nav-menu a {
    display: block; padding: 0.9rem 0; border-bottom: 1px solid var(--oat);
    font-family: var(--font-body); font-size: 1.125rem; font-weight: 500;
    color: var(--charcoal); text-decoration: none;
  }
  @media (min-width: 1024px) { .lp-nav-menu { display: none !important; } }
  @media (prefers-reduced-motion: reduce) {
    .lp-header, .lp-nav a::after, .lp-nav-bar { transition: none; }
  }

  /* Room for the mobile sticky bar (launch-sticky-bar.njk, detail page only); none needed on
     desktop where it is hidden. Other .lp-main pages have no bar, so no padding. */
  .lp-main--sticky { padding-bottom: 5.5rem; }
  @media (min-width: 1024px) { .lp-main--sticky { padding-bottom: 0; } }

  /* The tagline's own line under the project name: 20px, as typed (not uppercased). */
  .lp-tagline {
    display: block; margin-top: 0.4rem;
    font-size: 1.25rem; line-height: 1.3; font-weight: 400; letter-spacing: 0;
    text-transform: none; color: var(--mid);
  }
  .lp-phone { color: var(--charcoal); font-weight: 600; text-decoration: none; }
  .lp-phone:hover { text-decoration: underline; text-underline-offset: 3px; }

  /* Hero chips: key above value (the reference's fact list), wrapping to two rows at 390px.
     DoVP tooltip anchoring is split by breakpoint. Below md the LIST is the positioned
     ancestor (chip static), so the pop is capped at the list's width: on a phone the chips
     wrap unpredictably and a chip-anchored pop ran past the viewport (scrollWidth 474 at
     390px — a hidden absolute box still counts as overflow). From md the chips sit in ONE
     centred row, so the pop anchors to its own chip again (left-aligned, above it) and caps
     at 19rem / 90vw. When DoVP is the LAST chip it is the row's rightmost, the only place a
     left-aligned pop could reach the right edge — so there it right-aligns instead. */
  .lp-chips {
    position: relative;
    display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem;
    margin: 0.25rem 0 0; padding: 0; list-style: none;
  }
  /* Each fact: key over value, two lines of near-equal size (28/09). FILLED, no outline,
     softer corners than the buttons: an outlined capsule is this site's ghost BUTTON, and
     one sitting above the two CTA capsules read as a third, dead button. White on cream;
     the over-image fill is in launch-styles.njk's md block. No `position` here — see the
     tooltip note above. */
  .lp-chip {
    min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
    padding: 0.6rem 1.25rem; border-radius: 14px;
    background: #fff;
  }
  @media (min-width: 768px) {
    .lp-chip { position: relative; }
    .lp-chip .lp-tip-pop { max-width: min(19rem, 90vw); }
    .lp-chip:last-child .lp-tip-pop { left: auto; right: 0; }
  }
  /* No-image hero: the back link on the band measure (the hero is not an .lp-band). */
  /* 1.25rem above = the ~20px between the link and the H1 below it, so the link belongs to the
     hero, not to the header it sat 1px under (David 11/10/2026). */
  .lp-top-back { max-width: var(--content-max); margin: 0 auto; padding: 1.25rem 1rem 0; }
  .lp-chip .lp-fact-key { margin: 0; font-size: 0.8125rem; line-height: 1.3; letter-spacing: 0.02em; }
  .lp-chip .lp-fact-val { margin: 0; font-size: 1rem; line-height: 1.3; }
  /* Sold out: solid charcoal on the cream hero AND over the photo (where the other chips
     are a 62% tint). Five classes deep to out-rank launch-styles.njk's over-image chip rules. */
  .lp-top .lp-hero-copy .lp-chips .lp-chip.lp-chip-sold { background: var(--charcoal); }
  .lp-top .lp-hero-copy .lp-chips .lp-chip-sold .lp-fact-key,
  .lp-top .lp-hero-copy .lp-chips .lp-chip-sold .lp-fact-val { color: var(--cream); text-shadow: none; }
  .lp-chip-sold .lp-fact-val { font-weight: 700; }
  .lp-chip-link { color: inherit; text-decoration: none; display: flex; flex-direction: column; align-items: center; gap: 0.1rem; }
  .lp-chip-link:hover .lp-fact-val { text-decoration: underline; text-underline-offset: 3px; }
  /* Urgency: one admin-typed line with a mint dot (decorative). Two lines at most at 390px. */
  .lp-hero-urgency {
    margin: 0; max-width: 40ch;
    font-family: var(--font-body); font-size: 0.9375rem; font-weight: 600;
    color: var(--charcoal);
  }
  .lp-hero-urgency::before {
    content: ''; display: inline-block; width: 0.5rem; height: 0.5rem; border-radius: var(--radius-pill);
    background: var(--mint); margin-right: 0.5rem; vertical-align: middle;
  }
  /* Two CTAs: stacked full-width on a phone, one row from md. The WhatsApp pill takes the
     Book pill's size (`.lc-btn-book.lp-book`) so the pair reads as siblings. */
  .lp-hero-ctas {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem;
    width: 100%; margin-top: 0.5rem;
  }
  @media (max-width: 767px) {
    .lp-hero-ctas > * { flex: 1 1 100%; justify-content: center; }
  }
  .lp-main .lp-hero-wa {
    display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none;
    padding: 0.65rem 1.5rem; font-size: 0.9375rem; font-weight: 600;
  }
  /* CEA PC 04-13(b) notice on a launch not approved for sale: solid charcoal on the cream
     hero AND over the photo (the sold-out chip's treatment), so it reads at a glance and
     holds on any image. Four classes deep to out-rank the over-image text rules. */
  .lp-approval-notice {
    margin: 0; max-width: 40ch; padding: 0.6rem 1.1rem; border-radius: 14px;
    font-family: var(--font-body); font-size: 0.9375rem; font-weight: 600; line-height: 1.4;
  }
  .lp-top .lp-hero-copy .lp-approval-notice.lp-approval-notice {
    background: var(--charcoal); color: var(--cream); text-shadow: none;
  }
  /* Hero rhythm (28/09): grouped, not one 10px gap for everything (.lp-hero-copy's gap is
     0). Name + tagline are one unit (the tagline's own margin); the urgency line hangs
     under them; the facts are a separate beat; the buttons, the destination, stand
     furthest off. Each element may be absent, so every gap is its own margin. */
  .lp-hero-copy > .lp-hero-urgency { margin-top: 1rem; }
  .lp-hero-copy > .lp-chips { margin-top: 1.5rem; }
  .lp-hero-copy > .lp-hero-ctas { margin-top: 1.75rem; }
  .lp-hero-copy > .lp-approval-notice { margin-top: 1rem; }
  @media (min-width: 768px) {
    .lp-hero-copy > .lp-chips { margin-top: 1.75rem; }
    .lp-hero-copy > .lp-hero-ctas { margin-top: 2.25rem; }
  }

  /* Every content section is a full-width BAND with a width-capped inner wrapper, so a
     section can take its own background (or border, or full-bleed media) without the
     colour stopping at the text column. The band carries the id and
     the vertical rhythm; the inner div carries the measure. */
  .lp-band { width: 100%; }
  .lp-band-inner { max-width: var(--content-max); margin-left: auto; margin-right: auto; padding-left: 1rem; padding-right: 1rem; }
  /* Tone alternation (spec 2026-09-28): the class comes from planSections, by RENDERED
     index, so it stays strict when sections drop out. Two variables so a future dark
     theme (public pages carry none today — theme-init.njk is portal-only) swaps once. */
  .lp-main {
    --lp-band-cream: var(--cream);
    --lp-band-white: #fff;
  }
  [data-theme='dark'] .lp-main {
    --lp-band-cream: var(--color-surface);
    --lp-band-white: var(--color-surface-alt);
  }
  /* Topographic contour texture on the alternating bands (David 01/11/2026): our own art,
     drawn by scripts/generate-topo-pattern.ts (seamless tiles; strength = the stroke
     opacity set there). Dark theme drops it. */
  .lp-tone-cream {
    background: url('/images/launch-topo-a.svg') 0 0 / 720px repeat, var(--lp-band-cream);
  }
  .lp-tone-white {
    background: url('/images/launch-topo-b.svg') 0 0 / 720px repeat, var(--lp-band-white);
  }
  [data-theme='dark'] .lp-tone-cream, [data-theme='dark'] .lp-tone-white { background-image: none; }
  /* The booking-form band: outside the cream/white alternation, so it takes its own third tone.
     It used to inherit the page cream, which made it identical to a cream FAQ above it. */
  .lp-tone-alt {
    background: var(--cream-alt);
  }
  /* Bands touch: the tone change IS the separation (no hairline, DESIGN.md). */
  .lp-band.lp-section {
    margin-bottom: 0;
    padding: 3.5rem 0;
  }
  @media (min-width: 768px) {
    .lp-band.lp-section {
      padding: 5rem 0;
    }
  }
  /* A cream band straight after the (cream) plain hero has no tone change to separate it, so
     its top padding only doubles the hero's own: ~160px of empty cream on a facet page. */
  .lp-top-plain + .lp-tone-cream.lp-section { padding-top: 0; }
  .lp-eyebrow {
    margin: 0 0 0.35rem;
  }
  .lp-h3,
  .lp-spec-band {
    font-family: var(--font-body);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--charcoal);
    margin: 0 0 0.75rem;
  }
  /* Commercial floor bands: a gap above every band heading but the first. */
  .lp-spec-band:not(:first-of-type) {
    margin-top: 1.25rem;
  }
  /* Site plan + elevation chart on one row from md (David 01/11/2026): equal-height white
     frames, the site plan 2fr (the wide drawing), each image contained in its frame. Stacked
     on a phone. */
  .lp-plan-pair { display: grid; gap: 1.5rem; }
  .lp-plan-pair-item { margin: 0; min-width: 0; }
  @media (min-width: 768px) {
    .lp-plan-pair { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 1.25rem; }
    .lp-plan-pair .lp-figure { height: clamp(20rem, 38vw, 30rem); }
    .lp-plan-pair .lp-figure img { height: 100%; object-fit: contain; }
  }
  /* Intro copy: 1–2 <p>s between the H2 and the body, capped at the reading measure. */
  .lp-intro,
  .lp-intro-line {
    max-width: 72ch;
    margin: 0 0 1.5rem;
  }
  /* Reading settings = the blog's .article-body (input.css): 17px / 1.75, paragraphs 1.15em
     apart. At 16px / 1.65 the prose read cramped (28/09) — Instrument Sans is a narrow,
     tightly spaced grotesque, so it also gets a hair of positive tracking. */
  .lp-intro p,
  .lp-intro-line {
    font-family: var(--font-body);
    font-size: 1.0625rem;
    line-height: 1.75;
    letter-spacing: 0.005em;
    color: var(--charcoal);
    margin: 0 0 1.15em;
    text-wrap: pretty; /* no one-word last line ("sales.") under a centred hero */
  }
  .lp-intro p:last-child {
    margin-bottom: 0;
  }
  /* Section image slots: a CSS ratio box; the img carries srcset (LAUNCH_RENDITIONS copies + the 1600w file). */
  .lp-slot-img {
    margin: 0 0 1.5rem;
  }
  .lp-slot-btn {
    all: unset;
    display: block;
    width: 100%;
    cursor: zoom-in;
  }
  .lp-slot-btn:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
    border-radius: var(--radius-card);
  }
  .lp-slot-img img {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border: var(--border-card);
    border-radius: var(--radius-card);
    background: var(--cream-alt);
  }
  .lp-slot-wide .lp-slot-img img {
    aspect-ratio: 21 / 9;
  }
  .lp-slot-cover img {
    aspect-ratio: 3 / 2;
  }
  .lp-slot-img figcaption {
    margin-top: 0.5rem;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    color: var(--soft);
  }
  /* About: prose + slot left, quick facts right (stacked under md). */
  .lp-about {
    display: grid;
    gap: 2rem;
  }
  @media (min-width: 768px) {
    .lp-about {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      gap: 2.5rem;
      align-items: start;
    }
  }
  .lp-about > :only-child {
    grid-column: 1 / -1;
  }
  /* The slot image follows the scroll while the Details table runs on past it, like the
     Location map (David 01/11/2026): the copy column stretches to the row, so the sticky
     image stops at the table's bottom edge and never overruns it. Nothing moves when the
     image already reaches the table. */
  @media (min-width: 768px) {
    .lp-about-copy { align-self: stretch; }
    .lp-about-copy .lp-slot-img { position: sticky; top: calc(var(--lp-header-h) + 1rem); }
    .lp-about-copy .lp-slot-img:last-child { margin-bottom: 0; } /* ends flush with the table */
  }
  /* Brochure card: cover left, copy + download right from md; single column without a cover. */
  .lp-brochure {
    display: grid;
    gap: 1.5rem;
  }
  .lp-brochure .lp-slot-img {
    margin: 0;
  }
  @media (min-width: 768px) {
    .lp-brochure.lp-brochure-cover {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      align-items: center;
      gap: 2rem;
    }
  }
  .lp-brochure-download {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
  }
  /* The quiet section CTA: a ghost pill at 0.875rem — one mint pill per screen is the
     hero's and the form's. Three classes deep so it out-ranks .lc-btn-book's mint block. */
  .lp-cta-inline.lp-cta-quiet {
    margin-top: 1.5rem;
  }
  .lc-btn-book.lp-book.lp-cta-sm,
  .lc-btn-book.lp-book.lp-fp-request {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    width: auto;
    margin: 0;
    /* White, not transparent: the bands' contour texture would show through (David 01/11). */
    background: var(--lp-band-white);
    color: var(--charcoal);
    border-radius: var(--radius-pill);
    border: var(--border-width) solid color-mix(in srgb, var(--charcoal) 25%, transparent);
    padding: 0.55rem 1.1rem;
    font-size: 0.875rem;
    font-weight: 500;
  }
  .lc-btn-book.lp-book.lp-cta-sm:hover,
  .lc-btn-book.lp-book.lp-fp-request:hover {
    background: var(--charcoal);
    color: #fff;
    border-color: var(--charcoal);
  }
  /* The shared FAQ partial paints bg-bg (--color-surface, neither band tone); let the
     band's tone show through. The band IS #lp-faq, so no ancestor tone selector. */
  #lp-faq .bg-bg {
    background: transparent;
    padding: 0;
  }
  /* The partial's own py/px move to the band, so the eyebrow sits with the heading and the
     phone gutter is the inner's 1rem once, not 1rem + the partial's px-4. */
  #lp-faq {
    padding: 3.5rem 0;
  }
  @media (min-width: 768px) {
    #lp-faq {
      padding: 5rem 0;
    }
  }
  #lp-faq .lp-eyebrow {
    text-align: center; /* the partial centres its heading */
  }

  .lp-h2 { font-size: clamp(1.5rem, 3vw, 2rem); margin-bottom: 1rem; }
  /* Content-band H2s at the headline register (spec 2026-09-28 "Visual rules"). Scoped
     to .lp-section so the form band's heading (#lp-final, no .lp-section) keeps the base. */
  .lp-section .lp-h2 { font-size: clamp(1.75rem, 3.2vw, 2.5rem); margin-bottom: 1.25rem; }
  /* Location: map left, amenity rows right; stacked below 768px. The map sticks while
     a long list scrolls past it. */
  .lp-loc { display: grid; gap: 1.5rem; }
  @media (min-width: 768px) {
    .lp-loc { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 2.5rem; align-items: start; }
    .lp-loc > :first-child { position: sticky; top: 6rem; }
    .lp-loc #lp-map { height: 420px; }
  }
  /* Icon beside a text label (amenity headers, District / Tenure chips). --mint-text is the
     readable accent (green on smh, blueberry on bnc); the icon is decoration, the word carries it. */
  .lp-glyph { display: inline-block; flex: none; color: var(--mint-text); vertical-align: -0.2em; margin-right: 0.45rem; }
  .lp-chip .lp-glyph { width: 1rem; height: 1rem; margin-right: 0.3rem; }
  .lp-poi-group { margin-bottom: 1.5rem; }
  .lp-poi-label {
    font-family: var(--font-body); font-size: 1rem; font-weight: 600;
    color: var(--charcoal); margin: 0 0 0.25rem; padding-left: 0.5rem;
  }
  .lp-poi {
    display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
    width: 100%; margin: 0; padding: 0.7rem 0.5rem; background: none; text-align: left;
    border: 0; border-bottom: 1px solid var(--oat); cursor: pointer;
    font-family: var(--font-body); font-size: 0.9375rem;
    color: var(--charcoal); transition: background 0.15s;
  }
  /* Nunjucks `title` does not capitalise after a bracket — "(boon Lay …)". CSS does,
     and it leaves an already-upper acronym (MRT) alone, so the two compose. */
  .lp-poi-n { text-transform: capitalize; }
  .lp-poi:hover { background: var(--cream-alt); }
  /* Selected row: mint is decorative here — the text stays charcoal. */
  .lp-poi.is-active { background: color-mix(in srgb, var(--mint) 14%, transparent); box-shadow: inset 3px 0 0 var(--mint); }
  .lp-poi-d { color: var(--soft); font-size: 0.875rem; white-space: nowrap; }
  .lp-poi-more { cursor: default; color: var(--soft); font-size: 0.875rem; }
  /* Nearest-places strip above the map: the .lp-fac tile recipe, as a real button. */
  .lp-near { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; margin-bottom: 1rem; }
  @media (min-width: 768px) { .lp-near { grid-template-columns: repeat(4, 1fr); } }
  .lp-near-tile {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.2rem; min-width: 0;
    margin: 0; padding: 0.8rem 1rem; text-align: left; cursor: pointer;
    background: var(--lp-band-white); border: var(--border-card); border-radius: var(--radius-card);
    font-family: var(--font-body); font-size: 0.9375rem; line-height: 1.35; color: var(--charcoal);
  }
  .lp-near-tile:hover { background: var(--cream-alt); }
  .lp-near-key { font-size: 0.6875rem; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--mid); }
  /* Same reason as .lp-poi-n: `title` misses a letter after a bracket. */
  .lp-near-name { text-transform: capitalize; overflow-wrap: anywhere; }
  .lp-near-d { color: var(--soft); font-size: 0.875rem; }
  .lp-list { columns: 1; padding-left: 1.1rem; margin: 0; }
  @media (min-width: 640px) { .lp-list { columns: 2; column-gap: 2rem; } }
  .lp-list li {
    font-family: var(--font-body);
    font-size: 0.9375rem; line-height: 1.6; color: var(--charcoal); break-inside: avoid;
  }

  /* Key/value + unit-mix tables: the shared .rk-table look, scoped. */
  .lp-price-band { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 1.5rem; margin: 0 0 1rem; }
  .lp-price-band strong { font-family: var(--font-display); font-size: 1.35rem; font-weight: 600; color: var(--charcoal); }
  .lp-price-asat { font-size: 0.8125rem; color: var(--mid); }
  .lp-table { width: 100%; border-collapse: collapse; font-family: var(--font-body); font-size: 0.9375rem; }
  .lp-table th, .lp-table td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--oat); vertical-align: top; }
  .lp-table th { font-size: 0.6875rem; font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--charcoal); }
  .lp-table td.lp-key { color: var(--mid); width: 38%; }
  /* The quick-facts "Details" header lines up with the cell text, not the card edge. */
  .lp-facts-card > .lp-h3 { padding: 0 0.75rem; }
  /* `.num` is the unit-mix table's Size and Units columns. Centred, HEADER AND CELL
     together — the header inherited the table's left alignment while the cells were
     right-aligned, so the two never lined up. */
  .lp-table th.num, .lp-table td.num { text-align: center; font-variant-numeric: tabular-nums; }
  .lp-table tr:last-child td { border-bottom: none; }
  /* Unit cards (launch-unit-cards.njk; live feed or hand-entered mix): a scroll-snap row on a
     phone like .lp-fp-row, a wrapping row from md (2 per row, 3 from lg). Cards stretch to one
     height per row; the actions sit at the bottom. The floor-plan thumbnail is the lightbox
     trigger. The photo (.lp-unit-photo) grows into the rest of the last row; when that row is
     full it wraps alone at its 14rem min height. */
  .lp-unit-row {
    display: flex; gap: 1rem; overflow-x: auto; scroll-snap-type: x mandatory;
    padding-bottom: 0.75rem; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  }
  @media (min-width: 768px) {
    .lp-unit-row { flex-wrap: wrap; overflow: visible; padding-bottom: 0; }
  }
  .lp-unit-photo { flex: 0 0 min(80vw, 18rem); scroll-snap-align: start; margin: 0; display: flex; flex-direction: column; min-width: 0; }
  .lp-unit-photo .lp-slot-btn { position: relative; flex: 1; min-height: 14rem; }
  .lp-unit-photo img {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
    border: var(--border-card); border-radius: var(--radius-card); background: var(--cream-alt);
  }
  .lp-unit-photo figcaption { margin-top: 0.5rem; font-family: var(--font-body); font-size: 0.8125rem; color: var(--soft); }
  .lp-unit-card {
    flex: 0 0 min(80vw, 18rem); scroll-snap-align: start; display: flex; flex-direction: column; gap: 0.4rem;
    padding: 1rem; background: var(--lp-band-white); border: var(--border-card); border-radius: var(--radius-card);
  }
  @media (min-width: 768px) {
    .lp-unit-card { flex: 0 0 calc((100% - 1rem) / 2); }
    .lp-unit-photo { flex: 1 1 16rem; }
    /* The photo matches its row's card height exactly (David 01/11/2026): the caption hangs
       below the figure instead of taking height from the image; the row leaves it room. */
    .lp-unit-photo { position: relative; }
    .lp-unit-photo figcaption { position: absolute; top: 100%; left: 0; }
    .lp-unit-row:has(.lp-unit-photo) { margin-bottom: 3rem; }
  }
  @media (min-width: 1024px) { .lp-unit-card { flex-basis: calc((100% - 2rem) / 3); } }
  .lp-unit-card[hidden] { display: none; }
  .lp-unit-plan { all: unset; display: block; cursor: zoom-in; border-radius: var(--radius-panel); overflow: hidden; }
  .lp-unit-plan img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: contain; background: var(--cream-alt); }
  .lp-unit-plan:hover img { opacity: 0.85; }
  .lp-unit-plan:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-unit-type { margin: 0.25rem 0 0; font-family: var(--font-display); font-size: 1.25rem; font-weight: 600; color: var(--charcoal); }
  .lp-unit-meta, .lp-unit-psf, .lp-unit-unpriced { margin: 0; font-size: 0.875rem; color: var(--mid); }
  .lp-unit-price { margin: 0; font-weight: 600; color: var(--charcoal); font-variant-numeric: tabular-nums; }
  .lp-unit-actions { margin-top: auto; padding-top: 0.5rem; display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }

  /* Nearby launches (launch-nearby.njk): cards as block links, the public card radius
     (24px, DESIGN.md's floor; David 05/10/2026) + card border; the compare picks wrap as
     pill-free labels. Mint stays decorative (the focus
     ring); every readable line is charcoal or --mid. */
  .lp-nearby-grid {
    display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    container-type: inline-size;
  }
  .lp-nearby-card {
    display: flex; flex-direction: column; gap: 0.3rem; padding: 1rem;
    background: var(--lp-band-white); border: var(--border-card); border-radius: var(--radius-card);
    color: var(--charcoal); text-decoration: none; transition: border-color 0.15s;
  }
  .lp-nearby-card:hover { border-color: var(--charcoal); }
  .lp-nearby-card:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  /* Affordability (#lp-afford), after narra-residences.homes (David 01/11/2026): a split head
     (eyebrow + big H2 | intro + quiet CTA from md), then three accent cards — big display
     title, cream body, a cream glyph bottom-right. The card is --mint-text (blueberry on bnc,
     deep green on smh; charcoal read dour, David 01/11): cream on it clears AA on both. */
  .lp-afford-head { display: grid; gap: 1rem 3rem; margin-bottom: 2.5rem; }
  @media (min-width: 768px) {
    .lp-afford-head { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: start; }
  }
  #lp-afford .lp-h2 { font-size: clamp(2.25rem, 5vw, 3.5rem); line-height: 1.05; margin: 0; text-wrap: balance; }
  .lp-afford-side p {
    font-family: var(--font-body); font-size: 1.0625rem; line-height: 1.75; color: var(--mid);
    /* The negative top margin eats the line's half-leading, so its first line's top sits
       level with the H2's (1.05 line-height, almost none). */
    margin: -0.35em 0 1.25rem; max-width: 34rem; text-wrap: pretty;
  }
  .lp-afford-grid { display: grid; gap: 1.25rem; }
  @media (min-width: 768px) {
    .lp-afford-grid { grid-template-columns: repeat(3, 1fr); }
    .lp-afford-card { min-height: 22rem; }
  }
  .lp-afford-card {
    --lp-afford-bg: var(--mint-text);
    position: relative; display: flex; flex-direction: column; gap: 1rem;
    padding: 2rem 1.75rem 6.5rem;
    /* Near-square corners like the reference (David 01/11), not the 24px public card radius. */
    border-radius: 8px;
    /* Silk (David 01/11/2026): soft glows of the card's own colour, lighter and darker, plus
       one pale diagonal sheen — all color-mix of --lp-afford-bg, so smh's green cards get the
       same. The light glows sit low and right (the icon corner); the text's top-left stays at
       or below the base colour, where cream measures 8.7:1 (bnc) / 5.7:1 (smh). The layers
       are 140% of the card, so the hover can drift them. */
    --lp-silk-hi: color-mix(in srgb, var(--lp-afford-bg) 66%, white);
    --lp-silk-mid: color-mix(in srgb, var(--lp-afford-bg) 82%, white);
    --lp-silk-lo: color-mix(in srgb, var(--lp-afford-bg) 70%, black);
    --lp-silk-at: 70% 85%;
    background:
      linear-gradient(118deg, transparent 40%, color-mix(in srgb, white 12%, transparent) 49%, transparent 57%),
      linear-gradient(155deg, transparent 58%, color-mix(in srgb, white 8%, transparent) 66%, transparent 74%),
      radial-gradient(55% 45% at var(--lp-silk-at), var(--lp-silk-hi), transparent 70%),
      radial-gradient(22% 70% at 92% 45%, var(--lp-silk-mid), transparent 75%),
      radial-gradient(60% 40% at 100% 0%, var(--lp-silk-mid), transparent 70%),
      radial-gradient(60% 50% at 15% 25%, var(--lp-silk-lo), transparent 75%),
      radial-gradient(80% 55% at 0% 100%, var(--lp-silk-mid), transparent 70%),
      var(--lp-afford-bg);
    background-size: 140% 140%;
    background-position: 0% 0%;
    color: var(--cream); text-decoration: none;
    transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out), background-position 1.2s var(--ease-out);
  }
  /* Each card's bright fold sits in a different place, so the three read as one cloth. */
  .lp-afford-card:nth-child(2) { --lp-silk-at: 30% 95%; }
  .lp-afford-card:nth-child(3) { --lp-silk-at: 85% 60%; }
  .lp-afford-card:hover { background-position: 30% 20%; transform: translateY(-4px); box-shadow: 0 18px 40px -18px color-mix(in srgb, var(--charcoal) 55%, transparent); }
  .lp-afford-card:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-afford-title {
    font-family: var(--font-display); font-size: clamp(1.5rem, 2.2vw, 1.875rem); font-weight: 500;
    line-height: 1.15; letter-spacing: var(--tracking-title); color: var(--cream); margin: 0; max-width: 14ch;
  }
  .lp-afford-text {
    font-family: var(--font-body); font-size: 0.9375rem; line-height: 1.7; margin: 0; max-width: 24ch;
    color: color-mix(in srgb, var(--cream) 92%, transparent);
  }
  .lp-afford-ico {
    position: absolute; right: 1.75rem; bottom: 1.75rem; width: 4.5rem; height: 4.5rem;
    fill: var(--cream); transition: transform 0.3s var(--ease-out);
  }
  /* The glyphs' inner details: a dark inset of the card colour (the silk has no one flat tone to match). */
  .lp-afford-cut { fill: color-mix(in srgb, var(--lp-afford-bg) 80%, black); }
  .lp-afford-card:hover .lp-afford-ico { transform: scale(1.08) rotate(-4deg); }
  .lp-afford-go {
    position: absolute; left: 1.75rem; bottom: 1.75rem; font-size: 1.25rem; color: var(--cream);
    transition: transform 0.3s var(--ease-out);
  }
  .lp-afford-card:hover .lp-afford-go { transform: translateX(6px); }
  @media (prefers-reduced-motion: reduce) {
    .lp-afford-card, .lp-afford-ico, .lp-afford-go { transition: none; }
    .lp-afford-card:hover, .lp-afford-card:hover .lp-afford-ico, .lp-afford-card:hover .lp-afford-go { transform: none; }
  }
  [data-theme='dark'] .lp-afford-side p { color: var(--color-text-muted); }
  [data-theme='dark'] .lp-fac, [data-theme='dark'] .lp-fac-h { color: var(--color-text); }
  /* Facilities (#lp-facilities): the brochure inventory — the 4th gallery image beside the
     themed lists (photo column from md, stacked below), each theme a glyph + heading and its
     lines in two columns from sm. Hidden lines and themes wait for the "Show all N" toggle. */
  .lp-fac-layout { display: grid; gap: 1.5rem; align-items: start; }
  @media (min-width: 768px) {
    .lp-fac-layout--photo { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 2.5rem; }
  }
  .lp-fac-photo .lp-slot-img { margin: 0; }
  @media (min-width: 768px) { .lp-fac-photo { position: sticky; top: calc(var(--lp-header-h) + 1rem); } }
  .lp-fac-inventory { display: flex; flex-direction: column; gap: 1.5rem; min-width: 0; }
  .lp-fac-group[hidden] { display: none; }
  .lp-fac-h {
    display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0.5rem;
    font-family: var(--font-display); font-size: 1.25rem; font-weight: 600; letter-spacing: var(--tracking-title); color: var(--charcoal);
  }
  .lp-fac-h .lp-glyph { flex: none; }
  .lp-fac-list { list-style: none; margin: 0; padding: 0; columns: 1; column-gap: 2rem; }
  @media (min-width: 640px) { .lp-fac-list { columns: 2; } }
  .lp-fac {
    break-inside: avoid; padding: 0.4rem 0; border-bottom: 1px solid color-mix(in srgb, var(--oat) 70%, transparent);
    font-family: var(--font-body); font-size: 0.9375rem; line-height: 1.5; color: var(--charcoal); min-width: 0;
  }
  .lp-fac span { overflow-wrap: anywhere; }
  .lp-fac[hidden] { display: none; }
  .lp-fac-toggle {
    margin-top: 1rem; padding: 0.45rem 1rem; border-radius: var(--radius-pill); border: var(--border-card); background: var(--lp-band-white);
    font-family: var(--font-body); font-size: 0.875rem; font-weight: 600;
    color: var(--charcoal); cursor: pointer; transition: border-color 0.15s;
  }
  .lp-fac-toggle:hover { border-color: var(--charcoal); }
  .lp-fac-toggle:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-nearby-img {
    display: block; width: calc(100% + 2rem); margin: -1rem -1rem 0.5rem; max-width: none;
    aspect-ratio: 16 / 9; background: var(--cream-alt); overflow: hidden;
    border-radius: var(--radius-card) var(--radius-card) 0 0;
  }
  /* The photo fills its frame; on hover (or keyboard focus) it zooms 5% INSIDE the frame,
     which clips it (David 09/10/2026; the grid card's .lc-media does the same). */
  .lp-nearby-img > img {
    display: block; width: 100%; height: 100%; object-fit: cover;
    transition: transform 0.3s var(--ease-out);
  }
  .lp-nearby-card:hover .lp-nearby-img > img,
  .lp-nearby-card:focus-visible .lp-nearby-img > img { transform: scale(1.05); }
  @media (prefers-reduced-motion: reduce) {
    .lp-nearby-img > img { transition: none; }
    .lp-nearby-card:hover .lp-nearby-img > img,
    .lp-nearby-card:focus-visible .lp-nearby-img > img { transform: none; }
  }
  /* Photo-less card (URA-only project): the same 16:9 slot as the photo, an icon on cream-alt. */
  .lp-nearby-img--none { display: flex; align-items: center; justify-content: center; }
  .lp-nearby-img--none .lp-glyph { width: 2.25rem; height: 2.25rem; margin: 0; opacity: 0.8; }
  /* One card per row (the grid narrower than two 14rem columns + gap): no row to keep in one
     shape, so the icon tile drops to a strip. A 16:9 tile per card made a 14-project district
     page ~5,100px on a phone (David 11/10/2026). Photos keep 16:9. */
  @container (max-width: 28.99rem) { .lp-nearby-img--none { aspect-ratio: 3 / 1; } }
  /* 0.6875rem (11px, the legibility floor — it was 10px): "DEVELOPER SALES TO MAY 2026" no longer
     fits the 192px a 14rem grid card leaves after padding, so the stage line may wrap onto a
     second line (as an EC tag already did); cards in a row stretch to the tallest. */
  .lp-nearby-stage {
    font-family: var(--font-body); font-size: 0.6875rem; font-weight: 600;
    letter-spacing: 0.06em; text-transform: uppercase; color: var(--mid);
  }
  /* The name is the card's link text, so it carries the accent: --mint-text, the readable
     one (green on smh, blueberry on bnc), never --mint (decorative only on white). */
  .lp-nearby-name { font-family: var(--font-display); font-size: 1.1rem; font-weight: 600; line-height: 1.25; overflow-wrap: anywhere; color: var(--mint-text); }
  .lp-nearby-card:hover .lp-nearby-name { text-decoration: underline; text-underline-offset: 3px; }
  /* Cards in a row stretch to the tallest. Name, stage and the district/tenure line stay at the
     top; the stats after them (psf, units sold, Expected TOP — whichever are present) sit on the
     card's bottom edge, so a two-line name or stage never shifts them out of line with the row. */
  .lp-nearby-name + .lp-nearby-meta + .lp-nearby-meta { margin-top: auto; }
  /* Cards with no stats lines (the project pages' nearby section): the district / distance /
     tenure line is the card's footer — pinned to the bottom edge under a rule, so it lines up
     across a row whatever the name or stage wraps to. */
  .lp-nearby-card--foot .lp-nearby-name { margin-bottom: 0.6rem; }
  .lp-nearby-card--foot .lp-nearby-name + .lp-nearby-meta {
    margin-top: auto; padding-top: 0.7rem; border-top: 1px solid var(--oat);
    font-size: 0.8125rem; /* 13px: the caption step */
  }
  .lp-nearby-meta { font-family: var(--font-body); font-size: 0.875rem; color: var(--mid); }
  /* Facet page stat cards: 2 to 4 of them, so the columns follow the count rather than leaving
     an empty fourth slot; two per row on a phone. */
  .lp-facet-stats { display: grid; gap: 1rem; margin-bottom: 1rem; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }
  /* Phones: an odd last card spans the row, so three read as 2 + 1 full width, not a lone half. */
  @media (max-width: 639px) {
    .lp-facet-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .lp-facet-stats > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  }
  /* /mrt's fallback station list (no line data): compact cards; the same row shape at every
     width, so mobile needs no layout of its own. */
  .lp-hub-list {
    list-style: none; margin: 0; padding: 0;
    display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  }
  /* Column: the name over the stats, which fill the rest of the card so the units line sits
     on its bottom edge and lines up across a grid row. */
  .lp-hub-card {
    display: flex; flex-direction: column; gap: 0.25rem; height: 100%; padding: 1rem 1.125rem;
    background: var(--lp-band-white); border: var(--border-card); border-radius: var(--radius-card);
    color: var(--charcoal); text-decoration: none; transition: border-color 0.15s;
  }
  .lp-hub-card:hover { border-color: var(--charcoal); }
  .lp-hub-card:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-hub-head { display: flex; align-items: baseline; }
  .lp-hub-name { min-width: 0; font-family: var(--font-display); font-size: 1.125rem; font-weight: 600; line-height: 1.3; }
  .lp-hub-card:hover .lp-hub-name { text-decoration: underline; text-underline-offset: 3px; }
  .lp-hub-stats { flex: 1; display: flex; flex-direction: column; }
  .lp-hub-units { margin-top: auto; }
  .lp-hub-meta { font-family: var(--font-body); font-size: 0.875rem; color: var(--mid); font-variant-numeric: tabular-nums; }
  .lp-hub-find { max-width: 24rem; margin: 0 0 1.25rem; display: flex; flex-direction: column; gap: 0.4rem; }
  .lp-hub-find[hidden], .lp-hub-list > li[hidden] { display: none; }
  /* The example placeholder is read, so it takes the readable muted ink (5.9:1 on the cream
     input), not the browser's light grey (~2:1). Opacity 1: Firefox dims placeholders. */
  #lp-hub-find::placeholder { color: var(--soft); opacity: 1; }
  /* Phones: tighter rows so /mrt's 100+ stations stay scannable; the filter does the rest. */
  @media (max-width: 639px) {
    .lp-hub-list { gap: 0.5rem; }
    /* 12px on phones only (David 05/10/2026): at ~66px tall the 24px card radius reads as a
       pill. A deliberate exception to DESIGN.md's 24px floor, these compact rows only. */
    .lp-hub-card { padding: 0.7rem 0.9rem; gap: 0.1rem; border-radius: var(--radius-panel); }
    .lp-hub-name { font-size: 1rem; }
    .lp-hub-meta { font-size: 0.8125rem; }
    /* One stats line on a phone row: there is room, and 100+ rows add up. Full-width rows have
       no neighbour to line up with, so the units line need not sit on the bottom edge. */
    .lp-hub-stats { flex: none; flex-direction: row; flex-wrap: wrap; column-gap: 0.4rem; }
    .lp-hub-units { margin-top: 0; }
    .lp-hub-stats .lp-hub-meta + .lp-hub-meta::before { content: '·'; margin-right: 0.4rem; }
  }
  /* District panels (/districts, /browse): three region cards, each a compact list of
     hairline-divided rows (aiproperty.sg's index density, David 09/10/2026): bold code, area
     names in --mid, project count at the right. All 28 districts are rows; one with no project
     is unlinked and muted (code drops to regular weight). One column below lg. */
  .lp-regions { display: grid; gap: 1.5rem; align-items: start; }
  @media (min-width: 1024px) { .lp-regions { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .lp-region {
    padding: 1rem; background: var(--lp-band-white);
    border: var(--border-card); border-radius: var(--radius-card);
  }
  .lp-region-label { margin: 0.25rem 0 0.5rem; padding: 0 0.5rem; }
  .lp-region .lp-region-list { display: block; }
  .lp-region-list > li + li { border-top: var(--border-width) solid var(--oat); }
  .lp-region-row {
    display: flex; align-items: center; gap: 0.5rem; min-height: 2.5rem; padding: 0.5rem;
    font-size: 0.875rem; line-height: 1.45;
    color: var(--charcoal); text-decoration: none; border-radius: var(--radius-panel);
    transition: background-color 0.15s;
  }
  a.lp-region-row:hover { background: var(--cream); }
  a.lp-region-row:hover .lp-region-name { text-decoration: underline; text-underline-offset: 3px; }
  a.lp-region-row:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-region-code {
    flex: none; min-width: 2.25rem; font-family: var(--font-body); font-weight: 600;
    font-variant-numeric: tabular-nums;
  }
  .lp-region-name { flex: 1; min-width: 0; font-family: var(--font-body); color: var(--mid); }
  .lp-region-count {
    flex: none; font-size: 0.8125rem; color: var(--soft); font-variant-numeric: tabular-nums;
  }
  .lp-region-row--empty, .lp-region-row--empty .lp-region-name { color: var(--soft); }
  .lp-region-row--empty .lp-region-code { font-weight: 400; }
  [data-theme='dark'] a.lp-region-row:hover { background: var(--lp-band-cream); }
  /* /browse. Bands sit in a wider column than --content-max so the district rows keep their
     names on one line at desktop. Section h2s are a step down from .lp-section's (an index
     page, not a project page). */
  /* /browse has no filter, so its three panels share one height (David 09/10/2026); /districts
     keeps `start`, where a filtered panel would otherwise stretch around one row. */
  .lp-browse-page .lp-regions { align-items: stretch; }
  .lp-band-inner--wide { max-width: 80rem; }
  @media (min-width: 768px) {
    .lp-band-inner--wide { padding-left: 2rem; padding-right: 2rem; }
  }
  .lp-browse-page .lp-section .lp-h2 { font-size: clamp(1.5rem, 2.6vw, 1.875rem); margin-bottom: 0.75rem; }
  .lp-browse-sub {
    max-width: 72ch; margin: 0 0 2rem; font-family: var(--font-body); font-size: 1rem;
    line-height: 1.6; color: var(--mid);
  }
  @media (min-width: 768px) { .lp-browse-sub { margin-bottom: 2.5rem; } }
  /* /browse photos (new-launch-browse.njk). The box is the aspect ratio; the photo covers it. */
  .lp-photo { display: block; width: 100%; height: auto; object-fit: cover; border-radius: var(--radius-card); }
  .lp-photo--photo { aspect-ratio: 3 / 2; }
  .lp-photo--tile { aspect-ratio: 16 / 9; }
  /* MRT: heading + copy beside the photo from md; the line rows run full width below. Top-aligned
     so this h2 starts where the other sections' do, not halfway down the photo. */
  .lp-browse-split { display: grid; gap: 1.5rem; align-items: start; margin-bottom: 2rem; }
  @media (min-width: 768px) {
    .lp-browse-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 3rem; margin-bottom: 2.5rem; }
  }
  .lp-browse-split .lp-browse-sub { margin-bottom: 1.25rem; }
  /* One row per MRT line, two columns from md. */
  .lp-browse-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
  @media (min-width: 768px) { .lp-browse-lines { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  /* Phones: the counts always take their own line, so short and long line names read alike. */
  @media (max-width: 767px) { .lp-browse-line .lp-hub-meta { flex-basis: 100%; } }
  .lp-browse-line {
    display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem 0.75rem; min-height: 44px;
    padding: 0.75rem 1rem; background: var(--lp-band-white); border: var(--border-card);
    border-radius: var(--radius-panel); color: var(--charcoal); text-decoration: none;
    transition: border-color 0.15s;
  }
  .lp-browse-line:hover { border-color: var(--charcoal); }
  .lp-browse-line:focus-visible, .lp-browse-more:focus-visible, .lp-browse-tile:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-browse-line-name { flex: 1 0 auto; font-family: var(--font-display); font-size: 1.125rem; font-weight: 600; }
  .lp-browse-more { font-weight: 600; color: var(--charcoal); text-decoration: underline; text-underline-offset: 3px; }
  /* Freehold + EC: two photo tiles, side by side from md; the whole tile is the link. */
  /* No sub line under this h2, so the tiles take the gap the other sections leave above their
     content (it collapses with the h2's own margin). */
  .lp-browse-pair { display: grid; gap: 1.5rem; margin-top: 2rem; }
  @media (min-width: 768px) { .lp-browse-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 2.5rem; } }
  .lp-browse-tile {
    display: flex; flex-direction: column; overflow: hidden; background: var(--lp-band-white);
    border: var(--border-card); border-radius: var(--radius-card); color: var(--charcoal);
    text-decoration: none; transition: border-color 0.15s;
  }
  .lp-browse-tile:hover { border-color: var(--charcoal); }
  .lp-browse-tile .lp-photo { border-radius: 0; }
  .lp-browse-tile-body { display: flex; flex-direction: column; gap: 0.5rem; flex: 1; padding: 1.25rem 1.5rem 1.5rem; }
  .lp-browse-tile-title { margin: 0; font-family: var(--font-display); font-size: 1.375rem; font-weight: 600; line-height: 1.2; letter-spacing: var(--tracking-title); }
  .lp-browse-tile-text { font-family: var(--font-body); font-size: 0.9375rem; line-height: 1.6; color: var(--mid); }
  .lp-browse-tile .lp-browse-more { margin-top: auto; padding-top: 0.5rem; }
  .lp-browse-tile:hover .lp-browse-more { text-decoration-thickness: 2px; }
  /* Small print under a card is held to the same reading measure as the intro copy (it ran ~160
     characters wide inside a full-width card). */
  .lp-band .mr-hint { max-width: 72ch; }
  .lp-compare-picks { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; margin: 0.5rem 0 0; }
  .lp-compare-pick {
    display: inline-flex; align-items: center; gap: 0.4rem; cursor: pointer; min-height: 44px;
    font-family: var(--font-body); font-size: 0.75rem; color: var(--charcoal); /* 12px */
  }
  .lp-compare-pick input { accent-color: var(--charcoal); width: 1.25rem; height: 1.25rem; }
  .lp-compare-pick input:disabled { cursor: not-allowed; }
  .lp-compare-pick:has(input:disabled) { color: var(--mid); cursor: not-allowed; }
  #lp-compare [data-compare-self] { background: var(--cream-alt); }
  /* Vertical compare: project names head the columns, measures run down the first column. */
  .lp-table.lp-compare-v { font-size: 0.875rem; } /* 14px (David, 05/10/2026); the other .lp-tables stay 15px */
  .lp-compare-v th[scope='col'] {
    text-align: left; vertical-align: bottom; min-width: 9rem;
    font-family: var(--font-display); font-size: 1.0625rem; letter-spacing: 0; text-transform: none;
  }
  .lp-nearby-grid + .lp-h3 { margin-top: 2.5rem; }
  /* The EC + freehold pages' district groups (David 09/10/2026): a menu of the districts on the
     page beside the groups (heading, then that district's cards). From lg it is a sticky column
     with the towns; below lg a collapsed "Jump to a district" of codes only (two copies of the
     links in the markup, one shown per width). */
  .lp-facet-layout { display: grid; gap: 1.5rem; }
  @media (min-width: 1024px) {
    .lp-facet-layout { grid-template-columns: 13rem minmax(0, 1fr); gap: 2.5rem; }
    /* Twelve districts with their towns run ~600px: on a short screen the menu scrolls itself. */
    .lp-facet-nav { position: sticky; top: 6rem; align-self: start; max-height: calc(100vh - 7rem); overflow-y: auto; }
  }
  .lp-facet-nav ul { margin: 0; padding: 0; list-style: none; }
  .lp-facet-nav a { color: var(--charcoal); text-decoration: none; font-family: var(--font-body); }
  .lp-facet-nav a:hover { text-decoration: underline; text-underline-offset: 3px; }
  .lp-facet-nav a:focus-visible, .lp-facet-nav summary:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  /* Phones: the toggle, then the codes as tap-size pills. */
  .lp-facet-nav-desk { display: none; }
  .lp-facet-nav-phone summary {
    display: inline-flex; align-items: center; gap: 0.6rem; min-height: 2.75rem; padding: 0 1.1rem;
    border: var(--border-card); border-radius: var(--radius-pill); background: #fff; cursor: pointer;
    list-style: none; font-family: var(--font-body); font-size: 0.9375rem; font-weight: 600; color: var(--charcoal);
  }
  .lp-facet-nav-phone summary::-webkit-details-marker { display: none; }
  .lp-facet-nav-phone summary::after {
    content: ''; width: 0.45rem; height: 0.45rem; margin-top: -0.2rem;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg);
  }
  .lp-facet-nav-phone[open] summary::after { margin-top: 0.2rem; transform: rotate(-135deg); }
  .lp-facet-nav-phone ul { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
  .lp-facet-nav-phone a {
    display: inline-flex; align-items: center; min-height: 2.75rem; padding: 0 1rem;
    border: var(--border-card); border-radius: var(--radius-pill); background: #fff;
    font-size: 0.875rem; font-weight: 700;
  }
  /* Desktop: the column, "D02 Anson, Tanjong Pagar" like the group heading — code bold, towns grey. */
  @media (min-width: 1024px) {
    .lp-facet-nav-phone { display: none; }
    .lp-facet-nav-desk { display: block; border-left: var(--border-width) solid var(--oat); }
    /* Two columns per entry (David 09/10/2026): the code on its own, every code aligned; the
       towns in a column of their own that wraps under itself, never under the code. */
    .lp-facet-nav-desk a {
      display: grid; grid-template-columns: 2.75rem minmax(0, 1fr); column-gap: 0.5rem; align-items: baseline;
      padding: 0.45rem 0 0.45rem 1rem; margin-left: calc(-1 * var(--border-width));
      border-left: var(--border-width) solid transparent; font-size: 0.8125rem; line-height: 1.35;
    }
    .lp-facet-nav-desk a:hover { border-left-color: var(--charcoal); }
    .lp-facet-nav-code { font-size: 1rem; font-weight: 700; color: var(--charcoal); }
    .lp-facet-nav-areas { color: var(--mid); }
  }
  .lp-facet-group + .lp-facet-group { margin-top: 2.5rem; }
  /* Lands below the PUBLIC header these pages use (73px desktop, ~93px when its nav wraps on a
     phone), not under it: --lp-header-h (the guard in launch-copy.test.ts) plus room for that. */
  .lp-facet-group-h { margin: 0 0 1rem; scroll-margin-top: calc(var(--lp-header-h) + 2.5rem); font-family: var(--font-display); font-size: 1.35rem; font-weight: 600; line-height: 1.25; color: var(--charcoal); }
  /* "D17 Loyang, Changi" on one line (David 09/10/2026): the towns inline, in the grey. */
  .lp-facet-group-areas { font-weight: 400; color: var(--mid); }
  /* Smooth jumps — only on a page that HAS the menu, never for reduced-motion users
     (the blog TOC's rule, input.css). */
  @media (prefers-reduced-motion: no-preference) { html:has(.lp-facet-nav) { scroll-behavior: smooth; } }
  .lp-compare-v th[scope='row'] { text-align: left; font-weight: 600; color: var(--mid); white-space: nowrap; padding-right: 1.25rem; }
  .lp-compare-v .lp-cmp-corner { min-width: 0; }
  /* Full-width rules. The corner <td> is the header row's last-child <td>, so `.lp-table
     tr:last-child td` stripped its border (the line under the header stopped short of the first
     column); the last body row's label is a <th>, which that rule skips, so it alone kept one.
     Header: line all the way across. Last row: none anywhere, as in every other .lp-table. */
  .lp-table.lp-compare-v thead td.lp-cmp-corner { border-bottom: 1px solid var(--oat); }
  .lp-table.lp-compare-v tbody tr:last-child th { border-bottom: none; }
  .lp-compare-v th[scope='col'] a { display: inline-block; padding: 0.7rem 0; color: var(--charcoal); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
  #lp-compare td a { color: var(--charcoal); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }

  /* Icon-bearing buttons. Scoped to this page's <main> so the shared mr-btn-* classes
     are untouched everywhere else on the site. */
  .lp-main .lp-book, .lp-main .mr-btn-wa, .lp-main .mr-btn-ghost {
    display: inline-flex; align-items: center; gap: 0.5rem;
  }
  .lp-ico { width: 16px; height: 16px; flex: none; }

  /* Inline CTA between sections. */
  .lp-cta-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-top: 1.25rem; }
  .lp-cta-inline .mr-hint { margin: 0; }

  /* Header booking pill: desktop only — below `lg` the sticky bar carries the action.
     Both classes, so it outranks `.lc-btn-book`'s own `display: block`. */
  .lc-btn-book.lp-header-book { display: none; padding: 0.5rem 1.1rem; font-size: 0.875rem; }
  @media (min-width: 1024px) { .lc-btn-book.lp-header-book { display: inline-flex; align-items: center; } }

  /* Every booking trigger and contact control on the page gets a visible keyboard ring. */
  .lc-btn-book:focus-visible, .lp-poi:focus-visible, .lp-near-tile:focus-visible,
  .lp-sticky-btn:focus-visible, .lp-fp-download:focus-visible {
    outline: var(--focus-ring); outline-offset: var(--focus-offset);
  }

  /* Image sections: site plan / elevation as a single figure, floor plans + gallery as grids.
     Every image is a lightbox trigger (a <button>, so it is keyboard-reachable). */
  .lp-figure {
    background: #fff; border: var(--border-card); border-radius: var(--radius-card); overflow: hidden;
    padding: 0; margin: 0; width: 100%; cursor: zoom-in; display: block;
  }
  .lp-figure img { display: block; width: 100%; height: auto; }
  .lp-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); }
  @media (min-width: 768px) { .lp-grid { grid-template-columns: repeat(3, 1fr); } }
  /* Gallery: ONE row that drifts on its own and can be dragged (David 01/11/2026; the
     script after #lp-gallery). One and a bit images on a phone (the next one peeks in), two
     from sm, three 500px-tall cards from md (below). No scroll-snap: snapping fights the drift and the drag. */
  .lp-gallery {
    display: grid; gap: 1rem; grid-auto-flow: column;
    grid-auto-columns: calc((100% - 1rem) / 1.25);
    overflow-x: auto; padding-bottom: 0.75rem;
    -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
    cursor: grab;
  }
  @media (min-width: 640px) { .lp-gallery { grid-auto-columns: calc((100% - 1rem) / 2); } }
  /* From md: three cards per view, each 500px tall (David 01/11/2026), the image cropped to
     fill it (object-fit: cover, from .lp-thumb img); the phone keeps the 4:3 card above. */
  @media (min-width: 768px) {
    .lp-gallery { grid-auto-columns: calc((100% - 2rem) / 3); }
    .lp-gallery .lp-thumb img { height: 500px; aspect-ratio: auto; }
  }
  .lp-gallery.is-dragging { cursor: grabbing; user-select: none; }
  .lp-gallery.is-dragging [data-lightbox-src] { cursor: grabbing !important; }
  .lp-gallery img { -webkit-user-drag: none; user-select: none; }
  /* A styled bar, so it stays visible (macOS hides the native one until you scroll) — the
     cue that more images sit off to the right. Same split as .lp-nav's. */
  .lp-gallery { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--mid) 45%, transparent) var(--cream-alt); }
  @supports selector(::-webkit-scrollbar) {
    .lp-gallery { scrollbar-width: auto; scrollbar-color: auto; }
    .lp-gallery::-webkit-scrollbar { height: 8px; }
    .lp-gallery::-webkit-scrollbar-track { background: var(--cream-alt); border-radius: 4px; }
    .lp-gallery::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--mid) 45%, transparent); border-radius: 4px; }
  }
  /* One-item variant (the elevation chart): keeps the thumbnail card-sized instead of
     letting a lone grid item stretch the full row. */
  .lp-grid.lp-grid-one { grid-template-columns: minmax(0, 320px); }
  .lp-thumb {
    background: #fff; border: var(--border-card); border-radius: 16px; overflow: hidden;
    padding: 0; margin: 0; cursor: zoom-in; text-align: left; display: flex; flex-direction: column;
  }
  .lp-thumb img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--cream-alt); }
  .lp-thumb.lp-thumb-plan img { object-fit: contain; background: #fff; }
  .lp-thumb figcaption {
    padding: 0.6rem 0.8rem;
    font-family: var(--font-body); font-size: 0.8125rem; font-weight: 600; color: var(--charcoal);
  }
  /* Floorplans: bedroom-type pills, one horizontal scroll row, bottom button. */
  .lp-fp-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem; }
  .lp-fp-filter {
    padding: 0.45rem 1rem; border-radius: var(--radius-pill); border: var(--border-card); background: #fff;
    font-family: var(--font-body); font-size: 0.875rem; font-weight: 600;
    color: var(--charcoal); cursor: pointer; transition: background 0.15s, border-color 0.15s;
  }
  .lp-fp-filter:hover { border-color: var(--charcoal); }
  .lp-fp-filter[aria-pressed="true"] { background: var(--charcoal); border-color: var(--charcoal); color: #fff; }
  .lp-fp-filter:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }
  .lp-fp-row {
    display: flex; gap: 1rem; overflow-x: auto; scroll-snap-type: x mandatory;
    padding-bottom: 0.75rem; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  }
  /* Every card is the same height and as wide as its plan's own proportions: the image
     has a fixed height and `width: auto`; `min-content` stops a long caption widening the
     card past the image; `height: 100%` fills the row-stretched card, so a two-line
     caption doesn't make one card taller. Uploads store no dimensions, so the width is
     known only once the image loads. `max-width` keeps a very wide plan within a phone
     screen (it letterboxes instead). */
  .lp-fp-card { flex: 0 0 auto; scroll-snap-align: start; }
  .lp-fp-card .lp-thumb { width: min-content; height: 100%; }
  .lp-fp-card .lp-thumb img {
    height: 280px; width: auto; aspect-ratio: auto; min-width: 6rem; max-width: calc(100vw - 4rem);
  }
  @media (min-width: 640px) { .lp-fp-card .lp-thumb img { height: 340px; } }
  .lp-fp-card[hidden] { display: none; }
  .lp-fp-actions { margin-top: 1.25rem; }
  .lp-fp-download { display: inline-flex; align-items: center; gap: 0.5rem; }

  #lp-map { height: 340px; border: var(--border-card); border-radius: 16px; overflow: hidden; background: var(--cream-alt); }

  /* Final band = the lead form (spec 2026-09-27 §A1), on the page background like the
     rest of the page — no dark panel (28/09/2026): pitch on the left, the form in a white
     card beside it from lg. */
  .lp-lead-pitch p { color: var(--charcoal); font-family: var(--font-body); font-size: 0.9375rem; }
  /* The CEA link (salesperson card) inherits the card's text colour and is underlined,
     so it reads as a link without a second accent. */
  .lp-cea-link { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
  .lp-cea-link:hover { text-decoration-thickness: 2px; }
  .lp-lead {
    display: grid;
    gap: 1.5rem;
    text-align: left;
  }
  @media (min-width: 1024px) {
    .lp-lead {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      align-items: start;
      gap: 2.5rem;
    }
    /* The salesperson card follows the scroll beside the taller form (David 01/11/2026), the
       same way as the About image: the pitch column stretches to the row, the card sticks. */
    .lp-lead-pitch { align-self: stretch; }
    .lp-lead-pitch > .lp-agent-card { position: sticky; top: calc(var(--lp-header-h) + 1rem); }
  }
  /* The form band is not an .lp-section (its H2 keeps the base size), so it needs its
     own top padding — without it the heading sat on the band's top edge (28/09). Same
     rhythm as .lp-band.lp-section; the bottom comes from the band's pb-20/md:pb-28. */
  #lp-final {
    padding-top: 3.5rem;
  }
  @media (min-width: 768px) {
    #lp-final {
      padding-top: 5rem;
    }
  }
  .lp-lead-form {
    background: #fff;
    color: var(--charcoal);
    border-radius: var(--radius-card);
    padding: 1.75rem 1.5rem;
  }
  @media (min-width: 768px) {
    .lp-lead-form { padding: 2.25rem 2.25rem 2rem; }
  }
  /* Breathing room for the page's form (28/09). The base .lsm-* rhythm (launch-styles.njk)
     put 14px between separate questions and ~6px between a label and its own field, so
     nothing read as a group. Here: label → field 0.5rem, question → question 1.5rem, and
     the consents set apart under a hairline as the form's closing step. Scoped to
     .lp-lead-form — the grid pages' modal shares the partial but lives in 90vh. */
  .lp-lead-form .lsm-fields { gap: 1.5rem; }
  .lp-lead-form .lsm-label { margin-bottom: 0.5rem; }
  .lp-lead-form .lsm-input { padding: 0.75rem 1rem; }
  .lp-lead-form .lsm-choices,
  .lp-lead-form .lsm-message { margin-top: 1.5rem; }
  .lp-lead-form .choice-row { gap: 0.625rem; }
  .lp-lead-form .lsm-wa-link { margin-top: 1.25rem; }
  .lp-lead-form .lsm-consents {
    gap: 0.875rem; margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--oat);
  }
  .lp-lead-form #lsm-turnstile:not(:empty) { margin-top: 1.25rem; }
  .lp-lead-form .lsm-msg { margin-top: 1rem; }
  .lp-lead-form .lsm-submit { margin-top: 1.5rem; padding: 0.9rem 1.5rem; }
  /* Book clicks scroll here; clear the sticky header. 89px = the slim header's tallest
     measured height (81px at 768 and 1280 wide, 69px at 390) + 8px. A header redesign
     sets --lp-header-h. Below lg the form card is the target (the pitch sits above it). */
  #lp-final, #lp-final .lp-lead-form { scroll-margin-top: var(--lp-header-h, 89px); }
  /* The pitch column is left-aligned; mr-styles centres .mr-cta-row site-wide. */
  .lp-lead-pitch .mr-cta-row { justify-content: flex-start; }
  /* The project name on its own line under "Book a showflat viewing at" (28/09). A block
     span, not a <br>: the space before it stays in the heading's text content. */
  .lp-lead-name { display: block; }
  /* The line under it shares the heading's measure: with the name split off, the heading's
     first line is ~22rem, and a full-column paragraph ran past it to a short last line —
     two blocks, two right edges. 24rem + balanced lines ends both near one edge (28/09). */
  .lp-lead-lede { max-width: 24rem; text-wrap: balance; }

  /* Sticky mobile bar: Chat / Call / Register. Hidden from lg up. */
  .lp-sticky {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 9000;
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
    background: var(--oat); border-top: var(--border-card);
    padding-bottom: env(safe-area-inset-bottom);
  }
  /* Pending approval (CEA PC 04-13): "Register Interest" in a third of a 360px bar wraps to
     two lines, so its column takes the room Chat and Call do not need. */
  .lp-sticky:has(> .lp-sticky-book[data-launch-alert]) { grid-template-columns: 1fr 1fr 1.6fr; }
  @media (min-width: 1024px) { .lp-sticky { display: none; } }
  .lp-sticky-btn {
    display: flex; align-items: center; justify-content: center;
    background: #fff; color: var(--charcoal); border: none; margin: 0; width: 100%;
    gap: 0.4rem; padding: 0.95rem 0.5rem; cursor: pointer; text-decoration: none;
    font-family: var(--font-body); font-size: 0.875rem; font-weight: 600;
  }
  .lp-sticky-btn.lc-btn-book { position: static; z-index: auto; border-radius: 0; }
  .lp-sticky-wa { background: #25D366; color: #fff; }
  .lp-sticky-book { background: var(--mint); color: var(--on-mint); }

  /* Lightbox: one for site plan, floor plans, elevation and gallery. 10000+ band. */
  #lp-lightbox { position: fixed; inset: 0; z-index: var(--z-overlay); background: color-mix(in srgb, var(--charcoal) 92%, transparent); display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 1rem; }
  #lp-lightbox.hidden { display: none; }
  /* The stage clips the zoomed image (no sideways page scroll) and takes the touch
     gestures so a drag pans instead of scrolling the page behind the dialog. */
  .lp-lb-stage {
    max-width: 100%; max-height: 82vh; overflow: hidden; border-radius: 12px;
    display: flex; align-items: center; justify-content: center; touch-action: none;
  }
  #lp-lightbox img {
    max-width: 100%; max-height: 82vh; object-fit: contain; background: #fff; border-radius: 12px;
    transform-origin: center; transition: transform 0.12s ease-out; user-select: none; -webkit-user-drag: none;
  }
  .lp-lb-zoom { position: absolute; bottom: 1rem; left: 50%; transform: translateX(-50%); display: flex; gap: 0.4rem; }
  /* `.lp-lb-btn` is absolute (the close/prev/next controls place themselves). These sit
     in a flex row instead, so they must return to flow — matched with BOTH classes so the
     rule wins regardless of which block comes first in this file. An absolute child would
     collapse the row to zero height and hang the buttons off the bottom edge. */
  .lp-lb-btn.lp-lb-zoom-btn { position: static; }
  .lp-lb-btn.lp-lb-zoom-btn[disabled] { opacity: 0.45; cursor: default; }
  .lp-lb-btn.lp-lb-level { width: auto; padding: 0 0.9rem; font-size: 0.8125rem; font-weight: 600; font-family: var(--font-body); }
  @media (prefers-reduced-motion: reduce) { #lp-lightbox img { transition: none; } }
  #lp-lightbox figcaption { color: #fff; font-family: var(--font-body); font-size: 0.875rem; margin-top: 0.75rem; text-align: center; }
  .lp-lb-btn {
    position: absolute; background: #fff; color: var(--charcoal); border: none; border-radius: var(--radius-pill);
    width: 44px; height: 44px; cursor: pointer; font-size: 1.25rem; display: flex; align-items: center; justify-content: center;
  }
  .lp-lb-close { top: 1rem; right: 1rem; }
  .lp-lb-prev { left: 0.75rem; top: 50%; transform: translateY(-50%); }
  .lp-lb-next { right: 0.75rem; top: 50%; transform: translateY(-50%); }
  /* In-page nav focuses the section it scrolls to (launch-header.njk); the section itself
     needs no ring: the focus marks the reading position, it is not an interactive target. */
  [id^="lp-"][tabindex="-1"]:focus { outline: none; }

