/* ───────────────────────────────────────────────────────────────────────────
   DIRECTION CONTRACT — "Console"                       supersedes seed a7bde3a6

   THESIS: A modern operations console, chosen from a reference the user brought.
   It refuses the sidebar: the widest table here is fifteen columns, so horizontal
   space is what this product is short of, and navigation goes across the top.

   OWN-WORLD: Cool neutral near-black on a four-step ladder. Violet is brand and
   navigation only — never state — so the four state hues keep one meaning each.
   Rounded panels and controls, drawn icons on one stroke weight, tabular figures.

   STORY: Menu across the top, page title in real space, then the work: settings,
   then allocation, then the table that is the point of the product.

   FIRST VIEWPORT: Top bar with section switcher and page links; page title and
   who you are acting as; tabs; the table at Comfortable density.

   FORM: user-directed, from a supplied reference. Density chosen by measurement
   (Comfortable: ~8 rows per phone screen against Tight's 11 and Roomy's 6).

   FINISH: unreviewed and undocumented is unfinished; this build ends with the
   finish review, the verdict, DESIGN.md, and every shipping raster carrying its
   provenance.
   ─────────────────────────────────────────────────────────────────────────── */
:root{
  /* Dark is not a variant here. The scene is a phone at night beside the game's
     own dark UI, and a desk in the evening; a light rendition would be another
     world rather than this one in another key. Paper is handled where it is
     actually needed — the print blocks re-state everything black on white. */
  color-scheme: dark;

  /* ---- the ground: four steps, each far enough apart to separate and close
     enough not to stripe. Depth is value and a lit edge, never a drop shadow. */
  --g0:#0e0e10;         /* the page itself */
  --g1:#151517;         /* a panel standing on it */
  --g2:#1d1d20;         /* raised: table headers, inputs, controls at rest */
  --g3:#26262a;         /* hover, and the selected segment of a control */
  --line:#26262b;       /* hairline between things of the same kind */
  --line-strong:#35353c;/* structural edge: under a header row, around a scroller */

  --tx:#ededf0;         /* body */
  --tx-hi:#ffffff;      /* names, numbers, anything read first */
  --tx-lo:#9a9aa4;      /* secondary. Chosen against --g3, the DEEPEST surface it
                           ever sits on (a hovered row), not against the page: at
                           #7b8794 it read 3.8:1 there and 4.4:1 on a table header,
                           so the column labels failed the moment they were needed. */

  /* ---- colour is state, and state only ----
     Four roles, lifted from affiliation coding. If a colour appears anywhere it
     is answering "what is the status of this thing". A button is not a status,
     a link is not a status, and the current nav item is not a status — so none
     of them get any colour. That single rule is what stops the palette from
     reading as busy no matter how saturated these four are. */
  --claim:#4cc2e8;      /* claimed / present / confirmed */
  --unres:#e0b34a;      /* unresolved — gave no hours, nothing to act on yet */
  --absent:#ef7070;     /* absent / refused / over cap */
  --ok:#4ec98a;         /* completed, within target */

  /* Tinted grounds for banners and chips: the hue at low strength over a panel,
     so a filled chip is the same object as the panel it sits on. */
  --claim-bg: color-mix(in srgb, var(--claim) 15%, var(--g1));
  --unres-bg: color-mix(in srgb, var(--unres) 13%, var(--g1));
  --absent-bg: color-mix(in srgb, var(--absent) 15%, var(--g1));
  --ok-bg: color-mix(in srgb, var(--ok) 14%, var(--g1));

  /* The quiet member of the claim family. For typographic emphasis that must
     not be mistaken for a status: eyebrows, ranks, inline affordances. */
  /* Brand and navigation. Violet never means state: if it marked both "you are
     here" and "claimed", the two would be unreadable at a glance. It is a fill
     behind white text, never coloured text — as text it fails contrast on every
     surface here (4.03:1 on a panel), which is how the reference uses it too. */
  --brand:#7c5cf0;
  --brand-2:#5b3fd6;
  --brand-ink:#ffffff;
  --emph:#a48cf5;

  /* ---- legacy token names, remapped ----
     Roughly 450 references to these live across nineteen pages and their local
     stylesheets. Renaming them would mean editing every one of those files to
     change colours, which is how a restyle turns into a regression. The names
     stay; only what they resolve to changes. */
  --bg: var(--g0);
  --panel: var(--g1);
  --panel-2: var(--g2);
  --border: var(--line);
  --text: var(--tx);
  --text-muted: var(--tx-lo);
  --bronze: var(--emph);
  --bronze-ink: var(--g0);
  --moss: var(--ok);        --moss-bg: var(--ok-bg);
  --amber: var(--unres);    --amber-bg: var(--unres-bg);
  --crimson: var(--absent); --crimson-bg: var(--absent-bg);

  /* --shadow is still referenced by page-level stylesheets, so it keeps its
     name — but a drop shadow is invisible dark-on-dark, which is most of why
     the old dark mode read flat. It now carries the lit top edge instead: the
     one line that makes a dark panel read as an object rather than a hole. */
  --shadow: inset 0 1px 0 rgba(255,255,255,.05);
  --radius: 11px;       /* cards, panels, table containers */
  --radius-sm: 9px;     /* buttons, inputs, selects, segments */
  --radius-pill: 999px; /* chips and badges */
  /* The hairlines that quarter a city square into the four tiles it takes. */
  --tile-line: color-mix(in srgb, var(--tx-lo) 30%, transparent);

  /* Chart lettering is a grotesque set in caps with tracking, not a display
     face. Kept as a system stack on purpose: this is read on a phone on mobile
     data with a timer running, and a webfont on that critical path buys
     character at the cost of the one thing the page is for. */
  --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
body{
  background:var(--bg); color:var(--text); font-family:var(--font-body); font-size:14px; line-height:1.45;
  -webkit-font-smoothing:antialiased;
}
/* The browser's own surfaces belong to the design too. Left alone they ship the
   UA defaults, which are the loudest un-designed thing on a dark page. */
::selection{ background: var(--g3); color: var(--tx-hi); }
a{ color: var(--tx-hi); text-decoration-color: var(--line-strong); text-underline-offset:2px; }
a:hover{ text-decoration-color: var(--emph); }
::-webkit-scrollbar{ width:11px; height:11px; }
::-webkit-scrollbar-track{ background: var(--g0); }
::-webkit-scrollbar-thumb{ background: var(--g3); border:2px solid var(--g0); border-radius:99px; }
::-webkit-scrollbar-thumb:hover{ background: var(--line-strong); }
* { scrollbar-color: var(--g3) var(--g0); }

/* ---------- density ----------
   These pages are read on a phone, mid-battle, with a timer running. Every gap that isn't doing
   structural work is a line of information pushed below the fold, so the spacing scale here is
   deliberately tight: padding earns its place by separating things that would otherwise be
   confused for each other. Type sizes are NOT part of that trade — nothing below shrinks text. */
.wrap{ max-width:1180px; margin:0 auto; padding:18px 16px 56px; }
.wrap.narrow{ max-width:460px; padding-top:7vh; }

header.top{ display:flex; align-items:center; gap:11px; margin-bottom:14px; }
header.top .mark{ flex:none; width:34px; height:34px; color:var(--emph); }
/* Chart lettering: a grotesque in caps with tracking. The character comes from
   the setting, not from a face that would have to be downloaded first. */
header.top h1{
  font-family:var(--font-display); font-size:20px; font-weight:700; margin:0; text-wrap:balance;
  letter-spacing:.06em; text-transform:uppercase; color:var(--tx-hi);
}
/* The wordmark, where the brand IS the heading — the entry page and sign-in. It has to
   opt out of the page-title voice above it: the name is lowercase and set tight, and a
   heading here is uppercase and tracked out, so left alone it printed KSWIZARD. Set two
   points larger because lowercase at the same size reads smaller than caps beside a
   34px mark. The two-tone split matches the top bar, so the brand is one object across
   the app rather than a lockup in the bar and a plain word everywhere else. */
header.top h1.wordmark{ text-transform:none; letter-spacing:-.02em; font-size:23px; }
header.top h1.wordmark i{ color:var(--tx-lo); font-style:normal; font-weight:600; }
header.top p{ margin:2px 0 0; color:var(--text-muted); font-size:13px; }
header.top .who{ margin-left:auto; text-align:right; font-size:12px; color:var(--text-muted); }
header.top .who .name{ color:var(--tx-hi); font-weight:700; }

/* One segmented control. The selected segment is a value step, not a colour and
   not a floating card — colour on a navigation control would say "status". */
.page-nav{ display:flex; gap:2px; margin-bottom:12px; background:var(--g2); border-radius:var(--radius-sm); padding:3px; }
.page-nav a{
  flex:1; text-align:center; text-decoration:none; color:var(--text-muted); font-weight:600; font-size:13px;
  padding:7px 14px; border-radius:calc(var(--radius-sm) - 2px);
}
.page-nav a.active{ background:var(--g3); color:var(--tx-hi); box-shadow:var(--shadow); }
.page-nav a:not(.active):hover{ color:var(--tx-hi); background:color-mix(in srgb, var(--g3) 45%, transparent); }

/* Kept because it carries words a reader needs ("On this device", "New"), set
   quietly so it reads as a label on the heading rather than an accent above it. */
/* An icon sits ON the baseline of the text beside it, not above or below it.
   vertical-align:-0.14em is what puts a 24-grid glyph optically on a cap line. */
.ic{ vertical-align:-.14em; flex:none; }
button .ic, a .ic{ vertical-align:-.16em; }
.modal-close .ic{ vertical-align:middle; }

.eyebrow{
  display:inline-flex; align-items:center; gap:6px;
  font-family:var(--font-body); font-size:11px; font-weight:700; letter-spacing:.11em;
  text-transform:uppercase; color:var(--text-muted); margin:0 0 4px;
}

/* A panel is an object standing on the ground: one value step up, a hairline
   edge, and light catching its top edge. No drop shadow — dark on dark it is
   invisible, which is most of why the old dark mode read flat. */
.card{
  background:var(--panel); border:1px solid var(--border); border-radius:var(--radius);
  box-shadow:var(--shadow); padding:15px 17px; margin-bottom:14px;
}
.card > .card-head{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; margin-bottom:10px; flex-wrap:wrap; }
.card > .card-head h2{
  font-family:var(--font-display); font-size:14px; margin:0; color:var(--tx-hi);
  letter-spacing:.07em; text-transform:uppercase; font-weight:700;
}
.card > .card-head .sub{ color:var(--text-muted); font-size:12px; }

label.field{ display:flex; flex-direction:column; gap:3px; font-size:12px; color:var(--text-muted); min-width:0; }
label.field span.lbl{ font-weight:600; color:var(--text); }
/* A field is a slot cut into the panel, so it takes light on its inner top edge
   while the panel takes it on the outer one. That inversion is what makes a
   control read as something you type into rather than a tile sitting there. */
input, select, textarea{
  font:inherit; color:var(--tx-hi); background:var(--panel-2); border:1px solid var(--border);
  border-radius:var(--radius-sm); padding:6px 9px; min-width:0;
  box-shadow: inset 0 1px 0 rgba(0,0,0,.28);
}
input::placeholder, textarea::placeholder{ color:var(--text-muted); opacity:1; }
input:hover, select:hover{ border-color:var(--line-strong); }
/* Neutral, not coloured: focus is where you are, not what state something is in. */
input:focus, select:focus, textarea:focus, button:focus-visible, a:focus-visible, [tabindex]:focus-visible{
  outline:2px solid var(--tx-hi); outline-offset:1px;
}
input[type="number"]{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; text-align:right; }
input[type="checkbox"]{ width:16px; height:16px; accent-color:var(--emph); cursor:pointer; }
input[type="range"]{ accent-color:var(--emph); }
input[type="time"], input[type="date"]{ color-scheme: dark; }
input:disabled, select:disabled, button:disabled{ opacity:.5; cursor:not-allowed; }
/* A link that has nowhere to go yet. Looks like the disabled buttons beside it, and keeps its
   tooltip — which is where it says what is missing. */
a.is-disabled{ opacity:.5; cursor:not-allowed; }

/* A chip IS a status, so this is one of the few places colour belongs. Squared
   rather than pill-shaped: in this world a bounded rectangle is what a marked
   unit looks like, and the border is what says where it ends. */
.chip{
  display:inline-flex; align-items:center; gap:5px; font-size:11px; font-weight:700;
  padding:3px 10px; border-radius:var(--radius-pill); white-space:nowrap; font-variant-numeric:tabular-nums;
  border:1px solid transparent;
}
.chip.ok{ background:var(--moss-bg); color:var(--moss); border-color:color-mix(in srgb, var(--ok) 34%, transparent); }
.chip.warn{ background:var(--amber-bg); color:var(--amber); border-color:color-mix(in srgb, var(--unres) 34%, transparent); }
.chip.bad{ background:var(--crimson-bg); color:var(--crimson); border-color:color-mix(in srgb, var(--absent) 34%, transparent); }
.chip.neutral{ background:var(--panel-2); color:var(--text-muted); border-color:var(--border); }

/* Buttons carry no colour. The primary action is the brightest thing on the
   page because it is bright, not because it is blue — which leaves blue free to
   mean "claimed" and nothing else, everywhere, without exception. */
button{ font:inherit; cursor:pointer; border-radius:var(--radius-sm); border:1px solid transparent; padding:7px 14px; font-weight:600; }
button.primary{ background:linear-gradient(90deg, var(--brand-2), var(--brand)); color:var(--brand-ink); border-color:transparent; }
button.primary:hover{ filter:brightness(1.08); }
button.ghost{ background:var(--panel-2); border-color:var(--border); color:var(--text); box-shadow:var(--shadow); }
button.ghost:hover{ background:var(--g3); border-color:var(--line-strong); color:var(--tx-hi); }
button.danger-ghost{ background:var(--panel-2); border-color:var(--border); color:var(--crimson); box-shadow:var(--shadow); }
button.danger-ghost:hover{ background:var(--crimson-bg); border-color:color-mix(in srgb, var(--absent) 40%, transparent); }
button.icon-btn{ padding:5px 8px; line-height:1; }
button.block{ width:100%; }

/* Capped rather than growing without limit, so a long table scrolls inside its own box and the
   header row above can stay pinned to the top of it. Left to grow, the page itself would be the
   thing scrolling, and a sticky header inside a scroll container can only stick to that container —
   so the header would slide away and you'd be back to scrolling up to remember what a column was. */
.table-scroll{
  overflow:auto; max-height:min(70vh, 720px);
  border:1px solid var(--border); border-radius:var(--radius-sm);
}
table{ border-collapse:collapse; width:100%; font-size:13px; }
thead th{
  text-align:left; font-size:11px; letter-spacing:.05em; text-transform:uppercase; font-weight:700;
  color:var(--text-muted); background:var(--panel-2); padding:8px 11px; border-bottom:1px solid var(--border);
  white-space:nowrap;
  /* Above the frozen first column (z-index 1) so the two can cross without the column covering the
     header; the corner cell where they meet goes higher still — see .freeze-1 below. */
  position:sticky; top:0; z-index:2;
}
/* border-collapse drops the header's bottom border once it's sticky and scrolled away from the top
   edge, so draw it as a shadow that travels with the cell instead. */
thead th{ box-shadow:inset 0 -1px 0 var(--line-strong); }
tbody td{ padding:8px 11px; border-bottom:1px solid var(--border); vertical-align:middle; color:var(--tx-lo); }
tbody tr:last-child td{ border-bottom:none; }
tbody tr:hover td{ background:var(--g3); }
/* The identity column is what you are looking for when you scan; it gets the
   brightest ink on the row and everything hanging off it gets the quietest. */
tbody td:first-child{ color:var(--tx-hi); font-weight:600; }
tbody td:first-child div{ font-weight:400; color:var(--tx-lo); }
/* Numbers are the content of this product. Reading them at the same weight as
   the words around them is what made the old table something you searched
   rather than scanned. */
td.num, th.num{ text-align:right; font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
td.num{ color:var(--tx-hi); }
/* A zero is "nothing here". At full strength a screen of 0 and 0% competes with
   the six-digit numbers that are the actual answer — most of the ink on an
   allocation table was being spent saying nothing. */
td.num.zero, td.zero{ color:var(--tx-lo); opacity:.45; font-weight:400; }
/* A header sitting over a column of number INPUTS rather than bare numbers. The value lives inside
   a bordered, padded box, so its text is inset 11px (10px padding + 1px border) from the cell edge
   the header aligns to — leaving the header hanging out past every number underneath it. Pull it
   in by that much so the column reads as one straight edge. */
th.num.over-input{ padding-right:19px; }
/* Header over a column of checkboxes, which are centred in their cells. */
th.center{ text-align:center; }

.banner{
  display:flex; align-items:center; gap:8px; border-radius:var(--radius-sm); padding:8px 12px; font-size:13px;
  font-weight:600; margin-bottom:11px; border:1px solid transparent;
}
.banner.info{ background:var(--panel-2); color:var(--text-muted); border-color:var(--border); }
.banner.warn{ background:var(--amber-bg); color:var(--amber); border-color:color-mix(in srgb, var(--amber) 40%, transparent); }
.banner.bad{ background:var(--crimson-bg); color:var(--crimson); border-color:color-mix(in srgb, var(--crimson) 40%, transparent); }
.banner.ok{ background:var(--moss-bg); color:var(--moss); border-color:color-mix(in srgb, var(--moss) 40%, transparent); }

.empty-note{ color:var(--text-muted); font-size:13px; padding:8px 2px; }
footer.note{ color:var(--text-muted); font-size:12px; text-align:center; margin-top:12px; }

/* Fourteen hero chips beside an inline label left them squeezed into a narrow ragged block.
   The label gets its own line so the chips have the full width. */
.filter-stack{ display:flex; flex-direction:column; gap:5px; margin:0 0 9px; }

/* Keep the identity column pinned while the rest of a wide row scrolls under it — by the time you
   reach the right-hand end of a joiner row you've otherwise lost track of whose row it is. */
.table-scroll table.freeze-1 th:nth-child(1),
.table-scroll table.freeze-1 td:nth-child(1){
  position:sticky; left:0; z-index:1;
  background:var(--panel);
  /* A hairline marking where the frozen part ends, so the scroll reads as intentional. */
  box-shadow:1px 0 0 var(--border);
}
/* The corner: frozen left AND pinned top, so it has to sit above both and carry both edges — the
   column's right-hand hairline and the header's underline. */
.table-scroll table.freeze-1 thead th:nth-child(1){
  z-index:3; background:var(--panel-2);
  box-shadow:1px 0 0 var(--border), inset 0 -1px 0 var(--border);
}

/* Nobody picked any hours: colour the boxes themselves rather than adding a note beside them. The
   thing that's wrong IS the row of empty boxes, so marking them directly reads faster than a symbol
   the eye has to associate back. */
.hour-boxes{ display:inline-block; }
.hour-boxes.no-hours label,
.hgroup.no-hours label{ color:var(--crimson); }
.hgroup.no-hours label{ border-color:var(--crimson); }
.hour-boxes.no-hours input[type="checkbox"],
.hgroup.no-hours input[type="checkbox"]{
  outline:2px solid var(--crimson); outline-offset:1px; accent-color:var(--crimson);
}

/* Grid, not flex-wrap: flex-wrap packs each row independently, so rows of differently-sized
   labels (e.g. "Eric" vs "Yeonwoo") drift out of alignment with each other. A grid keeps every
   cell the same width, so checkboxes line up into clean columns regardless of label length. */
.hgroup{ display:grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap:6px; }
.hgroup label{
  display:flex; align-items:center; gap:6px; background:var(--panel-2); border:1px solid var(--border);
  border-radius:var(--radius-sm); padding:6px 10px; font-size:13px; cursor:pointer;
}
.hgroup input[type="checkbox"]:checked + span{ font-weight:700; color:var(--bronze); }

@media (max-width:640px){
  /* Tighter again on a phone, where the fold is roughly six inches down. Padding goes; nothing
     that carries meaning does — type sizes, line-height and tap targets all stay put. */
  .wrap{ padding:12px 10px 44px; }
  .card{ padding:11px 12px; margin-bottom:10px; }
  .card > .card-head{ margin-bottom:8px; }
  .page-nav{ margin-bottom:10px; }
  .banner{ padding:8px 11px; margin-bottom:9px; }

  /* The account block was competing with the title for the same line and squeezing it into three
     wrapped words. Give it its own line instead. */
  header.top{ flex-wrap:wrap; row-gap:10px; }
  /* Let the title block shrink so it stays beside the mark instead of being pushed under it. */
  header.top > div:not(.who){ flex:1 1 0; min-width:0; }
  header.top .mark{ width:32px; height:32px; }
  header.top h1{ font-size:21px; }
  header.top h1.wordmark{ font-size:21px; }
  header.top p{ font-size:12px; }
  header.top .who{ margin-left:0; width:100%; text-align:left; }

  /* Fixed-width form fields don't divide neatly into a phone; let them share the row instead. */
  .settings-group{ width:100%; }
  .settings-group .field{ flex:1 1 140px; width:auto; }
  .settings-group .field.narrow{ flex:1 1 0; min-width:74px; width:auto; }

  .modal-panel{ padding:18px 14px 20px; }
  .page-nav a{ padding:10px 8px; font-size:13px; }
}


/* A row of filter chips, and the chips themselves. Both pages that use these had an identical
   copy in their own <style>, which sat after this sheet and silently won every specificity tie —
   including the touch sizing below. One definition, one place to change it.
   Not scoped to .filter-bar: a chip should look like a chip wherever it sits. */
.filter-bar{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin:8px 0 10px; }
.chip-toggle{
  display:inline-flex; align-items:center; gap:5px; font-size:12px; padding:5px 11px; border-radius:99px;
  border:1px solid var(--border); background:var(--panel-2); color:var(--text-muted); cursor:pointer;
}
.chip-toggle.on{ background:var(--g3); border-color:var(--line-strong); color:var(--tx-hi); font-weight:700; }

/* An editable list of chips — alliances, heroes. Defined here rather than in each page that uses
   one: a page-level copy of this sits after the linked sheet in source order, which quietly beat
   any rule here that tried to adjust it. */
.chip-editor{ display:flex; flex-wrap:wrap; gap:8px; }
.chip-editor .chip{ padding-right:6px; }
.chip-editor .chip button{
  background:none; border:none; color:inherit; cursor:pointer;
  font-size:13px; line-height:1; padding:0 0 0 6px; opacity:.7;
}
.chip-editor .chip button:hover{ opacity:1; }

/* ---------- touch ----------
   Fingers are not cursors. On a coarse pointer the small controls — filter chips, the little "×"
   on a chip, the compact ghost buttons — grow to something you can actually hit without zooming.
   Scoped to (pointer:coarse) so a mouse still gets the tighter, denser layout it can aim at. */
@media (pointer: coarse){
  .chip-toggle{ min-height:32px; padding:7px 13px; }
  /* The remove control on an editable chip is a 14px glyph; give it real area without making the
     chip itself any taller, by letting the tap zone overhang the text. */
  .chip-editor .chip{ position:relative; min-height:30px; }
  .chip-editor .chip button{
    position:relative; padding:10px 4px 10px 10px; margin:-10px -2px -10px 0; font-size:15px;
  }
  button.ghost, button.danger-ghost{ min-height:36px; }
  /* Anything relying on the shared button padding is already 36px+; these are the exceptions. */
  .filter-bar button, .add-row-bar button{ min-height:34px; }
  /* The inline editors inside a table — a joiner's alliance, their tiers, their march — are the
     controls you actually spend time in on a phone, and they were the smallest things on the page. */
  table input, table select{ min-height:34px; }
  table button{ min-height:30px; }
}

/* ---------- the city map ----------
   Shared by the editor at /cities.html and the public link at /map.html. Kept here rather than in
   each page: a page-level copy sits after this sheet in source order and quietly wins every
   specificity tie, which is how the touch rules got defeated the last time.

   The map is built square and turned 45 degrees. A city is 2x2 tiles, so every square is quartered
   by two hairlines — that is one city, not one tile. */
.maparea{ overflow:auto; border:1px solid var(--border); border-radius:var(--radius); background:var(--panel-2);
          padding:10px; -webkit-overflow-scrolling:touch; }
/* The rotated grid's bounding box is bigger than the grid; the stage is sized in JS to hold it. */
.stage{ position:relative; margin:0 auto; }
.city-grid{
  --city:30px;
  position:absolute; top:50%; left:50%;
  transform:translate(-50%, -50%) rotate(45deg);
  /* No gutter between squares. The ring is ground, not a rack of pigeonholes: a city half a square
     off has to land ON the join, and a gap is what turns that into a city sitting in a gutter. */
  display:grid; gap:0;
  grid-template-columns:repeat(14, var(--city));
  grid-auto-rows:var(--city);
}
/* Squares are not framed. A frame reads as a wall — this many boxes, one city in each — and the
   whole point of the offsets is that a city goes where it actually landed, half across its
   neighbour if that is where it is. What is left is the tile mesh: the two hairlines that quarter
   every square into the four tiles a city covers, running unbroken across the ring so half a square
   is something the eye can measure rather than infer. */
.city-grid .slot{
  border:none; border-radius:0; background-color:var(--panel);
  font:inherit; color:var(--text); cursor:pointer; padding:0; overflow:hidden;
  display:flex; align-items:center; justify-content:center; line-height:1;
  background-image:
    linear-gradient(to right, transparent calc(50% - .5px), var(--tile-line) calc(50% - .5px), var(--tile-line) calc(50% + .5px), transparent calc(50% + .5px)),
    linear-gradient(to bottom, transparent calc(50% - .5px), var(--tile-line) calc(50% - .5px), var(--tile-line) calc(50% + .5px), transparent calc(50% + .5px));
}
/* Whole names, not four letters of one. Counter-rotated, the label runs along the square's
   DIAGONAL, which is 1.41 times its side — so there is more room across a tile than its width
   suggests, and a name only has to be sized to that strip to fit inside it whole.
   --len is the character count, set per tile. The first term is the cap a short name never exceeds;
   the second shrinks a long one until it fits, taking ~0.56em as the average glyph advance. */
.city-grid .slot .lbl{
  display:block; transform:rotate(-45deg);
  width:calc(var(--city) * 1.36); text-align:center;
  font-size:min(calc(var(--city) * .27), calc(var(--city) * 2.3 / var(--len, 4)));
  font-weight:700; letter-spacing:-.02em; white-space:nowrap;
}
/* Belt and braces: nothing should ever render one disabled, but the shared disabled rule
   halves opacity, and a half-strength alliance colour is a different colour. */
.city-grid .slot:disabled{ cursor:default; opacity:1; }
/* An empty square is ground, not content — it recedes so the placed cities read as a shape. */
.city-grid .slot.free{ background-color:color-mix(in srgb, var(--absent) 7%, var(--g1)); }
/* A city, on the other hand, IS an object, and now the only outlined thing on the map: its rim is
   what says where one ends and the next begins, whether or not it lines up with a square. */
.city-grid .slot.taken{ color:#141210; border:1px solid rgba(0,0,0,.28); border-radius:3px; }
/* A placed city is draggable, so the browser must stop treating a drag that starts on one as a
   pan. Empty squares keep their default behaviour, so the map is still scrollable by dragging
   anywhere that isn't a city. */
.city-grid.draggable .slot.taken{ touch-action:none; }
.city-grid .slot.lifting{ opacity:.35; }
.city-grid .slot.drop-to{ outline:3px solid var(--bronze); outline-offset:-1px; z-index:4; }

/* What follows the finger while a city is being dragged. Outside the rotated grid, so it stays
   level and legible instead of tumbling with the map. */
.drag-ghost{
  position:fixed; z-index:9999; pointer-events:none; transform:translate(-50%, -50%);
  padding:5px 11px; border-radius:var(--radius-pill); font-size:12px; font-weight:700; color:#141210;
  box-shadow:0 6px 18px -6px rgba(0,0,0,.5); white-space:nowrap;
}
.city-grid .slot.me{ outline:2px solid var(--text); outline-offset:1px; z-index:2; }
.city-grid .slot.hit{ animation:slotflash 1.6s ease-out 2; z-index:3; }
@keyframes slotflash{ 0%,100%{ box-shadow:0 0 0 0 transparent; } 50%{ box-shadow:0 0 0 4px var(--bronze); } }
/* Zoomed out far enough, a name that still fits is only a few pixels tall — the alliance colour is
   doing the talking at that size, so the text goes rather than turning into fuzz. Set per label
   rather than per map, because a short name stays legible at a zoom where a long one does not. */
.city-grid .slot .lbl.too-small{ display:none; }

/* The keep: six cities square, with a turret on each corner. Turned 45 degrees with everything
   else, those corners land on the north, east, south and west points of the diamond. */
.city-grid .keep{ grid-column:5 / span 6; grid-row:5 / span 6; display:grid; gap:2px;
       grid-template-columns:repeat(6, 1fr); grid-auto-rows:1fr;
       background:color-mix(in srgb, var(--bronze) 14%, var(--panel));
       border:1px solid color-mix(in srgb, var(--bronze) 50%, transparent); border-radius:var(--radius-sm); }
.city-grid .fort{ border:1px solid var(--bronze); border-radius:var(--radius-sm);
       background:color-mix(in srgb, var(--bronze) 30%, var(--panel));
       display:flex; align-items:center; justify-content:center; overflow:hidden; }
.city-grid .fort span{ display:block; transform:rotate(-45deg); font-family:var(--font-display);
       font-weight:700; color:var(--bronze); line-height:1;
       font-size:calc(var(--city) * .46); white-space:nowrap; }
.city-grid .fort.castle{ grid-column:2 / span 4; grid-row:2 / span 4; }
.city-grid .fort.castle span{ font-size:calc(var(--city) * .38); }
.city-grid .fort.t-n{ grid-column:1; grid-row:1; } .city-grid .fort.t-e{ grid-column:6; grid-row:1; }
.city-grid .fort.t-s{ grid-column:6; grid-row:6; } .city-grid .fort.t-w{ grid-column:1; grid-row:6; }

.maptools{ display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-bottom:9px; }
.maptools .spacer{ flex:1 1 auto; }
.zoom{ display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--text-muted); }
.zoom input[type="range"]{ width:104px; accent-color:var(--bronze); padding:0; background:none; border:none; }

.map-legend{ display:flex; gap:10px; flex-wrap:wrap; font-size:11px; color:var(--text-muted); margin-top:8px; }
.map-legend span{ display:inline-flex; align-items:center; gap:5px; }
.map-legend i{ width:10px; height:10px; border-radius:var(--radius-sm); display:inline-block; }

.side-counts{ display:flex; gap:0; flex-wrap:wrap; border:1px solid var(--border); border-radius:var(--radius);
              overflow:hidden; margin-bottom:12px; background:var(--panel); }
.side-counts div{ flex:1 1 96px; padding:7px 11px; border-right:1px solid var(--border); }
.side-counts div:last-child{ border-right:none; }
.side-counts b{ display:block; font-family:var(--font-mono); font-size:18px; font-variant-numeric:tabular-nums; color:var(--tx-hi); }
.side-counts span{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--text-muted); }
@media (max-width:560px){ .side-counts div{ flex:1 1 calc(50% - 1px); border-bottom:1px solid var(--border); } }

/* ---------- the section menu ----------
   A bar across the top, not a column down the side. The widest table in this app is
   fifteen columns and the pools are wider still; horizontal space is the thing this
   product is short of, and a sidebar spent 210px of it on every page whether or not
   anyone was looking at the menu. Vertical space is cheaper here because the page
   scrolls anyway.

   The page markup did not change for this. `.shell` was a two-column flex row; it is
   now a block, so the nav mount that used to be the left column becomes the top strip
   and `.shell-main` takes the full width underneath. */
.shell{ display:block; max-width:1180px; margin:0 auto; padding:0 0 56px; }
.shell > .shell-main{ padding:0 16px; }
.shell > .shell-nav{ position:sticky; top:0; z-index:20; margin-bottom:16px; }

.topbar{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  background:var(--g1); border-bottom:1px solid var(--line);
  padding:9px 16px;
}
.tb-brand{ display:inline-flex; align-items:center; gap:8px; text-decoration:none; color:var(--tx-hi); flex:none; }
.tb-brand .tb-mark{ color:var(--brand); flex:none; }
.tb-brand b{ font-size:15px; font-weight:700; letter-spacing:-.02em; }
/* The wordmark is two halves, not one word: "ks" is the kingdom it came from and
   "wizard" is what it does. Dropping the first half to --tx-lo is what keeps the
   brand from competing with the Rally / KvK switcher sitting right beside it. */
.tb-brand b i{ color:var(--tx-lo); font-weight:600; font-style:normal; }

/* Which half of the product you are in. Two sections, so a segmented control rather
   than a menu — the choice is always visible and always one tap. */
.tb-sect{ display:inline-flex; background:var(--g2); border:1px solid var(--line); border-radius:var(--radius-sm); padding:3px; gap:2px; flex:none; }
.tb-sect a{
  padding:5px 11px; font-size:12px; font-weight:600; color:var(--text-muted);
  border-radius:calc(var(--radius-sm) - 3px); text-decoration:none; white-space:nowrap;
}
.tb-sect a:hover{ color:var(--tx-hi); }
.tb-sect a.on{ background:var(--g3); color:var(--tx-hi); }

.tb-pages{ display:flex; gap:3px; align-items:center; flex-wrap:wrap; min-width:0; }
.tb-pages a{
  display:inline-flex; align-items:center; gap:7px; padding:7px 12px; border-radius:var(--radius-sm);
  text-decoration:none; color:var(--text-muted); font-size:13px; font-weight:500; white-space:nowrap;
}
.tb-pages a .ic{ color:var(--tx-lo); flex:none; }
.tb-pages a:hover{ background:var(--g3); color:var(--tx-hi); }
.tb-pages a:hover .ic{ color:var(--tx-hi); }
/* The one place brand colour is allowed to fill something: where you are. It is a
   fill behind white text, never coloured text. */
.tb-pages a.on{ background:linear-gradient(90deg, var(--brand-2), var(--brand)); color:var(--brand-ink); font-weight:600; }
.tb-pages a.on .ic{ color:var(--brand-ink); }
.tb-soon{
  display:inline-flex; align-items:center; gap:6px; padding:7px 12px; font-size:13px;
  font-weight:500; color:var(--text-muted); opacity:.6;
}
.tb-soon i{
  font-style:normal; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
  padding:1px 6px; border-radius:var(--radius-pill); background:var(--g2); border:1px solid var(--border);
}
.tb-more{
  display:none; align-items:center; gap:7px; background:var(--g2); border:1px solid var(--border);
  color:var(--text); font-size:13px; font-weight:600; padding:7px 12px; border-radius:var(--radius-sm);
}
.tb-more:hover{ background:var(--g3); color:var(--tx-hi); }

@media (pointer:coarse){ .tb-pages a{ padding:9px 12px; } }

@media (max-width:820px){
  /* Seven page links wrap to three rows and push the page below the fold, which is
     the thing somebody came for. They go behind the button; the section switcher
     stays, because which half you are in is worth one tap to see. */
  .topbar{ padding:8px 12px; gap:8px; }
  .tb-more{ display:inline-flex; margin-left:auto; }
  .tb-pages{ display:none; width:100%; flex-direction:column; align-items:stretch; gap:2px; padding-top:4px; }
  .tb-pages.open{ display:flex; }
  .tb-pages a{ padding:10px 12px; }
  .shell > .shell-main{ padding:0 10px; }
}

/* The page's own header keeps the title and who you are acting as, but not a second
   copy of the mark — the bar above it already carries the identity. */
.shell header.top .mark{ display:none; }
.shell header.top{ padding-top:2px; }


/* ---------- print ----------
   Save-as-PDF lives inside the browser's own print dialog, so one button covers printing and PDF
   and no PDF library has to be shipped to every visitor. What survives is the content: the menu,
   the buttons and anything that only exists to be pressed all go. */
@media print{
  /* Paper is white and the world is not. The screen tokens are re-stated here at
     the token level rather than rule by rule, so anything reading a token prints
     correctly without a matching print rule of its own — including every rule
     added later. Without this the headings print white on white, which is
     exactly the kind of defect nobody finds until it is on paper. */
  :root{
    --g0:#fff; --g1:#fff; --g2:#fff; --g3:#f2f2f2;
    --line:#bbb; --line-strong:#888;
    --tx:#000; --tx-hi:#000; --tx-lo:#444;
    --claim:#000; --unres:#000; --absent:#000; --ok:#000; --emph:#000;
    --claim-bg:#fff; --unres-bg:#fff; --absent-bg:#fff; --ok-bg:#fff;
    --shadow:none;
  }
  a{ color:#000; }
  input, select, textarea{ box-shadow:none; background:#fff; color:#000; }
  .shell{ display:block; padding:0; max-width:none; }
  .shell-nav, .topbar, .tb-more, .tb-pages, .tools, .daytabs, .page-nav, footer.note,
  .banner, select, button{ display:none !important; }
  .card{ box-shadow:none; border:none; padding:0; margin:0 0 14px; break-inside:avoid; }
  .rrow{ break-inside:avoid; padding:3px 0; border:none; border-bottom:1px solid #ddd; border-radius:0; }
  .rrow.empty{ background:none; }
  .table-scroll{ overflow:visible; max-height:none; border:none; }
  body{ background:#fff; color:#000; }
}

/* ---------- a short explanation attached to a control ----------
   For the questions that come up once, at the moment you're being asked something: what a
   coordinator actually is, why a player ID matters. A button rather than a bare icon, so it answers
   to a tap and to a keyboard and not only to a mouse that happens to hover — which is most of the
   people filling in a form on a phone. Shown on :focus-within as well as :hover for the same reason. */
.tip{ position:relative; display:inline-flex; vertical-align:middle; }
.tip > button{
  width:17px; height:17px; padding:0; border-radius:50%; cursor:help;
  border:1px solid var(--border); background:var(--panel-2); color:var(--text-muted);
  font:inherit; font-size:11px; line-height:1; display:inline-flex; align-items:center; justify-content:center;
}
.tip > button:hover{ color:var(--text); border-color:var(--text-muted); }
/* Anchored to the trigger's right edge rather than centred on it. These sit at the end of a row,
   so centring a 260px panel on one hangs it off the side of the page — which it did, by 14px, at
   700px wide. Right-anchoring cannot overflow as long as the trigger itself is on screen. */
.tip-body{
  display:none; position:absolute; z-index:40; right:0; left:auto;
  bottom:calc(100% + 8px); width:min(260px, 74vw);
  background:var(--panel); border:1px solid var(--border); border-radius:var(--radius-sm); box-shadow:var(--shadow);
  padding:10px 12px; font-size:12px; line-height:1.45; color:var(--text-muted); text-align:left;
  white-space:normal; font-weight:400;
}
.tip-body b{ color:var(--text); display:block; margin-bottom:2px; }
.tip-body b + span + b{ margin-top:8px; }
.tip:hover .tip-body, .tip:focus-within .tip-body{ display:block; }

/* ---------- a panel over the page ----------
   Used by the admin console, the calculator and the KvK pages. Kept here rather than copied into
   each: three copies is three files to forget, and a page-level copy sits after this sheet in
   source order and quietly wins every specificity tie. A page that wants a wider one says so with
   one line of its own. */
.modal-backdrop{
  display:none; position:fixed; inset:0; background:rgba(4,6,9,.74); z-index:30;
  align-items:flex-start; justify-content:center; padding:5vh 16px; overflow-y:auto;
}
.modal-backdrop.show{ display:flex; }
.modal-panel{
  background:var(--bg); border-radius:var(--radius); max-width:660px; width:100%; padding:16px 18px 18px;
  border:1px solid var(--line-strong);
}
.modal-head{ display:flex; align-items:baseline; justify-content:space-between; margin-bottom:6px; }
.modal-head h2{ font-family:var(--font-display); font-size:20px; margin:0; }
.modal-close{ background:none; border:none; color:var(--text-muted); cursor:pointer; line-height:1; padding:6px; border-radius:var(--radius-sm); display:inline-flex; }
.modal-close:hover{ background:var(--g3); color:var(--tx-hi); }
.modal-close:hover{ color:var(--text); }

/* ---------- "this changed since you last looked" ----------
   A round "!" in front of the name rather than a wide button after it, and a red dot that means
   changed since YOU looked — tracked per browser, so reading it clears the dot for you and for
   nobody else. The same badge marks an edited joiner and an edited KvK request, because it is the
   same question being asked about two different pools. */
.upd-badge{
  position:relative; flex:none; width:20px; height:20px; padding:0; margin-right:6px;
  border-radius:50%; border:1px solid var(--border); background:var(--panel-2);
  color:var(--text-muted); font-weight:800; font-size:12px; line-height:1;
}
.upd-badge:hover{ color:var(--text); border-color:var(--bronze); }
.upd-badge.unseen{ color:var(--bronze); border-color:var(--bronze); }
.upd-badge.unseen::after{
  content:""; position:absolute; top:-2px; right:-2px; width:7px; height:7px;
  border-radius:50%; background:var(--crimson); border:1px solid var(--panel);
}
/* Rows with no changes still reserve the badge's width, otherwise their name sits flush left while
   everyone else's is indented. */
.upd-spacer{ flex:none; width:20px; margin-right:6px; }
.name-cell-inner{ display:flex; align-items:center; }

/* One entry in a change log: who and when, then a line per field with the old value struck through
   beside the new one. Read by people, so it says "Hours (UTC): 12, 13 → 12, 13, 14". */
.change-entry{ border-bottom:1px solid var(--border); padding:10px 0; }
.change-entry:last-child{ border-bottom:none; }
.change-head{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.change-when{ font-size:12px; color:var(--text-muted); }
.change-line{ display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; font-size:13px; padding:2px 0 2px 4px; }
.change-line .cf{ min-width:140px; color:var(--text-muted); font-size:12px; }
.change-line .cv{ font-family:var(--font-mono); font-size:12px; padding:1px 6px; border-radius:var(--radius-sm); }
.change-line .cv.old{ background:var(--crimson-bg); color:var(--crimson); text-decoration:line-through; }
.change-line .cv.new{ background:var(--moss-bg); color:var(--moss); }
.change-line .carrow{ color:var(--text-muted); }

/* ---------- a city that didn't land squarely ----------
   Nudged half a city, so it lies across the join between two squares. Given a halo rather than left
   to be inferred from position alone: at a distance the whole map is small coloured tiles, and
   "that one is offset" is not something the eye picks up without help. The lift comes from the
   inline z-index the grid writes, since only it knows which tiles are nudged. */
.city-grid .slot.nudged{
  box-shadow:0 0 0 1.5px var(--bg), 0 0 0 3px rgba(255,255,255,.45);
}
/* The nine-position pad in the slot bar: a middle square that squares the city up, and eight
   half-city steps around it. Laid out as it is aimed — the button up and to the left moves the city
   up and to the left on the rotated map — because the person using it is looking at the picture. */
.nudge{ display:inline-grid; grid-template-columns:repeat(3, 22px); grid-auto-rows:22px; gap:2px; }
.nudge button{
  padding:0; font-size:11px; line-height:1; border-radius:var(--radius); display:flex;
  align-items:center; justify-content:center;
}
.nudge button.on{
  background:var(--bronze-bg, var(--panel-2)); border-color:var(--bronze, var(--text-muted));
  color:var(--bronze, var(--text));
}
@media (pointer:coarse){
  .nudge{ grid-template-columns:repeat(3, 30px); grid-auto-rows:30px; }
}
