/* NØK Basketball slot board.
   Art direction brief: knowledge/decisions/2026-09-17-slot-board-art-direction-brief.md

   Thesis: the week is a setting-out drawing and claiming hours means dimensioning them.
   Constraint: no photography, and THE ONLY CURVE IN THIS FILE IS THE Ø. Every other form
   is orthogonal. If you are about to add a border-radius, you are breaking the brief.
   Palette logic: CAD plot colours darkened until they clear 3:1 on white with hue kept,
   which is what tools/make_sheet.py print_legible() already does to real drawings.
   Redline is the only chroma and it is banned from decoration. */

:root {
  --ink: #14161A;          /* the drawing: type and marks */
  --sheet: #EFEFEA;        /* paper ground, pushed cool to stay clear of cream */
  --sheet-lift: #F6F6F2;   /* raised paper, for the rail */
  --graphite: #6B7078;     /* construction and extension lines, secondary dimensions */
  /* Same hue, darkened until small text clears WCAG AA 4.5:1 on the sheet. The line
     colour only has to clear 3:1 as a graphical object, which is the same split
     tools/make_sheet.py print_legible() makes between linework and lettering.
     Measured: #6B7078 gives 4.21:1 on --sheet and fails. This gives 5.03:1. */
  --graphite-text: #5F646B;
  --construction: #D8D9D2; /* the grid itself: must recede under the claims */
  --construction-2: #E4E4DE;
  --redline: #C1121F;      /* CONTENTION ONLY. Never decoration. */
  /* Priority. Mark, 2026-09-18: must-have and second-choice times colour coded.
     Both clear 3:1 against --sheet as graphics (WCAG 2.1 1.4.11). */
  --must: #1F4FB8;         /* must have: blueprint blue */
  --must-tint: #D9E2F4;
  --backup: #A35F00;       /* second choice: amber */
  --backup-tint: #F4E4C8;

  --rail: 288px;
  --hourcol: 52px;
  /* A row is a HALF HOUR, so it is no longer an hour tall. 32 rows at 30px is a 960px
     grid, which is the honest cost of 30-minute resolution: the alternative is a grid
     that fits the screen and cannot be read. Kept at 30 rather than halved to 19
     because the dimension figure inside a single-slot run is 9.5px type and needs the
     room, and renderDims() silently drops any run shorter than 14px. */
  --cell-h: 30px;

  --draw: cubic-bezier(.2, .8, .2, 1);
  --press: cubic-bezier(.34, 1.56, .64, 1);

  --mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;
  --body: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --display: 'Bricolage Grotesque', var(--body);
}

* { box-sizing: border-box; }

/* Any element carrying its own `display` rule beats the user-agent rule that hides
   [hidden], so setting the attribute from JS silently did nothing and the lockbar
   stayed on screen after the identity gate opened. This makes `hidden` mean hidden
   for every element on both pages, not just the one that was caught. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  background: var(--sheet);
  color: var(--ink);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

body { min-height: 100vh; }

/* ---------- type ---------- */

h1, h2, h3, .display {
  font-family: var(--display);
  font-variation-settings: 'wdth' 88;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.02;
  margin: 0;
}

h1 { font-size: clamp(30px, 4.4vw, 58px); }
h2 { font-size: 21px; font-variation-settings: 'wdth' 92; }

.eyebrow {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--graphite-text);
}

.num, .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* The wordmark. The Ø is a rim seen on an angle, and in engineering the same glyph
   means diameter. It is the one round thing in the system. */
.mark {
  font-family: var(--display);
  font-variation-settings: 'wdth' 100;
  font-weight: 800;
  font-size: 26px;
  letter-spacing: 0.04em;
  display: flex;
  align-items: baseline;
  gap: 1px;
}
.mark .o {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 0.78em;
  height: 0.78em;
  border: 2.5px solid var(--ink);
  border-radius: 50%; /* THE ONLY CURVE. See the header note. */
  transform: translateY(0.02em);
}
.mark .o::after {
  content: '';
  position: absolute;
  width: 128%;
  height: 2.5px;
  background: var(--ink);
  transform: rotate(-38deg);
}

/* ---------- sheet shell ---------- */

.sheet {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  min-height: 100vh;
}

/* ISO 5457 puts the title block bottom right on paper. On a screen the persistent
   rail is its equivalent, and it carries the running claim total. */
.rail {
  border-right: 1px solid var(--ink);
  background: var(--sheet-lift);
  padding: 22px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* The rail used to be `position: sticky` capped at 100vh with `overflow-y: auto`. At
   1280x900 its content runs to 1639px, so it clipped mid-field with no visible
   scrollbar and the phone, email and send button looked like they did not exist.
   A sticky box taller than the viewport is worse than a static one: top:0 pins the top
   forever, so its bottom is never reachable at all.
   So the rail scrolls with the page - one scrollbar, nothing hidden.

   AND THE TITLE BLOCK NO LONGER PINS EITHER. Measured 2026-09-18 at 1280x900: it is
   626px tall and the identity form is the next thing in the same column, so past a
   scroll of 504px the pinned block travelled down over the form and printed through
   it - 15 text-on-text collisions, and the name field a club has to fill in sitting
   behind a transparent panel. A sticky element stops at the bottom of its containing
   block, which here is the whole rail, so there is no offset from the top that pins it
   beside the grid AND keeps it off the form below it. The two are contradictory in one
   column, and pinning lost.
   This is NOT from the half-hour change: shrinking the grid back to its old height
   reproduced the same 15 collisions, because the rail sets the page height, not the
   grid. The tally is still readable - the headline over the grid carries the same
   total and it stays next to what you are drawing. */
.titleblock, .railform { display: flex; flex-direction: column; gap: 18px; }
.identity, .details { display: flex; flex-direction: column; gap: 10px; }
.identity h2, .details h2 { margin-bottom: 2px; }

/* The required marker is a word, not an asterisk. An asterisk needs a legend. */
.req {
  font-family: var(--mono);
  font-size: 8.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--redline);
  margin-left: 6px;
}
.identity.is-met .req { color: var(--graphite-text); }
.opt { font-family: var(--mono); font-size: 8.5px; font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--graphite-text); margin-left: 6px; }

/* The gate, stated where the action is rather than only in a rejected request. */
.lockbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin: 12px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--redline);
  border-left: 4px solid var(--redline);
  background: var(--sheet-lift);
}
.lockbar strong { display: block; font-size: 14px; }
.lockbar .note { margin: 2px 0 0; }

/* A locked grid still READS. It is greyed and refuses input, never hidden, because the
   whole reason to fill the form in is seeing what is already gone. */
/* The dimension layer is a SIBLING of .grid, so dimming the grid alone left the
   red annotations at full strength on a greyed board. Both carry the same state. */
.gridwrap.is-locked .grid,
.gridwrap.is-locked .dimlayer { opacity: .55; }
.gridwrap.is-locked .cell { cursor: not-allowed; }
.gridwrap.is-locked .xhair { display: none; }

/* `:invalid` matches an untouched empty required field, which put a red border on the
   admin "issue a link" box before anyone had typed in it. `:user-invalid` waits until
   the field has actually been interacted with, so red still means "you got this wrong"
   rather than "you have not started yet". The club page's identity fields are the
   deliberate exception: while the gate is shut, red IS the message. */
.field input:user-invalid, .field textarea:user-invalid { border-color: var(--redline); }
.identity:not(.is-met) input:invalid { border-color: var(--redline); }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--redline);
  outline-offset: 1px;
}

.tb-rows { border-top: 1px solid var(--construction); }
.tb-row {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  border-bottom: 1px solid var(--construction);
  font-size: 12px;
}
.tb-row dt {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite-text);
  padding: 7px 8px 7px 0;
  border-right: 1px solid var(--construction);
  margin: 0;
}
.tb-row dd { margin: 0; padding: 7px 0 7px 9px; font-weight: 500; }

.tally { border-top: 2px solid var(--ink); padding-top: 14px; }
.tally-big {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 40px;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  display: block;
}
.tally-unit {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--graphite-text);
}
.tally-line {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  padding: 3px 0;
  border-bottom: 1px dotted var(--construction);
}
.tally-line span:last-child { font-weight: 600; }

/* ---------- plan area ---------- */

.plan { padding: 22px 26px 80px; min-width: 0; }

.plan-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  border-bottom: 2px solid var(--ink);
  margin-bottom: 4px;
}

.surface-toggle { display: flex; border: 1px solid var(--ink); }
.surface-toggle button {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 9px 13px;
  background: transparent;
  border: 0;
  border-right: 1px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  transition: background .12s var(--draw), color .12s var(--draw);
}
.surface-toggle button:last-child { border-right: 0; }
.surface-toggle button[aria-pressed='true'] { background: var(--ink); color: var(--sheet); }
.yourlink { margin-top: 14px; padding: 12px; border: 1px solid var(--ink); background: var(--sheet-lift); }
.yourlink .linkbox { font-family: var(--mono); font-size: 12px; word-break: break-all; margin: 6px 0 8px; }
.priority-toggle { display: flex; border: 1px solid var(--ink); margin-top: 8px; }
.priority-toggle button {
  flex: 1; min-height: 44px; padding: 0 14px; border: 0; border-right: 1px solid var(--ink);
  background: var(--sheet); color: var(--ink); font: inherit; font-size: 13px; cursor: pointer;
}
.priority-toggle button:last-child { border-right: 0; }
.priority-toggle button[data-priority='must'][aria-pressed='true'] { background: var(--must); color: var(--sheet); }
.priority-toggle button[data-priority='backup'][aria-pressed='true'] { background: var(--backup); color: var(--sheet); }
.priority-toggle button:focus-visible { outline: 2px solid var(--redline); outline-offset: 2px; }
.priority-toggle .sw { display: inline-block; width: 10px; height: 10px; margin-right: 7px; vertical-align: -1px; }
.priority-toggle .sw.must { background: var(--must-tint); border: 2px solid var(--must); }
.priority-toggle .sw.backup { background: var(--backup-tint); border: 2px solid var(--backup); }
.surface-toggle button:focus-visible { outline: 2px solid var(--redline); outline-offset: 2px; }

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

.gridwrap { position: relative; overflow-x: auto; overflow-y: visible; }

.grid {
  position: relative;
  display: grid;
  grid-template-columns: var(--hourcol) repeat(7, minmax(78px, 1fr));
  border-left: 1px solid var(--ink);
  border-top: 1px solid var(--ink);
  min-width: 620px;
}

.grid > * { border-right: 1px solid var(--construction); border-bottom: 1px solid var(--construction); }
.grid > .dayhead:last-of-type { border-right: 1px solid var(--ink); }

.corner, .dayhead, .hourhead {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite-text);
  background: var(--sheet-lift);
  display: grid;
  place-items: center;
  height: 30px;
  border-bottom: 1px solid var(--ink) !important;
}
.hourhead {
  height: var(--cell-h);
  border-bottom: 1px solid var(--construction) !important;
  border-right: 1px solid var(--ink) !important;
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  /* Flex rather than the shared grid, so "6" and "AM" sit side by side on one line.
     Stacked they would need two lines in a 30px row and crowd the rule above. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}
.hourhead b { font-weight: 600; font-size: 11.5px; letter-spacing: 0; }
/* The meridiem is a marker, not a label: it appears at 6 AM and again at 12 PM and
   nowhere else, so it has to read as subordinate to the number beside it. */
.hourhead i { font-style: normal; font-size: 8px; color: var(--graphite-text); }
.hourhead.is-half { font-size: 8.5px; color: var(--graphite); }

/* A drawing subdivides a dimension with a lighter line than the one that bounds it.
   Same job here: the rule INSIDE an hour (between :00 and :30) is dotted, the rule
   BETWEEN hours stays solid, so the eye can still find 7 o'clock at a glance on a grid
   that now has twice as many rules on it. */
.cell.is-whole { border-bottom-style: dotted; }
.hourhead.is-whole { border-bottom: 1px dotted var(--construction) !important; }
.hourhead.is-hot { color: var(--ink); background: var(--construction-2); }
.dayhead.is-hot { color: var(--ink); background: var(--construction-2); }

/* band boundaries read as construction rules, thicker where the rate changes */
.hourhead.band-edge, .cell.band-edge { border-top: 1px solid var(--graphite); }

.cell {
  position: relative;
  height: var(--cell-h);
  background: transparent;
  border: 0;
  border-right: 1px solid var(--construction);
  border-bottom: 1px solid var(--construction);
  padding: 0;
  cursor: crosshair;
  font: inherit;
  color: inherit;
  transition: background .1s var(--draw);
}
.cell:nth-child(8n+8) { border-right: 1px solid var(--ink); }
.cell:hover:not(.is-blocked):not(.is-unpriced) { background: var(--construction-2); }
.cell:focus-visible { outline: 2px solid var(--redline); outline-offset: -2px; z-index: 5; }

/* unpriced: the rate card genuinely has no number for these hours. Hatched, not guessed. */
.cell.is-unpriced {
  cursor: not-allowed;
  background: repeating-linear-gradient(
    45deg, transparent 0 5px, var(--construction) 5px 6px
  );
}

/* held by someone else: solid ink. No name, ever. */
.cell.is-blocked { cursor: not-allowed; background: var(--ink); }
.cell.is-blocked::after {
  content: 'HELD';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.14em;
  color: var(--sheet);
  opacity: .62;
}

/* mine */
.cell.is-mine { background: var(--sheet-lift); box-shadow: inset 0 0 0 1.5px var(--ink); }
.cell.is-mine.surface-full { background: var(--construction-2); }
/* Your own times: filled, colour by priority. Full floor carries a heavier edge so
   the surface still reads without the tag. */
.cell.is-mine.prio-must { background: var(--must-tint); box-shadow: inset 0 0 0 1.5px var(--must); }
.cell.is-mine.prio-backup { background: var(--backup-tint); box-shadow: inset 0 0 0 1.5px var(--backup); }
.cell.is-mine.prio-must.surface-full { box-shadow: inset 0 0 0 3px var(--must); }
.cell.is-mine.prio-backup.surface-full { box-shadow: inset 0 0 0 3px var(--backup); }
.cell.is-mine .tag {
  position: absolute;
  /* The dimension chain owns the LEFT of the cell: renderDims() draws its extension
     line 8px in and its figure at left:4px. Two earlier attempts moved the tag along
     that same edge and both collided - at left:5px the extension line struck through
     it and "CRS" read as "C*S", and at left:15px the figure's own opaque background
     (it cuts out behind itself, the way a drawing breaks a line behind an inscription)
     clipped 3 to 4px off the bottom of the tag on every one-hour run.
     Measured 2026-09-18: 3 colliding pairs at 390px.
     So the two stop sharing an edge. Chain left, tag right. */
  right: 4px;
  top: 4px;
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.1em;
  color: var(--ink);
  font-weight: 600;
  /* line-height 1 on both the tag and the +N badge. At the inherited 1.5 the tag is
     12.75px tall from top:4 and the badge 13.5px tall from bottom:3, which in a 30px
     half-hour row overlap by 4px - measured 2026-09-18 on every claimed cell another
     club also wants. At 1 each is about 9px and they sit 6px apart. */
  line-height: 1;
}

/* Other clubs: an OUTLINE only, never a fill and never a name. Solid = somebody must
   have it; dashed = only a second choice. Inset so it sits inside your own fill. */
.cell .ring {
  position: absolute;
  inset: 4px;
  pointer-events: none;
  border: 2px solid var(--must);
}
.cell.others-backup .ring { border: 2px dashed var(--backup); }
/* The ring's top and right edges run exactly where the CRS tag and the +N badge sit.
   A drawing breaks a line behind an inscription, so both labels take the cell's own
   background and stack above the ring. */
.cell .tag, .cell .others { background: inherit; padding: 0 1px; z-index: 1; }
.cell .others {
  position: absolute;
  right: 4px;
  bottom: 3px;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 600;
  color: var(--redline);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.cell.is-blocked .others { color: var(--sheet); opacity: .75; }

/* one cross court left, the other is taken */
.cell.is-lastcross::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--graphite);
}

/* ---------- crosshair: the CAD snap ---------- */

.xhair {
  position: absolute;
  background: var(--redline);
  opacity: 0;
  pointer-events: none;
  z-index: 4;
  transition: opacity .1s var(--draw);
}
.xhair.v { width: 1px; top: 0; }
.xhair.h { height: 1px; left: 0; }
.gridwrap.is-tracking .xhair { opacity: .5; }

/* ---------- dimension chains: the signature ---------- */

.dimlayer { position: absolute; inset: 0; pointer-events: none; z-index: 3; }

.dim { position: absolute; }
.dim .line { position: absolute; left: 0; top: 0; bottom: 0; width: 1px; background: var(--ink); }
.dim .tick { position: absolute; left: -4px; width: 9px; height: 1px; background: var(--ink); }
.dim .tick.t { top: 0; }
.dim .tick.b { bottom: 0; }
.dim .arrow {
  position: absolute;
  left: -3px;
  width: 0; height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
}
.dim .arrow.t { top: 1px; border-bottom: 6px solid var(--ink); }
.dim .arrow.b { bottom: 1px; border-top: 6px solid var(--ink); }
.dim .fig {
  position: absolute;
  left: 4px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  background: var(--sheet-lift);
  padding: 1px 3px;
  color: var(--ink);
}

/* the stacked redline chain: two claims dimensioned on one feature, which is
   exactly how a real drawing shows a conflict */
.dim.redline .line, .dim.redline .tick { background: var(--redline); }
.dim.redline .arrow.t { border-bottom-color: var(--redline); }
.dim.redline .arrow.b { border-top-color: var(--redline); }
.dim.redline .fig { color: var(--redline); }

/* draw-in animation, in order: line, then ticks, then arrowheads, then the figure */
@media (prefers-reduced-motion: no-preference) {
  .dim .line { animation: drawline .34s var(--draw) both; transform-origin: top; }
  .dim .tick { animation: fadein .18s var(--draw) .3s both; }
  .dim .arrow { animation: fadein .16s var(--draw) .42s both; }
  .dim .fig { animation: figin .26s var(--press) .5s both; }
}
@keyframes drawline { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
@keyframes figin { from { opacity: 0; transform: translateY(-50%) translateX(-3px); } to { opacity: 1; transform: translateY(-50%) translateX(0); } }

/* ---------- legend + bands ---------- */

.bands { margin-top: 20px; display: flex; flex-wrap: wrap; gap: 0; border: 1px solid var(--ink); }
.band {
  flex: 1 1 150px;
  padding: 11px 13px;
  border-right: 1px solid var(--construction);
}
.band:last-child { border-right: 0; }
.band .t {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--graphite-text);
  display: block;
  margin-bottom: 5px;
}
.band .p { font-family: var(--mono); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.band .p b { font-weight: 600; }
.band.unpriced .p { color: var(--redline); }
/* The one line per tier that says how it is charged, in words rather than a figure. */
.band .p.pitch { margin-top: 6px; font-family: var(--body); font-size: 12.5px; color: var(--graphite-text); }

.legend {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite-text);
}
.legend i { display: inline-block; width: 13px; height: 13px; margin-right: 6px; vertical-align: -2px; border: 1px solid var(--graphite); }
.legend i.mine { background: var(--sheet-lift); box-shadow: inset 0 0 0 1.5px var(--ink); border-color: var(--ink); }
.legend i.held { background: var(--ink); border-color: var(--ink); }
.legend i.mine { background: var(--must-tint); box-shadow: inset 0 0 0 1.5px var(--must); border-color: var(--must); }
.legend i.mine2 { background: var(--backup-tint); box-shadow: inset 0 0 0 1.5px var(--backup); border-color: var(--backup); }
.legend i.cont { background: var(--sheet); border: 2px solid var(--must); }
.legend .legend-num { font-family: var(--mono); font-size: 10px; color: var(--redline); margin-right: 6px; }
.legend i.lastx { background: linear-gradient(transparent 6px, var(--graphite) 6px 7px, transparent 7px); }
.legend i.dimk { border: 0; border-left: 1px solid var(--ink); width: 6px; }
.bands-title { margin: 22px 0 10px; font-size: 17px; }
.legend i.cont2 { background: var(--sheet); border: 2px dashed var(--backup); }
/* Before a club sends its request there are no other clubs on the board, so the five
   entries that describe them would be a key to colours that are not there. */
.legend.hide-others .oc { display: none; }

/* The clash readout. Same frame as .lockbar so the page has one "read this" treatment
   rather than two, and keyed off the redline because that is already what this drawing
   uses for another club's interest. */
.overlaps {
  margin: 12px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--redline);
  border-left: 4px solid var(--redline);
  background: var(--sheet-lift);
}
.overlaps.is-clear { border-color: var(--graphite); border-left-color: var(--graphite); }
.overlaps strong { display: block; font-size: 14px; }
.overlaps ul { margin: 8px 0 0; padding: 0; list-style: none; }
.overlaps li {
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--ink);
}
.overlaps .note { margin: 8px 0 0; }

/* ---------- forms + buttons ---------- */

.field { display: block; margin-bottom: 11px; }
.field span {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite-text);
  margin-bottom: 4px;
}
.field input, .field textarea, .field select {
  width: 100%;
  font: inherit;
  font-size: 14px;
  padding: 8px 9px;
  background: var(--sheet);
  color: var(--ink);
  border: 1px solid var(--graphite);
  border-radius: 0;
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: 0;
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}
.field textarea { min-height: 62px; resize: vertical; }

.btn {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  padding: 12px 16px;
  background: var(--ink);
  color: var(--sheet);
  border: 1px solid var(--ink);
  border-radius: 0;
  cursor: pointer;
  box-shadow: 3px 3px 0 var(--graphite);
  transition: transform .09s var(--press), box-shadow .09s var(--press), background .12s var(--draw);
}
.btn:hover { background: #22262C; }
.btn:active { transform: translate(3px, 3px); box-shadow: 0 0 0 var(--graphite); }
.btn:focus-visible { outline: 2px solid var(--redline); outline-offset: 2px; }
.btn[disabled] { opacity: .4; cursor: not-allowed; box-shadow: none; }
.btn.ghost { background: transparent; color: var(--ink); box-shadow: 2px 2px 0 var(--construction); }
.btn.wide { width: 100%; }
.btn.danger { background: var(--redline); border-color: var(--redline); }

.note {
  font-size: 12.5px;
  color: var(--graphite-text);
  border-left: 2px solid var(--construction);
  padding-left: 10px;
  margin: 12px 0 0;
}
.note.warn { border-left-color: var(--redline); color: var(--ink); }

.toast {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%) translateY(12px);
  background: var(--ink);
  color: var(--sheet);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  padding: 11px 16px;
  z-index: 50;
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s var(--draw), transform .18s var(--draw);
  max-width: min(520px, 90vw);
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.err { background: var(--redline); }

.status-chip {
  display: inline-block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 3px 7px;
  border: 1px solid currentColor;
}
.status-chip.requested { color: var(--graphite-text); }
.status-chip.anchor { color: var(--sheet); background: var(--ink); border-color: var(--ink); }
.status-chip.awarded { color: var(--sheet); background: var(--ink); border-color: var(--ink); }
.status-chip.clash { color: var(--sheet); background: var(--redline); border-color: var(--redline); }

/* ---------- tables (admin) ---------- */

/* A table has a minimum width its content will not go below, so on a phone these pushed
   the whole admin page sideways: the BODY scrolled horizontally and the title block and
   headings drifted off screen with it. Each table gets its own scroller instead, the
   same treatment the week grid already has. `display: block` is what makes a <table>
   able to scroll at all. */
.tscroll { overflow-x: auto; }
table.plain { width: 100%; border-collapse: collapse; font-size: 13px; }
@media (max-width: 900px) {
  /* Wide enough that every column still carries a readable word. At 460px the columns
     divided down far enough that the link cell rendered the URL three characters to a
     line. Horizontal scrolling inside .tscroll is the cost, and it is the right one. */
  table.plain { min-width: 700px; }
  /* The link is for copying, never for reading. One line, clipped, with the Copy button
     under it doing the actual work. `break-all` in a narrow column is what shredded it. */
  .linkbox { word-break: normal; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}
table.plain th {
  text-align: left;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite-text);
  font-weight: 500;
  padding: 7px 9px;
  border-bottom: 1px solid var(--ink);
}
table.plain td { padding: 9px; border-bottom: 1px solid var(--construction); vertical-align: top; }
table.plain td.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px; }
.linkbox {
  font-family: var(--mono);
  font-size: 11px;
  background: var(--sheet-lift);
  border: 1px solid var(--construction);
  padding: 6px 8px;
  word-break: break-all;
  user-select: all;
}

/* ---------- provisional banner: this is not the brand ---------- */

.provisional {
  border-top: 1px solid var(--construction);
  margin-top: auto;
  padding-top: 13px;
  font-family: var(--mono);
  font-size: 9.5px;
  line-height: 1.6;
  letter-spacing: 0.04em;
  color: var(--graphite-text);
}

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  /* The grid IS the product. On a phone the rail stops being a container so its two
     halves can order around the plan: identity, then the week, then the form. A
     six-field form above the grid reads as paperwork, not a board. */
  .sheet { display: flex; flex-direction: column; }
  .rail { display: contents; }

  .railform { display: contents; }

  .titleblock {
    order: 1;
    background: var(--sheet-lift);
    border-bottom: 1px solid var(--ink);
    padding: 20px 16px 18px;
  }
  /* Identity gates claiming, so on a phone it comes BEFORE the grid. The optional
     half stays below it. Splitting the form is what lets both be true at once. */
  .identity {
    order: 2;
    background: var(--sheet-lift);
    border-bottom: 1px solid var(--ink);
    padding: 20px 16px 22px;
  }
  .plan { order: 3; padding: 18px 16px 34px; }
  .details {
    order: 4;
    background: var(--sheet-lift);
    border-top: 1px solid var(--ink);
    padding: 24px 16px 70px;
  }

  /* eight identity rows is most of a phone screen; paired, it is four */
  .tb-rows { display: grid; grid-template-columns: 1fr 1fr; border-top: 0; }
  .tb-row { border-top: 1px solid var(--construction); border-bottom: 0; }

  /* A fixed 74px label column inside a half-width cell leaves about 46px for the
     value, so "Indoor courts, Unit 6" wrapped to three lines and the NEXT row
     label rendered alongside the overflow - the labels appeared to sit on top of
     the values. Stack label over value on a phone: a 9.5px mono label costs one
     line and the value gets the whole cell. */
  .tb-row { grid-template-columns: 1fr; }
  .tb-row dt { border-right: 0; padding: 7px 0 0; }
  .tb-row dd { padding: 0 0 7px; }

  .grid { min-width: 640px; }
  .hourhead, .corner { position: sticky; left: 0; z-index: 2; }

  /* A finger is not a crosshair, and the 44px tap-target floor is a floor whatever a
     row happens to represent. Halving the slot to 30 minutes does NOT halve this: a
     22px cell would be unmissable on a plan and untappable on a phone. The cost is a
     taller grid, which scrolls; the alternative is targets a thumb cannot hit. */
  :root { --cell-h: 44px; }
  .cell { cursor: pointer; }
}

/* Soften, never kill. A blanket transition-duration reset here would also kill the
   fades this block is trying to keep. */
@media (prefers-reduced-motion: reduce) {
  .btn:active { transform: none; }
  .xhair { transition: none; }
}


/* Below this the paired columns are narrower than an address, so the pairing stops
   paying for itself and the rows run single file. */
@media (max-width: 359px) {
  .tb-rows { grid-template-columns: 1fr; }
}
/* ---------------------------------------------------------------------------
   ADMIN DEMAND CALENDAR
   Mark, 2026-10-09: "lets get this visual on there have it higher end with
   colours and what not to see if there are multiple and easy ux".

   Colour carries DEMAND COUNT, never club identity. Seven clubs would need
   seven hues, which breaks the five-colour ceiling and asks the reader to hold
   a key in their head; the question this grid answers is "where do they pile
   up", and one ramp answers it at a glance. Who is in a cell is one tap away.

   Redline stays contention-only, per the brief: it appears here if and only if
   the floor physically cannot carry the hour.
   --------------------------------------------------------------------------- */

:root {
  /* One hue, four steps. Blueprint blue, same family as --must, each step a
     visible jump from the last at 22px so the ramp reads without counting. */
  --dem-1: #E7EDF9;
  --dem-2: #CEDAF1;
  --dem-3: #AEC1E8;
  --dem-4: #8EA7DD;
  --cal-cell-h: 22px;
}

.cal-wrap { overflow-x: auto; margin-top: 14px; }

.cal {
  display: grid;
  grid-template-columns: var(--hourcol) repeat(7, minmax(78px, 1fr));
  min-width: 620px;
  border-top: 1px solid var(--ink);
  border-left: 1px solid var(--ink);
}
.cal > * {
  border-right: 1px solid var(--construction);
  border-bottom: 1px solid var(--construction);
}
.cal > .cal-day:last-of-type { border-right: 1px solid var(--ink); }

.cal-corner, .cal-day {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite-text);
  background: var(--sheet-lift);
  display: grid;
  place-items: center;
  height: 26px;
  border-bottom: 1px solid var(--ink) !important;
}

.cal-hour {
  font-family: var(--mono);
  font-size: 9.5px;
  color: var(--graphite-text);
  background: var(--sheet-lift);
  display: grid;
  place-items: center;
  height: var(--cal-cell-h);
}
/* Only the top of each clock hour is labelled. Thirty-six labels down one
   column is noise, and the half rows are still obvious from the dotted rule. */
.cal-hour.is-half { color: transparent; }
.cal-cell.is-half { border-bottom-style: dotted; }

.cal-cell {
  height: var(--cal-cell-h);
  background: none;
  border-top: 0;
  border-left: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  position: relative;
  display: grid;
  place-items: center;
}
.cal-cell:hover { background: var(--construction-2); }
.cal-cell:focus-visible { outline: 2px solid var(--redline); outline-offset: -2px; z-index: 5; }

.cal-cell.d1 { background-color: var(--dem-1); }
.cal-cell.d2 { background-color: var(--dem-2); }
.cal-cell.d3 { background-color: var(--dem-3); }
.cal-cell.d4 { background-color: var(--dem-4); }

/* The count, printed from two clubs up. One club needs no number: the tint says
   it. Two or more is the thing being looked for, so it gets a figure. */
.cal-cell .n {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
  pointer-events: none;
}

/* Over capacity. The floor cannot carry it as asked. The OUTLINE carries this, not
   the figure: redline on the darkest tint measures 2.60:1, which fails WCAG AA for
   11px text. Ink on the same tint is 7.57:1, so the count stays ink everywhere. */
.cal-cell.is-over { box-shadow: inset 0 0 0 2px var(--redline); }

/* An anchor hold LAYERS over the demand tint rather than replacing it. The first
   version replaced it, and a hold on an hour four clubs wanted came out looking the
   same as a hold on an hour nobody wanted - losing exactly the fact worth seeing.
   An award does replace it: that hour is settled and its demand is history. */
.cal-cell.is-anchor {
  background-image: repeating-linear-gradient(45deg,
    var(--graphite) 0 1px, transparent 1px 5px);
}
/* A plate under the figure, hatched cells only. Diagonals run straight through a
   numeral and 45-degree strokes through an 11px digit make it unreadable. */
.cal-cell.is-anchor .n {
  background: var(--sheet-lift);
  padding: 0 4px;
  box-shadow: 0 0 0 1px var(--sheet-lift);
}
.cal-cell.is-awarded { background-color: var(--ink); background-image: none; }
.cal-cell.is-awarded .n { color: var(--sheet); }

.cal-cell.is-sel { box-shadow: inset 0 0 0 2px var(--ink); z-index: 4; }
.cal-cell.is-sel.is-over { box-shadow: inset 0 0 0 2px var(--redline), inset 0 0 0 4px var(--ink); }

.cal-key {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  margin: 12px 0 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--graphite-text);
}
.cal-key span { display: inline-flex; align-items: center; gap: 6px; }
.cal-key i {
  width: 20px;
  height: 12px;
  border: 1px solid var(--construction);
  display: inline-block;
}
.cal-key i.k1 { background: var(--dem-1); }
.cal-key i.k2 { background: var(--dem-2); }
.cal-key i.k3 { background: var(--dem-3); }
.cal-key i.k4 { background: var(--dem-4); }
.cal-key i.ko { box-shadow: inset 0 0 0 2px var(--redline); }
.cal-key i.ka {
  background-image: repeating-linear-gradient(45deg,
    var(--graphite) 0 1px, transparent 1px 5px);
}
.cal-key i.kw { background: var(--ink); }

/* The readout under the grid. Fixed minimum height so selecting a cell never
   shifts the page under the pointer. */
.cal-detail {
  margin-top: 16px;
  border: 1px solid var(--construction);
  border-left: 2px solid var(--ink);
  background: var(--sheet-lift);
  padding: 12px 14px;
  min-height: 86px;
}
.cal-detail h3 {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 8px;
  color: var(--ink);
}
.cal-detail ul { margin: 0; padding: 0; list-style: none; }
.cal-detail li {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 5px 0;
  border-top: 1px solid var(--construction-2);
  font-size: 13px;
}
.cal-detail li:first-child { border-top: 0; }
.cal-detail li b { font-weight: 600; }
.cal-detail li .meta {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--graphite-text);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

@media (max-width: 860px) {
  .cal { min-width: 560px; grid-template-columns: 40px repeat(7, minmax(70px, 1fr)); }
}

/* The end-screen state. deadEnd() and retryScreen() add .is-ended to .sheet so the
   inputs go away with the board. The mark and the project facts stay: they cost nothing
   and keep a dead link looking like our page rather than a browser error. */
.sheet.is-ended .railform,
.sheet.is-ended .tally { display: none; }

/* WHICH floor an hour is asked for, on the face of the grid. The bar down the left edge
   of a block is the floor seen end on: full height is a full-court request, the bottom
   half is one half court, and two stacked bars are both halves asked for.

   It is on the left edge and on a pseudo element because every other channel in the cell
   is taken - background-color carries the club count, background-image carries an anchor
   hold, and inset box-shadow carries over capacity and selection. */
.cal-cell.sf-full::before,
.cal-cell.sf-half1::before,
.cal-cell.sf-half2::before {
  content: '';
  position: absolute;
  left: 0;
  width: 5px;
  background: var(--ink);
  pointer-events: none;
}
.cal-cell.sf-full::before { top: 0; bottom: 0; }
/* Bottom half, not top: a half court is the near half of the floor on a plan, and a
   bar hanging off the top of a 22px cell reads as a cut-off full bar. */
.cal-cell.sf-half1::before { bottom: 0; height: 50%; }
.cal-cell.sf-half2::before {
  top: 0;
  bottom: 0;
  background: linear-gradient(var(--ink) 0 45%, transparent 45% 55%, var(--ink) 55% 100%);
}
/* An awarded hour is filled with ink, so an ink bar on it is invisible. */
.cal-cell.is-awarded.sf-full::before,
.cal-cell.is-awarded.sf-half1::before { background: var(--sheet); }
.cal-cell.is-awarded.sf-half2::before {
  background: linear-gradient(var(--sheet) 0 45%, transparent 45% 55%, var(--sheet) 55% 100%);
}

/* The three floor swatches are miniature cells, not marks on white. A 5px bar in an
   empty 20px box read as a speck at 10px type; sitting it on the lightest demand tint
   gives it the thing it has on the grid, which is a block to be a bar on. */
.cal-key i.kfull,
.cal-key i.khalf,
.cal-key i.khalf2 { background-color: var(--dem-1); }
.cal-key i.kfull { background-image: linear-gradient(90deg, var(--ink) 0 6px, transparent 6px); }
.cal-key i.khalf {
  background-image: linear-gradient(90deg, var(--ink) 0 6px, transparent 6px);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 50%;
}
.cal-key i.khalf2 {
  background-image:
    linear-gradient(90deg, var(--ink) 0 6px, transparent 6px),
    linear-gradient(90deg, var(--ink) 0 6px, transparent 6px);
  background-repeat: no-repeat, no-repeat;
  background-position: 0 0, 0 100%;
  background-size: 100% 42%, 100% 42%;
}

/* ---------- the pick bar: what a tap means, kept on screen ---------- */

/* Sticky at the top of the viewport, so the mode is readable while the grid is being
   tapped. z-index 30 clears everything in the grid (highest is 5) and stays under the
   toast at 50. */
.pickbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--sheet);
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  padding: 10px 0 12px;
  margin-top: 20px;
}
.pickbar-label {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite-text);
  margin-bottom: 8px;
}
.pickbar-sets { display: flex; gap: 10px; align-items: stretch; }
/* Both toggles share the row evenly and neither carries the old stacked margin. */
.pickbar-sets > .surface-toggle,
.pickbar-sets > .priority-toggle { flex: 1; margin-top: 0; }

/* 44px minimum tap target, per design-standards.md. The surface buttons were 34px,
   which is the one a thumb misses. */
.surface-toggle button { flex: 1; min-height: 44px; padding: 0 10px; white-space: nowrap; }
.priority-toggle button { white-space: nowrap; }

/* On a phone the two toggles stack and each goes full width, so neither label wraps
   and both stay reachable with one thumb. */
@media (max-width: 760px) {
  .pickbar-sets { flex-direction: column; gap: 8px; }
  .pickbar { padding: 10px 0; }
  .surface-toggle button, .priority-toggle button { font-size: 12px; }
}

/* The sticky bar is clamped by this box, not by the page, so it lets go at the bottom
   of the grid. Without it the bar followed the reader down over the form. */
.pickzone { position: relative; }
