/* ═══════════════════════════════════════════════════════════════════════════════════════
   Bookr mobile foundation — the shared responsive layer this site never had.

   Load it in <head>, IMMEDIATELY AFTER bookr-shared.css, on every page:

       <link rel="stylesheet" href="assets/fonts.css">
       <link rel="stylesheet" href="assets/bookr-shared.css">
       <link rel="stylesheet" href="assets/bookr-mobile.css">   <!-- here, always -->
       <style> … page CSS … </style>

   ─── WHY THIS FILE USES !important, WHICH IS NORMALLY A SMELL ───────────────────────────

   Every page on this site keeps its CSS in an inline <style> in the document. An external
   sheet cannot beat that by source order, and three obvious alternatives all fail:

     • "just link it last" — 4 pages (for-businesses, pricing, setup, review) put a <style>
       block AFTER </head>. The correct insertion point would differ per page, no guard could
       assert it uniformly, and it silently breaks the day someone adds another <style>.
     • a specificity ladder — setup.html's `.hours-times input[type="time"]` is (0,2,1). You
       end up bidding higher on every page you touch and it still loses somewhere.
     • @layer — backwards here. Unlayered author rules beat ALL layered rules, and every
       page's inline <style> is unlayered, so a layer would always lose.

   !important is safe in this specific codebase because:
     • the lane is empty — 6 uses site-wide, none on the properties below;
     • every !important rule sits inside a max-width query, so desktop is provably untouched;
     • it beats inline style="" attributes, which is required for business.html's lightbox.

   ─── THE TIER CONTRACT (enforced by scripts/check-mobile-css.mjs) ───────────────────────

     T0  no media query, no !important. Base normalisation nothing on the site sets.
     T1  inside @media, !important REQUIRED, and ONLY these properties:
           font-size (text-entry controls only), padding / padding-*,
           height / min-height / max-height, display (ONLY on nav .nav-link)
     T2  .bkm-* opt-in utilities. New class names, so no cascade conflict, no !important.
     T3  anything page-specific stays in that page's own <style>.

   `display` is the dangerous one and is banned outside .nav-link: this site writes
   element.style.display from JS in 300+ places (business.html alone has ~120). An
   !important display rule would make a modal permanently unopenable. .nav-link is the sole
   carve-out — 11 pages hide it in their own media query and no JS anywhere touches it.

   ─── PARAMETERS ─────────────────────────────────────────────────────────────────────────

   Design at 390. Test 375 / 390 / 393 / 402 / 430, plus 852×393 landscape (the only place
   left/right insets are non-zero in Safari) and PWA standalone (the only place the TOP
   inset is non-zero — manifest.json is "display":"standalone"). 320 is a non-breaking
   floor, not a design target.

   Two breakpoints, chosen because they already dominate this codebase (600px has 30 rules,
   768px has 2 — both on the flagship page). Using anything else would open a dead band
   where a page has gone mobile and this sheet has not.

     768px  chrome  — nav, safe area, the input floor, tap targets
     600px  layout  — stacking, tables → cards, accordions

   719.98px on pricing.html and index.html stays. Those two are the best mobile work in the
   repo; a convention is not worth breaking working code for.
   ═══════════════════════════════════════════════════════════════════════════════════════ */


/* ═══ T0 — base normalisation ══════════════════════════════════════════════════════════ */

:root {
  /* Safe-area insets, named once. All four are 0px in portrait Safari, which is what makes
     the max() recipe below inert exactly where it isn't needed. */
  --bkm-sat: env(safe-area-inset-top, 0px);
  --bkm-sar: env(safe-area-inset-right, 0px);
  --bkm-sab: env(safe-area-inset-bottom, 0px);
  --bkm-sal: env(safe-area-inset-left, 0px);

  --bkm-gutter: 20px;  /* matches the 11 existing `nav { padding: 0 20px }` rules */
  --bkm-tap: 44px;     /* Apple HIG minimum touch target */
  --bkm-nav: 60px;     /* every nav on this site is 60px tall */
}

/* iOS inflates text on rotation unless told not to. No page sets this. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Anchor targets currently land UNDER the sticky nav — every legal page's table of contents
   is affected. 60px nav + 16px breathing room. Left unimportant so a page that already
   handles this (the legal pages use 80px) keeps its own value. */
:target,
h1[id], h2[id], h3[id], h4[id], section[id], article[id] {
  scroll-margin-top: 76px;
}


/* ═══ T1 — chrome, at ≤768px ═══════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

  /* ── The 16px input floor. The single highest-impact rule in this file. ──────────────
     Mobile Safari zooms the layout viewport when a text input under 16px takes focus and
     does NOT zoom back out on blur — the user is stranded on a horizontally scrolled page
     mid-form. setup.html (the onboarding wizard) had every field at 14px and its time
     pickers at 12px, so it fought the owner at every step.

     :not(.bkm-fs-keep) is the escape hatch, and it is required rather than cosmetic: this
     is a FLOOR, but CSS cannot express "raise to 16px if lower". A blanket 16px would also
     SHRINK the two deliberately large OTP inputs (login 22px, billing 20px). Those carry
     .bkm-fs-keep and keep their own size.

     Buttons, checkboxes, radios, ranges and file inputs are excluded — they never
     focus-zoom, and forcing 16px on them would blow up unrelated layouts. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not(.bkm-fs-keep),
  textarea:not(.bkm-fs-keep),
  select:not(.bkm-fs-keep) {
    font-size: 16px !important;
  }

  /* ── Safe area on the site chrome ───────────────────────────────────────────────────
     65 of 70 pages declare viewport-fit=cover — which opts the layout INTO the notch and
     home-indicator region — and then only three files anywhere use env(). Declaring
     viewport-fit=cover without honouring the insets is strictly worse than not declaring it.

     max() is what makes this safe to apply blanket-wide: in portrait Safari every inset is
     0px, so max(20px, 0px) resolves to 20px and nothing moves. It only does work in
     landscape and in the installed PWA. Longhands are used so they beat the pages'
     `padding: 0 20px` shorthand. */
  nav {
    padding-left: max(var(--bkm-gutter), var(--bkm-sal)) !important;
    padding-right: max(var(--bkm-gutter), var(--bkm-sar)) !important;
  }

  footer {
    padding-left: max(var(--bkm-gutter), var(--bkm-sal)) !important;
    padding-right: max(var(--bkm-gutter), var(--bkm-sar)) !important;
    padding-bottom: calc(36px + var(--bkm-sab)) !important;
  }

  /* ── Navigation must never be unreachable ───────────────────────────────────────────
     11 pages did `.nav-link { display: none }` at 600px (768px on for-businesses.html)
     with no hamburger, no drawer, no replacement — so "Log in" and "For businesses" simply
     vanished on every phone.

     There is no menu to build. Every nav on this site has exactly TWO links: at 375pt the
     logo (~92px) plus two pills (~160px) plus gutters comes to ~292px against 375px
     available. They fit. They always fitted.

     …with one exception, which is why `:not(.hide-sm)` is here. index.html's nav is not
     "logo + two links": it also carries a solid .nav-cta and a .nav-pill, and it marks its
     two secondary links `.hide-sm` to drop them on small screens. Forcing those back
     pushed .nav-right 23px off a 320px screen — the site's front door, scrolling
     sideways. The distinction this selector draws is deliberate: a blanket
     `.nav-link { display: none }` is the accident this rule exists to defeat, whereas
     `.hide-sm` is a page saying "this specific link is droppable, and I have kept a CTA
     and a footer that reach the same places". Opt-out by name, never by default. */
  nav .nav-link:not(.hide-sm) {
    display: inline-flex !important;
    align-items: center !important;
    min-height: var(--bkm-tap) !important;
  }

  /* ── Tap targets ────────────────────────────────────────────────────────────────────
     min-height ONLY — deliberately no `display` and no `width`.

     An earlier draft added `display:flex` here to centre the label inside the taller box.
     The tier guard rejected it, correctly: `display:flex` on a <summary> destroys the
     native disclosure marker, and on inline links it collapses the wrapping flow of a
     footer row. min-height alone grows the hit area, which is the entire point; these
     elements already carry padding that keeps the label looking centred.

     The selector list covers both conventions on purpose: most pages class their links
     .footer-link and .nav-link, but start.html and how-it-works.html use bare `<a>` inside
     <nav>/<footer>, and those were the two still measuring 23–24px after the class rule. */
  .footer-link,
  nav a,
  nav button,
  footer a,
  .toc a,
  summary,
  .faq-q {
    min-height: var(--bkm-tap) !important;
  }

  /* The logo lockup is a flex box, so min-height genuinely centres it. */
  nav .logo,
  nav a.logo {
    min-height: var(--bkm-tap) !important;
  }

  /* These are block-level, where min-height would just add dead space under top-aligned
     text. Symmetric vertical padding grows the hit area AND keeps the label centred.
     Measured at 390px: footer-logo was 53×22 and the cookie button 66×31. */
  .footer-logo {
    padding-top: 11px !important;
    padding-bottom: 11px !important;
  }
  #bookr-cookie-ok {
    padding-top: 12px !important;
    padding-bottom: 12px !important;
  }
}


/* ═══ T1 — layout, at ≤600px ═══════════════════════════════════════════════════════════ */

@media (max-width: 600px) {

  /* Long-form prose is READ, not scanned. The legal pages sit at 15px; on a phone that is
     the difference between "I'll read this" and "I'll scroll past it". */
  .prose p,
  .prose li {
    font-size: 16px !important;
    line-height: 1.65 !important;
  }
}


/* ═══ T1 — PWA standalone ══════════════════════════════════════════════════════════════ */

/* manifest.json is "display":"standalone". In an installed PWA there is no browser chrome,
   so env(safe-area-inset-top) is ~59px on a 16 Pro and the sticky nav sits UNDER the
   Dynamic Island. In ordinary portrait Safari the browser chrome already covers this and
   the inset is 0 — which is why this is scoped to display-mode rather than applied always. */
@media (display-mode: standalone) {
  nav {
    padding-top: var(--bkm-sat) !important;
    height: calc(var(--bkm-nav) + var(--bkm-sat)) !important;
  }
}


/* ═══ T2 — opt-in utilities (.bkm-*) ═══════════════════════════════════════════════════
   New class names, so nothing to fight. Pages add markup to receive these. */

/* ── .bkm-rail — honest horizontal scroll ───────────────────────────────────────────────
   For content that genuinely loses its meaning when stacked: a week calendar, a
   side-by-side comparison matrix. The alternative on this site today is being silently
   clipped by html,body{overflow-x:hidden}, which gives no scrollbar and no cue — the
   content just isn't there.

   Lifted from pricing.html's .pricing-grid, the one place this was already done right. */
.bkm-rail {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  padding-bottom: 4px;
}
.bkm-rail::-webkit-scrollbar { display: none; }
.bkm-rail > * {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

/* Edge fade, so the eye is told there is more to the right. Only on touch-ish widths —
   on desktop the rail usually isn't scrolling at all. */
@media (max-width: 768px) {
  .bkm-rail {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 26px), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 26px), transparent 100%);
  }
}

/* Swipe cue. Hidden by default and revealed by JS only when the rail actually overflows,
   so it never lies about scrollability. */
.bkm-rail-cue {
  display: none;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim, rgba(250,250,248,.50));
  transition: opacity .4s ease;
}
.bkm-rail-cue.show { display: flex; }
.bkm-rail-cue.faded { opacity: 0; }

/* ── .bkm-sr-only — visually hidden, still announced ────────────────────────────────── */
.bkm-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── .bkm-safe-overlay — fixed overlays that clear the Dynamic Island ────────────────
   business.html's gallery lightbox puts its close button at top:16px, which on a 15/16 Pro
   lands underneath the Dynamic Island. */
.bkm-safe-overlay {
  padding-top: max(16px, var(--bkm-sat));
  padding-right: max(16px, var(--bkm-sar));
  padding-bottom: max(16px, var(--bkm-sab));
  padding-left: max(16px, var(--bkm-sal));
}

/* ── .bkm-dvh — full-bleed overlay height ────────────────────────────────────────────
   100vh overshoots the visible viewport on iOS while the URL bar is showing, pushing the
   bottom of a modal (and its buttons) off-screen. Fallback first for Safari < 15.4.

   NOT for page shells — those want svh (see .bkm-svh), and NOT for scroll-snap scenes,
   where a URL-bar collapse mid-scroll would move every snap point. index.html's JS-set
   --scH is the right answer there and is deliberately left alone. */
.bkm-dvh { height: 100vh; height: 100dvh; }
.bkm-svh { min-height: 100vh; min-height: 100svh; }

/* ── .bkm-modal — a modal whose action buttons cannot be pushed off-screen ───────────
   Units alone do not fix this. my-bookings.html's dispute modal caps at
   calc(100vh - 40px) with the buttons INSIDE the scroll container, so on a short viewport
   they are simply below the fold. The fix is structural: scroll the body, pin the actions. */
.bkm-modal {
  display: flex;
  flex-direction: column;
  max-height: min(86dvh, calc(100dvh - 32px));
}
.bkm-modal-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.bkm-modal-actions {
  flex: 0 0 auto;
  padding-bottom: max(0px, var(--bkm-sab));
}

/* ── .bkm-tbl — wide tables become cards, WITHOUT losing table semantics ─────────────
   Replaces the old bookr-shared.css block that set `display:block` with
   `width:max-content !important` and `max-width:100% !important` fighting each other. That
   produced a clipped block with no scroll affordance AND stripped the table/row/cell roles
   from VoiceOver — on the pages that exist for compliance.

   Markup contract (CSS alone cannot do this accessibly):
     <table class="bkm-tbl" role="table">
       <thead><tr role="row"><th role="columnheader">Name</th>…
       <tbody><tr role="row"><td role="cell" data-label="Name">…
   The explicit roles re-assert what display:block strips; data-label supplies the column
   name as a mono eyebrow on each card. scripts/check-tables.mjs enforces both. */
@media (max-width: 600px) {
  table.bkm-tbl { display: block; border: 0; width: 100%; }

  table.bkm-tbl thead {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  table.bkm-tbl tbody,
  table.bkm-tbl tr,
  table.bkm-tbl td { display: block; width: auto; }

  table.bkm-tbl tr {
    border: 1px solid var(--border2, rgba(250,250,248,.14));
    border-radius: 12px;
    background: var(--faint, rgba(250,250,248,.04));
    padding: 14px 16px;
    margin: 0 0 10px;
  }

  table.bkm-tbl td {
    border: 0;
    padding: 0 0 12px;
    font-size: 14px;
    line-height: 1.55;
  }
  table.bkm-tbl td:last-child { padding-bottom: 0; }

  table.bkm-tbl td::before {
    content: attr(data-label);
    display: block;
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 10.5px;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--dim, rgba(250,250,248,.50));
    margin-bottom: 4px;
  }
  table.bkm-tbl td[data-label=""]::before { display: none; }
}
