/* BommerTracker dashboard — mobile first.
   The clan checks its stats from a phone, so the small screen is the default
   layout and wide screens only get more columns. */

:root {
  --bg: #14161c;
  --panel: #1c1f27;
  --line: #2b303b;
  --text: #e7e9ee;
  --muted: #939aab;
  --accent: #6d8cff;
  --danger: #ff6b6b;
  --ok: #4cc38a;
  --radius: 12px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
h1 { font-size: 1.35rem; margin: 0 0 1rem; }
h2 { font-size: 1.05rem; margin: 0 0 .75rem; }
small.muted, .muted { color: var(--muted); font-size: .8rem; }

/* ── chrome ── */

.top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
  padding: .75rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.brand { font-weight: 700; color: var(--text); }

.logout { margin-left: auto; }

/* The burger belongs to the phone; a wide display shows the menu anyway. */
.burger {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .35rem .6rem; border: 1px solid var(--line); border-radius: 8px;
  color: var(--text); font-size: .85rem; cursor: pointer; user-select: none;
}
.burger::before { content: "\2630"; font-size: 1rem; line-height: 1; }
.drawer-toggle:checked ~ .top .burger { background: #232734; border-color: var(--accent); }
.drawer-toggle:focus-visible ~ .top .burger { outline: 2px solid var(--accent); outline-offset: 2px; }

main { padding: 1rem; max-width: 1100px; margin: 0 auto; }
.bottom { padding: 1rem; color: var(--muted); font-size: .8rem; text-align: center; }

/* ── navigation ──

   One checkbox carries the menu: it opens the drawer on a phone. It stays
   focusable (opacity, not display), so Tab reaches it and Space toggles it;
   no JavaScript is involved, which is what keeps the menu working under the
   script-src of the CSP.

   The sidebar holds a single section, so its links are simply there: a group
   that is open by definition needs no toggle.

   On a phone the open drawer sits between the header and the content and
   pushes it down — it never covers a table and never widens the page. */

.drawer-toggle {
  position: absolute;
  width: 1px; height: 1px; min-height: 0;
  margin: 0; padding: 0; border: 0;
  opacity: 0;
}

.shell { display: block; }

.side { display: none; padding: .25rem 1rem; }
.drawer-toggle:checked ~ .shell .side { display: block; }

.nav-home {
  display: inline-block; padding: .4rem 0;
  color: var(--muted); font-size: .85rem;
}
.nav-home::before { content: "\2190"; margin-right: .4rem; }
.nav-home:hover { color: var(--text); text-decoration: none; }

.nav-group { border-top: 1px solid var(--line); margin-top: .25rem; }

.nav-title {
  margin: 0; padding: .6rem 0;
  color: var(--muted); font-size: .78rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
}

.nav-links { list-style: none; margin: 0 0 .5rem; padding: 0; }
.nav-links a {
  display: block; padding: .45rem .6rem; margin-bottom: .15rem;
  border-left: 3px solid transparent; border-radius: 0 8px 8px 0;
  color: var(--muted);
}
.nav-links a:hover { color: var(--text); background: #232734; text-decoration: none; }
.nav-links a.on {
  color: var(--text); background: #232734;
  border-left-color: var(--accent); font-weight: 600;
}
.nav-links a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── landing page ──

   The section cards are the whole navigation of this page, so each one is a
   link as a block: on a phone the tap target is the tile, not a word in it.
   They stack on a phone and stand side by side once there is room. */

.sections { display: grid; gap: .75rem; margin-top: 1rem; }
.section-card {
  display: flex; flex-direction: column; gap: .35rem;
  padding: 1.1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--text);
}
.section-card:hover {
  border-color: var(--accent);
  background: #232734;
  text-decoration: none;
}
.section-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.section-name { font-size: 1.1rem; font-weight: 700; }
.section-about { color: var(--muted); font-size: .9rem; }
.section-go { color: var(--accent); font-size: .85rem; font-weight: 600; }
.section-go::after { content: "\2192"; margin-left: .35rem; }

/* ── blocks ── */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 1rem;
}
.card.narrow { max-width: 26rem; margin: 3rem auto; }
.danger-zone { border-color: #57323a; }

.tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: .75rem; margin-bottom: 1rem; }
.tile {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .75rem;
  display: flex;
  flex-direction: column;
}
.tile .value { font-size: 1.6rem; font-weight: 700; }
.tile .label { color: var(--muted); font-size: .8rem; }

.tiers, .levels, .state { list-style: none; margin: .5rem 0 0; padding: 0; }
.state li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .35rem 0; border-bottom: 1px solid var(--line);
}
.tiers > li, .levels > li { border-bottom: 1px solid var(--line); }
.state li:last-child, .tiers > li:last-child, .levels > li:last-child { border-bottom: none; }

/* Distribution rows that open on click: <details>, so keyboard and
   no-JavaScript both work and the CSP never sees an inline script. */
.drill > summary, .drill-row {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; padding: .35rem 0;
}
.drill > summary { cursor: pointer; list-style: none; }
.drill > summary::-webkit-details-marker { display: none; }
.drill > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.drill-label::before { content: "\25B8"; color: var(--muted); margin-right: .4rem; }
.drill[open] .drill-label::before { content: "\25BE"; color: var(--accent); }
.drill-row .drill-label::before { visibility: hidden; }
.drill > summary:hover .drill-label { color: var(--accent); }

.roster {
  list-style: none; margin: 0 0 .5rem; padding: .15rem 0 .15rem .85rem;
  border-left: 2px solid var(--line);
}
.roster li {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: .75rem; padding: .15rem 0; font-size: .9rem;
}
.roster .who { overflow-wrap: anywhere; }
.roster .muted { white-space: nowrap; }

.flash {
  background: #1e2b24; border: 1px solid #2f5c46; color: var(--ok);
  padding: .6rem .8rem; border-radius: var(--radius); margin: 0 0 1rem;
}
.error {
  background: #2b1c1f; border: 1px solid #5c2f36; color: var(--danger);
  padding: .6rem .8rem; border-radius: var(--radius);
}
.empty, .hint { color: var(--muted); font-size: .85rem; }

/* ── forms ── */

input, select, button {
  font: inherit;
  color: var(--text);
  background: #232734;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: .5rem .6rem;
  min-height: 2.5rem;
}
input:focus, select:focus, button:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

button { background: var(--accent); border-color: var(--accent); color: #0e1017; font-weight: 600; cursor: pointer; }
button.danger { background: var(--danger); border-color: var(--danger); }
button.link, a.link { background: none; border: none; color: var(--accent); padding: 0; min-height: 0; cursor: pointer; }

/* The one button of the sign-in page, in the blurple of Discord — the colour
   is what people look for on a page like this, and it is not ours to invent. */
.discord {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .7rem 1rem; margin: .5rem 0;
  background: #5865f2; border-radius: var(--radius);
  color: #fff; font-weight: 700;
}
.discord:hover { background: #4752c4; text-decoration: none; }
.discord:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.stack { display: flex; flex-direction: column; gap: .4rem; }
.stack label { color: var(--muted); font-size: .85rem; }
.stack button { margin-top: .6rem; }

.filters, .row { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: 1rem; }
.filters input[type="search"] { flex: 1 1 12rem; }
.row select { flex: 1 1 12rem; }
.inline { display: inline-flex; align-items: center; gap: .25rem; margin: 0 .25rem .25rem 0; }
.inline input[type="number"] { width: 5rem; }

/* ── tables ── */

.grid { width: 100%; border-collapse: collapse; }
.grid th, .grid td { text-align: left; padding: .5rem; border-bottom: 1px solid var(--line); vertical-align: middle; }
.grid th { color: var(--muted); font-size: .8rem; font-weight: 600; }
.badge {
  display: inline-block; background: #3a2f1c; color: #e8c07d;
  border-radius: 999px; padding: 0 .5rem; font-size: .75rem;
}

.pager { display: flex; gap: 1rem; align-items: center; justify-content: center; color: var(--muted); font-size: .85rem; }

/* Charts get a fixed frame so frappe-charts can size its SVG. */
#chart-dynamics, #chart-tiers { min-height: 240px; }

@media (max-width: 640px) {
  .grid thead { display: none; }
  .grid, .grid tbody, .grid tr, .grid td { display: block; width: 100%; }
  .grid tr { border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: .75rem; padding: .25rem; }
  .grid td { border: none; display: flex; justify-content: space-between; gap: 1rem; }
  .grid td::before { content: attr(data-label); color: var(--muted); font-size: .8rem; }
  .grid td.actions { flex-direction: column; align-items: stretch; }
}

@media (min-width: 720px) {
  .tiles { grid-template-columns: repeat(4, 1fr); }
  .sections { grid-template-columns: repeat(2, 1fr); }
  h1 { font-size: 1.6rem; }
}

/* The menu is a drawer at every width, closed until it is asked for.

   A permanent column cost 15rem of every screen, and the page that needs the
   room most — the planner, a square board between two columns of names — is
   also the page whose menu holds a single link. So on a wide display the open
   menu is laid over the content instead of beside it: nothing reflows when it
   opens, and the board is not resized twice for a glance at the links. */
@media (min-width: 900px) {
  .shell { position: relative; }
  .drawer-toggle:checked ~ .shell .side {
    position: absolute; top: 0; left: 0; z-index: 5;
    width: 15rem; padding: 1rem;
    max-height: calc(100vh - 4rem); overflow-y: auto;
    background: var(--panel);
    border-right: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    border-radius: 0 0 var(--radius) 0;
    box-shadow: 0 8px 24px rgb(0 0 0 / .45);
  }
}

/* ── BGB event planner ──

   The map is one SVG scaled to the width of the column: on a phone it is the
   first thing on the screen and stays readable at 360px, on a desktop it is
   the same picture, larger. Everything below it is a list, because a list is
   what survives a narrow screen — the drag-and-drop of the desktop is an
   addition on top of the zone select each card carries. */

/* The heading row of the planner: the title on the left, the team switch
   pushed to the right edge. On a phone the switch drops under the title and
   stays right-aligned, so it never squeezes the heading. */
.bgb-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
}
.bgb-head h1 { margin-bottom: .5rem; }

/* The plan of the day at the right edge of the heading row, across it from
   the team switch: two controls that change different things must not read as
   one. The date select opens the plan on its own, so the button beside it is
   only there when the script that submits the form is not. */
.bgb-tools {
  display: flex; flex-wrap: wrap; justify-content: flex-end;
  align-items: center; gap: .5rem;
  margin-left: auto; margin-bottom: 1rem;
}
.bgb-tools .filters, .bgb-tools .row { margin-bottom: 0; }
.bgb-tools .filters.auto button[type="submit"] { display: none; }

/* Creating a plan is rare next to opening one: a pill with a blue plus, and
   the form drops out from under it. The drop is positioned, not in the flow,
   so opening it never changes the height of the heading row. */
.bgb-new { position: relative; }
.bgb-new > summary {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .9rem; border: 1px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--muted); font-size: .85rem;
  cursor: pointer; user-select: none; list-style: none;
}
.bgb-new > summary::-webkit-details-marker { display: none; }
.bgb-new > summary::before {
  content: "+"; color: var(--accent); font-weight: 700; font-size: 1rem;
}
.bgb-new > summary:hover { color: var(--text); border-color: var(--accent); }
.bgb-new > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bgb-new[open] > summary { color: var(--text); border-color: var(--accent); }
/* Below the calendar of the date field (z-index 30) but above the page: the
   calendar belongs to this very form and has to stay on top of it. */
.bgb-new-pop {
  position: absolute; z-index: 25; top: calc(100% + .4rem); right: 0;
  width: min(24rem, calc(100vw - 2rem));
  padding: .7rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 14px 30px rgb(0 0 0 / .5);
}
.bgb-new-pop .hint { margin-bottom: 0; }

/* The heading of the plan card: the day, and the button that fills the plan
   beside it. The drop of that one opens to the RIGHT — it hangs off a pill on
   the left edge of the card, where a right-aligned drop would run off the
   page — and it is wider, because it holds two upload forms. */
.bgb-plan-head {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .4rem .75rem; margin-bottom: .5rem;
}
.bgb-plan-head h2 { margin-bottom: 0; }
.bgb-upload-pop {
  left: 0; right: auto;
  width: min(34rem, calc(100vw - 2rem));
}
/* On a phone the pill stands next to the date, a third of the way in, and a
   drop hanging off it would run past the right edge whatever its width. There
   it simply opens in the flow, under the heading, across the card. */
@media (max-width: 700px) {
  .bgb-upload[open] { flex: 1 1 100%; }
  .bgb-upload-pop {
    position: static; width: auto; margin-top: .5rem; box-shadow: none;
  }
}

.tabs { display: flex; gap: .5rem; margin-bottom: 1rem; }
.tabs a {
  padding: .4rem .9rem; border: 1px solid var(--line); border-radius: 999px;
  color: var(--muted); background: var(--panel);
}
.tabs a.on { color: var(--text); border-color: var(--accent); background: #232734; font-weight: 600; }
.tabs a:hover { text-decoration: none; color: var(--text); }

/* The map is the battlefield of the game, redrawn to the design of the owner:
   a square panel with a warm rim, scorched ground, the golden ball in the
   middle, the drops of black gold around it, two stars, two crosses and the
   base shields. Nothing here is an image file — one inline SVG and this
   stylesheet.

   It is also the board the officer works on, and the board is not alone: the
   six teams stand beside it in panels of their own, three down each side,
   every one of them where its point stands on the field. The nicknames used to
   be written over the picture; they are not any more. A plate on the sand held
   three names of a zone of eight and covered the drawing with the rest, while
   the room left and right of a square board stood empty on every monitor wider
   than a tablet. The panels took that room, and the board grew into what the
   plates and the single Support column used to cost it.

   Two different measurements run through the section below and they are not
   interchangeable:

   · the width of the SCREEN decides the layout of the stage — whether the six
     panels stand beside the board or fall under it in two columns, and how
     much of the page the board itself may take;

   · the width of the BOARD decides everything written on the picture — the
     size of the names of the points and of their counters. The board is not
     the screen: at 900px the side navigation takes 15rem of it, so a 900px
     desktop shows a smaller picture than an 810px tablet. Asked of the screen,
     the lettering came out a third larger on the tablet than on the desktop —
     which is exactly what the owner saw. It is asked of the board, through the
     container queries below, and comes out the same size everywhere. */

/* The one button above the board: it fills every position at once, so it is
   centred over the map and pulses to be found at a glance. The pulse is the
   shadow, not the colour — a button that changes colour on its own reads as
   broken, one that breathes reads as waiting. */
.bgb-layout-row { display: flex; justify-content: center; margin: .6rem 0 0; }
.bgb-layout-row form { margin: 0; }
.beacon {
  padding: .55rem 1.4rem; border-radius: 999px; font-weight: 700;
  animation: beacon 2s ease-in-out infinite;
}
@keyframes beacon {
  0%, 100% { box-shadow: 0 0 0 0 rgb(109 140 255 / .55); }
  50% { box-shadow: 0 0 0 .7rem rgb(109 140 255 / 0); }
}
/* Somebody who asked the system for less motion gets a plain button. */
@media (prefers-reduced-motion: reduce) {
  .beacon { animation: none; }
}

/* The stage: the board and the two columns of teams.

   On a phone and on a tablet there is no room at the sides, so the board keeps
   the full width and the two columns fall under it — left column under the
   left half, right under the right, which is still the arrangement of the map.
   From 1200px up the board moves between them. */
.bgb-stage {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-areas:
    "board board"
    "left  right";
  gap: .6rem;
  align-items: start;
  margin: .6rem 0;
}

.bgb-board {
  grid-area: board;
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  margin: 0 auto;
  container-type: inline-size;
}
.bgb-map {
  position: absolute; inset: 0; width: 100%; height: 100%;
  filter: drop-shadow(0 10px 30px rgba(0, 0, 0, .55));
}

.bgb-side { display: grid; gap: .6rem; align-content: start; }
.bgb-side.left { grid-area: left; }
.bgb-side.right { grid-area: right; }
/* The two piles stand beside the board only where the screen has room to
   spare past the columns of teams — see the queries at the foot of this
   section. Everywhere else the cards under the map are the only ones, and a
   pile is not drawn at all rather than drawn somewhere it does not belong. */
.bgb-side.bench { display: none; grid-area: bench; }
.bgb-side.subs { display: none; grid-area: subs; }

/* ── the panel of the design ── */

.bgb-rim { fill: url(#bgb-rim-fill); }
.bgb-rim-light { fill: none; stroke: url(#bgb-rim-light); stroke-width: .3; }
.bgb-ground { fill: url(#bgb-ground); }
/* The 2px inner border of the design. It is drawn on a rectangle inset by half
   its own width (the template does the arithmetic), because an SVG stroke
   straddles its outline: painted on the field itself, half of it would lie on
   the rim and round the corner wider than the corner under it. */
.bgb-ground-line { fill: none; stroke: rgba(72, 44, 22, .55); stroke-width: .44; pointer-events: none; }
.bgb-ground-shade, .bgb-ground-veil { fill: url(#bgb-ground-shade); pointer-events: none; }
.bgb-ground-veil { opacity: .5; }

/* ── what stands on the ground ── */

.bgb-oil { fill: #fff; filter: drop-shadow(0 .42px .42px rgba(50, 25, 8, .45)); }
.bgb-mark { fill: #fff; }
.bgb-mark.star { filter: drop-shadow(0 .42px .64px rgba(50, 25, 8, .45)); }
.bgb-mark.cross { filter: drop-shadow(0 .42px .42px rgba(50, 25, 8, .4)); }
.bgb-ball { filter: drop-shadow(0 1.7px 3.4px rgba(50, 25, 8, .55)); }
/* The shield of a base is the coloured one with a white rim around it: the
   stroke is painted first, so it stays outside the colour. */
.bgb-shield {
  stroke: #fff; stroke-width: 5; paint-order: stroke;
  filter: drop-shadow(0 .42px .85px rgba(50, 25, 8, .5));
}
.bgb-shield.blue { fill: #3f74d8; }
.bgb-shield.red { fill: #cf3b30; }

/* ── the writing on the picture ──

   One number sets it all. Everything written on the map — the name of a point,
   its counter, the name of a base, the outline that keeps them readable on
   sand — is that number or a fixed fraction of it, so a size can not drift
   apart from one label to the next the way it had: the names of the points
   were set at 2.4 and their counters at 2.1 on the same screen, and the map
   read as a heap of labels instead of one drawing.

   The number is in the coordinates of the picture, where 100 is the width of
   the field, and the ladder below keeps what it comes to on the glass at
   about ten pixels on every board from a 360px phone to a 1280px desktop. The
   query is on the board, which is the container of the SVG. */
.bgb-map { --map-type: 2.7px; }
@container (min-width: 26rem) { .bgb-map { --map-type: 2.2px; } }
@container (min-width: 31rem) { .bgb-map { --map-type: 1.75px; } }
@container (min-width: 38rem) { .bgb-map { --map-type: 1.55px; } }
@container (min-width: 44rem) { .bgb-map { --map-type: 1.4px; } }

/* ── a point of the planner ── */

.bgb-point { cursor: pointer; }
/* The tap area holds the name, the marker and the numbers together; it stays
   invisible until a finger, a cursor or a dragged player finds it. */
.bgb-hit { fill: rgba(255, 255, 255, 0); stroke: none; pointer-events: all; }
/* The name of a point: one size, one outline, one distance from the marker,
   whatever the point and whatever the screen. A name too long for the width of
   the field is broken over two lines by the template — it is never set smaller
   than its neighbours, which is what made the picture look untidy. The count of
   the zone used to stand under the marker in the same size; the officer had it
   taken off the picture, and it lives in the panel beside the board. */
.bgb-zone-name {
  fill: #fff; font-size: var(--map-type); font-weight: 700; text-anchor: middle;
  letter-spacing: .02em;
  paint-order: stroke; stroke: rgba(12, 8, 4, .85);
  stroke-width: calc(var(--map-type) * .33); stroke-linejoin: round;
}

/* The field positions open later than the ball does, and the clock beside the
   marker is the whole of how the map says it. It used to be said three times
   over — a faded marker, a dashed ring around it and this badge — and with a
   clock on four of the five points a fade says nothing at all: everything on
   the field would be faded but the ball. */
.bgb-late-badge { fill: #fff; }

.bgb-map a:hover .bgb-hit { fill: rgba(255, 255, 255, .1); stroke: rgba(255, 255, 255, .5); stroke-width: .4; }
.bgb-map a:focus-visible .bgb-hit { fill: rgba(24, 30, 48, .5); stroke: var(--accent); stroke-width: .9; }
.bgb-map a:focus-visible { outline: none; }
.bgb-point.drop-over .bgb-hit { fill: rgba(34, 48, 88, .72); stroke: var(--accent); stroke-width: 1.1; }

/* ── a team beside the board ──

   All six panels are one element with one shape: the mark of the position, its
   name, its count, and the players under it. The Support Ball Team was the odd
   one out for as long as it was the only zone with a panel; it is not any
   more, and it keeps nothing of that but its blue — the colour the officer
   calls the position by.

   A panel is a control like a point of the map: the heading jumps to the list
   of the zone, the whole panel takes a dropped player, and every nickname in
   it can be dragged onto another panel or onto a point of the field. */
.bgb-panel {
  /* The ink of the position, taken from the field: the white marks stand on
     sand, the ball is gold, and Support is the blue of the panel. */
  --zone-ink: #d7b189;
  border: 1px solid var(--line);
  border-left: 3px solid var(--zone-ink);
  border-radius: var(--radius);
  background: var(--panel);
  padding: .45rem .55rem;
  /* The panel measures itself: how many columns of nicknames fit inside it is
     a question about the panel, not about the screen — it is 170px wide beside
     a desktop board and 380px wide under a tablet one. */
  container-type: inline-size;
}
.bgb-panel.glyph-ball { --zone-ink: #edb02c; }
/* Nobody's position, so no mark and no colour of the field: the heading is a
   single column where a team panel keeps one for its glyph. */
.bgb-panel-pile { --zone-ink: var(--muted); }
.bgb-panel-pile .bgb-panel-head { grid-template-columns: minmax(0, 1fr); }
.bgb-panel.glyph-support {
  --zone-ink: var(--accent);
  /* The frame of the panel is the deep blue of the position, the strip on its
     left the accent — declared after border-color, which would take the strip
     with it otherwise. */
  border-color: #35508f;
  border-left-color: var(--zone-ink);
  background: #171d2c;
}

.bgb-panel-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0 .4rem;
  align-items: center;
  color: var(--text);
  border-radius: 6px;
}
.bgb-panel-head:hover { text-decoration: none; }
.bgb-panel-head:hover .name { color: #93aaff; }
.bgb-panel-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The mark of the position, the same one the field carries. It stands over
   both lines of the heading, so name and count keep one left edge. */
.bgb-panel-glyph { grid-row: span 2; width: 1.05rem; height: 1.05rem; fill: #fff; }
.bgb-panel.glyph-support .bgb-panel-glyph { fill: var(--accent); }
/* The ball paints itself, in the gradients of the map. */
.bgb-panel.glyph-ball .bgb-panel-glyph { fill: none; }
.bgb-panel-head .name { font-weight: 700; font-size: .82rem; line-height: 1.25; }
.bgb-panel-head .stat {
  color: var(--muted); font-size: .75rem; font-variant-numeric: tabular-nums;
}
/* Rule 3 of the brief, as the panel says it: the two corner positions open
   later than the rest of the map, and their mark is faded like the star. */
.bgb-panel.late .bgb-panel-glyph { opacity: .6; }
.bgb-panel-head .badge { grid-column: 2; justify-self: start; margin-top: .15rem; font-size: .7rem; }

.bgb-panel-list {
  list-style: none; margin: .4rem 0 0; padding: 0;
  display: grid; gap: 2px 4px; grid-template-columns: minmax(0, 1fr);
}
@container (min-width: 15rem) { .bgb-panel-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container (min-width: 24rem) { .bgb-panel-list { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.bgb-panel-empty { color: var(--muted); font-size: .78rem; margin: .4rem 0 0; }

/* One nickname of a panel. The strip on the left is the role — green key
   player, yellow supporter, nothing for a reinforce — and it is the same
   colour the cards below the map carry. */
.bgb-name {
  display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 1px 5px;
  border-radius: 4px;
  border-left: 3px solid rgba(255, 255, 255, .35);
  background: #232734;
  color: var(--text);
  font-size: .78rem;
  line-height: 1.45;
}
.bgb-name.role-key { border-left-color: var(--ok); }
.bgb-name.role-supporter { border-left-color: #e8c07d; }
.bgb-name.more { border-left-color: transparent; background: transparent; color: var(--muted); }
.bgb-name.dragging { opacity: .5; }
.bgb-panel.glyph-support .bgb-name { background: rgba(30, 41, 68, .9); }

.bgb-panel.drop-over { border-color: var(--accent); background: #1d2740; }
.bgb-panel.drop-over .bgb-name { background: rgba(46, 64, 116, .95); }

/* ── the panel and its point are one thing ──

   The panel stands where the point stands and carries the mark of the point;
   pointing at either of them lights the other, so the pair can be read across
   the width of the board without counting rows. There is no way in CSS to say
   "the point whose key is the key of this panel", hence the five pairs written
   out; the sixth zone, Support, has no point on the field. */
.bgb-stage:has(.bgb-panel[data-zone="mc_upper_left"]:hover) .bgb-point[data-drop-zone="mc_upper_left"] .bgb-hit,
.bgb-stage:has(.bgb-panel[data-zone="ms_upper"]:hover) .bgb-point[data-drop-zone="ms_upper"] .bgb-hit,
.bgb-stage:has(.bgb-panel[data-zone="ball"]:hover) .bgb-point[data-drop-zone="ball"] .bgb-hit,
.bgb-stage:has(.bgb-panel[data-zone="ms_bottom"]:hover) .bgb-point[data-drop-zone="ms_bottom"] .bgb-hit,
.bgb-stage:has(.bgb-panel[data-zone="mc_lower_right"]:hover) .bgb-point[data-drop-zone="mc_lower_right"] .bgb-hit {
  fill: rgba(255, 255, 255, .1); stroke: rgba(255, 255, 255, .5); stroke-width: .4;
}
.bgb-stage:has(.bgb-point[data-drop-zone="mc_upper_left"]:hover) .bgb-panel[data-zone="mc_upper_left"],
.bgb-stage:has(.bgb-point[data-drop-zone="ms_upper"]:hover) .bgb-panel[data-zone="ms_upper"],
.bgb-stage:has(.bgb-point[data-drop-zone="ball"]:hover) .bgb-panel[data-zone="ball"],
.bgb-stage:has(.bgb-point[data-drop-zone="ms_bottom"]:hover) .bgb-panel[data-zone="ms_bottom"],
.bgb-stage:has(.bgb-point[data-drop-zone="mc_lower_right"]:hover) .bgb-panel[data-zone="mc_lower_right"] {
  border-color: var(--zone-ink);
}

/* From a tablet up the board stops taking the whole width: a square that wide
   is a square that tall, and the lists under it have to start above the fold.
   The panels keep their two columns under it. */
@media (min-width: 700px) {
  .bgb-board { max-width: min(34rem, calc(100vh - 12rem)); }
}

/* The wide desktop: the board between the two columns of teams.

   1000px is where the three of them fit without the board losing what the
   panels gain: below it the picture would come out smaller between the panels
   than it is above them. The threshold used to be 1240px because the menu
   held a column of the screen — now that it is a drawer, the same board is
   left the same 600px at 1000px of window.

   The planner page is also let out of the 1100px the rest of the dashboard is
   set in: that width is right for a table of members and it is what left the
   edges of a 1920px monitor empty here. The stage is centred in whatever it
   gets, so the slack of a very wide monitor falls outside the panels and not
   between a panel and its point. */
@media (min-width: 1000px) {
  main:has(.bgb-stage) { max-width: 92rem; }
  .bgb-stage {
    grid-template-areas: "left board right";
    /* The board takes the room left over by the two columns, up to a square
       that still fits a screen: 52rem is the size at which the picture stops
       gaining anything, and the height of the window is the real limit long
       before that on a laptop. */
    grid-template-columns:
      11rem
      minmax(0, min(52rem, calc(100vh - 10rem)))
      11rem;
    justify-content: center;
    gap: .6rem;
    /* A column is as tall as the board beside it, which is what lets the three
       panels spread over its height below. */
    align-items: stretch;
  }
  /* The three panels of a column stand at the height of their points: the
     first at the top of the board, the last at its foot, the ball zone between
     them. A column taller than the board simply grows down and the board keeps
     its top edge — it is square, and it may never be stretched to a row. */
  .bgb-side { align-content: space-between; }
  .bgb-board { align-self: start; max-width: 100%; }
}

/* Room for wider columns: a nickname of thirteen characters and the count of a
   position read better at 14.5rem, and a screen this wide has it to spare —
   below it every pixel of a column is one the board does not get, and the
   picture is what the officer reads first. 1360px is what a 1600px monitor
   was left of itself while the menu held its column. */
@media (min-width: 1360px) {
  .bgb-stage {
    grid-template-columns:
      14.5rem
      minmax(0, min(52rem, calc(100vh - 10rem)))
      14.5rem;
    gap: 1rem;
  }
}

/* Wide enough for the unplaced players to stand beside the board as well.

   1560px is what the four of them need: three columns of 14.5rem, three gaps
   and a board of the 740px a 900px-tall window allows it. Below that the
   column would be taken out of the board, and the board is what the officer
   reads first — so there it stays a card under the map and nothing else. */
@media (min-width: 1560px) {
  main:has(.bgb-stage) { max-width: 96rem; }
  .bgb-side.bench { display: grid; }
  /* The card at the foot of the page would now say twice what the column
     beside the board says: it goes, and with it its part as the target a
     player is dropped on to come off the board — the column took that on. */
  #bgb-bench { display: none; }
  .bgb-stage {
    grid-template-areas: "left board right bench";
    grid-template-columns:
      14.5rem
      minmax(0, min(52rem, calc(100vh - 10rem)))
      14.5rem
      14.5rem;
  }
}

/* And wide enough for the substitutes to stand on the other side of the board,
   past the left column: four columns of 14.5rem, four gaps and the board of
   740px — 1800px of window, which is the monitor the officer works on. Below
   it the pile would be taken out of the board. */
@media (min-width: 1800px) {
  main:has(.bgb-stage) { max-width: 112rem; }
  .bgb-side.subs { display: grid; }
  #bgb-substitutes { display: none; }
  .bgb-stage {
    grid-template-areas: "subs left board right bench";
    grid-template-columns:
      14.5rem
      14.5rem
      minmax(0, min(52rem, calc(100vh - 10rem)))
      14.5rem
      14.5rem;
  }
}

.chips { list-style: none; margin: .5rem 0; padding: 0; display: grid; gap: .4rem; }
.chip {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  padding: .45rem .6rem; border: 1px solid var(--line); border-left: 4px solid var(--line);
  border-radius: 8px; background: #1a1e27;
}
.chip .who { font-weight: 600; overflow-wrap: anywhere; margin-right: auto; }
.chip .pw { color: var(--text); font-variant-numeric: tabular-nums; }
.chip .tw { color: var(--muted); font-size: .8rem; }
.chip .role { color: var(--muted); font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; }
/* The colours of the officer's sheet: green key player, yellow supporter,
   no fill for the rest. */
.chip.role-key { border-left-color: var(--ok); background: #16241d; }
.chip.role-key .role { color: var(--ok); }
.chip.role-supporter { border-left-color: #e8c07d; background: #241f16; }
.chip.role-supporter .role { color: #e8c07d; }
.chip.dragging { opacity: .5; }

/* ── what can be picked up ──

   A card of a list and a nickname of a panel, for an admin. The cards carried
   a zone select, a role select and a Move button until the officer asked for
   the fewest controls possible; moving a player is the drag now, and the drag
   has to work under a finger.

   touch-action is the whole of it. ``pan-y`` leaves the browser its own
   vertical scrolling — momentum and all, which no script imitates well — and
   keeps for us the one direction it does not use, so a sideways pull starts a
   drag while a swipe up or down still scrolls the long page of the planner.
   A finger that simply rests on a card picks the player up as well; from that
   moment bgb.js cancels the touchmove and the page stops moving underneath.

   No selection and no callout: a press held on a card is a player being lifted
   and must not turn into a text selection or an iOS share menu. */
[data-drag] {
  cursor: grab;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
}
body.bgb-dragging, body.bgb-dragging [data-drag] { cursor: grabbing; }

/* What is being carried, under the pointer: the nickname and the colour of its
   role. It rides above the finger, so the finger does not cover the answer to
   the only question of the gesture — who am I moving. It is over the button
   back to the map (20) and over the calendar popup (30), because while it
   exists it is the thing the eye follows. */
.bgb-ghost {
  position: fixed; left: 0; top: 0; z-index: 40;
  pointer-events: none;
  max-width: 60vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: .35rem .6rem;
  border: 1px solid var(--accent);
  border-left: 4px solid var(--accent);
  border-radius: 8px;
  background: #1a1e27;
  color: var(--text);
  font-size: .85rem; font-weight: 600;
  box-shadow: 0 10px 24px rgba(0, 0, 0, .6);
}
.bgb-ghost.role-key { border-left-color: var(--ok); }
.bgb-ghost.role-supporter { border-left-color: #e8c07d; }

/* The one line that says how a player is moved now that nothing on a card says
   it. It stands above the lists, where the dragging happens. */
.bgb-drag-hint { margin: 1rem 0 .4rem; }

.bgb-zones { display: grid; gap: 0 1rem; }
.zone.drop-over { border-color: var(--accent); background: #1d2330; }
.why { color: var(--muted); font-size: .85rem; margin: .5rem 0 0; }
input.tower { width: 5.5rem; }

@media (min-width: 900px) {
  .bgb-zones { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}

/* ── back to the map ──

   Six zone lists, the bench, the substitutes and the line-up form make a page
   the officer scrolls through — on a phone a long one. The map is what he
   keeps checking against, so the way back to it stands in the corner of the
   screen and does not travel with the page.

   It is an ordinary anchor to the board: with the script blocked it still
   jumps there, and bgb.js only adds the soft scroll and the class that takes
   it off the screen while the map is in sight anyway.

   Panel colours and not the blue of the Support column that stands beside the
   map: this is a control of the page, not a second accent on it. */
.bgb-jump {
  position: fixed;
  z-index: 20;
  /* The datefield popup is z-index 30 and belongs to the form at the top of
     the page: if both are ever open, the calendar stays on top. */
  right: max(.5rem, env(safe-area-inset-right));
  bottom: max(.6rem, env(safe-area-inset-bottom));
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  /* A whole finger tall — the planner is worked from a phone. */
  min-height: 3rem;
  padding: 0 .85rem 0 .65rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #232734;
  color: var(--text);
  font-size: .85rem;
  font-weight: 600;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .55);
  transition: opacity .18s ease, visibility .18s ease;
}
.bgb-jump .icon { flex: none; width: 1.1rem; height: 1.1rem; fill: var(--accent); }
.bgb-jump:hover { text-decoration: none; border-color: var(--accent); background: #2c3242; }
.bgb-jump:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Set by bgb.js while the board is on the screen; without the script it is
   never set and the button simply stays visible. Hidden it also stops taking
   taps and drops. */
.bgb-jump.away { opacity: 0; visibility: hidden; pointer-events: none; }

/* Room under the last card, so that at the end of the page the button rests
   over the background instead of over a card. */
main:has(.bgb-jump) { padding-bottom: 4.5rem; }

@media (min-width: 720px) {
  .chips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── date field ──

   The calendar of the new-plan form is drawn by datefield.js instead of the
   native picker of the browser: the native one speaks the locale of the
   operating system, which puts Russian months into this English panel. The
   popup is anchored to the field, never wider than the card at 360px, and the
   script nudges it back on screen if the field sits near an edge. */

/* Wide enough for a date and no wider: a full line of empty field next to
   a ten-character value looks like a mistake on a desktop. */
.datefield {
  position: relative; display: flex;
  flex: 1 1 9rem; max-width: 13rem; min-width: 0;
}
.datefield-input {
  width: 100%; min-width: 0; padding-right: 2.4rem;
  font-variant-numeric: tabular-nums;
}

.datefield-toggle {
  position: absolute; right: .3rem; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; min-height: 0; padding: 0;
  background: none; border: 1px solid transparent; border-radius: 6px;
  cursor: pointer;
}
.datefield-toggle::before {
  content: ""; width: 1.15rem; height: 1.15rem;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23939aab' stroke-width='2' stroke-linecap='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='2'/%3E%3Cpath d='M3 10h18M8 3v4M16 3v4'/%3E%3C/svg%3E") center / contain no-repeat;
}
.datefield-toggle:hover { background: #2c3242; }

.datefield-pop {
  position: absolute; z-index: 30; top: calc(100% + .3rem); left: 0;
  width: min(17.5rem, calc(100vw - 2.5rem));
  padding: .5rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 14px 30px rgba(0, 0, 0, .5);
}
.datefield-pop[hidden] { display: none; }
/* Not enough room below on a short screen: the popup sits above the field. */
.datefield-pop.up { top: auto; bottom: calc(100% + .3rem); }

.datefield-head { display: flex; align-items: center; gap: .25rem; margin-bottom: .4rem; }
.datefield-title { flex: 1; text-align: center; font-weight: 600; font-size: .95rem; }
.datefield-nav {
  width: 2rem; height: 2rem; min-height: 0; padding: 0;
  background: #232734; border: 1px solid var(--line); border-radius: 8px;
  color: var(--text); font-size: 1.15rem; line-height: 1; font-weight: 400;
  cursor: pointer;
}
.datefield-nav:hover { border-color: var(--accent); color: var(--accent); }

.datefield-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: .15rem; }
.datefield-weekday {
  padding: .2rem 0; text-align: center; color: var(--muted);
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
}
/* A whole finger wide: the officer picks the date on a phone. */
.datefield-day {
  min-height: 2.25rem; padding: 0;
  background: none; border: 1px solid transparent; border-radius: 8px;
  color: var(--text); font-size: .9rem; font-weight: 400;
  font-variant-numeric: tabular-nums; cursor: pointer;
}
.datefield-day:hover { background: #232734; }
.datefield-day.outside { color: var(--muted); opacity: .55; }
.datefield-day.today { border-color: var(--accent); color: var(--accent); }
.datefield-day.on {
  background: var(--accent); border-color: var(--accent);
  color: #0e1017; font-weight: 700;
}

.datefield-foot { display: flex; gap: .4rem; margin-top: .5rem; }
.datefield-action {
  flex: 1; min-height: 2.1rem; padding: .3rem .5rem;
  background: #232734; border: 1px solid var(--line); border-radius: 8px;
  color: var(--text); font-size: .85rem; font-weight: 400; cursor: pointer;
}
.datefield-action:hover { border-color: var(--accent); color: var(--accent); }

/* ── reading screenshots ──
   The two upload forms under the planner, the popup that stands over it while
   the model works, and the same bar on the import page itself. */

/* Two upload forms side by side once the drop is wide enough for two;
   one under the other on a phone. */
.import-forms { display: grid; gap: 1rem; }
@media (min-width: 640px) {
  .import-forms { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.import-form h3 { margin: 0; font-size: .95rem; }
.import-form input[type="file"] { padding: .45rem .5rem; }
/* The line of one reading: the field the officer types in, what the model
   actually saw under it, and the flags under that. One flex child of the
   cell, so the mobile table (a card per row) cannot push it sideways. */
.import-cell { display: flex; flex-direction: column; gap: .25rem; min-width: 0; flex: 1; }
.import-cell input { width: 100%; }
.import-raw { color: var(--muted); font-size: .75rem; overflow-wrap: anywhere; }
.import-flags { display: flex; flex-wrap: wrap; gap: .25rem; }

/* The bar is the reason the popup exists: a screenshot takes about a minute
   and a half, so something has to move while the officer waits. */
.import-bar {
  position: relative;
  height: .55rem; border-radius: 999px; background: #232734;
  border: 1px solid var(--line); overflow: hidden;
}
.import-bar > span {
  display: block; height: 100%; width: 0;
  background: var(--accent); transition: width .4s ease;
}
/* The screenshot in the machine right now. It is drawn as its own slot of the
   bar and its stripes travel, because the truth about the minute and a half
   it takes is "this one is being read" — not a number crawling inside a file
   nobody can look into. */
.import-active {
  position: absolute; top: 0; left: 0; height: 100%;
  background-image: repeating-linear-gradient(
    -45deg, rgba(109, 140, 255, .85) 0 .4rem, rgba(109, 140, 255, .35) .4rem .8rem
  );
  background-size: 1.13rem 100%;
  animation: import-stripes 1s linear infinite;
  transition: left .4s ease, width .4s ease;
}
@keyframes import-stripes {
  from { background-position: 0 0; }
  to { background-position: 1.13rem 0; }
}
@media (prefers-reduced-motion: reduce) {
  .import-active { animation: none; }
}
.import-timing {
  margin: .2rem 0 0; color: var(--muted); font-size: .8rem;
  font-variant-numeric: tabular-nums;
}
.import-counter {
  margin: .4rem 0 0; color: var(--muted); font-size: .85rem;
  font-variant-numeric: tabular-nums;
}
.import-note { margin: .3rem 0 0; color: var(--danger); font-size: .85rem; }
.import-inline { margin-top: .75rem; }

.import-popup {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 1rem; background: rgba(10, 12, 16, .78);
}
.import-popup[hidden] { display: none; }

/* What is left of the popup once it is hidden: a chip in the corner that
   counts along and puts the popup back. Bottom LEFT — the jump-to-map button
   holds the right corner, and the two must not sit on each other. */
.import-chip {
  position: fixed; z-index: 20;
  left: max(.5rem, env(safe-area-inset-left));
  bottom: max(.6rem, env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .5rem .8rem;
  background: var(--panel); border: 1px solid var(--accent); border-radius: 999px;
  color: var(--text); font-size: .85rem; font-weight: 600;
  box-shadow: 0 10px 24px rgb(0 0 0 / .5);
}
.import-chip[hidden] { display: none; }
.import-chip::before {
  content: ""; width: .6rem; height: .6rem; border-radius: 50%;
  background: var(--accent);
}
.import-chip-show { color: var(--accent); font-weight: 700; }
.import-card {
  width: min(26rem, 100%); max-height: 100%; overflow: auto;
  padding: 1.1rem; text-align: center;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
}
.import-card h2 { margin: .8rem 0 .4rem; }
.import-card p { margin: 0 0 .6rem; color: var(--muted); font-size: .9rem; }
.import-card .row { justify-content: center; margin: .8rem 0 0; }
/* The meme is a square: capped by width so it never pushes the buttons of the
   popup off a small screen, and never stretched on a wide one. */
.import-meme {
  width: min(11rem, 45vw); height: auto; border-radius: var(--radius);
}
