/* Host scaffold only. Base typography, colours and element resets come from
   default-fuib.css (linked first in App.razor) — do not restate them here. Any colour added
   later must go through Fluent design tokens (var(--colorNeutral*), var(--colorBrand*)) so
   both the light and dark themes stay legible. */

html,
body {
    height: 100%;
    margin: 0;
}

/* Every page lays itself out with a FluentStack, which spaces its children itself. default-fuib.css
   zeroes the top margin of a heading but leaves the browser's bottom margin — and because it also
   sets h1 to the hero type size, that inherited margin is ~27px of nothing under a heading whose
   stack already put 16px there. The stack owns the spacing; headings own none. */
h1,
h2,
h3 {
    margin-bottom: 0;
}

/* FocusOnNavigate in Routes.razor puts the caret on the h1 after every navigation, so that a
   screen reader lands at the top of the page it just moved to. It does that by giving the heading
   tabindex="-1" — and the browser then draws its focus ring, on every page, every time. A heading
   with tabindex="-1" is not reachable by Tab, so that ring can only ever mean "the router just
   navigated", which is not something anybody needs a black box around. Only this case is
   suppressed; a real focusable element keeps its ring. */
h1[tabindex="-1"]:focus {
    outline: none;
}

/* The FluentUI bundle has no rule for Blazor's error banner, so it showed on every page.
   Hidden by default; the framework un-hides it by setting an inline display when a circuit
   error actually occurs. */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: 0.75rem 1.25rem;
    background: var(--colorPaletteRedBackground2, #fde7e9);
    color: var(--colorNeutralForeground1, #242424);
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

/* ===========================================================================
   The app shell: a navbar on a desktop, a bottom bar on a phone (#19)
   ===========================================================================

   WHAT THIS REPLACED. One wrapping row of links, measured in
   docs/mobile-ux-review.md B7 at 78px over three rows for a manager, with nine
   tappable controls 20px tall and one pixel between them — under the WCAG 2.2
   floor, never mind Apple's 44pt or Material's 48dp. ~90% of manager use and
   ~99% of player use is on a phone.

   ONE BREAKPOINT, 768px, and it is FluentLayout's own (MobileBreakdownWidth).
   Below it: a one-line top bar and a four-slot bottom bar. Above it: a single
   navbar row and no bottom bar at all. Everything here is written phone-first,
   so the media query at the end is the desktop half.

   THE BOTTOM BAR IS THE LAYOUT'S FOOTER ROW, not a fixed overlay. FluentLayout
   is a grid — header / content / footer — whose middle row is the scroller, so
   the bar is laid out AROUND the page instead of on top of it and there is no
   padding to keep in step with its height. See MainLayout for what that costs
   (100dvh rather than 100vh) and why.

   THE SAFE AREA is padding on the bar itself: an iPhone's home indicator and
   Android's gesture bar sit inside the viewport, so without it the last 34px of
   the bar are under the system's own furniture. */

:root {
    /* One slot's height, and the offset a sheet opens above. 3.5rem is 56px:
       comfortably past the 44px minimum with an icon and a label in it. Both
       rules read this, so a taller bar cannot leave a gap under its sheet. */
    --bottom-nav-height: 3.5rem;
}

/* Plain <a> and <button>, never FluentLink or FluentButton.

   FluentLayoutItem[area="header"] paints itself --colorBrandBackground and sets
   --colorNeutralForegroundOnBrand for its children, but FluentLink and
   FluentButton each set their own foreground inside a shadow root, so on the
   brand bar they render near-black and fail contrast. Rather than reaching into
   a shadow DOM, these are plain elements that inherit the bar's colour — which
   is the token the layout already chose. No colour is named here. */
.header-action {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0 0.25rem;
    background: none;
    border: 0;
    font: inherit;
    color: inherit;
    text-decoration: underline;
    cursor: pointer;
}

/* The app's name, which is also the link home. It is a title first and a link
   second: underlining it would make the bar read as a row of equal links with
   no name on it.

   `.header-action` IS IN THE SELECTOR BECAUSE IT HAS TO WIN, not because the
   element needs describing. The brand carries both classes and .header-action
   sets `text-decoration: underline` three rules up; as a bare `.header-brand`
   this tied with it and got the un-underlining only because it is written
   lower — the same accident that put the account menu off the bottom of the
   screen in #38. Two classes beats one wherever either is written.
   StylesheetTests holds the rule. */
.header-action.header-brand {
    flex: none;
    font-size: 1.0625rem;
    font-weight: 700;
    text-decoration: none;
}

/* The header area holds two rows now — the bar, and the player-view banner
   under it — so it stacks rather than laying its children out in a line.

   THE ANCESTOR IN THE SELECTOR IS LOAD-BEARING, on this rule and on the footer's
   below. The library styles these areas with `.fluent-layout-item[area=header]`
   — one class and one attribute, the same specificity as `.fluent-layout-item.
   app-topbar` — and a tie is supposed to go to whichever sheet is linked last,
   which is this one. Measured in Chrome, it does not: the area kept the
   library's `padding: 8px` and, on the footer, its grey `background-color`,
   while the properties the library does NOT set (a border, a flex direction)
   took ours from the same rule. Adding `.fluent-layout` puts the question beyond
   a tie-break nobody can reproduce on demand. If you write another rule against
   a `[area=...]` element, write it this way and check it in a browser. */
.fluent-layout .fluent-layout-item.app-topbar {
    flex-direction: column;
    padding: 0;
    overflow: visible;
}

/* THE GAPS HERE ARE A BUDGET, NOT A TASTE. This row wraps rather than overlaps
   (see .navbar), so every pixel it does not need is a pixel of margin before a
   manager's Turkish navbar goes onto two lines.

   Measured at 1280 signed in as a manager, which is the widest this app gets.
   Adding the account name in #38 took the Turkish row to brand 113 + navbar 902
   + account 226 + 48 of gaps and padding = 1289, against 1256 of usable width,
   and it wrapped. Tightening these gaps and the navbar's own paid 62px of that
   back; shortening two labels (Kartlar -> QR, Denetim kaydı -> Logs) paid the
   rest. Turkish now needs 1167 and English 1203 — the two renames were worth
   more in Turkish, so ENGLISH is the tight one now and the one to measure.

   Spend the remaining ~53px knowingly: if you widen a gap, lengthen a nav label
   or add a destination, measure at 1280 as a manager in BOTH languages rather
   than assuming the slack is still there. */
.topbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    width: 100%;
    padding: 0 0.75rem;
}

.topbar-account {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-inline-start: auto;
}

/* The account menu's trigger: who you are, what you are, and a chevron saying it
   opens. */
.account-menu {
    position: relative;
}

.account-trigger {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    min-height: 2.75rem;
    list-style: none;
    cursor: pointer;
}

.account-trigger::-webkit-details-marker {
    display: none;
}

/* The account name, on the bar rather than only inside the panel (#38).

   THE CAP IS ARITHMETIC, NOT TASTE. A manager's navbar plus the account laid out
   in a line needs about 1500px — that measurement is why the account became a
   menu at all — so a long name is the one thing left that can put this row back
   onto two.

   ENGLISH IS THE BINDING LANGUAGE FOR THIS ONE. Turkish is usually the long case
   in this app and it was here too until #38 shortened two labels — "Denetim
   kaydı" lost 59px becoming "Logs" while "Audit log" lost 24, which flipped it.
   English now has both the wider navbar (806 against 788, on "Previous
   tournaments") and the wider role chip next to this name ("Tournament manager"
   against "Turnuva yöneticisi"). Measured at 1280, everything on the row except
   this name: English 1143, Turkish 1109, inside 1256 of usable width. So English
   allows a 113px name and Turkish 147, and the smaller number is the one that
   decides. 6rem leaves 17px of margin on it.

   It has to be a CAP and not merely something shrinkable: flexbox wraps a line
   before it shrinks anything on it, so `min-width: 0` and a flex-shrink would
   not save the row. What the cap costs is a long username reading as
   `kaanyucelm…` — which is why the panel it opens still spells the name out in
   full. */
.account-name {
    max-width: 6rem;
    overflow: hidden;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The panel this trigger opens has no rule here at all — it is laid out in the
   desktop media query at the bottom of this file, as `.sheet.account-panel`, and
   putting one back here is how #38 happened. Read the comment there before
   moving it. */

/* Everything the top bar shows only when there is room for it. On a phone these
   live in the account sheet at the bottom of the screen, where a thumb is.

   `.topbar` in the selector, because some of the things carrying this class have
   a display of their own — .language-picker is a flex row — and a bare
   `.topbar-wide` ties with it on specificity and loses on order. The language
   switch was showing up on the brand bar at 375px, half off the right edge. */
.topbar .topbar-wide {
    display: none;
}

/* And its opposite: the role chip has to be visible in both layouts, but on a
   desktop it is the account menu's trigger rather than a chip of its own. */
.topbar .topbar-narrow {
    display: flex;
}

.topbar-user {
    white-space: nowrap;
}

.language-picker {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.language-current {
    opacity: 0.8;
}

/* ---------------------------------------------------------------------------
   The persistent role indicator (#19, option A)
   ---------------------------------------------------------------------------
   What you are, on every page, in both layouts. An outline, an icon and the
   word: the icon differs in shape and the word says it outright, so nothing
   here depends on telling two colours apart. */
.role-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    flex: none;
    padding: 0.125rem 0.5rem;
    border: 1px solid currentColor;
    border-radius: 999px;
    font-size: 0.75rem;
    white-space: nowrap;
}

.role-chip-manager {
    font-weight: 600;
}

/* ---------------------------------------------------------------------------
   The navbar — desktop only
   --------------------------------------------------------------------------- */
/* Wraps rather than overlaps. The manager's row is the widest thing in the app —
   three public destinations, a labelled group of five, and the toggle — and below
   about 1200px it does not fit beside the brand and the account menu. Wrapping
   costs a second line at those widths; not wrapping cost "Sign out" printed on
   top of "View as player", which is what this looked like before the account
   menu took four controls out of the line. */
.navbar {
    display: none;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.25rem 0.125rem;
    min-width: 0;
}

/* 0.25rem of side padding and not more. Nine of these are in a manager's row, so
   every extra pixel here costs nine — which is what the row could not afford once
   the account name went on the trigger (#38). The target is still 44px tall, and
   4px of padding either side of a label plus the 2px column gap leaves 10px
   between one destination and the next. */
.navbar-link {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    min-height: 2.75rem;
    padding: 0 0.25rem;
    background: none;
    border: 0;
    border-bottom: 3px solid transparent;
    font: inherit;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}

.navbar-link:hover {
    text-decoration: underline;
}

/* The page you are on. Three channels and one of them is the shape of the
   text: heavier, underscored by a rule, and announced as aria-current="page"
   for whoever is listening rather than looking. */
.navbar-link.current {
    font-weight: 700;
    border-bottom-color: currentColor;
}

/* The manager's own section (#19). A rule and a heading, not five more links in
   the same line — position is what carries the meaning, so a manager works out
   which half of the app is theirs once rather than per link. */
.navbar-group {
    display: flex;
    align-items: center;
    /* The same column gap the navbar itself uses. The links inside carry their
       own side padding, so this is the space between two of those — it was twice
       the navbar's and the difference was not doing anything a reader could see,
       while six of them are 12px of a row that has none to spare. */
    gap: 0.125rem;
    min-width: 0;
    margin-inline-start: 0.375rem;
    padding-inline-start: 0.5rem;
    border-inline-start: 1px solid currentColor;
}

.navbar-group-label {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.85;
    white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   "View as player" (#19, option D)
   ---------------------------------------------------------------------------
   The banner while it is on. Full width, under the bar, on every page: a
   manager who forgets they are in this mode is the one way the toggle can
   mislead, so it is loud, and the way out is inside it. */
.player-view-banner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    width: 100%;
    padding: 0.5rem 0.75rem;
    background: var(--colorPaletteYellowBackground2, #fbf6d9);
    color: var(--colorNeutralForeground1, #242424);
}

/* The icon keeps its size. A flex item's default is to shrink, and an SVG with
   no intrinsic width to defend shrank to a sliver beside two lines of wrapped
   Turkish. */
.player-view-banner svg {
    flex: none;
}

.player-view-text {
    flex: 1 1 12rem;
    font-weight: 600;
}

.player-view-exit {
    flex: none;
    min-height: 2.75rem;
    padding: 0 0.75rem;
    border: 1px solid var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusMedium);
    background: var(--colorNeutralBackground1);
    color: inherit;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

/* ---------------------------------------------------------------------------
   The bottom bar — phone only
   --------------------------------------------------------------------------- */
.fluent-layout .fluent-layout-item.app-bottomnav {
    padding: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--colorNeutralBackground1);
    border-top: 1px solid var(--colorNeutralStroke2);
    /* The sheets open upward out of this box. */
    overflow: visible;
}

.bottomnav {
    display: flex;
    align-items: stretch;
}

/* A slot is 56px tall and a quarter of the screen wide — 93px at 375px, which
   is the size a thumb is actually aiming at. Both the links and the two
   <summary> elements are slots, which is why the reset here covers a button's
   borders and a summary's marker. */
.bottomnav-slot {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.125rem;
    min-width: 0;
    height: var(--bottom-nav-height);
    padding: 0 0.25rem;
    border: 0;
    /* Reserved whether or not this is the current page, so nothing moves by 3px
       when it becomes one. */
    border-top: 3px solid transparent;
    background: none;
    font: inherit;
    color: var(--colorNeutralForeground2);
    text-decoration: none;
    list-style: none;
    cursor: pointer;
}

.bottomnav-slot::-webkit-details-marker {
    display: none;
}

/* One line, always. Turkish is the long case — "Puan durumu" is 11 characters
   against English's "Standings" — and it fits a 93px slot at this size with
   room to spare. The ellipsis is insurance for a language nobody has added
   yet, not something any label reaches today. */
.bottomnav-label {
    max-width: 100%;
    overflow: hidden;
    font-size: 0.6875rem;
    line-height: 1.1;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bottomnav-slot.current {
    border-top-color: var(--colorBrandStroke1, var(--colorBrandForeground1));
    color: var(--colorBrandForeground1);
}

.bottomnav-slot.current .bottomnav-label {
    font-weight: 700;
}

/* The manager's section is one slot, and the rule before it says where the
   player's half of the bar ends. */
.bottomnav-slot-manager {
    border-inline-start: 1px solid var(--colorNeutralStroke2);
}

/* An open sheet's slot is held down. Without it the bar looks the same whether
   a panel is up or not, and the way to close one is to press the slot again —
   which is only obvious if the slot looks pressed. */
.bottomnav-sheet[open] > .bottomnav-slot {
    background: var(--colorNeutralBackground3);
}

/* `display: block` and not flex. A closed <details> still lays its hidden panel
   out as a child, so as a flex row the summary shared the slot with it and came
   out 8px narrower than the two plain slots beside it — visible as a bar whose
   four cells are not the same width. As a block, the summary is the only thing
   with a width to take. */
.bottomnav-sheet {
    display: block;
    flex: 1 1 0;
    min-width: 0;
}

/* The panel a slot opens. Fixed rather than absolute: it spans the screen, not
   the 93px slot it belongs to, and it has to clear the bar's own height plus
   the safe-area inset underneath it. */
.sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 0px));
    max-height: 60vh;
    padding: 0.5rem 0;
    overflow-y: auto;
    background: var(--colorNeutralBackground1);
    border-top: 1px solid var(--colorNeutralStroke2);
    box-shadow: 0 -8px 16px rgba(0, 0, 0, 0.14);
}

.sheet-row {
    display: flex;
    align-items: center;
    box-sizing: border-box;
    width: 100%;
    min-height: 3rem;
    padding: 0 1rem;
    border: 0;
    border-inline-start: 3px solid transparent;
    background: none;
    font: inherit;
    color: inherit;
    text-align: start;
    text-decoration: none;
    cursor: pointer;
}

.sheet-row.current {
    border-inline-start-color: var(--colorBrandStroke1, var(--colorBrandForeground1));
    color: var(--colorBrandForeground1);
    font-weight: 700;
}

/* Who this sheet is about: the username, or the role whose destinations these
   are. Not a row — there is nothing to press. */
.sheet-who {
    margin: 0;
    padding: 0.25rem 1rem 0.5rem;
    color: var(--colorNeutralForeground3);
    font-size: 0.8125rem;
}

.sheet-languages {
    gap: 1rem;
    padding: 0.25rem 1rem;
}

.sheet-languages .header-action {
    padding: 0 0.25rem;
}

/* ---------------------------------------------------------------------------
   A manager's control on a page every player reads (#19)
   ---------------------------------------------------------------------------
   See ManagerOnly.razor for what this is answering. Three channels: the tint
   for the fast read across a lit hall, the rule down the leading edge for when
   colour is gone entirely, and the label for everybody the first two miss —
   including a screen reader, which gets only the words. */
.manager-only {
    display: flex;
    flex-direction: column;
    align-self: start;
    gap: 0.5rem;
    padding: 0.75rem;
    border-inline-start: 3px solid var(--colorBrandStroke1, var(--colorBrandForeground1));
    border-radius: var(--borderRadiusMedium);
    background: var(--colorNeutralBackground3);
}

.manager-only-tag {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0;
    color: var(--colorNeutralForeground2);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/* THE PAGE'S GUTTER IS ON A BOX WE OWN, NOT ON THE LAYOUT ITEM (#38).

   The 24px used to be here, on the scroller itself:

       .fluent-layout .fluent-layout-item.app-content { padding: 24px; }

   Half of it never applied. FluentLayout's JS puts a `mobile` attribute on the
   layout below MobileBreakdownWidth (768 by default — the same line this file's
   media query uses), and the shipped bundle then says

       .fluent-layout[mobile] .fluent-layout-item[area=content] {
           grid-area: content !important;
           padding-right: unset !important;
       }

   so below 768px every page in the app ran flush into the right edge of the
   screen while keeping its left gutter. Measured on /profile at 375px before
   this: computed padding 24px left, 0px right, with the card inside it touching
   the glass (#38; B5 in docs/mobile-ux-review.md and in #25).

   THE FIX IS NOT TO OUT-!important IT. This file has zero `!important`
   declarations and the library's bundle has about twelve hundred; the first one
   we add is the one that makes the second arguable. Winning that fight would
   also mean out-specifying a third-party selector that can be renamed in the
   next RC — and when it is, our rule does not fail loudly, it starts applying
   somewhere unintended.

   So the padding moved INSIDE, onto .app-page, an element the library has no
   selector for and no opinion about. The layout item keeps `overflow-y: auto`
   and stays the scroller; a scrollbar sits on its border edge either way, so
   nothing moved on a desktop. If you ever need the gutter to vary by width, put
   the media query on .app-page — not here. */
.fluent-layout .fluent-layout-item.app-content {
    padding: 0;
}

.app-page {
    padding: 24px;
}

/* ---------------------------------------------------------------------------
   Desktop: the navbar appears, the bottom bar goes away
   --------------------------------------------------------------------------- */
@media (min-width: 768px) {
    .navbar {
        display: flex;
    }

    .topbar .topbar-wide {
        display: flex;
    }

    .topbar .topbar-narrow {
        display: none;
    }

    /* One grid row that renders nothing. `display:none` on the item collapses
       the layout's third track, so the content area gets the height back. */
    .fluent-layout .fluent-layout-item.app-bottomnav {
        display: none;
    }

    /* THE ACCOUNT PANEL IS A MENU HERE, NOT THE PHONE'S BOTTOM SHEET. Anchored to
       its trigger rather than to the screen, and only as wide as it needs to be:
       a full-width drawer for four rows on a desktop is a modal in all but name.

       BOTH CLASSES IN THE SELECTOR ARE LOAD-BEARING, AND SO IS BEING IN HERE.
       The element is <div class="sheet account-panel">. Written as a bare
       `.account-panel` this ties with `.sheet` on specificity, and `.sheet` is
       ~270 lines further down, so source order gave the desktop menu the phone's
       bottom-sheet layout on EVERY viewport: `position: fixed; inset: 900px 0
       56px 0` at 1280x900, putting /profile 41px below the fold. The menu opened
       correctly and the thing you wanted was off the bottom of the screen, so
       the trigger read as a dead control — the whole of #38.

       Two classes beats one whatever the order, and the media query says out
       loud which viewport this is for. DO NOT "fix" a rule in this file by
       moving it below the one it loses to: that is the same fragility pointing
       the other way, and the next person to tidy the file re-breaks it. Nothing
       in this stylesheet should depend on source order.

       Every property `.sheet` sets and this panel does not want is restated
       here, not just the position: an unanchored `box-shadow: 0 -8px 16px` threw
       the menu's shadow upwards, and the bottom sheet's `border-top` is not the
       border a floating panel needs. */
    .sheet.account-panel {
        position: absolute;
        top: 100%;
        right: 0;
        bottom: auto;
        left: auto;
        min-width: 14rem;
        border: 1px solid var(--colorNeutralStroke2);
        border-radius: var(--borderRadiusMedium);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.16);
        color: var(--colorNeutralForeground1);
    }
}

/* ===========================================================================
   Rows of things: DataGrid, and the one hand-rolled table that has to match it
   ===========================================================================

   THE RULE: row height is set here, once, for the whole app. Never per page.

   Not out of tidiness — a per-page rule cannot work. In v5 the grid is
   `display: grid` and its rows are `display: contents`, so the things being
   laid out are the cells, and those `td`/`th` are generated inside
   FluentDataGrid. Blazor's CSS isolation only stamps its scope attribute on
   elements written in a page's own markup, so a rule in SomePage.razor.css
   never matches a grid cell. It does not warn; it just does nothing, and the
   next person tries a bigger number. */

:root {
    /* One row, everywhere: grid rows, and the roster's hand-rolled table. 44px
       is a 32px icon button with 6px of air above and below it. */
    --row-min-height: 44px;
}

/* Full width.

   v5's own `.fluent-data-grid { width: auto }` makes the table shrink to its
   content, which leaves a grid stopping short of the page with empty space to
   its right. KEEP THE `table.` PREFIX: it raises this selector to element+class
   specificity, matching the library's own `table.fluent-data-grid` rule so that
   load order decides — and app.css is linked after the library bundle, so this
   wins. A plain `.data-grid-fullwidth { width: 100% }` loses to it and the grid
   silently renders narrow. That cost Mercury real debugging; don't repeat it. */
table.data-grid-fullwidth {
    width: 100%;
}

/* Row height.

   v5 gives data cells `padding: 0 18px` — no vertical padding at all — so a row
   is exactly as tall as its tallest content. A row of text looks thin; a row
   with a button in it looks broken, which is the complaint that started this.

   The selector has to be `table.data-grid-fullwidth td:not(.col-select)` and not
   Mercury's shorter `.data-grid-fullwidth td`: in this version the library rule
   is `.fluent-data-grid td:not(.col-select)` (two classes, one element) and the
   short form loses on specificity, so the padding below never applies. This form
   beats it outright. `th` has no competing rule and takes the plain selector. */
table.data-grid-fullwidth td:not(.col-select),
table.data-grid-fullwidth th {
    min-height: var(--row-min-height);
    padding-top: 6px;
    padding-bottom: 6px;
    align-content: center;
    align-items: center;
}

/* Taller rows for grids whose cells stack two lines — a name over a username, a
   badge over its annotation. Apply alongside, never instead of:
   Class="data-grid-fullwidth data-grid-two-line". Its own modifier rather than a
   per-page height, so the two never drift apart. */
table.data-grid-two-line td:not(.col-select),
table.data-grid-two-line th {
    min-height: 64px;
    padding-top: 8px;
    padding-bottom: 8px;
}

/* Taller rows for a grid you press rather than read.

   The results grid's cells ARE the controls — both player seats are buttons and
   the report cell holds two more — and it is operated standing up, walking
   between boards, one-handed. At the 44px minimum those rows come out at the
   height of a row of text with buttons crammed into it, which is the complaint
   this answers.

   The extra height is deliberately AIR, not a bigger button: the seat keeps its
   own 44px minimum (BoardPlayer.razor.css), so what grows is the gap between one
   row's targets and the next one's — 12px to 20px — which is the part a thumb
   aiming at table 7 while table 6 is directly above it actually needs. A
   modifier alongside, never instead of: Class="data-grid-fullwidth
   data-grid-touch", so this height cannot drift from the app's own row rule. */
table.data-grid-touch td:not(.col-select),
table.data-grid-touch th {
    min-height: 64px;
    padding-top: 10px;
    padding-bottom: 10px;
}

/* Action columns.

   Add `data-grid-actions` when the last column holds row buttons. The library
   clips every data cell (`overflow: hidden`, for the ellipsis on a long string),
   which is right for text and wrong for a button — it trims the focus ring off
   whichever button sits at the cell's edge. The action column is an `auto`
   track, so nothing there needs clipping in the first place.

   A column's own `Class` cannot do this: v5 passes a column's `Style` down to
   its data cells but not its `Class`, so the class would only ever land on the
   header. Hence a grid-level modifier. */
table.data-grid-actions td:last-child {
    overflow: visible;
}

/* Wrap every grid in <div class="grid-scroll">.

   A grid with five columns does not fit a 390px phone, and a table that does
   not fit makes the PAGE scroll sideways — heading, filters, message bars and
   all. Scrolling the grid inside its own box keeps everything else where the
   manager put it. The roster's hand-rolled table does the same thing with its
   own rule; this is that rule, for grids. */
.grid-scroll {
    overflow-x: auto;
    width: 100%;
}

/* And a floor to scroll to. Without it the grid honours its own width:100%, fits itself into 390px
   and clips every cell instead — "Kulüp Şa", "Run…". Below this width it scrolls; above it, this
   does nothing. */
.grid-scroll table.data-grid-fullwidth {
    min-width: 44rem;
}

/* NOT ENABLED: ResizableColumns.

   v5 rewrites `grid-template-columns` to fixed pixels on the first drag, which
   ends the full width above; Mercury keeps a MutationObserver that puts the
   `1fr` filler track back after every drag. That JS is not ported here and no
   grid in this app asks to be resized. If you ever set ResizableColumns="true",
   port datagrid-fullwidth.js in the same change — and hide the last column's
   handle, because that column is content-sized and dragging it only fights the
   write-back. */

/* ---------------------------------------------------------------------------
   Filter rows
   ---------------------------------------------------------------------------
   A row of filters above a grid: labelled inputs, then a button that clears
   them. Put `filter-row` on the FluentStack holding them.

   Every labelled FluentUI control is wrapped in a FluentField that carries
   `margin: 12px 0` — the room its label needs. A bare button has no field and
   no margin, so bottom-aligning the row leaves the button sitting 12px below
   the inputs beside it. Measured on /manage/audit: inputs ended at 250px, the
   Clear button at 264px. Give the button the field's own margin back. */
.filter-row fluent-button {
    margin-bottom: 12px;
}

/* ---------------------------------------------------------------------------
   A message bar that asks a question
   ---------------------------------------------------------------------------
   The sentence, then the buttons that answer it, on one line. Put `bar-actions`
   on a plain <div> holding both.

   Not a FluentStack. FluentStack lays its children out at width:100% each and
   lets them shrink together, so inside a message bar the buttons are squeezed
   until their own labels wrap — "Yes, close / it" over two lines, which is what
   the close confirmation on /manage/tournaments did. Here the sentence takes the
   slack and each button stays the width of its words. (The same trap doubled
   every row's height on the roster page; see RowActions.) */
.bar-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

.bar-actions fluent-button {
    flex: none;
}

/* ---------------------------------------------------------------------------
   Cards
   ---------------------------------------------------------------------------
   Three widths, because there are three kinds of card here and pages kept
   inventing a fourth in an inline style. The `div.` prefix matches FluentCard's
   own `.fluent-card` padding rule on specificity and wins by load order. */

div.app-card {
    padding: 20px;
}

/* A sign-in form. Narrow on purpose: a wide box for two fields reads as a form
   with something missing. */
div.app-card-narrow {
    padding: 24px;
    max-width: 380px;
}

/* Anything being filled in or read: an editor, a list of people with a button
   each, a paragraph explaining a page. Wider than a sign-in, still short enough
   that a line of prose does not run the width of a monitor. */
div.app-card-wide {
    padding: 24px;
    max-width: 720px;
}

/* ---------------------------------------------------------------------------
   Which colour somebody has
   ---------------------------------------------------------------------------
   A hollow disc for White and a filled one for Black — the same two shapes as
   the pieces on the board, which is why it reads faster than the words do. The
   word is always beside it as well, for the screen reader and for anybody the
   discs do not work for; the disc itself is aria-hidden every time.

   Here rather than in a page's own stylesheet because three places show it now:
   /table/{n}, the home page's "you are at table 4" block, and every row of the
   home page's board list. It began as three rules inside TablePage.razor.css
   and was the first thing the second page needed to copy. `.disc` is ours
   alone — the FluentUI bundle has no rule of that name. */

.disc {
    flex: 0 0 auto;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 50%;
    border: 2px solid var(--colorNeutralForeground1);
}

.disc-white {
    background: var(--colorNeutralBackground1);
}

.disc-black {
    background: var(--colorNeutralForeground1);
}

/* ---------------------------------------------------------------------------
   A player's name, as the way to that player (#44)
   ---------------------------------------------------------------------------
   Wherever a name is printed — the standings, the home page's boards and
   ranking, the pairings, the results, the roster, the club's directory — it is
   a link to /players/{slug}. So the rule is here, once, rather than in six
   scoped stylesheets that would drift apart.

   IT DOES NOT LOOK LIKE A LINK UNTIL IT IS REACHED FOR. `color: inherit` and no
   underline: these names sit in table cells, sometimes dozens on one screen,
   and painting every one of them blue would turn a scoreboard into a page of
   links with a few numbers between them. The affordance arrives on hover and on
   keyboard focus, which is when somebody is asking for it.

   This began as `.person-link` in People.razor.css, which is where the only
   profile link in the app used to live. Same rule, one home, one name — a
   second near-identical class is what CLAUDE.md's shared-component rule is
   about, and it applies to CSS too. */
.player-link {
    color: inherit;
    text-decoration: none;
}

.player-link:hover,
.player-link:focus-visible {
    text-decoration: underline;
}

/* ---------------------------------------------------------------------------
   The claim list
   ---------------------------------------------------------------------------
   /claim shows the names a search matched, one per row: the name on the left,
   and on the right either the control that takes the account or a badge saying
   somebody already did.

   A <ul>, not a data grid. That page is statically rendered — it has to be, so
   that signing in can write a cookie — and FluentDataGrid needs a circuit. It is
   also read on a phone by somebody looking for exactly one name, where a list of
   rows is the whole requirement. */
.claim-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}

.claim-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--colorNeutralStroke2);
}

.claim-row:last-child {
    border-bottom: none;
}

/* A link, sized like the tap target it is: this is the one thing on the row
   somebody is aiming at, often one-handed, standing up. */
.claim-choose {
    flex: none;
    padding: 6px 12px;
    border-radius: var(--borderRadiusMedium);
    background: var(--colorBrandBackground);
    color: var(--colorNeutralForegroundOnBrand);
    text-decoration: none;
}

.claim-choose:hover {
    background: var(--colorBrandBackgroundHover);
}
