/* The campaign engine's screen (Story 3.6).
 *
 * ONE stylesheet for every universe. The engine's screen is the same
 * shape in every game, and what differs - the palette, the display font
 * - already differs through the tokens below, which each universe
 * defines for itself. That is the same arrangement the campaign map
 * engine uses: one engine, a look per game, no second implementation.
 *
 * Every token here is one this shell already declares. An undeclared
 * token paints NOTHING and reports nothing, which is how var(--danger)
 * was silently dropped from 22 live rules.
 */

.ce-screen {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-3);
    padding: var(--bb-space-3);
}

.ce-panel {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
    padding: var(--bb-space-3);
    border: 1px solid var(--bb-border);
    border-radius: var(--bb-radius-md, 6px);
    background: var(--bb-bg-surface);
}

.ce-section-title {
    margin: 0;
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-sm);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--bb-accent-primary);
}

.ce-message {
    padding: var(--bb-space-2);
    border-radius: var(--bb-radius-md, 6px);
    border: 1px solid var(--bb-border);
    font-size: var(--bb-text-sm);
}
.ce-message-ok { border-color: var(--bb-accent-primary); }
/* The comment that used to sit here said "no danger token exists in
   this shell - checked, not assumed". It was wrong on both counts:
   bb-tokens.css declares --bb-accent-danger and --bb-accent-warning in
   all three of its palettes, and the inline messages 370 lines below in
   this same file already use them. So one screen had two message
   systems that disagreed about what an error looks like, and a failed
   join was painted the same colour as a successful one. Adversarial
   review, 2026-09-07. */
.ce-message-error {
    border-color: var(--bb-accent-danger);
    color: var(--bb-accent-danger);
    font-weight: 600;
}
/* Emitted by the rematch prompt in `_freeGameForm` and never styled, so
   "you have already played them twice" read as a neutral note. */
.ce-message-warn { border-color: var(--bb-accent-warning); }

/* -- the list -- */
.ce-list { display: flex; flex-direction: column; gap: var(--bb-space-1, 0.25rem); }
.ce-campaign {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: var(--bb-space-2);
    overflow: hidden;
    border: 1px solid var(--bb-border);
    border-radius: var(--bb-radius-md, 6px);
    background: var(--bb-bg-input);
    color: var(--bb-text-primary);
    text-align: left;
    cursor: pointer;
}
.ce-campaign.active { border-color: var(--bb-accent-primary); }
.ce-campaign-name { font-weight: 600; }
.ce-campaign-shape { color: var(--bb-text-muted); font-size: var(--bb-text-sm); }

/* -- the type switch --
 * Wraps rather than scrolls: at 390px five types on one row would each
 * be sixty pixels, which is the End Battle dialog's mistake in another
 * place. */
/* The campaign-type tiles were removed on 2026-09-06 - see
   `_shapeOf` in campaign_engine.js. Their descriptors were wrong and
   the tile silently overrode the controls beneath it. The form now
   asks for the axes directly, so the only thing left of that block is
   the refusal a universe attaches to a shape it will not host. */
.ce-refusal { color: var(--bb-accent-danger); }

/* THE INVITE LINK IS TEXT, NOT JUST A CLIPBOARD CALL. See the comment
   at its call site: a link that lives only inside `writeText` has no
   way to fail visibly. */
.ce-invite { display: flex; flex-direction: column; gap: var(--bb-space-1); }
.ce-invite-url { font-size: var(--bb-text-sm); }

.ce-field { display: flex; flex-direction: column; gap: 2px; }
.ce-join { display: flex; gap: var(--bb-space-2); }
.ce-join .form-input { flex: 1 1 auto; text-transform: uppercase; }

/* -- the detail panel -- */
.ce-detail-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--bb-space-2);
    flex-wrap: wrap;
}
.ce-detail-name { margin: 0; font-family: var(--bb-font-display); }
.ce-detail-code {
    font-family: var(--bb-font-display);
    letter-spacing: 0.2em;
    color: var(--bb-accent-primary);
}
.ce-detail-meta { margin: 0; color: var(--bb-text-muted); font-size: var(--bb-text-sm); }
.ce-controls { display: flex; gap: var(--bb-space-2); flex-wrap: wrap; }

.ce-standings, .ce-round, .ce-report {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-1, 0.25rem);
    margin-top: var(--bb-space-3);
}
/* Rank, name and points on the first line; what explains them on the
   second. A fifth column would have bought the victory points with an
   ellipsis through everybody's name at 320px - measured, not guessed:
   "Ravener" plus a three-digit VP pair needs 38px more than the row
   has. */
.ce-standing {
    display: grid;
    grid-template-columns: 1.75rem minmax(0, 1fr) auto;
    align-items: center;
    gap: 2px var(--bb-space-2);
    padding: var(--bb-space-2);
    border: 1px solid var(--bb-border);
    border-radius: var(--bb-radius-md, 6px);
}
/* Under the name, not under the rank: it reads as a caption on the
   player rather than as a second row of the table. */
.ce-standing-detail {
    grid-column: 2 / -1;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 2px var(--bb-space-2);
}
.ce-vp {
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
    white-space: nowrap;
    /* Tabular figures so a column of scores lines up rather than
       shimmering as the digit widths change between rows. */
    font-variant-numeric: tabular-nums;
}
/* A withdrawn player stays on the table - their games happened and
   their opponents' records depend on them - but the table says so
   rather than leaving a name nobody can explain. */
.ce-standing.is-withdrawn { opacity: 0.6; }
.ce-rank { font-family: var(--bb-font-display); color: var(--bb-accent-primary); }
.ce-who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ce-tag { color: var(--bb-text-muted); }
.ce-points {
    font-size: var(--bb-text-sm);
    white-space: nowrap;
    font-family: var(--bb-font-display);
}
/* Stated in one place rather than set twice and settled by cascade
   order: the record moved to the caption line on 2026-09-14 and took
   the muted tone with it, but kept its `font-size` from a shared rule
   two lines up, so the size it actually got depended on which of the
   two came last in the file. */
.ce-record {
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
    white-space: nowrap;
}

.ce-pairing {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--bb-space-2);
    padding: var(--bb-space-2);
    border: 1px solid var(--bb-border);
    border-radius: var(--bb-radius-md, 6px);
    /* WITHOUT THIS THE ROW RUNS OFF THE PHONE. Reported with a photo,
       2026-09-17: an awaiting game showed "waiting to be confirmed",
       CHANGE, and a TAKE IT BACK sliced in half by the screen edge.
       A row of three controls and a pair of names has never fitted on
       390px in one line, and nothing here said it could wrap. */
    flex-wrap: wrap;
}
.ce-pairing-who {
    /* `overflow: hidden` WAS THE BUG, not the ellipsis. It makes this
       item's automatic minimum size 0 instead of its content width, so
       the flex line balanced by crushing the names to nothing - the
       photo shows a row whose players are simply absent - while the
       nowrap result and the buttons kept their width and overflowed.
       A basis it can wrap onto its own line is what was wanted. */
    flex: 1 1 11rem;
    min-width: 0;
    overflow-wrap: anywhere;
}
.ce-pairing-result { font-family: var(--bb-font-display); white-space: nowrap; }

.ce-report-row { display: flex; gap: var(--bb-space-2); }
.ce-report-row .ce-score { flex: 1 1 0; min-width: 0; }
.ce-report-actions { display: flex; gap: var(--bb-space-2); flex-wrap: wrap; }

/* -- the create form's controls ------------------------------------- */

/* A number that means one number. Left at the input's default width a
   "max players" box is as wide as a campaign name, which reads as a
   field expecting a sentence. */
.ce-narrow { width: 6rem; max-width: 100%; }

.ce-hint {
    display: block;
    margin-top: 2px;
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
}

.ce-switch-row {
    display: flex;
    align-items: center;
    gap: var(--bb-space-2);
    flex-wrap: wrap;
}
.ce-switch-row .form-label { margin: 0; }
.ce-switch-value {
    font-family: var(--bb-font-display);
    color: var(--bb-accent-primary);
    letter-spacing: 0.05em;
}

.ce-inline {
    display: flex;
    align-items: flex-end;
    gap: var(--bb-space-2);
    flex-wrap: wrap;
    margin-top: var(--bb-space-2);
}
.ce-inline-field { display: flex; flex-direction: column; gap: 2px; }
.ce-inline-unit {
    font-family: var(--bb-font-display);
    color: var(--bb-text-muted);
    padding-bottom: 0.5rem;
}

/* The ladder wraps rather than scrolls sideways: at 390px four rungs do
   not fit on one line, and a points box the player has to swipe to
   reach is a points box they will not check. */
.ce-ladder {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bb-space-2);
    margin-bottom: var(--bb-space-2);
}
/* Label across the top, points and the remove control side by side
   under it. A grid rather than a column so the minus sits beside the
   number it removes and not below it, which at four rungs made the
   ladder twice as tall as the form it lives in. */
/* One rung: a label and a points box. The per-rung remove control is
   gone - the ladder carries one ADD and one REMOVE underneath it now,
   see _renderLadder. */
.ce-rung {
    display: grid;
    grid-template-columns: 5.5rem auto;
    grid-template-areas: "label label" "pts pts";
    gap: 2px var(--bb-space-1, 4px);
    align-items: center;
}
.ce-rung-label {
    grid-area: label;
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
}
.ce-rung-pts { grid-area: pts; width: 5.5rem; }
/* Both ladder controls, on one line, at 44px so a thumb can hit them. */
.ce-ladder-actions { margin-top: var(--bb-space-2); }
.ce-ladder-actions .btn { min-height: 44px; }
.ce-ladder-actions .btn[disabled] { opacity: 0.4; cursor: not-allowed; }

/* WHEN IT RUNS. The four timeline questions read as one block rather
   than four unrelated fields scattered down the form. */
.ce-when {
    border-left: 2px solid var(--bb-border);
    padding-left: var(--bb-space-3);
}
.ce-when .ce-section-title { margin-bottom: var(--bb-space-2); }

/* Asked for only when a campaign fixes what its players bring. */
.ce-join-army { margin-top: var(--bb-space-2); }

/* Taking back a result nobody has confirmed. Quiet: it sits beside a
   game that is behaving normally, and it is not an error. */
.ce-undo { color: var(--bb-text-muted); }

/* A destructive control that has been tapped once and is asking. */
.btn.is-armed {
    color: var(--bb-accent-danger);
    border-color: var(--bb-accent-danger);
}

.ce-dates { display: flex; flex-direction: column; gap: var(--bb-space-2); }
.ce-date-row { display: flex; align-items: center; gap: var(--bb-space-2); }
.ce-date-label {
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
    min-width: 4.5rem;
}
.ce-date { flex: 1 1 0; min-width: 0; }

/* -- writing a round by hand ---------------------------------------- */

.ce-compose {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
    margin-top: var(--bb-space-3);
    padding-top: var(--bb-space-3);
    border-top: 1px solid var(--bb-border);
}
.ce-compose-row {
    display: flex;
    align-items: center;
    gap: var(--bb-space-2);
    flex-wrap: wrap;
}
.ce-compose-pick { flex: 1 1 8rem; min-width: 0; }
.ce-compose-vs { color: var(--bb-text-muted); }

/* 44px, because it is the control that deletes a game and it sits at the
   end of a row your thumb is already crossing. */
.ce-drop {
    min-width: 44px;
    min-height: 44px;
    color: var(--bb-accent-danger);
}

/* =====================================================================
   THE CAMPAIGN VIEW
   Added 2026-09-04 with the rewrite. The screen used to be one long
   scroll with the create form in the middle of it; a campaign is now
   its own view and these are its panels.
   ===================================================================== */

#ce-root { display: flex; flex-direction: column; gap: var(--bb-space-3); }

.ce-head .ce-back {
    align-self: flex-start;
    padding-left: 0;
    margin-bottom: var(--bb-space-2);
    min-height: 44px;
}
.ce-title {
    margin: 0 0 var(--bb-space-1, 4px);
    font-family: var(--bb-font-display);
    overflow-wrap: anywhere;
}
.ce-description { margin: 0 0 var(--bb-space-2); color: var(--bb-text-muted); }

/* Facts wrap into as many columns as fit rather than being a table.
   At 320px that is one per line and still readable. */
.ce-facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bb-space-2) var(--bb-space-3);
}
.ce-fact { display: flex; flex-direction: column; min-width: 0; }
.ce-fact-label {
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
    letter-spacing: 0.06em;
}
.ce-fact-value {
    font-family: var(--bb-font-display);
    overflow-wrap: anywhere;
}

.ce-badge {
    font-size: var(--bb-text-xs, 0.7rem);
    letter-spacing: 0.06em;
    padding: 2px 6px;
    border-radius: var(--bb-radius-sm, 3px);
    border: 1px solid var(--bb-border);
    color: var(--bb-text-muted);
    white-space: nowrap;
}
.ce-badge-active {
    color: var(--bb-accent-primary);
    border-color: var(--bb-accent-primary);
}
.ce-campaign-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--bb-space-2);
    width: 100%;
}

/* -- your next game -------------------------------------------------
   First panel under the header, and the only one most players open the
   screen for. */
.ce-next { border-left: 3px solid var(--bb-accent-primary); }
.ce-nextcard {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: var(--bb-space-2) 0;
}
.ce-nextcard-who {
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-lg, 1.1rem);
    overflow-wrap: anywhere;
}
.ce-nextcard-meta { color: var(--bb-text-muted); }
/* The game's own number. The only thing on two cards against the same
   opponent that is guaranteed to differ - see _nextGamePanel. */
.ce-nextcard-id {
    font-family: var(--bb-font-mono, monospace);
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
    letter-spacing: 0.08em;
}
/* "Ana is setting this one up." The other player's card says who is
   doing it rather than offering them a second PLAY button - see
   campaign_service.host_entrant_id. */
.ce-nextcard-waiting {
    display: block;
    margin-top: 4px;
    color: var(--bb-text-muted);
    line-height: 1.35;
}
.ce-bye { color: var(--bb-accent-primary); }

.ce-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bb-space-2);
    align-items: center;
    margin-top: var(--bb-space-2);
}
.ce-actions .btn { min-height: 44px; }

/* -- schedule -------------------------------------------------------- */

.ce-schedule { display: flex; flex-direction: column; }
.ce-schedule-row {
    display: grid;
    grid-template-columns: 2.5rem 1fr auto auto;
    gap: var(--bb-space-2);
    align-items: center;
    padding: var(--bb-space-2) 0;
    border-bottom: 1px solid var(--bb-border);
}
.ce-schedule-row:last-child { border-bottom: 0; }
.ce-schedule-round {
    font-family: var(--bb-font-display);
    color: var(--bb-text-muted);
}
.ce-schedule-points { font-family: var(--bb-font-display); }
.ce-schedule-when {
    font-size: var(--bb-text-sm);
    color: var(--bb-text-muted);
    white-space: nowrap;
}
.ce-schedule-now {
    font-size: var(--bb-text-xs, 0.7rem);
    letter-spacing: 0.06em;
    color: var(--bb-accent-primary);
}
/* A finished round is context, not news. */
.ce-schedule-done { opacity: 0.55; }
.ce-schedule-current .ce-schedule-round { color: var(--bb-accent-primary); }

/* -- challenges and the roster --------------------------------------- */

.ce-challenge, .ce-playerrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--bb-space-2);
    padding: var(--bb-space-2) 0;
    border-bottom: 1px solid var(--bb-border);
    flex-wrap: wrap;
}
.ce-challenge:last-child, .ce-playerrow:last-child { border-bottom: 0; }
/* WHEN THE ROW WRAPS, THE CONTROL STAYS ON THE RIGHT. Reported with a
   photo, 2026-09-17: "Swords and Sixguns - World Eaters" filled the
   line, so its CHALLENGE dropped to a second row and sat hard left
   while every shorter name's button stayed right. `space-between` puts
   a lone item on a line at flex-start; `margin-left: auto` is what
   keeps a wrapped one where the eye is already looking. */
.ce-challenge > :last-child:not(:only-child),
.ce-playerrow > :last-child:not(:only-child) { margin-left: auto; }
.ce-challenge-who, .ce-playerrow-name {
    min-width: 0;
    overflow-wrap: anywhere;
}
.ce-challenge .ce-actions, .ce-playerrow .ce-actions { margin-top: 0; }

.ce-players { display: flex; flex-wrap: wrap; gap: var(--bb-space-2); }
.ce-chip {
    padding: 2px 8px;
    border: 1px solid var(--bb-border);
    border-radius: var(--bb-radius-sm, 3px);
    font-size: var(--bb-text-sm);
}

.ce-freegame, .ce-edit, .ce-report {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
    margin-top: var(--bb-space-3);
    padding-top: var(--bb-space-3);
    border-top: 1px solid var(--bb-border);
}

/* Your own game, picked out of the round at a glance. */
.ce-pairing.is-mine { border-color: var(--bb-accent-primary); }

/* -- a message where the tap was ------------------------------------- */

.ce-inline-message {
    padding: var(--bb-space-2);
    margin: var(--bb-space-2) 0;
    border-radius: var(--bb-radius-md, 6px);
    border: 1px solid var(--bb-border);
    font-size: var(--bb-text-sm);
}
.ce-inline-message.ce-message-error {
    border-color: var(--bb-accent-danger);
    color: var(--bb-accent-danger);
}
.ce-inline-message.ce-message-ok { border-color: var(--bb-accent-primary); }

.ce-organiser { border-left: 3px solid var(--bb-border); }

/* -- a result waiting on you ----------------------------------------
   Above the fold on purpose: an unconfirmed result does not move the
   standings, so a campaign where nobody confirms is a campaign whose
   table has quietly stopped. */
.ce-confirm { border-left: 3px solid var(--bb-accent-warning); }
.ce-pairing-awaiting { color: var(--bb-accent-warning); }

.ce-admins {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
    margin-top: var(--bb-space-3);
    padding-top: var(--bb-space-3);
    border-top: 1px solid var(--bb-border);
}

/* A LOAD FAILURE IS NOT AN EMPTY LIST.
   Field report 2026-09-06: a player whose access cookie had expired was
   shown "You are not in a campaign yet", which is a claim about their
   account and was false. This is what that case looks like now - it
   reads as a problem with the request, and it carries a way out. */
.ce-load-error {
    color: var(--bb-accent-danger);
    background: var(--bb-bg-input);
    border: 1px solid var(--bb-accent-danger);
    border-radius: 4px;
    padding: var(--bb-space-3);
    margin-bottom: var(--bb-space-2);
    font-size: var(--bb-text-sm);
}

/* How this campaign scores, stated above its own table. Present only
   where the campaign does something a league table does not already
   imply - see `_scoringRule`. */
.ce-scoring-rule {
    color: var(--bb-text-muted);
    font-size: var(--bb-text-sm);
    margin: 0 0 var(--bb-space-2);
}

/* "You have already played them twice - record another game?" Asked
   beside the choice that raises it, never as a modal: the duplicate it
   prevents is recoverable and interrupting a battle setup is not. */
.bb-campaign-rematch {
    color: var(--bb-accent-warning);
    font-size: var(--bb-text-sm);
    margin: var(--bb-space-2) 0 0;
}

/* WHO WON, READ OFF THE TWO SCORES.
   Jesse, 2026-09-11: "The system should be smart enough to know if a
   game is win/draw/loss when recording results... don't make the user
   select it." Muted until the numbers answer it, then stated plainly -
   the point is that it is a READING of what was typed, not a fourth
   control to argue with. */
.ce-verdict {
    padding: var(--bb-space-2) 0;
    font-size: var(--bb-text-sm);
    color: var(--bb-text-muted);
}
.ce-verdict.is-known {
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-lg, 1.1rem);
    color: var(--bb-accent-primary);
    letter-spacing: 0.04em;
}
/* The fallback for a game with no scoreline, which is most games
   reported from memory. Hidden the moment the numbers speak. */
.ce-verdict-manual { margin-top: var(--bb-space-1, 4px); }

/* A linked battle that is over and whose result never arrived. Not an
   error - the game happened and the way through is right underneath -
   so it is stated rather than alarmed. See `battle_finished`. */
.ce-nextcard-stuck {
    display: block;
    margin: var(--bb-space-2) 0;
    color: var(--bb-accent-warning);
    font-size: var(--bb-text-sm);
}
.ce-pairing-stuck { color: var(--bb-accent-warning); }

/* Paid and unpaid, wherever the mark is drawn. The panel these were
   written for - a separate ENTRY FEE list - was replaced by the roster
   on 2026-09-17; the colours moved with the mark and the layout rules
   that only made sense inside that panel went. */
.ce-paid-tag { color: var(--bb-accent-warning); }
.ce-paid-tag.is-paid { color: var(--bb-accent-primary); }
/* The button onto a player's locked list. */
.ce-view-list { margin-left: var(--bb-space-2); }
/* The organiser's correction control on a settled game. Quiet: the
   result is right almost every time, and this must not read as an
   invitation. */
.ce-fix { color: var(--bb-text-muted); }


/* A game that came back because somebody disagreed. Warning-coloured
   rather than danger: nothing is broken and nobody did anything wrong -
   two people remember the afternoon differently, which is the case
   validation exists for. See `disputed_by`. */
.ce-pairing-disputed { color: var(--bb-accent-warning); }
.ce-nextcard-disputed {
    display: block;
    margin: var(--bb-space-2) 0;
    color: var(--bb-accent-warning);
    font-size: var(--bb-text-sm);
}

/* ----------------------------------------------------------------
   The organiser's notice board.

   Jesse, 2026-09-13: "a bright border and bold, bright text to make it
   obvious."

   Bright means the ACCENT, not a colour of its own. The accent is
   already the loudest thing each universe owns, it is what every
   display surface re-points, and a hardcoded gold here would be the
   one panel on the page that did not follow the Cogitator - which is
   the exact defect the surface audit spent a day removing.

   A FULL BORDER, not the left rule the confirm panel uses. That one
   marks a thing you owe an answer to; this marks a thing you have to
   read, and the two should not look the same from across a table.

   No background tint. The accent at low alpha behind bold accent text
   is how you get a bright box nobody can read, and the border already
   does the shouting.
   ---------------------------------------------------------------- */

.ce-notices {
    border: 2px solid var(--bb-accent-notice);
}

/* AND THE BORDER HAS TO SURVIVE A DISPLAY SURFACE.
   Measured 2026-09-13, the day this was written: on the Cogitator this
   panel came out as a 1px rgba(61,251,162,0.26) hairline - identical to
   the six panels below it - because `cogitator.css` styles every
   `.ce-panel` at (0,2,1) and the rule above is (0,1,0). The one panel
   whose entire job is to be obvious was the one the surface flattened.

   Written against `[data-surface]` rather than naming the Cogitator, so
   a surface added later inherits the fix instead of re-finding the bug.
   `--bb-accent-notice` is re-pointed inside a surface's scope, so this
   picks up the signal amber there and the sea green on Standard without
   naming either.

   IT USED TO BORROW `--bb-accent-primary`, which is the accent EVERY
   other panel on this screen already uses - so the one panel whose job
   is to be noticed was gold on a page of gold. Jesse, field test
   2026-09-14: "'from the organizer' section is not obvious enough,
   change the border color from gold, that blends in with the others."
   A panel emphasised in the same colour as everything around it is not
   emphasised; it has a thicker border and nothing else. */
html[data-surface] .ce-panel.ce-notices {
    border: 2px solid var(--bb-accent-notice);
}

.ce-notices .ce-section-title {
    color: var(--bb-accent-notice);
}

.ce-notice {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-1);
    padding: var(--bb-space-2) 0;
}

/* Every notice after the first gets a rule above it. Several notices
   with nothing between them read as one long paragraph. */
.ce-notice + .ce-notice {
    border-top: 1px solid var(--bb-border);
}

.ce-notice-text {
    margin: 0;
    color: var(--bb-accent-primary);
    font-weight: 700;
    /* A notch up from body copy. The organiser is not competing with
       the standings for attention, they are above them. */
    font-size: var(--bb-text-md, 1rem);
    /* AN ORGANISER TYPES A PARAGRAPH AND EXPECTS A PARAGRAPH. Without
       this, "we are at Dave's\nbring a table" arrives as one line. */
    white-space: pre-wrap;
    /* A pasted address or link has no spaces in it and would otherwise
       push the whole campaign screen sideways. */
    overflow-wrap: anywhere;
}

.ce-notice-when {
    color: var(--bb-text-muted);
}

.ce-notice-input {
    /* Room for two lines without becoming a document editor. */
    min-height: 3.5rem;
    resize: vertical;
}

/* ----------------------------------------------------------------
   Arranging a game for a particular evening.
   ---------------------------------------------------------------- */

.ce-challenge-form {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
    margin: var(--bb-space-2) 0;
    padding: var(--bb-space-3);
    border: 1px solid var(--bb-accent-primary);
    border-radius: var(--bb-radius-md, 6px);
    background: var(--bb-bg-input);
}

/* The slider and its label read as one control, and tapping the words
   works because the whole thing is a <label>. */
.ce-when-toggle {
    display: flex;
    align-items: center;
    gap: var(--bb-space-2);
    cursor: pointer;
}

.ce-when-input {
    /* iOS shrinks a date input to its content and then centres the
       text, which puts a 140px control in the middle of a card. */
    width: 100%;
}

/* The agreed time on a game card. Not muted: it is the one fact on the
   card a player came to the page to check. */
.ce-nextcard-when {
    color: var(--bb-accent-primary);
    font-weight: 600;
}

/* On a challenge, the proposal is still only a proposal. */
.ce-when {
    display: block;
    color: var(--bb-text-secondary);
}

/* =====================================================================
   The header, thinned out - 2026-09-14
   =====================================================================
   Jesse: "We have too much info on the top bar for regular campaign
   users." What left: the join code fact, the invite link box and its
   two buttons for everybody, and the full round schedule for everybody.
   What replaced them: one button beside the title and one beside the
   dates, both organiser-neutral in layout so the header does not jump
   between the two kinds of reader.
   ===================================================================== */

/* Title and INVITE on one line, and the title takes what is left.
   `wrap` rather than shrink: a long campaign name at 320px should push
   the button to its own line, not squeeze it under the tap target
   minimum. */
.ce-titlerow {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--bb-space-2);
}
.ce-titlerow .ce-title { flex: 1 1 12rem; min-width: 0; }
.ce-titlerow .ce-invite-open { flex: 0 0 auto; min-height: 44px; }

/* VIEW FULL SCHEDULE, under the facts it belongs with. */
.ce-headline-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bb-space-2);
    margin-top: var(--bb-space-2);
}
.ce-headline-actions .btn { min-height: 44px; }

/* -- inside the invite dialog --------------------------------------- */

.ce-invite-code {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ce-invite-code-value {
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-xl);
    letter-spacing: 0.12em;
    color: var(--bb-accent-primary);
    /* It gets read out loud and typed by hand. Selectable, and wide
       enough apart that O and 0 are different characters. */
    user-select: all;
    overflow-wrap: anywhere;
}

/* Both dialogs are text and controls rather than a wizard, so they read
   better narrow than at the shared 720px the setup wizard needs. */
.modal.ce-modal { max-width: 32rem; }

.ce-schedule-wrap { display: flex; flex-direction: column; gap: var(--bb-space-2); }

/* -- a panel heading that carries a control ------------------------- */

.ce-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--bb-space-2);
}
.ce-sort {
    /* Sized to its longest option rather than to the panel. A full-width
       select reads as the panel's main control, and this one is a
       preference about a list somebody is already reading. */
    width: auto;
    min-height: 44px;
    font-size: var(--bb-text-sm);
}

/* -- the date on a played game -------------------------------------- */

.ce-pairing-names { display: block; }
.ce-pairing-when {
    display: block;
    font-size: var(--bb-text-xs, 0.7rem);
    color: var(--bb-text-muted);
}

/* THE ROUND FILTER ON THE GAMES LOG, 2026-09-14.
   Jesse: "add tabs for 'all games' 10 per page, then additional tabs for
   each round. If we have too many rounds wrap to a second line." So the
   bar wraps rather than scrolling: a horizontal scroller hides the later
   rounds behind a gesture nobody is told about, and a league that runs
   twelve rounds is exactly when somebody needs round nine. */
.ce-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bb-space-1, 0.25rem);
    margin-bottom: var(--bb-space-2);
}
.ce-tab {
    /* 44px of height on a phone. These are the one control on this
       panel somebody taps repeatedly, and a 30px target in a pub is a
       control that gets tapped twice. */
    min-height: 44px;
    padding: 0 var(--bb-space-2);
    font-size: var(--bb-text-sm);
    white-space: nowrap;
}
.ce-tab.is-on {
    border-color: var(--bb-accent-primary);
    color: var(--bb-accent-primary);
}
.ce-pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--bb-space-2);
    margin-top: var(--bb-space-2);
    padding-top: var(--bb-space-2);
    border-top: 1px solid var(--bb-border);
}
.ce-pager .btn { min-height: 44px; }
.ce-pager-at {
    font-size: var(--bb-text-xs, 0.75rem);
    color: var(--bb-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* REPORT A BUG, at the bottom of the campaign. Jesse, 2026-09-15:
   "we need to add a bug report button on the bottom of the campaign,
   since this is still beta and we will have issues I'm sure."
   Quiet - it is a safety net, not a call to action - and separated from
   the last panel so it does not read as part of it. */
.ce-report-bug {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--bb-space-1, 0.25rem);
    margin-top: var(--bb-space-4, 1.5rem);
    padding-top: var(--bb-space-3);
    border-top: 1px solid var(--bb-border);
    text-align: center;
}
.ce-report-bug .btn { min-height: 44px; }

/* The weekly sheet's day, hour and minute, side by side. Three controls
   that are one answer - "every Friday at 7:30pm" - so they read as one
   line rather than as three stacked questions.

   THE DAY TAKES THE ROOM because it carries the longest label by far
   ("Every 7 days (no fixed day)") and the other two are four characters
   each. Sharing the width equally truncates the only one that needs it.
   On a phone the row wraps rather than squeezing three selects into
   360px, which is narrower than a select's own text can go. */
.ce-when {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bb-space-2);
    align-items: center;
}
.ce-when .form-input { margin: 0; }
.ce-when select { flex: 1 1 5rem; min-width: 0; }
.ce-when select:first-child { flex: 3 1 12rem; }

/* -- the danger zone ---------------------------------------------------
   Jesse, 2026-09-17: "make the button big scary and red way at the
   bottom for an EO only."

   BIG AND RED IS THE REQUIREMENT, and the panel carries as much of it as
   the button does: a red-bordered box under a gap twice the size of the
   one between every other panel is what stops somebody arriving here by
   scrolling. The button is full width because a destructive control
   should be impossible to hit by accident and impossible to miss on
   purpose - those are the same property from opposite directions. */

.ce-danger {
    margin-top: var(--bb-space-6, 2rem);
    border-color: var(--bb-accent-danger);
    /* `color-mix` degrades to the plain surface where it is unsupported,
       which is the correct fallback: the border and the button still say
       everything this tint is saying quietly. */
    background: color-mix(in srgb, var(--bb-accent-danger) 6%,
                          var(--bb-bg-surface));
}

.ce-danger-title {
    color: var(--bb-accent-danger);
}

.ce-danger-btn {
    align-self: stretch;
    padding: var(--bb-space-3);
    font-size: var(--bb-text-md, 1rem);
    letter-spacing: 0.08em;
}

.ce-danger-lede {
    margin: 0 0 var(--bb-space-2);
    font-weight: 600;
}

.ce-danger-list {
    margin: 0;
    padding-left: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-1, 0.25rem);
    font-size: var(--bb-text-sm);
    color: var(--bb-text-muted, inherit);
}

.ce-modal-danger .modal-title {
    color: var(--bb-accent-danger);
}

/* -- the roster --------------------------------------------------------
   One panel replacing the notes list and the entry-fee list, both of
   which filtered to active players and so hid anybody who had left.
   Jesse, 2026-09-17: "a definitive overview of who's in, who was in,
   and controls built around that." */

.ce-roster {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
}

.ce-rosterrow {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-1, 0.25rem);
    padding: var(--bb-space-2);
    border: 1px solid var(--bb-border);
    border-radius: var(--bb-radius-md, 6px);
}

/* A row for somebody who has left reads as present-but-inactive rather
   than as an error: they are not a problem, they are a fact about the
   campaign, and one the organiser may want to undo. */
.ce-rosterrow.is-gone {
    opacity: 0.75;
    border-style: dashed;
}

/* The name and the handle, and nothing else. NOT wrapping is the point:
   the hamburger has to stay on the name's line at every width, so the
   name shrinks and the button never moves. */
.ce-rosterrow-head {
    display: flex;
    align-items: center;
    gap: var(--bb-space-1, 0.25rem);
}
.ce-rosterrow-head .ce-playerrow-name { flex: 1 1 auto; min-width: 0; }

/* Everything they ARE, free to wrap because nothing here moves a
   tap target that has to stay put. */
.ce-rosterrow-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--bb-space-1, 0.25rem);
}

/* THE LAST LINE, carrying the note on the left and the paid mark on the
   right. Jesse, 2026-09-18: "move paid/unpaid to the bottom right under
   the hamburger button, right aligned."

   THE POINT IS THE COLUMN. The mark used to sit inline after the joined
   date, which put it at a different horizontal position on every row -
   so finding who still owes you meant reading six rows instead of
   glancing down one edge. Right-aligned it lines up under the handle
   and the roster can be read in a single sweep.

   NOT wrapping: a mark that drops to its own line at narrow widths is
   back to being at a different place on every row. The note shrinks
   instead - it is the thing here that can afford to. */
.ce-rosterrow-foot {
    display: flex;
    align-items: center;
    gap: var(--bb-space-2, 0.5rem);
}
.ce-rosterrow-foot .ce-roster-note-area { flex: 1 1 auto; min-width: 0; }
.ce-rosterrow-foot .ce-paid-tag { margin-left: auto; flex: 0 0 auto; }

/* THE NOTE AS A LINE OF TEXT, until the menu turns it into a field.
   Jesse, 2026-09-17: the row carried an input, a save, a paid toggle, a
   games button and a remove button, which at five players was a wall.
   What stays is what gets READ; what ACTS is behind the menu. */
.ce-roster-note-area {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--bb-space-1, 0.25rem);
}
.ce-roster-note-text {
    color: var(--bb-text-secondary);
    font-size: var(--bb-text-sm);
    font-style: italic;
    overflow-wrap: anywhere;
}
/* "No note" is a placeholder, not a note. It must not read as one. */
.ce-roster-note-text.is-empty { opacity: 0.55; }

.ce-roster-note {
    flex: 1 1 12rem;
    min-width: 0;
}

/* The hamburger sits at the end of the name line and never wraps away
   from it: it is the row's handle, and a handle on its own line under
   the thing it opens is not a handle. */
.ce-rostermenu {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: var(--bb-text-lg, 1.1rem);
    line-height: 1;
}

/* One action per line, full width, thumb-sized. This is a sheet on a
   phone held at a table, not a desktop context menu. */
.ce-sheet {
    display: flex;
    flex-direction: column;
    gap: var(--bb-space-2);
}
.ce-sheet-item {
    width: 100%;
    justify-content: center;
    padding: var(--bb-space-3);
}
.ce-sheet-danger {
    border-color: var(--bb-accent-danger);
    color: var(--bb-accent-danger);
}
.ce-sheet-go { border-color: var(--bb-accent-primary); }

.ce-roster-subhead {
    margin: var(--bb-space-3) 0 0;
    font-family: var(--bb-font-display);
    font-size: var(--bb-text-sm);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--bb-text-muted, inherit);
}

.ce-roster-when {
    color: var(--bb-text-muted, inherit);
    font-size: var(--bb-text-xs, 0.75rem);
}

.ce-tag.is-gone {
    border-color: var(--bb-accent-danger);
    color: var(--bb-accent-danger);
}

.ce-rosterrow .ce-remove {
    color: var(--bb-accent-danger);
}

/* The log, narrowed to one player from the roster. Says whose games are
   showing and how to get back to all of them - a filter with no way out
   reads as an empty campaign. */
.ce-games-filter {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--bb-space-2);
    padding: var(--bb-space-2);
    border: 1px solid var(--bb-accent-primary);
    border-radius: var(--bb-radius-md, 6px);
}

.ce-games-filter-who {
    flex: 1 1 auto;
    font-size: var(--bb-text-sm);
}

