/* ==========================================================================
   IñigoSync — Design Tokens (Dark & Light Mode)

   Two layers, on purpose (see implementation_plan.md L2):

   1. SURFACE ELEVATION — --surface-page < --surface-band < --surface-card,
      plus the border and shadow that make each step readable. The old token
      set reused --color-bg-elevated for both "section band" and "card", which
      in light mode meant #FFFFFF for both: every card was white-on-white with
      a near-invisible #EFE3D2 hairline and no shadow. The invariant this
      layer exists to enforce is: **a card's background is never the same
      colour as the section it sits on, in either theme, and every card
      carries a visible border AND a shadow.**

   2. BACK-COMPAT ALIASES — Style/Auth.css, Style/Dashboard.css and
      Pages/user_dashboard.html all consume --color-bg / --color-bg-elevated /
      --color-bg-card / --color-line. Those names stay, as aliases of layer 1
      and never as independent values, so the dashboards keep working.
      They are declared once in :root and resolve through whichever
      --surface-* is active, so they are deliberately NOT redeclared in the
      light block below.

   Every --color-ink* value clears WCAG AA (4.5:1) against the lightest
   (dark mode) / darkest (light mode) surface it can land on. Do not lighten
   --color-ink-faint in light mode or darken it in dark mode without
   re-checking those ratios.

   TYPE — Revision A2 (implementation_plan.md): Style/typography.css (linked
   before this file on every page) is now the single owner of
   --font-display/--font-body/--font-mono/--font-numeric, the --fs-* size
   scale, --fw-* weights and --lh-* and --ls-* tokens. This file only consumes
   them. Every font-size below is one of the --fs-* tokens (a handful of
   deliberate clamp()s are kept for hero-scale fluid sizing — each says why
   inline); --font-mono usages were re-homed to --font-display (labels) or
   --font-numeric (numbers/prices) since Space Mono was removed.
   ========================================================================== */

/* Dark mode (default) */
:root {
    /* Surface elevation */
    --surface-page: #14110d;
    --surface-band: #191410;
    --surface-card: #241d16;
    --surface-card-hover: #2b231a;
    --border-soft: rgba(244, 239, 230, 0.14);
    --border-card: rgba(244, 239, 230, 0.20);
    --shadow-card: 0 6px 20px rgba(0, 0, 0, 0.35);
    --shadow-card-hover: 0 12px 34px rgba(0, 0, 0, 0.45);

    /* Back-compat aliases — see layer 2 above */
    --color-bg: var(--surface-page);
    --color-bg-elevated: var(--surface-band);
    --color-bg-card: var(--surface-card);
    --color-line: var(--border-soft);

    /* Ink */
    --color-ink: #f4efe6;
    --color-ink-dim: #c9c2b4;
    --color-ink-faint: #988e83;

    /* Accent */
    --color-primary: #FF6115;
    --color-primary-dim: #D4410A;
    /* The accent used as *text*. #FF6115 clears AA on dark surfaces but only
       reaches ~2.9:1 on the light surfaces, so light mode swaps in a deeper
       burnt orange of the same hue family. Buttons/borders keep
       --color-primary; only text switches. */
    --color-accent-text: #FF6115;

    --color-court-green: #3e7a5e;
    --color-alert: #d6482f;
    /* The alert colour used as *text*, same split as --color-accent-text
       above: #d6482f measures only 4.20:1 on the dark modal/card surfaces, so
       dark mode lifts it to a lighter red for copy. Borders, dots and filled
       badges keep --color-alert; only text switches. Light mode's
       --color-alert already clears AA (5.12:1 on --surface-band), so the two
       are the same value there. */
    --color-alert-text: #ee6f66;

    /* Floating pill navbar (overlaid on the hero photo, so it needs its own
       translucent surface + shadow rather than a flat section surface) */
    --color-nav-bg: rgba(28, 23, 18, 0.75);
    --color-nav-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);

    /* Type — Revision A2 (implementation_plan.md): fonts/sizes/weights now
       live in Style/typography.css, linked before this file on every page,
       so --font-display/--font-body/--font-mono (and the --fs-* and --fw-*
       scale) are defined once for the whole product instead of here. */
}

/* Light mode */
:root[data-theme="light"] {
    /* Surface elevation */
    --surface-page: #FFFCF4;
    --surface-band: #F7F0E4;
    --surface-card: #FFFFFF;
    --surface-card-hover: #FFFDF8;
    --border-soft: #E6D8C2;
    --border-card: #DCCBB1;
    --shadow-card: 0 6px 18px rgba(90, 64, 30, 0.10);
    --shadow-card-hover: 0 14px 32px rgba(90, 64, 30, 0.16);

    /* Ink */
    --color-ink: #1F1B16;
    --color-ink-dim: #6B6055;
    --color-ink-faint: #756C61;

    /* Accent (secondary role in light mode) */
    --color-primary: #FF6115;
    --color-primary-dim: #C4430A;
    --color-accent-text: #B93E08;

    /* Semantic */
    --color-court-green: #1E8E4A;
    --color-alert: #C2281F;
    --color-alert-text: #C2281F;

    /* Floating pill navbar */
    --color-nav-bg: rgba(255, 252, 244, 0.9);
    --color-nav-shadow: 0 10px 30px rgba(26, 20, 14, 0.12);
}

/* ==========================================================================
   Base
   ========================================================================== */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
    scroll-padding-top: 110px;
}

body {
    background-color: var(--surface-page);
    color: var(--color-ink);
    /* font-family/font-size/line-height come from Style/typography.css's
       base `body` rule (Revision A2) — not redeclared here so there is one
       source of truth (was line-height: 1.5; typography.css's --lh-normal
       is 1.55, a deliberate, in-scope typographic change). */
    transition: background-color 0.2s ease, color 0.2s ease;
}

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

a {
    color: inherit;
}

:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 3px;
}

.section-inner {
    max-width: 1120px;
    margin: 0 auto;
    padding: 0 80px;
}

/* Visually hidden but still reachable by screen readers / SEO crawlers —
   used for the page's real <h1> where the visible heading role is played
   by the rotating hero caption (an <h2>, since only one may be on-screen). */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.eyebrow {
    /* Revision A2 (implementation_plan.md) — Space Mono removed; this was a
       label role (uppercase eyebrow caption), not numeric content, so it
       moves to --font-display rather than --font-numeric. */
    font-family: var(--font-display);
    font-size: var(--fs-sm);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: var(--color-accent-text);
    margin-bottom: 14px;
}

.section-title {
    font-family: var(--font-display);
    font-weight: var(--fw-semibold);
    /* Revision A2 (implementation_plan.md) — kept as its own clamp rather
       than --fs-hero: this class is reused as every section's own header
       (Courts, About, Pricing, …), one visual step below the page hero
       (.hero-title, --fs-hero), and swapping in --fs-hero's larger range
       would inflate every section title to hero size and flatten that
       hierarchy. The clamp already gives it fluid, hero-style responsive
       sizing on its own terms. */
    font-size: clamp(1.8rem, 3vw, 2.6rem);
    letter-spacing: var(--ls-tight);
    line-height: var(--lh-tight);
    margin-bottom: 20px;
}

.body-text {
    font-size: var(--fs-base);
    line-height: 1.75;
    color: var(--color-ink-dim);
    max-width: 640px;
}

.section-intro {
    margin-bottom: 40px;
}

/* Scroll reveal */
.reveal {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity 0.7s ease, transform 0.7s ease;
}

.reveal.is-visible {
    opacity: 1;
    transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .reveal {
        opacity: 1;
        transform: none;
        transition: none;
    }
    html {
        scroll-behavior: auto;
    }
}

/* ==========================================================================
   Header / Nav

   There is deliberately NO bare `header { ... }` rule here any more. This file
   used to carry a full-width `header { position: fixed; background:
   rgba(20,17,13,.5); backdrop-filter: blur(10px) }` block left over from
   before the navbar became a floating pill, plus a
   `:root[data-theme="light"] header { background: rgba(255,252,244,.5) }`
   override. `.site-nav` (0,1,0) could beat the base rule (0,0,1) but not the
   light override (0,2,1) — so light mode painted a full-bleed translucent
   white, blurred band across the top of the hero while dark mode looked fine.
   Both blocks are deleted rather than overridden (implementation_plan.md L1);
   the floating pill below is the only navbar. Do not reintroduce a bare
   element selector here.
   ========================================================================== */

/* Floating pill navbar — overlaid on top of the hero photo and fixed to the
   viewport, so it stays visible while the page scrolls. */
.site-nav {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 5;
    display: block;
    padding: 22px 40px;
    background: none;
    backdrop-filter: none;
}

/* Cross-page fallout from deleting the legacy `header` rules above.
   Pages/user_dashboard.html loads this stylesheet *and* Style/Dashboard.css,
   and its `<header class="dash-topbar">` was silently picking up its
   light-mode surface from `:root[data-theme="light"] header` (0,2,1 beat
   `.dash-topbar`'s 0,1,0). Dashboard.css ships no light-mode override of its
   own — it only sets `background: rgba(20,17,13,.6)` — so without this the
   customer dashboard's topbar would turn into a dark grey band on a light
   page. Style/Dashboard.css is out of scope for this change, so the surface
   it was already getting is restated here explicitly rather than left to a
   deleted rule. Delete this the day Dashboard.css grows its own light block. */
:root[data-theme="light"] .dash-topbar {
    background: rgba(255, 252, 244, 0.5);
}

.site-nav-pill {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    background: var(--color-nav-bg);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid var(--border-soft);
    border-radius: 999px;
    padding: 10px 14px 10px 18px;
    box-shadow: var(--color-nav-shadow);
}

.site-nav .logo-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.site-nav .logo-image {
    display: block;
    width: 38px;
    height: 38px;
    object-fit: contain;
    flex-shrink: 0;
}

.site-nav .logo-text {
    font-size: var(--fs-lg);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.site-nav-links ul {
    display: flex;
    list-style: none;
    gap: 4px;
    align-items: center;
}

.site-nav-links a {
    text-decoration: none;
    color: var(--color-ink);
    font-weight: 600;
    font-size: var(--fs-md);
    padding: 9px 18px;
    border-radius: 999px;
    transition: background-color 0.2s ease, color 0.2s ease;
}

.site-nav-links a:hover {
    background: rgba(255, 97, 21, 0.12);
    color: var(--color-accent-text);
}

.site-nav-links a.active {
    background: var(--color-primary);
    color: #14110d;
}

:root[data-theme="light"] .site-nav-links a.active {
    background: var(--color-primary-dim);
    color: #FFFCF4;
}

.site-nav-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

/* Off-canvas mobile menu — hidden by default, only ever shown (as a fixed
   panel) below the nav-collapse breakpoint. See the responsive section. */
.menu-toggle-checkbox {
    display: none;
}

.menu-toggle {
    display: none;
    cursor: pointer;
    position: relative;
    width: 26px;
    height: 20px;
    flex-shrink: 0;
}

.menu-toggle-bar {
    position: absolute;
    left: 0;
    width: 100%;
    height: 2px;
    border-radius: 2px;
    background: var(--color-ink);
    transition: transform 0.25s ease, opacity 0.2s ease, top 0.25s ease;
}

.menu-toggle-bar:nth-child(1) { top: 0; }
.menu-toggle-bar:nth-child(2) { top: 9px; }
.menu-toggle-bar:nth-child(3) { top: 18px; }

/* Morph into an X once the off-canvas menu is open. .menu-toggle lives
   inside .site-nav-pill, a sibling of the checkbox — chain the descendant
   selector off the sibling combinator to reach it. */
.menu-toggle-checkbox:checked ~ .site-nav-pill .menu-toggle-bar:nth-child(1) {
    top: 9px;
    transform: rotate(45deg);
}

.menu-toggle-checkbox:checked ~ .site-nav-pill .menu-toggle-bar:nth-child(2) {
    opacity: 0;
}

.menu-toggle-checkbox:checked ~ .site-nav-pill .menu-toggle-bar:nth-child(3) {
    top: 9px;
    transform: rotate(-45deg);
}

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

.nav-menu {
    display: none;
}

nav ul {
    display: flex;
    list-style: none;
    gap: 40px;
    align-items: center;
}

nav ul li a {
    text-decoration: none;
    color: var(--color-ink);
    font-weight: 600;
    font-size: var(--fs-base);
    transition: color 0.25s;
}

nav ul li a:hover {
    color: var(--color-accent-text);
}

nav ul li a.active {
    color: var(--color-accent-text);
}

.theme-toggle {
    background: transparent;
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--color-ink);
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

.theme-toggle:hover {
    background-color: rgba(255, 97, 21, 0.1);
    border-color: var(--color-primary);
}

:root[data-theme="light"] .theme-toggle:hover {
    background-color: rgba(255, 97, 21, 0.08);
}

.theme-toggle svg {
    width: 20px;
    height: 20px;
}

.login-btn,
.book-now,
.cta-button {
    background-color: var(--color-primary);
    color: #14110d;
    padding: 12px 26px;
    border-radius: 999px;
    font-weight: 700;
    text-decoration: none;
    transition: background 0.25s, transform 0.2s;
    display: inline-block;
    text-align: center;
    border: none;
    cursor: pointer;
}

:root[data-theme="light"] .login-btn,
:root[data-theme="light"] .book-now,
:root[data-theme="light"] .cta-button {
    background-color: var(--color-primary-dim);
    color: #FFFCF4;
}

.login-btn:hover,
.book-now:hover,
.cta-button:hover {
    background-color: var(--color-primary-dim);
    transform: translateY(-1px);
}

:root[data-theme="light"] .login-btn:hover,
:root[data-theme="light"] .book-now:hover,
:root[data-theme="light"] .cta-button:hover {
    background-color: #A83408;
}

.book-now {
    padding: 10px 22px;
    font-size: var(--fs-base);
}

/* ==========================================================================
   Hero — rotating featured-event photos with the floating pill navbar
   and bottom-left caption overlaid on top
   ========================================================================== */
.hero {
    position: relative;
    min-height: 100vh;
    width: 100%;
    overflow: hidden;
}

.hero-media {
    position: absolute;
    inset: 0;
    z-index: 0;
}

.hero-media-slide,
.hero-media-slot {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity 1s ease;
}

.hero-media-slide { overflow: hidden; }
.hero-media-img { position: absolute; left: 50%; top: 110px; transform: translateX(-50%); width: calc(100% - 48px); height: calc(100% - 390px); object-fit: contain; }

@media (min-width: 1200px) {
    .hero-media-img { height: calc(100% - 320px); }
}

@media (prefers-reduced-motion: reduce) {
    .hero-media-slide,
    .hero-media-slot {
        transition: none;
    }
}

.hero-media-slide.is-active,
.hero-media-slot.is-active {
    opacity: 1;
    z-index: 1;
}

/* Image slot — used for hero slides whose event has no image_url yet (see
   includes/home-showcase.js and D2 in implementation_plan.md). A soft
   gradient + dotted pattern + a large translucent sport monogram, so a
   slide with no photo yet still looks intentional rather than broken. */
.hero-media-slot {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: radial-gradient(120% 120% at 50% 0%, var(--surface-band) 0%, var(--surface-page) 65%);
}

.hero-media-slot::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: radial-gradient(var(--border-soft) 1.5px, transparent 1.5px);
    background-size: 28px 28px;
    opacity: 0.6;
}

.hero-media-slot-monogram {
    position: relative;
    z-index: 1;
    font-family: var(--font-display);
    font-weight: var(--fw-semibold);
    /* Revision A2 (implementation_plan.md) — kept as its own clamp, not
       --fs-hero: this is a giant decorative background watermark letter
       (opacity 0.16) behind a hero slide with no photo yet, not a heading —
       --fs-hero's 3.5rem ceiling would shrink it drastically from its
       current 13rem max. */
    font-size: clamp(6rem, 20vw, 13rem);
    line-height: 1;
    color: var(--color-primary);
    opacity: 0.16;
    user-select: none;
}

.hero-scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(180deg, rgba(20, 17, 13, 0.55) 0%, rgba(20, 17, 13, 0.12) 32%, rgba(20, 17, 13, 0.92) 100%);
}

:root[data-theme="light"] .hero-scrim {
    background: linear-gradient(180deg, rgba(255, 252, 244, 0.8) 0%, rgba(255, 252, 244, 0.22) 36%, rgba(26, 20, 14, 0.82) 100%);
}

.hero-content {
    position: absolute;
    inset: auto 0 0 0;
    z-index: 2;
    padding: 0 min(80px, 6vw) 46px;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 40px;
}

.hero-copy-wrap {
    max-width: 640px;
}

.hero-copy-stack {
    position: relative;
}

.hero-copy {
    display: none;
}

.hero-copy.is-active {
    display: block;
    animation: hero-copy-in 0.6s ease both;
}

@keyframes hero-copy-in {
    from { opacity: 0; transform: translateY(14px); }
    to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .hero-copy.is-active {
        animation: none;
    }
}

.hero-tag {
    display: inline-block;
    /* Revision A2 (implementation_plan.md) — label role (uppercase pill), so
       it moves to --font-display, not --font-numeric. */
    font-family: var(--font-display);
    font-size: var(--fs-xs);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: #FFFCF4;
    background: var(--color-primary);
    padding: 5px 12px;
    border-radius: 999px;
    margin-bottom: 16px;
}

.hero-title {
    font-family: var(--font-display);
    /* Revision A2 (implementation_plan.md) — the page's real hero headline,
       so it maps to --fs-hero (was its own clamp(2rem,4.5vw,3.4rem), now
       clamp(2.25rem,5vw,3.5rem) — a near-identical fluid range). Sora renders
       heavier/wider than Oswald at the same weight, so the former Oswald 700
       steps down to --fw-semibold (600) to keep the same visual weight and
       avoid extra wrapping in the hero's 640px-wide copy column. */
    font-weight: var(--fw-semibold);
    font-size: var(--fs-hero);
    letter-spacing: -0.02em;
    line-height: 1.08;
    margin-bottom: 10px;
    color: #FFFCF4;
}

.hero-meta {
    /* Revision A2 (implementation_plan.md) — a date + free-text sentence,
       not a label or a number/ID/time value on its own, so it inherits the
       body font rather than taking --font-numeric or --font-display. */
    font-size: var(--fs-md);
    color: rgba(255, 252, 244, 0.86);
    margin-bottom: 26px;
}

.cta-buttons {
    display: flex;
    gap: 14px;
    justify-content: flex-start;
    flex-wrap: wrap;
}

.cta-button.secondary {
    background-color: rgba(255, 252, 244, 0.14);
    border: 1px solid rgba(255, 252, 244, 0.4);
    color: #FFFCF4;
}

.cta-button.secondary:hover {
    background-color: rgba(255, 252, 244, 0.22);
}

.hero-progress {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 14px;
    flex-shrink: 0;
}

.hero-progress-caption {
    /* Revision A2 (implementation_plan.md) — plain sentence ("Photos change
       every 5s"), inherits the body font. */
    font-size: var(--fs-xs);
    color: rgba(255, 252, 244, 0.7);
    white-space: nowrap;
}

.hero-dots {
    display: flex;
    gap: 8px;
}

.hero-dot {
    width: 34px;
    height: 4px;
    border-radius: 999px;
    background: rgba(255, 252, 244, 0.4);
    cursor: pointer;
    border: none;
    padding: 0;
    transition: background-color 0.2s ease;
}

.hero-dot:hover {
    background: rgba(255, 252, 244, 0.6);
}

.hero-dot.is-active {
    background: #FFFCF4;
}

/* ==========================================================================
   Image slots — shared by the court grid cards and the court viewer modal.
   The standalone Featured Events section (and with it .event-grid /
   .event-card*) was removed in the landing-page redesign; the event data it
   used still drives the hero carousel through the same
   window.InigoContent.getEvents() (see includes/home-showcase.js), it just
   no longer has a second grid of its own.

   No court photos exist in the DB yet (image_url is nullable on `court`);
   this renders a pattern + sport monogram instead of a broken <img>, and
   swaps in a real photo with no markup changes once the owner sets image_url
   through admin Court Listings (D2 in implementation_plan.md, OQ1 resolved:
   images are database-driven, none ship in the repo).
   ========================================================================== */
.media-slot {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: linear-gradient(150deg, var(--surface-band), var(--surface-page));
    border-bottom: 1px solid var(--border-soft);
    display: flex;
    align-items: center;
    justify-content: center;
}

.media-slot::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: radial-gradient(var(--border-card) 1px, transparent 1px);
    background-size: 16px 16px;
    opacity: 0.6;
}

.media-slot-monogram {
    position: relative;
    z-index: 1;
    font-family: var(--font-display);
    /* Revision A2 (implementation_plan.md) — Sora renders visibly heavier/
       wider than Oswald at the same weight, so display headings that were
       Oswald 700 step down to --fw-semibold (600) rather than --fw-bold. */
    font-weight: var(--fw-semibold);
    font-size: var(--fs-2xl);
    letter-spacing: 0.02em;
    color: var(--color-accent-text);
    opacity: 0.55;
}

.media-slot.has-image {
    background: var(--surface-band);
}

.media-slot.has-image::before {
    display: none;
}

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

/* ==========================================================================
   About — the Iñigos story + stats, then the Feedback & Reviews sub-block,
   then the CTA band. All three live inside #about so the nav's "About" link
   covers everything between Pricing and the site footer
   (implementation_plan.md L3/L6).

   Note the section itself is NOT .reveal: it is now several viewports tall,
   and the shared IntersectionObserver in includes/landingPage.js uses
   `threshold: 0.15` of the *target's own area*, which a very tall section can
   struggle to satisfy on short viewports. The three sub-blocks carry .reveal
   individually instead, which also gives a nicer staged reveal.
   ========================================================================== */
.about {
    padding: 100px 0 0;
    background: var(--surface-page);
    transition: background-color 0.2s ease;
}

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

.about-grid {
    display: grid;
    grid-template-columns: 1.4fr 1fr;
    gap: 60px;
    align-items: start;
}

.about-stats {
    display: flex;
    flex-direction: column;
    gap: 28px;
    border-left: 1px solid var(--border-soft);
    padding-left: 32px;
    transition: border-color 0.2s ease;
}

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

.stat-number {
    display: block;
    font-family: var(--font-display);
    font-size: var(--fs-3xl);
    /* Revision A2 (implementation_plan.md) — Oswald 700 steps down to
       --fw-semibold (600); Sora renders heavier at the same weight. */
    font-weight: var(--fw-semibold);
    color: var(--color-accent-text);
    line-height: 1;
}

.stat-label {
    font-size: var(--fs-md);
    color: var(--color-ink-faint);
}

/* ==========================================================================
   Courts & Pricing

   Each card is a real <button> (includes/landingPage.js renderCourtCard) that
   opens the court viewer for that sport, so keyboard and screen-reader
   support come for free — no tabindex/role patching. The rules below undo the
   UA button defaults (font, colour, alignment, width) so it still reads as a
   card, not a form control.

   Background: kept at --surface-page rather than adopting the old standalone
   #pricing section's --surface-band. That band existed to give Pricing its
   own visual identity as a distinct strip sitting BETWEEN two --surface-page
   sections (Courts above, About below); now that it is folded into Courts
   instead of sitting after it, Courts is once again directly followed by
   About, and .about is also --surface-page (see below) — matching keeps that
   handoff seamless instead of introducing a band that would flash on for one
   section and back off for the next.
   ========================================================================== */
.courts {
    padding: 100px 0;
    background: var(--surface-page);
    transition: background-color 0.2s ease;
}

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

.court-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
}

.court-card {
    /* Button reset. `display: flex` rather than `block` is deliberate: Chrome
       vertically centres a <button>'s anonymous content box when the button is
       taller than its contents, so in a stretched grid row the cards with a
       shorter note floated their photo and text in the middle instead of
       filling the card. An explicit flex column takes that layout over. */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    width: 100%;
    margin: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;

    /* Card surface — never the same colour as .courts, always bordered and
       lifted, in both themes (implementation_plan.md L2). */
    background: var(--surface-card);
    border: 1px solid var(--border-card);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: var(--shadow-card);
    transition: transform 0.25s ease, border-color 0.25s ease,
                background-color 0.2s ease, box-shadow 0.25s ease;
}

.court-card .media-slot {
    flex-shrink: 0;
}

.court-card-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    padding: 20px 24px 24px;
}

.court-card:hover,
.court-card:focus-visible {
    transform: translateY(-4px);
    background: var(--surface-card-hover);
    border-color: var(--color-primary);
    box-shadow: var(--shadow-card-hover);
}

/* Affordance that the card opens something, without adding markup.
   margin-top: auto pins it to the bottom of the card body so the row of
   "View" labels lines up across cards with different note lengths. */
.court-card-open {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 6px;
    margin-top: auto;
    padding-top: 12px;
    /* Revision A2 (implementation_plan.md) — label role ("View" affordance),
       so it moves to --font-display, not --font-numeric. */
    font-family: var(--font-display);
    font-size: var(--fs-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-accent-text);
}

.court-card-open svg {
    width: 12px;
    height: 12px;
}

@media (prefers-reduced-motion: reduce) {
    .court-card {
        transition: border-color 0.25s ease, background-color 0.2s ease;
    }

    .court-card:hover,
    .court-card:focus-visible {
        transform: none;
    }
}

.court-card h3 {
    font-family: var(--font-display);
    font-size: var(--fs-lg);
    font-weight: 600;
    margin-bottom: 6px;
}

.court-count {
    /* Revision A2 (implementation_plan.md) — leads with a number
       (.court-count-value), so it takes --font-numeric (tabular-nums)
       instead of the removed --font-mono. */
    font-family: var(--font-numeric);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    color: var(--color-accent-text);
    margin-bottom: 4px;
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.court-count-unit {
    font-family: var(--font-body);
    font-size: var(--fs-xs);
    color: var(--color-ink-faint);
}

.court-note {
    font-size: var(--fs-md);
    color: var(--color-ink-faint);
}

/* Rate + rating — see includes/landingPage.js's renderCourtCard. Rate is
   NULL for every court until the owner confirms prices (rendered ".is-tba"
   here); rating doesn't exist until database/schema/003_court_rating.sql is
   applied and only shows once a court actually has one. */
.court-rate {
    /* Revision A2 (implementation_plan.md) — a peso amount, so it takes
       --font-numeric (tabular-nums) instead of the removed --font-mono. */
    font-family: var(--font-numeric);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-base);
    color: var(--color-accent-text);
    margin-bottom: 4px;
}

.court-rate span {
    font-family: var(--font-body);
    font-size: var(--fs-xs);
    color: var(--color-ink-faint);
}

.court-rate.is-tba {
    font-family: var(--font-body);
    font-size: var(--fs-md);
    color: var(--color-ink-faint);
}

.court-rating {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: var(--fs-sm);
    color: var(--color-accent-text);
    margin-bottom: 4px;
}

.court-rating svg {
    width: 13px;
    height: 13px;
}

.court-rating span {
    color: var(--color-ink-faint);
    margin-left: 2px;
}

/* ==========================================================================
   Court viewer — ONE reusable dialog for every court card. Opened by
   includes/landingPage.js with the same normalized court object the grid
   renders, and the same renderMediaSlot() output, so while image_url is NULL
   it shows an honest large placeholder ("Photo coming soon") and swaps to the
   real photo the moment the owner sets one (OQ1). No image paths live in the
   repo.

   It also carries the per-unit combobox (.court-viewer-units, further down)
   that switches the photo between the individual courts/lanes/tables of one
   sport — Increment 2 in implementation_plan.md.
   ========================================================================== */
.court-viewer {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
}

.court-viewer[data-open] {
    opacity: 1;
    pointer-events: auto;
}

/* .court-viewer sets `display: flex`, and an author declaration beats the UA
   `[hidden] { display: none }` — so [hidden] has to be restated here or the
   dialog would stay laid out, focusable and exposed to screen readers while
   "closed". (Style/Auth.css restates it as `display: flex` for its own
   overlay, which is why that one keeps a tabbable close button while hidden;
   this dialog does not copy that.) The fade-out still runs because
   includes/landingPage.js only sets [hidden] after the transition finishes. */
.court-viewer[hidden] {
    display: none;
}

body.court-viewer-lock {
    overflow: hidden;
}

.court-viewer-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(10, 8, 6, 0.72);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.court-viewer-dialog {
    position: relative;
    width: 100%;
    max-width: 620px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    background: var(--surface-card);
    border: 1px solid var(--border-card);
    border-radius: 20px;
    box-shadow: var(--shadow-card-hover);
    transform: translateY(16px) scale(0.98);
    opacity: 0;
    transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.22s ease;
}

.court-viewer[data-open] .court-viewer-dialog {
    transform: translateY(0) scale(1);
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .court-viewer,
    .court-viewer-dialog {
        transition: none;
    }

    .court-viewer-dialog {
        transform: none;
    }
}

.court-viewer-close {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 2;
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-card);
    border-radius: 999px;
    background: var(--surface-card);
    color: var(--color-ink);
    cursor: pointer;
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

.court-viewer-close:hover {
    background: var(--surface-card-hover);
    border-color: var(--color-primary);
}

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

/* The modal reuses the grid's .media-slot markup verbatim — only the framing
   changes, so a future real photo needs no new CSS. */
.court-viewer-media .media-slot {
    aspect-ratio: 16 / 9;
    border-bottom: 1px solid var(--border-soft);
    border-radius: 19px 19px 0 0;
}

.court-viewer-media .media-slot-monogram {
    /* Revision A2 (implementation_plan.md) — kept as its own clamp, not
       --fs-hero: a larger decorative monogram for the bigger viewer modal
       image, same reasoning as .hero-media-slot-monogram above. */
    font-size: clamp(2.6rem, 9vw, 4.4rem);
}

.court-viewer-body {
    padding: 24px 28px 28px;
}

.court-viewer-title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--fs-2xl);
    line-height: 1.2;
    /* Was 10px, back when a data-court-viewer-count stat sat directly under
       this and supplied its own margin-bottom too. That line is gone (its
       quantity+unit now lives in .court-viewer-price below, so it is not
       shown twice — see the comment in Pages/Index.html), so this alone
       carries the gap down to the next block. */
    margin-bottom: 14px;
}

/* --------------------------------------------------------------------------
   Per-unit picker — which individual court / lane / table the photo above
   belongs to, and the combobox that switches between them. Both are set to
   [hidden] by includes/landingPage.js whenever a sport resolves to a single
   unit (Volleyball, quantity 1): a one-item <select> is a dead control, and
   a lone "Court 1" caption says nothing the title and price block don't
   already.

   Neither needs an explicit `[hidden] { display: none }` restatement — that
   is only necessary where an author rule sets `display` itself, as
   .court-viewer[hidden] does above.
   -------------------------------------------------------------------------- */

/* Carries .eyebrow as well, so it inherits the page-wide mono/uppercase/accent
   caption treatment and sits above the sport name exactly like every other
   eyebrow on this page. Only the spacing is tightened for the modal. */
.court-viewer-unit {
    margin-bottom: 8px;
}

.court-viewer-units {
    margin-bottom: 16px;
}

.court-viewer-units-label {
    display: block;
    /* Revision A2 (implementation_plan.md) — uppercase label, not numeric
       content, so it moves to --font-display. */
    font-family: var(--font-display);
    font-size: var(--fs-xs);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: var(--color-ink-faint);
    margin-bottom: 8px;
}

.court-viewer-select-shell {
    position: relative;
    max-width: 320px;
}

/* The chevron lives on the shell rather than the <select> so it can be drawn
   in a theme token — a background-image data-URI would need one hardcoded
   copy per theme, and the browser's own arrow is what `appearance: none`
   removes. Purely decorative: the selected option's text carries the meaning,
   so `content: ""` keeps it out of the accessibility tree and
   `pointer-events: none` keeps it from ever swallowing a click on the field. */
.court-viewer-select-shell::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 16px;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--color-ink-dim);
    border-bottom: 2px solid var(--color-ink-dim);
    transform: translateY(-70%) rotate(45deg);
    pointer-events: none;
}

/* Same field treatment as .dash-select / .auth-field input elsewhere in the
   project (--surface-band inset on a --surface-card panel, 10px radius,
   1px token border, --color-primary on hover/focus), so it reads as an
   IñigoSync control in both themes rather than an OS default white box with
   a system arrow on a dark card.

   No `outline: none` on :focus — the global :focus-visible ring in Base is
   the keyboard indicator and must survive. */
.court-viewer-select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    width: 100%;
    padding: 11px 40px 11px 14px;
    font-family: var(--font-body);
    font-size: var(--fs-base);
    line-height: 1.4;
    color: var(--color-ink);
    background: var(--surface-band);
    border: 1px solid var(--border-card);
    border-radius: 10px;
    cursor: pointer;
    transition: border-color 0.2s ease, background-color 0.2s ease;
}

.court-viewer-select:hover,
.court-viewer-select:focus {
    border-color: var(--color-primary);
}

/* The dropdown list itself is painted by the OS, and Chrome/Edge take its
   colours from the <option>, not the <select> — without this the open list
   is dark-on-dark in dark mode. */
.court-viewer-select option {
    background-color: var(--surface-band);
    color: var(--color-ink);
}

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

.court-viewer-note {
    font-size: var(--fs-base);
    line-height: 1.7;
    color: var(--color-ink-dim);
    margin-bottom: 14px;
}

/* Price block — "What you book" + Rate, folded in from the old #pricing
   section's rate-sheet row during the Courts+Pricing merge (see the comment
   above this markup in Pages/Index.html). Each row stacks its label above
   its value rather than sitting side by side: the dialog is one narrow
   column at every width (no header row to line up with, unlike the old
   section's desktop 3-column grid), and the Rate row's TBA state is a full
   sentence ("Rate TBA — rates are still being confirmed with the front
   desk."), not a short number, so it needs its own line. This is the same
   stacked shape the old section fell back to on its own narrow/mobile
   layout (.pricing-cell-label above .pricing-rate-value). */
.court-viewer-price {
    display: grid;
    gap: 10px;
    margin-bottom: 14px;
}

.court-viewer-price-label {
    display: block;
    /* Revision A2 (implementation_plan.md) — uppercase label, not numeric
       content, so it moves to --font-display. */
    font-family: var(--font-display);
    font-size: var(--fs-xs);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: var(--color-ink-faint);
    margin-bottom: 4px;
}

.court-viewer-price-value {
    /* Revision A2 (implementation_plan.md) — "<quantity> <unit>" leads with
       a number, so it takes --font-numeric (tabular-nums). */
    font-family: var(--font-numeric);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-base);
    color: var(--color-ink-dim);
}

.court-viewer-rate {
    /* Revision A2 (implementation_plan.md) — a peso amount; the "Rate TBA"
       sentence state overrides back to --font-body via .is-tba below. */
    font-family: var(--font-numeric);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-base);
    color: var(--color-accent-text);
}

/* "Rate TBA" is the state of every sport today — ported from the old
   #pricing section's (now deleted) .pricing-rate-value.is-tba pill rather
   than rebuilt, so the one visual idea the standalone sheet had worth
   keeping ("TBA is deliberate, not a greyed-out apology") survives the
   merge. No invented prices (D1). */
.court-viewer-rate.is-tba {
    display: inline-block;
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--color-ink-dim);
    background: var(--surface-band);
    border: 1px solid var(--border-soft);
    border-radius: 999px;
    padding: 6px 14px;
}

.court-viewer-photo-status {
    font-size: var(--fs-md);
    color: var(--color-ink-faint);
    padding-top: 14px;
    border-top: 1px solid var(--border-soft);
}

.court-grid-empty {
    padding: 28px 24px;
    background: var(--surface-card);
    border: 1px solid var(--border-card);
    border-radius: 14px;
    box-shadow: var(--shadow-card);
    color: var(--color-ink-dim);
    font-size: var(--fs-base);
}

/* ==========================================================================
   Feedback & Reviews — the old standalone .testimonials section, now a
   sub-block inside #about (implementation_plan.md L6). Labeled honestly as
   testimonials, never as a live Google Reviews feed (D3). Exactly 3 render
   per load, chosen at random by includes/landingPage.js.

   It gets its own full-bleed --surface-band strip inside the
   --surface-page #about section so the testimonial cards (--surface-card)
   still sit on a surface they contrast against.

   Since Increment 2 removed the CTA band, this is the LAST block in #about
   and its strip runs straight into .site-footer, which is the same
   --surface-band. The bottom padding is therefore deliberately smaller than
   the top (56 vs 84): the footer contributes its own 48px of top padding
   below the hairline, so a symmetric 84px here would read as one ~132px
   dead stripe between the testimonial cards and the footer. 56 + hairline +
   48 keeps the seam looking intentional. Same reasoning for the 768px
   override further down.
   ========================================================================== */
.about-reviews {
    margin-top: 96px;
    padding: 84px 0 56px;
    background: var(--surface-band);
    transition: background-color 0.2s ease;
}

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

.testimonial-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
}

.testimonial-card {
    background: var(--surface-card);
    border: 1px solid var(--border-card);
    border-radius: 14px;
    box-shadow: var(--shadow-card);
    padding: 28px 24px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    transition: border-color 0.25s ease, background-color 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
    .testimonial-card {
        transition: background-color 0.2s ease;
    }
}

.testimonial-quote {
    font-size: var(--fs-base);
    font-style: italic;
    line-height: 1.7;
    color: var(--color-ink);
}

.testimonial-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    margin-top: auto;
}

.testimonial-stars {
    /* Revision A2 (implementation_plan.md) — star glyphs (★), not numbers or
       a label; inherits the body font, letter-spacing still does the even
       spacing job Space Mono's fixed-width glyphs used to. */
    letter-spacing: 2px;
    color: var(--color-accent-text);
    font-size: var(--fs-base);
    width: 100%;
}

.testimonial-author {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--fs-md);
}

.testimonial-source {
    font-size: var(--fs-xs);
    color: var(--color-ink-faint);
}

/* ==========================================================================
   Site Footer — now sits directly below .about-reviews (Increment 2 removed
   the "Ready to lock in your slot?" CTA band that used to close out #about,
   along with the background-photo treatment that was this stylesheet's only
   url() reference into assets/). The image file itself is untouched on disk.

   §8 of the feedback doc ("Footer Updates"): the footer was disproportionately
   large — this block only reduces padding/font/logo/gap/map sizing to make it
   much more compact. Every rule below still targets the exact same content
   (brand, Visit us / Contact / Follow cards, embedded map) — nothing was
   removed, only shrunk. This file is shared by Pages/Index.html AND
   Pages/user_dashboard.html (both <link> it), so the compaction applies to
   both automatically — §8's "Global Application" — with zero duplication
   into Style/Dashboard.css.
   ========================================================================== */
.site-footer {
    background: var(--surface-band);
    border-top: 1px solid var(--border-soft);
    padding: 26px 0 22px;
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

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

.footer-inner {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.footer-brand {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.footer-brand-main {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.footer-logo {
    width: 30px;
    height: 30px;
    object-fit: contain;
}

.footer-tagline {
    font-size: var(--fs-sm);
    color: var(--color-ink-dim);
    line-height: 1.5;
    max-width: 560px;
}

.footer-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}

.footer-card {
    background: var(--surface-card);
    border: 1px solid var(--border-card);
    border-radius: 12px;
    padding: 13px 14px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    box-shadow: var(--shadow-card);
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

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

.footer-card h3 {
    font-family: var(--font-display);
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--color-accent-text);
    margin-bottom: 1px;
}

.footer-card p,
.footer-card a {
    font-size: var(--fs-sm);
    color: var(--color-ink-dim);
    line-height: 1.45;
    text-decoration: none;
}

.footer-card a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    transition: color 0.2s ease;
}

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

.footer-card a:hover {
    color: var(--color-accent-text);
}

.footer-social-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.footer-social-icon svg {
    width: 15px;
    height: 15px;
}

.footer-map-block {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.footer-map-label {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--color-ink);
}

.footer-map-frame {
    width: 100%;
    min-height: 150px;
    border: 0;
    border-radius: 12px;
}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 900px) {
    .section-inner {
        padding: 0 32px;
    }

    .about-grid {
        grid-template-columns: 1fr;
    }

    .about-stats {
        flex-direction: row;
        border-left: none;
        border-top: 1px solid var(--border-soft);
        padding-left: 0;
        padding-top: 28px;
        gap: 20px;
        flex-wrap: wrap;
    }

    .court-grid,
    .testimonial-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .site-nav {
        padding: 18px 24px;
    }
}

@media (max-width: 768px) {
    html {
        scroll-padding-top: 90px;
    }

    .site-nav .logo-brand {
        gap: 0.55rem;
    }

    .footer-grid {
        grid-template-columns: 1fr;
    }

    /* Slightly tighter still at phone widths (§8) — the 3-column grid above
       already collapses to 1 column here, so without this the footer would
       be the tallest section on a phone purely from stacking. */
    .site-footer {
        padding: 20px 0 18px;
    }

    .footer-map-frame {
        min-height: 130px;
    }

    .site-nav .logo-image {
        width: clamp(30px, 8vw, 38px);
    }

    .site-nav .logo-text {
        font-size: var(--fs-lg);
    }

    .site-nav {
        padding: 14px 16px;
    }

    .site-nav-pill {
        padding: 8px 10px 8px 14px;
        gap: 14px;
    }

    /* Collapse the pill to: logo + theme toggle + hamburger. The "Log In /
       Sign Up" button moves into the off-canvas menu below — keeping it in
       the pill too left too little room for the logo and made everything
       crowd/overlap on narrow phones. */
    .site-nav-links {
        display: none;
    }

    .site-nav-actions {
        gap: 8px;
    }

    .site-nav-actions .book-now {
        display: none;
    }

    .menu-toggle {
        display: block;
    }

    .book-now {
        padding: 10px 18px;
        font-size: var(--fs-base);
    }

    .nav-menu {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        height: 100vh;
        width: 260px;
        background: rgba(20, 17, 13, 0.98);
        overflow-y: auto;
        transform: translateX(-100%);
        transition: transform 0.3s ease;
        padding: 24px 0;
        z-index: 6;
    }

    :root[data-theme="light"] .nav-menu {
        background: rgba(255, 252, 244, 0.98);
    }

    .menu-toggle-checkbox:checked ~ .nav-menu {
        display: block;
        transform: translateX(0);
    }

    /* Brand row at the top of the off-canvas menu, matching the pill navbar */
    .nav-menu-brand {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 0 20px 20px;
        margin-bottom: 8px;
        border-bottom: 1px solid var(--border-soft);
    }

    .nav-menu-logo {
        width: 32px;
        height: 32px;
        object-fit: contain;
        flex-shrink: 0;
    }

    .nav-menu-brand .logo-text {
        font-size: var(--fs-lg);
    }

    .nav-menu ul {
        display: flex;
        flex-direction: column;
        gap: 0;
        padding: 0 20px 18px;
    }

    .nav-menu ul li {
        width: 100%;
    }

    .nav-menu ul li a {
        display: block;
        padding: 16px 0;
        width: 100%;
        font-size: var(--fs-base);
    }

    .nav-menu ul li + li {
        border-top: 1px solid var(--border-soft);
    }

    .nav-menu-cta {
        display: block;
        width: calc(100% - 40px);
        margin: 4px 20px 0;
        text-align: center;
    }

    /* Hero reflow — stack the caption/CTAs above the progress dots, and
       strengthen the scrim so text stays legible over the photo. */
    .hero-scrim {
        background: linear-gradient(180deg, rgba(20, 17, 13, 0.62) 0%, rgba(20, 17, 13, 0.3) 28%, rgba(20, 17, 13, 0.95) 100%);
    }

    :root[data-theme="light"] .hero-scrim {
        background: linear-gradient(180deg, rgba(255, 252, 244, 0.82) 0%, rgba(255, 252, 244, 0.32) 30%, rgba(26, 20, 14, 0.9) 100%);
    }

    .hero-content {
        padding: 0 20px 28px;
        flex-direction: column;
        align-items: flex-start;
        gap: 20px;
    }

    .hero-copy-wrap {
        max-width: 100%;
    }

    .hero-meta {
        margin-bottom: 20px;
    }

    .cta-buttons {
        flex-direction: column;
        gap: 12px;
        width: 100%;
    }

    .cta-button,
    .cta-button.secondary {
        width: 100%;
        text-align: center;
    }

    .hero-progress {
        flex-direction: row;
        align-items: center;
        gap: 10px;
    }

    .hero-progress-caption {
        display: none;
    }

    .courts {
        padding: 64px 0;
    }

    .about {
        padding: 64px 0 0;
    }

    /* Asymmetric bottom padding for the same reason as the desktop rule —
       .site-footer has no narrow-width override, so its 48px of top padding
       continues this strip at every width. */
    .about-reviews {
        margin-top: 64px;
        padding: 56px 0 40px;
    }

    .court-grid,
    .testimonial-grid {
        grid-template-columns: 1fr;
    }

    .about-stats {
        flex-direction: column;
    }

    .court-viewer {
        padding: 16px;
    }

    .court-viewer-body {
        padding: 20px 20px 24px;
    }
}
.onsite-reviews, .historical-google-reviews { margin-top: clamp(28px, 5vw, 54px); }
.onsite-reviews h3, .historical-google-reviews h3 { margin-bottom: 18px; font-family: var(--font-display); font-size: clamp(1.25rem, 2.5vw, 1.7rem); color: var(--color-ink); }
.onsite-review-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 14px; }
.onsite-review-card { min-width: 0; padding: 18px; border: 1px solid var(--color-line); border-radius: var(--radius-card, 16px); background: var(--color-bg-card); color: var(--color-ink); }
.onsite-review-card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.onsite-review-card-head strong, .onsite-review-card p { overflow-wrap: anywhere; }
.onsite-review-card-head span { flex: 0 0 auto; color: var(--color-primary); letter-spacing: .06em; }
.onsite-review-card p { margin: 12px 0 8px; color: var(--color-ink-dim); }
.onsite-review-card time { font-size: .82rem; color: var(--color-ink-faint); }
.onsite-reviews > button { display: block; margin: 20px auto 0; }
