:root {
  /* The page canvas is near-black — teal is demoted from "the brand colour" to
     a surface tint, so we don't read as the venue's own teal-on-white booking
     app. Glass surfaces float above this and pick up the tropical glow below. */
  --bg: #0b1013;
  /* Solid fallbacks for surfaces where backdrop-filter is unsupported. The
     glass rules below layer over these; if blur fails, these still look right. */
  --bg-elev: #141d21;     /* match card */
  --bg-elev-hover: #1a252a;
  --bg-elev-2: #1c282d;   /* filled slot inside a card */
  --fg: #eaf3f2;
  --fg-dim: #8fa9ae;
  --border: #2b4048;
  --accent: #cbe45f;      /* open seat — this colour means one thing only */
  --accent-dim: #5d6b2c;
  /* Taken cell in the week map. Lifted to read on the darker glass panel. */
  --neutral: #46626b;
  --focus: #6fb1ff;
  /* On court right now. Red is a warning colour everywhere else, so it is
     never used alone here — always with the word LIVE beside the dot. */
  --live: #ff6b5e;
  /* Frosted-glass surfaces (Apple-ish liquid glass): translucent teal fill,
     hairline rim, a bright top edge for the specular highlight. */
  --glass: rgba(28, 44, 51, 0.42);
  --glass-hover: rgba(38, 56, 64, 0.52);
  --glass-rim: rgba(255, 255, 255, 0.10);
  --glass-top: rgba(255, 255, 255, 0.12);
  --mono: ui-monospace, SFMono-Regular, Menlo, "Roboto Mono", monospace;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  min-height: 100%;
}

/* Tropical ambience the glass refracts. Fixed, behind everything, non-inter-
   active — without something coloured here the frosted panels have nothing to
   pick up and read as flat grey. Kept dim so text contrast never suffers. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1100px 620px at 12% -8%, rgba(203, 228, 95, 0.10), transparent 58%),
    radial-gradient(1000px 760px at 112% 12%, rgba(41, 120, 132, 0.28), transparent 60%),
    radial-gradient(900px 900px at 50% 120%, rgba(41, 120, 132, 0.14), transparent 60%),
    var(--bg);
}

body {
  max-width: 780px;
  margin: 0 auto;
  padding: 16px 14px 80px;
}

header.top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0 14px;
}
header.top .source {
  color: var(--fg-dim);
  font-size: 12px;
  text-align: right;
}

/* Brand lockup: a lime court chip + the wordmark. "Let's play" is the hero
   (this whole app is an invitation to play); "@ Liga Umalas" is the venue
   credit, kept small — the league is the place, not the product. */
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}
.brand-mark {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 13px;
  background: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
}
.brand-mark svg { display: block; }
.brand-text {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.brand-mark-word {
  font-family: "Bricolage Grotesque", system-ui, sans-serif;
  font-weight: 800;
  font-size: 25px;
  line-height: 0.95;
  letter-spacing: -0.5px;
  color: var(--accent);
}
.brand-mark-word b { font-weight: 800; color: var(--fg); }
.brand-venue {
  font-size: 10px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.brand-venue i { font-style: normal; color: var(--accent); }

/* Week at a glance — per-day availability columns.
   One row always; every weekday column grows upward from a shared baseline.

   At rest this is one card: date range, map, legend. Once it pins to the top
   the range and legend fold away and only the map rides along — the card
   becomes a thin indicator. The whole card stays sticky the entire time (a
   sticky box can only travel inside its parent, so the map cannot detach and
   go on alone); what changes is how much of it is left. */
.glance {
  position: sticky;
  top: 0;
  z-index: 20;
  margin-bottom: 16px;
  padding: 12px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  backdrop-filter: blur(20px) saturate(1.35);
  border: 1px solid var(--glass-rim);
  border-radius: 14px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45), inset 0 1px 0 var(--glass-top);
  transition: padding 220ms ease, box-shadow 220ms ease;
}
/* Zero-height marker sitting where the card starts. Once it scrolls out of
   view the card is pinned, and app.js flips .stuck. */
#glance-sentinel { height: 1px; margin-bottom: -1px; }

/* Pinned: tighten the shell so the map reads as a bar, not a truncated card,
   and deepen the shadow so it clearly floats over the list it now covers. */
.glance.stuck {
  padding: 8px 12px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55), inset 0 1px 0 var(--glass-top);
}
/* A row that animates 1fr → 0fr collapses its content without anyone having to
   know its height. max-height would mean guessing a number that either clips
   the content or leaves dead space once the motion ends.
   The outer margin lives here, not on the child: a margin inside the collapsing
   row survives the squeeze and leaves a stubborn gap behind. */
.fold {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 240ms ease, opacity 160ms ease,
              margin 240ms ease, visibility 240ms;
}
.fold > * { min-height: 0; overflow: hidden; }
.fold-head   { margin-bottom: 10px; }
.fold-legend { margin-top: 10px; }

.glance.stuck .fold {
  grid-template-rows: 0fr;
  opacity: 0;
  margin: 0;
  /* Not just invisible — the pager buttons must leave the tab order too, or
     they stay reachable by keyboard while collapsed to nothing. */
  visibility: hidden;
}

/* Worn for the length of one measurement (see stickyHeight in app.js) so the
   pinned height can be read without any of it animating into view. */
.glance.measuring, .glance.measuring .fold { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .glance, .fold { transition: none; }
}
/* [‹] [ range, centred ] [›]. The arrows sit in fixed-width side columns, so
   whether one is present or hidden the range never moves — paging a window
   leaves the headline where the eye already is. */
.glance-head {
  display: grid;
  grid-template-columns: 34px 1fr 34px;
  align-items: center;
  gap: 8px;
}
/* The date span is the headline of the top bar now: which days you're looking
   at is the one thing a first-time reader needs before the map means anything. */
.glance-range {
  justify-self: center;   /* centre the (nowrap) box within its 1fr column */
  white-space: nowrap;    /* one line always — never let the range stack up */
  text-align: center;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--fg);
}

#week-map {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 6px;
  align-items: end;   /* labels sit on one baseline; bars rise above */
}
.day-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 6px 2px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 10px;
  cursor: pointer;
  font: inherit;
  -webkit-appearance: none;
  appearance: none;
}
.day-col.active {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* Each match is one row of 4 cells — a padel match is 4 players, so a row
   reads as "this match, N seats open". Rows stack up the day column. */
.day-matches {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  max-width: 44px;
}
.match-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
}
.gcell {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 2px;
}
.gcell.open  { background: var(--accent); }
.gcell.taken { background: var(--neutral); }
/* The active day's date, above its weekday. Hidden — not absent — on every
   other column: the space stays reserved so the sticky bar keeps one height
   and the feed below never jumps as the active day moves.

   It waits for .stuck because until the bar pins, the range header above is
   still saying JUL 26 – AUG 1. Once that folds away, this is the only place
   left that gives a number to the day you are reading. */
.day-date {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;   /* four glyphs (10/31) in a ~44px column — no padding out */
  line-height: 1;
  color: var(--fg-dim);
  visibility: hidden;
}
/* Full strength, one step brighter than the weekday under it: this is the line
   the reader came here to read, and the weekday is the label that never moves.

   Not lime. On this page lime means "a seat you can take right now" (the map's
   whole language is one colour), and a date is not something you can claim. */
.glance.stuck .day-col.active .day-date {
  visibility: visible;
  color: var(--fg);
}
.day-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--fg-dim);
}
.day-col.active .day-label { color: var(--fg); }
/* A day inside the window with nothing on it. It keeps its column so the
   weekdays below never shift, but nothing about it invites a tap.
   Not called `empty`: that class is the page's "nothing here" message box,
   and a day column wearing it inherits a 30px dashed panel. */
.day-col.blank { cursor: default; }
.day-col.blank .day-label { color: color-mix(in srgb, var(--fg-dim) 55%, transparent); }
.day-col.today .day-label {
  color: var(--fg);
  font-size: 10px;
  letter-spacing: 0.3px;
}

/* Window pager. Arrows flank the range they move, so the control and the
   thing it changes are the same object. */
.wnav {
  background: none;
  border: 0;
  padding: 4px 0;
  font-size: 20px;
  line-height: 1;
  color: var(--fg-dim);
  cursor: pointer;
}
.wnav:hover { color: var(--fg); }
/* Nowhere to go this direction. Hidden, not greyed — a dim arrow advertises
   days that don't exist. visibility (not display) keeps the side column's
   width, so the range stays dead centre whether or not the arrow is there,
   and an invisible button is out of the tab order too. */
.wnav.off { visibility: hidden; }

/* Spacing lives on .fold-legend so it can collapse with the row. */
.glance-legend {
  display: flex;
  gap: 16px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.glance-legend .lg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--fg-dim);
}
.swatch { width: 12px; height: 12px; border-radius: 3px; }
.swatch.open  { background: var(--accent); }
.swatch.taken { background: var(--neutral); }

/* No backdrop-filter (older engines): drop the translucency and fall back to
   the solid surface tokens so panels stay legible instead of muddy. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .glance { background: var(--bg-elev); }
  .match { background: var(--bg-elev); }
  .match.linked:hover { background: var(--bg-elev-hover); }
}

button { font: inherit; color: var(--fg); }

/* Day divider — the weekday is the loudest thing in the list body, so the
   reader always knows which day they've scrolled into. */
.date-header {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 28px 0 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
  font-weight: 400;
}
.dh-day {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: 1px;
  color: var(--fg);
}
.dh-date {
  font-size: 13px;
  color: var(--fg-dim);
}

.match {
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  backdrop-filter: blur(20px) saturate(1.35);
  border: 1px solid var(--glass-rim);
  border-radius: 14px;
  padding: 14px;
  margin-bottom: 12px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45), inset 0 1px 0 var(--glass-top);
  transition: background 0.2s, box-shadow 0.2s;
}
.match.focused {
  box-shadow: 0 0 0 2px var(--focus), 0 10px 34px rgba(0, 0, 0, 0.45);
}
.match.linked { cursor: pointer; }
.match.linked:hover { background: var(--glass-hover); }

.match-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.time {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
/* Start time and court on one row — see render.js. Centred so the small pill
   sits against the big number rather than hanging off its top. */
.time-top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.time-start {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.5px;
  line-height: 1;
}
.time-end {
  font-size: 12px;
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}

/* On court now. A hairline inside the card rather than a red box around it —
   one card in a list should not shout over its neighbours to say "started". */
.match.live {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--live) 42%, transparent);
}
.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  align-self: flex-start;
  margin-top: 5px;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--live) 15%, transparent);
  color: var(--live);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.2px;
}
.live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--live);
  animation: live-pulse 1.8s ease-in-out infinite;
}
@keyframes live-pulse { 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) {
  .live-dot { animation: none; }
}

/* Called off. The whole card fades and the time gets struck through, so it
   reads as "was going to happen, isn't" at a glance — no accent, no border
   shout. It stays in the list (and keeps its /m/<id>/ link) rather than
   vanishing, because a link to it may already be out in the group chat. */
.match.cancelled {
  opacity: 0.6;
}
.match.cancelled .time-start,
.match.cancelled .time-end {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}
.cancelled-badge {
  align-self: flex-start;
  margin-top: 5px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.2px;
}

/* Where to walk. Same chip as CANCELLED but readable rather than dimmed — this
   is information, not a struck-out state. Never lime: on this page lime means
   a seat you can take right now, and a court number is not a thing to claim. */
.court-badge {
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--border) 30%, transparent);
  color: var(--fg);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.2px;
}

/* Level meter: 5 bars filled to the modifier step, tinted by tier. */
.levels {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  padding-top: 2px;
}
.level {
  --tier-color: #888;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.level-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--tier-color);
  white-space: nowrap;
}
.rungs {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
}
.rung {
  width: 3px;
  height: 12px;
  border-radius: 1px;
  background: color-mix(in srgb, var(--tier-color) 22%, transparent);
}
.rung.on { background: var(--tier-color); }

.note {
  font-size: 12px;
  color: var(--fg-dim);
  margin: -4px 0 10px;
  font-style: italic;
}

/* Four seats, one row — a padel match is 4 players and the row says so. */
.slots {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 12px 4px;
  border-radius: 12px;
  min-width: 0;
  font: inherit;
  font-size: 12px;
  text-align: center;
}
.slot.filled {
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: var(--fg);
}
.slot.open {
  background: transparent;
  border: 1.5px dashed var(--accent);
  color: var(--accent);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background 0.15s;
}
.slot.open:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
/* With the word gone the circle is the whole content, so it centres in the box
   and grows to carry it — a 34px circle pinned to the top left the seat looking
   like something had been deleted from underneath it.

   The cross is two bars rather than a "+" glyph: a character is placed by the
   font's baseline, so it sits a couple of px below the circle's true centre.
   Bars are centred on the box itself, which is exact at any size. */
.slot.open .plus {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1.5px solid var(--accent);
}
.slot.open .plus::before,
.slot.open .plus::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--accent);
  border-radius: 1px;
}
.slot.open .plus::before { width: 17px; height: 2.5px; }
.slot.open .plus::after  { width: 2.5px; height: 17px; }

/* An empty seat in a match already on court. It keeps the dashed shape so the
   row still reads as "this four was never filled", but drops the accent: lime
   means "you can have this seat" everywhere on the page, and here you can't. */
.slot.open.closed {
  border-color: var(--border);
  color: var(--fg-dim);
  cursor: default;
  opacity: 0.55;
}
.slot.open.closed:hover { background: transparent; }
.slot.open.closed .plus { border-color: var(--border); }
.slot.open.closed .plus::before,
.slot.open.closed .plus::after { background: var(--border); }
/* Waitlist — only ever drawn on a full, upcoming match.
   Nothing here is lime. On this page lime means "a seat you can take right
   now", and a queue is the opposite of that: the whole row exists because
   there is no seat. It reads in the muted palette instead, separated from the
   seats by a hairline so it is clearly a second, lesser way in. */
.waitlist {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
}
/* Label left, join button hard right. Its own row rather than sharing one with
   the names: a queue of five wraps to two lines, and a button riding along at
   the end of that would land in a different place on every match. */
.waitlist-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.waitlist-label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.2px;
  color: var(--fg-dim);
  white-space: nowrap;
}
/* Small and outlined — the page's one filled button is Open WhatsApp, which is
   still the way to say anything at all about this match. No lime: that colour
   is reserved for a seat you can take, and this row exists because there is
   none. */
.waitlist-join {
  flex: none;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  font: inherit;
  font-size: 12px;
  line-height: 1.3;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background 0.15s, border-color 0.15s;
}
.waitlist-join:hover {
  background: rgba(255, 255, 255, 0.07);
  border-color: var(--fg-dim);
}
/* The one spot is taken. Same treatment as a seat in a match already on court:
   the shape stays so the row still reads as a queue, but it dims out and stops
   responding. The name beside it is what filled it. */
.waitlist-join:disabled {
  color: var(--fg-dim);
  opacity: 0.5;
  cursor: default;
}
.waitlist-join:disabled:hover {
  background: transparent;
  border-color: var(--border);
}
.waitlist-none {
  font-size: 12px;
  color: var(--fg-dim);
  font-style: italic;
}
/* Wraps rather than scrolls: a queue is short, and a row that runs off the
   edge hides the very name someone came here to find. */
.waiters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  min-width: 0;
}
/* Beside their name, not stacked over it like a seat. A seat is a slot in a
   four-wide grid; a waiter is a line in a list, and laying them out the same
   way would have the queue reading as a second set of seats. */
.waiter {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  font-size: 12px;
}
.waiter .avatar {
  width: 28px;
  height: 28px;
  font-size: 10px;
}

.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;
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  border: 2px solid #4a6b73;
  font-size: 11px;
  font-weight: 700;
  color: var(--fg);
  flex-shrink: 0;
}
.player-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.empty {
  padding: 30px;
  text-align: center;
  color: var(--fg-dim);
  border: 1px dashed var(--border);
  border-radius: 12px;
}
.empty p { margin: 0 0 14px; }

/* Single-match page (/m/<id>/) */
.back {
  color: var(--fg-dim);
  text-decoration: none;
  font-size: 13px;
}
.back:hover { color: var(--fg); }

.actions {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 8px;
  margin-top: 12px;
}
.act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 13px 8px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  font: inherit;
  font-size: 13px;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s;
}
.act:hover { background: var(--bg-elev); }
.act.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #28340a;
  font-weight: 600;
}
.act.primary:hover { background: color-mix(in srgb, var(--accent) 88%, #fff); }
.empty .act { display: inline-flex; }

/* Toast */
#toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(20px);
  background: #eaeef4;
  color: #0b1220;
  padding: 10px 16px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
