:root {
  --green: #1E6B49;
  --green-dark: #154D37;
  --gold: #B68A45;
  --red: #B24B3D;
  --bg: #F6F4EF;
  --panel: #FFFFFF;
  --border: #E1DED2;
  --text: #14201B;
  --muted: #5B6860;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
}

.app-shell { display: flex; align-items: flex-start; min-height: 100vh; }
/* Real gap found live: .main-panel (base.html's own <main>) never had
   a desktop width rule at all — a flex item with no flex-basis/width
   of its own just grows to fill whatever's left beside the sidebar,
   which on any reasonably wide monitor left cards and tables
   stretched edge-to-edge with a lot of dead whitespace inside them.
   Capped to a comfortable reading/working width and centred in the
   remaining flex space (margin:auto DOES center a flex item within
   its container, not just block-level content) instead. */
.main-panel { max-width: 900px; width: 100%; margin: 0 auto; padding: 32px 32px; }
/* Login/Setup have no sidebar, so .content (flex:1 1 auto, capped at
   max-width) is the ONLY flex child — with nothing else in the row, a
   plain flex layout leaves its box flush against the left edge rather
   than centering it, which is what left the login card stranded in
   the top-left corner instead of top-centre. Scoped to just this case
   (a .logged-out modifier, set by base.html) so every logged-in page's
   existing content-hugs-the-sidebar layout is untouched. */
.app-shell.logged-out { justify-content: center; }

.sidebar {
  flex: 0 0 260px;
  /* Without this, a flex item's automatic min-width defaults to its
     content's min-content size — so any unbreakable content inside
     (an overflowing toggle label, a native date input's own intrinsic
     minimum) could silently force the whole sidebar wider than its
     260px flex-basis, then narrower again once that content changed
     on the next render. That's the real cause behind the pane
     visibly "breathing" wider/narrower while a booking ran — pinning
     min-width to 0 makes the 260px basis an actual hard cap; anything
     that still doesn't fit is clipped by overflow-x below instead of
     resizing the pane. */
  min-width: 0;
  overflow-x: hidden;
  /* Reserves the vertical scrollbar's own width permanently, whether
     or not one is currently needed — without this, a classic
     (non-overlay) scrollbar rendering — e.g. Windows' own Basic theme,
     common over RDP — appearing/disappearing repeatedly changes the
     content box's available width each time, which can retrigger the
     exact same width-driven reflow min-width:0 above was already
     written to prevent, just in the OTHER direction (a scrollbar
     stealing width rather than unbreakable content adding it).
     Reported live 2026-09-15: an oscillating sidebar + scrollbar while
     a booking ran, on Edge under RDP. Unsupported browsers just ignore
     this property and keep their previous (stable, for them) rendering. */
  scrollbar-gutter: stable;
  /* Sticky, not fixed: fixed positioning would pull it out of the
     flex flow entirely, breaking the @media stack-above-content
     layout on narrow screens below. Sticky keeps it in flow (so that
     still works) while pinning it to the viewport as the content
     column scrolls past it — the actual ask: don't let the nav
     scroll away with a long page (Help, a big My Bookings table). */
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  background: #11201A;
  color: #F3F1E8;
  padding: 24px 18px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.sidebar-brand { display: flex; align-items: center; justify-content: center; }
.brand-logo { width: 100%; height: auto; border-radius: 18px; display: block; flex-shrink: 0; }
.login-logo { height: 140px; width: 140px; border-radius: 22px; display: block; margin: 0 auto 12px; }
.version { font-weight: 400; opacity: 0.6; font-size: 0.8em; }

.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar-nav a {
  color: #F3F1E8; text-decoration: none; padding: 9px 10px;
  border-radius: 6px; font-size: 0.95em;
}
.sidebar-nav a:hover { background: rgba(255,255,255,0.08); }
.sidebar-nav a.active { background: rgba(255,255,255,0.14); font-weight: 700; }

/* Player Rating+Credit / "Next booking" countdown each get their own
   bordered box, matching the sidebar's own dark-panel look. */
.rating-block, .countdown-block {
  border: 1px solid #2A3A32; border-radius: 8px; padding: 10px 12px;
}
.rating-block { display: flex; flex-direction: column; gap: 4px; }
.rating-row { display: flex; justify-content: space-between; color: #F3F1E8; font-size: 0.9em; }
.rating-row strong { font-variant-numeric: tabular-nums; }
/* Real gap found live: Credit Balance only ever updated as a side
   effect of some OTHER dispatch happening to print it, so it could
   sit stale with no way to ask Padel4all directly — this makes the
   figure itself the button, styled to look exactly like the plain
   text it replaces until you actually hover/tap it. */
.credit-refresh-btn {
  background: none; border: none; padding: 0; margin: 0;
  color: inherit; font: inherit; cursor: pointer;
}
.credit-refresh-btn:hover strong, .credit-refresh-btn:focus-visible strong {
  text-decoration: underline;
}
.credit-refresh-btn:disabled { cursor: wait; opacity: 0.7; }
.countdown-block {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  color: var(--gold); font-weight: 700;
}
/* Two short lines ("Next" / "booking") rather than one cramped line
   squeezed against the digits — real gap found live: "Next booking
   in" wrapped mid-word ("Next booking" / "in") once the countdown's
   own fixed-width digits left it too little room. */
.countdown-block span { line-height: 1.25; }
.countdown-block strong { font-size: 1.2em; font-variant-numeric: tabular-nums; white-space: nowrap; }

.sidebar-status {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px 0; border-top: 1px solid #2A3A32; border-bottom: 1px solid #2A3A32;
}
.button-row { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
/* getting_started.html's own "Book a session"/"Book a court" links —
   .primary-button/.secondary-button are plain classes (not scoped to
   button[type=submit] alone) so they already colour an <a> the same
   as a real button, but an anchor is inline by default: no box model
   for padding to properly expand into, and a default underline. */
.button-row a.primary-button, .button-row a.secondary-button {
  display: inline-block; text-decoration: none;
}

.wizard-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.wizard-head h1 { margin-bottom: 0; }
.wizard-skip { margin-bottom: 0 !important; white-space: nowrap; }
.wizard-dots { display: flex; gap: 6px; margin: 14px 0 22px; }
.wizard-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); }
.wizard-dot.active { background: var(--green); }
.wizard-step h3 { margin-top: 0; }
.wizard-nav { justify-content: flex-end; }
.button-row .inline-form { margin: 0; }
/* A toggle button plus a short status line next to it (Court Monitor's
   own Turn on/off + "Currently: On/Off") — a bare <form> and <p>
   sitting next to each other otherwise each keep their own default
   block margin, stacking them instead of reading as one line. */
.inline-status-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.inline-status-row form, .inline-status-row p { margin: 0; }
.inline-checkbox { display: flex; align-items: center; gap: 8px; font-weight: normal; cursor: pointer; }
.secondary-button {
  background: none; border: 1px solid var(--border); color: var(--text);
  padding: 10px 18px; border-radius: 8px; font-size: 0.95em; cursor: pointer;
  transition: background-color 0.15s ease, transform 0.1s ease;
}
.secondary-button:hover { background: var(--bg); }
.secondary-button:disabled { color: var(--muted); cursor: not-allowed; opacity: 0.6; }
.secondary-button:disabled:hover { background: none; }
/* Session/Court Finder's own "Select" — sits inline right next to a
   session name and the "?" icon now, not alone in its own column, so
   the normal .secondary-button size (built for a standalone action
   like Cancel/Book) read as oversized next to them. */
.small-button { padding: 5px 12px; font-size: 0.85em; }
/* The "?"/Select pair sits on its own line below the session name
   itself, not sharing a line with it — consistently, regardless of
   whether a particular session's own name happens to be short enough
   to fit alongside them (real gap found live: it looked inconsistent
   card to card, on purely by accident of name length). */
.session-finder-actions { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
/* button[type=submit]'s own rule below (higher specificity — an
   attribute selector counts the same as a class) otherwise wins over
   a plain .secondary-button class, same reasoning as .link-button's
   own !important further down — real bug found live: Save/Verify
   player names/etc. (type=submit buttons using formaction to post
   elsewhere, not "the" primary action of their form) rendered as
   solid green, indistinguishable from Book Session/Book Court. */
button[type=submit].secondary-button {
  background: none; color: var(--text);
}
button[type=submit].secondary-button:hover { background: var(--bg); }

/* Status Log's own Detailed view (status_log_detailed.html) — the raw
   captured engine output. */
.panel-log {
  background: #11201A; color: #D9E6DE; padding: 12px; border-radius: 8px;
  font-size: 0.8em; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
  height: 65vh; overflow-y: auto; margin: 0;
}
/* The "still going" banner for whichever button-triggered scan is
   currently running — see _activity_banner's own docstring
   (courtcatcha_web.py) for what does and doesn't show this. Sits at
   the bottom of the centre pane (base.html), the same spot a flash
   message renders in once the action's actually done — same box
   shape/padding as .flash below, kept a neutral colour rather than
   gold so "still going" reads visibly differently from "done". */
.content-activity {
  display: flex; align-items: center; gap: 10px; margin: 16px 0;
  padding: 10px 14px; background: var(--bg); border: 1px solid var(--border);
  border-radius: 8px; font-size: 0.9em; color: var(--text);
}
/* .content-activity's own `display: flex` above otherwise beats the
   browser's native [hidden] styling on specificity (a class selector
   outranks a bare attribute selector) — without this, the box still
   rendered ("None in progress") even carrying the hidden attribute. */
.content-activity[hidden] { display: none; }
.content-activity-dots { display: inline-flex; gap: 3px; flex-shrink: 0; }
/* Same specificity-vs-the-browser's-own-[hidden]-styling fix as
   .content-activity[hidden] just above — needed here too now that
   the "Next booking" pop-up (base.html) hides just the dots (not the
   whole .content-activity box) once a booking's actually done. */
.content-activity-dots[hidden] { display: none; }
.content-activity-dots span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--green);
  animation: content-activity-bounce 1.2s infinite ease-in-out both;
}
.content-activity-dots span:nth-child(1) { animation-delay: -0.32s; }
.content-activity-dots span:nth-child(2) { animation-delay: -0.16s; }
@keyframes content-activity-bounce {
  0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
  40% { transform: scale(1); opacity: 1; }
}
/* Deliberately NOT disabled under prefers-reduced-motion — unlike a
   decorative flourish, this is the only visible signal that a scan is
   still running rather than stuck, so it stays animated regardless. */
.last-refreshed-row {
  display: flex; align-items: baseline; justify-content: flex-start; gap: 10px; margin-bottom: 14px;
}
.last-refreshed-row .muted { margin: 0; }
.hide-cancelled-toggle {
  display: inline-flex; align-items: baseline; gap: 6px; font-size: 0.88em; color: var(--muted); cursor: pointer;
  white-space: nowrap;
}
.hide-cancelled-toggle input[type="checkbox"] { margin: 0; position: relative; top: 2px; }

/* Narrow screens: the sidebar becomes a hidden, off-canvas drawer,
   opened by a small tab fixed to the screen's own left edge — real
   gap found live with an earlier full-width top-bar version of this
   toggle: it had to sit at a HIGHER z-index than the drawer to stay
   clickable while closed, which then left it visually overlapping the
   drawer's own brand/logo header the moment the drawer opened. A
   left-edge tab has no such conflict (hidden outright once open, see
   base.html's own setOpen()) and stays out of the content's way
   entirely rather than permanently occupying a header strip. Nine nav
   items plus Log out only really works as a drawer at this width.
   Hidden by default (desktop); only ever shown inside the ≤780px
   media query below. */
.nav-edge-tab {
  display: none;
  position: fixed; top: calc(16px + env(safe-area-inset-top, 0px)); left: 0; z-index: 40;
  width: 32px; height: 44px; border: none; border-radius: 0 10px 10px 0;
  background: #11201A; color: #F3F1E8; font-size: 1.2em; line-height: 1;
  cursor: pointer; box-shadow: 2px 2px 10px rgba(0,0,0,0.2);
}
.nav-backdrop {
  display: none;
  position: fixed; inset: 0; background: rgba(10, 16, 13, 0.5); z-index: 30;
  opacity: 0; transition: opacity 0.2s ease;
  backdrop-filter: blur(1px); -webkit-backdrop-filter: blur(1px);
}
.nav-backdrop.visible { display: block; opacity: 1; }


.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 28px;
  /* A soft lift rather than a flat, purely-bordered box — subtle
     enough to stay out of the way on a dense page (Scheduled
     Bookings/Current Bookings stack several cards in a row) while
     still reading as a distinct surface, not just a line drawn on the
     background. */
  box-shadow: 0 1px 2px rgba(20, 32, 27, 0.04), 0 6px 16px rgba(20, 32, 27, 0.05);
}
.card + .card { margin-top: 18px; }
.card.narrow { max-width: 380px; margin: 60px auto; }

h1 { margin-top: 0; }
h2 { margin: 16px 0 4px; font-size: 1.05em; }

/* Home's own welcome card — the one place in the app that's a plain
   "you've arrived" moment rather than a working page, so it gets a
   proper hero logo + tagline instead of the usual bare heading. */
.home-hero { text-align: center; }
.home-logo { width: 140px; height: 140px; border-radius: 26px; margin: 0 auto 16px; display: block; }
.home-hero h1 { margin-bottom: 10px; }
.home-tagline { color: var(--muted); font-size: 1.15em; max-width: 480px; margin: 0 auto 20px; }
/* Each sentence its own block with margin between — NOT a bigger
   line-height on the whole paragraph, which would also stretch the
   gap between two WRAPPED lines of the same sentence on a narrow
   phone, not just between separate sentences. */
.tagline-line { display: block; margin-bottom: 10px; }
.tagline-line:last-child { margin-bottom: 0; }
.home-hero p:not(.home-tagline) { text-align: left; max-width: 480px; margin-left: auto; margin-right: auto; }

fieldset {
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 18px;
  padding: 14px 16px;
}
legend { font-weight: 600; padding: 0 6px; }
input:read-only { background: var(--bg); color: var(--muted); cursor: default; }

label { display: block; margin-bottom: 10px; font-size: 0.95em; }
label.radio, label.checkbox { display: inline-flex; align-items: center; gap: 6px; margin-right: 16px; }
input[type=text], input[type=email], input[type=password], input[type=date],
input[type=time], input[type=number], select {
  display: block; width: 100%; margin-top: 4px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px;
  font-size: 1em;
}
/* Date/time pickers only ever hold a fixed, short number of digits
   ("dd/mm/yyyy", "--:--") — real gap found live: they inherited the
   same width:100% as a free-text field, stretching to fill whatever
   width their container (a form, a field-row column) happened to be,
   which just wasted space and made the small font look lost in a
   wide box. Narrower and a larger, easier-to-read font instead.
   .time-select is every half-hour-slot <select> in the app (Court
   Finder's Earliest/Latest time, Court Monitor's own Time) — not a
   native time input at all (see _run_at_field.html's own comment on
   why), but still wants this same compact date/time-picker sizing
   rather than a plain full-width <select> like Session name's own.
   input[type=time] is Run-at Time specifically — deliberately still a
   native picker, not a half-hour <select>, so it can accept any
   minute (see _run_at_field.html's own comment on why). */
input[type=date], input[type=time], .time-select {
  font-size: 1.1em;
  /* Real gap found live: a native type=date control and a native
     type=time control don't compute the same height from identical
     padding/font-size — each browser draws its own picker chrome
     (calendar icon, spinner, whatever) with a slightly different
     internal box, so Date and Time ended up visibly different
     heights sitting side by side. An explicit height (not just
     padding) forces both to match; box-sizing: border-box is already
     the page-wide default (see the * rule up top) so this height
     includes the border, not just the content box. Browsers already
     vertically-centre a text-like input's own value within whatever
     height it's given. */
  height: 44px; padding-top: 0; padding-bottom: 0;
  /* Real gap found live: a fixed height alone does NOT vertically
     centre a native date/time input's text on every browser — several
     mobile browsers (iOS Safari in particular) position a single-line
     value relative to line-height, not the box's own height, and left
     it sitting at the top of the 44px box otherwise. 42px = the 44px
     height minus this rule's own 1px top+bottom border (box-sizing:
     border-box is the page-wide default — see the * rule up top), so
     the line box exactly fills the content area and centres within
     it. This is the single generic input[type=date]/input[type=time]
     rule every date/time field on every page shares, so fixing it
     here fixes it everywhere at once rather than page by page. */
  line-height: 42px;
}
/* width, not max-width — real gap found live: max-width is only a
   ceiling, and these sit in a flex item with flex-grow: 0 (see
   .run-at-field/.field-row.tight), which sizes to the INPUT's own
   intrinsic rendered width, not the available space. On an iPad that
   intrinsic width came out smaller than intended, so raising max-width
   alone changed nothing — nothing was pushing the box up to that
   ceiling in the first place. An explicit width is a real target, not
   just a limit; flex-shrink (still 1, inherited) still lets it give
   way on a phone too narrow to fit it at all. */
input[type=date] { width: 170px; padding-left: 32px; }
input[type=time] { width: 110px; padding-left: 32px; }
.time-select { width: 110px; padding-left: 32px; }
/* Real gap found live: these widths were tuned against a narrow phone
   screen, but they're not scoped to the mobile media query below —
   they apply everywhere, including a much wider iPad, where the same
   110/170px boxes truncated their own value text instead of just
   looking small. Anything wider than a phone gets noticeably more
   room; there's no shortage of it there. */
@media (min-width: 600px) {
  input[type=date] { width: 240px; padding-left: 40px; }
  input[type=time] { width: 190px; padding-left: 40px; }
  .time-select { width: 190px; padding-left: 40px; }
  label:has(> input[type=date])::after, label:has(> input[type=time])::after, label:has(> .time-select)::after {
    left: 14px; font-size: 1.2em;
  }
}
/* Court Finder's own Duration — a plain unstyled <select> otherwise
   inherits width: 100% from the generic input/select rule up top, and
   stretches to fill its own row's full width once it's the only field
   left on that row (see courts_page.html) — fine for a long list of
   named options, but odd for a two-option 60/90 minutes choice. */
.duration-select { max-width: 110px; }

/* Calendar/clock icon inside every date/time box — real gap found
   live: on a PC, Chrome/Edge already draw their own calendar icon
   inside a native date box for free, but mobile Safari draws NONE at
   all (there, a native date box is just plain text, tap anywhere to
   open the wheel picker), so the mobile app always looked one icon
   short of the desktop rendering. A custom icon here makes every box
   look the same on every browser instead of depending on what a given
   browser happens to draw by default.

   Left, not right — real gap found live: on the right, this icon sat
   on top of a native date control's OWN right-aligned chrome (the
   browser's real calendar icon in Chrome/Edge), which visually
   collided with it in a way the left side of the box never has
   anything else drawn on it at all.

   `> input`/`> select` (direct child) matches every date/time field on
   every page — see the actual markup: session/monitor/court-finder's
   own <label>Session date<input...></label> shape, and
   _run_at_field.html's own <label class="run-at-field"><span>...</span>
   <input type="time">...</label> shape — in both, the field is the
   LAST direct child of its <label>. bottom: 0; height: 44px on the
   icon itself (rather than trying to vertically centre against the
   label's own full height, caption text and all) anchors it to the
   FIELD's own box specifically — that field is always its label's
   bottom edge, and always 44px tall (the shared height rule above),
   regardless of what label text/caption sits above it. */
label:has(> input[type=date]), label:has(> input[type=time]), label:has(> .time-select) { position: relative; }
label:has(> input[type=date])::after, label:has(> input[type=time])::after, label:has(> .time-select)::after {
  position: absolute; left: 10px; bottom: 0; height: 44px;
  display: flex; align-items: center;
  font-size: 1.05em; line-height: 1; pointer-events: none;
}
label:has(> input[type=date])::after { content: "📅"; }
label:has(> input[type=time])::after, label:has(> .time-select)::after { content: "🕐"; }
/* The native indicator (where a browser draws one at all — mobile
   Safari doesn't) stays functionally in place, just invisible, rather
   than removed outright — real gap found live: `display: none` on it
   in Chrome/Edge also disables the click-to-open behaviour tied to
   it, silently breaking the picker on desktop while looking identical
   to `opacity: 0` until you actually tried clicking. Sized/positioned
   over the LEFT side of the box now, under our own custom icon above,
   so a click there still lands on it — Chrome/Edge otherwise still
   draw their own real calendar/clock icon at the box's right edge, now
   sitting harmlessly alongside our left-side one instead of under it. */
input[type=date]::-webkit-calendar-picker-indicator,
input[type=time]::-webkit-calendar-picker-indicator {
  opacity: 0; position: absolute; left: 0; width: 34px; height: 100%;
  margin: 0; cursor: pointer;
}

button[type=submit], .primary-button {
  background: var(--green);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 12px 22px;
  font-size: 1.05em;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.1s ease;
}
button[type=submit]:hover, .primary-button:hover { background: var(--green-dark); }
/* A small tactile "press" on tap/click — mainly felt on a touchscreen,
   where there's no :hover state to signal a tap actually registered. */
button[type=submit]:active, .primary-button:active, .secondary-button:active {
  transform: scale(0.97);
}
button[type=submit]:disabled, .primary-button:disabled {
  background: #9CA9A2; color: #F3F1E8; cursor: not-allowed; opacity: 0.7;
}
button[type=submit]:disabled:hover, .primary-button:disabled:hover { background: #9CA9A2; }

.inline-form { display: inline; margin: 0 0 10px; }

/* Sessions/Courts pages: several fields side by side on one line
   (matching the Tkinter reference's own dense rows) instead of one
   full-width field per line — wraps to stacked below ~700px. */
.field-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; }
.field-row > label { flex: 1 1 160px; margin-bottom: 14px; }
.field-row > label.grow { flex: 2 1 260px; }
/* Checkbox labels sit at their own natural width (not stretched by
   the flex-grow every other label gets) so two of them — e.g. "Hide
   my name"/"Join waitlist if full" — sit snugly next to each other
   instead of spreading apart with a big gap. A trailing .push-right
   element (e.g. "Refresh sessions") still gets pushed to the far
   right. */
.field-row > label.checkbox { flex: 0 0 auto; margin-bottom: 14px; }
.field-row > .push-right { margin-left: auto !important; }
.rating-inline { flex: 0 0 auto; margin-bottom: 14px; display: inline-flex; align-items: center; gap: 6px; font-weight: 700; }
.rating-inline strong { font-variant-numeric: tabular-nums; }
.field-row.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.field-row.two-col > label { margin-bottom: 14px; }
/* The tick sits right next to "Player N" itself, not off at the far
   right edge of the row. */
.player-label-row { display: flex; justify-content: flex-start; align-items: center; gap: 6px; }
/* Real bug found live: "Player 1" etc. wrapped onto two lines because
   the label's own text was a flex child competing for space with the
   input (which has its own width:100% from the generic input rule
   above, fighting for the same row) — min-width:0 on the input lets
   IT shrink instead, and flex:0 0 auto + nowrap on the label text
   means it never breaks. */
.player-label-text { flex: 0 0 auto; white-space: nowrap; }
.player-label-row input { flex: 1 1 auto; min-width: 0; margin-top: 0; }
.verified-tick { color: var(--green); font-weight: 700; }

.finder-table-scroll { overflow-x: auto; max-height: 420px; overflow-y: auto; }
.finder-table { width: 100%; border-collapse: collapse; font-size: 0.88em; }
.finder-table th, .finder-table td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; }
/* !important: button[type=submit]'s own rule above outranks a plain
   .link-button class on specificity alone (an attribute selector counts
   the same as a class) — same reasoning as .quit-button's override
   just above. */
.link-button {
  background: none !important; border: none !important; padding: 0 !important;
  margin: 0 0 10px !important; color: var(--green-dark) !important;
  text-decoration: underline; cursor: pointer; font-size: 0.9em !important;
  font-weight: 400 !important; display: block;
}
.link-button:hover { color: var(--green) !important; }
/* Log out sits among the sidebar's own nav links (.sidebar-nav a,
   0.95em) — .link-button's own smaller 0.9em (shared with e.g. dialog
   Cancel buttons elsewhere) read as visually "smaller than the menu"
   next to them. Keeps .link-button's own green colour as-is. */
/* Real gap found live: .link-button's own padding: 0 !important left
   "Log out" flush against the sidebar's edge, 10px further left than
   every .sidebar-nav a above it (their own padding: 9px 10px) — lines
   its left edge up with theirs instead. */
.sidebar-logout { font-size: 0.95em !important; padding-left: 10px !important; }

.muted { color: var(--muted); font-size: 0.9em; }
.warning { color: var(--red); font-weight: 600; }
.status-ok { color: var(--green-dark); font-weight: 600; }
/* The forwarding address on the Help page (and anywhere else a short
   literal value — an email address, a key name — needs to read as
   "copy this exactly", not prose. */
.mono-inline {
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px; font-size: 0.92em;
}

/* Run booking at (book.html) — date + time pickers on one row instead
   of a single free-text "YYYY-MM-DD HH:MM" field. Lives on the light
   .card main panel here, not a dark sidebar, so it reuses the generic
   input/label/.secondary-button rules above rather than the dark-
   themed .run-at-row variant styled for _booking_controls.html. */
.run-at-fields { margin-bottom: 14px; }
.run-at-fields .control-label { font-weight: 700; margin-bottom: 4px; font-size: 0.95em; }
.run-at-fields .control-label .muted { font-weight: 400; }
.run-at-fields .run-at-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.run-at-fields .run-at-row + .run-at-row { margin-top: 10px; }
/* Wraps each date/time input with its own small "Date"/"Time" caption
   — real gap found live: a blank native date/time picker shows no
   hint at all on some mobile browsers (just an empty grey box), so
   the two fields were indistinguishable until you actually tapped
   one. flex-end above lines Clear up with the INPUTS themselves, not
   the shorter caption text sitting above them. */
/* flex: 0 1 auto (not 1 1 auto) — real gap found live: the Time field
   used to flex-grow to fill the whole row, which left "Repeat weekly"
   sitting much further right than the visible Time box, with a big
   stretch of empty label-width in between. Shrink-only lets this
   field hug its input's actual width instead. */
.run-at-field { display: flex; flex-direction: column; gap: 3px; flex: 0 1 auto; min-width: 0; margin-bottom: 0; }
.run-at-fields input[type=date], .run-at-fields input[type=time] {
  /* font-size explicit here, not left to the generic input[type=date]
     rule above — real bug found live: .run-at-row (this component's
     own inner wrapper) collides by NAME with an unrelated leftover
     dark-sidebar rule from _booking_controls.html (a component this
     cloud fork never actually uses), which sets font-size: 0.85em at
     equal specificity to a plain `input[type=date]` selector — so the
     generic fix never actually reached this specific field.

     No width override here any more — real gap found live: width:
     100% fought the generic rule's own explicit width (170px/110px,
     wider still on tablet/desktop), stretching this field to its
     PARENT's width instead — and that parent (.run-at-field, flex-
     grow: 0) sizes ITSELF from this very input's own width, so 100%
     of "whatever the input turns out to be" just fed back into
     whatever the browser's intrinsic guess happened to be, which is
     exactly the narrow-on-iPad bug the generic fix exists to remove. */
  margin-top: 0; font-size: 1.1em;
}
.run-at-fields .secondary-button { flex: 0 0 auto; padding: 8px 14px; }
/* baseline lines the checkbox up with the actual text next to it, not
   just its top edge (which read as floating noticeably above it). */
.repeat-weekly-label { display: flex; align-items: baseline; gap: 8px; white-space: normal; }
.repeat-weekly-label input { flex: 0 0 auto; }
/* Real gap found live, twice: sharing a row with Time meant "Repeat
   weekly" position depended on exactly how wide a native time input
   rendered on a given device (which varied enough between an iPhone
   and an iPad alone to break a fixed margin tuned for one of them) —
   its own row (see _run_at_field.html) removes that dependency
   entirely rather than trying to out-guess it a third time. */
.run-at-row .repeat-weekly-label { margin-bottom: 0; }

dialog {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px;
  max-width: 320px;
  width: 90%;
}
dialog::backdrop { background: rgba(20, 32, 27, 0.45); }
dialog h2 { margin: 0 0 4px; font-size: 1.1em; }
.payment-method-choice { display: flex; gap: 16px; margin: 10px 0; }
.payment-method-choice label.radio { display: inline-flex; align-items: center; gap: 6px; margin-right: 0; }
.dialog-actions {
  display: flex; justify-content: flex-end; align-items: center; gap: 16px;
  margin-top: 14px;
}
.dialog-actions .link-button { margin: 0 !important; }

.flash {
  /* Now the last thing in .content (base.html), not the first — the
     same spot a scan's own "still going" banner occupies while it's
     running, so a result reads as a continuation of that, not a jump
     back up to the top of the page. */
  background: #FFF7E6;
  border: 1px solid var(--gold);
  border-radius: 8px;
  padding: 10px 14px;
  margin-top: 16px;
}
.flash p { margin: 4px 0; }

/* Wide content (My Bookings' table) gets its own horizontal scroll —
   the page body itself must never scroll sideways. */
.table-scroll { overflow-x: auto; margin-top: 14px; }
table { border-collapse: collapse; width: 100%; font-size: 0.92em; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; }
th { color: var(--muted); font-weight: 600; }
td form { margin: 0; }

pre#log-tail {
  background: #14201B;
  color: #D9E6DE;
  padding: 14px;
  border-radius: 8px;
  overflow-x: auto;
  max-height: 320px;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Status Log's own leading icon column (a magnifying glass, not the
   text link "Detailed" it replaced) — no underline on an emoji glyph,
   and a narrow fixed column so it doesn't stretch with the table. */
#status-log-table th:first-child, #status-log-table td:first-child { width: 1%; white-space: nowrap; }
.status-log-detail-link { text-decoration: none; font-size: 1.05em; }

/* Message column: truncated with an ellipsis rather than pushing the
   table wider than the page (real bug found live — a long booking/
   detail line ran off the right edge of the screen entirely). Needs
   table-layout: fixed for the ellipsis to actually take effect at
   all — otherwise the column just grows to fit the text, same as the
   bug being fixed. Full text still available via the cell's own
   title attribute (a native browser tooltip on hover). */
#status-log-table { table-layout: fixed; width: 100%; }
.status-log-message {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  cursor: help;
}

/* Session Finder's own "?" — reinstated Check availability/Who's
   playing, collapsed into one small icon next to the session name
   rather than the standalone app's own multi-select-rows-then-a-
   shared-button UI. */
.session-info-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-left: 4px; padding: 0;
  border: 1px solid var(--border); border-radius: 50%; background: none;
  color: var(--muted); font-size: 0.75em; line-height: 1; cursor: pointer;
}
.session-info-btn:hover { background: var(--bg); color: var(--text); }
.session-info-row td { white-space: normal; }

/* A red "✕" icon in place of a text "Remove" button — Scheduled
   Bookings' own Recurring table. */
.icon-btn-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 1px solid #D9534F; border-radius: 50%; background: none;
  color: #D9534F; font-size: 0.9em; line-height: 1; cursor: pointer;
}
.icon-btn-remove:hover { background: #D9534F; color: #fff; }


/* Global "a scheduled booking just went through" pop-up — see
   base.html's own script for when this shows. The container is fixed
   at the top so toasts are visible regardless of which page happened
   to be open when the booking actually fired; individual toasts stack
   inside it rather than each being fixed themselves. */
.recent-booking-toast-container {
  position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
  z-index: 50; display: flex; flex-direction: column; gap: 8px;
  max-width: 90vw; align-items: center;
}
.recent-booking-toast {
  background: var(--panel); border: 1px solid var(--border);
  border-left: 4px solid var(--green); border-radius: 8px;
  padding: 12px 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.12);
  display: flex; align-items: center; gap: 12px; max-width: 90vw;
}
.recent-booking-toast.test { border-left-color: var(--gold); }
.recent-booking-toast button {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 1.1em; line-height: 1; padding: 0;
}

/* Every mobile override lives in this ONE block, positioned at the
   very END of the file deliberately — not for tidiness, but because a
   real, repeated bug came from having it near the TOP instead: at
   equal selector specificity, CSS's own tiebreaker is source order
   (whichever rule comes LAST in the file wins), and this file has
   plenty of base rules for the exact same selectors (.card,
   .field-row, .field-row.two-col, input[type=date] max-width, table/
   td, #status-log-table, even a couple of !important declarations)
   sitting further down than an earlier version of this block was.
   Every one of those silently defeated this block's own override —
   confirmed live: Player 1-4 kept rendering two-per-row on a phone no
   matter what this block said, My Player Rating stayed pushed right,
   and Status Log's message column kept truncating with an ellipsis
   instead of wrapping — because the LATER, unconditional rule for
   each of those always won regardless of screen width. Any new mobile
   override belongs in here, at the bottom, not spliced in next to the
   desktop rule it's overriding. */
@media (max-width: 780px) {
  .app-shell { flex-direction: column; }
  .nav-edge-tab { display: block; }
  /* The logo's own desktop rule (.brand-logo, width: 100%) assumes a
     full-width sidebar column — needs an actual fixed size on the
     drawer's own narrower width instead. Centred (.sidebar-brand's own
     base rule already does that) — no close button to share the row
     with any more, the edge tab/backdrop are the only way to dismiss
     the drawer now. */
  .sidebar-brand .brand-logo { width: 84px; height: 84px; flex: 0 0 auto; }
  .sidebar {
    position: fixed; top: 0; left: 0; z-index: 35;
    /* Narrower than before — real gap found live: at the old 84vw/
       300px, the sidebar was wider than its own content (nav links,
       the Next booking countdown box) needed, leaving the countdown
       box's left/right margins reading unevenly against the drawer's
       own edges instead of sitting symmetrically inside it. */
    width: 76vw; max-width: 270px; height: 100vh;
    padding-left: calc(18px + env(safe-area-inset-left, 0px));
    padding-top: 14px; padding-bottom: 14px;
    box-shadow: 4px 0 24px rgba(0,0,0,0.25);
    transform: translateX(-100%);
    transition: transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1);
    /* Real gap found live: the desktop 24px gap between brand/nav/
       status (three sections) plus roomier per-link padding below
       added up to more height than a phone screen actually has —
       Credit Balance/Next booking ended up needing a scroll to reach.
       Tighter throughout so the whole drawer fits on one screen. */
    gap: 10px; overflow-y: auto;
  }
  .sidebar.open { transform: translateX(0); }
  /* Clears the edge tab's own bottom edge (top: 16px + height: 44px)
     plus a small gap, so it never overlaps a card's own heading
     sitting flush at the top-left of the page. */
  .main-panel { padding: calc(env(safe-area-inset-top, 0px) + 70px) 16px 32px; width: 100%; max-width: none; }
  /* .keep-inline opts a specific .field-row OUT of the blanket "stack
     everything" treatment below — Court Finder's own From/To and
     Duration/Earliest/Latest groups are deliberately short, narrow
     fields meant to stay paired on one line (wrapping via the base
     rule's own flex-wrap if they truly don't fit), not stacked one
     per line like Sessions' own Join waitlist/Hide my name/Player
     Rating cluster still needs. */
  .field-row:not(.keep-inline), .field-row.two-col:not(.keep-inline) { display: block; }
  .field-row > label { margin-bottom: 14px; }
  /* Tighter than the base 160px flex-basis — Duration/Earliest time/
     Latest time are all short fields (a number, a "HH:MM") that don't
     need anywhere near that much room, and fitting three of them on
     one line at all requires giving up that generous a minimum. */
  .field-row.tight > label { flex: 1 1 90px; }
  /* "Join waitlist"/"Hide my name" read as two closely-related toggles
     — real gap found live: each carried the same 14px bottom margin
     an actual full-width INPUT field needs to separate it from what
     follows, which read as an oddly large gap between two short
     checkboxes stacked right on top of each other. */
  .field-row > label.checkbox { margin-bottom: 6px; }
  /* My Player Rating no longer has a row of sibling fields to sit at
     the END of (everything's stacked now) — push-right's own
     margin-left: auto just stranded it on its own line, right-aligned
     with nothing to its left. */
  .field-row > .push-right { margin-left: 0 !important; margin-bottom: 14px; }
  /* Refresh / Hide cancelled / Hide test bookings stacked instead of
     one flex row with no wrap — real gap found live: "Hide test
     bookings" ran off the right edge of the screen instead of ever
     reaching a second line. */
  .last-refreshed-row { flex-direction: column; align-items: flex-start; gap: 12px; }
  /* Roomier tap targets and a touch less padding hunting for space on
     a phone-width card — 28px on all four sides of every card added
     up fast on a 375px-wide screen. */
  .card { padding: 20px; }
  .card + .card { margin-top: 14px; }
  button[type=submit], .primary-button, .secondary-button:not(.small-button) { min-height: 44px; }
  .icon-btn-remove, .session-info-btn { width: 32px; height: 32px; }
  /* Taller than desktop (a thumb, not a mouse pointer) but tighter
     than an earlier version of this rule (13px) — that, plus the
     drawer's own 24px section gap, pushed Credit Balance/Next booking
     below the fold on a real phone screen. */
  .sidebar-nav { gap: 0; }
  .sidebar-nav a { padding: 9px 12px; font-size: 0.98em; }
  .sidebar-status { gap: 8px; padding: 10px 0; }
  .sidebar-brand { margin-bottom: 4px; }
  /* Forces the "?"/Select pair (.session-finder-actions) onto its own
     line below the session name — a 100%-wide flex item can never
     share a row with anything else, so combined with the parent td's
     own flex-wrap (below) this always starts a fresh line. */
  .session-finder-actions { flex: 1 0 100%; }

  /* Every table becomes one card per row instead of a horizontally-
     scrolling grid — the explicit design brief: never make the user
     scroll sideways to read anything, only up/down (modelled on
     Padel4all's own mobile site, whose own schedule reads as a plain
     vertical list rather than a table at all). The header row itself
     disappears; each cell shows its own column name instead, via
     data-label (every table in every template sets this per <td> —
     see e.g. current_bookings.html's own comment on why headers can't
     just be read from the <thead> in CSS: attr() only ever reads the
     SAME element's own attribute, never a sibling's). Applies to
     every plain <table> (Scheduled Bookings, Waitlist, Monitored
     Courts, Status Log) and .finder-table (Session/Court Finder,
     Current Bookings) alike; #status-log-table's own desktop-only
     table-layout/ellipsis rules (below) are overridden back out. */
  table, .finder-table { display: block; width: 100%; border: none; }
  table thead, .finder-table thead { display: none; }
  table tbody, .finder-table tbody, table tr, .finder-table tr { display: block; width: 100%; }
  table tr, .finder-table tr {
    margin-bottom: 10px; padding: 4px 14px;
    border: 1px solid var(--border); border-radius: 10px; background: var(--panel);
  }
  /* Label then value, wrapping onto a second line together rather
     than being squeezed onto one (flex-wrap: wrap) if the value is
     long — a booking's own name or a Status Log message can run much
     longer than "Date"/"Time" ever does, and a fixed left/right split
     would either clip it or force the row itself wider. */
  table td, .finder-table td {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 9px 0; border-bottom: 1px solid var(--border);
    white-space: normal; font-size: 0.92em;
  }
  table td:last-child, .finder-table td:last-child { border-bottom: none; }
  table td::before, .finder-table td::before {
    content: attr(data-label); font-weight: 600; color: var(--muted); flex: 0 0 auto;
  }
  table td:not([data-label])::before, .finder-table td:not([data-label])::before { content: none; }
  /* A cell with no visible content and no label (a pure spacer/action
     column some rows leave empty, e.g. Scheduled Bookings' own
     "Confirm card now" column) would otherwise still draw an empty
     row with just a bottom border — collapse it instead. */
  table td:empty:not([data-label]), .finder-table td:empty:not([data-label]) { display: none; }
  /* No horizontal OR clipped-height scrolling once rows are full-width
     stacked cards — this is now just part of the page's own natural
     vertical flow ("easy to go up and down"). */
  .finder-table-scroll, .table-scroll { overflow: visible; max-height: none; }
  /* Status Log's own desktop-only fixed-width/ellipsis treatment
     (needed there to stop a long message pushing a real GRID table
     wider than the screen) is moot once every row is a stacked card
     instead — the message now just wraps like any other cell. */
  #status-log-table { table-layout: auto; }
  /* Real gap found live: this rule reset overflow/text-overflow but
     left white-space: nowrap in place from the desktop rule above —
     with nowrap and overflow now visible (instead of hidden), a long
     message had nowhere to go but spill off the right edge of the
     card/screen instead of wrapping. white-space: normal is the actual
     fix; overflow/text-overflow above are harmless once nowrap is
     gone, kept for clarity. */
  .status-log-message { overflow: visible; text-overflow: clip; white-space: normal; max-width: 100%; }
}
