/* FILE: ticketbook/ui/board.css
 *
 * THE REGISTER GATHERED BY WHO IS HOLDING IT — the board's own ruling.
 *
 * Loaded by board.js rather than by a <link> in index.html, because index.html
 * belongs to the shell and this region does not get to edit it. Under the page's
 * `style-src 'self'` an inline <style> would be refused outright, so a real
 * same-origin stylesheet is not merely the tidy shape here, it is the only one.
 *
 * ── What this file is allowed to do ────────────────────────────────────────
 *
 * Nothing new. Every colour, size, weight and step below is a token from
 * app.css; there is no value in this file that app.css did not already publish.
 * `--radius`/`--radius-sm` are new tokens as of the Linear-inspired redesign
 * (2026-09-30) and are spent here exactly as app.css spends them — on chrome
 * (the slip's own buttons, the assign menu), never on a row or a column: the
 * slip stays a ruled row, not a card, and `border-radius: 0` is still the
 * default for everything in this file that does not name one explicitly. If
 * something here looks like a new idea it is a new ARRANGEMENT of old parts,
 * which is the licence ui/CONTRACT.md §7 grants a region and the only one it
 * grants.
 *
 * ── The two weights carry the whole structure ──────────────────────────────
 *
 * `--w-rule`, one device pixel, separates LIKE FROM LIKE: slip from slip, one
 * person's column from the next.
 *
 * `--w-band`, 2px, separates UNLIKE FROM UNLIKE, and it is spent three times:
 *
 *   · under a column head, which is a heading over what it heads;
 *   · between Unassigned and the people, because a triage pile and a person's
 *     hand are not two of the same thing — this is the board's transposition of
 *     the register's own load-bearing rule, the unclaimed pile above the rest;
 *   · between NOT STARTED and IN HAND inside a person's column, which is the
 *     distinction the product refuses to let anyone collapse.
 *
 * There is no third weight. A slip's internal lines are not ruled at all: the
 * ruling between slips is what makes a slip one object, and ruling inside it
 * would make it three.
 *
 * ── The pitch ──────────────────────────────────────────────────────────────
 *
 * A slip is exactly three register rows tall — `calc(var(--m-row) * 3)` — so a
 * column of slips lands on the same 28px grid as the rail beside it and the fact
 * lines below it. `--m-row` is written by rules.js as a whole number of device
 * pixels; this file reads it and never sets it, which is the Whole-Pixel Rule.
 */

/* ── The region ──────────────────────────────────────────────────────────── */

/* The board owns the region's height exactly, and the COLUMNS are what scroll —
   in both directions, inside their own box. That is not a detail: with the whole
   region scrolling, the columns' minimum width stretched the head with them and
   pushed the head's own controls off the right of the screen, where nothing
   could reach them. Confining the scroll to the columns also gives the sticky
   column heads the right thing to stick to. */
.board {
  height: 100%;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  min-width: 0;
}

/* Placed explicitly, and it is load-bearing rather than tidy. The empty note is
   `hidden` most of the time, and a display:none grid item is not an item at all
   — so with auto-placement the columns fell back into row 2, which is `auto`,
   and stopped filling the region. The columns then ended wherever the tallest
   pile ended, and the dead space below them was not a drop target. */
.board__head {
  grid-row: 1;
}

.board__empty {
  grid-row: 2;
}

.board__cols {
  grid-row: 3;
}

/* Head ------------------------------------------------------------------- */

.board__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "where";
  align-items: baseline;
  gap: var(--s-2) var(--s-4);
  padding: var(--s-3) var(--s-4) var(--s-2);
  border-bottom: var(--w-rule) solid var(--rule-2);
  background: var(--ink-1);
}

.board__where {
  grid-area: where;
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  min-width: 0;
}

.board__title {
  margin: 0;
  font-size: var(--t-sub);
  font-weight: 600;
  letter-spacing: -0.005em;
  white-space: nowrap;
}

/* The state of the desk, stated once, in the figure face because every part of
   it is a measurement. It is not a hero metric and it is not a tile. */
.board__count {
  margin: 0;
  font-family: var(--font-figure);
  font-size: var(--t-micro);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--bone-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The empty note. Reuses the shell's own `.stub__empty` — a written note against
   a heavy band — and only sets it into the board's inset. */
.board__empty {
  margin: var(--s-5) var(--s-5) var(--s-5);
}

/* ── Columns ─────────────────────────────────────────────────────────────── */

/* Columns SCROLL rather than compress, the same decision the rail makes at its
   narrow breakpoint: a column too narrow to read a folio in is not a smaller
   column, it is an unusable one. */
.board__cols {
  display: grid;
  grid-auto-flow: column;
  /* 196px, not 206: Unassigned and four people is five columns, and five of
     these plus the rail is what a 1280px window has room for once the resting
     trail has given the register its width back (app.css). At 206 the fifth
     column fell off the right-hand edge of every laptop the desk actually uses,
     which on THIS board means a colleague's whole pile was out of sight. */
  grid-auto-columns: minmax(196px, 1fr);
  align-items: stretch;
  min-height: 0;
  overflow: auto;
}

.col {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  min-width: 0;
  border-right: var(--w-rule) solid var(--rule-1);
}

.col:last-child {
  border-right: 0;
}

/* Unassigned is not one of the people. It is the triage pile, and the desk's
   documented practice is to work from it rather than from the top — so the rule
   between it and the first person is the band, not the hairline. */
.col[data-kind="pile"] {
  border-right-width: var(--w-band);
  border-right-color: var(--rule-3);
}

/* And neither is Off the roster. */
.col[data-kind="other"] {
  border-left: var(--w-band) solid var(--rule-3);
}

.col__head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  height: var(--m-row);
  /* The name sits in the middle of the column; the count is pinned to the right
     edge (below), and this side padding is the room it needs, so a long name
     ellipsises before it reaches the count instead of running under it. */
  padding: 0 var(--s-7);
  background: var(--ink-2);
  border-bottom: var(--w-band) solid var(--rule-3);
}

/* The head is the column's handle. Only a person's column has one — Unassigned
   is the pile every gesture starts from and stays leftmost, Off the roster is
   not a seat — so the affordance is attached to the attribute that says the
   head can actually be picked up, and never to `.col__head` at large. */
.col__head[draggable="true"] {
  cursor: grab;
}

.col__head[draggable="true"]:active {
  cursor: grabbing;
}

/* The column in flight, marked the way a slip in flight is marked: it is still
   drawn where it was, because nothing has moved yet. */
.col__head[data-dragging="true"] {
  opacity: 0.4;
}

/* Where it would land: a rule, on the boundary it would take, in the one colour
   licensed to mean live. It is `--w-band` because it separates unlike from
   unlike — this column from the one beside it — and it is drawn against the
   head rather than the whole column so that the answer sits where the question
   was asked. */
.col[data-headdrop] .col__head::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: var(--w-band);
  background: var(--live);
}

.col[data-headdrop="before"] .col__head::after {
  left: 0;
}

.col[data-headdrop="after"] .col__head::after {
  right: 0;
}

/* While a head is in flight the slips are not the subject, and a slip lighting
   up under the pointer would be answering a gesture nobody made. */
.board[data-headdrag="true"] .slip__link:hover {
  background: transparent;
}

.col__name {
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bone-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Self, said in a word as well as in colour — a state is never encoded in hue
   alone here. `--live` means live AND self, and the caller's own hand is the
   second of those. */
.col[data-self="true"] .col__head {
  background: var(--live-wash);
}

.col[data-self="true"] .col__name {
  color: var(--bone-1);
}

.col__mine {
  font-family: var(--font-figure);
  font-size: var(--t-micro);
  color: var(--live-ink);
  letter-spacing: 0.04em;
}

.col__count {
  position: absolute;
  right: var(--s-3);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-figure);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--bone-3);
}

.col[data-self="true"] .col__count {
  color: var(--live-ink);
}

.col__note {
  margin: 0;
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--bone-3);
  border-bottom: var(--w-rule) solid var(--rule-1);
}

.col__body {
  min-height: 0;
  background: var(--ink-1);
}

.col[data-kind="pile"] .col__body,
.col[data-kind="other"] .col__body {
  background: var(--ink-2);
}

/* The column under the pointer during a drag. The whole column takes the hover
   ground — the drop target is the column, so the column is what lights. */
.col[data-drop="claim"] .col__body,
.col[data-drop="take"] .col__body,
.col[data-drop="assign"] .col__body,
.col[data-drop="unassign"] .col__body {
  background: var(--ink-3);
  outline: var(--w-band) solid var(--live);
  outline-offset: calc(-1 * var(--w-band));
}

/* A column that is where the slip already is: reachable, and worth nothing. */
.col[data-drop="idle"] .col__body {
  background: var(--ink-3);
}

/* ── Piles ───────────────────────────────────────────────────────────────── */

.pile {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pile__head {
  margin: 0;
  height: var(--m-row);
  display: flex;
  align-items: center;
  padding: 0 var(--s-3);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bone-3);
}

/* One pile from another is unlike from unlike, so the rule between them is the
   band. It is drawn only where there IS another pile above — the first pile in a
   column already sits under the column head's band. */
.pile:not(:empty) ~ .pile__head:not([hidden]) {
  border-top: var(--w-band) solid var(--rule-3);
}

.pile:not(:empty) ~ .pile__head[hidden] + .pile:not(:empty) {
  border-top: var(--w-band) solid var(--rule-3);
}

/* ── The slip ────────────────────────────────────────────────────────────── */

.slip {
  position: relative;
  border-bottom: var(--w-rule) solid var(--rule-1);
}

/* Three register rows exactly. The folio line and the queue line take what they
   need; the title takes the rest, which is two lines of it at this measure. A
   title longer than that is clamped and the entry is one click away — a slip is
   an index entry, not the entry itself. */
.slip__link {
  display: grid;
  grid-template-rows: var(--m-row) minmax(0, 1fr) auto;
  height: calc(var(--m-row) * 3);
  padding: 0 var(--s-3) var(--s-1);
  color: inherit;
  text-decoration: none;
  cursor: grab;
}

.slip__link:active {
  cursor: grabbing;
}

.slip__link:hover {
  background: var(--ink-3);
}

/* The open entry, marked the way the rail marks the selected queue. */
.slip[data-selected="true"] .slip__link {
  background: var(--live-wash);
}

.slip__line {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.slip__mark,
.slip__notch {
  line-height: 0;
  flex: none;
}

.slip__folio {
  font-family: var(--font-figure);
  font-size: var(--t-small);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--live-ink);
  white-space: nowrap;
}

.slip__age,
.slip__flight {
  margin-left: auto;
  font-family: var(--font-figure);
  font-size: var(--t-micro);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--bone-3);
  white-space: nowrap;
}

.slip__title {
  align-self: center;
  font-size: var(--t-base);
  line-height: 1.35;
  color: var(--bone-1);
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

/* The queue, in the margin's own face. A queue name is prose, never a figure and
   never a chip: there is no ground behind it and no box around it. */
.slip__foot {
  display: flex;
  align-items: center;
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bone-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Assigned and not yet started.
 *
 * The status mark already says it — an empty ruled cell against a filled one,
 * legible with all colour removed — and the pile it sits in says it again. This
 * third statement is the one a colleague reads without looking: the folio drops
 * to the secondary ink, so a column of work in hand and a column of work merely
 * handed over do not read the same from across a desk. */
.slip[data-state="unstarted"] .slip__folio {
  color: var(--bone-2);
}

/* In flight. Not a move: the slip stays exactly where it was until the desk
   answers, because two people work here at once and the server is the record. */
.slip[data-pending="true"] {
  opacity: 0.55;
}

.slip[data-pending="true"] .slip__link {
  cursor: progress;
  pointer-events: none;
}

.slip[data-dragging="true"] {
  opacity: 0.4;
}

/* The slip's own verbs, over the folio line and not there until you are. Ghost
   buttons in the shell's own idiom: a hairline box, micro caps, no ground until
   the pointer is on them. They carry a `title` and no visible hint — a sentence
   under every slip explaining what Claim does would be the same sentence twenty
   times down a column. */
.slip__acts {
  position: absolute;
  top: 2px;
  right: var(--s-2);
  display: flex;
  gap: var(--s-1);
  opacity: 0;
}

.slip__btn {
  font: inherit;
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bone-2);
  background: var(--ink-3);
  border: var(--w-rule) solid var(--rule-2);
  border-radius: var(--radius-sm);
  padding: 1px var(--s-1);
  line-height: 1.3;
  cursor: pointer;
}

/* The committed one of the two. It is the desk's own gesture — the unclaimed
   pile into your hand — and it is the only place on a slip that takes ink. */
.slip__btn--act {
  color: var(--bone-1);
  border-color: var(--rule-3);
}

.slip:hover .slip__acts,
.slip__acts:focus-within,
.slip[data-menu="true"] .slip__acts {
  opacity: 1;
}

.slip__btn:hover {
  color: var(--bone-1);
  border-color: var(--rule-3);
  background: var(--ink-4);
}

.slip__btn:disabled {
  cursor: default;
  color: var(--bone-3);
  border-color: var(--rule-2);
}

/* ── The assign menu ─────────────────────────────────────────────────────── */

/* Anchored to the viewport against the slip it belongs to, so it is not clipped
   by the region's own scroll. It closes on a scroll rather than chasing one. */
.board__menu {
  position: fixed;
  z-index: 20;
  background: var(--ink-2);
  border: var(--w-rule) solid var(--rule-3);
  border-radius: var(--radius);
  padding: var(--s-1);
  min-width: 12rem;
  overflow: hidden;
}

.board__menuitem {
  display: flex;
  align-items: baseline;
  width: 100%;
  height: var(--m-row);
  padding: 0 var(--s-2);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: var(--t-base);
  color: var(--bone-2);
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
}

.board__menuitem:hover,
.board__menuitem:focus-visible {
  background: var(--ink-3);
  color: var(--bone-1);
}

/* ── Motion ──────────────────────────────────────────────────────────────── */
/*
 * THE LANDING, and it is the region's one authored moment.
 *
 * board.js names every visible slip for the duration of a move and lets the
 * default view-transition group animate it, so a slip travels from the column it
 * left to the column the desk put it in. That is what makes a non-optimistic
 * drop still feel like a drop: the movement is real, it is just the server's
 * answer arriving rather than a guess.
 *
 * The two suppressions below are the price of borrowing a document-level
 * transition for a change inside one region. app.css rules the trail in from the
 * fold on `view-transition-name: trail`, unconditionally — so without this the
 * trail would slide every time somebody allocated a ticket, and the root group's
 * cross-fade would ghost the whole page behind it. `data-board-move` is set on
 * the document element by board.js for exactly the length of the transition.
 *
 * Reduced motion is answered twice, as DESIGN.md requires: board.js does not
 * call startViewTransition at all when it is set, and app.css's own reduce block
 * already stops every view-transition animation if a browser starts one some
 * other way.
 */

:root[data-board-move="true"]::view-transition-old(trail),
:root[data-board-move="true"]::view-transition-new(trail),
:root[data-board-move="true"]::view-transition-old(root),
:root[data-board-move="true"]::view-transition-new(root) {
  animation: none;
}

/* ── Paper ───────────────────────────────────────────────────────────────── */
/*
 * The board is an INSTRUMENT, not evidence. What a colleague attaches to a
 * change request is an entry and its trail, and app.css already prints that.
 * When the register prints alone — on /app, with nothing open — it prints as a
 * plain list of who is holding what, with every control gone.
 */

@media print {
  .board__menu,
  .slip__acts,
  .col__note {
    display: none !important;
  }

  .board,
  .board__cols {
    display: block;
  }

  .col {
    border-right: 0;
    border-bottom: var(--w-band) solid var(--rule-3);
    break-inside: avoid;
  }

  .col__head {
    /* Not sticky on paper, but still the box the count is pinned in. */
    position: relative;
    background: transparent;
  }

  .slip__link {
    height: auto;
    display: block;
  }
}

/* ── Adaptation ──────────────────────────────────────────────────────────── */
/*
 * Desktop-primary, like the rest of the desk. The columns keep ONE readable
 * measure at every width and the region scrolls sideways when they run out of
 * room — a column too narrow to read a folio in is not a smaller column, it is
 * an unusable one, and this board's columns are people rather than queues, so a
 * column that falls off the edge is a colleague whose pile nobody can see.
 *
 * The width itself is not defended here. It is defended in app.css, by the
 * resting trail giving the register back the space it was holding for a note.
 */

@media (max-width: 820px) {
  .board__head {
    padding: var(--s-2) var(--s-3);
  }

  /* Touch has no hover to reveal an affordance with, and these are the only
     path to a write without a keyboard. */
  .slip__acts {
    opacity: 1;
  }
}

/* The supervisor mark on a slip's folio line, beside the status mark and the
 * priority notch. Hidden unless the two names disagree. */
.slip__flag {
  display: inline-flex;
  align-items: center;
  color: var(--rubric-ink);
}
