/* 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: 40px;      /* one bookable hour of the venue's day */
  /* The tab that stands on the top line of the table, over today's column. It
     is written down here because it is a grid measurement like the two below
     rather than a property of any one box: the band the tab is clipped in, the
     tab inside it and the drop that lands its foot on the line are three rules
     that have to agree about one height (see .wg-tabstrip). */
  --mc-todaytab-height: 1.1rem;
  /* 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: 128px;
  /* The circles that ride the two ends of the week (see .wg-chev). Written down
     out here with the other grid measurements because that is what it is: each
     circle is centred on one of the grid's own vertical lines, and where it goes
     is that line's position less half of this. A number that has to be halved in
     two rules to place one control belongs in one place. */
  --wg-chev-size: 2.5rem;
}

/* 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 128px 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: 164px; }
}

/* --- 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; }
}

/* The question a booking dropped somewhere new is left standing in — "Move Jun
   Reyes to Saturday, 6 Jun 2026, 7:00pm?" with Yes and No under it. It wears
   the toast card's own treatment (the border, the corner, the shadow and the
   colour bar down the left) because it arrives in the same place for the same
   reader, and two message cards at the top of one screen that looked different
   would read as two different kinds of thing.

   Directly UNDER the toast stack, and that is the one number here worth
   defending: a move that is refused answers in a toast, and the refusal has to
   land over the question it is about rather than behind it.

   Wider than a toast, since it carries a name, a date, an hour and two buttons
   on one line. No backdrop anywhere — the week under it stays live on purpose,
   because taking hold of the block again is how a drop is corrected. */
.mc-moveask {
  position: fixed;
  top: 1rem;
  left: 0;
  right: 0;
  margin-inline: auto;
  z-index: 9998;
  width: min(28rem, calc(100vw - 2rem));
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem 0.75rem 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-accent), 0 10px 28px rgba(16, 24, 40, 0.18);
}
.mc-moveask[hidden] { display: none; }
.mc-moveask__text { flex: 1 1 auto; min-width: 0; margin: 0; }
.mc-moveask__actions { flex: 0 0 auto; display: flex; gap: 0.4rem; }
@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 disc — a 104px white badge riding the top edge of the
   calendar's card, and the door to the venue's Facebook page — was drawn here.
   It is gone: the mark lives on the navbar now (.mc-navlogo below), where a
   customer and a member of staff find it in the same corner of the same bar,
   and the band of padding the well kept clear for it went with it (see
   #player-page .mc-well). */

.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 {
  /* Positioned for the burger's dropdown, which hangs off the bar itself rather
     than off the button — the panel wants the bar's own right-hand edge, and the
     button is only the thing that opened it. */
  position: relative;
  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);
}
/* The email a menu that can sign you out has to say it would be signing out.
   Two menus wear it — the desk's account dropdown and the phone's burger panel,
   which IS the account menu at that width — and they wear it identically,
   because they are the same card hanging off the same bar and any difference
   between them would read as a mistake. The phone's one takes a ceiling as
   well: the panel is shrink-to-fit, so a long address left to itself would drag
   it most of the way across the screen instead of wrapping. */
.mc-usermenu__who,
.mc-navbar__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-navbar__who { max-width: 14rem; }
.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;
}

/* The page links, and the burger they go behind on a phone. Both bars are built
   this way — the customer's three and the desk's two-to-four — because there is
   one bar with two sets of links in it, not two bars.

   ONE SET OF LINKS AT EVERY WIDTH. `display: contents` is what buys that: the
   box is not drawn at all on a desk, so the links inside it are laid out as
   flex children of the bar exactly as they were before there was a box round
   them, and the phone rules below turn the same box into the panel. The
   alternative — a row for wide screens and a menu for narrow ones — is two
   copies of the same list waiting to disagree about which page the reader is
   on. */
.mc-navbar__links { display: contents; }

/* A section of the phone's menu: the super-admin's three, and the sign-out at
   the bottom. `display: contents` again, so a group is nothing at all on a desk
   and the links inside it stay flex children of the bar — the group exists to
   carry the rule drawn over it on a phone, and a rule is not a thing a desk's
   bar has. */
.mc-navbar__group { display: contents; }

/* And the parts of that menu the desk has its own copy of: the email, Settings,
   the sign-out form. They are in the links box because a phone gets ONE menu;
   they are not drawn at all above 640px because up there the account dropdown
   beside the bar is carrying every one of them. Last of these three rules on
   purpose — it is what takes the display away from the group above. */
.mc-navbar__phone { display: none; }

.mc-navbar__burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  background: none;
  color: rgba(255, 255, 255, 0.85);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}
.mc-navbar__burger:hover { color: #fff; }

@media (max-width: 640px) {
  .mc-navbar__burger { display: inline-flex; }
  .mc-navbar__links { display: none; }

  /* ONE MENU ON A PHONE. The account dropdown goes, and what it carried is
     carried by the panel below instead (see admin/_nav, which renders the email,
     Settings and Sign out into the links box a second time for this width).
     Two menus a thumb's width apart on a bar with room for neither is one menu
     too many, and the one that went is the one whose target was the width of an
     email address. Nothing on the customer's bar is touched by this: that bar
     has no account menu to take away. */
  .mc-usermenu { display: none; }

  /* The same white card the account menu hung, off the same edge of the same
     bar, because on a phone this panel IS that menu now. The links inside are
     white on the bar and have to stop being white in here — the panel is white
     too. */
  .mc-navbar.is-open .mc-navbar__links {
    position: absolute;
    right: 1rem;
    top: calc(100% + 0.45rem);
    z-index: 1100;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
    min-width: 10rem;
    padding: 0.4rem 0.75rem 0.5rem;
    border: 1px solid var(--mc-line);
    border-radius: 10px;
    background: #fff;
    box-shadow: 0 10px 28px rgba(16, 24, 40, 0.18);
  }
  .mc-navbar.is-open .mc-navbar__links a {
    padding: 0.3rem 0;
    color: var(--mc-ink);
    font-size: 0.95rem;
    text-decoration: none;
  }
  .mc-navbar.is-open .mc-navbar__links a:hover {
    color: var(--mc-brand);
    text-decoration: underline;
  }
  .mc-navbar.is-open .mc-navbar__links a.is-current {
    color: var(--mc-brand);
    font-weight: 600;
  }

  /* The parts of the panel that only exist down here. `block` rather than a
     display of their own: the email is a line and Settings is a link, and the
     one of these that is a section gets its display back from the rule under
     this one, which is why that rule comes second. */
  .mc-navbar.is-open .mc-navbar__links .mc-navbar__phone { display: block; }

  /* A section, and the thin rule over it that makes it one. The rule is a
     border on the section rather than a separator element between sections, so
     that a section which is not rendered — the super-admin's, to everybody
     else — takes its own divider away with it and cannot leave a line with
     nothing under it. The email at the top draws the first line from below in
     the same way (.mc-navbar__who), for the same reason. */
  .mc-navbar.is-open .mc-navbar__links .mc-navbar__group {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
    width: 100%;
    margin-top: 0.3rem;
    padding-top: 0.35rem;
    border-top: 1px solid var(--mc-line);
  }

  /* Sign out is a form and a button rather than a link, so none of the panel's
     link rules reach it, and the bar's own white-on-black rule does. It is
     drawn to match the links either side of the rule above it — the same size,
     the same ink, the same hover — because in this menu it is one of them. */
  .mc-navbar.is-open .mc-navbar__links form { display: block; }
  .mc-navbar.is-open .mc-navbar__links .mc-signout {
    padding: 0.3rem 0;
    color: var(--mc-ink);
    font-size: 0.95rem;
  }
  .mc-navbar.is-open .mc-navbar__links .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 and the week bar — and what is left is the grid's.
     It is deliberately not trimmed now that the status line under the bar has
     gone to the toast stack: the budget erring high costs the grid a row and
     the alternative is a page that scrolls. 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 bars the week is moved by ----------------------------------------
   Scoped to this box and to nothing else. The page's own scrollbar and every
   other scroller in the app are left exactly as the platform draws them: a bar
   restyled everywhere is a bar the reader has to learn, and the only one worth
   the lesson is the one standing inside the card all day.

   A pill of mahogany floating in a gutter wider than itself. The 3px of
   transparent border with `background-clip: padding-box` is the whole of the
   floating: the thumb is still the full ten pixels the gutter is wide — still
   ten pixels to the pointer, which is what it has to be, since a bar that is
   slim to GRAB is a bar that gets grabbed twice — and only the four in the
   middle of it are painted.

   The colour is --mc-brand (#7b3f21) at 35%, written out in channels rather
   than referred to, because a custom property holding a hex cannot be given an
   alpha without being torn into its three numbers first, and three more
   variables to say one colour once is not a trade this file makes. Translucent
   rather than a lighter brown so it reads over what it crosses: the grid is
   white nearly everywhere, but today's column is cream and a booking block is
   green or yellow, and the bar passes over all of them. */
.wg-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.wg-scroll::-webkit-scrollbar-track { background: transparent; }
.wg-scroll::-webkit-scrollbar-thumb {
  background-color: rgba(123, 63, 33, 0.35);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: padding-box;
}
/* `background-color` rather than the shorthand, here and above: `background:`
   would reset the clip to the border box along with the colour, and the pill
   would fill the gutter the moment the pointer touched it. */
.wg-scroll::-webkit-scrollbar-thumb:hover { background-color: rgba(123, 63, 33, 0.55); }
.wg-scroll::-webkit-scrollbar-corner { background: transparent; }

/* And where the two bars BEGIN, which is the point of all this.
   The day headings scroll in this box along with the hours — they are sticky
   against it, which is what freezes them, so they cannot be lifted out of it —
   and the frozen time column is the grid's first track inside it. So a bar
   drawn the whole length of its side runs past both: a vertical thumb sliding
   up alongside SUN MON TUE, which do not move, and a horizontal one running
   under the hour labels, which do not move either. Both are then reporting a
   scroll position for something that is not scrolling.

   Insetting the TRACK is what fixes that, rather than insetting the thumb:
   margin on the track shortens the runway, and the thumb is laid out inside
   whatever the runway is left as — so the bar not only starts below the
   headings, it reaches its far end when the LAST HOUR does.

   --wg-head-h is written onto this element by JavaScript after every rebuild,
   because the height of a heading is the height of the type in it and this app
   cannot know what that is (see sizeScrollbarInset in calendar.js). The 84px is
   what it is worth at the default size with a court strip under the date, and
   it is only ever seen in the frames before the first measurement lands.
   --mc-time-col needs no such help: the column is that wide by declaration, on
   a phone exactly as on a desk (see :root). */
.wg-scroll::-webkit-scrollbar-track:vertical { margin-top: var(--wg-head-h, 84px); }
.wg-scroll::-webkit-scrollbar-track:horizontal { margin-left: var(--mc-time-col); }

/* Firefox, which has the two standard properties and none of the pseudo
   elements. It gets the colour and a thin bar and it does not get the inset:
   there is no way to tell `scrollbar-color` where to start, so a Firefox reader
   sees a brown bar running the full height of the box, past the headings. That
   is a knowingly worse version of the same bar rather than a broken one, and
   the alternative — a scroller with its overflow moved somewhere the standard
   properties could be aimed at — costs the sticky headings, the scroll sync and
   three JavaScript files' worth of `closest(".wg-scroll")`. Not for two pixels
   of margin.

   The @supports is NOT a nicety. Chromium understands both spellings, and when
   a box is given both it takes the standard one and throws every ::-webkit rule
   above away — so left unguarded, this short block would silently be the whole
   design everywhere, thin grey-brown bar and no inset at all. `selector()` is
   false in Chromium (it HAS ::-webkit-scrollbar) and true in Firefox (it has
   not); an engine too old to parse `selector()` at all fails the query and
   keeps the pseudo elements, which is the right answer for old Safari. */
@supports not selector(::-webkit-scrollbar) {
  .wg-scroll {
    scrollbar-width: thin;
    scrollbar-color: rgba(123, 63, 33, 0.45) transparent;
  }
}

/* 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. They sit
   under the frozen time column (z-index 4, raised above the closed-day panel
   so a shut day cannot scroll over the axis), so the row band's tint stops at
   the axis instead of washing the hour label. The day headers are higher
   still; 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 40px 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 40px 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 40px of a
   one-hour selection, which at this size they do with about five 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 tab over the top edge of a selection a finger has left standing: "DOUBLE
   TAP". A phone has no hover and no status bar, so the one thing the grid never
   said out loud is the thing that finishes a booking — a tap picks the hour and
   a second tap on the same rectangle opens the form, and a customer meeting a
   blue box for the first time has no reason to try the second one. It borrows
   the TODAY tab's vocabulary — small white caps, wide tracking, a rounded tab
   over the thing it names — in the accent rather than the brand, because it is
   about the blue rectangle underneath it and not about the week.

   Drawn only where the page asked for the words AND only while the selection is
   standing: the first is what keeps it off the admin grid (the desk's paint()
   writes nothing into the span, and an empty tab is not drawn), and the second
   is what keeps it off a rectangle still being dragged out, where an instruction
   about letting go would be answering a question nobody has finished asking.
   The `:not(:empty)` is load-bearing rather than belt-and-braces: the standing
   rule is the more specific of the two and would otherwise put an empty blue
   blob over every selection on the desk's own grid.

   It clears the top grip rather than sitting on it — 0.9rem is the 12px the
   grip stands above the edge plus a couple to see daylight through — and takes
   no pointer of its own, so the double tap it is describing lands on the cell
   underneath instead of on the words about it. z-index 4 is the grips': above a
   booking block, and below the sticky day headings, which is what makes a
   selection on the topmost row visible slide under them the way the rectangle
   itself does rather than float over a heading it is no longer near. */
.wg__seltip {
  display: none;
  position: absolute;
  bottom: calc(100% + 0.9rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 4;
  padding: 0.1rem 0.4rem;
  border-radius: 6px;
  background: var(--mc-accent);
  color: #fff;
  font-size: 0.5625rem;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* The words break where the page put the break and nowhere else: `pre-line`
     honours the newline in the hint and still collapses everything a line of
     source code might otherwise contribute, and what it does NOT do is let a
     narrow column choose its own breaks — the two lines are a sentence in two
     halves ("Double tap" / "to book"), and a column that broke them anywhere
     else would be reading the reader something they did not write. Both halves
     are short enough to stand in a half-column at this size. */
  white-space: pre-line;
  /* And the box is as wide as its longest line rather than as wide as whatever
     is left of a half-column, which is what an absolutely positioned box would
     otherwise shrink to — 35px, and "Double tap" broken after "Double". */
  width: max-content;
  text-align: center;
  pointer-events: none;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.3);
}
.wg__sel.is-standing .wg__seltip:not(:empty) { display: block; }
/* Under the rectangle on the day's first hour, where there is no grid above to
   stand on — see paint(). The same clearance the other way round, so it misses
   the bottom grip exactly as it misses the top one. */
.wg__sel.is-tip-below .wg__seltip { top: calc(100% + 0.9rem); bottom: auto; }

/* 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; }

/* And the same two things said again while a BOOKING is being carried across
   the week rather than a range drawn on it. Copied from the rule above rather
   than folded into it because they are two gestures that happen to want the
   same two things: the crosshair follows the pointer either one is using and
   would only argue with what that pointer is drawing, and a week being dragged
   on is not a document to sweep words out of. */
.wg-moving {
  user-select: none;
  -webkit-user-select: none;
}
.wg-moving .wg__rowband,
.wg-moving .wg__colband { display: none; }

/* The week while one booking is in the hand. A drag that has been armed by a
   press held on a block has to be UNMISTAKABLE — it is a mode, it was entered
   by holding still, and the desk that did not mean to enter it has to be able
   to see that it has. So the week steps back and the booking steps forward:
   every other block goes half-strength, and the hours this one cannot reach go
   with them, leaving the places it CAN go standing at full strength beside the
   block that is ringed in the accent (.wgb.is-moving) and the ghost that is
   standing on one of them.

   Which cells those are is not something CSS can work out — it is the same list
   the magnet snaps to, courts and all — so the controller marks them (see
   markDropCells) and this only says what a mark means. Nothing here costs
   anything when the class is off, and the class and the marks come off
   together in endMove. */
.wg-moving .wgb { opacity: 0.45; }
.wg-moving .wg__cell { opacity: 0.55; }
.wg-moving .wg__cell.is-drop { opacity: 1; }

/* Where the booking would land: the block drawn again, at the hour and on the
   courts the magnet has snapped it to, while the booking itself stays where it
   still is with the same ring round it (.wgb.is-moving). It is the answer
   rather than a hint — the drop
   writes exactly what this is standing on — so it is drawn as the thing it
   would become, in the settled green a moved booking will still be, with the
   accent ring round it saying it has not happened yet.

   Absolutely positioned inside the grid like the selection rectangle and the
   crosshair, so the grid's own placement never sees it, and pointer-transparent
   so it can never come between the pointer and the block being dragged — or
   between it and the cell underneath, which is what a snap has to be measured
   against.

   z-index 4 puts it over the booking blocks (1) and over a blanked closed
   column (3), and under the frozen headings (5), so a ghost carried to the
   top of the week slides under the dates exactly as the blocks it is
   standing among do.

   The ring is a pixel heavier than a block's own border, and it is the same
   accent the block in hand is ringed in: the two of them are the ends of one
   move — where the booking is, and where it would be — and they are the only
   two things on a faded week drawn at full strength. */
.wg__ghost {
  position: absolute;
  z-index: 4;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 1px;
  min-width: 0;
  overflow: hidden;
  padding: 2px 3px;
  border-radius: 6px;
  background: #d9f2e3;
  box-shadow: inset 0 0 0 3px var(--mc-accent);
  color: #145b36;
  font-size: 0.72rem;
  line-height: 1.2;
  text-align: center;
  pointer-events: 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.

   The overlay carries the lower three-quarters wherever it can be seen, which
   is every day but one: a day the venue does not open on is blanked by an
   opaque panel standing above it, and there the panel draws the same three
   edges itself (see .wg__closed.is-today-col). The heading half is untouched by
   that — it is the same heading whether the day below it opens or not. */
.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 tab that says TODAY -----------------------------------------------
   The circle, the cream and the mahogany edge all say WHICH column today is,
   and not one of them says it in a word — somebody opening the week for tonight
   is looking for one day out of seven, and a word is the fastest thing on a
   page to find. So today's column is given a tab: a band of the same mahogany
   standing on the table's top line with TODAY on it, rounded at the top corners
   and square where it meets the heading, which is the shape of the tab on a
   folder in a drawer and reads as a label ON this column rather than as a row
   above the week.

   It costs the table nothing, and that is the first thing about it rather than
   a happy result. The tab is not in the grid, not in the heading and not in the
   box the week scrolls in: it hangs off the wrapper round the scroller
   (.mc-weekload, positioned already for the spinner it centres) and is painted
   over the whitespace that has always been between the week bar and the table.
   A week with a today in it and a week without are the same table at the same
   place at the same size, scrolling through the same box; the only difference
   is whether something is floating above the line.

   Outside the scroller because there is nowhere inside it to stand. The
   scroller clips at its own edges both ways — which is what freezes the
   headings and the time column — so anything drawn above the first row of the
   table is cut off by the top of the box. Room can be made for it, and was,
   twice; but any room the tab stands in is room the table has given up, which
   shows as a white band between the card's border and the week and as a week
   scrolling in a shorter box, both for the sake of a label.

   Which is why the placing is JavaScript (see trackTodayTab). The tab has to
   stay over a column that moves, and the column moves because the week scrolls
   sideways inside a box the tab is not in — from out here, nothing follows it
   for free. So it is told where today's heading is, on every redraw and on
   every scroll of the scroller: the heading's own left in the grid, less how
   far the grid has been scrolled. A transform rather than anything that
   reflows, because what it is keeping up with is a thumb.

   The strip round it is the frozen time column's doing. A column scrolled far
   enough goes UNDER the time labels and stays there, and a tab drawn from
   outside the box would have gone on floating over them with no column beneath
   it. So the tab sits inside a band that begins where the frozen column ends
   and is cut off at the far side of the scrollport, with its overflow hidden:
   today sliding under the labels takes its tab under them too, a slice at a
   time, and nothing has to be told when to stop drawing it.

   Vertically it does not move at all, and does not need to. The table's top
   line is where today's heading carries its mahogany top border, and that is
   where the heading is at every scroll position there is — a sticky heading at
   the top of the box IS the top of the box. So the tab is dropped until its
   foot laps two pixels over that border, and the joint is the same joint
   scrolled or unscrolled: brown over brown, where two browns merely touching
   can be parted by a hair of rounding on a fractional column width.

   Nothing to press and nothing to read out. It is a mark ON a column, and a
   screen reader brought to it here would meet a word standing after the table
   with no column attached to it; the heading it names is in the reading order
   already, with the date it is about. */
.wg-tabstrip {
  position: absolute;
  /* The scroller's top left corner is the wrapper's own, so both of these are
     measured from the table: down past the card's 1px border and the 2px of
     lap, then up again by the whole height of the tab; and in from the left by
     the frozen column, which is the first track the grid lays down. The width
     is the one measurement left to JavaScript, because the far side of the
     scrollport is wherever the browser's own scrollbar has left it. */
  top: calc(1px + 2px - var(--mc-todaytab-height));
  left: calc(1px + var(--mc-time-col));
  height: var(--mc-todaytab-height);
  overflow: hidden;
  pointer-events: none;
}
.wg-todaytab {
  display: block;
  height: 100%;
  border-radius: 8px 8px 0 0;
  background: var(--mc-brand);
  color: #fff;
  text-align: center;
  font-size: 0.64rem;
  font-weight: 700;
  line-height: var(--mc-todaytab-height);
  letter-spacing: 0.06em;
}

/* 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 above that, so a week scrolled
   sideways slides today under the time labels exactly the way it slides
   everything else. Which is also what puts a closed day's panel, at 3, over the
   top of it — a price paid knowingly, and paid back by the panel. */
.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 chevrons on the ends of the week -----------------------------------
   Two circles riding the two ends of the WEEK ITSELF, one on each, and between
   them the whole of what a reader needs to get across seven days that do not
   fit. Held, they walk the grid sideways; pressed at the end of the week, they
   turn it, by calling the same two actions the ← Previous / Next → buttons
   call. Which of the two happens is decided once, at the moment of the press,
   from where the scroller already is — see trackWeekChevrons in calendar.js,
   where that reasoning is written out, and shared/_calendar_shell, where these
   are drawn.

   They hang off .mc-weekload rather than living in the scroller, for the same
   reason the TODAY tab does: everything inside .wg-scroll is clipped by it and
   swept along by it, and here there is a second reason on top of that — the
   contents of the scroller are what its scrollWidth is, and scrollWidth is the
   number the edge test reads. A control that changed the measurement it is
   deciding on would be a control arguing with itself.

   ACROSS: each circle is centred on one of the WEEK'S OWN two edges — not on
   the card's. The card's border is the outermost line the calendar has and it
   belongs to the whole instrument, week bar and legend and footer included; a
   handle hung on it points at the card. These point at the week, so they sit on
   the two lines the week is bounded by, and the well's border stays clearly
   outside both of them:

     ‹  the left border of the FIRST DAY, which is the right-hand edge of the
        frozen time column. That line is stationary whatever the week does — the
        column is sticky against the left of the scrollport, so its far edge is
        one time column in from there at every scroll position — which is why it
        can be written here at all. From the wrapper it is the scroller's own
        border plus --mc-time-col, and the circle is that less half of itself.

     ›  the right border of the LAST DAY IN VIEW, which is the far inner edge of
        the scrollport. Inboard of the vertical scrollbar, not over it: the bar
        is a thing to be grabbed and a circle sitting on it would take half the
        runway away. From the wrapper that is the border plus the width of the
        scrollbar gutter, and again less half a circle.

   Which puts them OVER THE WEEK, and that is the price of the position rather
   than an oversight of it. Half of the ‹ lies on the hour labels and half on the
   first day's cells; the › covers a slice of whichever day happens to be at the
   right-hand edge. A circle is 40px on a 128px column, level with the middle of
   the scrollport, so what it hides is part of one cell in one column out of
   seven at one hour out of the day — and the cell either side of it, above it
   and below it is untouched and still takes a press. That is the trade: a
   control on the week's own edge is a control the eye finds without being told,
   and a week has room to give up 40px of one cell for it.

   .mc-weekload has no padding of its own, so the scroller's corners are the
   wrapper's own corners and both offsets are measured straight from them — the
   same arithmetic, from the same corner, that puts the TODAY tab's strip at
   `1px + var(--mc-time-col)` a few rules above. Nothing hangs outside the card
   any more, so the page keeps the gutter every other page has and no calendar
   page asks for room it does not need.

   The gutter is the one number here a stylesheet cannot know — a scrollbar is
   ten pixels of styled pill on this desk, an overlay of no width at all on a
   phone, and whatever the reader's own platform says everywhere else — so it is
   measured and written on the wrapper by the same pass that writes the vertical
   one below. The 10px fallback is what this app's own bar is worth (see
   .wg-scroll::-webkit-scrollbar) and is only ever seen in the frames before the
   first measurement lands.

   DOWN: on the middle of the hours in view. Not the heading row, which is where
   these first went: a control level with the day names sits over the one band
   of the grid that is a label rather than a thing, and reads as punctuation on
   the header instead of as a way across the week. The middle of the VISIBLE
   hours is the middle of what the reader is actually looking at — at any window
   height, on any week, and on a phone as much as on a desk — and because the
   buttons hang off the wrapper rather than off the grid, that point stands
   still while the hours scroll under it.

   Which leaves the two numbers no rule can know, both written on the wrapper by
   JavaScript after every redraw and on every resize:

     --wg-chev-gutter
                     how wide the scrollport's vertical scrollbar is, which is
                     the reader's platform's business and not this file's: the
                     difference between the box's outer width and the width it
                     shows, less its two borders.

     --wg-chev-mid   how far below the top of the scrollport the middle of the
                     hours falls: the headings' own height, which is the height
                     of the type in them and whether this venue has a court
                     strip under its dates, plus half of whatever is left of the
                     scrollport underneath them — and what is left is the
                     window's height, the venue's opening hours and whether the
                     card is carrying a footer.

   --wg-head-h, which is the first half of that second sum and is already being
   taken, CANNOT be borrowed: it is set on .wg-scroll, because that is the element
   whose scrollbar track reads it, and a custom property travels DOWN from the
   element it is set on and never sideways to a sibling. The fallback is 50% of
   the wrapper — the same point with the headings ignored, which is a few dozen
   pixels high rather than wrong — and it is only ever seen in the frames before
   the first measurement lands. The 1px in front of it is the scroller's own
   border, which the measurement is taken inside of.

   Quiet, but not hiding: on a desk they rest a shade under full and take the
   venue's mahogany when they are pointed at or tabbed to — the same flip the ?
   in the well's corner does. On a phone there is no pointing at anything, so
   there they are simply on. */
.wg-chev {
  position: absolute;
  top: calc(1px + var(--wg-chev-mid, 50%));
  transform: translateY(-50%);
  /* Over everything the grid has, which now matters: the ‹ lands on the seam
     between the frozen time column and the first day, and the highest thing in
     there is the month corner at 6. A circle half-swallowed by the column it is
     standing on would read as a rendering fault. */
  z-index: 7;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--wg-chev-size);
  height: var(--wg-chev-size);
  padding: 0;
  border: 1px solid var(--mc-line);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.82);
  color: var(--mc-muted);
  font-size: 1.4rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  opacity: 0.9;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.08);
  transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease,
              border-color 0.15s ease;
  /* A press on one of these is a hold, and every default a browser has for a
     hold on a small round thing is wrong here: panning the page under the
     finger, the callout menu, the double-tap zoom, the word-selection sweep of
     a mouse dragged off the edge. The gesture is answered in full by the
     script; nothing is left for the browser to also do with it. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
/* THE GLYPH IS NOT THE INK. ‹ and › are punctuation, and a font sets punctuation
   against the baseline rather than in the middle of the em box: the ink of these
   two runs from a little above the baseline to about the cap line, and a whole
   descender's worth of empty space is counted in underneath it all the same.
   What the flexbox above centres is the SPAN — the em box, its leading and that
   empty descender and all — so the arrow lands LOW in the circle by half of what
   is hanging below the baseline. Measured on this app's own type it is 1.47px of
   ink adrift in a 22.4px glyph, which is nothing on a line of text and a plainly
   crooked button once it is the only thing inside a 2.5rem disc.

   So the span is lifted back by two pixels, which is the owner's own eye on the
   rendered button — a scan of the ink put the drift at about a pixel and a half,
   so the eye and the measurement agree on everything but the rounding, and on a
   button this is drawn by eye the eye gets the final say. It is a nudge for ONE
   font — every sans-serif hangs its guillemets differently, over a range of
   about a pixel at this size — but every one of them hangs them low, so the
   correction is right in direction everywhere.

   HORIZONTALLY THERE IS NOTHING TO CORRECT, which is worth writing down because
   sideways is where the eye goes first and where the next person will reach. The
   same scan puts the ink's bounding box 0.21px LEFT of centre for ‹ and 0.19px
   right for › — mirrored, as mirrored glyphs will be — while the ink's centre of
   MASS falls on the other side of the line by 0.08px. The two honest answers to
   "where is the middle of this arrow" straddle the circle's centre by a fifth of
   a pixel, under the grid it is drawn on. A per-side nudge in there would be
   fitting to noise, and to one platform's font at that.

   A TRANSFORM, and on the span rather than the button, because the button's own
   transform is load-bearing twice: translateY(-50%) is what holds it on the
   middle of the hours, and :active scales it. Nothing here may join that
   argument. Inside, the arrow simply rides the press. */
.wg-chev > span {
  transform: translateY(-2px);
}

/* Each line's own position, less half a circle to centre the circle on it. */
.wg-chev--prev {
  left: calc(1px + var(--mc-time-col) - (var(--wg-chev-size) / 2));
}
.wg-chev--next {
  right: calc(1px + var(--wg-chev-gutter, 10px) - (var(--wg-chev-size) / 2));
}

/* Nowhere behind this week to go — the player's calendar on the current week,
   where the server hands back no previous week at all (see syncPreviousWeek).
   Dimmed rather than shut, because the two jobs this button does have different
   answers to that fact: there is no week behind, but there is plenty of THIS
   week off the left of a phone, and it is still the way to it. So the attribute
   only takes the colour down; the press still scrolls, and the press AT the
   edge is quietly inert, refused inside previousWeek() where the rule already
   lives. Never `disabled`, which would take the scrolling away with it.

   Desktop only. On a phone the circles are the only way across the week that
   announces itself, and a faded one reads as broken.

   Written BEFORE the hover rule below on purpose: a dimmed chevron pointed at
   is still a working scroll control, and it should light up like the other. */
@media (hover: hover) {
  .wg-chev[data-limit] { opacity: 0.35; }
}

.wg-chev:hover,
.wg-chev:focus-visible {
  opacity: 1;
  background: var(--mc-brand);
  border-color: var(--mc-brand);
  color: #fff;
}
/* The transform is restated because it is what holds the circle on the middle
   of the hours; a bare scale() here would drop it half its own height down the
   page for as long as it was held. */
.wg-chev:active { transform: translateY(-50%) scale(0.94); }

/* A phone is where these earn their keep — the week is at its widest relative
   to the screen and there is no scrollbar worth aiming at — so there is no
   resting state to fade into and no hover to come out of. The size is the same
   as the desk's and always was meant to be: 2.5rem is a fingertip's worth of
   circle, and a control that changed size between a phone and a laptop would be
   two controls. What is left here is the white it stands in — solid rather than
   the desk's near-white, because it stands over the week itself, hour lines and
   day borders and booking blocks, and there is no cursor coming to firm it up
   the moment it matters. */
@media (hover: none), (max-width: 640px) {
  .wg-chev {
    opacity: 1;
    background: #fff;
  }
}

/* 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;
  /* Above the closed-day panel (.wg__closed, z-index 3): the panel is opaque
     and spans every hour of its column, so at anything lower a shut day
     scrolled sideways slid OVER the axis and blanked the times — the one
     column that must stay put. Still under the sticky headings (5), which the
     hours scroll beneath. */
  z-index: 4;
  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 stops at the axis: the band sits at z-index 2 and this
   column at 4 (see .wg__time — the axis has to stand above the closed-day
   panel), so a hovered row tints the days and leaves this label alone. */
.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.

   One line comes back, and it is the one the column is not the column without:
   the boundary against the day before it. That line is a cell's left edge
   everywhere else on the grid (see the day-boundary rule below), so a column
   with no cells had nothing to draw it — and an opaque panel standing over the
   place it would have been fused a closed Monday to the Sunday beside it into
   a single grey field with no telling where one ended. The panel rules its own
   left edge instead, in the same stronger grey a day boundary is said in
   anywhere else. Its right edge needs nothing: the next day's first cell draws
   that same boundary as its own left, and a closed day beside a closed day is
   two panels each ruling the one edge it owns.

   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 the
   hairlines inside it — 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), and a finger that lands on no cell
   falls through to the page with the gesture untouched. There is nothing under
   the panel to reach either: the customer's payload carries no reservations at
   all on a day the venue does not open (see the availability endpoint's
   public_bookings), so what this covers is empty column and not a booking
   somebody might have wanted to press. */
.wg__closed {
  position: relative;
  z-index: 3;
  cursor: not-allowed;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #ededed;
  border-left: 1px solid var(--mc-line-strong);
  color: var(--mc-muted);
  font-size: 0.66rem;
  font-weight: 600;
}

/* And when the day the venue is shut is also today, this panel is the frame.
   The line round today is drawn in two halves, and the lower one — the sides
   and the foot — is an overlay pinned beneath everything the grid body can lay
   over it, for a reason that has nothing to do with closed days and cannot be
   given up (see .wg__todayframe above). This panel is opaque and stands higher,
   so on a shut today the reader saw a heading in mahogany with a top and two
   ears and then nothing at all down either side of it: three-quarters of a
   frame missing, on the one column of the week the frame exists to find.

   So the panel wears the frame at the height it is actually visible from. The
   same two pixels of the same mahogany on the same three edges, over a column
   that is exactly today's and hours that are exactly all of them, which is what
   makes it the frame rather than an imitation of it — and it meets the
   heading's own borders above squarely, both being borders drawn inside the
   same grid track. The overlay underneath is left where it is: it is covered
   entire, it costs nothing, and a closed day is not a reason for buildWeekGrid
   to learn what a closed day is.

   The grey stays. Closed is still closed, and the frame is a boundary rather
   than an offer — the only thing being said here is where today is. */
.wg__closed.is-today-col {
  border-left: 2px solid var(--mc-brand);
  border-right: 2px solid var(--mc-brand);
  border-bottom: 2px solid var(--mc-brand);
}

/* 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.
   A column with no cells in it has no first cell to ask, which is the one place
   this rule cannot reach: a closed day draws the same boundary out of its own
   panel, in the same colour, for the same reason (see .wg__closed). */
.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 (4) and the hover bands (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;
  /* A block is held for a second and a half to pick it up and move it (see
     trackBlockDrag), and a phone answers a long press on a piece of text by
     offering to select it, magnify it, or put a menu over the very thing being
     dragged. All three are taken away here.

     `touch-action` is deliberately `manipulation` rather than `none`: the week
     is a box that scrolls both ways, and a block that refused to be panned
     would be a hole in the middle of the grid a thumb could not get past. What
     `manipulation` drops is only the double-tap zoom, which is the one thing a
     hold-and-drag would otherwise have to compete with. The hold itself does the
     rest: a finger that travels before they are up is scrolling, and the engine
     lets it. */
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}
/* The booking while it is in the air. It stays exactly where it is — that is
   still where the booking IS until the server says otherwise — and it is the
   one block on the week drawn at full strength, ringed in the accent the ghost
   is ringed in, so the two read as the two ends of one move. Everything else
   steps back; see the .wg-moving rules beside the selection ones above.

   Written under the container's class rather than on the block alone, and that
   is load-bearing: the verified and unverified colours below name a border
   colour of their own at the same weight as `.wgb.is-moving` would have, and
   the block in hand has to out-argue whichever of them it is wearing. The two
   classes are put on and taken off together, so there is no state this fails to
   describe. */
.wg-moving .wgb.is-moving {
  opacity: 1;
  border-width: 2px;
  border-color: var(--mc-accent);
}
.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; } }
/* Unless the second column has nothing in it. A plain manual booking has no
   gateway to report and no code to scan, and neither has a settled one read by
   an ordinary admin — the PayMongo box is the super-admin's alone — so the
   width taken for two columns would be a screenful of white beside one. The
   panel then takes exactly the curve every other single-column form on the site
   takes, which is what the rules at the top of this section say; only the
   admin-scoped widths above have to be given back, and only where they apply.
   Below sm the columns stack and the panel is the width of the phone either
   way. Put on the modal by admin_calendar_controller#syncPanelWidth, which is
   the one place that knows what is standing over there. */
#admin-calendar .mc-modal.is-narrow .mc-modal__panel { max-width: min(100%, 40rem); }
@media (min-width: 768px) {
  #admin-calendar .mc-modal.is-narrow .mc-modal__panel { width: 66.6667vw; }
}
@media (min-width: 992px) {
  #admin-calendar .mc-modal.is-narrow .mc-modal__panel { width: 41.6667vw; }
}
/* And the details take the whole of that narrower panel rather than half of it.
   The columns are Bootstrap's, which know nothing about what was put in them: a
   col-sm-6 with an empty twin is still a half, so narrowing the panel alone
   would leave the booking's own fields squeezed into half of half and the blank
   space merely smaller. So the one column with something in it is widened to
   the full row and the empty one is taken out of the flow — which is only ever
   the second, since everything the payload can put on the right lives there. */
#admin-calendar .mc-modal.is-narrow .row > .col-sm-6:first-child { width: 100%; }
#admin-calendar .mc-modal.is-narrow .row > .col-sm-6:last-child { display: none; }

.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; }
/* Grey, and deliberately none of the three above: a booking being written down
   has no money question yet, and a colour that means something about a payment
   would answer one nobody has asked. The grid's own reserved palette, so the
   neutral here is the neutral this app already draws. */
.mc-tag--new { background: #eef0f2; border-color: var(--mc-reserved-line); color: var(--mc-reserved-ink); }

/* --- 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, IN the edit modal's heading: "Booking MC-10181". The string
   an admin scans for while holding a player's screenshot against it, which is
   why it is the heading rather than a row under one — the number is what the
   panel is about, and the word in front of it only says what kind of thing the
   number names. Not a field: it is derived from the booking's id and there is
   nothing to type. A booking being entered has no id yet, so the chip is hidden
   and the heading stands as its own sentence. */
.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);
}
.mc-refno__id[hidden] { display: none; }

/* Whether the money arrived, on the row under what was owed — the tag, the
   Verified button that judges it, and the sentence saying why that button
   cannot be pressed. The row wraps so the hint drops under the two controls in
   a narrow panel rather than squeezing them. The tag brings its own colours
   from .mc-tag, which is the customer receipt's vocabulary and must stay the
   one this says it in. */
.mc-status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}
/* And the button is cut to the tag's height rather than the other way round.
   The two stand against each other on one row saying two halves of one thing —
   what the money did, and whether anybody has checked — and a btn-sm is half
   again as tall as a tag, which read as one control beside a label rather than
   as a pair. The tag is the reference because it is the customer's own
   vocabulary (see .mc-tag, and bookings/_receipt): its size is a fact this row
   borrows and must not restate.

   So the four things that make a height are taken from it exactly — the type
   size, the leading, the padding and the one-pixel border the tag already
   draws — and the corner radius with them, which is what stops a matched pair
   still reading as two families. Nothing else moves: it is the same button in
   the same place with the same classes, so btn-success and btn-outline-secondary
   go on saying which state it is in (see setVerified), disabled goes on looking
   disabled, and it is still what the keyboard lands on. Written against
   .mc-status .btn rather than a class of its own, because the row has exactly
   one button and a class would have to be kept in step in two files. */
.mc-status .btn {
  font-weight: 700;
  font-size: 0.78em;
  line-height: 1.2;
  padding: 0.2em 0.55em;
  border-radius: 3px;
}

/* 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 past the end of the
   legend. Pinned rather than laid out — the well is positioned for exactly this,
   and the week bar keeps its whole layout whatever the viewport does, so nothing
   here can crowd the navigation or shove the legend along. It used to ride the
   band of top padding the logo disc was given; that band is gone with the disc,
   so it stands level with the week bar's first row now and the bar carries a
   gutter on its right for it (see #player-page .mc-weekbar).

   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: 1.25rem;
  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; }
/* The one details table in the app whose values are allowed to wrap. The nowrap
   on .mc-details dd is there so "August 8, 2026" is never broken across two
   lines, and every row it was written for is short by construction; this one
   carries a whole appointment on a single row — "(Tuesday) August 18, 2026,
   5:00pm - 7:00pm" — inside a panel that is deliberately slim, which is three
   times the width there is. Wrapped it reads; nowrap it runs off the card. */
.mc-reschedule__details dd { white-space: normal; }
/* Switch Courts: the panel's own tick boxes, standing on their own. In the
   booking panel they sit in a details table and take their row from
   .mc-details__field; here there is no table under them, so the row is said
   again — the spacing itself still comes from .mc-courts, which is what keeps
   the two surfaces one shape. */
.mc-reschedule__courts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
/* Switch Courts, on the customer's own copy of the dialog, standing clear of the
   two boxes above it. The desk's version runs the two questions together because
   an admin moving a booking is working down a form they use all day; a customer
   meets this once, in the minute after paying, and the gap is what stops the
   tick boxes reading as part of the answer to "when". A whole number of pixels
   rather than an em, because what is being set is a gap between two questions
   rather than a space in a paragraph. */
.mc-reschedule__switch { margin-top: 20px; }
/* And the countdown under them: it is a note about the offer rather than a field
   of it, so it takes the quiet size .mc-sub gives everything of its kind and
   keeps a line of its own above the buttons. */
.mc-reschedule__window { margin: 0.6rem 0 0; }
/* A dialog whose window has run out. Everything in it is properly `disabled` —
   there is nothing left to ask, so unlike the button that opened it there is no
   press worth taking — and the note that says so is what the reader is left
   looking at. */
.mc-reschedule.is-closed .form-label { opacity: 0.65; }

/* 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.

   The tab over today's column fades with it, being the going week's own mark
   and not the wrapper's: a label left at full strength over a week that has
   gone pale is the one thing on the card claiming to be current. It has
   nothing to put out of reach — there was never anything on it to press. */
.mc-weekload > .wg-scroll,
.mc-weekload > .wg-tabstrip { transition: opacity 0.15s ease; }
.mc-weekload.is-loading > .wg-scroll {
  opacity: 0.5;
  pointer-events: none;
}
.mc-weekload.is-loading > .wg-tabstrip { opacity: 0.5; }

/* The chevrons go with it, and unlike the tab they have something to put out of
   reach. A press on one while the next week is landing is a second week asked
   for on top of the one already in the air — or, worse, a scroll driven against
   a grid that is about to be replaced under it. They come back the instant the
   week does, which on a load quick enough to read is no time at all. They are
   dimmed further than the week is because they are chrome: the reader should be
   watching the grid. The fade needs no rule of its own here: .wg-chev already
   carries a transition on opacity for the hover, and one is all this wants. */
.mc-weekload.is-loading > .wg-chev {
  opacity: 0.35;
  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; }

/* The player's calendar gives its tall top back, the way the store, the wifi
   list and the policies poster do. That 4em band was room for the logo disc
   riding the card's edge; the mark is on the navbar now, and a week grid in a
   card with three tight sides and one gaping one reads as a mistake.

   Which leaves the ? in that corner standing where the disc's band used to hold
   it clear of everything. It is pinned, so it does not push the week bar about —
   the bar is given the gutter instead, and the two numbers have to agree: 1.25rem
   of card padding plus the 2rem mark is the 2.5rem asked for here, with the
   rounding up as the air between the mark and the end of the legend. */
#player-page .mc-well { padding-top: 1.25rem; }
#player-page .mc-weekbar { padding-right: 2.5rem; }

.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 record needs — a label, and somebody's own words shown afterwards as a
   quotation — plus the little the two sides of ASKING for one need. */
/* Somewhere to type the reason, on the form that enters a booking. It sits in
   the details table, where every other control is a box for a number and is
   capped to the width of one, so it takes the column back (.mc-details__text)
   and asks for the height a sentence or two wants. Draggable, because a reason
   that runs longer than that is the venue's business rather than this rule's. */
.mc-override__input { min-height: 5rem; resize: vertical; }
/* The price of a booking that is not sold at the rate card, in the row where
   the computed figure otherwise stands. A little wider than the counted boxes
   beside it — those hold two digits and this holds an amount in pesos with its
   centavos and a spinner. */
.mc-amount__input { max-width: 8rem; }
/* The two ways an override is finally paid for, under the reason they are about.
   Borrowed whole from .mc-qrph__actions, which is the row this app puts a pair
   of alternatives in — and turned back to the left, because everything else in
   this box reads down the left edge and a centred pair would be the only thing
   in it that does not. */
.mc-override__settle { justify-content: flex-start; }
/* And the code one of them puts there, under the buttons. Nothing but the gap
   that keeps it off them: what is inside is drawn in the same classes every
   other code in this app is (see .mc-qrph__card), and centres itself. */
.mc-override__code { margin-top: 0.7rem; }
.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;
}

/* --- the walk-in store -----------------------------------------------------
   A table of transactions rather than a grid of hours, so almost nothing on
   this page could be borrowed from the calendar — but everything it is BUILT
   from is: the same well, the same boxed sections, the same modal, the same
   mahogany. What is new here is the table itself, the stepper, and the receipt
   the customer photographs.

   The well gives its tall top 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
   heading in a card with three tight sides and one gaping one reads as a
   mistake. */
.mc-well--flat { padding-top: 1.25rem; }

.mc-storebar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.mc-storebar .mc-spacer { margin-left: auto; }
.mc-storebar h1 {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--mc-brand);
  text-transform: uppercase;
}
/* The one thing there is to do on this page, so it is the one filled button on
   it and it says what it does in the venue's own shorthand. */
.mc-storebar__add {
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* The table of takings. It scrolls SIDEWAYS inside its own box on a narrow
   screen rather than pushing the card off the page — the same trade the week
   grid makes, and for the same reason: three columns of real words do not
   compress into 360px of glass without one of them becoming an ellipsis. */
.mc-txtable__scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.mc-txtable {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}
.mc-txtable th {
  padding: 0.5rem 0.75rem;
  text-align: left;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mc-muted);
  border-bottom: 2px solid var(--mc-line-strong);
  white-space: nowrap;
}
/* Money is read down a column, so it is right-aligned and set in tabular
   figures — a list of amounts whose digits do not line up is a list nobody can
   add up by eye. */
.mc-txtable__figure {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Including its HEADING, which needs saying twice because `.mc-txtable th`
   above is a class AND an element and so beat the class on its own — leaving
   the one column head in the table sitting over the left edge of figures
   stacked against the right. A column label belongs over its column. */
.mc-txtable th.mc-txtable__figure { text-align: right; }

/* A transaction is a row you can press, so it says so on hover and carries the
   caret every disclosure in this app carries. */
.mc-txrow {
  cursor: pointer;
  border-bottom: 1px solid var(--mc-line);
}
.mc-txrow:hover { background: var(--mc-open-hover); }
.mc-txrow td { padding: 0.55rem 0.75rem; vertical-align: middle; }
.mc-txrow__ref {
  font-weight: 700;
  white-space: nowrap;
  color: var(--mc-brand);
}
/* The state of the money, as a bar down the left edge rather than as another
   column. It is the fact a person scanning the list is actually looking for —
   which of these is still owed — and a colour answers it without costing the
   table a fourth thing to read. */
.mc-txrow.is-paid .mc-txrow__ref { box-shadow: inset 3px 0 0 #2e9e68; }
.mc-txrow.is-pending .mc-txrow__ref { box-shadow: inset 3px 0 0 #e0c24e; }

/* --- the cart --------------------------------------------------------------
   One line per thing the venue sells, read in two rows rather than three
   columns: what it is and what it costs at the top left, what that comes to
   opposite it at the top right, and the stepper on its own line underneath at
   the left.

   The pair a person checks against each other while building a cart is the
   THING and its PRICE, so those two are the ones set side by side; how many of
   it is what they are in the middle of deciding, and it sits under them rather
   than between them. It also gives the stepper a whole line to itself, which is
   what keeps it thumb-sized on the phone at the counter instead of being the
   first thing squeezed when a product has a long name.

   A grid rather than a wrapping flex row, because the arrangement is a
   DECISION: every one of the three is placed by name below, so a long product
   name or an empty total cannot rearrange the row by running out of space. */
.mc-cartline {
  display: grid;
  /* The right-hand column keeps the width the total was given when it stood
     where the stepper stands now, so a figure appearing or disappearing as the
     count changes does not shuffle the name beside it every press. */
  grid-template-columns: 1fr minmax(5.5rem, auto);
  /* Baselines, so the total lines up with the product NAME rather than with the
     middle of the name-and-unit-price block beside it. */
  align-items: baseline;
  column-gap: 0.75rem;
  row-gap: 0.4rem;
  padding: 0.5rem 0;
  /* And room down the left, so the mahogany edge below is a rule beside the row
     rather than a bar the words are pressed up against. On the row rather than
     on the text, so the name, the unit price and the stepper under them share
     one left edge — and on EVERY row rather than only the marked ones, so
     nothing shifts sideways at the moment a line is added to the cart. */
  padding-left: 0.6rem;
  border-bottom: 1px solid var(--mc-line);
}
.mc-cartline:last-of-type { border-bottom: 0; }
.mc-cartline__what { grid-column: 1; grid-row: 1; min-width: 0; }
.mc-cartline__total {
  grid-column: 2;
  grid-row: 1;
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.mc-cartline .mc-stepper { grid-column: 1; grid-row: 2; }
/* The row's own title, so it is a step above the body text around it — enough
   to be found by eye down a list of products, and not enough to read as a
   heading in a form that already has a legend over it. */
.mc-cartline__name { display: block; font-size: 1.05rem; font-weight: 600; }
.mc-cartline__unit { display: block; font-size: 0.8rem; color: var(--mc-muted); }
/* A line with something on it is worth seeing at a glance while the cart is
   being built, so the name goes to ink and the row keeps a faint mahogany
   edge. */
.mc-cartline.is-chosen { box-shadow: inset 2px 0 0 var(--mc-brand); }
.mc-cartline.is-chosen .mc-cartline__name { color: var(--mc-brand); }

.mc-stepper {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
/* Square, and big enough for a thumb at a counter. The number between them is
   typed as often as it is stepped — ten of something is a number somebody types
   — so it is a real input rather than a label. */
.mc-stepper .btn {
  width: 2.1rem;
  padding: 0.2rem 0;
  font-weight: 700;
  line-height: 1.4;
}
.mc-stepper input {
  width: 3.4rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
/* The spinner arrows a number input draws are a second, tinier stepper beside
   the one that is there for thumbs. */
.mc-stepper input::-webkit-outer-spin-button,
.mc-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.mc-stepper input { -moz-appearance: textfield; appearance: textfield; }

.mc-carttotal {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0.8rem 0 0;
  padding-top: 0.6rem;
  border-top: 2px solid var(--mc-line-strong);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--mc-muted);
}
.mc-carttotal .mc-amount {
  margin-left: auto;
  font-size: 1.25rem;
  color: var(--mc-ink);
  font-variant-numeric: tabular-nums;
}

/* --- the receipt -----------------------------------------------------------
   The one thing in the store that is not for the venue: a customer is holding a
   phone up to it, and what they are holding it up to should be the same piece of
   paper their booking receipt is.

   So there is almost nothing here. The letterhead, the reference band and the
   details card are .mc-letterhead, .mc-reference and .mc-box — the very rules
   the player's booking receipt is drawn with (see bookings/show and
   bookings/_receipt) — and borrowing them rather than answering them again is
   the whole point: a house style that is copied is a house style that drifts.
   What is left below is only the two things that are true of this receipt and
   not of that one. */

/* --- how much room the purchase panel gets ---------------------------------
   Wider than the app's other dialogs, all of it. This panel is not a question
   with two buttons under it — it is a counter: a cart with a stepper per
   product on the left and prices down the right, and then a code, and then a
   receipt. The slim width the reschedule and delete panels wear was making
   every one of those work harder than it had to.

   The width is stated here rather than left to .mc-modal__panel's own
   breakpoint curve, which narrows as the screen GROWS (41.67vw from lg) — a
   sensible shape for a short form and the wrong one for a document. One
   max-width and a full-width box under it: on a phone that is the whole screen
   inside the modal's padding, and on a desk it is a panel of a fixed, readable
   measure. */
#admin-store .mc-modal__panel {
  max-width: min(100%, 36rem);
  width: 100%;
}

/* And the receipt gets more again, because it is the one face of this panel
   that is not a dialog at all: a customer is holding a phone up to a document,
   and a receipt folded into a dialog's column reads as a dialog. */
#admin-store .mc-modal__panel.is-receipt {
  max-width: min(100%, 44rem);
  padding: 1.75rem 1.5rem 1.5rem;
}

/* The order summary under the code. The panel around it is centred, because
   almost everything in it is a picture or a line about a picture (see
   .mc-qrph) — a label/value table is neither, so it takes its alignment back.
   The same trade .mc-qrph__payee makes on the customer's own booking page. */
.mc-qrph__order { text-align: left; }

/* And the lines the controller writes into the details card, which are the one
   part of it that varies in number. A dotted rule under each, because a list of
   things bought is read down rather than across — the static rows around them
   (Name, Total, Status) are single facts and keep the plain spacing every other
   .mc-details in the app has. */
.mc-receipt .mc-receipt__item {
  padding-bottom: 0.3rem;
  border-bottom: 1px dotted var(--mc-line);
}

/* --- 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,
  .wg-chev,
  .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;
  }
}

/* ==========================================================================
   The piso-WiFi portal
   ==========================================================================

   MOBILE FIRST AND CARRYING ITS OWN CHROME, which is the whole difference from
   every other block in this file. The rest of the app is Bootstrap plus these
   rules; the portal is these rules and nothing else — it is served to a phone
   that has no internet yet, behind a captive portal whose walled garden is 32
   addresses long, so a stylesheet or a font from a CDN would simply never
   arrive and a guest deciding whether to trust the venue with ₱50 would be
   looking at unstyled text (see layouts/wifi.html.erb).

   Everything here is `mc-wifi`-prefixed and reaches nothing outside this file.
   The one thing it takes from above is the palette, which is the venue's. */

body.mc-wifi {
  /* THE VENUE, BEHIND EVERYTHING AND OUT OF FOCUS. The one picture on this page
     that is not the guest's own code: an aerial of the two courts, blurred and
     dimmed, so the card floats on the place the guest is standing in rather than
     on a grey rectangle. It is a 1200px JPEG at ~200 KB because it is never read
     — a blur throws away exactly the detail a bigger file would have paid for —
     and this page is served over the venue's own uplink to a phone that has no
     internet yet. */
  background-image: none;
  background-color: #2a2f28;
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 17px;
  line-height: 1.5;
}

/* Two fixed layers behind the content and neither of them in the flow: the
   photograph, and a scrim over it. FIXED rather than scrolled, so a card taller
   than the handset does not drag the courts up past the header.

   The overscan is what a blur costs — a blurred edge is a soft grey border
   otherwise — and `-3rem` is comfortably more than the radius. */
body.mc-wifi::before,
body.mc-wifi::after {
  content: "";
  position: fixed;
  pointer-events: none;
}

body.mc-wifi::before {
  inset: -3rem;
  background: #2a2f28 url("/mc-court-1.jpg") center / cover no-repeat;
  filter: blur(10px);
  z-index: -2;
}

/* Enough to hold white text on the header and to keep a bright court from
   competing with the card, and no more: the photograph is meant to be read as
   the place, not as a texture. */
body.mc-wifi::after {
  inset: 0;
  background: rgba(20, 24, 20, 0.34);
  z-index: -1;
}

/* The bar across the top, and the venue's name on it. Centred because it is a
   title rather than a navigation — there is nowhere else on this host to go. */
.mc-wifi__header {
  position: relative;
  background: #1c211c;
  padding: 0.9rem 1rem;
  text-align: center;
}

.mc-wifi__brand {
  color: #fff;
  text-decoration: none;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.01em;
}

.mc-wifi__main {
  /* Wide enough for the QR to be scanned off a screen by somebody else's phone,
     narrow enough that the whole panel is one thumb's reach on a handset. */
  position: relative;
  max-width: 30rem;
  margin: 0 auto;
  padding: 1rem 1rem 3rem;
}

/* THE WELL. Plain #fff because the venue's logo is a PNG on a white ground and
   the card has to be the same colour as the picture at the top of it (see
   _logo.html.erb); a shadow rather than a border, because on a photograph a
   hairline reads as a seam and a shadow reads as paper. */
.mc-wifi__panel {
  background: #fff;
  border-radius: 12px;
  padding: 1.5rem 1.25rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}

/* The venue's mark at the top of both states of the card. Centred, and sized as
   a proportion of the card rather than in pixels, so it holds its proportions on
   a small handset and on a tablet. */
.mc-wifi__logo {
  display: block;
  width: 62%;
  max-width: 15rem;
  height: auto;
  margin: 0 auto 0.75rem;
}

.mc-wifi__title {
  margin: 0 0 1rem;
  font-size: 1.4rem;
  line-height: 1.25;
  color: var(--mc-ink);
}

/* What this page is, said once between the mark and the prices. Centred and
   letter-spaced so it reads as part of the mark rather than as the first
   sentence of the page. */
.mc-wifi__wordmark {
  margin: 0 0 1rem;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-align: center;
  color: var(--mc-muted);
}

.mc-wifi__lead {
  margin: 0 0 1rem;
  color: var(--mc-ink);
}

.mc-wifi__note {
  margin: 1rem 0 0;
  font-size: 0.9rem;
  color: var(--mc-muted);
}

/* How much time this device already has, which sits ABOVE the package list and
   never in place of it (FR-026). */
.mc-wifi__running {
  margin: 0 0 1rem;
  padding: 0.6rem 0.8rem;
  background: var(--mc-today);
  border: 1px solid #e6d9a8;
  border-radius: 8px;
}

.mc-wifi__notice,
.mc-wifi__alert {
  max-width: 30rem;
  margin: 0 auto 0.8rem;
  padding: 0.7rem 0.9rem;
  border-radius: 8px;
  font-size: 0.95rem;
}

.mc-wifi__notice {
  background: #eef6ff;
  border: 1px solid #bcd7f5;
}

.mc-wifi__alert {
  background: #fdeceb;
  border: 1px solid #f2b8b5;
}

/* --- the packages --------------------------------------------------------

   One tap each, and a tap target a thumb cannot miss: ≤ 5 taps from arriving to
   being online is the whole shape of this feature (SC-001). */

.mc-wifi__packages {
  display: grid;
  gap: 0.6rem;
  margin: 0;
}

.mc-wifi__package {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  min-height: 3.4rem;
  padding: 0.8rem 1rem;
  background: #fff;
  border: 1px solid var(--mc-line-strong);
  border-radius: 10px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--mc-ink);
}

.mc-wifi__package:hover,
.mc-wifi__package:focus-visible {
  border-color: var(--mc-accent);
  background: var(--mc-open-hover);
  outline: none;
}

.mc-wifi__package-price {
  font-size: 1.35rem;
  font-weight: 700;
}

.mc-wifi__package-duration {
  color: var(--mc-muted);
}

/* --- the tap, while somebody else's server thinks about it ------------------

   A tap on a price opens a payment intent at PayMongo, and until it comes back
   there is nothing on this page to say the tap landed — which on a phone is how
   somebody taps a second price. So the list goes out of focus under a spinner
   the moment a finger lifts, and stays there until the code replaces the whole
   card.

   BLUR AND DIM RATHER THAN A CURTAIN: the prices are still readable enough for
   the guest to see that what they chose is still what is on the page, and
   plainly not tappable. The wrapper is positioned so the spinner has something
   to be in the middle of; the class on it is the only thing JavaScript touches
   (see wifi_portal_controller.js#showBusy), so with no JavaScript at all this is
   an ordinary div round an ordinary form. */
.mc-wifi__buying {
  position: relative;
}

.mc-wifi__buying .mc-wifi__packages {
  transition: filter 120ms linear, opacity 120ms linear;
}

.mc-wifi__buying.is-busy .mc-wifi__packages {
  filter: blur(2px);
  opacity: 0.5;
  /* Belt and braces beside the `disabled` the controller sets: the buttons are
     already refusing the press, and this refuses it before it reaches one. */
  pointer-events: none;
}

/* In the page from the start and shown by the class, because a spinner that has
   to be built is a spinner that arrives a frame late. The animation is the app's
   one `mc-spin` rather than a second set of keyframes saying the same thing. */
.mc-wifi__spinner {
  display: none;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.2rem;
  height: 2.2rem;
  margin: -1.1rem 0 0 -1.1rem;
  border: 3px solid var(--mc-line);
  border-top-color: var(--mc-brand);
  border-radius: 50%;
  animation: mc-spin 0.8s linear infinite;
}

.mc-wifi__buying.is-busy .mc-wifi__spinner {
  display: block;
}

/* --- the code ------------------------------------------------------------- */

/* Big, square and on white, in a box of its own — because what happens to this
   picture next is that somebody photographs it or uploads it into a wallet app,
   and a QR needs a quiet zone round it to be read out of a screenshot. The box
   is what draws that zone; the border is what tells a guest where to crop. */
.mc-wifi__qr-box {
  width: 100%;
  max-width: 17rem;
  margin: 0 auto 0.9rem;
  padding: 0.75rem;
  background: #fff;
  border: 1px solid var(--mc-line);
  border-radius: 12px;
}

.mc-wifi__qr {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  image-rendering: pixelated;
}

/* What is being paid for, in one line and in the two facts the guest chose it
   by: the price they tapped and the time it buys. */
.mc-wifi__amount {
  margin: 0 0 0.2rem;
  font-size: 1.15rem;
  font-weight: 700;
  text-align: center;
  color: var(--mc-ink);
}

.mc-wifi__deadline {
  margin: 0 0 1rem;
  text-align: center;
  font-size: 0.95rem;
}

.mc-wifi__countdown {
  font-variant-numeric: tabular-nums;
  color: var(--mc-muted);
}

.mc-wifi__steps {
  margin: 0;
  padding-left: 1.2rem;
}

.mc-wifi__steps li {
  margin-bottom: 0.35rem;
}

/* --- changing your mind ---------------------------------------------------- */

/* Under a dashed rule, because what is below it is not part of paying: it is the
   way out of it. */
.mc-wifi__panel-foot {
  margin-top: 1.1rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--mc-line);
}

/* SMALL, PLAIN AND ON THE LEFT, and deliberately not a button of the same weight
   as a package. Cancelling closes a live payment code at the gateway; it should
   be findable by somebody looking for it and unfindable by a thumb resting on
   the screen while a wallet app is being opened. */
.mc-wifi__cancel {
  padding: 0.5rem 1rem;
  background: #fff;
  color: var(--mc-ink);
  border: 1px solid var(--mc-line-strong);
  border-radius: 8px;
  font: inherit;
  font-size: 0.95rem;
  cursor: pointer;
}

.mc-wifi__cancel:hover,
.mc-wifi__cancel:focus-visible {
  border-color: var(--mc-ink);
}

/* Pressed, and waiting on the gateway. The word goes transparent rather than
   away, so the button keeps exactly the width and height it had — a control that
   resizes under a thumb is a control that moves out from under it — and the
   spinner is drawn in the space the word was standing in. */
.mc-wifi__cancel.is-busy {
  position: relative;
  color: transparent;
  cursor: default;
}

.mc-wifi__cancel.is-busy::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 1.1rem;
  height: 1.1rem;
  margin: auto;
  border: 2px solid var(--mc-line);
  border-top-color: var(--mc-brand);
  border-radius: 50%;
  animation: mc-spin 0.8s linear infinite;
}

/* Still turning, because it is the only thing saying the page is alive — just
   slowly enough not to be movement anybody has to look away from, which is the
   same allowance the rest of the app makes. */
@media (prefers-reduced-motion: reduce) {
  .mc-wifi__spinner,
  .mc-wifi__cancel.is-busy::after {
    animation-duration: 3s;
  }
}

/* --- the way onward ------------------------------------------------------- */

.mc-wifi__actions {
  margin: 1.2rem 0 0;
}

/* A link that looks like a button and is one to a thumb. Never a redirect: a
   captive-network assistant reads a redirect as the network having come up and
   closes itself (FR-034). */
.mc-wifi__button {
  display: inline-block;
  width: 100%;
  padding: 0.8rem 1rem;
  background: var(--mc-brand);
  color: #fff;
  border: 0;
  border-radius: 10px;
  text-align: center;
  text-decoration: none;
  font-weight: 600;
}

.mc-wifi__button:hover,
.mc-wifi__button:focus-visible {
  filter: brightness(1.1);
  color: #fff;
}

/* --- a stacked form on the portal's card ------------------------------------

   THE STAFF FOLD THAT NAMED THESE IS GONE from the guest's page (see
   _card.html.erb), and what is left of its shapes is what the rehearsal page
   still stands on: a label, a box and a full-width button, stacked. The names
   are kept rather than renamed because renaming them would rewrite a page whose
   whole existence is a developer's convenience, and the shape is the same shape
   either way. The two rules that were the FOLD itself — the disclosure and its
   summary — went with the markup they styled, and so did the upper-casing box,
   which only ever held a staff code. */

.mc-wifi__staff-form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.mc-wifi__staff-label {
  font-size: 0.85rem;
  color: var(--mc-muted);
}

.mc-wifi__staff-button {
  width: 100%;
  padding: 0.8rem 1rem;
  background: var(--mc-ink);
  color: #fff;
  border: 0;
  border-radius: 10px;
  font-weight: 600;
  cursor: pointer;
}

.mc-wifi__staff-button:hover,
.mc-wifi__staff-button:focus-visible {
  filter: brightness(1.2);
}

/* The rehearsal tools, which exist on a developer's machine only (see
   Wifi::SimulationsController). They borrow the staff form's shapes because they
   are the same shape — a stacked label, box and button — and take their own box
   for the one difference that matters: a hardware address is upper-cased and a
   web address is not, and a field that displayed "HTTP://NEVERSSL.COM/" would be
   a field arguing with what was typed into it. */
.mc-wifi__sim-input {
  width: 100%;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  font-size: 1rem;
}

/* Beside the code, under a rule, so it reads as what it is: not part of the page
   the guest is looking at. */
.mc-wifi__sim {
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--mc-line);
  font-size: 0.9rem;
  color: var(--mc-muted);
}

.mc-wifi__sim-outcomes {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.6rem;
}

.mc-wifi__sim-outcome {
  padding: 0.5rem 0.9rem;
  background: transparent;
  color: var(--mc-ink);
  border: 1px solid var(--mc-line);
  border-radius: 10px;
  font-size: 0.9rem;
  cursor: pointer;
}

.mc-wifi__sim-outcome:hover,
.mc-wifi__sim-outcome:focus-visible {
  border-color: var(--mc-ink);
}

/* ==========================================================================
   The WiFi device manager
   ==========================================================================

   /admin/wifi: one row per device the network knows about, and a dialog with
   exactly two buttons on it. It borrows the store counter's table wholesale
   (.mc-txtable, .mc-txrow) because it IS that table with different columns —
   what is added here is the one thing a list of devices has that a list of
   transactions does not, which is a device's STATE, and the state has to be
   readable without reading. */

.mc-wifi-devices__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.mc-wifi-devices__bar .mc-spacer { margin-left: auto; }
.mc-wifi-devices__bar h1 {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--mc-brand);
  text-transform: uppercase;
}

/* What the page says when the controller could not be asked. It sits in the bar
   rather than over the table ON PURPOSE: the table below it is still showing the
   last thing the network actually said, and a banner across it would read as a
   verdict about those rows rather than about the asking (FR-048). */
.mc-wifi-devices__status {
  margin: 0;
  padding: 0.35rem 0.7rem;
  border-radius: 8px;
  background: #fdf2e6;
  color: #8a4b08;
  font-size: 0.82rem;
  max-width: 32rem;
}

/* An address is a serial number, not a word: it is compared character by
   character against a sticker on the back of a phone, so it is set in the one
   family where 0 and O are two different shapes. */
.mc-wifi-devices__mac {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.85rem;
  white-space: nowrap;
}

/* THE STATUS DOT, which is the whole reason this block exists. Green is on the
   network, grey is not, red is a device the venue has taken off — three states
   the owner reads at a glance and then confirms in the word beside it, because
   colour alone is not a thing everybody can read. */
.mc-wifi-devices__dot {
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  margin-right: 0.4rem;
  border-radius: 50%;
  background: var(--mc-reserved-line);
  vertical-align: baseline;
}
.mc-wifi-devices__row.is-on .mc-wifi-devices__dot { background: #2e9e5b; }
.mc-wifi-devices__row.is-blocked .mc-wifi-devices__dot { background: #b3261e; }

/* A device that is not here is drawn quieter than one that is. The row is still
   fully readable — it is history, not noise, and the owner opens this page
   precisely to look for a phone that has gone. */
.mc-wifi-devices__row.is-off {
  color: var(--mc-muted);
  background: var(--mc-past);
}

/* A device the venue waved through, named on its own row. Without it, free use
   and a device that bought a year differ only by a number in the last column,
   which is the least readable thing on the page. */
.mc-wifi-devices__tag {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0 0.35rem;
  border-radius: 4px;
  background: var(--mc-brand);
  color: #fff;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  vertical-align: 1px;
}

/* RENAMING, IN PLACE. The pencil is drawn quiet and only comes up to full
   strength when the row is under the pointer or the button itself has focus:
   one of these sits on every row of the table, and a column of dark pencils
   would read as the point of the page rather than as something available on it.
   It is never display:none, because a control that appears only on hover is a
   control a touch screen does not have. */
.mc-wifi-devices__pencil {
  margin-left: 0.4rem;
  padding: 0 0.2rem;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--mc-muted);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.45;
  transition: opacity 0.12s ease, color 0.12s ease;
}
.mc-wifi-devices__row:hover .mc-wifi-devices__pencil,
.mc-wifi-devices__pencil:hover,
.mc-wifi-devices__pencil:focus-visible {
  opacity: 1;
  color: var(--mc-brand);
}

/* The box the name becomes. It is sized to the cell it replaced so the table
   does not jump a column wider the moment somebody presses the pencil — the
   rows around it are being read at the same time. */
.mc-wifi-devices__nameinput {
  width: 100%;
  min-width: 8rem;
  padding: 0.15rem 0.35rem;
  border: 1px solid var(--mc-brand);
  border-radius: 6px;
  background: #fff;
  font: inherit;
  color: inherit;
}

/* Saving, said on the cell rather than in a toast: the answer is about to
   replace this one cell, and the wait is a fraction of a second on a network the
   reader is standing next to. */
.mc-wifi-devices__table [data-name-cell].is-saving { opacity: 0.55; }

/* The two actions, side by side and equal — neither is the ordinary one. A
   venue blocks a phone about as often as it waves one through, and a page that
   made one of them the filled button would be recommending it. */
.mc-wifi-devices__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 1rem;
}
.mc-wifi-devices__actions .btn { flex: 1 1 12rem; }

.mc-wifi-devices__note {
  margin: 0.75rem 0 0;
  font-size: 0.8rem;
  color: var(--mc-muted);
}

/* --- the metrics page -------------------------------------------------------
   Five figures across the top, then the payments they add up to. The boxes wrap
   rather than scroll: a stat nobody can see is a stat that is not on the page,
   and five short numbers fit two-up on a phone without any of them shrinking. */
.mc-statrow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}
.mc-stat {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.85rem 0.9rem;
  border: 1px solid var(--mc-line);
  border-radius: 0.5rem;
  background: #fff;
}
.mc-stat__label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mc-muted);
}
/* Tabular figures for the same reason the amount column has them: five numbers
   read across a row have to line up as numbers rather than as words. */
.mc-stat__value {
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--mc-brand);
}
.mc-stat__note {
  font-size: 0.74rem;
  color: var(--mc-muted);
}
.mc-stat__note[hidden] { display: none; }

/* The filter bar: what to look for at one end, and when to look at it at the
   other. A flex row rather than two blocks, because the two controls are one
   question and belong on one line — and it WRAPS, so on a phone the menu drops
   under the box at full width instead of squeezing it to nothing. */
.mc-metricsearch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}
.mc-metricsearch .form-control { max-width: 24rem; }
/* Pushed to the far end by the space left over rather than by a width on the
   box beside it, so the search input keeps its own size and the menu keeps the
   right edge whatever is between them. `auto` survives the wrap: on a line of
   its own it is still the thing at the end of it. */
.mc-metricsearch__range {
  width: auto;
  margin-left: auto;
}

/* The reference is the one thing on the row that does something, so it is a
   button rather than a clickable cell — a keyboard has to be able to reach it —
   and it is drawn as the text it already was. */
.mc-txrow__open {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.mc-txrow__payid { font-size: 0.82rem; word-break: break-all; }
.mc-txrow__when { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Which counter a row came from, as a bar down its left edge rather than as a
   sixth column — the same trick the store's own table plays with paid and
   pending (see .mc-txrow.is-paid). */
.mc-txrow.is-booking .mc-txrow__ref { box-shadow: inset 3px 0 0 #2e9e68; }
.mc-txrow.is-store   .mc-txrow__ref { box-shadow: inset 3px 0 0 #7a5cc4; }

/* --- the traffic page -------------------------------------------------------
   The same five boxes across the top the metrics page has (.mc-statrow above is
   reused whole rather than copied), then one bar of controls, then one chart.

   THE BAR IS THE SAME KIND OF THING .mc-metricsearch IS and is deliberately not
   that class: this one carries two menus and a three-part period stepper rather
   than a box and a menu, and inheriting a layout to override half of it is how a
   shared class stops meaning anything. What it borrows is the behaviour — a flex
   row that WRAPS, so on a phone the stepper drops under the menus at full width
   instead of squeezing them to nothing. */
.mc-trafficbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.mc-trafficbar__label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mc-muted);
}
.mc-trafficbar__select { width: auto; }

/* The period and its two chevrons, held together so a wrap never leaves a
   chevron on one line and the week it steps on another. */
.mc-trafficbar__period {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
/* Square, so the two chevrons are the same object at either end of the label
   whatever is written between them. */
.mc-trafficbar__step {
  min-width: 2.25rem;
  padding: 0.25rem 0.5rem;
  line-height: 1.4;
}
.mc-trafficbar__now {
  min-width: 11rem;
  text-align: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.mc-trafficbar__current[hidden] { display: none; }

/* The chart's own box. A minimum height so the card does not collapse and then
   jump when the library lands — the page is drawn before ApexCharts has
   finished being fetched, and a section that changes height under the reader is
   the whole reason lazy loading gets a bad name. */
.mc-trafficchart {
  min-height: 360px;
  margin-bottom: 0.5rem;
}

/* The tooltip is the library's own box with our words in it (see
   traffic_controller#tooltipFor): the box, its border and its shadow are
   ApexCharts', and what is set here is the type and the little two-column table
   inside it.

       Thursday
       Income:    ₱4,230 (+100%)
       Average:   ₱2,115

   A GRID RATHER THAN SPACES OR A <table>. The two values have to start at the
   same x or the comparison the whole chart exists for is a thing the eye has to
   hunt for, and "Income" and "Bookings Made" are not the same width — so the
   label column is `auto` and sizes itself to whichever measure is being drawn,
   and both values line up behind it whatever that turns out to be. */
.mc-charttip {
  padding: 0.5rem 0.7rem;
  font-size: 0.82rem;
  line-height: 1.45;
}
.mc-charttip__title {
  font-weight: 700;
  margin-bottom: 0.15rem;
}
.mc-charttip__rows {
  display: grid;
  grid-template-columns: auto auto;
  column-gap: 0.75rem;
  row-gap: 0.05rem;
  margin: 0;
}
/* dt and dd both, because Bootstrap's own reset gives one of them a weight and
   the other a bottom margin, and a definition list is what this is. */
.mc-charttip__label {
  margin: 0;
  font-weight: 500;
  color: var(--mc-muted);
}
.mc-charttip__value {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The comparison against the all-time average, and it is DELIBERATELY SMALLER
   than the number it follows. The value is the fact; the percentage is a
   remark about it, and a remark set at the same size as its subject reads as a
   second number to be compared against a third.

   The two colours are this app's own — #157347 is the notice toast's accent and
   #b42318 is the alert's (see .mc-toast--notice / --alert), which is the same
   pair the desk already reads "that worked" and "that did not" in. Nought is
   grey rather than green: a bucket that landed exactly on its average has not
   done well, it has done usual. */
.mc-charttip__delta {
  font-size: 0.8em;
  font-weight: 600;
}
.mc-charttip__delta--up { color: #157347; }
.mc-charttip__delta--down { color: #b42318; }
.mc-charttip__delta--flat {
  color: var(--mc-muted);
  font-weight: 500;
}

/* This device, in or out of the venue's own figures. A footnote rather than a
   control on the bar: it is set once per device and then never again. */
.mc-trafficopt { margin-top: 1rem; }
.mc-trafficopt__switch {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  cursor: pointer;
}
.mc-trafficopt__note {
  margin: 0.4rem 0 0;
  font-size: 0.8rem;
  color: var(--mc-muted);
}

/* --- the About Us hero ------------------------------------------------------
   THE WHITE MAT ROUND THE PICTURES, which is the poster by the courts said
   again. The policies page hangs its sheet in a thin white frame on this same
   photographic backdrop (see .mc-well, and #court-policies-page's 1.25rem
   top override), and these two pages are the venue's two pieces of signage: a
   reader moving between them should be looking at the same object twice. So the
   numbers here are the well's own and are not eyeballed — #fff, the same
   hairline, the same 12px corner, the same shadow, and 1.25rem of white all
   round. .mc-well itself is deliberately NOT reused: its 4em of top padding and
   its --mc-grid-footer plumbing are the calendar's furniture, and inheriting
   them to throw two of them away is how a shared class stops meaning anything.

   THIS IS THE OUTSIDE OF THE CARD AND ONLY THE OUTSIDE, which is the whole of
   the distinction. What was refused was white INSIDE the card — a mat between
   the frame and the photographs, framing each picture in its own margin — and
   that is still refused: below this rule the pictures reach every edge of the
   box they are in, with no padding and no gutter between the panel and the
   picture. White between the card and the page is a different thing entirely,
   and it is what makes this read as a print on a wall rather than as a browser
   window with a photograph in it. */
.mc-about-frame {
  background: #fff;
  border: 1px solid var(--mc-line);
  border-radius: 12px;
  padding: 1.25rem;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}

/* ONE BOX, THREE LAYERS. The photographs fill the whole of it and the panel is a
   VEIL over them, not a column beside them — so the left of this band is the
   same picture, dimmed, with white type on it. That is the venue's own spec, and
   the reference image (testa.png) is a layout guide to it: the proportions, the
   chevrons and the dots come from there, the flat black does not.

   No border and no shadow of its own: the frame outside it carries the card edge
   now, and two hairlines 1.25rem apart would be the same card drawn twice. The
   corner is 8px against the frame's 12px, so the inner clip sits just inside the
   outer curve and the two nest rather than crossing.

   `isolation` gives the layers a stacking context of their own, so nothing on
   this card can be climbed over by a z-index somewhere else in a 4200-line
   stylesheet. */
.mc-about-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 8px;
  background: #000;             /* what shows while the first frame decodes */
  /* Room at the foot for the control row, so the chevrons and the dots never
     land on the type. On a desk they move to the right-hand half instead and
     this goes away — see the media query below. */
  padding-bottom: 3.5rem;
}

/* Under everything, and across ALL of it. */
.mc-about-hero__slides { position: absolute; inset: 0; z-index: 0; }

.mc-about-hero__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;            /* mixed orientations, and the venue adds its
                                   own: most of these photographs are portrait
                                   and are cropped to their middle third by this.
                                   The alternatives are black bars, which look
                                   broken, and a picture floating in a box, which
                                   looks unfinished. */
  object-position: center;
  opacity: 0;
  transition: opacity 900ms ease;
}
.mc-about-hero__slide.is-current { opacity: 1; }

/* The veil. 0.72 here because on a phone it is over the WHOLE picture and there
   is no undimmed half to balance it.

   NEITHER OPACITY IS A TASTE. White type over a see-through layer is only as
   readable as the brightest thing that can ever be behind it, and these are
   outdoor courts in Philippine sunlight — pale concrete, white line paint, blown
   sky — so both numbers are chosen for a worst case of pure #fff underneath
   rather than for the photographs as they happen to look today. Composited over
   white, 0.66 gives 7.3:1 against white text and 0.72 gives 9.2:1, so the type
   clears WCAG AAA in either case; 0.55 would only scrape AA. If the picture
   needs to be more present the answer is a better crop, never thinner ink. */
.mc-about-hero__panel {
  position: relative;
  z-index: 1;
  width: 100%;
  padding: 2rem 1.5rem;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
}

/* The controls layer. On a phone it is the reserved strip at the foot; on a desk
   it becomes the undimmed half of the picture. It passes clicks through to
   nothing — only the buttons inside it are pressable, because the layer covers
   more than half the photograph and would otherwise swallow every one. */
.mc-about-hero__controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3.5rem;
  z-index: 2;
  pointer-events: none;
}
.mc-about-hero__controls button { pointer-events: auto; }

/* Bare glyphs at the vertical midline, as the reference draws them. No disc
   behind them: a plate over a photograph is chrome the picture has to make room
   for, and these have to be legible over any picture the venue drops in, which a
   shadow does and a fixed-colour plate does not. */
.mc-about-hero__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  padding: 0.35rem 0.6rem;
  border: 0;
  background: none;
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75);
}
.mc-about-hero__arrow.is-prev { left: 0.25rem; }
.mc-about-hero__arrow.is-next { right: 0.25rem; }
.mc-about-hero__arrow:hover { color: var(--mc-open-hover); }
.mc-about-hero__arrow:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  border-radius: 6px;
}

/* Where in the run this picture is. Along the bottom of the photograph itself —
   no strip, band or gutter is made for these, because a strip is a region the
   photograph does not fill, which is the white-mat problem in another colour. */
.mc-about-hero__dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.9rem;
  display: flex;
  justify-content: center;
  gap: 0.5rem;
}

/* White filled, 45% white hollow — not mahogany. --mc-brand (#7b3f21) is a dark
   colour and would disappear into the dark half of a photograph, which is the
   one place a "you are here" mark must never vanish. White on a shadow is the
   only pair that holds over every frame, including ones nobody has taken yet. */
.mc-about-hero__dot {
  width: 0.6rem;
  height: 0.6rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.45);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
  transition: background 0.15s ease;
}
.mc-about-hero__dot.is-current { background: #fff; }
.mc-about-hero__dot:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* The type on the veil. --mc-muted is deliberately not used anywhere in here: it
   is a grey mixed for white backgrounds (see the top of this file), and over a
   dark veil it is neither one thing nor the other. Secondary text is white at
   82%, which over the desktop veil still lands near 6:1 — comfortably past AA
   for body copy, and a floor rather than a preference. */
.mc-about__title {
  margin: 0 0 1.25rem;
  font-size: 2.1rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.01em;
  /* In the stylesheet and not in the template: the venue's name is a proper noun
     in the database, and capitals typed into the markup are what makes a screen
     reader spell it out letter by letter. The same rule the booking page's
     panel headings keep. */
  text-transform: uppercase;
  color: #fff;
}

.mc-about__section {
  margin: 1.5rem 0 0.6rem;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
}
.mc-about__section:first-of-type { margin-top: 0; }

.mc-about__body {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.82);
}

.mc-about__links {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: 0.92rem;
  color: rgba(255, 255, 255, 0.82);
}
.mc-about__links li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}
/* A fixed column for the marks, so the four labels line up down one edge even
   though the glyphs are different widths. */
.mc-about__mark {
  flex: 0 0 1.1rem;
  text-align: center;
  font-style: normal;
}
.mc-about__label { flex: 0 0 8.5rem; color: rgba(255, 255, 255, 0.82); }

.mc-about__rates {
  margin: 0;
  display: grid;
  grid-template-columns: max-content auto;
  gap: 0.35rem 1.25rem;
  font-size: 0.92rem;
}
.mc-about__rates dt { color: rgba(255, 255, 255, 0.82); font-weight: 400; }
.mc-about__rates dd { margin: 0; color: #fff; font-weight: 600; }

.mc-about__credit {
  margin: 1.5rem 0 0;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.82);
}

/* Every link on the veil is white and underlined. A coloured link over a
   photograph is a colour competing with whatever is behind it; an underline is
   not. */
.mc-about-hero__panel a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.mc-about-hero__panel a:hover,
.mc-about-hero__panel a:focus { color: var(--mc-open-hover); }

/* The "← Back to Main" that sat in the white band at the foot of both signage
   cards was styled here, by one rule, so the two pages went on reading as the
   same object hung twice. Both links are gone: the bar over them carries a
   Schedule link on every page at every width, and a second way home at the foot
   of a card somebody has to scroll to the end of is the harder one to find. */

/* A desk splits it down the middle, which is the reference. */
@media (min-width: 992px) {
  .mc-about-hero { min-height: 34rem; display: flex; padding-bottom: 0; }

  /* 36% — narrowed from the reference's 44% by the venue's own eye, to give the
     photograph more of the band. `min-width` stops the paragraph setting to a
     column too narrow to read at the awkward widths just past the breakpoint.
     0.66 rather than the phone's 0.72 — the veil is over a third of the picture
     here, with an undimmed side beside it to carry the composition. */
  .mc-about-hero__panel {
    width: 36%;
    min-width: 22rem;
    padding: 3rem 2.5rem;
    background: rgba(0, 0, 0, 0.66);
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* THE CONTROLS MOVE TO THE UNDIMMED HALF. Inset from the left by exactly the
     panel's width, so the midline the chevrons sit on and the line the dots run
     along belong to the part of the picture nothing is covering. The two 36%s
     have to agree; they are the only pair of numbers in this block that do. */
  .mc-about-hero__controls { top: 0; left: 36%; height: auto; }
}

/* The other half of the reduced-motion answer, and it matters on its own: the
   controller reads matchMedia once, at connect, so a reader who flips the OS
   setting mid-visit still has a live timer. With the transition gone the worst
   that happens is an instant swap rather than a fade. The chevrons and the dots
   are deliberately NOT touched here — reduced motion is a request not to be
   moved at, not a request to be denied the controls. */
@media (prefers-reduced-motion: reduce) {
  .mc-about-hero__slide { transition: none; }
}
