/* Tallium stylesheet.
 *
 * This used to be a <style> block inside sessions_page.html, with the detail
 * row and half the form carrying their rules inline. It is a file because a
 * media query cannot reach an inline style: nothing about the detail row could
 * be made to fit a phone until its styles had names. Every rule that applies
 * only on a narrow screen or a touch device lives at the bottom, under the two
 * media signals described there.
 */

/* The accent, in one place. A dark green: the selected chip, the primary
   button, the chosen result, the entry-format and definer-scope toggles,
   the open team tab, focus rings and the expanded row all read it. */
:root { --accent: #355e3b; --accent-hover: #2a4b2f; --accent-tint: #e8f0e9; --accent-ring: #b5cdb9; --accent-text: #2f5234; }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f5; color: #1a1a1a; }
.container { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; }
header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
h1 { font-size: 1.4rem; font-weight: 700; letter-spacing: -.02em; }
table { width: 100%; border-collapse: collapse; background: white; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,.08); overflow: hidden; }
thead { background: #f0f0f0; }
th { padding: .6rem 1rem; font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: #555; text-align: left; }
td { padding: .65rem 1rem; border-top: 1px solid #f0f0f0; font-size: .875rem; vertical-align: middle; }
tr[data-session-row]:hover td { background: #fafafa; }
.actions { display: flex; gap: .4rem; justify-content: flex-end; }
.btn { padding: .35rem .8rem; border-radius: 4px; border: 1px solid #d0d0d0; background: white; font-size: .8rem; cursor: pointer; font-family: inherit; color: #333; }
.btn:hover { background: #f5f5f5; }
.btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-danger { border-color: #f87171; color: #dc2626; }
.btn-danger:hover { background: #fef2f2; }
.btn-sm { font-size: .75rem; padding: .25rem .6rem; }
.empty { text-align: center; padding: 3rem; color: #aaa; font-size: .875rem; }
/* The signed-in player's outcome on a listed session: a small pill after
   the game name. Won reads the accent; the rest stay quiet. */
.outcome { display: inline-block; margin-left: .45rem; padding: .1rem .45rem; border-radius: 999px; font-size: .7rem; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; vertical-align: middle; background: #f0f0f0; color: #666; }
.outcome-win { background: var(--accent-tint); color: var(--accent-text); }
.outcome-loss { background: #fef2f2; color: #dc2626; }
.brand { color: inherit; text-decoration: none; }
.header-actions { display: flex; gap: .6rem; align-items: center; }
/* The user icon: initials on the accent, top right, linking to the profile. */
.avatar { width: 2.1rem; height: 2.1rem; border-radius: 50%; background: var(--accent); color: white; display: inline-flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 600; letter-spacing: .02em; text-decoration: none; flex: 0 0 auto; }
.avatar:hover { background: var(--accent-hover); }
.avatar:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
/* The login and profile pages: one card, narrow, centred. */
.card { background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.08); padding: 1.5rem; max-width: 26rem; margin: 1.5rem auto; }
.card h2 { font-size: 1.1rem; font-weight: 700; margin-bottom: 1rem; }
.card h3 { font-size: .9rem; font-weight: 600; margin: 1.5rem 0 .75rem; padding-top: 1.25rem; border-top: 1px solid #eee; }
.card .form-actions { margin-bottom: .25rem; }
.card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: .75rem; }
.card-head h2 { margin-bottom: 1rem; }
.card-close { text-decoration: none; font-size: 1.2rem; }
/* The profile card inside the modal: the sheet is the card, so no second
   box, and the sheet narrows to the card's width on a desktop. */
#modal-inner .card { box-shadow: none; padding: 0; margin: 0; max-width: none; }
#modal-inner:has(.profile-card) { width: min(30rem, 96vw); }
.profile-meta { display: grid; grid-template-columns: max-content 1fr; gap: .3rem .75rem; font-size: .875rem; margin-bottom: 1.25rem; }
.profile-meta dt { color: #888; font-weight: 500; }
.form-ok { margin-bottom: .6rem; padding: .5rem .7rem; border: 1px solid var(--accent-ring); border-radius: 4px; background: var(--accent-tint); color: var(--accent-text); font-size: .8rem; }
.signout { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid #eee; }
#page-error { margin-bottom: 1rem; }
/* The sessions list's filter: a radio group drawn as a segmented control,
   in the entry-format toggle's shape but its own class (see .team-tabs for
   why .mode-toggle is not reused). The radio is hidden but focusable; the
   label is what is seen, and the checked one reads the accent. */
.session-filter { display: inline-flex; margin-bottom: 1rem; border: 0; min-inline-size: 0; }
.session-filter input { position: absolute; opacity: 0; width: 1px; height: 1px; overflow: hidden; pointer-events: none; }
.session-filter label { padding: .35rem .8rem; border: 1px solid #d0d0d0; background: white; font-size: .8rem; cursor: pointer; margin-left: -1px; color: #444; white-space: nowrap; }
.session-filter label:first-of-type { border-radius: 4px 0 0 4px; margin-left: 0; }
.session-filter label:last-of-type { border-radius: 0 4px 4px 0; }
.session-filter label:hover { background: #f5f5f5; }
.session-filter input:checked + label { background: var(--accent); color: white; border-color: var(--accent); z-index: 1; position: relative; }
.session-filter input:focus-visible + label { outline: 2px solid var(--accent-ring); outline-offset: 2px; z-index: 2; position: relative; }

/* The session detail is a row inside the table, not a panel below it, so it
   opens where you clicked rather than at the bottom of the page. */
tr[data-session-row].expanded td { background: var(--accent-tint); }
tr[data-session-row].expanded td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
/* The card used to float on the page background with its own shadow and
   radius. Inside a row it already sits on a tinted band, so both were noise. */
.session-detail-row > td { padding: 0; background: #f7f8fa; border-top: none; }
.session-detail-card { padding: 1.25rem 1rem; }
.detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: .75rem; margin-bottom: 1rem; }
.detail-title { font-size: 1.1rem; font-weight: 700; }
.detail-by { font-size: .8rem; color: #888; margin-top: .15rem; }
.detail-actions { display: flex; gap: .5rem; align-items: center; }
.detail-close { background: none; border: none; cursor: pointer; font-size: 1.1rem; color: #aaa; line-height: 1; }
.detail-meta { display: grid; grid-template-columns: max-content 1fr; gap: .3rem .75rem; font-size: .875rem; margin-bottom: 1.25rem; }
.detail-meta dt { color: #888; font-weight: 500; }
.detail-table { width: 100%; border-collapse: collapse; font-size: .875rem; border-radius: 0; box-shadow: none; }
.detail-table thead tr { background: #f5f5f5; }
.detail-table th { padding: .4rem .75rem; text-align: left; font-weight: 600; font-size: .75rem; color: #666; text-transform: uppercase; letter-spacing: .05em; }
.detail-table td { padding: .4rem .75rem; border-top: 1px solid #f0f0f0; vertical-align: top; }
.detail-table .num { text-align: right; }
/* Participant-scoped stats sit under the player rather than in their own
   columns: which stats exist varies by game, so a column per stat would
   reshape the table for every game. */
.stat-inline { margin-top: .2rem; display: flex; flex-wrap: wrap; gap: .5rem; }
.stat-inline-item { font-size: .75rem; color: #666; }
.stat-inline-label { color: #999; }
.none { color: #bbb; }
.stat-raw { font-size: .7rem; color: #a33; }

#overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 50; align-items: center; justify-content: center; }
#overlay.open { display: flex; }

/* Wide enough for the player table, which grows a column per stat. */
#modal-inner { background: white; border-radius: 8px; padding: 1.75rem 2rem; width: min(1100px, 96vw); max-height: 92vh; overflow-y: auto; }
.form-title { font-size: 1.1rem; font-weight: 700; margin-bottom: 1.25rem; }

/* The footer is sticky inside the scrolling sheet, so Save stays in reach at
   the bottom of a long form. Sticky rather than fixed: iOS ignores the
   viewport's interactive-widget hint and lays the keyboard over the page,
   and a fixed bar would float mid-screen while a sticky one moves with the
   sheet's own scroll container. */
/* z-index: the chosen result and entry-format buttons are positioned with
   z-index 1 so their border overlaps their neighbours'; without a higher
   one here they scrolled up through the footer and drew over Cancel and
   Save. The typeahead list keeps 5 -- it is meant to be above everything
   in the sheet. */
.form-footer { position: sticky; bottom: 0; z-index: 3; background: white; margin-top: 1rem; padding: .6rem 0; }
/* htmx does not swap 4xx responses, so a rejected save needs somewhere for
   its message to land; app.js fills this from the response body. */
.form-error { margin-bottom: .6rem; padding: .5rem .7rem; border: 1px solid #f5c2c7; border-radius: 4px; background: #f8d7da; color: #842029; font-size: .8rem; }
.form-actions { display: flex; gap: .5rem; justify-content: flex-end; }
/* openModal() runs before the form has been fetched, so on a phone the sheet
   is up before there is anything in it. */
#modal-inner:empty::before { content: "Loading…"; color: #999; font-size: .875rem; }
body.modal-open { overflow: hidden; }

/* Session details on the left, the player table on the right. The left is
   capped rather than an equal fraction: it holds three short inputs, and
   every pixel beyond that is better spent on the table. */
.form-columns { display: grid; grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
@media (max-width: 860px) { .form-columns { grid-template-columns: 1fr; } }

/* Form primitives, so the inputs stop each carrying their own copy of this. */
.field { margin-bottom: .85rem; }
.field > label { display: block; font-size: .8rem; font-weight: 600; margin-bottom: .3rem; }
.input { width: 100%; padding: .4rem .6rem; border: 1px solid #d0d0d0; border-radius: 4px; font-size: .875rem; font-family: inherit; background: white; }
.input:focus { outline: 2px solid var(--accent-ring); outline-offset: -1px; border-color: var(--accent); }
textarea.input { resize: vertical; }
.input-sm { padding: .3rem .4rem; font-size: .8rem; border: 1px solid #d0d0d0; border-radius: 4px; font-family: inherit; background: white; }
.input-sm:focus { outline: 2px solid var(--accent-ring); outline-offset: -1px; border-color: var(--accent); }
.panel { border: 1px solid #e0e0e0; border-radius: 6px; padding: .85rem 1rem; }
.panel-title { font-size: .8rem; font-weight: 600; margin-bottom: .5rem; }
.muted { color: #999; font-size: .8rem; }
.hint { margin-bottom: .6rem; }
.btn-ghost { padding: .3rem .7rem; border: 1px dashed #ccc; border-radius: 4px; background: none; font-size: .8rem; cursor: pointer; color: #555; font-family: inherit; }
.btn-ghost:hover { background: #fafafa; }
.btn-ghost:disabled { opacity: .45; cursor: not-allowed; }
.btn-x { background: none; border: none; cursor: pointer; color: #bbb; font-size: 1rem; line-height: 1; padding: .2rem .3rem; font-family: inherit; }
.btn-x:hover { color: #dc2626; }

/* The typeahead's list. Fixed, not absolute: the player table scrolls
   sideways on a desktop and a scroller clips any absolutely positioned
   descendant, which drew a player row's list behind the table's bottom edge.
   The component sets top/left/width from the input's rectangle on show and
   on every scroll or resize while open. #overlay is the stacking context,
   where nothing else in the sheet sits above z-index 1. */
.typeahead-list { position: fixed; z-index: 5; margin-top: .2rem; background: white; border: 1px solid #d0d0d0; border-radius: 4px; box-shadow: 0 6px 20px rgba(0,0,0,.12); max-height: 14rem; overflow-y: auto; list-style: none; }
.typeahead-list li { padding: .45rem .7rem; font-size: .875rem; cursor: pointer; }
.typeahead-list li.active, .typeahead-list li:hover { background: var(--accent-tint); }

/* Whole-game stats: Pandemic's outbreaks, not Wingspan's per-player eggs. */
.game-stats { display: flex; flex-direction: column; gap: .55rem; margin-bottom: .75rem; }
.game-stat { display: flex; align-items: flex-start; justify-content: space-between; gap: .6rem; }
.game-stat-label { font-size: .75rem; color: #555; padding-top: .3rem; }
/* The stats to record this time: one chip per definition, and the chip is
   the control -- solid when the stat is on, plain when it is off. An
   unticked one is rendered nowhere else in the form, so the chip is the
   only place it still shows. A pending declaration is dashed. */
.stat-picks { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .6rem; }
.stat-pick { display: inline-flex; align-items: center; line-height: 1.2; gap: .35rem; padding: .25rem .65rem; border: 1px solid #c8c8c8; border-radius: 10px; background: white; color: #444; font-size: .75rem; font-family: inherit; cursor: pointer; user-select: none; }
.stat-pick-type { font-size: .65rem; color: #999; }
.stat-pick.on { background: var(--accent); border-color: var(--accent); color: white; }
.stat-pick.on .stat-pick-type { color: rgba(255,255,255,.65); }
.stat-pick.stat-pending { border-style: dashed; }
.stat-pick:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; }
/* Until a game is chosen the stats box is one line; there is nothing to
   record yet and nothing to define a stat for. */
.stats-collapsed { font-size: .8rem; color: #999; padding: .6rem .1rem; margin-bottom: .85rem; border-top: 1px dashed #e0e0e0; border-bottom: 1px dashed #e0e0e0; }
.table-actions { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: space-between; align-items: center; margin-top: .6rem; }
.remove-team { color: #b91c1c; border-color: #f5c2c7; }
.remove-team:hover { background: #fef2f2; }
.limit-note { margin-left: .5rem; }

/* One row per player, one column per custom stat. This replaced a split
   where the name lived in a fieldset on the left and was mirrored into a
   separate grid on the right by hand-written JS -- two rows to keep in
   step for every player, and nothing gained by it. */
.player-table-wrap { overflow-x: auto; border: 1px solid #e0e0e0; border-radius: 6px; }
.player-table { width: 100%; border-collapse: collapse; font-size: .8rem; background: white; border-radius: 0; box-shadow: none; }
.player-table th { padding: .45rem .5rem; font-size: .68rem; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; text-align: left; background: #f7f7f7; border-bottom: 1px solid #e8e8e8; }
.player-table td { padding: .4rem .5rem; border-top: 1px solid #f2f2f2; vertical-align: top; font-size: .8rem; }
.player-table tr:hover td { background: transparent; }
/* Every other block shaded: a tbody is one player in a free-for-all and one
   side otherwise, and either is the unit worth telling apart. On the tbody
   rather than the td, because the hover rule above wipes a td background and
   a tbody's shows through a transparent cell regardless. */
.player-table tbody:nth-of-type(even) { background: #f7f8fa; }
.player-table th.stat-col { border-left: 1px solid #ececec; }
.player-table td.stat-col { border-left: 1px solid #f5f5f5; }
.player-table th.col-remove { width: 1.8rem; }
/* The pick-list, with the new-name box stacked beneath it when it is open. */
.col-name { display: grid; gap: .3rem; }
.col-name select, .col-name input { min-width: 8.5rem; width: 100%; }
/* A number box holds a few digits; the width it had was for a label it does
   not carry. Text keeps room to read back what was typed. */
.col-score input { width: 4.5rem; }
.stat-col input[type="number"] { width: 4.5rem; }
.stat-col input[type="text"] { width: 8rem; }
.stat-check { width: 1rem; height: 1rem; vertical-align: middle; }
.stat-pending { color: var(--accent-text); }

/* Entry format. Two of the three modes need no grouping control at all,
   which is what keeps free-for-all entry as cheap as it was before teams. */
.mode-toggle { display: inline-flex; margin-bottom: .6rem; }
.mode-toggle button { padding: .3rem .7rem; border: 1px solid #d0d0d0; background: white; font-size: .75rem; cursor: pointer; font-family: inherit; margin-left: -1px; color: #444; }
.mode-toggle button:first-of-type { border-radius: 4px 0 0 4px; margin-left: 0; }
.mode-toggle button:last-of-type { border-radius: 0 4px 4px 0; }
.mode-toggle button.active { background: var(--accent); color: white; border-color: var(--accent); z-index: 1; position: relative; }

/* Teams: a tab per team. Its own class rather than .mode-toggle, which the
   definer's "Applies to" control already reuses, and because a
   variable-count row must scroll sideways rather than squeeze -- the
   coarse-pointer rules stretch .mode-toggle's buttons to fill the row. */
.team-tabs { display: flex; align-items: flex-end; gap: .25rem; margin-bottom: .6rem; padding: .15rem 0; overflow-x: auto; border-bottom: 1px solid #d0d0d0; }
.team-tabs button { flex: 0 0 auto; padding: .35rem .8rem; border: 1px solid #d0d0d0; border-bottom: none; border-radius: 6px 6px 0 0; background: #f7f7f7; font-size: .8rem; cursor: pointer; font-family: inherit; color: #444; white-space: nowrap; }
.team-tabs button.active { background: white; color: #1a1a1a; font-weight: 600; border-color: var(--accent); border-bottom: 1px solid white; margin-bottom: -1px; }
.team-tabs .team-tabs-add { border: 1px dashed #ccc; border-bottom: none; background: none; color: #555; }
.team-tabs .team-tabs-add:disabled { opacity: .45; cursor: not-allowed; }
.tab-result { color: var(--accent); font-weight: 600; }
.tab-result::before { content: " · "; color: #999; font-weight: 400; }

/* The side's panel -- everyone in co-op, the open tab in Teams: its result,
   its score and its team-scoped stats, entered once and written to every
   member. Free-for-all has these per row instead. */
.team-panel { margin-bottom: .6rem; padding: .5rem .7rem; border: 1px solid #e0e0e0; border-radius: 6px; background: #fafafa; }
.team-panel-head { display: flex; align-items: center; gap: .6rem; }
.team-panel-label { font-size: .75rem; font-weight: 600; color: #555; }
.team-panel-fields { display: flex; flex-wrap: wrap; gap: .6rem 1rem; margin-top: .55rem; }
.team-field { display: flex; flex-direction: column; gap: .2rem; font-size: .68rem; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .04em; }
.team-field .input-sm { width: 6rem; }
.team-field input[type="text"] { width: 9rem; }
.player-table td.col-result { vertical-align: middle; }

.result-toggle { display: flex; }
.result-toggle button { padding: .25rem .5rem; border: 1px solid #d0d0d0; background: white; font-size: .7rem; cursor: pointer; font-family: inherit; margin-left: -1px; position: relative; }
.result-toggle button:first-of-type { border-radius: 4px 0 0 4px; margin-left: 0; }
.result-toggle button:last-of-type { border-radius: 0 4px 4px 0; }
.result-toggle button.active { background: var(--accent); color: white; border-color: var(--accent); z-index: 1; }

.stat-list { display: flex; flex-direction: column; gap: .2rem; }
.stat-list-row { display: flex; gap: .2rem; align-items: center; }
.stat-list-row .btn-x { font-size: .8rem; }
.stat-list-add { align-self: flex-start; padding: .05rem .35rem; font-size: .65rem; }

/* Defining a stat happens in a native <dialog>, opened with showModal() so
   it sits in the top layer above the form's overlay whatever the z-index.
   A centred card here; the narrow-screen rules turn it into a bottom sheet. */
.definer { margin: auto; border: 1px solid #e0e0e0; border-radius: 8px; padding: 1rem 1.1rem; width: min(22rem, calc(100vw - 2rem)); box-shadow: 0 10px 30px rgba(0,0,0,.2); }
/* margin: auto is what centres a modal dialog, and the reset at the top of
   this file zeroes every margin -- without restating it here the dialog
   opens pinned to the top-left corner. */
.definer::backdrop { background: rgba(0,0,0,.35); }
.definer-title { font-size: 1rem; font-weight: 700; margin-bottom: .5rem; }
.definer-label { display: block; font-size: .8rem; font-weight: 600; margin: .7rem 0 .3rem; }
.definer-scope { display: flex; margin-bottom: 0; }
.definer-scope button { flex: 1; }
.definer-error { font-size: .75rem; color: #b91c1c; margin-top: .5rem; }
.definer-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: 1rem; }
.chips { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: .6rem; }
.chip { font-size: .7rem; background: var(--accent-tint); color: var(--accent-text); border: 1px solid var(--accent-ring); border-radius: 10px; padding: .1rem .2rem .1rem .5rem; display: inline-flex; align-items: center; gap: .2rem; }
.chip .btn-x { font-size: .8rem; color: var(--accent-text); }

/* Alpine hides x-cloak elements until it has initialised them. This rule has
   to load with the page, or the form flashes its raw templates. */
[x-cloak] { display: none !important; }

/* ---------------------------------------------------------------------------
   Narrow screens and touch.

   Two signals, kept apart on purpose. (max-width: 640px) decides layout: the
   full-screen sheet, the stacked columns, the player cards, the bottom-sheet
   dialog. (pointer: coarse) decides ergonomics: 16px inputs, because iOS
   zooms on focus into anything smaller and never zooms back, and targets a
   thumb can hit. A phone held sideways is 844px wide and still a thumb; a
   touchscreen laptop is coarse at 1400px and wants the desktop layout.
   Merging the two breaks one or the other.
   ------------------------------------------------------------------------ */

@media (pointer: coarse) {
  .input, .input-sm, select, textarea { font-size: 1rem; }
  .input { min-height: 2.75rem; }
  .input-sm { min-height: 2.5rem; padding: .4rem .5rem; }
  .btn, .btn-ghost { min-height: 2.75rem; padding: .5rem .9rem; font-size: .9rem; }
  .btn-x { min-width: 2.75rem; min-height: 2.75rem; font-size: 1.3rem; }
  .mode-toggle { display: flex; width: 100%; }
  .result-toggle { width: 100%; }
  .result-toggle button, .mode-toggle button { flex: 1; min-height: 2.75rem; padding: .4rem .5rem; font-size: .85rem; }
  .team-tabs button { min-height: 2.75rem; font-size: .9rem; }
  .session-filter { display: flex; width: 100%; }
  .session-filter label { flex: 1; min-height: 2.75rem; display: flex; align-items: center; justify-content: center; padding: .4rem .5rem; font-size: .85rem; }
  .stat-list-add { min-height: 2.25rem; font-size: .8rem; padding: .3rem .6rem; }
  .stat-check { width: 1.5rem; height: 1.5rem; }
  /* Thinner than the other targets on purpose: a row of these wraps, and
     baseline alignment inside a tall chip left a blank band under the text. */
  .stat-pick { min-height: 2.2rem; padding: .3rem .8rem; font-size: .85rem; }
  .stat-pick-type { font-size: .75rem; }
  .detail-close { min-width: 2.75rem; min-height: 2.75rem; }
  .avatar { width: 2.75rem; height: 2.75rem; font-size: .9rem; }
  .typeahead-list li { min-height: 2.75rem; display: flex; align-items: center; font-size: 1rem; }
}

@media (max-width: 640px) {
  .container { padding: 1rem .75rem; }
  header { margin-bottom: 1rem; }
  th, td { padding: .5rem .6rem; }
  /* The sessions list drops its Players column; the count is one tap away
     in the detail, and the row's width is better spent on the game. The
     column count stays four, so the detail row's colspan still holds. */
  .container > table > thead th:nth-child(3),
  #sessions-list > tr[data-session-row] > td:nth-child(3) { display: none; }
  .actions { flex-direction: column; align-items: stretch; }

  .session-detail-card { padding: 1rem .75rem; }
  .detail-head { flex-wrap: wrap; }
  .detail-meta { grid-template-columns: 1fr; gap: .1rem; }
  .detail-meta dd { margin-bottom: .45rem; }

  /* The form is a full-screen sheet. 100vh first as the fallback for
     browsers without dvh; dvh is what excludes the browser's own bars. */
  #overlay { align-items: stretch; justify-content: stretch; }
  #modal-inner { width: 100%; height: 100vh; height: 100dvh; max-height: none; border-radius: 0; padding: 1rem .9rem 0; overscroll-behavior: contain; }
  .form-title { margin-bottom: .9rem; }
  .form-columns { gap: 1rem; }
  .form-footer { padding: .6rem 0 calc(.6rem + env(safe-area-inset-bottom)); border-top: 1px solid #eee; }
  .form-actions .btn { flex: 1; }
  .team-panel-head { flex-wrap: wrap; }
  .team-panel-fields .team-field { flex: 1 1 100%; }
  .team-field .input-sm, .team-field input[type="text"] { width: 100%; }

  /* The player table becomes a shaded card per player without its markup
     changing. Each tr is a grid that adds a track per ~4.5rem of width: the
     name spans all but the last track and the remove button takes that one,
     then the score and every number or yes/no stat (the kind-* class the
     cell carries) take one track each and wrap as stats are defined, while
     a text or list stat and the result span the whole row. Cells are
     labelled from data-label because the header row is hidden. dense, so a
     number defined after a text stat backfills beside the earlier numbers
     rather than starting a row of its own. In free-for-all the result is
     the last cell, so a card ends with it; in Teams and co-op the result
     sits above the table with the tab.

     No display:contents: with it, two players' cells interleave through one
     auto-placement cursor, and the tds collapse into an anonymous table.
     tr:first-of-type rather than :first-child, because Alpine's x-for
     template is the tbody's actual first child. The card's shade is here,
     on the tr; the desktop stripe on the tbody is turned off, since the
     cards carry their own separation. */
  .player-table-wrap { overflow: visible; border: 0; border-radius: 0; }
  .player-table { display: block; }
  .player-table thead { display: none; }
  .player-table tbody { display: block; padding: 0; border: 0; background: none; }
  .player-table tr { display: grid; grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr)); grid-auto-flow: row dense; gap: .45rem .6rem; align-items: end; margin-bottom: .55rem; padding: .6rem .7rem; background: #f4f5f7; border-radius: 8px; }
  .player-table td { display: block; padding: 0; border: 0; grid-column: 1 / -1; }
  .player-table td.col-name { grid-row: 1; grid-column: 1 / -2; }
  .player-table td.col-remove { grid-row: 1; grid-column: -2 / -1; justify-self: end; }
  .player-table td.col-score, .player-table td.kind-number, .player-table td.kind-bool { grid-column: auto; }
  .player-table td[data-label]::before { content: attr(data-label); display: block; font-size: .7rem; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .04em; margin-bottom: .2rem; }
  .col-score input, .stat-col input[type="number"], .stat-col input[type="text"], .col-name select, .col-name input { width: 100%; min-width: 0; }

  /* The definer is a bottom sheet. */
  .definer { width: 100%; max-width: none; margin: auto 0 0; border-radius: 12px 12px 0 0; padding-bottom: calc(1rem + env(safe-area-inset-bottom)); }
}
