/* ======================================================================
   Tournament Builder & Pool Manager — theme
   Scoped .tn-app. Loaded third: styles.css -> tool-base.css -> this file.

   THE VAR INDIRECTION IS THE CONTRACT. The wrapper declares the tool's OWN
   names and maps each onto a site token; no rule below ever references a site
   token directly. tool-base.css re-declares exactly these ten names inside
   @media print, so a token introduced under any OTHER name will not flip on
   paper. Add a name here only if you also add it there.

   PRINT MIRRORS: none, and that is STRUCTURAL rather than an omission. Every
   editor panel carries .no-print, so there is not one form control in the
   printed area and the site-wide print-mirror contract does not apply. The QA
   suite asserts that control count is zero. If you ever print the editor, the
   mirror contract applies and this comment is wrong.

   DOUBLED CLASSES: tool-base.css writes its print rules as `.tool-app.tool-app`
   so they outrank screen rules at equal specificity. This file doubles
   `.tn-app.tn-app` ONLY on rules that must beat one in the base. Never double
   wholesale "to be safe" — a tool's print rule may be deliberately losing to a
   more specific screen rule, which is how the planner's savings headline
   flipped colour.

   COLOUR: no new colour, and no DESIGN.md amendment. A bracket and a
   leaderboard both want hue and neither may have it.
     - Advancement is carried by WEIGHT, not hue (the cyber-readiness
       .gap-state pattern). Eliminated = muted text + --line-strong hairline.
       Advanced = --ink text + --ink border + 700. Amber for the champion
       ONLY, because the champion is the finding.
     - A won match is not a success state and a lost match is not an error.
       Both are peers, exactly like a pro and a con. No green, no red.
     - No coloured left border on a node to mark a winner — named in
       subnet-calculator.theme.css as an AI-slop pattern present in zero
       shipped tools here.

   TOUCH: the 44px pass ships with v1 rather than joining the debt pile the
   four older tools carry. Every size in the touch block is written in px,
   never rem — styles.css:1144 drops :root to 15px at 480px, so a rem-based
   44px target renders 41px on exactly the phones that need it most.
   ====================================================================== */

.tn-app {
    --bg: var(--color-bg-primary);
    --card: var(--color-surface);
    --raised: var(--color-raised);
    --overlay: var(--color-overlay);
    --ink: var(--color-text-primary);
    --dim: var(--color-text-secondary);
    --muted: var(--color-text-muted);
    --line: var(--color-border);
    --line-strong: var(--color-border-strong);
    --accent: var(--color-primary);
    --accent-soft: rgba(229, 163, 68, 0.12);

    /* DESIGN.md: flat surfaces, hairline rules, no glow — structure, not effects */
    --shadow: none;

    background: var(--bg);
    color-scheme: dark;
    /* Firefox draws a solid white scrollbar without this; color-scheme alone
       is not enough on a scrolling child. */
    scrollbar-color: var(--line-strong) transparent;

    font-family: var(--font-primary);
    /* dense data tool: deliberately below the site's 1.0625rem body */
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink);
}

/* `color: inherit` is load-bearing: it also keeps the printed masthead h1
   black, because styles.css's h1 colour would otherwise beat the inherited
   value. */
.tn-app h1,
.tn-app h2,
.tn-app h3,
.tn-app p {
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
    letter-spacing: normal;
    color: inherit;
    margin: 0;
    max-width: none;
}

.tn-app * {
    box-sizing: border-box;
}

/* The `hidden` ATTRIBUTE is only a UA display:none rule, so ANY author
   `display` declaration beats it — which silently makes `node.hidden = true`
   a no-op on anything flex or grid. Assert computed display in tests, never
   the property. */
.tn-app [hidden] {
    display: none !important;
}

.tn-app .wrap {
    max-width: 1240px;
    margin: 0 auto;
    /* 112px clears the fixed navbar */
    padding: 112px 2rem 96px;
}

.tn-app .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    /* must be the clip-rect form — display:none / visibility:hidden suppress
       live-region announcements entirely */
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------- tool header ---------- */

.tn-app header.app {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 1.75rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--line);
}

.tn-app .doc-title {
    display: block;
    width: 100%;
    max-width: 34ch;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--line);
    padding: 4px 0 6px;
    font-family: var(--font-display, var(--font-primary));
    font-size: 1.6rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--ink);
}

.tn-app .doc-title::placeholder {
    color: var(--muted);
    font-weight: 400;
}

.tn-app .doc-title:focus {
    outline: none;
    border-bottom-color: var(--accent);
}

.tn-app .sub {
    margin-top: 8px;
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
}

.tn-app .actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* Two labels per action button, one shown at a time. On a phone the header is
   competing directly with the fold, and "Open file / Save file / Save as PDF"
   wraps to two rows at 320px where "Open / Save / PDF" does not. See the touch
   block for the measured budget this belongs to. */
.tn-app .lbl-short {
    display: none;
}

/* ---------- buttons ---------- */

.tn-app button {
    font-family: var(--font-primary);
    font-size: 0.82rem;
    border-radius: var(--radius-md, 0.375rem);
    border: 1px solid var(--line-strong);
    background: var(--raised);
    color: var(--ink);
    padding: 9px 14px;
    cursor: pointer;
    /* kills the 300ms double-tap-zoom delay */
    touch-action: manipulation;
    /* Chrome Android paints its own grey box over a tap. With a press state of
       our own that is two pieces of feedback for one press. */
    -webkit-tap-highlight-color: transparent;
    transition: border-color var(--transition-fast, 0.18s) ease,
                background var(--transition-fast, 0.18s) ease,
                color var(--transition-fast, 0.18s) ease;
}

.tn-app button.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #171104;
    font-weight: 600;
}

.tn-app button.ghost {
    background: transparent;
    color: var(--dim);
}

/* Every :hover sits inside a hover gate. Without it, hover fires on tap and
   sticks until the next tap elsewhere — repo-wide there were zero such gates
   before the pro/con list. :active is the deliberate replacement for the press
   feedback that removes, and sits OUTSIDE the gate so both pointer types
   get it. */
@media (hover: hover) {
    .tn-app button:hover {
        border-color: var(--accent);
        color: var(--ink);
    }

    .tn-app button.primary:hover {
        background: var(--color-primary-hover, #D18F2E);
    }

    .tn-app .tn-del:hover {
        border-color: var(--color-error, #E5484D);
        color: var(--color-error, #E5484D);
    }

    .tn-app .is-off:hover {
        border-color: var(--line-strong);
        color: var(--ink);
    }
}

/* A COLOUR PRESS, NOT A 1px SHIFT. A one-pixel drop is invisible under a
   fingertip, and on touch this is the only confirmation a press registered at
   all -- the same call the pro/con list and the grocery list both made. */
.tn-app button:active {
    background: var(--card);
    border-color: var(--accent);
}

.tn-app button.primary:active {
    background: var(--color-primary-hover, #D18F2E);
    border-color: var(--color-primary-hover, #D18F2E);
}

.tn-app button:focus-visible,
.tn-app input:focus-visible,
.tn-app textarea:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ---------- layout ---------- */

.tn-app .grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 1.5rem;
    align-items: start;
}

.tn-app .col-side .summary {
    position: sticky;
    top: 100px;
}

@media (max-width: 1000px) {
    .tn-app .grid {
        display: flex;
        flex-direction: column;
        /* LOAD-BEARING. The grid rule's `align-items: start` becomes the CROSS
           axis on a column flex box and shrink-wraps every panel to its content
           width, overflowing the inputs. Same trap tool-base.css documents for
           print. */
        align-items: stretch;
    }

    .tn-app .col-side .summary {
        position: static;
    }
}

/* THE UNSAVED MARKER. recompute() has always toggled `is-dirty` on #btnSave
   and this file has never carried a rule for it, so the one indicator on a
   tool whose entire persistence model is "save the file yourself" rendered
   nothing at all. The ui suite only ever asserted the class was ABSENT when
   clean, which is why it never noticed. A dot, not a colour change: it reads
   at 320px, it survives greyscale, and .actions is .no-print so it costs
   paper nothing. */
.tn-app #btnSave.is-dirty::before {
    content: "\2022\00a0";
    color: var(--accent);
    font-weight: 700;
}

/* ---------- panels ---------- */

.tn-app .panel {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg, 0.5rem);
    overflow: hidden;
    box-shadow: var(--shadow);
}

.tn-app .panel + .panel {
    margin-top: 1.25rem;
}

.tn-app .panel h2 {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    background: var(--raised);
    border-bottom: 1px solid var(--line);
    padding: 12px 1.25rem;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent);
}

/* A FOLDED PANEL. The heading band is the control, so the whole bar is the
   target -- the same "the label is the target" rule the checkbox follows.
   `list-style: none` plus the WebKit pseudo-element because an unstyled
   disclosure triangle inside a mono uppercase band reads as a stray glyph;
   the chevron is drawn as text so it inherits colour and needs no asset. */
.tn-app .panel > .fold > summary {
    list-style: none;
    cursor: pointer;
}

.tn-app .panel > .fold > summary::-webkit-details-marker {
    display: none;
}

.tn-app .panel > .fold > summary > h2::after {
    content: "\203A";
    margin-left: auto;
    padding-left: 0.75rem;
    font-size: 1.1rem;
    line-height: 1;
    color: var(--dim);
    transform: rotate(90deg);
    transition: transform var(--transition-fast, 0.18s) ease;
}

.tn-app .panel > .fold[open] > summary > h2::after {
    transform: rotate(-90deg);
}

@media (hover: hover) {
    .tn-app .panel > .fold > summary:hover > h2::after {
        color: var(--accent);
    }
}

.tn-app .panel > .fold > summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* The hint carries what is inside, so a shut panel still says what it holds
   rather than being a word and a chevron. Not another kicker -- sentence
   case, normal tracking, so it reads as prose beside the heading. */
.tn-app .fold-hint {
    margin-left: 0.75rem;
    font-family: var(--font-primary);
    font-size: 0.72rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--muted);
}

/* The winning square. Weight and a hairline, not a fill: the bracket makes the
   same call, and this cell has to stay legible in a greyscale print. */
.tn-app .sq-grid td.is-win {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.tn-app .sq-grid td.is-win .sq-cell {
    color: var(--accent);
    font-weight: 600;
}

.tn-app .badge {
    font-family: var(--font-mono);
    font-size: 0.64rem;
    letter-spacing: 0.08em;
    color: var(--muted);
    text-transform: none;
}

.tn-app .panel .body {
    padding: 1.25rem;
}

.tn-app .note {
    color: var(--muted);
    font-size: 0.8rem;
    max-width: 68ch;
}

.tn-app .sub-head {
    margin: 18px 0 8px;
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--dim);
}

.tn-app .sub-head:first-child {
    margin-top: 0;
}

.tn-app .footnote {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
    color: var(--muted);
    font-size: 0.76rem;
}

/* ---------- inputs ---------- */

.tn-app input[type="text"],
.tn-app input[type="number"],
.tn-app textarea {
    font-family: var(--font-primary);
    font-size: 0.86rem;
    background: var(--bg);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm, 0.25rem);
    color: var(--ink);
    padding: 8px 10px;
    width: 100%;
}

.tn-app input::placeholder,
.tn-app textarea::placeholder {
    color: var(--muted);
}

.tn-app .add-bar {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.tn-app .add-bar .tn-text {
    flex: 1 1 auto;
    min-width: 0;
}

.tn-app .bulk {
    margin-bottom: 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm, 0.25rem);
    padding: 10px 12px;
}

.tn-app .bulk summary {
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--dim);
}

.tn-app .bulk .note {
    margin: 10px 0;
}

.tn-app .tn-area {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    resize: vertical;
}

.tn-app .bulk-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-top: 8px;
    flex-wrap: wrap;
}

/* ---------- team list ---------- */

.tn-app .tn-list {
    list-style: none;
    margin: 0;
    padding: 0;
    /* THE CAP IS A CORRECTNESS FIX, NOT TIDINESS. The list is unbounded — at
       the 144-team ceiling it is roughly 7,000px of page, which puts the
       Format panel (the control that drives the entire cost readout, the
       tool's headline feature) an unreachable distance below the fold. It is
       already past the fold at 24 teams, measured. Capping the list and
       scrolling it internally keeps teams -> format -> venue in the order the
       work actually happens, instead of reordering the page around a
       scrolling problem. */
    max-height: 26rem;
    overflow-y: auto;
    /* Firefox draws a solid white bar on a scrolling child without this;
       color-scheme: dark on the wrapper alone is not enough. */
    scrollbar-color: var(--line-strong) transparent;
    scrollbar-width: thin;
}

.tn-app .tn-row {
    display: grid;
    grid-template-columns: 2.2rem minmax(0, 1fr) minmax(0, 0.7fr) auto;
    gap: 0.5rem;
    align-items: center;
    padding: 6px 0;
    border-bottom: 1px solid var(--line);
}

.tn-app .tn-seed {
    font-family: var(--font-mono);
    font-size: 0.74rem;
    color: var(--muted);
    text-align: right;
}

.tn-app .tn-club {
    color: var(--dim);
}

.tn-app .tn-del {
    /* Destructive controls are muted at rest and red only on hover/press —
       a permanently red delete reads as an error state on the page and
       contradicts red's demotion to semantic error. */
    border-color: var(--line);
    color: var(--muted);
    padding: 6px 10px;
    line-height: 1;
    /* banked space away from the repeated controls, per DESIGN.md — a gap
       bump is not enough between a destructive control and its neighbours */
    margin-left: auto;
}

.tn-app .list-actions {
    margin-top: 12px;
    display: flex;
    justify-content: flex-end;
}

/* ---------- format picker ---------- */

/* Named .tn-seg, not .seg. The estimator's print block hides `.seg` outright
   and the share bar already hit that trap; a distinct name cannot collide even
   if this tool's panels are ever made printable. */
.tn-app .tn-seg {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.75rem;
}

.tn-app .tn-seg-btn {
    flex: 1 1 auto;
    background: transparent;
    border-color: var(--line);
    color: var(--dim);
    font-size: 0.78rem;
}

.tn-app .tn-seg-btn.is-on {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}

.tn-app .fields {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1.25rem;
    margin: 1rem 0;
}

.tn-app .field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 8rem;
}

.tn-app .field .lab {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
}

.tn-app .field input[type="checkbox"] {
    width: 20px;
    height: 20px;
    accent-color: var(--accent);
}

/* ---------- the explainer ----------
   Closed by default. Somebody who already knows what a Swiss is should not
   scroll past a description of one every time they change a setting, and
   somebody who does not should not have to go and look it up. */

.tn-app .explain {
    margin: 0.75rem 0 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm, 0.25rem);
    padding: 10px 12px;
}

.tn-app .explain summary {
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--dim);
}

.tn-app .explain-body {
    margin-top: 10px;
}

.tn-app .explain-head {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
    margin: 12px 0 6px;
}

.tn-app .explain-head:first-child {
    margin-top: 0;
}

.tn-app .explain-list {
    margin: 0 0 4px;
    padding-left: 1.15rem;
    color: var(--dim);
    font-size: 0.8rem;
    max-width: 74ch;
}

.tn-app .explain-list li {
    margin-bottom: 5px;
}

.tn-app .explain-note {
    margin-top: 8px;
    font-size: 0.78rem;
    color: var(--muted);
    max-width: 74ch;
}

.tn-app .explain-note strong {
    color: var(--dim);
    font-weight: 600;
}

/* ---------- split suggestions ---------- */

.tn-app .splits {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.tn-app .split {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    background: transparent;
    border-color: var(--line);
    padding: 8px 12px;
}

.tn-app .split-k {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    color: var(--ink);
}

.tn-app .split-v {
    font-size: 0.7rem;
    color: var(--muted);
}

.tn-app .split.is-on {
    border-color: var(--accent);
    background: var(--accent-soft);
}

/* ---------- match list ---------- */

.tn-app .match {
    display: grid;
    /* time · court · home · games · away · result type
       THE NAME COLUMNS NEED A FLOOR. `minmax(0, 1fr)` lets them collapse to
       nothing once the games area grows — at best-of-3 the row carries six
       score boxes and a select, and "Nepean Nitro" wrapped to one character
       per line at 1440px. A 6rem floor is roughly the shortest a real club
       name can be set at without breaking mid-word. */
    grid-template-columns: 3.4rem 3rem minmax(6rem, 1fr) auto minmax(6rem, 1fr) auto;
    gap: 0.5rem;
    align-items: center;
    padding: 7px 0;
    border-bottom: 1px solid var(--line);
}

/* One pair of boxes per game in the series. They wrap rather than pushing the
   team names out of the row, which is what a best-of-7 would otherwise do. */
.tn-app .m-games {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.5rem;
    justify-content: center;
}

.tn-app .m-pair {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

/* An awarded result has no score to type, so the row says what happened
   instead of showing boxes that cannot be used. */
.tn-app .m-awarded {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    color: var(--muted);
    text-align: center;
}

.tn-app .m-status {
    font-family: var(--font-primary);
    font-size: 0.72rem;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm, 0.25rem);
    color: var(--muted);
    padding: 5px 6px;
    max-width: 9rem;
}

/* ---------- tiebreaker chain ---------- */

/* THE WORKED-EXAMPLE BANNER. A statement of what the page is showing, not an
   alert -- so it takes the hairline and the raised surface every panel uses
   rather than a colour of its own. The accent is spent on one word. */
.tn-app .tn-example {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem 1rem;
    margin-bottom: 1.25rem;
    padding: 0.75rem 1rem;
    background: var(--raised);
    border: 1px solid var(--line);
    border-left: 2px solid var(--accent);
    border-radius: var(--radius-lg);
}

.tn-app .tn-example-copy {
    margin: 0;
    max-width: 74ch;
    color: var(--dim);
    font-size: 0.82rem;
    line-height: 1.5;
}

.tn-app .tn-example-copy strong {
    color: var(--ink);
    font-weight: 600;
}

/* The prop row carries one more column than a team row: text, answer, weight,
   delete. Scoped to its host like every other `.tn-row` variant. */
.tn-app #propHost .tn-row {
    grid-template-columns: 2.2rem minmax(0, 1fr) minmax(0, 0.7fr) 4.5rem auto;
}

.tn-app .prop-pts {
    width: 100%;
    text-align: right;
}

.tn-app .tn-row.is-chain {
    grid-template-columns: 2.2rem minmax(0, 1fr) auto auto auto;
}

/* THE LIST FIELDS ARE QUIET UNTIL TOUCHED. `.tn-name` and `.tn-club` had no
   rules of their own at all, so they fell through to the base input styling --
   a darker-than-the-card fill inside a hard border. At twelve teams that is
   twenty-four boxes stacked down the panel; at the 144-team ceiling it is 288,
   and the list reads as a form to be filled rather than a list to be read.
   Grocery-list's `.gl-name` is the solved version of exactly this: transparent
   at rest, a hairline on hover, a real field on focus. Nothing is lost -- the
   text is still editable in place -- and the row's actual content, the name,
   becomes the thing you see.

   The club field goes further and drops to `--dim`: it is optional, it is
   blank on most rows, and twelve repetitions of the word "Club" should not
   carry the same weight as twelve team names. */
/* THE ATTRIBUTE SELECTOR IN THE BASE RULE IS WHY THESE CARRY `input[type=...]`
   THEMSELVES. `.tn-app input[type="text"]` is (0,2,1); a plain `.tn-app
   .tn-name` is (0,2,0) and loses to it no matter how far down the file it
   sits. The first version of this block was written without it and changed
   nothing at all -- caught by looking at the render, not by reading the
   cascade. Same class of trap as the doubled-class print contract. */
.tn-app input[type="text"].tn-name,
.tn-app input[type="text"].tn-club {
    background: transparent;
    border-color: transparent;
}

.tn-app input[type="text"].tn-club {
    color: var(--dim);
}

.tn-app input[type="text"].tn-name::placeholder,
.tn-app input[type="text"].tn-club::placeholder {
    color: var(--muted);
    opacity: 1;
}

@media (hover: hover) {
    .tn-app input[type="text"].tn-name:hover,
    .tn-app input[type="text"].tn-club:hover {
        border-color: var(--line);
    }
}

.tn-app input[type="text"].tn-name:focus,
.tn-app input[type="text"].tn-club:focus {
    background: var(--bg);
    border-color: var(--line-strong);
    color: var(--ink);
    outline: none;
}

/* The row tools are quiet too, and only the row under the pointer shows them
   at full strength. On touch there is no hover, so they simply stay legible. */
.tn-app #teamHost .tn-row .chain-btn,
.tn-app #teamHost .tn-row .tn-del {
    border-color: transparent;
    color: var(--muted);
}

@media (hover: hover) {
    .tn-app #teamHost .tn-row:hover .chain-btn,
    .tn-app #teamHost .tn-row:hover .tn-del {
        border-color: var(--line);
    }
}

.tn-app #teamHost .tn-row .chain-btn:focus-visible,
.tn-app #teamHost .tn-row .tn-del:focus-visible {
    border-color: var(--line-strong);
}

/* THE TEAM ROW HAS ITS OWN TEMPLATE, scoped to its host. `.tn-row` is shared
   by four lists -- teams, entrants, the tiebreaker chain and prop questions --
   so a bare `.tn-row` rule reaches all of them, and only this one carries the
   seeding controls. */
.tn-app #teamHost .tn-row {
    grid-template-columns: 2.2rem minmax(0, 1fr) minmax(0, 0.7fr) auto auto auto;
}

/* The armed half of the two-step delete. Red is semantic here rather than
   decorative -- this is the one press that destroys recorded results -- and it
   is the same hue `.tn-del:hover` already uses, so no new colour enters the
   system. */
.tn-app .tn-del.is-armed {
    border-color: var(--color-error, #E5484D);
    color: var(--color-error, #E5484D);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    padding: 6px 8px;
}

/* The armed half of "start over", on both of its buttons. Same semantic red as
   the delete arm and the same reasoning: this is the press that discards the
   whole document, which is the one thing on this page worth spending an error
   colour on. No new hue enters the system. */
.tn-app .ghost.is-armed {
    border-color: var(--color-error, #E5484D);
    color: var(--color-error, #E5484D);
}

/* THE ARMED LABEL MUST NOT BE WIDER THAN THE RESTING ONE. At 320px the actions
   row gives each button 68px; "Sure?" plus the reset glyph needs more than
   that and wrapped the row 44px -> 59px, jumping the header 15px in the middle
   of a two-step press -- under the thumb that is mid-gesture. The glyph means
   "start over" and the armed state is a question, not an action, so it is the
   part that goes. Measured: header stays 200px armed and unarmed. */
.tn-app .ghost.is-armed .lbl-icon {
    display: none;
}

.tn-app .tn-warn {
    grid-column: 1 / -1;
    display: block;
    padding: 6px 0 10px;
    border-bottom: 1px solid var(--line);
    color: var(--dim);
    font-size: 0.76rem;
    line-height: 1.45;
    max-width: 68ch;
}

.tn-app .chain-name {
    font-size: 0.84rem;
}

.tn-app .chain-btn {
    padding: 6px 10px;
    line-height: 1;
    font-family: var(--font-mono);
    border-color: var(--line);
    color: var(--dim);
}

/* AN UNAVAILABLE BUTTON IS aria-disabled, NEVER disabled — a disabled button
   cannot hold focus, so the browser blurs it and drops the user at the top of
   the document. It has to LOOK unavailable without being unfocusable.

   SCOPED TO THE CLASS, NOT TO .chain-btn, AND THAT IS THE FIX. recompute()
   also writes `is-off` onto #btnKnockout and #btnNextRound, which are `.ghost`
   — so while this rule read `.chain-btn.is-off` the not-yet-available state
   rendered NOTHING on the two buttons the refuse-with-a-reason pattern exists
   for, and "not ready" was pixel-identical to "ready".

   0.45, not 0.35, and the difference is measured rather than chosen: --dim
   #9E9A93 at 0.35 over --card #121214 composites to about 1.86:1, under any
   legible floor. 0.45 is the figure both sibling tools already use, and
   DESIGN.md requires a neutral to be measured before it goes in. */
.tn-app .is-off {
    opacity: 0.45;
    cursor: not-allowed;
}

/* An unavailable button must not physically depress under a press, or it
   reads as working while doing nothing. */
.tn-app .is-off:active {
    background: var(--raised);
    border-color: var(--line-strong);
}

.tn-app .chain-add {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 10px;
}

.tn-app .chain-add-btn {
    background: transparent;
    border-color: var(--line);
    border-style: dashed;
    color: var(--muted);
    font-size: 0.76rem;
}

/* A tight segmented control for a short list that sits inside a .field. */
.tn-app .tn-seg.tight {
    margin-bottom: 0;
    gap: 0.3rem;
}

.tn-app .tn-seg.tight .tn-seg-btn {
    flex: 0 0 auto;
    font-size: 0.72rem;
    padding: 6px 10px;
}

.tn-app .m-when,
.tn-app .m-court {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    color: var(--muted);
}

.tn-app .m-a {
    text-align: right;
}

.tn-app .m-a,
.tn-app .m-b {
    font-size: 0.84rem;
    min-width: 0;
    /* `anywhere` breaks mid-word, which is right for a long unbroken string and
       wrong for an ordinary two-word name — paired with the column floor above
       it now only fires when a single word genuinely cannot fit. */
    overflow-wrap: break-word;
}

.tn-app .m-v {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    color: var(--muted);
    text-align: center;
}

/* NO NATIVE SPINNERS ON A SCORE FIELD, and this is a correctness fix rather
   than a cosmetic one — found by the real-mouse suite, which is the only
   harness that could find it.

   The field is ~51px wide and Chromium paints its spinner in the right ~13px
   of it, so a quarter of the control is a pair of 6px arrows. A real press
   aimed at the CENTRE of the field lands on that spinner, and on an EMPTY
   number input a spinner press writes `min` — which here is 0. So a single
   click, with no typing at all, silently recorded a 0-0 result: state went to
   [[0, null]] and the match started counting as scored.

   dispatchEvent cannot reproduce this: it never hit-tests, so it always lands
   on the input proper. Typing and the keyboard arrows both still work. */
.tn-app .m-score {
    font-family: var(--font-mono);
    text-align: center;
    padding: 6px 4px;
    appearance: textfield;
    -moz-appearance: textfield;
}

.tn-app .m-score::-webkit-outer-spin-button,
.tn-app .m-score::-webkit-inner-spin-button {
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
}

/* A BYE IS MUTED, NEVER STRUCK THROUGH OR COLOURED. It is not a result and
   not a failure — the team simply advances. Weight carries it, as everywhere
   else on this page. */
.tn-app .match.is-bye {
    color: var(--muted);
}

.tn-app .m-bye {
    grid-column: 4 / 7;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
}

/* ---------- standings table ---------- */

.tn-app .table-scroll {
    overflow-x: auto;
    margin-bottom: 1rem;
    scrollbar-color: var(--line-strong) var(--card);
}

.tn-app table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.83rem;
}

/* Horizontal hairlines only. No zebra striping, no vertical rules, no outer
   border and no cell backgrounds — the idiom both shipped tables here use. */
.tn-app th {
    font-family: var(--font-mono);
    font-size: 0.64rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    border-bottom: 1px solid var(--line);
    padding: 6px 8px;
    white-space: nowrap;
    text-align: left;
}

.tn-app td {
    padding: 7px 8px;
    border-bottom: 1px solid var(--line);
    font-family: var(--font-mono);
}

.tn-app td.num,
.tn-app th.num {
    text-align: right;
    width: 1%;
    white-space: nowrap;
}

/* The one free-text column drops out of mono and is allowed to wrap; the
   numeric columns size themselves. Hard-coded pixel widths are what made the
   subnet plan table wrap on paper. */
.tn-app td.tname {
    font-family: var(--font-primary);
    overflow-wrap: anywhere;
}

.tn-app td.rank {
    color: var(--muted);
}

.tn-app td.pts {
    color: var(--ink);
    font-weight: 600;
}

/* ---------- bracket ----------
   Connectors are hairline strokes in --line; the advancing path is inked, not
   coloured. There is deliberately NO coloured left border on a node to mark a
   winner — that exact move is named in subnet-calculator.theme.css as an
   AI-slop pattern present in zero shipped tools here. */

.tn-app .bracket-scroll {
    overflow-x: auto;
    scrollbar-color: var(--line-strong) var(--card);
    padding-bottom: 8px;
}

.tn-app .bracket {
    display: flex;
    gap: 1.75rem;
    align-items: stretch;
    min-width: min-content;
}

.tn-app .bk-col {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    min-width: 12.5rem;
}

/* EQUAL-HEIGHT SLOTS ARE WHAT ALIGN A ROUND TO THE ONE THAT FEEDS IT.
   `flex: 1 1 0` gives every slot in a column the same height whatever its node
   contains, so four slots centre at 12.5/37.5/62.5/87.5 per cent and two
   centre at 25/75 -- putting each round-2 node exactly on the midpoint of its
   pair. The column used `justify-content: space-around`, which spreads nodes
   evenly and aligns nothing: the connector out of each node arrived beside the
   next node instead of at it, which is what "bad alignment" looked like. */
.tn-app .bk-slots {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
}

.tn-app .bk-slot {
    /* NO `min-height: 0` HERE. A flex item's automatic minimum size is its
       content, and that floor is the only thing stopping a basis-0 slot from
       collapsing: with it disabled every column shrank to nothing and the
       nodes overlapped each other outright. */
    flex: 1 1 0;
    display: flex;
    align-items: center;
}

/* The node is a flex ITEM now, so without this it shrink-wraps to its own text
   and every box in a column ends up a different width. */
.tn-app .bk-slot > .bk-node {
    flex: 1 1 auto;
    min-width: 0;
}

/* The third-place playoff shares the final's column but is fed by the semi
   LOSERS, so it is out of the slot run -- in it, the Final would be pushed off
   the centre its own feeders sit either side of. */
.tn-app .bk-extra {
    flex: 0 0 auto;
    margin-top: 0.6rem;
}

.tn-app .bk-head {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
    padding-bottom: 4px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 2px;
}

/* NO `overflow: hidden` HERE, and that is load-bearing rather than an
   omission. The connector below is an absolutely positioned ::after at
   `left: 100%` -- entirely outside the padding box -- so a clip on this
   element removes EVERY connector in the bracket and leaves three columns of
   disconnected boxes. It shipped that way: measured 2026-08-24, clientWidth
   198 against scrollWidth 226, the missing 28px being exactly the 1.75rem
   connector. Nothing inside a node paints a background, so the rounded corners
   need no clip to protect them, and .bk-name carries `overflow-wrap: anywhere`
   so a long club name wraps rather than escaping. */
.tn-app .bk-node {
    position: relative;
    border: 1px solid var(--line);
    border-radius: var(--radius-md, 0.375rem);
    background: var(--raised);
}

/* The connector: a hairline out of each node into the next column. Drawn with
   pseudo-elements so no extra markup is needed and nothing can drift out of
   sync with the DOM. */
.tn-app .bk-col:not(:last-child) .bk-slot .bk-node::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 100%;
    width: 1.75rem;
    border-top: 1px solid var(--line);
}

.tn-app .bk-team {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 7px 10px;
    font-size: 0.82rem;
    color: var(--dim);
}

.tn-app .bk-team + .bk-team {
    border-top: 1px solid var(--line);
}

/* WEIGHT, NOT HUE. Advanced is inked and bold; eliminated is muted. An
   undecided match stays neutral so a pending fixture does not read as a loss. */
.tn-app .bk-team.is-through {
    color: var(--ink);
    font-weight: 700;
}

.tn-app .bk-team.is-out {
    color: var(--muted);
}

/* Amber is spent on the champion and nothing else, because the champion is
   the finding. */
.tn-app .bk-team.is-champ {
    color: var(--accent);
}

.tn-app .bk-name {
    min-width: 0;
    overflow-wrap: anywhere;
}

.tn-app .bk-score {
    font-family: var(--font-mono);
    font-size: 0.76rem;
    color: var(--muted);
}

.tn-app .bk-node.is-bye,
.tn-app .bk-node.is-third {
    border-style: dashed;
}

.tn-app .bk-tag {
    padding: 3px 10px 6px;
    font-family: var(--font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
}

/* ---------- pool: picks ---------- */

.tn-app .pick-row {
    display: grid;
    grid-template-columns: 8rem minmax(0, 1fr) auto;
    gap: 0.5rem;
    align-items: center;
    padding: 7px 0;
    border-bottom: 1px solid var(--line);
}

.tn-app .pick-choose {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.tn-app .pick-btn {
    flex: 1 1 auto;
    background: transparent;
    border-color: var(--line);
    color: var(--dim);
    font-size: 0.8rem;
}

/* A chosen pick is inked and bordered, not filled with a second colour —
   the same weight-over-hue rule the bracket uses. */
.tn-app .pick-btn.is-on {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}

/* "not scored" beside a survivor pick that broke one of the two rules. Muted
   weight, never red: an invalid pick is not an error state, it is the rule
   working -- the same call the eliminated-entrant row makes. */
.tn-app .pick-warn {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
}

.tn-app .m-score.conf {
    width: 3.4rem;
}

/* ---------- pool: squares grid ---------- */

.tn-app .sq-scroll {
    overflow-x: auto;
    scrollbar-color: var(--line-strong) var(--card);
}

.tn-app .sq-grid {
    border-collapse: collapse;
    font-size: 0.75rem;
}

.tn-app .sq-grid th {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    color: var(--muted);
    padding: 4px 6px;
    text-align: center;
    border: 1px solid var(--line);
}

.tn-app .sq-grid td {
    border: 1px solid var(--line);
    padding: 0;
}

.tn-app .sq-cell {
    width: 4.6rem;
    border: 0;
    background: transparent;
    text-align: center;
    font-size: 0.72rem;
    padding: 6px 2px;
}

/* ---------- pool: leaderboard ---------- */

/* ELIMINATION IS MUTED, NEVER RED. Being knocked out of an office pool is not
   an error state — it is the game working. Same rule as a lost match. */
.tn-app tr.is-out td {
    color: var(--muted);
}

/* ---------- the paper document ----------
   Hidden on screen, block in print. Built from state as plain text, so there
   is not one form control inside it. */

.tn-app .tn-paper {
    display: none;
}

/* ---------- summary stats ---------- */

.tn-app .stat {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 8px 0;
    border-bottom: 1px solid var(--line);
}

.tn-app .stat .k {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
}

.tn-app .stat .v {
    font-family: var(--font-mono);
    font-size: 0.95rem;
    color: var(--ink);
}

.tn-app .stat.big {
    display: block;
    border-bottom: 0;
    padding-bottom: 0;
}

/* THE PAYOFF FIGURE HAS TO READ AS THE PAYOFF. It was Geist Mono at weight
   400 in the same amber as every panel heading on the page -- thinner and
   smaller than both sibling tools' equivalent, and painted in a colour used
   thirteen other times here, so the one number the hub card calls the tool's
   reason to exist did not stand out from the furniture. Display face, 800,
   in --ink: the treatment grocery-list and pros-cons already share, and it
   hands the accent back to the champion, which is what this file's own
   header says amber is for. */
.tn-app .stat.big .v {
    font-family: var(--font-display);
    font-size: 2.6rem;
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin: 2px 0 1px;
}

/* ---------- verdict ---------- */

.tn-app .verdict {
    margin-top: 16px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm, 0.25rem);
    background: var(--raised);
}

.tn-app .verdict.empty {
    background: transparent;
}

/* A refusal is escalated by WEIGHT and hairline, not by hue. It is not an
   error — the tool is answering the question correctly, and the answer is
   "that is a season". */
.tn-app .verdict.refused {
    border-color: var(--line-strong);
}

.tn-app .verdict-head {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--ink);
    margin-bottom: 6px;
}

.tn-app .verdict-why {
    font-size: 0.8rem;
    color: var(--dim);
}

.tn-app .verdict-warn {
    margin-top: 8px;
    font-size: 0.76rem;
    color: var(--muted);
}

.tn-app .alt {
    display: block;
    width: 100%;
    text-align: left;
    margin-top: 6px;
    font-size: 0.76rem;
    background: transparent;
    border-color: var(--line);
    color: var(--dim);
}

/* ---------- CTA ---------- */

.tn-app .tool-cta {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--line);
    color: var(--dim);
    font-size: 0.86rem;
}

.tn-app .tool-cta p + p {
    margin-top: 1rem;
}

/* ======================================================================
   TOUCH — 620px and below.
   Every size below is px, never rem. styles.css:1144 drops :root to 15px at
   480px, so a rem-based 44px target renders 41px on exactly the phones that
   need it most. Text inputs never go below 16px: iOS Safari zooms the
   viewport on focus below that and does not zoom back out.
   ====================================================================== */

@media (max-width: 620px) {
    .tn-app .wrap {
        padding: 96px 1rem 72px;
    }

    /* THE HEADER COMPETES WITH THE FOLD, and this is a measured budget, not a
       tidy-up. Adding the pool mode switch pushed the team field to 560px
       against the iPhone SE's 460px of visible height — caught by the fold
       suite, which is the only check that asserts about the viewport rather
       than an element.
       At 320px the header was 332px: title 82 + mode switch 94 (two rows) +
       actions 96 (two rows). Both segments are forced onto ONE row here and
       the shorter labels are what make that fit. If you lengthen an action
       label or add a fourth, re-measure — do not assume the headroom is
       still there. */
    .tn-app header.app {
        gap: 0.6rem;
        margin-bottom: 1rem;
        padding-bottom: 0.75rem;
    }

    .tn-app .lbl-full {
        display: none;
    }

    .tn-app .lbl-short {
        display: inline;
    }

    .tn-app .actions {
        width: 100%;
        gap: 0.4rem;
    }

    .tn-app .actions button {
        flex: 1 1 0;
        padding: 10px 6px;
        font-size: 14px;
    }

    .tn-app #modeSeg {
        width: 100%;
        margin: 0;
    }

    /* `flex: 1 1 45%` is right for the five-button pool picker and wrong for a
       two-button switch, which it forces onto two rows. */
    .tn-app #modeSeg .tn-seg-btn {
        flex: 1 1 0;
    }

    .tn-app button {
        min-height: 44px;
        padding: 10px 14px;
        font-size: 15px;
    }

    .tn-app input[type="text"],
    .tn-app input[type="number"],
    .tn-app textarea {
        font-size: 16px;
        min-height: 44px;
        padding: 10px 12px;
    }

    .tn-app .doc-title {
        font-size: 20px;
        min-height: 44px;
    }

    /* The row goes to two lines on a phone. At 320px the four columns need
       more content width than the viewport has, and the club field collapsed
       under the name field rather than beside it. */
    .tn-app .tn-row {
        grid-template-columns: 2.2rem minmax(0, 1fr) 52px;
        gap: 8px;
        padding: 10px 0;
    }

    .tn-app .tn-club {
        grid-column: 2 / 4;
    }

    .tn-app .tn-del {
        min-width: 44px;
        min-height: 44px;
        grid-row: 1;
        grid-column: 3;
    }

    /* THE TEAM ROW IS THREE LINES ON A PHONE, because three honest 44px tools
       plus the seed and the name do not fit on one. Measured the pros-cons
       way rather than eyeballed: at 320px the row has 231px of content width
       and up + down + delete alone need 132px before gaps, which leaves the
       name field 60px. So the tools take a line of their own and each one
       keeps its full target. Do not tighten this back to two lines without
       re-measuring, and do not shave the targets to force it. */
    .tn-app #teamHost .tn-row {
        grid-template-columns: 2.2rem repeat(3, minmax(44px, 1fr));
        row-gap: 6px;
    }

    .tn-app #teamHost .tn-seed { grid-row: 1; grid-column: 1; }
    .tn-app #teamHost .tn-name { grid-row: 1; grid-column: 2 / 5; }
    .tn-app #teamHost .tn-club { grid-row: 2; grid-column: 1 / 5; }

    .tn-app #teamHost .tn-row [data-tmove="up"] { grid-row: 3; grid-column: 2; }
    .tn-app #teamHost .tn-row [data-tmove="down"] { grid-row: 3; grid-column: 3; }

    .tn-app #teamHost .tn-row .tn-del {
        grid-row: 3;
        grid-column: 4;
        margin-left: 0;
    }

    .tn-app #teamHost .tn-row .chain-btn {
        min-width: 44px;
        min-height: 44px;
    }

    /* Same reasoning as the team row: the weight field plus the delete button
       do not fit beside the question text at 320px. */
    .tn-app #propHost .tn-row {
        grid-template-columns: 2.2rem minmax(0, 1fr) 4.5rem 44px;
    }

    .tn-app #propHost .tn-club {
        grid-row: 2;
        grid-column: 1 / 5;
    }

    /* A 24px checkbox in a label that shrink-wraps its content is a 24px
       target. The grocery list's answer applies here: THE LABEL IS THE TARGET,
       so the whole 44px box toggles natively with no click handler to keep in
       sync. The checkbox itself stays a comfortable 24px inside it — a 44px
       checkbox glyph looks broken; a 24px glyph in a 44px label does not. */
    .tn-app .field input[type="checkbox"] {
        width: 24px;
        height: 24px;
    }

    .tn-app .field:has(input[type="checkbox"]) {
        min-height: 44px;
        justify-content: center;
    }

    /* The <summary> is a real control — it opens the paste panel — and was
       15px tall, the one target the first pass missed. Same class of miss as
       the grocery list's .doc-title: it does not look like a control, so it
       did not read as one needing a target size. */
    .tn-app .bulk summary,
    .tn-app .explain summary {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* Below 16px iOS Safari zooms the viewport on focus and does not zoom back
       out. The textarea was 0.8rem = 12px. */
    .tn-app .tn-area {
        font-size: 16px;
    }

    .tn-app .tn-seg-btn {
        flex: 1 1 45%;
    }

    /* THE PHONE BLOCK'S BUTTON RULE REACHES ALMOST NONE OF THESE BUTTONS.
       `.tn-app button` above is (0,1,1); every button class on this page is
       (0,2,0) and wins on font-size regardless of source order -- so the
       format picker, the pool picker, the mode switch, the alternatives and
       every pick button kept 0.76-0.8rem, which is 11-12px below 480px where
       styles.css drops :root to 15. The 44px target survived (those rules
       declare no min-height, so the element rule still applies per-property);
       it was only ever the text. Mirror image of the .sq-cell case below,
       where an ATTRIBUTE selector at (0,2,1) beats the class instead. */
    .tn-app .tn-seg-btn,
    .tn-app .pick-btn,
    .tn-app .chain-add-btn,
    .tn-app .alt,
    .tn-app .split-k {
        font-size: 15px;
    }

    .tn-app .split-v {
        font-size: 13px;
    }

    .tn-app .tn-seg.tight .tn-seg-btn {
        min-width: 44px;
    }

    /* ---------- pool mode: its first phone rules of any kind ----------
       Not one pool selector appeared in this block before, while the
       tournament half was budgeted line by line against a 320x460 iPhone SE.
       Measured 2026-08-24: .pick-choose overflowed its panel by 8-25px at
       320px and was CLIPPED by .panel's overflow:hidden, so the second team
       button was cut in half on screen -- and in print, where .panel is
       overflow:visible, the same defect escapes the box instead. */

    /* Two lines, for the same reason .match takes three. At 320px the row has
       about 246px of content width; an 8rem fixture label plus a 3.4rem
       confidence box leaves ~48px for TWO team buttons. So the fixture and its
       confidence rank take line one and the picks take the full width below.
       Do not tighten this back to one line without re-measuring. */
    .tn-app .pick-row {
        grid-template-columns: minmax(0, 1fr) 72px;
        row-gap: 8px;
        padding: 10px 0;
    }

    .tn-app .pick-row .m-when {
        grid-column: 1;
        font-size: 12px;
    }

    .tn-app .pick-row .m-score.conf {
        grid-column: 2;
        width: 100%;
        min-width: 52px;
    }

    .tn-app .pick-row .pick-choose {
        grid-column: 1 / 3;
    }

    /* `flex: 1 1 auto` leaves min-width at its `auto` default, so a button
       cannot shrink below the widest word in a club name and the row overflows
       instead of wrapping. This is the whole clipping fix. */
    .tn-app .pick-btn {
        min-width: 44px;
        overflow-wrap: anywhere;
    }

    /* THE ATTRIBUTE SELECTOR IS NOT DECORATION. `.tn-app .sq-cell` is (0,2,0)
       and the generic `.tn-app input[type="text"]` above is (0,2,1), so the
       generic rule wins and forces 16px text with 10px/12px padding into a
       4.6rem box -- about 46px of content inside a 69px cell, ten across. This
       re-specifies at (0,2,1) to match, which is the same trap documented for
       .tn-name. 16px stays 16px: the narrow suite measures this input for the
       iOS focus-zoom rule and dropping it would be a real defect. */
    .tn-app input[type="text"].sq-cell {
        width: 56px;
        padding: 10px 2px;
        font-size: 16px;
    }

    /* A 10-wide grid scrolls sideways on a phone, which is fine -- losing the
       row's digit while you scroll is not, because the digit is the whole
       point of the row. */
    .tn-app .sq-grid th:first-child,
    .tn-app .sq-grid td:first-child {
        position: sticky;
        left: 0;
        background: var(--card);
    }

    /* The match row goes to three lines on a phone. Six columns do not fit at
       320px and the score boxes were being squeezed under 44px, so home and
       away take a line each and the games sit between them. */
    .tn-app .match {
        grid-template-columns: 3.4rem 3rem minmax(0, 1fr);
        row-gap: 8px;
        padding: 10px 0;
    }

    .tn-app .m-games,
    .tn-app .m-awarded {
        grid-column: 1 / 4;
    }

    .tn-app .m-b,
    .tn-app .m-status {
        grid-column: 1 / 4;
        max-width: none;
    }

    .tn-app .m-score {
        min-width: 52px;
    }

    .tn-app .m-status {
        min-height: 44px;
        font-size: 16px;
    }

    .tn-app .chain-btn {
        min-width: 44px;
        min-height: 44px;
    }

    .tn-app .tn-seg.tight .tn-seg-btn {
        font-size: 15px;
        padding: 10px 12px;
        min-height: 44px;
    }

    /* Shorter on a phone: 26rem of list plus the add bar plus the panel head
       already exceeds a 460px iPhone SE viewport on its own. */
    .tn-app .tn-list {
        max-height: 60vh;
    }
}

/* ======================================================================
   PRINT
   Only tool-specific surfaces belong here. The body reset, color-scheme,
   the ten light variables, .wrap, the .print-only masthead, the .grid flex
   fix for Gecko and the page-break atom list all live in tool-base.css and
   must NOT be re-declared.
   ====================================================================== */

/* THE SMALLEST PHONE STILL HAS TO SHOW THE BANNER WHOLE. At 320x460 -- an
   iPhone SE with browser chrome taken off -- the example bar measured 464
   against a 460 fold, four pixels over, so the sentence explaining what the
   page is showing was cut in half on exactly the device least able to
   recover from it. Trim the bar, never the assertion: the same call the
   grocery list made at this width, and the fold suite holds it. */
@media (max-width: 360px) {
    /* FOUR ACTIONS DO NOT FIT AT 320px IN GECKO WITHOUT THIS, and the reason is
       the scrollbar. Firefox reserves a classic 12px one where
       chrome-headless-shell reserves none, so the document is 308px wide, not
       320 -- leaving 276px of content against four 65px buttons plus three
       6.4px gaps, which is 279. It wrapped the actions row 44px -> 62px and
       pushed the example banner to 465 against a 460 fold: a Firefox-only fold
       failure from a change that measured free in Chromium.
       Trimming the gap to 4px gives back 7.2px and it fits at 272. Per this
       file's own rule, trim the bar, never the assertion. */
    .tn-app .actions {
        gap: 0.25rem;
    }

    .tn-app .actions button {
        padding: 10px 4px;
    }

    .tn-app .tn-example {
        padding: 0.5rem 0.75rem;
        margin-bottom: 0.875rem;
        gap: 0.5rem 0.75rem;
    }

    .tn-app .tn-example-copy {
        font-size: 0.78rem;
        line-height: 1.45;
    }
}

@media print {

    /* Every editor panel is .no-print, so there is deliberately no blanket
       `input, select { display: none }` here. A blanket hide would silently
       swallow a control that leaked into the printed area instead of the QA
       suite catching it. */

    .tn-app .panel {
        border-color: #d7dce3;
        background: #fff;
        overflow: visible;
        /* NOT break-inside: avoid — on a CARD that sends anything too tall for
           the remaining space to a fresh sheet, which is how the estimator
           printed four pages with two of them half empty. The atoms carry it
           instead, from tool-base.css. */
    }

    .tn-app .stat,
    .tn-app .verdict {
        break-inside: avoid;
    }

    .tn-app .tool-cta {
        display: none;
    }

    /* THE WHOLE PRINTED DOCUMENT. Every editor panel carries .no-print, so
       this plus the masthead is all that reaches paper — which is why this
       tool needs no print mirrors and the suite can assert zero form controls
       in the printed area. */
    .tn-app .tn-paper {
        display: block;
    }

    .tn-app .pp-head {
        font-family: var(--font-mono);
        font-size: 0.72rem;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        color: var(--ink);
        margin: 14px 0 6px;
        padding-bottom: 4px;
        border-bottom: 1px solid var(--line);
        /* A heading stranded at the foot of a sheet is worse than a short
           page, so it stays with what follows it. */
        break-after: avoid;
        break-inside: avoid;
    }

    .tn-app .pp-sub {
        font-family: var(--font-mono);
        font-size: 0.64rem;
        letter-spacing: 0.1em;
        text-transform: uppercase;
        color: var(--dim);
        margin: 10px 0 4px;
        break-after: avoid;
    }

    .tn-app .pp-table {
        width: 100%;
        border-collapse: collapse;
        font-size: 9.5px;
        margin-bottom: 6px;
    }

    .tn-app .pp-table th {
        font-family: var(--font-mono);
        font-size: 8px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        text-align: left;
        color: var(--muted);
        border-bottom: 1px solid var(--line);
        padding: 3px 5px;
        white-space: nowrap;
    }

    .tn-app .pp-table td {
        border-bottom: 1px solid var(--line);
        padding: 3px 5px;
        font-family: var(--font-mono);
    }

    .tn-app .pp-table td.pp-team {
        font-family: var(--font-primary);
        overflow-wrap: anywhere;
    }

    .tn-app .pp-table td.num,
    .tn-app .pp-table th.num {
        text-align: right;
        width: 1%;
        white-space: nowrap;
    }

    .tn-app .pp-v {
        color: var(--muted);
        text-align: center;
        width: 1%;
    }

    /* Repeating the header on every sheet MUST be paired with pinning tfoot
       back to a body group — tfoot defaults to table-footer-group, so turning
       thead on silently turns any tfoot into a repeating footer. */
    .tn-app .pp-table thead {
        display: table-header-group;
    }

    .tn-app .pp-table tfoot {
        display: table-row-group;
    }

    /* Atoms, never cards. break-inside on a card sends anything too tall for
       the remaining space to a fresh sheet. */
    .tn-app .pp-table tr,
    .tn-app .pp-list li {
        break-inside: avoid;
    }

    .tn-app .pp-list {
        list-style: none;
        margin: 0 0 8px;
        padding: 0;
        font-size: 9.5px;
        font-family: var(--font-mono);
    }

    .tn-app .pp-list li {
        padding: 2px 0;
        border-bottom: 1px solid var(--line);
    }

    .tn-app .pp-card {
        break-inside: avoid;
        margin-bottom: 8px;
    }

    .tn-app .pp-plan {
        font-family: var(--font-mono);
        font-size: 9px;
        color: var(--dim);
        margin-bottom: 10px;
        padding-bottom: 6px;
        border-bottom: 1px solid var(--line);
    }

    .tn-app .pp-note {
        font-size: 9px;
        color: var(--muted);
        margin-top: 4px;
    }

    /* A blank score cell has to be visibly writeable, so it gets height and a
       rule rather than being an empty box the eye slides over. */
    .tn-app .pp-sheet td.pp-blank {
        min-width: 46px;
        height: 20px;
    }
}
