/* components.css — Namesome shared components.
   Every tool page composes its UI from these classes; nothing here is page-specific.
   Ported from the signed-off Phase B prototype (docs/prototype/assets/css/components.css,
   frozen reference): every rule below is byte-identical to it, and this header plus the
   `min-inline-size: 0` note above the chip is the whole of the difference.

   Rules this file keeps:
   • Tokens only. No literal colours. Where a state needs a shade the token set does not
     carry, it is derived with color-mix() from a token — never typed as hex.
   • Logical properties only (padding-inline, inset-inline-start, border-block-end …).
     The two physical values are `transform: scaleX(-1)` on directional icons and the
     select chevron's `background-position` (which has no logical form) — each is a
     mirror, written out with its `[dir="rtl"]` twin.
   • A handful of literal control sizes remain (2.75rem touch target, 1px/2px hairlines,
     icon squares) — there is no size token for them and base.css sets the precedent.
   • Status is never colour alone: every state also carries a word, a glyph or a position.
   • A flex or grid ITEM that carries a name sets `min-inline-size: 0` — see the note
     above .chip__name. The check battery enforces it.

   Order: buttons · icons · inputs & fields · card · chips · entries editor · pills ·
          switch · tabs · modal · result banner · projector · print
*/

/* ── buttons ─────────────────────────────────────────────────────────── */
/* One base, three variants. Works on <button> and on <a>. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-block-size: 2.75rem;            /* 44px — the house touch-target floor */
  padding-block: var(--sp-2);
  padding-inline: var(--sp-4);
  background: var(--surface);
  color: var(--ink);
  /* --line is 1.25:1 against --bg: a hairline, not a control edge. Focusable
     controls take --ink-2 or --brand so their boundary clears 3:1. */
  border: 1px solid var(--ink-2);
  border-radius: var(--r-1);
  font-size: var(--fs-1);
  font-weight: 700;
  line-height: var(--lh-tight);
  text-align: center;
  text-decoration: none;
  transition: background-color var(--t-fast) ease-out,
              border-color var(--t-fast) ease-out,
              color var(--t-fast) ease-out;
}
.btn:hover { background: var(--surface-2); }

.btn-primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}
/* No hover token exists. Mixing --brand toward --ink darkens it in light and
   lightens it in dark — contrast against --brand-ink rises either way. */
.btn-primary:hover {
  background: color-mix(in srgb, var(--brand) 88%, var(--ink));
  border-color: color-mix(in srgb, var(--brand) 88%, var(--ink));
}

.btn-quiet {
  background: none;
  border-color: transparent;
  color: var(--ink-2);
  font-weight: 600;
}
.btn-quiet:hover { background: var(--surface-2); color: var(--ink); }

.btn-danger { color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: var(--surface); }

/* The × before the word on a Close (owner, 2026-09-03: a bare word did not read as the way
   out). Decorative: the span is aria-hidden and the localized word remains the label.
   `.btn-close` beside it is a marker — "this button carries the glyph" — and needs no rule of
   its own: `.btn` is already an inline-flex row with `gap: var(--sp-2)`, so a span placed
   first sits at the inline-start with the right spacing in both directions. */
.btn-close__x { font-size: 1.35em; font-weight: 400; line-height: 1; }

/* Last, so it wins over every variant's hover. */
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--ink-2);
  cursor: not-allowed;
}

/* ── icons ───────────────────────────────────────────────────────────── */
/* Inline SVG sized to the text it sits beside; colour comes from the text. */
.icon {
  inline-size: 1.125em;
  block-size: 1.125em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Directional icons mirror in RTL. Checkmarks, play triangles, the wheel and the
   logo never do — give those no mirroring class. */
[dir="rtl"] .icon-chevron { transform: scaleX(-1); }

/* ── inputs and fields ───────────────────────────────────────────────── */
.input {
  inline-size: 100%;
  min-block-size: 2.75rem;
  padding-block: var(--sp-2);
  padding-inline: var(--sp-3);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--ink-2);
  border-radius: var(--r-1);
  font-size: var(--fs-1);
  line-height: var(--lh-body);
  transition: border-color var(--t-fast) ease-out;
}
.input::placeholder { color: var(--ink-2); opacity: 1; }
.input:focus-visible { border-color: var(--brand); }   /* ring itself is base.css */
.input:disabled { background: var(--surface-2); color: var(--ink-2); cursor: not-allowed; }
/* THE FIELD A REFUSAL IS ABOUT (Task 18). Keyed on `aria-invalid` rather than on a class,
   because that is what the state IS and it is what a screen reader announces — a class
   would be a second, silent copy of the same fact. Two channels, never colour alone: the
   border goes to --danger AND thickens, so it survives a mono print and a colour-blind
   reader, which is the rule `.pill-danger` and `.ss-alert` already keep. */
.input[aria-invalid="true"] { border-color: var(--danger); border-width: 2px; }

textarea.input { min-block-size: 8rem; resize: vertical; }
/* The arrow is drawn, not native: a styled select in Chrome and WebKit parks the
   native arrow hard against the control's end edge (owner change, Checkpoint 2).
   appearance:none swaps it for the token chevron, inset a full --sp-3 from the
   edge. background-position has no logical form yet, so this is the file's second
   physical-value exception beside scaleX(-1); the [dir="rtl"] line below is its
   mirror, written out. */
select.input {
  appearance: none;
  padding-inline-end: var(--sp-7);
  background-image: var(--chevron-down);
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  background-size: 1rem;
}
[dir="rtl"] select.input { background-position: left var(--sp-3) center; }

/* Label, control, hint.
   ┌─ RULE: a .hint inside a .field is WIRED to its control ──────────────────────────┐
   │   <div class="field">                                                            │
   │     <label class="field__label" for="x">…</label>                                │
   │     <input class="input" id="x" aria-describedby="x-hint">                       │
   │     <span class="hint" id="x-hint">…</span>                                      │
   │   </div>                                                                         │
   │                                                                                  │
   │ The hint sits AFTER the control, so a screen-reader user tabbing into the field   │
   │ hears the label and then nothing: focus does not read what comes next. The        │
   │ association has to be stated, and `aria-describedby` is the attribute that states │
   │ it — the description is then read after the label, every time, in every mode.     │
   │                                                                                  │
   │ WHY NOT `aria-labelledby`, and why not fold the hint into the label: a label is   │
   │ the control's NAME and is repeated in every list of form fields; a hint is a      │
   │ DESCRIPTION and is read once, after it. "One name per line — Arabic, Chinese, any │
   │ language. Or separate them with any of these: , ; ،" is not a field's name.       │
   │                                                                                  │
   │ SEVERAL IDS ARE FINE and are how an error joins a hint: `aria-describedby="x-hint │
   │ x-error"` reads the guidance and then what went wrong (src/pages/lobby.tsx does    │
   │ exactly this). Order in the attribute is reading order.                           │
   │                                                                                  │
   │ Check battery job 9 holds every built page to this: a `.hint` inside a `.field`   │
   │ that holds a control, with no `aria-describedby` pointing at it, is a finding. A   │
   │ hint that really is standalone prose rather than a description of the control     │
   │ says so with `data-hint-standalone` and gives its reason there.                   │
   └──────────────────────────────────────────────────────────────────────────────────┘ */
.field { display: grid; gap: var(--sp-2); }
.field__label { font-weight: 600; }

/* Small quiet caption. Used inside .field and anywhere a line of guidance sits
   under something. */
.hint { font-size: var(--fs-0); color: var(--ink-2); }

/* ── card ────────────────────────────────────────────────────────────── */
/* A card and a modal are the same surface; only the sizing differs. */
.card,
.modal {
  display: grid;
  gap: var(--sp-4);
  align-content: start;
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}

.card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
  padding-block-end: var(--sp-3);
  border-block-end: 1px solid var(--line);
}
/* Pushes everything after the title to the far end, in either direction. */
.card__title {
  margin-inline-end: auto;
  font-size: var(--fs-2);
  font-weight: 800;
  line-height: var(--lh-tight);
}
.card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: flex-end;
}

/* ── chips ───────────────────────────────────────────────────────────── */
/* One entry: an index badge in the wheel's colour cycle, the name, a remove ×.
   The chip list is the wheel's legend — chip N and slice N share a colour. */
.chip {
  --chip-bg: var(--surface);
  --chip-ink: var(--ink);
  --chip-edge: var(--line);

  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 2.75rem;
  padding-inline: var(--sp-4);
  padding-block: var(--sp-1);
  max-inline-size: 100%;
  background: var(--chip-bg);
  color: var(--chip-ink);
  border: 1px solid var(--chip-edge);
  border-radius: var(--r-round);
  font-size: var(--fs-1);
}
/* Segment colours are identical in both themes, so a chip that carries one takes
   the fixed --seg-ink instead of --ink, which would flip to near-white in dark. */
.chip-seg-1 { --chip-bg: var(--seg-1); --chip-edge: var(--seg-1); --chip-ink: var(--seg-ink); }
.chip-seg-2 { --chip-bg: var(--seg-2); --chip-edge: var(--seg-2); --chip-ink: var(--seg-ink); }
.chip-seg-3 { --chip-bg: var(--seg-3); --chip-edge: var(--seg-3); --chip-ink: var(--seg-ink); }
.chip-seg-4 { --chip-bg: var(--seg-4); --chip-edge: var(--seg-4); --chip-ink: var(--seg-ink); }
.chip-seg-5 { --chip-bg: var(--seg-5); --chip-edge: var(--seg-5); --chip-ink: var(--seg-ink); }
.chip-seg-6 { --chip-bg: var(--seg-6); --chip-edge: var(--seg-6); --chip-ink: var(--seg-ink); }

.chip__n {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--sp-5);
  block-size: var(--sp-5);
  border-radius: var(--r-round);
  /* A wash of the chip's own ink: readable on white and on all six segments. */
  background: color-mix(in srgb, var(--chip-ink) 14%, transparent);
  font-size: var(--fs-0);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  direction: ltr;                     /* a position is a number, never reordered */
}
/* ┌─ RULE: a content-carrying flex/grid ITEM sets `min-inline-size: 0` ─────────────┐
   │ A flex or grid item's automatic minimum size is its MIN-CONTENT size, so an item │
   │ holding a name refuses to shrink below the longest unbreakable run in it. One    │
   │ pasted "Bartholomew-Fitzgerald" or one long CJK run then pushes its siblings —   │
   │ the index badge, the remove ×, the skill number — out of the row, or bursts the  │
   │ card. `min-inline-size: 0` releases that floor; `overflow-wrap: anywhere` gives  │
   │ the text somewhere to go once it has. The pair is the fix, not either alone.     │
   │                                                                                  │
   │ It applies to an item WITH SIBLINGS THAT CANNOT WRAP AWAY from it. This list is  │
   │ the whole of it for this file, and the check battery is written FROM this list — │
   │ so a class that carries content belongs in one of the two columns or the sweep   │
   │ fires. (Audited against every display:flex/grid rule in this file.)              │
   │                                                                                  │
   │ GUARDED — carries content, siblings are trapped on the same line:                │
   │   .chip__name              item of .chip (no wrap), beside .chip__n and .chip__x │
   │   .team-card__member-name  item of .team-card__member (no wrap), beside __tag    │
   │                            and __skill                                           │
   │                                                                                  │
   │ EXEMPT — each with the reason, because "it looked fine" is not one:              │
   │   .result-banner__name  sole content item of a single-column grid; its           │
   │                         `overflow-wrap: anywhere` already takes min-content down │
   │                         to one character, so there is nothing to squeeze.        │
   │   .qr-card__name        same shape, same reason.                                 │
   │   .card__title          item of .card__head, which is `flex-wrap: wrap`: a long  │
   │                         title takes its own line rather than squeezing           │
   │                         .card__actions. Wrapping IS the release valve here.      │
   │   .team-card__name      item of .team-card__head, also `flex-wrap: wrap`; same   │
   │                         reason, beside .team-card__meta and the per-card .btn.   │
   │   .team-card__meta      a label and a number, not free text.                     │
   │   .team-card__tag       a short tag, `flex: none` by intent — it is meant to     │
   │                         keep its width.                                          │
   │   .qr-card__caption     fixed guidance string, grid item, `max-inline-size: 24ch`│
   │   .ns-import__preview   item of `.ns-tools` (order: 1, full width) —             │
   │                         `overflow-y: auto` already makes it a scroll             │
   │                         container, so its automatic minimum size resolves        │
   │                         to 0 on its own — nothing here to squeeze.               │
   │   .ns-import__error     item of `.ns-tools`, full width, alone on its line;      │
   │                         its content is always one of two fixed, translated       │
   │                         sentences (csv-import.js), never a user's own text —     │
   │                         no unbreakable run here to guard against.                │
   │                                                                                  │
   │ Known residual, deliberately NOT fixed here: the two `flex-wrap: wrap` exemptions│
   │ wrap to their own line but carry no `overflow-wrap`, so one unbreakable run wider │
   │ than the card still overflows it. Both are signed-off prototype rules and adding  │
   │ a declaration would change approved rendering — ledgered for the owner, not      │
   │ patched in a port.                                                               │
   └──────────────────────────────────────────────────────────────────────────────────┘ */
.chip__name { min-inline-size: 0; overflow-wrap: anywhere; }

/* Sits at the end of the chip, so the chip loses its end padding to it. */
.chip:has(.chip__x) { padding-inline-end: var(--sp-1); }
.chip__x {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--sp-6);
  block-size: var(--sp-6);
  border-radius: var(--r-round);
  color: inherit;
  font-size: var(--fs-2);
  line-height: 1;
  transition: background-color var(--t-fast) ease-out;
}
.chip__x:hover { background: color-mix(in srgb, currentColor 18%, transparent); }

/* Duplicate. An outline, not a border: --warn is only 1.5–2.8:1 against the
   segment colours but 4.8:1 against the page, and an outline adds no reflow.
   Never the only signal — the chip also carries a title and the editor counts
   duplicates in a .pill-warn. */
.chip-dup {
  outline: 2px dashed var(--warn);
  outline-offset: 2px;
}

/* ── entries editor ──────────────────────────────────────────────────── */
/* The signature component: a card holding the textarea and the live chip list.
   Its parts belong together, so it sits tighter than a default card. */
.entries-editor { gap: var(--sp-3); }

.entries-editor__list {
  display: flex;
  flex-wrap: wrap;
  /* --sp-3, not --sp-2: a duplicate chip's dashed outline needs 4px of air. */
  gap: var(--sp-3);
}
.entries-editor__empty {
  padding: var(--sp-4);
  border: 1px dashed var(--line);
  border-radius: var(--r-1);
  color: var(--ink-2);
  font-size: var(--fs-0);
  text-align: center;
}

/* ── tools row ────────────────────────────────────────────────────────── */
/* The list editor's footer (owner fix B2, 2026-09-03): shuffle/clear, the CSV button and the
   share seam used to be three separate `.card__actions` boxes, each right-hugged by that
   class's own `justify-content: flex-end` — three ragged rows instead of one. `.ns-tools` is a
   `.row` (base.css) modifier: `.row` already gives flex, wrap, `align-items: center` and a gap,
   with no `justify-content`, so it starts at the inline-start edge in both directions on its
   own. `.card__actions` never set a margin (its rule, above): the space the tools row used to
   sit in came from `.entries-editor`'s own grid `gap: var(--sp-3)`, the same gap every other
   stacked child of the card gets. `margin-block-start: var(--sp-2)` is a deliberate EXTRA gap
   on top of that one, so the row reads as the card's footer rather than just its next
   paragraph. */
.ns-tools { margin-block-start: var(--sp-2); }

/* ── CSV import ──────────────────────────────────────────────────────── */
/* The shared import widget (ruling R28): a button, and a preview that has to be
   accepted before a name reaches the roster. Its markup is _controls.tsx's
   `CsvImport` and its behaviour static/js/csv-import.js; it sits INSIDE an
   entries-editor card on three pages, so it takes the card's own gaps and adds
   only what a nested panel needs.

   The preview is a well rather than a card: a card inside a card is two borders
   and two shadows arguing, and this one is temporary — it exists between
   choosing a file and pressing Add.

   THE WIDGET CONTRIBUTES ITS BUTTON to the `.ns-tools` row it sits in and drops its preview and
   its one-line error below the whole row: `display: contents` hands the children to the row's
   flex layout, so `.ns-import` itself draws no box; the two block children take the full width
   and sort last. */
.ns-import { display: contents; }
.ns-import__pick { display: inline-flex; }

.ns-import__preview {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  background: var(--surface-2);
  /* The file decides how many rows there are, and a 200,000-row file must not
     make a page you cannot scroll past. The list inside is capped at 20 rows by
     the script; this is the belt to that pair of braces. */
  max-block-size: 24rem;
  overflow-y: auto;
}

/* Not an error colour: a file that is too big or empty is a thing to fix, not a
   failure of the page. `--danger` is kept for the states that really are. */
.ns-import__error { color: var(--ink-2); }

/* Both drop below the row as full-width items, last — see the widget note above. */
.ns-import__error,
.ns-import__preview { flex: 1 1 100%; order: 1; }

/* Nothing to import on paper — the panel is a control, and by the time a sheet is printed the
   names are in the editor above it. Named individually: a `display: contents` box (`.ns-import`
   itself) cannot be hidden by its own display, so the rule names the children instead. */
@media print {
  .ns-import__pick, .ns-import__error, .ns-import__preview { display: none; }
}

/* ── pills ───────────────────────────────────────────────────────────── */
/* Compact status. Each carries a glyph, so the state survives a colour-blind
   reader, a projector and a black-and-white print. */
.pill-ok,
.pill-warn,
.pill-danger {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding-block: 2px;
  padding-inline: var(--sp-2);
  border: 1px solid currentColor;
  border-radius: var(--r-round);
  font-size: var(--fs-0);
  font-weight: 700;
  white-space: nowrap;
}
.pill-ok { color: var(--ok); }
.pill-warn { color: var(--warn); }
.pill-danger { color: var(--danger); }
.pill-ok::before { content: "\2713"; font-weight: 800; }   /* ✓ — never mirrored */
.pill-warn::before { content: "!"; font-weight: 800; }
.pill-danger::before { content: "\2717"; font-weight: 800; }   /* ✗ — never mirrored */

/* ── switch ──────────────────────────────────────────────────────────── */
/* A real checkbox with a drawn track. The input stays in the layout (opacity 0,
   not display:none) so it keeps its keyboard focus and its label association. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: 2.75rem;
  cursor: pointer;
}
.switch__input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
  margin: 0;
}
.switch__track {
  position: relative;
  flex: none;
  inline-size: 3rem;
  block-size: 1.75rem;
  background: var(--surface-2);
  border: 1px solid var(--ink-2);
  border-radius: var(--r-round);
  transition: background-color var(--t-fast) ease-out, border-color var(--t-fast) ease-out;
}
.switch__thumb {
  position: absolute;
  inset-block-start: 50%;
  /* Logical inset, so the thumb travels the other way in RTL with no override. */
  inset-inline-start: 2px;
  translate: 0 -50%;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  background: var(--ink-2);
  border-radius: var(--r-round);
  transition: inset-inline-start var(--t-fast) ease-out,
              background-color var(--t-fast) ease-out;
}
.switch__input:checked + .switch__track {
  background: var(--brand);
  border-color: var(--brand);
}
.switch__input:checked + .switch__track .switch__thumb {
  inset-inline-start: calc(100% - 1.25rem - 2px);
  background: var(--brand-ink);
}
.switch__input:focus-visible + .switch__track {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
.switch__input:disabled + .switch__track { opacity: .5; cursor: not-allowed; }
/* …and the WHOLE control, not just its track. `.switch` is a <label> carrying
   `cursor: pointer`, so a disabled switch offered a pointer over its words and a
   not-allowed cursor over its track — the label is the bigger target and it is the
   half people aim at. One rule rather than moving the cursor onto the track alone,
   because a label IS the control here: clicking the word toggles it. */
.switch:has(.switch__input:disabled) { cursor: not-allowed; }

/* Optional state word: both strings live in the markup, where the i18n loader
   can reach them, and CSS shows the one that applies. */
.switch__on,
.switch__off { font-size: var(--fs-0); font-weight: 700; color: var(--ink-2); }
.switch__input:checked ~ .switch__off { display: none; }
.switch__input:not(:checked) ~ .switch__on { display: none; }

/* ── tabs ────────────────────────────────────────────────────────────── */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  border-block-end: 1px solid var(--line);
}
.tabs__tab {
  min-block-size: 2.75rem;
  padding-inline: var(--sp-4);
  margin-block-end: -1px;             /* the underline sits on the tablist rule */
  border-block-end: 2px solid transparent;
  color: var(--ink-2);
  font-size: var(--fs-1);
  font-weight: 700;
  transition: color var(--t-fast) ease-out, border-color var(--t-fast) ease-out;
}
.tabs__tab:hover { color: var(--ink); }
.tabs__tab[aria-selected="true"] {
  color: var(--ink);
  border-block-end-color: var(--brand);
}
.tabs__panel { padding-block-start: var(--sp-4); }
/* No `[hidden]` rule here: base.css owns `[hidden] { display: none !important }`. */

/* ── modal ───────────────────────────────────────────────────────────── */
/* Opened with Proto.openModal(id) / closed with Proto.closeModal(id): focus
   trap, Esc, backdrop click, focus returned to the opener. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  overflow: auto;
  overscroll-behavior: contain;
  /* The scrim has to darken in BOTH themes, so it cannot be built from --ink or
     --bg, which swap. --seg-ink is the token set's one non-inverting dark. */
  background: color-mix(in srgb, var(--seg-ink) 66%, transparent);
  animation: ns-fade-in var(--t-fast) ease-out;
}
/* No `[hidden]` rule here either: base.css owns `[hidden]` for the whole suite. */

.modal {
  inline-size: 100%;
  max-inline-size: 34rem;   /* three actions fit on one row at this measure */
  max-block-size: calc(100dvh - var(--sp-6));
  overflow: auto;
  animation: ns-modal-in var(--t-base) ease-out;
}

@keyframes ns-fade-in { from { opacity: 0; } }
@keyframes ns-modal-in {
  from { opacity: 0; translate: 0 var(--sp-3); }
}

/* ── result banner ───────────────────────────────────────────────────── */
/* The winner. An aria-live="polite" region in the markup, so the name is spoken
   as well as shown. Pair it with an .eyebrow label. */
.result-banner {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-5);
  background: var(--brand-soft);
  border: 1px solid var(--brand);
  border-radius: var(--r-2);
  text-align: center;
}
.result-banner__name {
  font-size: var(--fs-4);
  font-weight: 800;
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}

/* ── team card ───────────────────────────────────────────────────────── */
/* One team from the team maker: a coloured header chip carrying the team's
   name, its members under it, and a copy button for that team alone.
   The colour comes from the same six-step cycle the chips and the wheel's
   slices use, so team 3 here and slice 3 there are the same green.
   The chip is the ONLY place the colour lives: never a filled panel — six
   saturated cards side by side would drown the names they exist to carry —
   and never an accent edge (owner ruling, Checkpoint 2: no card carries a
   one-sided border). A team sheet is printed far more often than it is
   admired. */
.team-card {
  --team-seg: var(--seg-1);

  display: grid;
  gap: var(--sp-3);
  align-content: start;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  break-inside: avoid;                /* never split a team over two printed pages */
}
.team-card-seg-1 { --team-seg: var(--seg-1); }
.team-card-seg-2 { --team-seg: var(--seg-2); }
.team-card-seg-3 { --team-seg: var(--seg-3); }
.team-card-seg-4 { --team-seg: var(--seg-4); }
.team-card-seg-5 { --team-seg: var(--seg-5); }
.team-card-seg-6 { --team-seg: var(--seg-6); }

.team-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding-block-end: var(--sp-2);
  border-block-end: 1px solid var(--line);
}
/* Segment colours are identical in both themes, so anything drawn on one takes
   the fixed --seg-ink rather than --ink, which flips to near-white in dark. */
.team-card__name {
  display: inline-flex;
  align-items: center;
  min-block-size: 2rem;
  padding-inline: var(--sp-3);
  background: var(--team-seg);
  color: var(--seg-ink);
  border-radius: var(--r-round);
  font-size: var(--fs-1);
  font-weight: 800;
  line-height: var(--lh-tight);
}
/* A label and its value. The space between them is a gap and not a typed one:
   a literal " " next to a <bdi> lands on the wrong side of the isolate in an
   RTL line, and "Skill 12" comes out as "المستوى12". */
.team-card__meta {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--fs-0);
  color: var(--ink-2);
}
/* Pushes the per-card action to the far end, in either direction. */
.team-card__head .btn { margin-inline-start: auto; }

.team-card__list { display: grid; }
.team-card__member {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding-block: var(--sp-2);
  border-block-end: 1px solid var(--line);
}
.team-card__member:last-child { border-block-end: 0; padding-block-end: 0; }
.team-card__member-name { min-inline-size: 0; overflow-wrap: anywhere; }
.team-card__tag {
  flex: none;
  padding-inline: var(--sp-2);
  border: 1px solid var(--line);
  border-radius: var(--r-round);
  font-size: var(--fs-0);
  color: var(--ink-2);
}
/* A level is a number: tabular, LTR in every locale, and parked at the end of
   the row so a column of them lines up down the card. */
.team-card__skill {
  flex: none;
  margin-inline-start: auto;
  font-size: var(--fs-0);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
  color: var(--ink-2);
}

/* ── QR card ─────────────────────────────────────────────────────────── */
/* One person's private link, on paper: their name, their QR, and the line that
   tells them what they are holding. The organiser prints a sheet, cuts it up and
   hands each card to its person — which is how a gift draw runs in a room with no
   phone numbers in it.
   A card is a dashed edge, not a filled panel: the dashes are the cut line, and
   every drop of ink on the page that is not the QR is ink the scanner has to
   ignore. .qr-sheet is the grid they print in — eight to a page. */
.qr-card {
  display: grid;
  gap: var(--sp-3);
  justify-items: center;
  align-content: center;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--r-2);
  text-align: center;
  break-inside: avoid;                /* never split a card over two printed pages */
}
.qr-card__name {
  font-size: var(--fs-2);
  font-weight: 800;
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}
/* A QR is black on white in both themes or it does not scan, so the plinth takes
   --seg-wash: the token set's one fixed, non-inverting white. The padding is the
   quiet zone the generator is asked to leave out (margin: 0). */
.qr-card__code {
  display: grid;
  place-items: center;
  inline-size: 9rem;
  block-size: 9rem;
  padding: var(--sp-3);
  background: var(--seg-wash);
  border: 1px solid var(--line);
  border-radius: var(--r-1);
}
.qr-card__code svg { display: block; inline-size: 100%; block-size: auto; }
.qr-card__caption { font-size: var(--fs-0); color: var(--ink-2); max-inline-size: 24ch; }

.qr-sheet {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

/* ── projector ───────────────────────────────────────────────────────── */
/* A page state, normally on <body>: chrome away, type up, nothing to promote.
   It does not touch the palette — only the type scale, and only inside itself. */
.projector {
  --fs-0: 1rem;
  --fs-1: 1.375rem;
  --fs-2: 1.75rem;
  --fs-3: 2.25rem;
  --fs-4: 3.25rem;
  --fs-5: 4.5rem;
}
.projector .site-nav,
.projector .skip-link,
.projector .site-foot,
.projector [data-no-projector] { display: none; }

/* ── print ───────────────────────────────────────────────────────────── */
@media print {
  /* base.css hides buttons; an open modal would otherwise print over the page. */
  .modal-backdrop { display: none !important; }

  /* Segment colours never invert, so base.css's print overrides do not touch
     them — which would leave a header chip whose fill most browsers refuse to
     print at all. The card takes the print palette's own ink instead, so the
     sheet is black on white whether or not background printing is switched on. */
  .team-card { border-color: var(--ink-2); }
  .team-card__name { background: none; color: var(--ink); border: 1px solid var(--ink); }

  /* The QR sheet is the one place in the suite where paper, not the screen, is the
     target — so it is the one place sized in millimetres. Two columns of four cards
     is eight to a page, and it has to be eight on A4 AND on US Letter, which is the
     shorter of the two: a card comes out about 58 mm tall, so 4 × 58 mm and
     3 × 5 mm of gutter is 247 mm — inside the 259 mm a Letter page has left after a
     10 mm margin, with room for a name that wraps (A4 leaves 277 mm).
     A 34 mm code carries the 47-character reveal URL: 33 modules across the 30 mm
     that is left once the 2 mm quiet zone is taken off, so about 0.9 mm a module —
     comfortably above what a phone camera resolves at reading distance. */
  .qr-sheet { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5mm; }
  .qr-card {
    /* min, not a fixed height: a name that wraps must push the card taller rather
       than spill out of it. */
    min-block-size: 56mm;
    padding: 3mm;
    gap: 2mm;
    border-color: var(--ink-2);
  }
  .qr-card__code {
    inline-size: 34mm;
    block-size: 34mm;
    padding: 2mm;
    border-color: var(--ink-2);
  }
  /* Two lines of caption on paper, set tight: the line is guidance, and every
     millimetre it gives back is a millimetre the code can have. */
  .qr-card__caption { line-height: var(--lh-tight); max-inline-size: 30ch; }
}
