/* Mahogany Courts — everything Bootstrap does not give us: the week grid, the
   booking modal, the admin booking blocks, the toasts. Bootstrap 5 (see the
   layout) supplies buttons, forms and badges. */

:root {
  --mc-ink: #1f2933;
  --mc-muted: #6b7280;
  --mc-line: #e2e6ea;
  /* The same grey, taken as far down as the grid's own palette goes: it is the
     colour a merged block is ringed in below. The hairline above rules the
     inside of a day — the hours, and the courts sharing the column; this one
     rules the frame around it, which the eye reads the week's shape from: the
     day boundaries, the header row, and the edge of the whole table. */
  --mc-line-strong: #aab1ba;
  --mc-open: #ffffff;
  --mc-open-hover: #eef6ff;
  --mc-accent: #2563eb;      /* hover, focus, links into the grid */
  --mc-reserved: #c7ccd3;    /* a taken slot: light-mid grey */
  --mc-reserved-ink: #454c56;
  --mc-reserved-line: #aab1ba;   /* the one border round a merged block */
  --mc-reserved-past: #e2e5e9;
  --mc-reserved-past-line: #ccd1d7;
  --mc-today: #fff8e6;
  --mc-past: #f4f5f7;
  --mc-brand: #7b3f21; /* mahogany */
  --mc-slot-height: 34px;      /* one bookable hour of the venue's day */
  /* Fits "10pm - 11pm", which is the widest an hour of the venue's day can be
     written (see buildWeekGrid): business hours must fall on the hour, so no row
     label ever carries a ":30", and the longest range is two two-digit hours.
     The same width on a phone as on a desk — the type does not shrink there, so
     the column cannot either. */
  --mc-time-col: 100px;
  /* The narrowest a day of the week may be drawn. Seven of these plus the time
     column is what the grid is at least as wide as, and anything past that is
     spare room shared out between the days — so on a desk it is a week that
     fits, and on a phone it is a week that scrolls. */
  --mc-day-col: 104px;
}

/* The venue itself, behind everything.
 *
 * An aerial photograph of the two courts (app/assets/images/court-backdrop.jpg),
 * and nothing over it: the picture arrives already blurred and darkened, and it
 * is shown as it is. No wash, no tint, no gradient between the photograph and
 * the page.
 *
 * The colour stays as `background-color` rather than being folded into the
 * shorthand, so it is still what shows while the photo loads, if it 404s, and
 * on the printed page — where the @media print rules put the background back to
 * plain white (see the foot of this file).
 *
 * `fixed` so the courts sit still while a week scrolls over them, except where
 * that is a bad trade: a touch device repaints a fixed background on every
 * scroll frame, and phones are most of this app's traffic, so there it scrolls
 * with the page like an ordinary image. */
body {
  color: var(--mc-ink);
  background-color: #f7f8fa;
  background-image: url("/assets/court-backdrop-7876bbeb.jpg");
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
}
@media (hover: none), (max-width: 640px) {
  body { background-attachment: scroll; }

  /* A phone cannot show seven days at once, and the version of it that tries —
     seven 104px columns crushed into 412px of glass — is a week nobody can
     read: a name on a booking block is three letters and an ellipsis, and the
     times written across a selection do not fit at all. So the columns keep the
     width they need and the week scrolls sideways instead, which is the trade a
     phone is used to making. The frozen time column and the sticky day headers
     go on doing their jobs through it: both are `position: sticky` against the
     scroller, which is what they were before there was anything to scroll. */
  :root { --mc-day-col: 140px; }
}

/* --- toasts ---------------------------------------------------------------
   Every message this app has — the flash from the last request and every refusal
   a booking modal used to print on a line inside itself — arrives at the top of
   the screen, centred, and leaves on its own. Above everything: the tallest
   layer anything else claims is the user menu's panel at 1100 (Bootstrap's own
   ceiling is its tooltip at 1080), and a message has to clear all of it, because
   a modal's own error is one of the things being said.

   Centred by margin rather than a translate, so the stack keeps its own
   transform free for the toasts' entrance. */
.mc-toasts {
  position: fixed;
  top: 1rem;
  left: 0;
  right: 0;
  margin-inline: auto;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(24rem, calc(100vw - 2rem));
  pointer-events: none;   /* the band stays clickable between toasts */
}
/* The colour bar down the left is an inset shadow rather than a thick border, so
   it follows the rounded corner instead of curving away from the card. */
.mc-toast {
  --mc-toast-accent: var(--mc-muted);
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.65rem 0.5rem 0.65rem 1rem;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  background: #fff;
  color: var(--mc-ink);
  font-size: 0.92rem;
  line-height: 1.35;
  box-shadow: inset 4px 0 0 0 var(--mc-toast-accent), 0 10px 28px rgba(16, 24, 40, 0.18);
  transition: opacity 0.25s ease, transform 0.25s ease;
  animation: mc-toast-in 0.18s ease-out;
}
.mc-toast--notice { --mc-toast-accent: #157347; }
.mc-toast--alert { --mc-toast-accent: #b42318; }
.mc-toast.is-leaving { opacity: 0; transform: translateY(-0.4rem); }
.mc-toast__text { flex: 1 1 auto; min-width: 0; }
.mc-toast__close {
  flex: 0 0 auto;
  border: 0;
  background: none;
  padding: 0 0.35rem;
  font-size: 1.15rem;
  line-height: 1;
  color: var(--mc-muted);
  cursor: pointer;
}
.mc-toast__close:hover { color: var(--mc-ink); }
@keyframes mc-toast-in {
  from { opacity: 0; transform: translateY(-0.5rem); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .mc-toast { animation: none; transition: none; }
}

.mc-shell {
  max-width: 1180px;
  margin: 0 auto;
  padding: 4.25rem 1rem 2rem;
}
/* The tall top is for the public pages, where the card floats on the photograph
   and the logo disc rides up off its edge. Behind the admin bar the mark lives
   on the bar itself, so all that room would just be distance between the bar
   and the desk: any shell that follows the bar starts close under it. */
.mc-navbar + .mc-shell { padding-top: 1.5rem; }

/* --- headers -------------------------------------------------------------- */

.mc-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.mc-header h1 {
  margin: 0;
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--mc-brand);
}

/* The player page's logo rides the top edge of the calendar's card: centred in
   the 3em band of padding the well keeps clear at the top, so the disc sits
   half on the card and half over the photograph behind it. Centring is done
   with a transform rather than measurements, so the smaller disc a phone gets
   lands in the same place by the same rule. */
.mc-well > .mc-logo-badge {
  position: absolute;
  top: 0.1em;
  left: 50%;
  transform: translate(-50%, -50%);
}
.mc-well > .mc-logo-badge.is-link:hover,
.mc-well > .mc-logo-badge.is-link:focus-visible {
  transform: translate(-50%, calc(-50% - 1px));
}
@media (prefers-reduced-motion: reduce) {
  .mc-well > .mc-logo-badge.is-link:hover { transform: translate(-50%, -50%); }
}
/* The logo sits in a white disc rather than directly on the page: the page is
   grey, most logos are drawn for white, and a badge is the one shape that
   flatters a square mark, a round one and a wide wordmark alike. The disc is
   larger than the mark inside it, so the artwork keeps its own margin instead
   of running into the edge. */
.mc-logo-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 104px;
  height: 104px;
  padding: 8px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.10), 0 0 0 1px rgba(16, 24, 40, 0.04);
  overflow: hidden;
}
.mc-logo {
  display: block;
  height: 80px;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}
/* The disc as a control — a door for either viewer, though to different rooms.
   It says so under the pointer and to the keyboard, and in no other way. */
.mc-logo-badge.is-link {
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.mc-logo-badge.is-link:hover,
.mc-logo-badge.is-link:focus-visible {
  box-shadow: 0 2px 10px rgba(16, 24, 40, 0.18), 0 0 0 2px var(--mc-brand);
  transform: translateY(-1px);
  outline: none;
}
@media (prefers-reduced-motion: reduce) {
  .mc-logo-badge.is-link { transition: none; }
  .mc-logo-badge.is-link:hover { transform: none; }
}

@media (max-width: 34rem) {
  .mc-logo-badge { width: 84px; height: 84px; padding: 6px; }
  .mc-logo { height: 64px; }
}

.mc-header .mc-sub {
  margin: 0.15rem 0 0;
  color: var(--mc-muted);
  font-size: 0.92rem;
}

/* A header with nothing in it but the venue's name, over a single centred card.
   On every other page the header is a bar with things at both ends of it, so it
   is laid out end to end; here there is nothing to put at the ends. */
.mc-header--centred {
  justify-content: center;
  text-align: center;
}

/* The booking page's letterhead, at the top of the card itself: the venue's
   logo, centred, at letterhead size — it IS the heading, so it gets a heading's
   presence. Contained rather than cropped, so a wide wordmark and a square
   badge both arrive whole.

   A venue with no logo uploaded puts its name here instead, and the name has to
   carry the same weight the picture would have, so it is styled to match the
   h1 every other page's header uses. */
.mc-letterhead {
  display: block;
  margin: 0 auto 0.75rem;
  max-height: 200px;
  max-width: 100%;
  object-fit: contain;
}
.mc-letterhead__name {
  margin: 0 0 0.75rem;
  text-align: center;
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--mc-brand);
}

.mc-navbar {
  background: #000000c7;
  color: #fff;
}
.mc-navbar .mc-shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1rem;
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
}
.mc-navbar .mc-brand {
  font-weight: 700;
  letter-spacing: 0.01em;
  color: #fff;
  text-decoration: none;
  margin-right: auto;
}
.mc-navbar a {
  color: rgba(255, 255, 255, 0.85);
  text-decoration: none;
  font-size: 0.92rem;
}
.mc-navbar a:hover { color: #fff; text-decoration: underline; }
.mc-navbar a.is-current { color: #fff; font-weight: 600; }
.mc-navbar form { display: inline; }
.mc-navbar .mc-signout {
  background: none;
  border: 0;
  padding: 0;
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.92rem;
}
.mc-navbar .mc-signout:hover { color: #fff; text-decoration: underline; }
.mc-navbar .mc-who { font-size: 0.82rem; color: rgba(255, 255, 255, 0.65); }

/* The venue's mark where its name was, at the height the name was — so the bar
   does not grow to make room for it. The same white disc it wears over the
   player's calendar, at a size that reads on a bar rather than over a page. */
.mc-navlogo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 3px;
  border-radius: 50%;
  background: #fff;
  overflow: hidden;
}
.mc-navlogo img {
  display: block;
  height: 30px;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}
.mc-navbar .mc-brand { display: inline-flex; align-items: center; }

/* The account menu: a disclosure, so the browser owns the opening and closing.
   The panel hangs off the right-hand end of the bar rather than pushing the bar
   about, which is what makes it a menu instead of a paragraph that appears. */
.mc-usermenu {
  position: relative;
  margin-left: 0.25rem;
}
.mc-usermenu > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.1rem;
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.92rem;
  cursor: pointer;
  list-style: none;          /* the default marker, which we draw ourselves */
}
.mc-usermenu > summary::-webkit-details-marker { display: none; }
.mc-usermenu > summary:hover { color: #fff; }
.mc-usermenu__caret { font-size: 0.7rem; opacity: 0.8; }
.mc-usermenu[open] .mc-usermenu__caret { transform: rotate(180deg); }

.mc-usermenu__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.45rem);
  z-index: 1100;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  min-width: 12rem;
  padding: 0.5rem 0.75rem 0.6rem;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 10px 28px rgba(16, 24, 40, 0.18);
}
.mc-usermenu__who {
  display: block;
  padding-bottom: 0.35rem;
  margin-bottom: 0.25rem;
  border-bottom: 1px solid var(--mc-line);
  width: 100%;
  color: var(--mc-muted);
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}
.mc-usermenu__panel a,
.mc-usermenu__panel .mc-signout {
  padding: 0.2rem 0;
  color: var(--mc-ink);
  font-size: 0.9rem;
  text-decoration: none;
}
.mc-usermenu__panel a:hover,
.mc-usermenu__panel .mc-signout:hover {
  color: var(--mc-brand);
  text-decoration: underline;
}

/* --- week toolbar --------------------------------------------------------- */

.mc-weekbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}
.mc-weekbar .mc-week-label {
  font-weight: 600;
  font-size: 1.02rem;
  min-width: 14rem;
}
.mc-weekbar .mc-spacer { margin-left: auto; }

.mc-legend {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: center;
  font-size: 0.82rem;
  color: var(--mc-muted);
}
.mc-legend span { display: inline-flex; align-items: center; gap: 0.35rem; }
.mc-legend i {
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 3px;
  display: inline-block;
  border: 1px solid var(--mc-line);
}
/* The colours both grids are read by. The white one has to read as a swatch
   rather than as a gap in the sentence, so its outline is darker than the grid's
   hairlines. Green and yellow are the booking blocks, and are the same two
   colours .wgb is drawn in below — a legend that has drifted from the grid it
   explains is worse than no legend. */
.mc-legend .sw-open { background: var(--mc-open); border-color: #b9c0c8; }
.mc-legend .sw-reserved { background: var(--mc-reserved); border-color: var(--mc-reserved-line); }
.mc-legend .sw-unverified { background: #fdf1c4; border-color: #e0c24e; }
.mc-legend .sw-verified { background: #d9f2e3; border-color: #94d3b3; }

/* --- the week grid -------------------------------------------------------- */

/* The grid scrolls inside its own box, spreadsheet-style: the day headers stay
   frozen at the top and the time column stays frozen on the left, so the page
   itself never has to scroll to keep your bearings. */
.wg-scroll {
  overflow: auto;
  /* The 15rem is everything above the grid on the page — the shell's margins,
     the well's own padding, the week bar and the status line — and what is left
     is the grid's. A well that carries a footer under the scroller (see
     shared/_calendar_shell) hands that line's height back out of the same
     budget, so the card still ends where it ended and the PAGE still does not
     scroll: the grid gives up the rows, not the reader. */
  max-height: calc(100vh - 15rem - var(--mc-grid-footer, 0rem));
  min-height: 22rem;
  background: #fff;
  border: 1px solid var(--mc-line-strong);
  border-radius: 10px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}

/* The minimum is stated twice on purpose. `minmax` is what stops a column being
   squeezed below it; the `min-width` is what makes the GRID BOX as wide as its
   own tracks, so that everything measured against that box — the crosshair
   bands, the selection rectangle, the sticky day headers — is measured against
   the whole week rather than against the slice of it currently on screen. */
.wg {
  position: relative;   /* the row band below is positioned against this */
  display: grid;
  grid-template-columns: var(--mc-time-col) repeat(7, minmax(var(--mc-day-col), 1fr));
  min-width: calc(var(--mc-time-col) + (var(--mc-day-col) * 7));
}

/* The crosshair: the hour you are reading across and the day you are
   reading down, tinted end to end under the hover the one cell beneath the
   cursor gets. Both bands are translucent and pointer-transparent, and sit at
   the frozen time column's own z-index but later in the DOM, so they tint the
   frozen label rather than sliding underneath it. The day headers are higher
   still (z-index 3); the hovered one takes the tint as a class instead, since a
   sticky header is not where a band drawn at the top of the grid would be. */
.wg__rowband,
.wg__colband {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  background: rgba(37, 99, 235, 0.06);
}
.wg__rowband {
  left: 0;
  right: 0;
  box-shadow: inset 0 1px 0 rgba(37, 99, 235, 0.16), inset 0 -1px 0 rgba(37, 99, 235, 0.16);
}
.wg__colband {
  bottom: 0;
  box-shadow: inset 1px 0 0 rgba(37, 99, 235, 0.16), inset -1px 0 0 rgba(37, 99, 235, 0.16);
}
.wg__rowband[hidden],
.wg__colband[hidden] { display: none; }

/* --- the hours being picked ------------------------------------------------
   A press dragged down a column is a booking being drawn on the week, and this
   is it: one rectangle over the hours it has taken in, laid over the grid the
   same way the crosshair bands are — absolutely positioned, pointer-transparent,
   and therefore unable to disturb the placement of a single cell underneath.
   One element moved and resized rather than a class put on every cell in the
   range, which is what keeps it right over a row whose free time is only half
   the column wide.

   It takes no z-index of its own, which puts it exactly where it belongs in the
   stack: over the cells, which are ordinary grid items, and UNDER the booking
   blocks and the frozen time column, which both claim one. That matters on a row
   whose free time is half a column — the half a booking is standing on keeps its
   own colour instead of going blue behind the selection, which would be the
   rectangle claiming courts it cannot have.

   The fill is the accent at a weight that reads as "chosen" over white and over
   today's cream alike, and the inset edge is what gives it a boundary without
   spending a pixel of the grid's own hairlines. */
.wg__sel {
  position: absolute;
  /* A centred column, because the rectangle carries up to two lines of caption —
     the courts over the times — and they have to sit as one stack in the middle
     of a box whose height is however many hours have been taken. Centring the
     PAIR is what keeps a one-hour selection's label inside its own 34px row;
     two separately-centred labels would each want the middle and the upper one
     would climb out of the top. Either line alone still lands dead centre, which
     is why the admin grid, which writes only the times, looks exactly as it did.
     The grips are positioned absolutely and take no part in this. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  pointer-events: none;
  border-radius: 6px;
  background: rgba(37, 99, 235, 0.16);
  box-shadow: inset 0 0 0 2px var(--mc-accent);
}
.wg__sel[hidden] { display: none; }

/* The times, written across the middle of the rectangle: "6:00pm – 8:00pm".
   The rectangle says how many hours have been taken and this says which — read
   where the hand already is, instead of in the time column at the far side of
   the week.

   A pill rather than bare text, because what is behind it is a pale blue wash
   over white, over today's cream, and over the hairlines between the rows; the
   near-white plate is what makes one set of type legible over all three. It is
   centred on the rectangle whatever the rectangle's height — by the flex column
   above rather than by any offset of its own — so the label of a one-hour
   selection sits in a single row exactly as a six-hour one sits in six.

   It inherits `pointer-events: none` from the rectangle and must go on doing
   so: the double tap that books a standing selection is a tap on the CELL
   underneath, and a label that answered a finger would swallow it.

   `white-space: nowrap` is deliberate over any kind of clipping. A range broken
   across two lines in a 34px row, or cut off at "6:00pm – 8:0", is worse than
   one that reaches a few pixels past a narrow column — this is a label that is
   only on screen while a hand is moving, and being readable is the whole of its
   job. */
.wg__selrange {
  padding: 0.1rem 0.35rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--mc-accent);
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* Nothing to say when there is no range — an empty pill is a white blob. */
.wg__selrange:empty { display: none; }

/* The courts, on the line above the times: "Court #1", "Court #1 and #2".
   Written only where a page asks for it — the player grid does, the admin grid
   does not — and the asking is the whole of the difference, because a span
   nobody has written into is empty, and an empty pill is not drawn.

   Same plate, same accent, same weight as the range beneath it: the two lines
   are one caption and should read as one. A step smaller, and with less padding,
   for the two reasons that pull the same way — it is the quieter half of what
   the box says, and both pills together have to fit inside the 34px of a
   one-hour selection, which at this size they do with about two pixels to spare
   at either end.

   `nowrap` for the same reason the range has it: "Court #1 and #2" broken over
   two lines in a half-column-wide rectangle is worse than one that reaches a
   little past its sides, and this is type that is only on screen while a
   selection is being made. */
.wg__selcourts {
  padding: 0.05rem 0.3rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--mc-accent);
  font-size: 0.5625rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.wg__selcourts:empty { display: none; }

/* The grips: the four edges of a selection a finger has left standing, and the
   only part of the rectangle that answers a touch. They exist because a phone
   has no hover and no second button — a range you cannot correct without
   starting again is a range you will get wrong — and they are absent on the
   mouse's own drag, where letting go opens the form and there is nothing left to
   correct.

   Sized for a fingertip and centred on the edge they move, with a white ring so
   they read against a booking block as well as against an empty cell.
   `touch-action: none` is what stops the page scrolling out from under one
   while it is being pulled. */
.wg__grip {
  display: none;
  position: absolute;
  /* Above the booking blocks, unlike the rectangle they hang off: the fill may
     go behind a block, but a control that cannot be seen cannot be pulled. And
     above a blanked closed column too — a selection on the day beside one hangs
     half of its side grip over the boundary, and half a handle is worse than
     none. */
  z-index: 4;
  width: 1.5rem;
  height: 1.5rem;
  margin: -0.75rem 0 0 -0.75rem;
  border: 2px solid #fff;
  border-radius: 999px;
  background: var(--mc-accent);
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.4);
  pointer-events: auto;
  touch-action: none;
}
.wg__sel.is-standing .wg__grip { display: block; }
/* The two on the ends move the HOURS, so they sit halfway across the rectangle
   and on its top and bottom lines — pulled up and down, which is the direction
   the week's hours run in. */
.wg__grip--top { top: 0; left: 50%; }
.wg__grip--bottom { top: 100%; left: 50%; }
/* The two on the sides move the COURTS, so they sit halfway down the rectangle
   and on its left and right lines — pulled across, which is the direction a day
   column's courts are laid out in. A selection with a handle on all four sides
   says, without a word of instruction, that it can be made taller and it can be
   made wider; the pair that was here before said only the first of those, and a
   player wanting the second court had no reason to think there was one.
   Drawn only where the venue has more than one court — see buildWeekGrid. */
.wg__grip--left { top: 50%; left: 0; }
.wg__grip--right { top: 50%; left: 100%; }

/* While a range is actually being dragged out, the week is not a document to
   select words in — and the crosshair, which follows the same pointer, would
   only argue with the rectangle it is drawing. */
.wg-selecting {
  user-select: none;
  -webkit-user-select: none;
}
.wg-selecting .wg__rowband,
.wg-selecting .wg__colband { display: none; }

/* A heading is a column: the date, and — where the venue has more than one
   court — the strip of court numbers under it. A column rather than plain flow
   because the two parts have to be anchored at opposite ends of a box whose
   height is not its own: every heading in the row is stretched to the tallest
   of them, and the tallest is today's, whose date is a circle. */
.wg__head {
  position: sticky;
  top: 0;
  /* Above everything the grid body can put under it, the panel that blanks a
     closed column included (.wg__closed, z-index 3) — a heading a column can
     scroll over is a heading that stops being frozen. */
  z-index: 5;
  background: #fff;
  border-bottom: 1px solid var(--mc-line-strong);
  border-left: 1px solid var(--mc-line-strong);
  padding: 0.5rem 0.35rem;
  text-align: center;
  line-height: 1.2;
  display: flex;
  flex-direction: column;
}
/* Which is what this is for: the date takes all the slack, so whatever is under
   it starts from the FOOT of the heading rather than from wherever that day's
   own date happened to end. Without it, Sunday's numbers sit under a line of
   text and today's sit under a circle nine pixels lower, and a row that is
   meant to read as one band reads as seven. */
.wg__date { flex: 1 1 auto; }
.wg__head:first-child { border-left: 0; }
/* The corner, above the time column, is a heading of the same shape as the rest
   of the row and not a box with a word centred in it: the month on top, and the
   court strip's caption at the foot where the strips are. So it keeps the column
   and the stretch it inherits from .wg__head — the centring that used to be
   declared here is gone, and .wg__month below does that job for the month alone,
   which is what leaves the caption free to sit against the bottom edge. */
.wg__head--time {
  position: sticky;
  left: 0;
  z-index: 6;
  border-right: 1px solid var(--mc-line-strong);
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--mc-brand);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.5rem 0.2rem;
}
/* The month, and the corner's half of the mechanism the whole heading row is
   lined up by: it takes the slack exactly as .wg__date does in the seven
   headings beside it, so the caption under it starts from the FOOT of the corner
   rather than from wherever the month happened to end — the same line, by the
   same means, as the seven strips it is naming. The centring is here rather than
   on the cell because it is the MONTH's, not the corner's: one line, centred in
   whatever room the caption left it. On a one-court venue, where there is no
   caption at all, that room is the whole cell and the month sits dead centre in
   it the way it always did. */
.wg__month {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.wg__head .wg__dow {
  display: block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mc-muted);
}
.wg__head .wg__dom {
  display: block;
  font-size: 1.05rem;
  font-weight: 600;
}
/* The courts, named across the foot of every day heading: | #1 | #2 |.
   A day column is not one space — it is the venue's courts side by side, every
   cell in it is drawn on one court's own half, and this is what says which half
   is which. Equal columns, so a number sits centred over the cells it belongs
   to; the rule between them is the same hairline the cells below are divided by,
   and it lands on the same fraction of the column, so the caption and the week
   under it are one grid rather than two.
   Drawn only where there is more than one court — see buildWeekGrid.

   The negative margins are what make that true. The strip is pulled out to the
   heading's padding edges — sideways so its halves are the same halves the cells
   below are placed in, and downwards so it sits ON the heading's bottom edge,
   its rule reading as one line with the border under it rather than as a second
   line floating above it. Its own top rule is the only thing that separates it
   from the date, so the band is bounded the same way every other row of this
   grid is: hairlines, no fills, nothing of its own. */
.wg__head .wg__courts {
  display: flex;
  margin: 0.3rem -0.35rem -0.5rem;
  border-top: 1px solid var(--mc-line);
  font-size: 0.66rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--mc-muted);
}
.wg__head .wg__court {
  flex: 1 1 0;
  padding: 0.12rem 0;
  border-left: 1px solid var(--mc-line);
}
.wg__head .wg__court:first-child { border-left: 0; }

/* And the caption at the foot of the corner — "COURT", against the row of
   numbers it belongs to. It is written as the same two elements the strips are
   (see buildWeekGrid) so that everything above is already true of it: pulled to
   the foot by the same negative margin, ruled off by the same hairline, set in
   the same small muted type. Nothing here restates any of that, and nothing here
   can drift out of step with it.

   The side margins are the one thing it cannot take as it stands. That pull is
   out to the HEADING's padding edge and the corner is padded 0.2rem where a day
   is padded 0.35rem, so the distance is different while the intent is identical —
   without this the strip would hang a couple of pixels past the frozen column's
   own edge and over Sunday.

   A word rather than a numeral, so it is kept to one line and cut with an
   ellipsis rather than wrapped: in a 100px column "COURT" has room to spare,
   but the failure worth designing for is the one where it does not, and a
   caption that grew a second line would push the month up and take the corner
   out of the row it is meant to be flush with. */
.wg__head--time .wg__courts {
  margin-left: -0.2rem;
  margin-right: -0.2rem;
}
.wg__head--time .wg__court {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.wg__head.is-today { background: var(--mc-today); }
/* The head of the day under the pointer, tinted with the column below it. After
   is-today so it wins on today's column too — it is a hover, not a state. */
.wg__head.is-hovered { background: #eef4fe; }
.wg__head.is-today .wg__dom {
  color: #fff;
  background: var(--mc-brand);
  border-radius: 999px;
  width: 1.8rem;
  height: 1.8rem;
  line-height: 1.8rem;
  margin: 0 auto;
}

/* --- today, framed --------------------------------------------------------
   The circle under SUN and the cream behind the hours are both true, and
   neither of them survives the week being read at arm's length: cream over
   white is a shade rather than a boundary, and the circle is one mark at the
   top of a column that is a foot long. So today is also given an EDGE — a line
   over the heading, down both sides, and along the foot of the last hour — and
   that is what says, from across the room, where the day begins and ends.

   Round the OUTSIDE and nowhere else. The hairlines between today's own cells
   stay the hairlines every other day is ruled by, because what is being drawn
   is the boundary of the day, not a box round each of its hours: a column ruled
   in mahogany twelve times over would read as twelve things rather than as one.

   It takes two elements, and the reason is the sticky heading. A single box
   over the whole column would have its top edge scroll up out of the box while
   the heading it belongs to stayed frozen at the top of it — a frame with the
   lid slid off. So the heading carries its own three edges and travels with
   them, and the overlay below carries the sides and the foot of the hours;
   unscrolled they are one line, and scrolled they are a heading with a top and
   a column with sides, which is the honest picture of both. */
.wg__head.is-today {
  border-top: 2px solid var(--mc-brand);
  border-left: 2px solid var(--mc-brand);
  border-right: 2px solid var(--mc-brand);
}
/* The other three-quarters: a grid item placed in today's column and stretched
   down every hour of it, which is what keeps it right without a measurement
   anywhere. It is the column by construction — at whatever width the week has
   been given, through a phone's sideways scroll, and through every redraw,
   because it is part of the markup a redraw rebuilds (see buildWeekGrid).

   Empty and pointer-transparent: it lies over every cell in the column and not
   one of them may notice, or the day the week is most likely to be booked on
   would be the one day nothing on it could be pressed.

   z-index 1 is chosen twice over. It puts the frame above the cells, and —
   drawn after them in the DOM at the same level — above the booking blocks,
   which claim 1 themselves and would otherwise cover the sides wherever a
   reservation takes the whole width of the column. And it keeps the frame BELOW
   the frozen time column at 2 and the headings at 3, so a week scrolled sideways
   slides today under the time labels exactly the way it slides everything
   else. */
.wg__todayframe {
  position: relative;
  z-index: 1;
  pointer-events: none;
  border-left: 2px solid var(--mc-brand);
  border-right: 2px solid var(--mc-brand);
  border-bottom: 2px solid var(--mc-brand);
}

/* The time column also holds the rows open. A reserved block can swallow every
   day cell of a row, and on a busy week every column at once — the label left in
   column 1 is what keeps that row the height it would have been. */
.wg__time {
  position: sticky;
  left: 0;
  z-index: 2;
  min-height: var(--mc-slot-height);
  border-top: 1px solid var(--mc-line);
  /* The one line of the axis that is a day boundary rather than an hour rule:
     it is the left edge of Sunday, and it is drawn as one. */
  border-right: 1px solid var(--mc-line-strong);
  /* Barely any side padding, and that is the centring's doing rather than a
     taste for tight margins: a label ranged right only had to clear one edge,
     and a centred one has to fit BETWEEN two. "10pm - 11pm" is 88px of type in a
     100px column, so what used to be breathing room on the right is now most of
     the margin the widest hour of the day has left to sit in. */
  padding: 0.2rem 0.1rem 0;
  font-size: 0.74rem;
  color: var(--mc-muted);
  /* Centred rather than ranged against the day boundary. Set right, the times
     read as a caption hanging off Sunday's edge; centred, the column reads as an
     axis of its own — which is what it has to be before one of its hours can be
     picked out without that hour looking as though it has slipped. Top of the
     row, still: the label names the hour's START, and the row is the hour. */
  text-align: center;
  background: #fff;
}
/* --- the hour it is now ----------------------------------------------------
   The clock, marked on the axis: the label of the hour that is happening is
   written in the venue's own mahogany, on a wash of the same, with a stub of it
   against the outer edge. Mahogany because the week already has exactly one way
   of saying "this is the one that is now" — today's date, in a mahogany circle,
   at the top of a mahogany-framed column — and a second colour would be a
   second thing to learn.

   Not a filled pill, which is what the circle at the top of the column would
   suggest, and the reason is the width of the track it would have to sit in.
   The time column is 100px on every screen, which is as much as can be spared
   from a week that is already scrolling sideways on a phone — "10pm - 11pm"
   fills very nearly the whole of it at this size, and a plate round the type
   would need several pixels more on each side than the column has to give. It
   would overhang into Sunday, and only on the handful of hours whose label runs
   to two two-digit hours, which is the worst kind of fault: one that looks right
   all afternoon and breaks at ten, at eleven and at midnight. So the mark is
   made out of things that take up no room at all — a colour, a weight, the brand
   at a wash, and an inset rule down the outer edge — and every hour of the day
   wears it identically.

   Which is also what keeps the grid still. Nothing here changes the width of
   the column or the height of a row, so the mark can come and go on the hour
   (see markCurrentHour) without a single cell moving under the reader.

   The hover band passes over the top of it untouched: the band is drawn later
   in the DOM at the time column's own z-index, so a hovered row tints this
   label the same faint blue it tints the rest of the row — which is what says
   the band crosses the whole week rather than stopping at the axis. */
.wg__time.is-now {
  background: #f4ebe5;
  box-shadow: inset 3px 0 0 var(--mc-brand);
}
/* On the label rather than on the cell, and that is not fussiness: the player
   grid sets the weight of this column through an id (see below), which would
   beat any class put on the cell itself. The wrapper is a plain span until it
   is this hour — see buildWeekGrid, which wraps every label alike so that a
   row's markup never depends on the time of day. */
.wg__time.is-now .wg__hour {
  color: var(--mc-brand);
  font-weight: 700;
}
/* On the player grid the axis is the last thing left reading down the page: the
   free cells beside it are blank until pointed at, so the times down the edge
   are what a player scans to find the evening they want. Bold enough to be
   scanned, and only here — the admin grid draws its free time the same way now,
   but it is a week of blocks carrying names and hours, and an axis competing
   with those would be noise. */
#player-page .wg__time { font-weight: 600; }

.wg__cell {
  border-top: 1px solid var(--mc-line);
  border-left: 1px solid var(--mc-line);
  min-height: var(--mc-slot-height);
  padding: 3px;
  margin: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  justify-content: center;
  align-items: stretch;
  font: inherit;
  font-size: 0.76rem;
  text-align: center;
  background: var(--mc-open);
  color: var(--mc-muted);
  transition: background-color 0.12s ease, box-shadow 0.12s ease;
}
.wg__cell.is-today-col { background: var(--mc-today); }

/* A free cell on either grid is a real <button>, because it is really a
   control — and it must not look like one. The grid rules every cell with a
   border on its top and left only, letting the neighbours below and to the right
   draw the other two sides, so a button left to its own devices keeps the
   browser's heavy outset border on exactly the two edges the grid never touches,
   and every open cell reads as a boxed key on a keyboard.
   `appearance` strips the native widget, and the two zeroed sides put the cell
   back on the same hairlines as the divs around it. */
button.wg__cell {
  appearance: none;
  -webkit-appearance: none;
  border-right: 0;
  border-bottom: 0;
  border-radius: 0;
}

/* A free hour answers a press as well as a click now — held, it is the start of
   a range dragged down the column — so nothing about it may be text as far as
   the browser is concerned: no words swept up by a drag, no magnifier or callout
   under a long press, and no double-tap zoom to fight the double tap that opens
   the form. `manipulation` leaves ordinary scrolling exactly where it was, which
   is the one gesture the grid must never take away from a phone. */
button.wg__cell.is-open {
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
button.wg__cell.is-open:focus-visible {
  background: var(--mc-open-hover);
  box-shadow: inset 0 0 0 2px var(--mc-accent);
  color: var(--mc-accent);
  outline: none;
}
/* A free cell is blank until it is pointed at, and then it says the one thing it
   has to say: the time it starts. Blank is the point — the week reads as free
   space and taken space at a glance, with no wall of repeated times to look past,
   and the label arrives on the one cell being considered. It answers to
   :focus-visible as well as :hover so tabbing through the grid reads the same way
   as pointing at it, and neither has anything to do with whether the cell can be
   pressed: it is a button all along.

   Both grids draw free time this way. The admin's open cells are the same
   buttons carrying the same .is-open, and what is different about them — how many
   courts are left in the hour — is on the tooltip rather than printed into
   the week. */
button.wg__cell.is-open .wg__cta { display: none; font-weight: 600; }
button.wg__cell.is-open:focus-visible .wg__cta { display: block; }

/* And the hover half of it is only ever offered to something that can actually
   hover, which a phone cannot.

   A touch screen has no pointer resting anywhere, so it has nothing honest to
   put :hover on — and what it does instead is leave the state on whatever was
   last touched, until something else arrives to take it away. On this grid
   nothing does: every press the selection engine deals with ends in
   preventDefault(), which is what stops the browser inventing a second gesture
   out of the same finger, and it takes the phantom mouse move that would have
   moved the hover along with it. So the first cell a thumb rested on lit up and
   stayed lit — through the next long press, through the selection made three
   hours below it, through everything — and a week with two cells claiming to be
   the one under the finger is a week telling a lie about where the hand is.

   Wrapping the hover rules in a query for a device that hovers is the whole fix:
   a mouse still lights the cell under it, and a phone is simply never told about
   a state it has no way to leave. The keyboard's :focus-visible above is
   deliberately NOT in here — focus is a real place on any device, and it moves
   when it is asked to. */
@media (hover: hover) {
  button.wg__cell.is-open:hover {
    background: var(--mc-open-hover);
    box-shadow: inset 0 0 0 2px var(--mc-accent);
    color: var(--mc-accent);
    outline: none;
  }
  button.wg__cell.is-open:hover .wg__cta { display: block; }
}

/* A taken slot is grey and says nothing: what is booked is drawn as a block over
   it, and a bucket whose courts are gone with no block on it — time held by a
   booking nobody has verified yet — is grey for the same reason it is
   unclickable. The word is on the cell's title and aria-label, for the reader
   that cannot see the colour. */
.wg__cell.is-reserved {
  background: var(--mc-reserved);
  color: var(--mc-reserved-ink);
  font-weight: 600;
  border-left-color: var(--mc-reserved);
  cursor: not-allowed;
}

.wg__cell.is-past {
  background: #ededed;
  color: #cfcfcf;
  cursor: not-allowed;
}
.wg__cell.is-past.is-reserved {
  background: var(--mc-reserved-past);
  color: #9aa1aa;
  border-left-color: var(--mc-reserved-past);
}

/* --- a day the venue does not open on -------------------------------------- */

/* A closed day is not an hour-by-hour picture of nothing available: it is a day
   that does not happen, and the week says so by drawing it as one unbroken
   thing. The player grid draws no cells at all in that column (see
   player_calendar_controller#cell), and since every hairline on this grid is
   drawn BY a cell — its own top and left edge — leaving the cells out is the
   whole of what takes the rules out of the column. What is left is this: one
   panel down the length of the day with the word on it.

   The grey is the SAME grey a played hour is drawn in (.wg__cell.is-past), and
   deliberately the same: a player is reading this week for time they can take,
   and an hour that has gone and a day that never opens are one answer to that.
   Two greys for one meaning would be the week inventing a distinction it does
   not have. What the closed day does not take from that treatment is its
   hairlines — it is one flat field rather than a stack of hours — and its
   near-invisible dash ink, which is right for an em-dash and would lose a word.

   The word is set the way this grid sets every other small label on itself —
   the court strip under the dates, which is the same 0.66rem at the same weight
   in the same muted tone (see .wg__head .wg__courts). Two sizes for two labels
   of the same standing would be the week disagreeing with itself, and the
   weight is what carries the tone at about 4:1 on this grey: heavy enough to
   read at arm's length, quiet enough not to announce a day nobody can book.

   Opaque, and above the crosshair bands at z-index 2 rather than under them.
   The bands are one row-wide strip drawn across the FULL width of the week, so
   pointing at any hour of any day would otherwise wash a tinted line, with a
   blue rule top and bottom, straight through the column and hand back the very
   grid lines this is here to take away. Masking them is what keeps the day one
   flat field whatever the pointer is doing — hence the ladder this sits in the
   middle of, and the raised headings and grips above it.

   And it takes the pointer rather than letting it through, for one reason: the
   cursor. `not-allowed` is what a grey hour and a taken court already answer a
   mouse with, and a closed day owes the same answer — but a rule about the
   cursor is a rule about the element under it, so the panel has to be under it.
   Nothing else is bought by that and nothing is lost: the crosshair reads the
   pointer's own coordinates rather than what it is over (trackGridHover), a
   selection is only ever started from a `button.wg__cell` and there is not one
   in this column (trackGridSelection#cellAt), a finger that lands on no cell
   falls through to the page with the gesture untouched, and the bookings drawn
   on a closed day sit ABOVE this at z-index 4, so they answer a press as they
   would on any other day. */
.wg__closed {
  position: relative;
  z-index: 3;
  cursor: not-allowed;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #ededed;
  color: var(--mc-muted);
  font-size: 0.66rem;
  font-weight: 600;
}

/* Where one day ends and the next begins, said in the stronger grey.
   A day column is the venue's courts side by side and every court is a cell of
   its own, so the vertical hairlines in the body are two different lines wearing
   one colour: the boundary BETWEEN days, which the week is read by, and the rule
   between courts INSIDE a day, which is a detail of the column. Only the first
   kind is drawn up.
   Which a cell is, is in where it was placed: courtStyle gives every cell the
   left edge of its own court's band, so a cell at `margin-left:0%` — or one with
   no band at all, which is a past hour drawn across the whole day — is standing
   on the boundary, and every other is standing inside the day.
   Last of the cell rules, and specific enough to carry a taken slot too: a
   boundary that vanishes down a grey run of reserved hours is the week losing
   its shape exactly where it is busiest. The rule between COURTS keeps the
   reserved grey it had, so a taken hour still reads as one block. */
.wg .wg__cell[style*="margin-left:0%"],
.wg .wg__cell:not([style*="margin-left:"]) {
  border-left-color: var(--mc-line-strong);
}

/* --- a booking is one block on the grid ------------------------------------ */

/* A booking is a box as tall as the time it takes, laid over the hour cells
   it covers, on both grids. It sits above the cells (which come later in the
   DOM) but below the frozen time column and the hover bands at z-index 2, so the
   column still covers it when the box is scrolled sideways and the crosshair
   still tints across it.

   On the admin grid colour is the verification and nothing else: yellow is
   waiting to be checked, green has been. There is no tick on it — the whole
   block already says so. The player grid only ever draws the green ones, because
   an unchecked booking is not something to show a stranger as settled. */
.wgb {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 1px;
  min-width: 0;
  overflow: hidden;
  padding: 2px 3px;
  border: 1px solid;
  border-radius: 6px;
  font: inherit;
  font-size: 0.72rem;
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
}
.wgb.is-unverified {
  background: #fdf1c4;
  border-color: #e0c24e;
  color: #6b4e07;
}
.wgb.is-verified {
  background: #d9f2e3;
  border-color: #94d3b3;
  color: #145b36;
}
/* The viewer's own booking: the one green block that answers the pointer (it
   is a button where the others are painted on). The heavier edge is what lets
   it be spotted as theirs before it is ever hovered. */
.wgb.is-own { border-width: 2px; border-color: #2e9e68; }
.wgb:hover,
.wgb:focus-visible {
  box-shadow: inset 0 0 0 2px currentColor;
  outline: none;
}

/* A block with nothing behind it to open: a label rather than a control. It
   used to be every booking on the player grid; now that pressing one asks the
   server what it is, it is only the block that arrived without the id to ask
   with (see player_calendar_controller's block()). Taking it out of the pointer
   entirely is what says so — no hover, no cursor, and the crosshair and the
   cell underneath carry on as if the block were painted on them. */
.wgb.is-static {
  cursor: default;
  pointer-events: none;
}

/* Half a column is narrow, and a name cut to "John Sm…" is worth less than a
   name on two lines — so it wraps, and only what will not fit in the block at
   all is lost. The hours underneath stay on one line, always. */
.wgb__name {
  max-width: 100%;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The player's blocks carry a booking reference where the admin's carry a name.
   A reference is one token that is worth nothing half-read, so it is never
   wrapped: it fits or it is cut with an ellipsis, and the block is sized so it
   fits. */
.wgb__ref {
  max-width: 100%;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wgb__when {
  max-width: 100%;
  font-size: 0.66rem;
  opacity: 0.85;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- modal ---------------------------------------------------------------- */

.mc-modal {
  position: fixed;
  inset: 0;
  z-index: 1080;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.mc-modal[hidden] { display: none; }
.mc-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(16, 24, 40, 0.45);
}
/* The panel tracks Bootstrap's column widths — col-xs-12 col-sm-10 col-md-8
   col-lg-5 — at Bootstrap's own breakpoints, so it takes proportionally MORE of
   the screen the smaller that screen is: all of a phone, most of a tablet, two
   thirds from md, and a little over 40% on a laptop or bigger. The max-width is
   the one addition, so a 4K monitor does not get an absurdly wide form. */
.mc-modal__panel {
  position: relative;
  width: 100%;
  max-width: min(100%, 40rem);
  max-height: 92vh;
  overflow-y: auto;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 18px 45px rgba(16, 24, 40, 0.28);
  padding: 1.25rem;
}
@media (min-width: 576px) { .mc-modal__panel { width: 83.3333vw; } }
@media (min-width: 768px) { .mc-modal__panel { width: 66.6667vw; } }
@media (min-width: 992px) { .mc-modal__panel { width: 41.6667vw; } }
/* A panel carrying the GCash QR beside the form needs the extra third, so the
   form itself stays as wide as it is without one. Below lg the columns stack and
   the normal widths already fit. */
.mc-modal__panel.is-wide { max-width: min(100%, 60rem); }
@media (min-width: 992px) { .mc-modal__panel.is-wide { width: 66.6667vw; } }
/* The player's reservation modal is the widest thing the page opens: a form
   that is asking for a lot, and with a GCash QR beside it two columns of equal
   weight. So it takes two or three more of Bootstrap's spans than the curve
   above gives it, at every size with the room to spare — col-8 rather than
   col-5 on a laptop, col-10 rather than col-8 on a tablet, and col-10 rather
   than col-8 again once the QR is there. Below sm it was already the whole
   screen and there is nothing left to widen.

   Scoped to that one modal by the Stimulus target that opens it, so the
   confirmation panel shown afterwards — which has one short line to say — keeps
   the narrower curve rather than being dragged wide with it. */
#player-page [data-player-calendar-target="modal"] .mc-modal__panel {
  max-width: min(100%, 60rem);
}
@media (min-width: 768px) {
  #player-page [data-player-calendar-target="modal"] .mc-modal__panel { width: 83.3333vw; }
}
@media (min-width: 992px) {
  #player-page [data-player-calendar-target="modal"] .mc-modal__panel { width: 66.6667vw; }
}
#player-page [data-player-calendar-target="modal"] .mc-modal__panel.is-wide {
  max-width: min(100%, 76rem);
}
@media (min-width: 992px) {
  #player-page [data-player-calendar-target="modal"] .mc-modal__panel.is-wide { width: 83.3333vw; }
}
/* The admin's booking modal is two columns — the booking on the left, the GCash
   transaction on the right — so it keeps col-sm-10's share of the page at every
   size instead of narrowing as the screen grows, the way the player's
   single-column form does. Scoped to the admin page, and by id, so it wins over
   the widths above at every breakpoint without repeating them. */
#admin-calendar .mc-modal__panel { max-width: min(100%, 76rem); }
@media (min-width: 576px) { #admin-calendar .mc-modal__panel { width: 83.3333vw; } }
.mc-modal__panel h2 {
  font-size: 1.15rem;
  font-weight: 700;
  margin: 0 0 0.9rem;
}
/* MAKE A RESERVATION — the player's booking heading is the venue's own, so it
   takes the mahogany the "Mahogany Courts" title is written in, in capitals.
   Set here rather than in the string so the heading text stays one readable
   sentence in player_calendar_controller.js. */
#player-page .mc-modal__panel h2 {
  text-transform: uppercase;
  color: var(--mc-brand);
}
.mc-modal__actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: 1.1rem;
}
.mc-modal__actions .mc-spacer { margin-left: auto; }
/* Submit before the policies have been agreed to. It has to LOOK shut and still
   TAKE the click, because the click is how a player asks what is missing and the
   toast is the answer — so this is drawn by hand rather than with Bootstrap's
   own .disabled, which sets pointer-events: none and would swallow exactly the
   press worth answering. */
.mc-modal__actions .btn[aria-disabled="true"] {
  opacity: 0.65;
  cursor: not-allowed;
}
/* A button that has to be a form to be a DELETE (button_to). It is one button in
   the row and nothing else, so the form around it is given no size of its own. */
.mc-inline-form { margin: 0; }

/* --- agreeing to the court policies ---------------------------------------
   One sentence across the foot of the form rather than a field in the column
   above it, so it is centred as a sentence: the box rides in the flow of the
   text instead of floating off to the left of it, which is what keeps the whole
   thing in the middle on a phone, where it takes two lines. */
.mc-agree {
  margin-top: 1rem;
  text-align: center;
}
.mc-agree .form-check {
  display: block;
  padding-left: 0;
  margin-bottom: 0;
}
.mc-agree .form-check-input {
  float: none;
  margin: 0 0.4rem 0 0;
  vertical-align: -0.15em;
}
/* Inline, against Bootstrap's inline-block label: an inline-block cannot break
   across lines, so the sentence would drop whole to the line under the box
   instead of starting beside it. */
.mc-agree .form-check-label {
  display: inline;
  font-size: 0.88rem;
}
/* The link is a name, so it wraps as one rather than leaving "Court" at the end
   of a line and "Policies" at the start of the next. */
.mc-agree a { white-space: nowrap; }
/* "(REQUIRED)" — said in the venue's own voice, the way every other insistence
   on this page is said: shouted in the styling rather than in the sentence, so
   what the label READS as text stays the plain English a screen reader gets. */
.mc-agree__required {
  color: var(--mc-muted);
  font-weight: 700;
  text-transform: uppercase;
}
/* --- the boxed sections of a booking modal (Details, proof of payment) ----- */

.mc-box {
  margin-top: 1rem;
  padding: 0.85rem 0.9rem 0.95rem;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  background: #fbfcfd;
}
.mc-box legend {
  float: none;
  width: auto;
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--mc-muted);
  margin-bottom: 0.4rem;
}
/* The field labels inside a box are the bold ones: Ref. Number, Amount Paid,
   Screenshot. Labels outside (name, hours, courts) keep Bootstrap's weight. */
.mc-box .form-label {
  margin-bottom: 0.2rem;
  font-weight: 700;
}
.mc-required { color: #b42318; }

/* The booking, as one table: the two boxes that are typed on the top rows and
   the four values they add up to underneath, labels left and values right, so
   every row lines up on the same two columns. */
.mc-details {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: center;
  gap: 0.35rem 0.6rem;
  margin: 0;
  font-size: 0.88rem;
}
/* The label of a typed row is a <label>, and has to sit exactly where the plain
   ones do rather than carrying Bootstrap's own spacing. */
.mc-details dt .form-label {
  margin: 0;
  font-weight: inherit;
  color: inherit;
  font-size: inherit;
}
/* A number that never runs past two digits does not need a box the width of the
   panel; it lines up with the values under it instead. */
.mc-details .form-control {
  max-width: 6.5rem;
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
}
/* The rows that are typed WORDS rather than counted things — the name and the
   phone number — need the width the counts do not, so they take the column.
   Same rule for the +63 group, whose own box is inside it. */
.mc-details .mc-details__text { max-width: none; width: 100%; }
.mc-details .mc-details__text .form-control { max-width: none; }
/* A typed row and the rate that prices it: the box keeps its own narrow width
   and the rate sits beside it, where it reads as an annotation on the number
   being typed rather than as part of the question in the label. Wraps under the
   box on a narrow phone rather than squeezing it. */
.mc-details__field {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.15rem 0.5rem;
}
.mc-details__rate {
  font-weight: 400;
  color: var(--mc-muted);
  font-size: 0.82rem;
  white-space: nowrap;
}
/* Which courts, as one tick box per court on one line. Bootstrap's inline form
   check carries a right margin meant for a form laid out in rows; in a two-column
   details table it pushes the last box out of its cell, so the spacing is the
   row's own gap instead. The label sits with its box and never wraps away from
   it — "Court 1" split across two lines reads as two answers. */
.mc-courts { gap: 0.15rem 0.9rem; }
.mc-courts__choice { margin-right: 0; }
.mc-courts__choice .form-check-label { white-space: nowrap; }
/* The rules between the table's groups. Full width of the grid, and the same
   hairline every other border in the app is drawn in — a divider that is darker
   than the box it is inside reads as a cut rather than as a pause. */
.mc-details__rule {
  grid-column: 1 / -1;
  margin: 0.4rem 0;
  border: 0;
  border-top: 1px solid var(--mc-line);
  opacity: 1;   /* Bootstrap's reboot fades an <hr>; this one is already faint */
}
.mc-details dt {
  font-weight: 600;
  color: var(--mc-muted);
}
.mc-details dd {
  margin: 0;
  font-weight: 600;
  color: var(--mc-ink);
  white-space: nowrap;   /* "August 8, 2026" is one line, never two */
}
.mc-details .mc-amount { font-weight: 800; }
/* The footnote under the gateway box, explaining why the panel it is in cannot
   be typed into. Ruled off from the details above it, so it reads as a note
   about the box rather than as one more row of it. */
.mc-payment__note {
  margin: 0.7rem 0 0;
  padding-top: 0.6rem;
  border-top: 1px solid var(--mc-line);
}

/* A state, said as a tag rather than as a word in a sentence: Bootstrap's badge
   in shape and weight, but square, because everything else on these pages is
   drawn with corners and a pill would be the only thing on the card pretending
   to be a button. One base class and a modifier per state, so a second tag is a
   colour rather than a new shape. */
.mc-tag {
  display: inline-block;
  padding: 0.2em 0.55em;
  border: 1px solid transparent;
  border-radius: 3px;
  font-size: 0.78em;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: 0.05em;
}
/* Amber: the same colour the grids draw a booking nobody has checked the money
   for, which is exactly what this is. */
.mc-tag--unpaid { background: #fdf1c4; border-color: #e0c24e; color: #6b4e00; }
/* Green, the colour both grids draw a booking whose money is settled. */
.mc-tag--paid { background: #d9f2e3; border-color: #94d3b3; color: #14603c; }
/* Red: the only one of the three that is not on the way anywhere. */
.mc-tag--expired { background: #fbe3e1; border-color: #e39a94; color: #8a1c11; }

/* --- the booking reference (MC-10059) --------------------------------------
   The player's copy, on the panel that goes up when a booking is taken. It is
   there to be photographed, so it is the biggest thing on the panel, in the
   venue's mahogany, with the digits spaced far enough apart to be read back off
   a phone screen — and it sizes itself down on a narrow one rather than
   wrapping mid-reference. */
.mc-reference {
  text-align: center;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  background: #fbfcfd;
}
.mc-reference__label {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--mc-muted);
}
.mc-reference__id {
  margin: 0.2rem 0 0.35rem;
  font-size: clamp(1.9rem, 9vw, 2.75rem);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: 0.05em;
  color: var(--mc-brand);
  white-space: nowrap;
}
.mc-reference__note {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--mc-ink);
}

/* The admin's copy, at the top of the left-hand column of the edit modal: the
   string an admin scans for while holding a player's screenshot against it. Not
   a field — it is derived from the booking's id and there is nothing to type.
   Beside it sits Verified, the button itself rather than a badge reporting one:
   "MC-10059, verified" is one fact, decided in the same place it is read. The
   row flexes so the button sits cleanly whether the reference half is drawn
   (editing) or not (a booking being entered), and the locked hint can wrap
   under it in a narrow modal. */
.mc-refno {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 0.75rem;
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--mc-muted);
}
/* The button and the hint beside the label are a state and a sentence, not
   headings, so neither takes the label's capitals. */
.mc-refno .btn,
.mc-refno .mc-sub {
  text-transform: none;
  letter-spacing: normal;
}
.mc-refno .btn { font-weight: 700; }
.mc-refno__id {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--mc-line);
  border-radius: 6px;
  background: #fff;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--mc-brand);
}

/* The attached screenshot, as a thumbnail that opens full size in a new tab. */
/* The picture is the proof, so it is shown at a size the transfer can be read
   at rather than as a 92px chip — still a link to the full-size original in a
   new tab, with the filename, Remove and the box that replaces it underneath
   it, in that order. */
.mc-proof__shot { display: block; }
.mc-proof__shot[hidden] { display: none; }
.mc-proof__shot a { display: block; }
.mc-proof__shot img {
  display: block;
  max-width: 100%;
  max-height: 20rem;   /* its own shape, just never taller than the box */
  border: 1px solid var(--mc-line);
  border-radius: 8px;
  background: #fff;
}
.mc-proof__shot a:hover img { border-color: var(--mc-accent); }
.mc-proof__meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.4rem;
}
.mc-proof__meta .mc-spacer { margin-left: auto; }

/* Verified: one button in two states, in the box with the proof it judges. */
/* --- the venue's GCash QR, beside the player's reservation form ------------ */

/* The QR takes the full height of the form beside it rather than floating small
   at the top of it. Bootstrap already stretches the column to the row's height;
   these rules hand that height down — column to aside to the image. All of it
   here, so the markup carries nothing but the grid. The QR is the second of the
   row's two halves, which is what :last-child picks out. */
#player-page .mc-modal__panel .row > .col-lg-6:last-child {
  display: flex;
}
.mc-qr {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  text-align: center;
}
/* The form is what sets the height of the row; the QR follows it and must never
   extend it. A tall portrait QR would otherwise be the taller of the two columns
   and push the modal down past what the form alone needs, so once the two are
   side by side the aside is lifted out of the flow: the column then contributes
   no height of its own, Bootstrap stretches it to whatever the form comes to,
   and the image scales down inside that. Below lg the row stacks, the aside goes
   back into normal flow, and it is the only thing setting its own height. */
@media (min-width: 992px) {
  #player-page .mc-modal__panel .row > .col-lg-6:last-child {
    position: relative;
  }
  #player-page .mc-modal__panel .row > .col-lg-6:last-child > .mc-qr {
    position: absolute;
    inset: 0;
    padding: 0 calc(var(--bs-gutter-x) * 0.5);
  }
}
/* The image is the only thing in the column now, so it takes all of it — scaled
   to fit rather than stretched, so the QR keeps its shape. That is what caps it:
   the column is taller than it is wide, so the QR grows to the column's width
   and the leftover height is empty space above and below it. flex-basis stays
   auto, so when the row stacks below the lg breakpoint and there is no spare
   height to grow into, the image is simply its own size. */
.mc-qr__image {
  flex: auto;
  min-height: 0;
  width: 100%;
  object-fit: contain;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  background: #fff;
  padding: 0.4rem;
}

/* --- the venue's uploads on the settings page ------------------------------
   A preview of what is stored now, beside what it is for and a Remove button.
   The QR and the logo sit in a square box — contained, not cropped, so a wide
   wordmark still fits whole; the court policy is a portrait page, so it is
   sized by its height and keeps its own aspect ratio instead. */

.mc-upload__current {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
}
.mc-upload__current[hidden] { display: none; }
.mc-upload__current img {
  border: 1px solid var(--mc-line);
  border-radius: 8px;
  background: #fff;
  padding: 0.25rem;
}
.mc-upload__current a:hover img { border-color: var(--mc-accent); }
.mc-upload__square {
  width: 108px;
  height: 108px;
  object-fit: contain;
}
.mc-upload__page {
  height: 150px;
  width: auto;
  max-width: 130px;
  object-fit: contain;
}

/* --- how to reserve --------------------------------------------------------
   The poster that opens over the calendar on a first visit, and the ? on the
   corner of the calendar's card that brings it back. It covers the viewport, so
   nothing behind it can be clicked, and the page under it is blurred for as long
   as it is up. */

.mc-blurred {
  filter: blur(6px);
  pointer-events: none;
  user-select: none;
}

.mc-splash {
  position: fixed;
  inset: 0;
  z-index: 1090;   /* over the booking modal, which is 1080 */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  /* Every part of it is the way out — see splash_controller#close — so every
     part of it is drawn as something to click. */
  cursor: pointer;
}
/* It is a flex box, so the browser's own [hidden] rule has to be said again to
   win. Hiding rather than removing is what lets the ? put it back. */
.mc-splash[hidden] { display: none; }
.mc-splash__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(16, 24, 40, 0.55);
}
.mc-splash__panel {
  position: relative;
  width: 100%;
  max-width: min(100%, 34rem);
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 18px 45px rgba(16, 24, 40, 0.35);
  padding: 1.25rem;
}
/* The poster is portrait and taller than a phone: it scrolls inside the panel
   rather than being shrunk until the steps on it cannot be read. */
.mc-splash__scroll {
  overflow-y: auto;
  border: 1px solid var(--mc-line);
  border-radius: 8px;
  background: #fbfcfd;
  padding: 0.5rem;
}
.mc-splash__image {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  height: auto;
}

/* The ? that puts the poster back, in the well's top corner over the end of the
   legend. Pinned rather than laid out, in the same band of top padding the logo
   disc rides — the well is positioned for exactly this, and the week bar under
   it keeps its whole width whatever the viewport does, so nothing here can
   crowd the navigation or shove the legend along.

   Deliberately quiet: it is there for the one player in twenty who wants it,
   and the other nineteen should read it as a mark on the card rather than a
   control to be dealt with — so it is drawn in the legend's own muted grey on
   the card's own white, and only takes the brand when it is pointed at. */
.mc-well > .mc-help {
  position: absolute;
  top: 1rem;
  right: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--mc-line);
  border-radius: 50%;
  background: #fff;
  color: var(--mc-muted);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.mc-well > .mc-help:hover,
.mc-well > .mc-help:focus-visible {
  background: var(--mc-brand);
  border-color: var(--mc-brand);
  color: #fff;
}

/* --- sign in -------------------------------------------------------------- */

.mc-signin {
  max-width: 25rem;
  margin: 3.5rem auto;
  padding: 0 1rem;
}
.mc-signin__card {
  background: #fff;
  border: 1px solid var(--mc-line);
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}

/* --- paying through the gateway --------------------------------------------
   Everything the PayMongo flow put on screen that manual capture never needed:
   the note where the proof boxes used to be, the panel a player waits behind on
   the way out, and the pages they come back to. */

/* The note that replaces PROOF OF PAYMENT in the booking modal. It is prose, not
   fields, so it does not get the bold field labels the other boxes do. */
.mc-box--gateway .mc-gateway {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--mc-ink);
}

/* A panel with one sentence on it has no business being as wide as the form it
   replaced. */
/* Reschedule to: [ date ][ time ]. Two boxes on one line, wrapping under each
   other on a narrow phone rather than squeezing to nothing. The date takes the
   room it needs and the time takes what a "12:30 PM" needs. */
.mc-reschedule { margin-top: 1rem; }
.mc-reschedule__inputs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}
.mc-reschedule__inputs .form-control { flex: 1 1 11rem; min-width: 9rem; }
.mc-reschedule__inputs .form-select { flex: 0 1 9rem; }

/* A question asked OVER a panel that is already open, so it has to sit above it
   and dim it the way that panel dims the page. */
.mc-modal--over { z-index: 1090; }

.mc-modal__panel--slim { max-width: min(100%, 28rem) !important; width: auto !important; }

/* Something is happening elsewhere and this is the line that says so: a spinner
   and a sentence, on one row, vertically centred against each other. The same
   pair serves the "taking you to GCash" panel and the "confirming your payment"
   line on the page they come back to. */
.mc-redirect {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.75rem;
  font-size: 0.92rem;
  line-height: 1.4;
  color: var(--mc-ink);
}
/* It is a flex row, so the browser's own [hidden] rule has to be said again to
   win — the panels that put a wait in front of their content take it away
   again when the content arrives. */
.mc-redirect[hidden] { display: none; }
/* Standing in the receipt's action row where Generate QR stood a moment ago:
   no bottom margin, so the row keeps the shape the button gave it. */
.mc-modal__actions .mc-redirect { margin: 0; }
.mc-spinner {
  flex: 0 0 auto;
  width: 1.1rem;
  height: 1.1rem;
  border: 2px solid var(--mc-line);
  border-top-color: var(--mc-brand);
  border-radius: 50%;
  animation: mc-spin 0.8s linear infinite;
}
@keyframes mc-spin { to { transform: rotate(360deg); } }
/* The same spinner, sitting inside a button while it waits on the server: small
   enough not to change the button's height, and drawn in the button's own ink
   rather than the venue's mahogany, which would disappear against the blue. */
.mc-spinner--button {
  width: 0.85rem;
  height: 0.85rem;
  border-width: 2px;
  border-color: rgba(255, 255, 255, 0.45);
  border-top-color: #fff;
  display: inline-block;
  vertical-align: -0.12em;
  margin-right: 0.45rem;
}
/* Still turning, because it is the only thing saying the page is alive — just
   slowly enough not to be movement anyone has to look away from. */
@media (prefers-reduced-motion: reduce) {
  .mc-spinner { animation-duration: 3s; }
}

/* --- a week on its way -----------------------------------------------------
   Moving between weeks is one week giving way to the next, so the week already
   on screen stays where it is and goes dim under the same spinner everything
   else in the app waits behind. It used to be a sentence on the status line,
   which is the wrong place to say it: the line is under the bar and the reader
   is looking at the grid, and a page that answers "is anything happening?"
   somewhere other than where the change will appear is not answering it.

   The wrapper is the box the wait is measured against — see
   shared/_calendar_shell for why it is out here rather than in the scroller. */
.mc-weekload { position: relative; }

/* Out of reach as well as dimmed. A stale week is still a week full of
   buttons, and a booking pressed while the next week is landing would open a
   panel onto the week that is going. The fade is what keeps a load too quick
   to read from registering as a flinch. */
.mc-weekload > .wg-scroll { transition: opacity 0.15s ease; }
.mc-weekload.is-loading > .wg-scroll {
  opacity: 0.5;
  pointer-events: none;
}

/* Over the middle of whatever is behind — an empty grid on the first load,
   last week's on every one after — and never in the way of it: the scroller
   keeps the whole of the pointer, which matters on the frames either side of a
   wait rather than during one. Faded in and out with the dimming it belongs
   to, and taken out of the accessibility tree between times, so the live
   region inside speaks once per wait rather than sitting there mid-sentence. */
.mc-weekload__wait {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease, visibility 0.15s;
}
.mc-weekload.is-loading .mc-weekload__wait {
  opacity: 1;
  visibility: visible;
}

/* --- the QR Ph code, in the dialog Pay opens -------------------------------
   The whole payment, on the venue's own page: a code, the name and the amount it
   is drawn for, and the clock it is good for. Centred, because a QR is a picture
   to point a phone at and everything else in the panel is a caption to it.

   (Distinct from .mc-qr above, which is the venue's own GCash poster beside the
   reservation form — that one is an image the venue uploaded once, this one is
   minted per booking and dies in ten minutes.) */

.mc-qrph { text-align: center; }
/* Both of these are display-something in their own right, so the browser's own
   [hidden] rule has to be said again to win. Four states share the panel and
   only one may ever be on screen. */
.mc-qrph[hidden],
.mc-qrph__state[hidden] { display: none; }

/* Who is being paid and how much, under the code. A QR says nothing a person can
   read, so these are said in words — in the booking's own label/value table
   (.mc-details) rather than in a layout invented for this panel, muted labels
   and dark values like every other row on the page.

   Inline, so the pair sits centred under the code the way everything else in
   here is centred, and left-aligned within itself, so both values start on one
   column instead of drifting about under labels of different lengths.

   A size up on the table's own 0.88rem: in the Booked box these are two rows
   among six and read as a table, and here they are two rows on their own, read
   at arm's length on a phone. */
.mc-qrph__payee {
  display: inline-grid;
  grid-template-columns: max-content max-content;
  margin: 0.85rem 0 0;
  text-align: left;
  font-size: 1.05rem;
}
/* Capped rather than stretched. The code arrives as a data URI of whatever size
   PayMongo drew it at, and a QR blown up past its own resolution scans no better
   for being bigger — 15rem is comfortably wider than any phone camera needs. */
.mc-qrph__image {
  display: block;
  width: 100%;
  max-width: 15rem;
  margin: 0 auto;
  padding: 0.5rem;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  background: #fff;
}
/* The one sentence in the panel, and it sits over the code rather than under it:
   by the time the phone is up, nobody is reading. Its bottom margin is the gap
   to the code — in the expired panel, where it is the only thing there, it has
   nothing to be a gap to and does no harm. */
.mc-qrph__note {
  margin: 0 0 0.85rem;
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--mc-muted);
}
/* Saving the code: in the action row opposite Close, because on the phone this
   is meant for those are the two things left to do. The same primary blue as
   Generate QR, because it is that same errand carried on rather than a second
   one. Bootstrap gives .btn a display of its own, so [hidden] has to be said
   again to win — the same note as .mc-qrph above, for the same reason. */
.mc-qrph__download[hidden] { display: none; }
/* PAYMENT CONFIRMATION — the heading over the code in the calendar's booking
   panel, and the only heading that panel has. Everything about how it is drawn
   is already decided by the modal it lives in (capitals, the venue's mahogany,
   the panel's own heading size); all it needs of its own is to be centred over
   the code it introduces, which is centred too. */
.mc-booking-panel__title { text-align: center; }
/* The whole of the payment as one white card, in the panel the week calendar
   opens over itself: the code, the name the money goes to, and the total. A
   player is holding a phone up to this, and a slip with a payee and an amount
   on it is a thing everybody already knows how to read — where a picture, a
   table and a grey well around the pair of them was three things asking to be
   understood as one.

   The picture gives up its own frame inside here (it carries one of its own
   for the dialog on the booking page, where it IS the card), so this is one
   box rather than a box in a box.

   Sized by what is ON it rather than by the modal it happens to be in. Left to
   fill the panel it drew a slip nearly 470px wide around a 240px code, which is
   a lot of white paper either side of the only thing anybody is pointing a
   phone at — and a payment slip that is mostly margin reads as a section of the
   page rather than as one object. `fit-content` hands the width back to the
   picture and the two lines under it, and the auto margins put the whole thing
   back in the middle of the panel; the clock below stays where it was, outside
   the card and centred by .mc-qrph. `max-width: 100%` is the promise that on
   the narrowest phone the card gives way rather than pushing the panel sideways.

   Slightly more padding than before, because on a card this size the border is
   close enough to the code to be read as part of it. */
.mc-qrph__card {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  padding: 1rem 1.25rem 1.15rem;
  border: 1px solid var(--mc-line);
  border-radius: 12px;
  background: #fff;
}
/* And the code itself is asked for a size here instead of taking whatever the
   box gives it, which is what makes the box a size at all. 13.75rem is a little
   under the 15rem cap it wears in the dialog on the booking page — it is being
   read at arm's length inside a panel rather than being the whole of the
   screen — and still far more than any phone camera needs to decode it. */
.mc-qrph__card .mc-qrph__image {
  width: 13.75rem;
  max-width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}
/* The two lines under the code, and they are values with no labels: "Pay to"
   and "Amount" were answering questions nobody asks about a payment slip. A
   name under a QR code is who is being paid, a peso figure under that is what
   it costs, and with the labels gone both can be centred and bold under the
   picture where they are read at a glance. */
.mc-qrph__value {
  margin: 0.55rem 0 0;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--mc-ink);
}
/* The clock, and it is OUTSIDE the card: it is not one of the things being paid,
   it is how long the card is good for. Small, centred and muted, with the one
   thing in ink being the number — the only part of any of this that changes
   while somebody is looking at it — in tabular figures, so counting down from
   10:00 does not shuffle the words either side of it every second. */
.mc-qrph__line {
  margin: 0.6rem 0 0;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--mc-muted);
}
.mc-qrph__line strong {
  color: var(--mc-ink);
  font-variant-numeric: tabular-nums;
}
/* The same button under the clock instead of in an action row, which is where
   the calendar's own booking panel puts it (see bookings/_panel): a phone is
   the only device it ever appears on, and on a phone the panel is read straight
   down — the code, how long it is good for, then the way to get it into GCash.
   Full width because it is the only thing on that screen to press, and [hidden]
   said once more so the browser's rule still wins over the display below. */
.mc-qrph__download--under {
  display: block;
  width: 100%;
  margin-top: 0.6rem;
}
.mc-qrph__download--under[hidden] { display: none; }

.mc-qrph__clock {
  margin: 0.5rem 0 0;
  font-size: 0.86rem;
  color: var(--mc-muted);
}
/* Tabular figures, so a clock counting down from 10:00 does not shuffle the
   words either side of it every second. */
.mc-qrph__clock strong {
  color: var(--mc-ink);
  font-variant-numeric: tabular-nums;
}
/* The waiting line is the app's usual spinner-and-a-sentence, centred here
   because everything else in this panel is. */
.mc-qrph__state { justify-content: center; }

/* The two things there are to do about a transaction still waiting for its
   money (see the admin calendar's Scan to pay box): they are alternatives, so
   they stand side by side rather than one under the other, and they wrap rather
   than shrink when the panel is narrow. */
.mc-qrph__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 0.7rem 0 0;
}

/* The test-mode stand-in for a phone. Deliberately the quietest thing in the
   panel — it is scaffolding for the venue's own staff before they go live, and
   it never appears against a live key at all. */
.mc-qrph__test {
  margin: 0.4rem 0 0;
  font-size: 0.78rem;
  color: var(--mc-muted);
}
.mc-qrph__test[hidden] { display: none; }

/* The clock on a draft. Said where the decision to pay is made and on the page
   they come back to while it is still running — never on the receipt, where the
   deadline has already been met. Plain, and quiet: it is a fact about the
   booking, not a warning about it. */
.mc-hold-note {
  margin: 0.9rem 0 0;
  font-size: 0.86rem;
  line-height: 1.4;
  color: var(--mc-muted);
}
.mc-hold-note strong { color: var(--mc-ink); }

/* At the foot of the Booked box it is a footnote to the table above it, so it
   sits inside the box's own margins with a hairline between them rather than
   floating under it like a second paragraph. */
.mc-box .mc-hold-note {
  margin: 0.7rem 0 0;
  padding-top: 0.6rem;
  border-top: 1px solid var(--mc-line);
}

/* --- the pages PayMongo sends the player back to ---------------------------
   One card in the middle of an otherwise empty page. There is nothing else on
   these pages — no grid, no week bar — so the shell narrows to the card rather
   than leaving it stranded at the left of a 1180px column. */

.mc-shell--narrow { max-width: 34rem; }

.mc-panel {
  background: #fff;
  border: 1px solid var(--mc-line);
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}

/* The well the calendar sits in: week bar, legend and grid as one card, on both
 * the player's page and the admin's. The same face as .mc-panel with room-sized
 * padding — a week grid is wider furniture than a booking receipt. */
.mc-well {
  position: relative;
  background: #fff;
  border: 1px solid var(--mc-line);
  border-radius: 12px;
  padding: 4em 1.25rem 1.25rem;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}

/* The line under the grid, and the room it is given. The variable is declared on
   the well rather than on the line itself because the height has to be read by a
   SIBLING — the scroller above it — and a value set on the footer would be no
   use up there. 2rem is the line and the gap over it, which is what the grid
   goes shorter by. */
.mc-well.has-footer { --mc-grid-footer: 2rem; }

.mc-rates {
  margin: 0.75rem 0 0;
  font-size: 0.82rem;
  color: var(--mc-muted);
  text-align: center;
}

/* --- the court policies poster ---------------------------------------------
   The same well, with its tall top given back. That 4em band is room the
   calendar keeps clear for the logo disc riding its edge; there is no disc on
   this page, and a poster in a card with three tight sides and one gaping one
   reads as a mistake rather than as breathing room. */
#court-policies-page .mc-well { padding-top: 1.25rem; }

/* The poster is a wide sheet, wider than the card it is in on every screen, so
   it is only ever being shrunk — and nothing caps it, because the card is
   already as wide as the shell allows. On a phone it lands small and is read by
   pinching, the way the printed one on the wall is read by stepping closer. */
.mc-policies__image {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  height: auto;
}

/* The same voice the booking modal speaks in — mahogany, in capitals — because
   this is the same conversation, finished on a different page. */
#booking-page .mc-panel h2,
#booking-cancelled-page .mc-panel h2 {
  margin: 0 0 0.9rem;
  font-size: 1.15rem;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--mc-brand);
}
/* Except the thank-you. Every other heading here is the venue announcing a
   state — confirming, cancelled, not paid — and capitals suit that. This one is
   the venue speaking to a person who has just paid them, and shouting it back
   would undo the warmth it is there for. It keeps the mahogany and the weight;
   it just uses its indoor voice.

   It is also the only heading here that is centred, and for the same reason:
   it sits directly under the venue's own name, which is centred over the card,
   and the two of them read as one greeting rather than as a title and a
   left-aligned announcement under it. The reference below is centred too, so
   the top of the receipt is one column down the middle. */
#booking-page .mc-panel h2.mc-thanks {
  text-transform: none;
  text-align: center;
  font-size: 1.3rem;
  letter-spacing: -0.01em;
}

/* The unpaid page's heading is centred for the same reason — under the venue
   name, down the same column — but it stays in the app's capitals, because
   "what happens next" is an announcement rather than an answer. */
#booking-page .mc-panel h2.mc-centred { text-align: center; }
#booking-page .mc-panel .mc-sub,
#booking-cancelled-page .mc-panel .mc-sub {
  color: var(--mc-muted);
  font-size: 0.92rem;
  line-height: 1.5;
}


/* The gateway's own transaction id: a long opaque string, and the one thing on
   this page that is read character by character. Monospace, and allowed to wrap
   rather than be cut — half a transaction id is worth nothing. */
.mc-details dd.mc-txn {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
  font-weight: 500;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* "Save or screenshot this page." The only instruction on the page, so it is
   the only thing on it that looks like one. */
.mc-keepsake {
  margin: 0.9rem 0 0;
  padding: 0.6rem 0.75rem;
  border: 1px dashed var(--mc-brand);
  border-radius: 8px;
  background: #fdf8f4;
  color: var(--mc-brand);
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.4;
}

/* --- admin: the payment state of a booking ---------------------------------
   Read-only reporting in the left column of the edit modal. */

.mc-badge {
  display: inline-block;
  padding: 0.12rem 0.55rem;
  border: 1px solid;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* The same three colours the grid is read by, so a badge and a block never
   disagree about what a booking is: green settled, yellow still moving, grey
   came to nothing. Blue is the fourth thing — a booking no gateway ever saw. */
/* Verified and unverified share those two exactly, because they ARE those two:
   the badge beside the reference and the block on the grid are reporting the
   same state, and a badge that has drifted from the block it explains is worse
   than no badge. */
.mc-badge--paid,
.mc-badge--verified { background: #d9f2e3; border-color: #94d3b3; color: #145b36; }
.mc-badge--pending,
.mc-badge--unverified { background: #fdf1c4; border-color: #e0c24e; color: #6b4e07; }
.mc-badge--dead { background: var(--mc-past); border-color: #ccd1d7; color: var(--mc-muted); }
.mc-badge--manual { background: #eef4fe; border-color: #b6cdf5; color: #1d4ed8; }

.mc-payment__state {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.6rem;
  margin: 0 0 0.6rem;
}
.mc-payment__state .mc-sub { flex: 1 1 12rem; min-width: 0; font-size: 0.82rem; }

/* The one line in the box that is asking for something to be done, so it is the
   one line that is coloured like a warning rather than like a label. */
.mc-payment__attention {
  margin: 0 0 0.6rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid #f0c2bd;
  border-radius: 8px;
  background: #fdf3f2;
  color: #8a2018;
  font-size: 0.84rem;
  font-weight: 600;
  line-height: 1.4;
}
.mc-payment__attention[hidden] { display: none; }

/* The gateway ids. Opaque strings nobody reads unless they are looking one up,
   so they are small, monospace and allowed to wrap — a truncated id is worse
   than a long one, since the only thing anyone does with it is copy it. */
.mc-payment__refs {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--mc-line);
  font-size: 0.78rem;
}
.mc-payment__refs[hidden] { display: none; }
.mc-payment__refs dd {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-weight: 500;
  font-size: 0.74rem;
  white-space: normal;
  overflow-wrap: anywhere;
}
.mc-details dt[hidden], .mc-details dd[hidden] { display: none; }

/* --- a court taken without a payment ---------------------------------------
   The desk's override, in the same well the rest of the panel is built from:
   .mc-box draws the border and the legend, and what is added here is only what
   the two extra shapes need — somewhere to type the reason, and somebody's own
   words shown afterwards as a quotation. */
/* Tall enough for a sentence or two without being dragged, and draggable when
   the reason runs longer than that. */
.mc-override__input { min-height: 6rem; resize: vertical; }
.mc-override__label {
  margin: 0.7rem 0 0.3rem;
  font-size: 0.88rem;
  font-weight: 700;
}
/* Typed by a person about one booking, so it is set as the quotation it is —
   and `pre-wrap` because the line breaks somebody typed are part of what they
   wrote. */
.mc-override__quote {
  margin: 0;
  padding-left: 0.6rem;
  border-left: 3px solid var(--mc-line);
  font-size: 0.88rem;
  font-style: italic;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- a court somebody is at the till for -----------------------------------
   A draft is drawn as one block over the hours it holds, the same as any other
   booking and the same as the admin grid draws it: yellow because it is not
   settled, hatched and dashed (.wgb.is-hold) because it is the one kind of
   booking that can still evaporate on its own. A three-hour hold is one block
   saying one thing, rather than three hours each saying it separately. */
.mc-legend .sw-pending {
  background: #fdf1c4 repeating-linear-gradient(-45deg,
    rgba(255, 255, 255, 0.8) 0 2px, rgba(255, 255, 255, 0) 2px 5px);
  border-color: #e0c24e;
  border-style: dashed;
}

/* The one yellow block that is a way back INTO something: the draft this
   browser is paying for, which opens the code to scan (.is-own, the same word
   the green block wears for the same reason). Solid where every other hold is
   dashed: this one is not a court that might evaporate, it is a court the
   reader can still keep. Every other hold on the week — a stranger's, and every
   one of them under an admin's eye — is a court that may still go, and is drawn
   dashed and hatched to say so. */
button.wgb.is-hold.is-own {
  border-style: solid;
  border-color: #d8a51f;
}
button.wgb.is-hold.is-own:hover,
button.wgb.is-hold.is-own:focus-visible {
  background-color: #fbe8a6;
  box-shadow: inset 0 0 0 2px #b7860b;
  outline: none;
}

/* The same offer, made above the grid, for the player whose draft is not on the
   week they happen to be looking at. Yellow for the same reason, and laid out
   like the week bar it sits under. */
.mc-resume {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.9rem;
  margin-bottom: 0.75rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid #e0c24e;
  border-radius: 10px;
  background: #fdf1c4;
  color: #6b4e07;
  font-size: 0.9rem;
}
.mc-resume[hidden] { display: none; }
.mc-resume .mc-spacer { margin-left: auto; }
.mc-resume__when { color: #7d5f10; }
.mc-resume--panel { margin: 0 0 0.25rem; }

/* The payment sits in the action row at the foot of the booking, the same size
   and shape as every other primary button in the app — it is the obvious thing
   to press because it is the only thing to press, which needs no help from a
   larger button, and needs no styles of its own at all. */

/* A held court, on the admin grid. It is a booking and it is drawn as one — it
   takes its courts exactly like the rest — but it is the only kind that can
   still evaporate on its own, so it is hatched and its border is dashed. The
   colour underneath stays the verification, the way every other block's does. */
.wgb.is-hold {
  border-style: dashed;
  background-image: repeating-linear-gradient(-45deg,
    rgba(255, 255, 255, 0.65) 0 4px, rgba(255, 255, 255, 0) 4px 9px);
}
.mc-legend .sw-hold {
  background: #fdf1c4 repeating-linear-gradient(-45deg,
    rgba(255, 255, 255, 0.8) 0 2px, rgba(255, 255, 255, 0) 2px 5px);
  border-color: #e0c24e;
  border-style: dashed;
}

/* --- misc ----------------------------------------------------------------- */

.mc-empty {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--mc-muted);
}

/* The time column used to be narrowed here, back when a row said one time and a
   phone could be given "12:00pm" in 64px. A row now says the whole hour, and
   there is nothing left to take: the label is the same string at the same size
   whatever the screen is, so a narrower track would only clip it. The week
   scrolls sideways on a phone either way. */
@media (max-width: 640px) {
  .mc-header h1 { font-size: 1.3rem; }
}

/* --- on paper --------------------------------------------------------------
   Print is offered on one page in this app — a settled booking, where the panel
   IS the player's receipt (see bookings/show and print_controller). So these
   rules have one job: put that panel on the sheet and take everything that is
   screen off it.

   Nothing is rebuilt for print. What comes out is the page as it stands, minus
   the things that only make sense to a pointer — the buttons, the dialogs
   behind them, the toast stack — and minus its own on-screen instruction to
   screenshot it, which is not advice anybody needs while holding the paper.
   What is left is the venue's letterhead, the MC- reference, the booking, the
   amount, and the PAID row with the date it was paid on: everything the desk
   would ever be shown this sheet for.

   The panel goes flat — no shadow, no card, no page background — because paper
   is already the card, and a printer asked for a grey field will happily use
   half a cartridge agreeing with it. The colours that MEAN something (the
   mahogany letterhead, the green PAID tag) are kept with print-color-adjust, so
   a receipt printed in colour still reads the way the screen did, while a
   black-and-white printer simply renders them grey. */
@media print {
  @page { margin: 1.4cm; }

  body {
    background: #fff;
    color: #000;
  }

  /* Everything that is chrome rather than receipt. `!important` because most of
     these carry a display of their own (flex, or the modal's own layout) that
     would otherwise win. */
  .mc-toasts,
  .mc-modal,
  .mc-modal__actions,
  .mc-keepsake,
  .mc-navbar,
  .mc-weekbar,
  .mc-resume { display: none !important; }

  .mc-shell,
  .mc-shell--narrow {
    max-width: none;
    margin: 0;
    padding: 0;
  }

  /* The card was a card because the screen around it was grey. */
  .mc-panel {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
  }

  /* The venue's mark, at a size that leaves room for the booking under it on
     one sheet. */
  .mc-letterhead { max-height: 140px; }

  /* The three boxed things on the receipt keep their outlines — they are what
     separates the reference from the booking on a page with no colour left —
     but lose the tints that would be printed as flat grey blocks. */
  .mc-reference,
  .mc-box {
    background: none;
    break-inside: avoid;
  }

  /* Colour that carries meaning survives a colour printer; the rest of the
     sheet is ink on white either way. */
  .mc-tag,
  .mc-letterhead,
  .mc-reference__id {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}
