/* FONT SWITCH (one line). The next line, @import url('fonts.css');, loads the app's three
   typefaces (Inter, Baloo 2, IBM Plex Mono) from its own files in public/fonts/ (switched
   2026-09-24). public/css/fonts.css holds their @font-face rules, tools/fonts.md lists every file
   with where it came from and its checksum, tools/build-standalone.js puts them into the standalone
   page, and tests/nothing-borrowed.js checks the files and that nothing is fetched from the
   internet. Going back to Google's font service is this one line in reverse: tools/fonts.md has
   the line to put here instead. */
@import url('fonts.css');

:root{
  --ink:#131311;
  --ink-2:#1A1A16;
  --panel:#1E1E1A;
  --panel-2:#272723;
  --wood:#1C170B;
  --wood-2:#241D0E;
  --fairway:#C99A6D;
  --fairway-light:#DBB98A;
  --tracer:#E8C79A;
  --brass:#B7935F;
  --rough:#5C4326;
  --chalk:#F5F2EA;
  --chalk-dim:#CBC7B9;
  --chalk-faint:#9D9989;
  --hairline:#333029;
  --hairline-light:#454136;
  --danger:#D97563;
  --radius:12px;
  --shadow: 0 10px 28px rgba(0,0,0,0.35);
  --chevron-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cline x1='6' y1='5' x2='14' y2='5' stroke='%23000' stroke-width='1.7' stroke-linecap='round'/%3E%3Cpath d='M7 5L10 15L13 5' fill='none' stroke='%23000' stroke-width='1.7' stroke-linejoin='round' stroke-linecap='round'/%3E%3C/svg%3E");
  --logo-mask: url("/assets/logo-mask.png");
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;}

/* A tiny recolorable mark cut from the real logo — shows up small, in whatever color fits
   the spot it's in, so the brand carries through the app without ever feeling stamped on. */
/* SMALL LOGOS HAVE NO LETTERING (logos-popup; the owner, 2026-10-03: "any mulligans logo that is reduced to
   very small take out the indoor virtual golf part of the logo, im talking like the small ones on the tabs and
   the copy right and any other that are as small as them"). A logo drawn 48 CSS px wide or less, on the screen
   it is on, is the mark without its "Indoor Virtual Golf" arc: --logo-mark (public/assets/logo-mark-small.png,
   150x110, made by tools/logo-mark.js; three device pixels to each of its 48 at most). That is the chosen tab's
   (16px), the © line's (12px), a popup title's (25px; the league form's 44px), an empty state's (34px), the TV
   ticker's separators (1.55em of its words: 21px on a phone, 27px in the Bay Board tab's copy, 31px at 1280x720,
   46px at 1920x1080), the one-line masthead's below 760px (46px, 40px at 560px and under; the 760px block) and,
   as before, the league cards' (44px), the round's hole marks (24px) and the pull's (26px). Bigger ones keep the
   full logo, --logo-mask: the desk masthead's (140x104), the loading logo (70px) and the one that flies from it,
   the sign-in's (90px), League Central's (150px), the page's one faint mark (100vmin) and the TV's (118vmin). */
.logo-badge{
  display:inline-block; flex:none;
  -webkit-mask-image:var(--logo-mask); mask-image:var(--logo-mask);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
.chevron-icon{
  display:inline-block; flex:none; width:20px; height:20px; background-color:var(--chalk-faint);
  -webkit-mask-image:var(--chevron-mask); mask-image:var(--chevron-mask);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
  transform:rotate(180deg);
  transition:transform .15s ease;
}
/* The tee (owner, 2026-09-24: "as if it's releasing the menu as the tee drops down"): at rest it
   stands on its head, point UP; open, it points DOWN at the menu below it (the mark as drawn). The
   opening turn is a drop — teeDrop, once, on the popup's own length and curve, sweeping the same
   arc the tee always has and swinging a little past point-down at 65 %, where the popup overshoots
   too (30-render-core.js: turnTee starts it on the popup's frame; tee-hold keeps the rest pose
   until then). A tee born or rendered open simply points down. Closing is the quick .15s turn back. */
.chevron-icon.is-open{transform:rotate(0deg);}
.chevron-icon.is-open.tee-hold{transform:rotate(180deg); transition:none;}
.chevron-icon.is-dropping{animation:teeDrop var(--popup-in) cubic-bezier(.3,.7,.3,1) both; transition:none;}
@keyframes teeDrop{
  0%{transform:rotate(180deg);}
  65%{transform:rotate(-14deg);}
  100%{transform:rotate(0deg);}
}
.modal h3::before{
  content:""; display:inline-block; width:25px; height:25px; margin-right:9px; vertical-align:-6px;
  background-color:var(--fairway-light);
  -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
.modal-league-form h3::before{width:44px; height:44px; margin-right:13px; vertical-align:-13px;}
.empty::before{
  content:""; display:block; width:34px; height:34px; margin:0 auto 12px;
  background-color:var(--hairline-light);
  -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
.tab-btn.active::before{
  content:""; display:inline-block; width:16px; height:16px; margin-right:7px; vertical-align:-3px;
  background-color:#20180F; opacity:.55;
  -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}

body{
  background:var(--ink);
  color:var(--chalk);
  font-family:'Inter',sans-serif;
  font-size:15px;
  line-height:1.5;
  min-height:100vh;
  -webkit-font-smoothing:antialiased;
}
.app{max-width:1180px;margin:0 auto;padding:0 20px 80px;}

/* ---------- The page's watermark (owner, 2026-09-25: "there is way to many watermarks on the
   interface just have one big one centered in the background on each page") ----------
   ONE big, faint Mulligan's mark behind every screen, staff and player (renderPass puts it first in
   #app on every real screen: PAGE_MARK, 30-render-core.js). Fixed in the middle of the screen, so it
   never scrolls, as wide as the screen's shorter side (the size the player side's own mark always had)
   and under everything: z-index -1 paints it straight onto the page's --ink, so every card, row and
   word is on top of it, and it shows all round the floating tab bar. (That needs <html> to carry no background:
   body's --ink is then the canvas, under the mark.) Its colour is --brass at 5 % over --ink, mixed in
   advance — on --ink it is the same pixel the old opacity .05 gave — so a view transition can lift it
   over the morph and blend it in (the Motion policy block): it never moves, scales or flashes with a
   drill-in or out. No mark on the loading, load-error and reload screens, the sign-in, popups or paper;
   the TV covers it with its own. Hidden from screen readers (aria-hidden), and it never takes a tap.
   A PLAIN PICTURE, NO MASK (layers, 2026-10-03): it was the logo's shape cut out of a block of that colour
   (a CSS mask), and WebKit draws a masked layer with an extra pass on every frame the screen changes, which
   is every frame of a scroll on the owner's iPhone. Now it is the same logo made once into a picture with
   that colour baked in (public/assets/page-mark.png, by tools/page-mark.js from logo-mask.png and :root's
   --brass and --ink: the logo's own 400x297 pixels and opacity, so it is drawn at the same size and
   softness), one fixed layer that is painted once and never again. */
.page-mark{position:fixed; inset:0; margin:auto; z-index:-1; width:100vmin; height:calc(100vmin * 297 / 400);
  background:url("/assets/page-mark.png") center / contain no-repeat; pointer-events:none;}

/* ---------- Header ---------- */
.masthead{
  padding:26px 0 20px;
  display:flex; align-items:flex-end; justify-content:space-between; gap:16px;
  border-bottom:1px solid var(--hairline);
  flex-wrap:wrap;
}
.brand-block{display:flex; align-items:center; gap:18px;}
/* The League Ops block in gold (owner, 2026-09-24: "gold title + gold logo"): the logo in the tan
   gold --fairway, the divider --brass, "League Ops" the light gold --tracer (the Dashboard's stat
   numbers) and the tagline --fairway like the logo. The same colours in the one-line bar. */
/* The divider between the logo and the title is white (--chalk) (owner, 2026-09-24). */
.logo-lockup{display:flex; align-items:center; flex:none; padding-right:22px; border-right:1px solid var(--chalk);}
/* The logo is the mark every other logo in the app is — --logo-mask filled with a colour — instead
   of the white PNG it was, in the same box: 104px tall at the logo's own proportions, so it has its
   width before the mask has decoded (the first-open morph measures it). */
.logo-img{height:104px; width:auto; display:block; aspect-ratio:400/297; background-color:var(--tracer);
  -webkit-mask-image:var(--logo-mask); mask-image:var(--logo-mask);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
/* The title (owner, 2026-09-24: "make the 'League Ops' bigger"): sized to the 104px logo beside it
   so it reads as the page's name, not a caption. The eyebrow line that sat above it is gone.
   White (--chalk) against the gold logo and tagline (owner, 2026-09-24: "make league ops white"). */
.brand-text h1{
  font-family:'Baloo 2',sans-serif; font-weight:700; font-size:40px; line-height:1.1; letter-spacing:.005em;
  margin:0; color:var(--chalk);
}
/* The tagline takes the light gold the old "Berlin, CT · League Desk" line used, --tracer (owner, 2026-09-24). */
.brand-text p{margin:3px 0 0; color:var(--tracer); font-size:13.5px;}
.masthead-meta{text-align:right; font-family:'Inter',sans-serif; font-size:13px; color:var(--chalk-faint); line-height:1.7;}
.player-lookup{position:relative; margin-top:8px; display:inline-block; text-align:left;}
#player-lookup-search{
  width:180px; padding:7px 10px 7px 30px !important; font-size:12.5px; border-radius:7px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='8.5' cy='8.5' r='5.5' fill='none' stroke='%23B7A98A' stroke-width='1.6'/%3E%3Cline x1='12.7' y1='12.7' x2='17' y2='17' stroke='%23B7A98A' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:8px center; background-size:14px;
}
.player-lookup-results{
  position:absolute; top:calc(100% + 4px); right:0; width:260px; background:var(--panel);
  border:1px solid var(--fairway-light); border-radius:12px; box-shadow:var(--shadow); z-index:400;
  max-height:320px; overflow-y:auto; padding:4px;
}
.player-lookup-row{padding:9px 10px; border-radius:7px; cursor:pointer; display:flex; align-items:center; justify-content:space-between; gap:8px;}
@media (hover:hover) and (pointer:fine){ .player-lookup-row:hover{background:var(--panel-2);} }
.player-lookup-name{font-family:'Inter',sans-serif; font-weight:600; font-size:13.5px; color:var(--chalk);}
.player-lookup-meta{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint); white-space:nowrap;}
.player-lookup-empty{padding:10px; font-size:12.5px; color:var(--chalk-faint); text-align:center;}
/* Only the COUNTS are brass. This used to be every span in the meta block, which also turned the
   player finder's result names (spans inside it) brass (owner, 2026-09-24: names in white). */
.masthead-meta .mh-facts span{color:var(--brass); font-weight:600;}
/* wrappers only there so a phone can hide the counts and the button's word; on a desk they are invisible */
.masthead-meta .mh-facts, .masthead-meta .mh-label{color:inherit; font-weight:inherit;}
.mh-access{margin-top:8px;}
.mh-access .btn{font-size:12px;}
/* The Settings gear (owner, 2026-09-25) sits just before Player Access, 6px apart like the bar's own
   pieces: the same button, the same press, the same height in each masthead size, icon only and square
   (its name is its aria-label and title). While the Settings page shows it is lit like a chosen tab
   (aria-pressed, syncSettingsGear), and no tab is. */
.mh-access{display:flex; justify-content:flex-end; gap:6px;}
.mh-gear{width:30px; padding-left:0; padding-right:0; justify-content:center;}
.mh-gear svg{margin-right:0 !important;}
.mh-access .btn.mh-gear[aria-pressed="true"]{background:var(--fairway); border-color:var(--fairway);}
.mh-access .btn.mh-gear[aria-pressed="true"] svg{color:#20180F;}
/* The desk masthead's right side, sized to sit with the 40px title and the 104px logo (owner,
   2026-09-24: bigger). Desk only: the one-line bar at 760px and below keeps its own sizes. Still two
   rows, bottom-aligned with the logo — the counts and the player finder, then Player Access —
   40 + 6 + 40px, so the block runs from the title's top to the logo's foot and the masthead stays
   as tall as its logo. The finder is as wide as its results list (260px), which opens flush under
   it. Where the two rows no longer fit beside the brand (below ~927px) the right side does not drop
   under the logo as a whole any more — it stacks, counts over finder over Player Access, 18 + 40 +
   6 + 40 = 104px: still the logo's height, so the masthead is 151px at every desk width. */
@media (min-width:761px){
  .masthead{flex-wrap:nowrap;}
  .brand-block{flex:none;}
  .masthead-meta{font-size:15px; line-height:1.2;}
  .player-lookup{margin-top:0; margin-left:12px;}
  #player-lookup-search{width:260px; height:40px; font-size:15px; padding:9px 12px 9px 38px !important; border-radius:8px; background-position:12px center; background-size:17px;}
  .mh-access{margin-top:6px; display:flex; justify-content:flex-end;}
  .mh-access .btn{font-size:14px; height:40px; padding:0 15px;}
  .mh-access .btn svg{width:16px; height:16px;}
  .mh-access .mh-gear{width:40px; padding:0;}
}

/* ---------- Tabs ---------- */
/* The staff tab row stays pinned at the top while the page scrolls, and the rounded bar is all there
   is of it (owner, 2026-09-25: "that bar needs to just be floating attached to nothing else"). The
   wrapper keeps its sticky box, so the bar sits in the same place at the same size and the content
   still lands just under it (contentTopOffset), but it paints nothing and takes no taps: no band,
   tint or blur across the screen, at rest, pinned or during a morph (the Motion policy block), so the
   page (its watermark, cards and words) shows all round the bar right up to its edge, and a tap or a
   click beside the bar reaches what is under it. Only the bar takes them. Its own fill is opaque
   (--panel), so whatever scrolls under it never shows through its words. */
#sec-tabs{position:sticky; top:0; z-index:40; padding:14px 0 12px; margin:8px 0 14px; pointer-events:none;}
/* The player side draws no staff tab row, but the empty wrapper kept its padding and margins: a
   48px blank strip at the top of every player screen that stuck while scrolling and caught taps. */
#sec-tabs:empty{display:none;}
.tabs{
  position:relative; display:flex; gap:2px; margin:0; background:var(--panel);
  border:1px solid var(--hairline); border-radius:999px; padding:4px; width:fit-content;
  max-width:100%; overflow-x:auto; pointer-events:auto;
}
/* One highlight per tab row that glides to the active tab (see syncTabIndicator). It moves
   like liquid: quick out of the gate, a stretch while in flight, a soft overshoot to rest.
   The tab CONTENT never slides or fades — the new screen is simply there (Apple Music).
   The glide itself runs on the compositor (desk-tabs-smooth, 2026-09-27: 30-render-core.js, tabGlide; the phone's
   bar too since phone-gold-ends), so the gold has no CSS transition: its left edge on cubic-bezier(.3,1.3,.45,1),
   its width on cubic-bezier(.3,1.15,.4,1), its height on ease over .15s and glideStretch on top, all over --glide,
   worked out a frame apart. While it glides the gold's own box is already on the new tab, unpainted (.is-pieces),
   and three pieces inside it draw it where it is on its way: a round end each side, only ever scaled by the stretch,
   and a middle stretched across between them, so its ends stay exactly as round as CSS draws them. A finger sliding
   a row places it directly (.no-glide), with glideStretch (or glideHop) at each tab it crosses. */
.tab-glide{position:absolute; left:0; top:0; z-index:0; border-radius:999px; background:var(--fairway);
  box-shadow:0 2px 10px rgba(201,154,109,.3); pointer-events:none; will-change:translate;}
.tab-glide.is-pieces{background:transparent; box-shadow:none;}
.tab-glide-piece{position:absolute; left:0; top:0; transform-origin:0 0; background:var(--fairway);
  box-shadow:0 2px 10px rgba(201,154,109,.3); pointer-events:none; will-change:transform;}
.tab-glide-end-l{border-radius:999px 0 0 999px;}
.tab-glide-end-r{border-radius:0 999px 999px 0;}
.tab-glide.is-gliding{animation:glideStretch var(--glide) cubic-bezier(.3,.7,.3,1);}
@keyframes glideStretch{0%{scale:1 1;} 40%{scale:1.16 .92;} 100%{scale:1 1;}}
/* A move to the other line of a tab row that wraps (tabs-and-morph, feel 2) hops across: the same spring
   carries it over while it dips in size on the way, instead of sliding diagonally through the tabs. */
.tab-glide.is-hopping{animation:glideHop var(--glide) cubic-bezier(.3,.7,.3,1);}
@keyframes glideHop{0%{scale:1 1;} 45%{scale:.62 .62;} 100%{scale:1 1;}}
/* The same stretch and hop under a second name each (speed, 2026-10-06): a finger crossing another tab while one runs
   starts it again by switching to the other name (tabGlideStretchAgain, 30-render-core.js), so the new animation is
   worked out with the frame's own style instead of a style read inside the finger's move. Identical keyframes, curve
   and time: nothing looks different. */
.tab-glide.is-gliding-b{animation:glideStretchB var(--glide) cubic-bezier(.3,.7,.3,1);}
@keyframes glideStretchB{0%{scale:1 1;} 40%{scale:1.16 .92;} 100%{scale:1 1;}}
.tab-glide.is-hopping-b{animation:glideHopB var(--glide) cubic-bezier(.3,.7,.3,1);}
@keyframes glideHopB{0%{scale:1 1;} 45%{scale:.62 .62;} 100%{scale:1 1;}}
/* ---------- Skins: Gross or Net (owner, 2026-09-25, look 8: "just do a toggle between the two") ----------
   The Skins tab shows one board at a time, its hole-by-hole table with it, and a small two-way toggle above
   it picks which (renderLeagueSkins, 35-player.js): the player side, the staff league page and Past seasons
   alike. Both boards are in the page; .skins-view's data-side hides the other one, and the toggle stands in
   for the board's title. Each half is a real button (aria-pressed) with the app's one press, tint and focus
   ring, a full 44px to tap on a phone (the 560px block). The gold is an element of its own, inset 3px in the
   chosen half, and glides to the other half on the tab bars' spring with their stretch in flight
   (setSkinsSide switches the side on the page first, so the render that follows rebuilds nothing); the
   chosen word turns dark the moment it is chosen and the word left turns light with it, both over .15s, as the tab
   bars' words do (phone-gold-ends, 2026-09-27; it waited 40 % of --glide for the gold before). A toggle drawn
   fresh (another week) is simply right. Paper prints both boards under their titles, as it always did. */
.skins-toggle{position:relative; display:inline-grid; grid-template-columns:1fr 1fr; margin:0 0 14px; padding:0;
  background:var(--panel); border:1px solid var(--hairline); border-radius:999px; -webkit-user-select:none; user-select:none;}
.skins-toggle-glide{position:absolute; z-index:0; top:3px; bottom:3px; left:3px; width:calc(50% - 6px); border-radius:999px;
  background:var(--fairway); box-shadow:0 2px 10px rgba(201,154,109,.3); pointer-events:none;
  transition:translate var(--glide) cubic-bezier(.3,1.3,.45,1);}
.skins-view[data-side="net"] .skins-toggle-glide{translate:calc(100% + 6px) 0;}
.skins-toggle-glide.is-gliding{animation:glideStretch var(--glide) cubic-bezier(.3,.7,.3,1);}
.skins-toggle-btn{position:relative; z-index:1; min-width:84px; min-height:38px; padding:0 18px; border:none; border-radius:999px;
  background:transparent; cursor:pointer; font-family:'Inter',sans-serif; font-size:14px; font-weight:600; color:var(--chalk-dim);
  transition:color .15s ease, transform .12s ease;}
@media (hover:hover) and (pointer:fine){ .skins-toggle-btn:hover{color:var(--chalk);} }
.skins-toggle-btn[aria-pressed="true"]{color:#20180F;}
.skins-view[data-side="gross"] .skins-board[data-board="net"], .skins-view[data-side="net"] .skins-board[data-board="gross"],
.skins-view .skins-board > .mini-label:first-child{display:none;}
.tab-btn{
  position:relative; z-index:1; touch-action:manipulation;
  font-family:'Baloo 2',sans-serif; font-size:14.5px;
  font-weight:500; color:var(--chalk-dim); background:transparent; border:none; cursor:pointer;
  padding:10px 20px; border-radius:999px; white-space:nowrap; transition:color .15s ease, transform .12s ease;
}
@media (hover:hover) and (pointer:fine){ .tab-btn:hover{color:var(--chalk);} }
.tab-btn.active{background:transparent; color:#20180F; box-shadow:none;} /* the press is the shared rule at the end of the Motion policy block */
/* A staff tab's drawing shows over its word on a phone's bottom bar only (staff-dock, the 560px block): a desk shows the word. */
.tab-ico{display:none;}

/* ---------- Generic layout bits ---------- */
.section-head{display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0 0 16px; flex-wrap:wrap;}
.section-head h2{font-family:'Baloo 2',sans-serif; font-size:20px; margin:0; font-weight:600;}
/* Each section heading opens with its own outline icon, in brass, IN FRONT of its words (sectionIcon in
   20-helpers.js, the owner-approved mockup #3's drawings). No heading carries the Mulligan's logo (owner,
   2026-09-25: "icon in front no logo"): the icon sits exactly where the logo sat — a 19px box, 8px before
   the words, 3px under the baseline, so the words start where they did and its middle is level with
   theirs. */
.section-head h2 .sec-ico{display:inline-block; margin-right:8px; vertical-align:-3px; color:var(--brass);}
.section-head .sub{color:var(--chalk-faint); font-size:13.5px; margin-top:4px;}

/* One secondary button (owner-approved mockup #3, 2026-09-24): the plain .btn, dark fill and thin
   border, is every "not the main action" button; the borderless ghost and the gold-outline brass
   looks are retired. Primary (gold) is the one main action on a screen, danger (red) anything that
   removes something, and .btn-sm says an action matters less. */
.btn{
  font-family:'Inter',sans-serif; font-weight:600; font-size:13.5px; cursor:pointer; border-radius:8px;
  padding:10px 17px; border:1px solid var(--hairline-light); background:var(--panel-2); color:var(--chalk);
  transition:background-color .15s ease, border-color .15s ease, color .15s ease, transform .12s ease;
  display:inline-flex; align-items:center; gap:6px;
}
@media (hover:hover) and (pointer:fine){ .btn:hover:where(:not(:disabled)){border-color:var(--fairway-light);} }
.btn-primary{background:var(--fairway); border-color:var(--fairway); color:#20180F;}
@media (hover:hover) and (pointer:fine){ .btn-primary:hover:where(:not(:disabled)){background:var(--fairway-light);} }
.btn-danger{background:transparent; border-color:var(--danger); color:var(--danger);}
@media (hover:hover) and (pointer:fine){ .btn-danger:hover:where(:not(:disabled)){background:rgba(217,117,99,.12);} }
.btn-sm{padding:7px 12px; font-size:12.5px;}
/* an icon in a secondary button is the printer's gold, whatever colour the words are */
.btn:not(.btn-primary):not(.btn-danger) svg{color:var(--fairway-light);}
/* Disabled looks the same everywhere: faded, a not-allowed cursor, no hover tint (the :where guards
   above keep their specificity), and the press never moves it (the disabled line that ends the press
   block in the Motion policy section). */
.btn:disabled{opacity:.45; cursor:not-allowed;}
/* a disclosure's tee inside a button (Show archived leagues): the 20px mark without growing the button */
.btn .chevron-icon{margin:-3px -3px -3px -1px;}

.card{background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); padding:18px;}
.empty{
  border:1px dashed var(--hairline-light); border-radius:var(--radius); padding:34px 20px; text-align:center;
  color:var(--chalk-faint); font-size:14px;
}
.empty b{color:var(--chalk-dim); display:block; font-family:'Baloo 2',sans-serif; font-size:16px; margin-bottom:5px;}

.mini-label{
  font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--chalk-faint); display:block;
}
.badge{
  display:inline-flex; align-items:center; gap:5px; font-family:'Inter',sans-serif; font-size:12px; font-weight:600;
  padding:4px 9px; border-radius:6px; border:1px solid var(--hairline-light);
  color:var(--chalk-dim);
}

/* ---------- Dashboard ---------- */
.dash-grid{display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:28px;}
.stat-tile{background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); padding:18px;}
.stat-tile .n{font-family:'Baloo 2',sans-serif; font-size:32px; font-weight:600; color:var(--tracer); line-height:1;}
.stat-tile .l{font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--chalk-faint); margin-top:7px;}

/* the Player Link box (its type was inline, where a phone's 16px rule could not reach it) */
#player-link-field{font-family:'IBM Plex Mono',monospace; font-size:13px;}
/* Player Link's list of the leagues and their players' passwords (play-site) */
.play-link-leagues{flex-basis:100%; display:flex; flex-direction:column; gap:6px;}
.play-link-league{display:flex; flex-wrap:wrap; justify-content:space-between; align-items:baseline; gap:2px 12px; padding:8px 11px; background:var(--panel-2); border:1px solid var(--hairline); border-radius:8px;}
.play-link-name{font-weight:600; font-size:13.5px; color:var(--chalk);}
.play-link-pw{font-size:13px; color:var(--chalk-faint);}
.play-link-pw b{font-family:'IBM Plex Mono',monospace; font-weight:600; color:var(--tracer);}
.leader-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(270px,1fr)); gap:14px;}
.leader-card{background:var(--panel); border:1px solid var(--hairline-light); border-left:3px solid var(--fairway); border-radius:var(--radius); padding:18px;}
.leader-card .lg-name{font-family:'Baloo 2',sans-serif; font-size:14px; color:var(--brass); margin-bottom:10px;}
.leader-card .lg-team{font-family:'Baloo 2',sans-serif; font-size:20px; color:var(--chalk); display:flex; align-items:center; gap:8px;}
.leader-card .lg-score{font-family:'Inter',sans-serif; color:var(--chalk-dim); font-size:13px; margin-top:6px;}

/* ---------- Flip board standings ---------- */
.flipboard{border:1px solid var(--hairline-light); border-radius:var(--radius); overflow:hidden;}
.flip-row{
  display:grid; grid-template-columns:38px 1fr 90px 90px; align-items:center;
  background:var(--ink-2); border-bottom:1px solid var(--hairline);
}
.flip-row:last-child{border-bottom:none;}
.flip-row.head{background:var(--panel-2);}
.flip-row.head .fc{font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint);}
.flip-row.lead{background:rgba(232,199,154,.08);}
.fc{padding:12px 10px; font-size:14.5px;}
.fc.rank{font-family:'Baloo 2',sans-serif; font-weight:600; color:var(--chalk-faint); text-align:center;}
.flip-row.lead .fc.rank{color:var(--tracer);}
.fc.name{font-weight:600; display:flex; align-items:center; gap:8px;}
.fc.mono{font-family:'IBM Plex Mono',monospace; text-align:right; padding-right:16px; color:var(--chalk-dim);}
.flip-row.lead .fc.mono.pts{color:var(--tracer); font-weight:700;}
/* Match play's record ("7-1-0 · +17 holes") wrapped onto three lines in the 90px column on a desk,
   and the leader's points onto two. Above phone width that column is as wide as the record — the
   cells are right-aligned against a points column of one fixed width, so the numbers still line up
   down the board — and the team name takes the rest. A phone has its own layout (560px block). */
@media (min-width:561px){
  .standings-board.is-match .flip-row{grid-template-columns:38px minmax(0,1fr) max-content 104px;}
  .standings-board.is-match .fc.mono{white-space:nowrap;}
}
/* the staff schedule's columns (a phone restacks them — see the 560px block); .flip-row.sched-row
   so the 760px .flip-row template never reaches it, as the inline style it replaces never did */
.flip-row.sched-row{grid-template-columns:1fr 110px 140px 80px 210px;}
.sched-row .sched-act{text-align:right; padding-right:10px;}
/* A week already played, a little faded like the player Schedule's (look 3; the owner: "make sure thats done
   on staff side as well"): the row sinks to the page's --ink, its name drops to --chalk-dim (11.0:1, a week
   ahead 15.6:1) and its date and time to --chalk-faint (6.5:1); its buttons stay as they are. Screens only:
   paper prints every week alike. A past season (all played) is not marked. */
@media screen{
  .flip-row.sched-row.is-past{background:var(--ink);}
  .sched-row.is-past > .fc.name{color:var(--chalk-dim);}
  .sched-row.is-past > .fc.mono{color:var(--chalk-faint);}
}
/* A past season's schedule (55-past-seasons.js) has nothing to change and no buttons, so its button
   column takes no room on a desk or a tablet (it held 210px, empty). A phone keeps its row (560px block). */
.sched-readonly > .sched-row{grid-template-columns:1fr 110px 140px 80px 0;}
@media (min-width:561px){ .sched-readonly > .sched-row > .fc:nth-child(5){display:none;} }
/* On a desk the columns fit what is in them: at 1280–1354 the date and the time wrapped onto two
   lines (and Del under the other buttons) in the fixed columns while the week column took ~600px.
   Every row shares the board's column tracks (subgrid), so each column is as wide as its widest
   cell and still lines up down the board; the week takes what is left (at 761px still ~110px).
   Narrower than a desk (760px and below) there is not room for every column at its full width, so
   a small tablet keeps the fixed columns above, as does a browser without subgrid; a phone
   restacks the row. */
@supports (grid-template-columns:subgrid){
  @media not all and (max-width:760px){
    .flipboard.sched-board{display:grid; grid-template-columns:1fr auto auto auto auto;}
    .sched-board > .flip-row.sched-row{grid-column:1 / -1; grid-template-columns:subgrid;}
    .sched-board > :not(.flip-row){grid-column:1 / -1;}
  }
}

/* ---------- Leagues list ---------- */
.league-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:14px;}
.league-card{
  background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); padding:18px;
  cursor:pointer; position:relative;
  transition:border-color .15s ease, opacity .15s ease, transform .12s ease;
}
@media (hover:hover) and (pointer:fine){ .league-card:hover{border-color:var(--fairway-light);} }
.league-card.is-archived{opacity:.55;}
@media (hover:hover) and (pointer:fine){ .league-card.is-archived:hover{opacity:.85;} }
.league-card h3{font-family:'Baloo 2',sans-serif; font-size:18px; margin:0 0 9px;}
.league-card .meta-row{display:flex; gap:6px; flex-wrap:wrap; margin-bottom:12px;}
.league-card .foot{display:flex; justify-content:space-between; align-items:center; font-size:13px; color:var(--chalk-faint); border-top:1px solid var(--hairline); padding-top:10px;}
.league-card .foot b{color:var(--chalk-dim); font-weight:600;}
/* Past seasons: the yearly archive, shown read-only (55-past-seasons.js) */
.past-seasons{display:flex; flex-direction:column; gap:20px; padding-top:2px;}
.past-year-head{display:flex; align-items:baseline; gap:10px; margin:0 0 10px;}
.past-year-n{font-family:'Baloo 2',sans-serif; font-size:18px; font-weight:600; color:var(--tracer);}
.past-year-meta{font-family:'Inter',sans-serif; font-size:12.5px; color:var(--chalk-faint);}
.past-dates{font-size:12.5px; color:var(--chalk-faint); margin:0 0 10px;}
.league-header .past-dates{margin:8px 0 0;}
.league-card.is-past .foot span:first-child{color:var(--tracer);}
.badge.badge-past{border-color:var(--brass); color:var(--tracer);}
.earlier-years{margin-top:16px; display:flex; flex-direction:column; gap:10px;}
.earlier-years > .btn{align-self:flex-start;}
.earlier-years .past-year-head{margin:6px 0 0;}
.past-toggle{display:inline-flex; align-items:center; gap:8px; font-family:'Inter',sans-serif; font-size:13px; color:var(--chalk-dim); cursor:pointer; white-space:nowrap;}
.past-results{margin-top:20px; padding-top:14px; border-top:1px solid var(--hairline);}
.past-results > .mini-label{margin-bottom:8px;}
.ticket.is-past{border-style:dashed;}

/* ---------- League detail ---------- */
.back-link{display:inline-flex; align-items:center; gap:6px; color:var(--chalk-faint); font-size:13px; font-weight:600; cursor:pointer; margin-bottom:14px;}
@media (hover:hover) and (pointer:fine){ .back-link:hover{color:var(--tracer);} }
/* ← Back on the Settings page is a real button (keyboard, screen readers), drawn exactly like the link */
button.back-link{background:none; border:0; padding:0; font-family:inherit; line-height:inherit;}
.league-header{
  background:var(--panel); border-left:3px solid var(--fairway);
  border:1px solid var(--hairline-light); border-left:3px solid var(--fairway); border-radius:var(--radius); padding:22px; margin-bottom:20px;
  display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; align-items:flex-start;
}
.league-header h2{font-family:'Baloo 2',sans-serif; font-size:24px; margin:0 0 9px;}
.league-header .meta-row{display:flex; gap:6px; flex-wrap:wrap;}

.week-strip{display:flex; align-items:center; gap:10px; margin-bottom:18px; flex-wrap:wrap;}
.week-strip select, .styled-select{
  background-color:var(--panel-2); border:1px solid var(--hairline-light); color:var(--chalk); font-family:'Inter',sans-serif;
  font-size:13.5px; padding:9px 30px 9px 12px; border-radius:8px; cursor:pointer;
}
.week-strip label{font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--chalk-faint);}
/* Red only for real problems (owner, 2026-09-26: "I like that only having red for real problems, make sure
   staff side gets same continuity but be sure red is still used"). The plain note is RED: something failed
   or was refused, a mistake to fix (a hole left empty, an impossible score), a conflict, a damaged file, a
   week that is over with a team's score still missing. A NORMAL state — waiting on teams, entry not open
   yet or closed, a bye, "every team reported" — is the calm gold note (.is-calm, and .missing-scores-ok
   which it always was), each with its own drawn mark. The list of every red in the app and why it stays
   is docs/changes/print-and-red.md. */
.missing-scores-banner{
  background:rgba(217,117,99,.12); border:1px solid var(--danger); color:var(--chalk);
  border-radius:9px; padding:10px 14px; font-size:13px; margin-bottom:14px;
}
.missing-scores-banner.missing-scores-ok, .missing-scores-banner.is-calm{background:rgba(232,199,154,.1); border-color:var(--fairway-light); color:var(--chalk-dim);}
.missing-scores-banner .lock-icon{margin-right:6px;}

.tabsub{position:relative; display:flex; gap:6px; margin-bottom:18px; border-bottom:1px solid var(--hairline); overflow-x:auto;}
.tabsub button{
  position:relative; z-index:1; touch-action:manipulation;
  background:none;border:none;color:var(--chalk-faint); font-family:'Baloo 2',sans-serif;
  font-size:14px; padding:10px 4px; margin-right:20px; cursor:pointer; border-bottom:2px solid transparent; white-space:nowrap;
  transition:color .15s ease;
}
.tabsub button.active{color:var(--tracer); border-bottom-color:transparent;}
.tabsub .tab-glide{height:2px; border-radius:1px; background:var(--tracer); box-shadow:none;}
.tabsub .tab-glide.is-pieces{background:transparent;}
.tabsub .tab-glide-piece{background:var(--tracer); box-shadow:none;}
/* The same for a staff league's sub-tabs (tabs-and-morph): a sub-tab's area is at least a screen tall, less
   the row and the page's foot, so switching to a short one never clamps the page and pulls the pinned staff
   bar down (landingScroll's tabTop). Paper has no screen to fill (the print block). */
.league-body{min-height:calc(100vh - 200px);}

.team-card{background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); padding:16px; margin-bottom:12px;}

/* ---------- Bay Board ---------- */
/* the "card turned in" tick (40-bayboard.js: reportedTickIcon) — one inline-SVG mark, sized per place */
.bb-tick{display:block; flex:none;}
.bay-board-leaderboard{
  flex:none; width:380px; background:var(--panel); border:1px solid var(--hairline-light); border-radius:14px;
  padding:22px; position:sticky; top:16px;
}
.lb-title{font-family:'Baloo 2',sans-serif; font-size:26px; color:var(--chalk); padding-bottom:14px; margin-bottom:8px; border-bottom:3px solid var(--fairway);}
.lb-row{display:flex; align-items:center; gap:12px; padding:12px 0; border-bottom:1px solid var(--hairline);}
.lb-row:last-child{border-bottom:none;}
.lb-rank{font-family:'Baloo 2',sans-serif; font-size:24px; color:var(--brass); width:34px; flex:none;}
.lb-name{font-family:'Inter',sans-serif; font-weight:600; font-size:20px; color:var(--chalk); flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.lb-val{font-family:'IBM Plex Mono',monospace; font-size:19px; color:var(--tracer); font-weight:700; flex:none;}
/* ---------- The board: the TV (Present Board) and its copy on the Bay Board tab ----------
   (owner-approved, 2026-09-24: "Present Board owns the TV"; only the bays in play, same day;
   bayboard-v2, 2026-09-25: today's board and board A together, several leagues at once, "simple
   is key".) One tile per bay in play, in bay order: the bay number big and the team names bigger
   still, the players under each, and one calm status line per team; a match play pairing with
   both cards in adds its result. The fewer the tiles, the bigger they are, type included
   (.tv-body[data-bays] below). Every measure is a multiple of --u — 1% of the screen's height on
   a 16:9 screen, held to the width on anything narrower — so 1920x1080 across a room and 1280x720
   are the same picture at two sizes; inside a tile it is --t (--u times the layout's --k) and, for
   the bay number, --tn. The Bay Board tab shows the same picture a size down (.tv-frame: there --u
   is 1% of a 16:9 frame's height). A portrait or phone-sized screen gets tiles that stack and
   scroll. The overlay is the element the Present view transition morphs (vtPlan "present"). */
.bayboard-present-overlay{
  --u:min(1vh, .5625vw);
  position:fixed; inset:0; z-index:900; background:var(--ink); overflow-y:auto;
  padding:calc(var(--u) * 3.2) calc(var(--u) * 4);
}
/* The TV's own big logo: --brass at .16 over the TV's --ink, as a plain picture with that colour baked in
   (public/assets/tv-mark.png, by tools/page-mark.js: the logo's own pixels, fitted to the same square), not a mask over a
   block of colour, which WebKit draws with an extra pass on every frame the ticker crawls (layers, 2026-10-03). */
.bayboard-present-overlay::before{
  content:""; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
  width:118vmin; height:118vmin; background:url("/assets/tv-mark.png") center / contain no-repeat;
  pointer-events:none; z-index:0;
}
.bayboard-present-inner{max-width:1600px; margin:0 auto; position:relative; z-index:1;}
.bayboard-present-exit{
  position:fixed; top:18px; right:18px; z-index:901; background:var(--panel-2); border:1px solid var(--hairline-light);
  color:var(--chalk-faint); font-size:18px; width:38px; height:38px; border-radius:50%; cursor:pointer;
}
@media (hover:hover) and (pointer:fine){ .bayboard-present-exit:hover{color:var(--chalk); border-color:var(--fairway-light);} }
.tv-board{max-width:none; height:100%; display:flex; flex-direction:column; gap:calc(var(--u) * 2.4);}
/* The tab's copy: the TV's board in a 16:9 frame the width of the page, under Board Settings. The
   frame is a size container, so the copy's --u follows the FRAME (1% of its 16:9 height), not the
   screen. It has no fill and no watermark of its own (logo-pass, 2026-09-25): the page is the same
   --ink, and the page's one big mark (.page-mark) shows through it like everywhere else on the page.
   (A browser without container units gets a plain unit and a board as tall as its content.) */
.tv-frame{position:relative; overflow:hidden; container-type:inline-size; margin-bottom:16px;
  border:1px solid var(--hairline-light); border-radius:14px;}
.tv-frame > .tv-board{--u:.5vw; padding:calc(var(--u) * 3.2) calc(var(--u) * 4);}
/* --grow (tvFrameGrow, board-followups): a night whose tiles don't fit even at the smallest makes the frame taller */
@supports (width:1cqw){
  .tv-frame > .tv-board{--u:calc(100cqw / 177.78); height:calc(100cqw * .5625 + var(--grow, 0px));}
}
.tv-frame > .tv-board > .empty{margin:auto;}
.tv-frame .empty::before{display:none;}   /* no little mark in the board's empty states: the page's big one is behind the frame */
.tv-frame .tv-head{padding-right:0;}
/* the night: league name(s), week and time (the right is kept clear of the TV's exit button) */
.tv-head{display:flex; align-items:flex-end; justify-content:space-between; gap:calc(var(--u) * 4);
  padding:0 56px calc(var(--u) * 1.6) 0; border-bottom:2px solid var(--fairway);}
.tv-leagues{display:flex; flex-wrap:wrap; align-items:flex-end; gap:calc(var(--u) * 1.2) 0; min-width:0;}
.tv-league{min-width:0; padding-right:calc(var(--u) * 4 * var(--head-fit, 1));}
.tv-league + .tv-league{padding-left:calc(var(--u) * 4 * var(--head-fit, 1)); border-left:2px solid var(--hairline-light);}
.tv-league-name{font-family:'Baloo 2',sans-serif; font-weight:600; font-size:max(22px, calc(var(--u) * 4.8 * var(--head-fit, 1))); line-height:1.05; color:var(--chalk); overflow-wrap:anywhere;}
.tv-league-meta{display:flex; flex-wrap:wrap; gap:0 1.1em; margin-top:calc(var(--u) * .5 * var(--head-fit, 1));
  font-family:'IBM Plex Mono',monospace; font-weight:600; font-size:max(13px, calc(var(--u) * 2.3 * var(--head-fit, 1))); color:var(--brass);}
/* Three or four leagues on one row (tvFitHead, 40-bayboard.js; a second row of leagues took the
   tiles' room). When they don't fit one row at full size the header's own type steps down together
   (--head-fit: the names, the week, date and time, the space between leagues; never under the
   minimum sizes above), and when that is not enough each league's week stands over its date and time
   (.is-stacked), then the date over the time (.is-split). Nothing is left out; a night that still won't fit wraps as before, at full size. The
   tiles are never made smaller for it. Only on the live page, and never where the leagues stack. */
.tv-head.is-one-row .tv-leagues{flex-wrap:nowrap;}
.tv-head.is-one-row .tv-league{min-width:auto;}
.tv-head.is-one-row .tv-league-name, .tv-head.is-one-row .tv-league-meta{white-space:nowrap;}
.tv-head.is-one-row .tv-league-meta{flex-wrap:nowrap;}
.tv-head.is-stacked .tv-league-meta{flex-direction:column;}
/* the last league needs no room after it on a fitted row (the row is left-aligned: nothing moves; desk-looks) */
.tv-head.is-one-row .tv-league:last-child{padding-right:0;}
/* the third step: the date over the time as well (the dot between them goes with the line break) */
.tv-head.is-split .tv-league-time{display:block;}
.tv-head.is-split .tv-league-dot{display:none;}
/* How the tiles share the screen (data-bays = how many bays are in play): one row of one, two or
   three; 2 x 2 for four (twice as wide as four across, so a pairing stands side by side); 3 x 2 for
   five or six; 4 x 2 for seven or eight. The fewer the tiles, the bigger their type: --k scales a
   tile's text and spacing, --kn its bay number. Beside one to three tiles the leaderboard is wider
   and a size up (--lbw, --lk) so it is not dwarfed by them. A row with a crowded bay takes what it
   needs and the other row gives it up; when a night needs more room than the screen has,
   tvFitBoard (40-bayboard.js) steps every tile down together (--fit) and the leaderboard on its
   own (--lb-fit), so the TV never scrolls. */
.tv-body{--cols:4; --rows:2; --k:1; --kn:1; --lbw:40; --lk:1;
  flex:1 1 0; min-height:0; display:grid; grid-template-columns:minmax(0,1fr); grid-template-rows:minmax(0,1fr); gap:calc(var(--u) * 2.4);}
.tv-body[data-bays="1"]{--cols:1; --rows:1; --k:2.5; --kn:2.3; --lbw:50; --lk:1.3;}
.tv-body[data-bays="2"]{--cols:2; --rows:1; --k:1.8; --kn:1.7; --lbw:46; --lk:1.25;}
.tv-body[data-bays="3"]{--cols:3; --rows:1; --k:1.5; --kn:1.45; --lk:1.15;}
.tv-body[data-bays="4"]{--cols:2; --rows:2; --k:1.35; --kn:1.3;}
.tv-body[data-bays="5"], .tv-body[data-bays="6"]{--cols:3; --rows:2; --k:1.05; --kn:1.05;}
.tv-body.has-lb{grid-template-columns:minmax(0,1fr) calc(var(--u) * var(--lbw));}
.tv-grid{display:grid; grid-template-columns:repeat(var(--cols), minmax(0,1fr)); grid-template-rows:repeat(var(--rows), minmax(min-content, 1fr)); gap:calc(var(--u) * 2.2);}
.tv-tile{--t:calc(var(--u) * var(--k) * var(--fit, 1)); --tn:calc(var(--u) * var(--kn) * var(--fit, 1));
  position:relative; min-width:0; display:flex; flex-direction:column; gap:calc(var(--t) * 1.2);
  padding:calc(var(--t) * 1.9) calc(var(--t) * 2.2) calc(var(--t) * 2.1);
  border:1px solid var(--hairline-light); border-radius:calc(var(--t) * 1.8); background-color:rgba(30,30,26,.9);}
.tv-tile-head{display:flex; align-items:baseline; gap:calc(var(--tn) * .8); line-height:.8;}
.tv-tile-word{font-family:'Inter',sans-serif; font-weight:700; font-size:max(12px, calc(var(--tn) * 1.45)); letter-spacing:.16em; text-transform:uppercase; color:var(--brass);}
.tv-tile-num{font-family:'Baloo 2',sans-serif; font-weight:700; font-size:calc(var(--tn) * 4.1); line-height:.8; color:var(--chalk);}
/* lit: a band in the team colour(s) along the top, their tint in the corners, the border warmed by
   the first. (Colours are hex-checked in boardColor; without color-mix the band alone is kept.) */
.tv-tile.is-lit{
  border-color:color-mix(in srgb, var(--tile-c1) 55%, var(--hairline-light));
  background-image:linear-gradient(90deg, var(--tile-c1) 0 50%, var(--tile-c2) 50% 100%);
  background-image:linear-gradient(90deg, var(--tile-c1) 0 50%, var(--tile-c2) 50% 100%),
    radial-gradient(120% 95% at 0% 0%, color-mix(in srgb, var(--tile-c1) 24%, transparent), transparent 64%),
    radial-gradient(120% 95% at 100% 100%, color-mix(in srgb, var(--tile-c2) 16%, transparent), transparent 64%);
  background-size:100% calc(var(--t) * .8), 100% 100%, 100% 100%; background-repeat:no-repeat;
}
/* a tile's teams: each league on it is a group, its teams one under the other; short names (a
   pairing's two, or a league's teams, .is-short) stand side by side when the tile is wide enough
   for both at full size — a long name keeps the width to itself rather than being cut */
.tv-tile-teams{display:grid; grid-template-columns:minmax(0,1fr); align-items:start; gap:calc(var(--t) * 1.5) calc(var(--t) * 2.4); min-width:0;}
.tv-tile-group, .tv-match{display:grid; grid-template-columns:minmax(0,1fr); align-items:start; gap:calc(var(--t) * 1.3) calc(var(--t) * 2.4); min-width:0;}
.tv-tile-group.is-short, .tv-match.is-short{grid-template-columns:repeat(auto-fit, minmax(min(100%, max(9.5rem, calc(var(--t) * 17))), 1fr));}
.tv-tile-league, .tv-match, .tv-match-result{grid-column:1 / -1;}
.tv-tile-league{font-family:'Inter',sans-serif; font-weight:700; font-size:max(12px, calc(var(--t) * 1.35)); letter-spacing:.1em; text-transform:uppercase; color:var(--brass); margin-bottom:calc(var(--t) * -.5);}
.tv-tile-head > .tv-tile-league{margin:0 0 0 auto; padding-left:calc(var(--t) * 1.2); text-align:right; line-height:1.1;}
.tv-team{min-width:0;}
.tv-team-name{display:flex; align-items:center; gap:.26em; min-width:0;
  font-family:'Baloo 2',sans-serif; font-weight:700; font-size:max(16px, calc(var(--t) * 4.5)); line-height:1.02; color:var(--chalk);}
.tv-team-label{flex:1; min-width:0; overflow-wrap:anywhere; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden;}
.tv-team-mark{flex:none; width:.36em; height:.36em; border-radius:.1em;}
.tv-team-mark.is-photo{width:.74em; height:.74em; border-radius:50%; object-fit:cover;}
.tv-team-players{margin-top:calc(var(--t) * .3); overflow-wrap:anywhere;
  font-family:'Inter',sans-serif; font-weight:500; font-size:max(12px, calc(var(--t) * 1.7)); line-height:1.25; color:var(--chalk-dim);}
/* one status line per team: a quiet "Playing" until its card is in, then the tick, "Card in" and
   the team's score (teamsWithoutScore decides which; 40-bayboard.js: tvTeamHtml) */
.tv-team-status{display:inline-flex; align-items:center; gap:.45em; margin-top:calc(var(--t) * .6); padding:.36em .95em; border-radius:999px;
  border:max(1px, calc(var(--t) * .16)) solid var(--hairline-light); white-space:nowrap; transform-origin:left center;
  font-family:'Inter',sans-serif; font-weight:700; font-size:max(12px, calc(var(--t) * 1.45)); line-height:1.1; letter-spacing:.12em; text-transform:uppercase; color:var(--chalk-faint);}
.tv-team-status.is-in{background:var(--fairway); border-color:var(--fairway); color:#20180F; padding-left:.4em;}
.tv-team-status .bb-tick{width:1.45em; height:1.45em;}
.tv-team-score{font-family:'IBM Plex Mono',monospace; font-weight:700; font-size:1.3em; letter-spacing:0; margin-left:.1em; padding-left:.45em;
  border-left:max(1px, calc(var(--t) * .16)) solid rgba(32,24,15,.3);}
/* match play: one result line for a pairing once both its cards are in (computeMatchResult) */
.tv-match-result{padding:.28em .9em; border-radius:calc(var(--t) * 1); border:max(1px, calc(var(--t) * .16)) solid var(--tracer); background:rgba(232,199,154,.1);
  font-family:'Baloo 2',sans-serif; font-weight:700; font-size:max(13px, calc(var(--t) * 1.95)); line-height:1.1; color:var(--tracer); text-align:center; overflow-wrap:anywhere;}
/* three or more teams on one bay: the number and the type step down so the tile still fits, and on
   a tile wide enough for it each league's teams stand side by side (two leagues sharing a bay) */
.tv-tile[data-density]{gap:calc(var(--t) * 1);}
.tv-tile[data-density] .tv-tile-num{font-size:calc(var(--tn) * 3.3);}
.tv-tile[data-density] .tv-tile-teams{grid-template-columns:repeat(auto-fit, minmax(min(100%, max(12rem, calc(var(--t) * 26))), 1fr)); align-content:start; gap:calc(var(--t) * 1.2) calc(var(--t) * 2.4);}
.tv-tile[data-density="crowded"] .tv-team-name{font-size:max(14px, calc(var(--t) * 3.5));}
.tv-tile[data-density="crowded"] .tv-team-players{font-size:max(12px, calc(var(--t) * 1.45));}
.tv-tile[data-density="crowded"] .tv-team-status{font-size:max(12px, calc(var(--t) * 1.25));}
.tv-tile[data-density="crowded"] .tv-match-result{font-size:max(12px, calc(var(--t) * 1.6));}
.tv-tile[data-density="packed"] .tv-team-name{font-size:max(13px, calc(var(--t) * 2.9));}
.tv-tile[data-density="packed"] .tv-team-players{font-size:max(12px, calc(var(--t) * 1.3));}
.tv-tile[data-density="packed"] .tv-team-status{font-size:max(12px, calc(var(--t) * 1.1));}
.tv-tile[data-density="packed"] .tv-match-result{font-size:max(12px, calc(var(--t) * 1.4));}
/* nothing in play: one calm line where the tiles would be (the header still names the night; the
   big mark behind the board — the TV's own, or on the tab the page's — stands in for the little mark
   an empty state carries elsewhere) */
.tv-board .tv-empty{align-self:center; justify-self:center; width:min(100%, calc(var(--u) * 90)); border:none; padding:calc(var(--u) * 4);
  font-family:'Inter',sans-serif; font-size:max(14px, calc(var(--u) * 2.3)); color:var(--chalk-faint);}
.tv-board .tv-empty::before{display:none;}
.tv-board .tv-empty b{font-size:max(20px, calc(var(--u) * 4.8)); font-weight:600; color:var(--chalk-dim); margin-bottom:calc(var(--u) * .8);}
.tv-board .bay-board-leaderboard{--lu:calc(var(--u) * var(--lk) * var(--lb-fit, 1));
  width:auto; position:static; align-self:stretch; min-width:0; overflow:hidden; background:rgba(30,30,26,.9);
  padding:calc(var(--lu) * 2.6) calc(var(--lu) * 2.8); border-radius:calc(var(--u) * 1.8);}
.tv-board .lb-title{font-size:max(17px, calc(var(--lu) * 3.4)); padding-bottom:calc(var(--lu) * 1.3); margin-bottom:calc(var(--lu) * .6);}
.tv-board .lb-row{gap:calc(var(--lu) * 1.4); padding:calc(var(--lu) * 1.1) 0;}
.tv-board .lb-rank{font-size:max(15px, calc(var(--lu) * 3.1)); width:max(22px, calc(var(--lu) * 4.2));}
.tv-board .lb-name{font-size:max(13px, calc(var(--lu) * 2.7));}
.tv-board .lb-val{font-size:max(13px, calc(var(--lu) * 2.6));}
.tv-board .bay-board-leaderboard .tag-input-hint{font-size:max(12px, calc(var(--lu) * 2.1));}
/* The ticker (owner, 2026-09-25, from board A): one calm crawl along the bottom, the screen's full
   width, league by league — the Mulligan's mark, the league in gold, then its standings (several
   leagues), tonight's results and the skins leaders. Big, high-contrast type on a dark band, faded
   at both ends. The crawl is 40-bayboard.js (boardTickerSync); the markup never moves. */
.tv-ticker{flex:none; position:relative; display:flex; align-items:center; overflow:hidden; margin:0 calc(var(--u) * -4);
  height:calc(var(--u) * 6.4); background:rgba(11,11,9,.92); border-top:2px solid var(--fairway);
  font-family:'Inter',sans-serif; font-weight:600; font-size:max(13px, calc(var(--u) * 2.75)); line-height:1.2; color:var(--chalk); white-space:nowrap;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 calc(var(--u) * 3), #000 calc(100% - var(--u) * 3), transparent);
  mask-image:linear-gradient(90deg, transparent, #000 calc(var(--u) * 3), #000 calc(100% - var(--u) * 3), transparent);}
.tv-ticker-track{display:flex; align-items:center; flex:none; will-change:transform;}
.tv-ticker-run{display:flex; align-items:center; flex:none;}
.tv-tick-seg{display:flex; align-items:center; flex:none; gap:0 1em; padding:0 1em 0 1.6em;}
.tv-tick-mark{width:1.55em; height:1.15em; background-color:var(--brass); -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);}   /* small: the mark without its lettering (logos-popup) */
.tv-tick-league{font-family:'Baloo 2',sans-serif; font-weight:700; font-size:1.12em; color:var(--tracer);}
.tv-tick-label{font-size:max(12px, .62em); font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--brass); margin-left:.9em;}
.tv-tick-item{display:inline-flex; align-items:baseline; gap:.42em;}
.tv-tick-item + .tv-tick-item{padding-left:1em; border-left:2px solid var(--hairline-light);}
.tv-tick-place{font-family:'IBM Plex Mono',monospace; font-weight:600; font-size:max(12px, .9em); color:var(--brass);}
.tv-tick-val{font-family:'IBM Plex Mono',monospace; font-weight:700; font-size:max(12px, .92em); color:var(--tracer);}
/* portrait, or a phone: tiles in two columns (one for a lone bay, one on a phone) that scroll, the
   leaderboard and the ticker under them, every size back to the plain screen unit — no layout
   scale, no fit */
@media (max-aspect-ratio: 1/1), (max-width: 760px){
  .bayboard-present-overlay{--u:1.2vw; padding:22px 16px 28px;}
  .bayboard-present-overlay .tv-board{height:auto; min-height:100%;}
  .bayboard-present-overlay .tv-body{flex:1 1 auto; grid-template-rows:none; align-content:start;}
  .bayboard-present-overlay .tv-head{flex-direction:column; align-items:flex-start; gap:10px;}
  .bayboard-present-overlay .tv-leagues{flex-direction:column; align-items:flex-start; gap:10px;}
  .bayboard-present-overlay .tv-league, .bayboard-present-overlay .tv-league + .tv-league{padding:0; border-left:none;}
  .bayboard-present-overlay .tv-body.has-lb{grid-template-columns:minmax(0,1fr);}
  .bayboard-present-overlay .tv-grid{grid-template-columns:repeat(2, minmax(0,1fr)); grid-template-rows:none; grid-auto-rows:minmax(calc(var(--u) * 26), auto);}
  .bayboard-present-overlay .tv-body[data-bays="1"] .tv-grid{grid-template-columns:minmax(0,1fr);}
  .bayboard-present-overlay .tv-tile{--t:var(--u); --tn:var(--u);}
  .bayboard-present-overlay .tv-board .bay-board-leaderboard{--lu:var(--u);}
  .bayboard-present-overlay .tv-ticker{margin:0 -16px; height:auto; padding:12px 0;}
}
/* The tab's copy on a narrow page (a phone, a tablet standing up): the same board with its tiles
   stacked — two across while there is room, one on a phone — the page scrolling instead of the
   frame, sizes off a phone-sized unit (and the plain screen minimums), no fit. */
@container (max-width: 760px){
  .tv-frame > .tv-board{--u:min(1.43cqw, 5.2px); height:auto; padding:16px 14px 0; gap:14px;}
  .tv-frame .tv-head{flex-direction:column; align-items:flex-start; gap:8px;}
  .tv-frame .tv-leagues{flex-direction:column; align-items:flex-start; gap:8px;}
  .tv-frame .tv-league, .tv-frame .tv-league + .tv-league{padding:0; border-left:none;}
  .tv-frame .tv-body{flex:none; grid-template-rows:none; align-content:start;}
  .tv-frame .tv-body.has-lb{grid-template-columns:minmax(0,1fr);}
  .tv-frame .tv-grid{grid-template-columns:repeat(2, minmax(0,1fr)); grid-template-rows:none;}
  .tv-frame .tv-body[data-bays="1"] .tv-grid{grid-template-columns:minmax(0,1fr);}
  .tv-frame .tv-tile{--t:var(--u); --tn:var(--u);}
  .tv-frame .tv-board .bay-board-leaderboard{--lu:var(--u);}
  .tv-frame .tv-ticker{margin:0 -14px; height:auto; padding:12px 0;}
}
@container (max-width: 560px){
  .tv-frame .tv-grid{grid-template-columns:minmax(0,1fr);}
}
/* ---------- A card coming in (40-bayboard.js: boardQueueMotion) ----------
   The tile bubbles (the popups' squash-and-settle), a tracer halo lights up and fades over a
   couple of seconds, the team's "Card in" line pops in with its score (the tick popping inside
   it), and a match result the card just finished pops in after it. A re-ordered leaderboard glides
   its rows (FLIP) on the tab highlight's curve. The classes are put on by script after the pass and
   never appear in the markup; --glow-ago lets a tile rebuilt mid-glow pick up where it was.
   Transform and opacity only. */
:root{--report-glow:2.4s;}
.tv-tile.is-reporting{z-index:2;
  animation:reportBubble calc(var(--popup-in) * 2) cubic-bezier(.3,.7,.3,1) both; animation-delay:calc(0ms - var(--glow-ago, 0ms));}
.tv-tile.is-reporting::after{content:""; position:absolute; inset:-1px; border-radius:inherit; pointer-events:none;
  box-shadow:0 0 0 calc(var(--u) * .45) var(--tracer), 0 0 calc(var(--u) * 7) calc(var(--u) * 2) rgba(232,199,154,.55), inset 0 0 calc(var(--u) * 5) rgba(232,199,154,.22);
  animation:reportGlow var(--report-glow) ease-out both; animation-delay:calc(0ms - var(--glow-ago, 0ms));}
.is-just-reported .bb-tick{animation:reportTick calc(var(--popup-in) * 1.8) cubic-bezier(.3,.7,.3,1) both; animation-delay:calc(140ms - var(--glow-ago, 0ms));}
.tv-team.is-just-reported .tv-team-status{animation:reportPop calc(var(--popup-in) * 1.8) cubic-bezier(.3,.7,.3,1) both; animation-delay:calc(60ms - var(--glow-ago, 0ms));}
.tv-match-result.is-just-reported{animation:reportPop calc(var(--popup-in) * 1.8) cubic-bezier(.3,.7,.3,1) both; animation-delay:calc(320ms - var(--glow-ago, 0ms));}
@keyframes reportBubble{0%{transform:scale(1);} 35%{transform:scale(1.045);} 65%{transform:scale(.99);} 100%{transform:scale(1);}}
@keyframes reportGlow{0%{opacity:0;} 12%{opacity:1;} 55%{opacity:.8;} 100%{opacity:0;}}
@keyframes reportTick{0%{transform:scale(.3); opacity:0;} 55%{transform:scale(1.28); opacity:1;} 80%{transform:scale(.94);} 100%{transform:scale(1); opacity:1;}}
@keyframes reportPop{0%{transform:scale(.6); opacity:0;} 55%{transform:scale(1.1); opacity:1;} 80%{transform:scale(.97);} 100%{transform:scale(1); opacity:1;}}
.lb-row.is-flipping{position:relative; z-index:1; background:var(--panel); transition:transform calc(var(--glide) * 2) cubic-bezier(.3,1.15,.4,1);}
.lb-row.is-flipping.is-climbing{z-index:2;}
@media (prefers-reduced-motion: reduce){
  .is-reporting, .is-reporting::after, .is-just-reported .bb-tick{animation:none !important;}
  .tv-team.is-just-reported .tv-team-status, .tv-match-result.is-just-reported{animation:none !important;}
  .is-reporting::after{opacity:0;}
  .lb-row.is-flipping{transition:none !important;}
}
.team-card .th{display:flex; align-items:center; justify-content:space-between; margin-bottom:10px;}
.team-card .th .tname{display:flex; align-items:center; gap:9px; font-family:'Baloo 2',sans-serif; font-size:16.5px;}
.swatch{width:12px;height:12px;border-radius:3px;flex:none;}

/* Bay Board admin controls — small and out of the way, since customers see this screen */
.bayboard-settings{background:var(--panel-2); border:1px solid var(--hairline-light); border-radius:9px; margin-bottom:16px; overflow:hidden;}
.bayboard-settings-toggle{
  width:100%; display:flex; justify-content:space-between; align-items:center;
  background:none; border:none; cursor:pointer; padding:7px 12px;
  font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint);
}
@media (hover:hover) and (pointer:fine){ .bayboard-settings-toggle:hover{color:var(--chalk-dim);} }
.bayboard-settings-body{padding:2px 12px 10px; border-top:1px solid var(--hairline); font-size:12.5px;}
.bs-row{display:flex; align-items:center; gap:8px; padding:5px 0; flex-wrap:wrap;}
.bs-row .mini-label{min-width:auto; flex:none; font-size:12px;}
.bs-row input[type=number]{max-width:56px; padding:5px 7px; font-size:12.5px;}
.bayboard-settings-body select{max-width:190px; padding:6px 30px 6px 10px; font-size:12.5px;}
.bayboard-settings-body .tee-wrap{max-width:190px;}
.bayboard-settings-body .tee-wrap > .tee-mark{right:7px;}
/* a league's week box: wide enough for a long week's whole name and date on a desk ("Preview Fri · Sep 25, 2026" read
   "… Sep 25, 202" at 170-190px); anything longer ends in "…" and the box's title has it whole (desk-looks) */
.bayboard-settings-body .bs-week-pick > .tee-wrap{max-width:260px;}
.bayboard-settings-body .bs-week-pick select{max-width:260px; text-overflow:ellipsis;}
.bs-week-pick{display:flex; align-items:center; gap:6px;}
.bs-week-pick > .mini-label{white-space:nowrap;}
.bs-bayassign{display:flex; flex-wrap:wrap; gap:16px; padding-top:4px;}
.bs-bayassign-group{min-width:170px;}
.bs-bayassign-league{font-family:'Inter',sans-serif; font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--chalk-faint); margin-bottom:4px;}
.bs-bayassign-row{display:flex; align-items:center; justify-content:space-between; gap:8px; padding:3px 0;}
.bs-bayassign-team{display:flex; align-items:center; gap:5px; font-size:12px; font-weight:600; color:var(--chalk-dim); white-space:nowrap;}
.bs-bayassign-row select{max-width:118px; padding:4px 26px 4px 7px; font-size:12px;}
.bs-bayassign-row .tee-wrap{max-width:118px;}
.bs-bayassign-row .tee-wrap > .tee-mark{right:4.5px;}
.roster-chip{
  display:inline-flex; align-items:center; gap:6px; background:var(--panel-2); border:1px solid var(--hairline-light);
  border-radius:20px; padding:6px 7px 6px 12px; font-size:13.5px; margin:0 6px 6px 0;
}
.roster-chip button{background:none;border:none;color:var(--chalk-faint); cursor:pointer; font-size:14px; line-height:1; padding:3px 5px;}
@media (hover:hover) and (pointer:fine){ .roster-chip button:hover{color:var(--danger);} }
/* An icon-only button's drawn mark sits as a block, so the button is its padding plus the mark
   (sized to the old glyph's line) and a row of them is exactly as tall as it was with ✎ ✕ ⇄. */
.roster-chip button .ui-mark, .player-row-top button .ui-mark{display:block;}
.roster-chip .hc{color:var(--chalk-faint); font-family:'IBM Plex Mono',monospace; font-size:12px;}
.add-player-row{display:flex; gap:8px; align-items:center; margin-top:8px;}

.player-row{background:var(--panel-2); border:1px solid var(--hairline-light); border-radius:9px; padding:9px 12px;}
.player-row-top{display:flex; align-items:center; justify-content:space-between; gap:10px;}
.player-row-name{font-family:'Baloo 2',sans-serif; font-size:16px; cursor:pointer;}
@media (hover:hover) and (pointer:fine){ .player-row-name:hover{color:var(--fairway-light);} }
.player-row-top button{background:none;border:none;color:var(--chalk-faint); cursor:pointer; font-size:17px; line-height:1; padding:5px 7px; border-radius:7px;}
@media (hover:hover) and (pointer:fine){ .player-row-top button:hover{background:var(--panel);} }
@media (hover:hover) and (pointer:fine){ .player-row-top button:hover{color:var(--danger);} }
@media (hover:hover) and (pointer:fine){ .player-row-top button[title="Edit handicap"]:hover{color:var(--fairway-light);} }
/* marking a substitute removes nothing: its hover is the gold of the pencil, not the cross's red (print-and-red) */
@media (hover:hover) and (pointer:fine){ .player-row-top .sub-toggle-btn:hover{color:var(--fairway-light);} }
.sub-toggle-btn{font-size:14px !important;}
.sub-toggle-btn.is-active{color:var(--tracer) !important;}
@media (hover:hover) and (pointer:fine){ .sub-toggle-btn.is-active:hover{color:var(--tracer) !important;} }
.sub-tag{
  display:inline-block; font-family:'Inter',sans-serif; font-size:12px; line-height:1.25; font-weight:700; letter-spacing:.04em;
  color:var(--tracer); border:1px solid var(--tracer); border-radius:4px; padding:1px 5px; vertical-align:middle; margin-left:4px;
}
.is-sub-row{border-color:var(--tracer);}
.player-row-stats{display:flex; gap:16px; margin-top:7px; flex-wrap:wrap;}
.player-stat{display:flex; flex-direction:column; gap:1px;}
.player-stat-val{font-family:'IBM Plex Mono',monospace; font-size:13.5px; color:var(--chalk);}
.player-stat .hc{font-family:'Inter',sans-serif; font-size:12px; color:var(--chalk-faint);}
.player-row-compact{padding:8px 10px;}
.player-row-compact .player-row-name{font-size:14.5px;}
.player-row-compact .player-row-stats{gap:12px; margin-top:5px;}
.player-row-compact .player-stat-val{font-size:12.5px;}
.player-row-compact .mini-label{font-size:12px;}
.player-row-compact .player-stat .hc{font-size:12px;}
/* A team's season (see 2, 70-modals.js: modalTeamRounds): the Standings row on top, then each week's one line with the
   running total on its right, level with the line's first words every week (on a narrow phone the words wrap beside
   it), so the totals read down the season as a column; a week gone by with nothing to show is quieter; the weeks
   still to come are plain lines after the season so far. */
.season-sum{margin:0 0 16px;}
.season-line{display:flex; align-items:baseline; gap:12px; font-family:'Inter',sans-serif; font-size:14px; line-height:1.4; color:var(--chalk);}
.season-res{flex:1 1 auto; min-width:0;}
.season-tot{flex:none; font-family:'IBM Plex Mono',monospace; font-size:13.5px; font-weight:600; color:var(--tracer); white-space:nowrap;}
.season-line.is-quiet{font-size:13.5px; color:var(--chalk-faint);}
.season-ahead{margin:4px 0 0;}
.season-ahead .mini-label{margin-bottom:4px;}
.season-ahead-row{display:flex; align-items:baseline; flex-wrap:wrap; gap:2px 10px; padding:7px 0; border-top:1px solid var(--hairline); font-family:'Inter',sans-serif; font-size:13px; color:var(--chalk-faint);}
.season-ahead-name{color:var(--chalk-dim); font-weight:600;}
.season-ahead-date{margin-left:auto; font-family:'IBM Plex Mono',monospace; font-size:12px; white-space:nowrap;}
/* A player's numbers (see 5, 70-modals.js: renderPlayerNumbers): one calm box at the top of their rounds popup, the
   Players tab's own label-over-number stats two to a row, the estimate across the whole box under a hairline. */
.player-numbers{background:var(--ink-2); border:1px solid var(--hairline-light); border-radius:12px; padding:12px 14px 14px; margin:0 0 16px;}
.player-numbers-head{color:var(--chalk-dim); margin-bottom:10px;}
.player-numbers .player-row-stats{display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:12px 16px; margin-top:0;}
.player-numbers .player-row-stats + .player-row-stats{margin-top:12px; padding-top:12px; border-top:1px solid var(--hairline);}
.player-numbers .player-stat-val{font-size:15px;}
.player-numbers .is-estimate{grid-column:1 / -1;}
.player-numbers .is-estimate .player-stat-val{color:var(--tracer);}

.score-grid{display:grid; grid-template-columns:1fr 90px 90px; gap:9px 10px; align-items:center; margin-top:10px;}
.score-grid .sg-label{font-size:14px; color:var(--chalk-dim); display:flex; align-items:center; gap:6px;}
.score-grid .sg-center{justify-content:center; text-align:center;}
.score-grid .sg-label .hc{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint);}
/* The desk's Week-by-Week numbers (cards-only): text where the grid's boxes were, as the player's Scoring tab shows them
   (a player's gross, their net fainter; a scramble's two team totals bigger, the net in gold) */
.score-fig{font-family:'IBM Plex Mono',monospace; color:var(--chalk); text-align:center;}
.score-fig.is-net{color:var(--chalk-faint);}
.score-fig.is-team{font-size:18px; padding:9px 0; text-align:left;}
.score-fig.is-team.is-net{color:var(--tracer);}
input[type=number], input[type=text], input[type=date], select, textarea{
  background-color:var(--ink-2); border:1px solid var(--hairline-light); color:var(--chalk); border-radius:8px;
  padding:9px 11px; font-family:'Inter',sans-serif; font-size:14px; width:100%;
}
input[type=number]{font-family:'IBM Plex Mono',monospace;}
/* Dropdowns are a choice, not a quantity — strip the browser's native up/down control
   (some browsers draw it as a stacked up/down spinner); the tee below stands in for it.
   Actual number inputs keep their native up/down since those really do deal with quantities. */
select{
  -webkit-appearance:none; -moz-appearance:none; appearance:none;
  padding-right:30px; cursor:pointer;
}
select::-ms-expand{display:none;}
/* ---------- The tee on every dropdown (owner, 2026-09-24: "all dropdown menus in the interface
   should operate like the bay board one where the tee points up at first and then down when the
   menu is hit") ----------
   It IS the Board Settings tee: the .chevron-icon rule above — the mark, 20px, --chalk-faint,
   point up while the menu is closed, dropping point-down as it opens (teeDrop). A select, the date box
   and the course box can't hold an element of their own (a select used to paint a static tee as
   its background, which could never turn), so 30-render-core.js (enhanceTees) wraps each one after
   every pass: the wrapper takes the control's place and box — full width like the control, and
   capped wherever a rule caps a select below — the control fills it, and the tee sits over its
   right edge, where the old arrow was, never catching a tap. */
.tee-wrap{position:relative; display:inline-flex; width:100%;}
.tee-wrap > select, .tee-wrap > input{flex:1 1 auto; min-width:0;}
.tee-wrap > .tee-mark{position:absolute; top:50%; right:8px; margin-top:-10px; pointer-events:none;}
input.date-field-trigger, input.course-input{padding-right:30px;}
/* One focus ring for the whole app (--tracer, same as :focus-visible and the checkboxes) */
input:focus, select:focus, textarea:focus{outline:2px solid var(--tracer); outline-offset:1px;}
input[type=number]::-webkit-outer-spin-button, input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none; margin:0;}
input[type=number]{-moz-appearance:textfield;}

/* A bigger, themed +/− stepper standing in for the tiny native spinner, on standalone
   quantity fields (season weeks, handicap, bay count) — hole-by-hole and gross-score
   grids stay plain, since those are typed, not clicked up/down. */
.number-stepper{display:inline-flex; align-items:stretch; border:1px solid var(--hairline-light); border-radius:8px; overflow:hidden; background:var(--ink-2); width:100%;}
.number-stepper input[type=number]{border:none !important; border-radius:0 !important; text-align:center; background:transparent !important; min-width:0; flex:1;}
.number-stepper input[type=number]:focus{outline:none;}
.ns-btn{
  background:var(--panel-2); border:none; color:var(--fairway-light); font-family:'Inter',sans-serif; font-size:18px; font-weight:700;
  width:36px; flex:none; cursor:pointer; line-height:1; display:flex; align-items:center; justify-content:center; padding:0;
}
/* Held down, − and + keep counting (30-render-core.js: stepperHoldStart): a long press is a hold, so it never
   selects the sign or opens the phone's callout. */
.ns-btn{-webkit-user-select:none; user-select:none; -webkit-touch-callout:none;}
@media (hover:hover) and (pointer:fine){ .ns-btn:hover{background:var(--fairway); color:#20180F;} }
.ns-btn:active{background:var(--fairway-light);}
.ns-minus{border-right:1px solid var(--hairline-light);}
.ns-plus{border-left:1px solid var(--hairline-light);}
input[type=date]::-webkit-calendar-picker-indicator{filter:invert(76%) sepia(24%) saturate(390%) hue-rotate(354deg) brightness(96%); cursor:pointer;}

/* ---------- Checkboxes: ours, not the browser's (owner, 2026-09-23: "make it so that the
   check boxes when clicking bays match the theme of the interface"). System blue and white was
   the one control left that came from the browser. One rule covers every checkbox in the app —
   the league form's bay chips, the Bay Board's league toggles, the league-players rows — so a
   tick is brass-on-dark everywhere: the same 5px-radius panel-and-hairline box the other
   controls use, filled with --fairway and carrying an inline-SVG mark in the dark ink the
   active tab pill uses for its text. `min-width`/`min-height` are there so a stray inline
   `width:auto` (what the old markup used to keep these off the full-width input rule) can never
   collapse the box. Motion is the app's: colour in .15s, a small press, nothing that delays the
   click. A keyframe pop on :checked would replay on EVERY already-ticked box in a popup,
   because a toggle re-renders the whole form — the press is the feedback instead. ---------- */
input[type=checkbox]{
  -webkit-appearance:none; appearance:none; margin:0; padding:0; flex:none; cursor:pointer;
  width:18px; height:18px; min-width:18px; min-height:18px; vertical-align:-3px;
  background-color:var(--panel-2); border:1px solid var(--hairline-light); border-radius:5px;
  background-repeat:no-repeat; background-position:center; background-size:70%;
  transition:background-color .15s ease, border-color .15s ease, transform .12s ease;
}
@media (hover:hover) and (pointer:fine){ input[type=checkbox]:hover:not(:disabled){border-color:var(--fairway-light);} }
/* A deeper press than the .97 everything else uses: on an 18px box .97 is half a pixel. */
input[type=checkbox]:active:not(:disabled){transform:scale(.92);}
input[type=checkbox]:checked{
  background-color:var(--fairway); border-color:var(--fairway);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M4.2 10.6L8.2 14.6L15.8 5.9' fill='none' stroke='%2320180F' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* A tick sitting ON a gold chip (a selected bay, a showing league) would be gold on gold —
   there the box inverts: dark ink with a gold mark, which is the chip's own colour pair. */
.chip-on input[type=checkbox]:checked{
  background-color:#20180F; border-color:#20180F;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M4.2 10.6L8.2 14.6L15.8 5.9' fill='none' stroke='%23DBB98A' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
input[type=checkbox]:focus{outline:none;}
input[type=checkbox]:focus-visible{outline:2px solid var(--tracer); outline-offset:2px;}
input[type=checkbox]:disabled{opacity:.45; cursor:not-allowed;}

/* Our own themed calendar, replacing the tiny native date popup. The box opens a dropdown like
   every select, so it carries the same tee (.tee-wrap, above) where its calendar mark used to be.
   The date reads in the mono face at 15px, like the app's other dates. `input.` so it outranks the
   general text-box rule, which silently won before (the box was plain 14px Inter). The box keeps
   the other fields' padding — its text starts on their left edge, the tee's 30px on the right —
   with a pixel less above and below for the mono face's taller line, so it stays as tall as the
   selects and boxes beside it (the 11px 14px this rule once asked for made it 43.5px against 37). */
input.date-field-trigger{
  cursor:pointer; font-size:15px; font-family:'IBM Plex Mono',monospace; padding-top:8px; padding-bottom:8px;
}
.date-field-trigger:focus{outline:2px solid var(--tracer); outline-offset:1px;}
.date-picker-popup{
  background:var(--panel); border:1px solid var(--fairway-light); border-radius:12px; box-shadow:var(--shadow);
  padding:14px; width:290px; z-index:1000;
}
.dp-header{display:flex; align-items:center; justify-content:space-between; margin-bottom:10px;}
.dp-month-label{font-family:'Baloo 2',sans-serif; font-size:16px; color:var(--chalk);}
.dp-nav{
  background:var(--panel-2); border:1px solid var(--hairline-light); color:var(--fairway-light); cursor:pointer;
  width:28px; height:28px; border-radius:7px; font-size:16px; line-height:1; font-family:'Inter',sans-serif;
}
@media (hover:hover) and (pointer:fine){ .dp-nav:hover{border-color:var(--fairway-light);} }
.dp-dow-row{display:grid; grid-template-columns:repeat(7,1fr); margin-bottom:4px;}
.dp-dow{text-align:center; font-family:'Inter',sans-serif; font-size:12px; font-weight:700; color:var(--chalk-faint); text-transform:uppercase; padding:4px 0;}
.dp-grid{display:grid; grid-template-columns:repeat(7,1fr); gap:2px;}
.dp-cell{aspect-ratio:1; display:flex; align-items:center; justify-content:center;}
.dp-empty{background:none; border:none;}
.dp-day{
  background:none; border:none; border-radius:8px; color:var(--chalk); font-family:'IBM Plex Mono',monospace;
  font-size:13.5px; cursor:pointer;
}
@media (hover:hover) and (pointer:fine){ .dp-day:hover{background:var(--panel-2);} }
.dp-today{color:var(--tracer); font-weight:700;}
.dp-selected{background:var(--fairway) !important; color:#20180F !important; font-weight:700;}
.dp-footer{display:flex; justify-content:space-between; gap:8px; margin-top:10px; padding-top:10px; border-top:1px solid var(--hairline);}
.dp-today-btn, .dp-clear-btn{
  background:none; border:none; color:var(--fairway-light); font-family:'Inter',sans-serif; font-size:12px; font-weight:600; cursor:pointer; padding:4px 6px;
}
.dp-clear-btn{color:var(--chalk-faint);}
@media (hover:hover) and (pointer:fine){ .dp-today-btn:hover, .dp-clear-btn:hover{text-decoration:underline;} }

/* Custom dropdown list for every <select> in the app — same panel/gold theme, same 12px corner,
   border and shadow as the calendar and the course list, so the three read as one control. */
.select-dropdown-popup{
  background:var(--panel); border:1px solid var(--fairway-light); border-radius:12px; box-shadow:var(--shadow);
  padding:6px; overflow-y:auto;
}
.select-option-row{
  padding:8px 12px; border-radius:7px; font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk); cursor:pointer;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
@media (hover:hover) and (pointer:fine){ .select-option-row:hover{background:var(--panel-2);} }
.select-option-row.selected{background:var(--fairway); color:#20180F; font-weight:600;}
.select-option-row.disabled{color:var(--chalk-faint); cursor:not-allowed; opacity:.5;}

/* Season schedule preview — a small "tear-off calendar" chip per week, easier to scan than a table */
.sched-preview-list{max-height:280px; overflow-y:auto; display:flex; flex-direction:column; gap:7px; margin-top:12px; padding-right:2px;}
.sched-preview-row{display:flex; align-items:center; gap:12px; background:var(--panel); border:1px solid var(--hairline-light); border-radius:10px; padding:8px 12px;}
.sched-preview-row.is-playoff{border-color:var(--tracer);}
.sched-date-chip{flex:none; width:44px; text-align:center; background:var(--ink-2); border-radius:7px; padding:5px 0; border:1px solid var(--hairline-light);}
.sdc-month{font-family:'Inter',sans-serif; font-size:12px; font-weight:700; letter-spacing:.06em; color:var(--brass); text-transform:uppercase;}
.sdc-day{font-family:'Baloo 2',sans-serif; font-size:18px; color:var(--chalk); line-height:1.15;}

/* ---------- Dashboard: Upcoming This Week, one line per league night (owner, 2026-09-25) ----------
   30-render-core.js: renderUpcomingSchedule. One panel, a line per league-week, soonest first: the day,
   the league and its week, the time, how many teams. On a desk the columns line up down the list (the
   day and the teams keep their width, the time is as wide as it is, the league takes the rest) and a
   line never wraps: a name too long for the room ends in "…". A phone takes two lines a night (the
   560px block). The cards that listed every team (and their lineups) are gone. */
.upc-list{background:var(--panel); border:1px solid var(--hairline-light); border-radius:var(--radius); padding:2px 20px;}
.upc-row{display:grid; grid-template-columns:7.4em minmax(0,1fr) auto minmax(5.6em,auto); column-gap:18px; align-items:baseline; padding:12px 0;
  font-family:'IBM Plex Mono',monospace; font-size:12.5px; color:var(--chalk-dim);}
.upc-row + .upc-row{border-top:1px solid var(--hairline);}
.upc-date{color:var(--brass); font-weight:600; white-space:nowrap;}
.upc-name{min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-family:'Inter',sans-serif; font-size:13.5px;}
.upc-league{font-family:'Baloo 2',sans-serif; font-size:17px; color:var(--chalk);}
.upc-time{white-space:nowrap; text-align:right;}
.upc-teams{white-space:nowrap; text-align:right; color:var(--chalk-faint);}
/* a player's leagues in their rounds popup (the masthead's finder: modalPlayerRounds, 70-modals.js) */
.upcoming-team-row{display:flex; align-items:center; gap:12px; padding:9px 0; flex-wrap:wrap;}
.upcoming-team-row:not(:last-child){border-bottom:1px solid var(--hairline);}
.upcoming-team-name{font-family:'Inter',sans-serif; font-weight:700; font-size:16px; color:var(--chalk); flex:none;}
.upcoming-team-players{font-family:'Inter',sans-serif; font-size:14px; color:var(--chalk-dim);}

/* ---------- Dashboard: Tonight (owner-approved mockup #1, 2026-09-24) ----------
   The Dashboard's first card, under its four number tiles (30-render-core.js: renderTonightCard): tonight's league night,
   or the next one. One block per league (several on one night: .is-compact, no player line); the
   count and its bar; one row per team in bay order with its bay and its score or "Waiting". The
   whole block opens the league. Blocks sit in a flex column so a block leaving or arriving (the
   list motion) folds exactly one gap; the rule between two blocks is the second one's top border. */
.tonight-card{background:var(--panel); border:1px solid var(--hairline-light);
  border-left:3px solid var(--fairway); border-radius:16px; padding:18px 22px 14px; margin-bottom:24px;}
.tonight-card.is-next{border-left-color:var(--brass);}
.tn-top{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; min-height:26px;}
.tn-eyebrow{display:flex; align-items:center; font-family:'Inter',sans-serif; font-weight:600; font-size:12.5px; letter-spacing:.03em; color:var(--tracer);}
.tonight-card.is-next .tn-eyebrow{color:var(--brass);}
.tn-blocks{display:flex; flex-direction:column; gap:14px;}
.tn-block{cursor:pointer; transition:transform .12s ease;}
.tn-block + .tn-block{border-top:1px solid var(--hairline-light); padding-top:14px;}
.tn-head{display:flex; justify-content:space-between; align-items:flex-end; gap:14px; padding-bottom:10px;}
.tn-title{min-width:0;}
.tn-league{font-family:'Baloo 2',sans-serif; font-size:24px; color:var(--chalk); line-height:1.15;}
.tn-meta{font-family:'IBM Plex Mono',monospace; font-size:13px; color:var(--brass); margin-top:4px;}
.tn-bit{white-space:nowrap;}
.tn-count{flex:none; text-align:right;}
.tn-count-n{font-family:'Baloo 2',sans-serif; font-size:32px; font-weight:600; color:var(--tracer); line-height:1;}
.tn-count-n small{font-size:17px; font-weight:500; color:var(--chalk-dim);}
.tn-count-l{font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--chalk-faint); margin-top:5px;}
.tn-count.is-next .tn-count-n{color:var(--chalk);}
.tn-count.is-all .tn-count-l{color:var(--fairway-light);}
.tn-meter{display:grid; grid-auto-flow:column; grid-auto-columns:1fr; gap:3px; margin:0 0 6px;}
.tn-meter span{height:4px; border-radius:2px; background:var(--hairline-light);}
.tn-meter span.on{background:var(--fairway);}
.tn-team{display:grid; grid-template-columns:26px minmax(0,1fr) auto auto; align-items:center; gap:4px 12px; padding:10px 0;}
.tn-team:not(:last-child){border-bottom:1px solid var(--hairline);}
.tn-team-name{font-family:'Inter',sans-serif; font-weight:700; font-size:15.5px; color:var(--chalk);}
.tn-vs{font-weight:600; font-size:12.5px; color:var(--chalk-faint); margin-left:4px;}
.tn-team-players{font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-dim);}
.tn-bay{font-family:'IBM Plex Mono',monospace; font-size:12.5px; font-weight:600; color:var(--brass); white-space:nowrap; text-align:right;}
/* one cell for the chip: a team that reports swaps "Waiting" for its score in place (the list
   motion folds the old chip away under the new one rising in), and the column never changes width */
.tn-status-slot{display:grid; justify-items:end; align-items:center; min-width:78px;}
.tn-status-slot > *{grid-area:1 / 1;}
.tn-status{display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font-family:'Inter',sans-serif; font-size:12px; font-weight:600; padding:4px 9px; border-radius:6px; border:1px solid var(--hairline-light);}
.tn-status.is-in{color:var(--fairway-light); border-color:rgba(219,185,138,.4); font-family:'IBM Plex Mono',monospace; font-weight:700;}
.tn-status.is-wait{color:var(--chalk-faint);}
.tn-ring{width:8px; height:8px; border-radius:50%; border:1.5px solid currentColor; flex:none;}
.tn-foot{display:flex; justify-content:space-between; gap:6px 12px; flex-wrap:wrap; font-size:13px; color:var(--chalk-faint);
  border-top:1px solid var(--hairline); padding-top:10px; margin-top:2px;}
.tn-open{color:var(--chalk-dim); font-weight:600; transition:color .15s ease;}
@media (hover:hover) and (pointer:fine){ .tn-block:hover .tn-open{color:var(--tracer);} }
.tn-empty{color:var(--chalk-faint); font-size:14px; padding:6px 0 4px;}
.tn-block.is-compact .tn-league{font-size:20px;}
.tn-block.is-compact .tn-count-n{font-size:26px;}
.tn-block.is-compact .tn-count-n small{font-size:15px;}
.tn-block.is-compact .tn-team{grid-template-columns:20px minmax(0,1fr) auto auto; padding:7px 0;}
.tn-block.is-compact .tn-team-name{font-size:14.5px;}
/* Opening a league from a block grows the league out of the block (a view transition): the block
   has no fill of its own (the card's panel shows through it), so it takes the panel colour while the
   morph captures it — the growing box is card-shaped, never text floating over the dimmed page. */
html.vt-in .tn-block[data-action]{background:var(--panel);}

/* ---------- League cards say what's happening (owner-approved mockup #2, 2026-09-24) ----------
   50-leagues.js: leagueSeasonStatus. The status line (tonight in light gold), the live dot while
   scores can be entered (the LIVE pill's pulse), and the season bar: one notch per week, played
   brass, tonight bright gold, a playoff week still ahead darker. */
.lc-status{display:flex; align-items:center; gap:8px; font-family:'Inter',sans-serif; font-size:13px; font-weight:600;
  color:var(--chalk-dim); margin:0 0 10px;}
.lc-status.is-tonight{color:var(--tracer);}
.lc-status.is-done{color:var(--chalk-faint); font-weight:500;}
.lc-live-dot{flex:none; width:8px; height:8px; border-radius:50%; background:var(--tracer);
  box-shadow:0 0 0 3px rgba(232,199,154,.16); animation:live-pulse 2s ease-in-out infinite;}
.lc-season{margin:0 0 12px;}
.lc-meter{display:grid; grid-auto-flow:column; grid-auto-columns:1fr; gap:3px;}
.lc-meter span{height:4px; border-radius:2px; background:var(--hairline-light);}
.lc-meter span.played{background:var(--brass);}
.lc-meter span.tonight{background:var(--tracer); box-shadow:0 0 6px rgba(232,199,154,.45);}
.lc-meter span.playoff{background:var(--rough);}
.lc-season-l{font-family:'Inter',sans-serif; font-size:12px; color:var(--chalk-faint); margin-top:6px;}

/* a phone (the mockup's own breakpoint) */
@media (max-width:520px){
  .tonight-card{padding:16px 16px 12px;}
  .tn-league{font-size:21px;}
  .tn-count-n{font-size:28px;}
  /* a phone: the bay over the chip on the right, the team and its players down the left */
  .tn-team{grid-template-columns:26px minmax(0,1fr) auto;}
  .tn-team > .swatch, .tn-team > img{grid-row:1 / span 2;}
  .tn-team-body{grid-row:1 / span 2; grid-column:2;}
  .tn-bay{grid-column:3; grid-row:1;}
  .tn-status-slot{grid-column:3; grid-row:2;}
  .tn-block.is-compact .tn-team{grid-template-columns:20px minmax(0,1fr) auto;}
}
/* The pulse is motion: with Reduce Motion on, every never-ending pulse holds still at its resting look (wizard-feel,
   2026-09-26; owner, feel 10): the live dot, every LIVE pill (the Tonight card's, League Central's) and the loading mark
   (the first open, the load-error and "was updated" screens). The global rule below shortens every animation to 1ms,
   which made these a strobe (the pill's brightness jumping about 20 times a second, the mark's size and brightness too).
   !important: the pulses' own rules come later in this file. */
@media (prefers-reduced-motion: reduce){ .lc-live-dot, .live-pill, .app-loading-logo{animation:none !important;} }
.sched-preview-info{flex:1; min-width:0;}
.sched-preview-label{font-family:'Inter',sans-serif; font-weight:600; font-size:13.5px; color:var(--chalk);}
.sched-preview-time{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint); margin-top:1px;}
.team-score-out{font-family:'Baloo 2',sans-serif; font-size:15px; color:var(--tracer); text-align:right; margin-top:8px;}

/* ---------- Shared row actions ---------- */
.row-actions{display:flex; gap:6px; flex-wrap:wrap;}

/* ---------- Scorecards ---------- */
.scorecard-form{background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); padding:18px; margin-bottom:24px;}
.form-grid{display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:14px;}
.form-grid label{font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--chalk-faint); display:block; margin-bottom:6px;}
/* the New Scorecard form's first row — League, then Date and Holes (or a league's Week and Team) —
   three across (a class, not an inline style, so a phone can re-balance it: the 560px block) */
.form-grid.sc-form-top{grid-template-columns:repeat(3,1fr);}

.ticket{
  background:var(--panel); border:1px solid var(--hairline-light); border-left:3px solid var(--fairway);
  border-radius:var(--radius); padding:18px 20px; margin-bottom:14px;
  display:grid; grid-template-columns:1fr auto; gap:14px; align-items:center;
}
/* A ticket is a card you open, so it answers the pointer like every other card does */
.ticket[data-action]{cursor:pointer;}
@media (hover:hover) and (pointer:fine){ .ticket[data-action]:hover{border-color:var(--fairway-light);} }
.ticket .tk-name{font-family:'Baloo 2',sans-serif; font-size:16.5px;}
.ticket .tk-meta{font-family:'Inter',sans-serif; font-size:12.5px; color:var(--chalk-faint); margin-top:4px;}
.ticket .tk-scores{display:flex; gap:18px; text-align:center;}
.ticket .tk-scores div span{display:block; font-family:'Baloo 2',sans-serif; font-size:24px; color:var(--tracer); line-height:1;}
.ticket .tk-scores div small{font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint);}
/* The detail line is built from pieces (60-scorecards.js: ticketMeta); on a desk screen they are
   the one dotted line they always were — the dot is each piece's ::before. */
.ticket .tk-bit + .tk-bit::before{content:" · ";}
/* A scramble week's team totals as a row of its own (60-scorecards.js: teamTotalsTicket; scramble-totals): its Edit
   and Delete side by side where a card's Delete is, and with skins on the calm line saying it isn't in skins. */
.ticket .tk-acts{display:flex; gap:8px; align-self:center;}
.ticket .tk-note{margin-top:6px; font-family:'Inter',sans-serif; font-size:12.5px; color:var(--fairway-light);}
/* The team's two totals, side by side (+ New Scorecard's "Team totals only": draftTotalsBoxes; their Edit popup); on a
   phone they shrink rather than wrap, as Week-by-Week's Team gross and Team net do */
.sc-totals{display:flex; gap:12px;}
.sc-totals .field{flex:0 1 190px; min-width:0;}

/* ---------- The paper card (60-scorecards.js: paperCard) ----------
   Holes nine across under a row of hole numbers, one row per score line, the nine's OUT / IN and
   the round's TOTAL in columns of their own — the staff form, Edit scores and a round's detail.
   One grid holds both nines so the columns line up. The wrapper scrolls sideways only if the card
   is wider than the space it has (a phone); the page itself never does. */
.pc-wrap{margin:8px 0 6px; overflow-x:auto; overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch;}
.pc{display:grid; gap:5px; align-items:center; --pc-cell:36px; --pc-sum:46px; --pc-tot:52px;}
.pc-18{grid-template-columns:max-content repeat(9, minmax(var(--pc-cell), 1fr)) minmax(var(--pc-sum), 1fr) minmax(var(--pc-tot), 1fr);}
.pc-9{grid-template-columns:max-content repeat(9, minmax(var(--pc-cell), 1fr)) minmax(var(--pc-tot), 1fr);}
.pc-read{--pc-cell:20px; --pc-sum:30px; --pc-tot:34px; gap:4px 3px;}
.pc-read.pc-18{grid-template-columns:repeat(9, minmax(var(--pc-cell), 1fr)) minmax(var(--pc-sum), 1fr) minmax(var(--pc-tot), 1fr);}
.pc-read.pc-9{grid-template-columns:repeat(9, minmax(var(--pc-cell), 1fr)) minmax(var(--pc-tot), 1fr);}
.pc-lab{font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-dim); padding-right:8px; white-space:nowrap;}
.pc-hn, .pc-h{font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint); text-align:center; white-space:nowrap;}
.pc-h{font-size:12px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;}
.pc-lab.pc-h{text-align:left;}
.pc-b0{margin-top:8px;}
.pc-c{min-width:0;}
.pc-c input{text-align:center; padding:8px 2px; height:38px; min-width:0;}
.pc-sum:not(.pc-h), .pc-tot:not(.pc-h):not(.pc-blank){
  font-family:'IBM Plex Mono',monospace; font-size:14px; font-weight:600; color:var(--chalk); text-align:center;
  background:var(--panel-2); border:1px solid var(--hairline-light); border-radius:7px; height:38px; line-height:36px; padding:0 4px;
}
.pc-tot:not(.pc-h):not(.pc-blank){color:var(--tracer); font-weight:700;}
.pc-v{font-family:'IBM Plex Mono',monospace; font-size:14px; color:var(--chalk); text-align:center; background:var(--ink-2); border:1px solid var(--hairline-light); border-radius:7px; height:34px; line-height:32px;}
.pc-v.is-counted{color:var(--tracer); border-color:var(--tracer); font-weight:700;}
.pc-read .pc-sum:not(.pc-h), .pc-read .pc-tot:not(.pc-h):not(.pc-blank){height:34px; line-height:32px; font-size:13.5px; padding:0 2px;}

/* ---------- The player's Scoring tab (50-leagues.js; scoring-tab, 2026-09-26) ----------
   look 4 (owner: "yes to the match board, teams players yes but keep the highlight"): each player's holes are the
   read-only paper card above (renderPlayerHoleCard), and the ball that counted toward the team's best ball keeps the
   sideways strip's highlight, filled gold with gold bold figures (a round's own popup marks it with a gold edge
   instead). A finished match's holes are a board a nine to a row, the front nine over the back nine (matchBoardHtml):
   the strip's three lines per hole in nine equal columns, so nothing scrolls sideways at any width. The desk's
   Week-by-Week Scoring draws the same cards and board with these same rules (desk-scoring, 2026-09-26).
   see 3 ("yes to first proposed"): a match-play week's results on a calm card under the week picker
   (renderWeekResults): the gold edge, never red; its heading opens with its icon, no logo; nothing under 12px. */
.play-holes .pc-v.is-counted{background:rgba(232,199,154,.18); border-color:transparent;}
.match-board{display:flex; flex-direction:column; gap:8px;}
.mb-nine{display:grid; grid-template-columns:repeat(9, minmax(0, 1fr)); gap:3px;}
.mb-hole{min-width:0; text-align:center;}
.mb-hn, .mb-vals{font-size:12px; color:var(--chalk-faint);}
.mb-mark{font-family:'IBM Plex Mono',monospace; font-size:12px; font-weight:700; color:var(--chalk-faint);}
.mb-mark.won-a{color:var(--fairway-light);}
.mb-mark.won-b{color:var(--tracer);}
.week-results{position:relative; z-index:1; background:var(--panel); border:1px solid var(--hairline-light); border-left:3px solid var(--tracer);
  border-radius:var(--radius); padding:13px 14px 11px; margin:0 0 14px;}
.wr-title{display:flex; align-items:center; font-family:'Baloo 2',sans-serif; font-size:19px; font-weight:600; line-height:1.2; color:var(--chalk);}
.wr-title .sec-ico{flex:none; margin-right:8px; color:var(--brass);}
.wr-sub{font-size:12.5px; color:var(--chalk-faint); margin:2px 0 6px;}
.wr-line{display:flex; flex-wrap:wrap; align-items:baseline; column-gap:10px; row-gap:2px; padding:8px 0; border-top:1px solid var(--hairline); font-size:14px; color:var(--chalk-dim);}
.wr-main{flex:1 1 auto; min-width:0;}
.wr-line b, .wr-low b{color:var(--chalk); font-weight:600;}
.wr-pts{margin-left:auto; font-family:'IBM Plex Mono',monospace; font-size:12.5px; color:var(--chalk-faint); text-align:right;}
.wr-pts span{white-space:nowrap;}
.wr-low{padding-top:8px; border-top:1px solid var(--hairline); font-size:13px; color:var(--chalk-faint); line-height:1.6;}
.wr-low > span{display:inline-block;}

/* ---------- Modal ---------- */
/* The dimmed page round a popup is a tap that closes it (80-events.js), on an iPhone too: it says so the way Safari
   listens for (a pointer), with no grey flash over the screen as it is tapped; and a finger dragged on it never
   scrolls the page behind (player-popup). */
.modal-overlay{
  position:fixed; inset:0; display:flex; align-items:center;
  justify-content:center; z-index:100; padding:20px;
  cursor:pointer; -webkit-tap-highlight-color:transparent; touch-action:none;
}
/* The dark backdrop is a layer of its own, so it can fade in while the box (which grows out of
   the button you tapped) is fully visible from its first frame. It is the dim alone, in step with the box
   (the Motion policy block, below): its 2px blur is gone (player-popup, 2026-09-28; the owner, on his iPhone:
   "framey and laggy"), since a blur over the whole screen is costly on an iPhone and barely showed. */
.modal-overlay::before{content:""; position:absolute; inset:0; background:rgba(10,10,8,.72);}
/* The box itself is not the tap that closes (its own cursor and tap flash back); its scroll never carries on into
   the page behind it (overscroll-behavior). --x-pad-top / --x-pad-side say what its padding is, for its × below.
   Its height (logos-popup, 2026-10-03): never more than the overlay it sits in (100%: the overlay is fixed over the
   screen the browser shows, on an iPhone the part between Safari's bars), else 86vh as ever. On an iPhone `vh` is the
   LARGE viewport, Safari's bars put away (about 810px on the owner's 402x874 phone, whose screen between the bars is
   660), so 86vh made a 697px box in a 660px screen: its top and its × went under Safari's top bar and its foot under
   the bottom one. On a desk 86vh is always the smaller (100% is the window less 40px), so nothing there moves; a
   phone's own size and margins are in the 560px block. */
.modal{
  position:relative; background:var(--panel); border:1px solid var(--hairline-light); border-radius:14px; padding:24px; width:100%;
  max-width:480px; max-height:min(86vh, 100%); overflow-y:auto; box-shadow:var(--shadow);
  cursor:auto; -webkit-tap-highlight-color:initial; overscroll-behavior:contain; --x-pad-top:24px; --x-pad-side:24px;
}
.modal.modal-wide{max-width:660px;}
.modal h3{font-family:'Baloo 2',sans-serif; margin:0 0 5px; font-size:20px;}
.modal .modal-sub{color:var(--chalk-faint); font-size:13.5px; margin-bottom:18px;}
/* The way out of a popup that only shows something (player-popup; owner, 2026-09-28: "there is no easy way to get out
   of that page when it pops up on the phone"; 70-modals.js: MODAL_CLOSE_X): a round × at its top right that stays in
   view while the popup's content scrolls under it. Its pin is sticky exactly where it starts, at the top of the box's
   content (a sticky box's `top` counts from inside its scroller's padding, in Chromium and WebKit alike), holding a 44px
   tap 4px in from the box's corner whatever its padding, round a 32px disc in the popup's own grey with a brass ×. The
   title keeps clear of it (the pin never lets the title's margin through: flow-root). */
.modal-x-pin{position:sticky; top:0; height:0; display:flow-root; z-index:3;}
.modal-x{
  position:absolute; top:calc(4px - var(--x-pad-top)); right:calc(4px - var(--x-pad-side)); width:44px; height:44px; margin:0; padding:0; border:0; border-radius:50%;
  background:none; display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--brass);
  -webkit-tap-highlight-color:transparent;
}
.modal-x-disc{
  width:32px; height:32px; border-radius:50%; background:var(--panel-2); border:1px solid var(--hairline-light);
  display:flex; align-items:center; justify-content:center; box-shadow:0 2px 8px rgba(0,0,0,.35);
}
/* …and the popup's GRAB ZONE, where a finger pushes it up and away (30-render-core.js: pushGrabZone): the title, its
   box reaching up over the band the grab bar sits in, the line under it, and the ×. The browser never scrolls from
   there (touch-action:none), so the push's listeners are all passive and no scroll in a popup waits for them. */
.modal .modal-x-pin + h3{margin-top:calc(0px - var(--x-pad-top)); padding-top:var(--x-pad-top); padding-right:calc(46px - var(--x-pad-side));}
.modal-x-pin, .modal-x, .modal-x-pin + h3, .modal-x-pin + h3 + .modal-sub{touch-action:none; -webkit-user-select:none; user-select:none;}
/* A pushable popup too short to scroll is grab zone all over (popupStillSync puts on `is-still`, live only); a paper
   card's own strip still slides sideways there. */
.modal.is-still{touch-action:none;}
.modal.is-still .pc-wrap{touch-action:pan-x;}
.field{margin-bottom:14px;}
.field label{font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--chalk-faint); display:block; margin-bottom:6px;}
.field-row{display:flex; gap:10px;}
.field-row .field{flex:1;}
/* The league form's Holes · Standings mode · Skins line (form-trim): Holes is two digits, so it gives its room to the two
   words beside it, which then show whole at 1024 ("Cumulative strokes", "On · gross & net") */
.league-holes-row > .field:nth-child(1){flex:.6;}
.league-holes-row > .field:nth-child(2){flex:1.25;}
.league-holes-row > .field:nth-child(3){flex:1.1;}
.modal-actions{display:flex; justify-content:flex-end; gap:8px; margin-top:18px; flex-wrap:wrap;}
.tag-input-hint{font-size:12.5px; color:var(--chalk-faint); margin-top:5px;}

/* ---------- The Owner's Manual (owners-manual, 2026-10-05; the owner: "create owners manual in the settings tab") ----------
   Settings' last section (.manual-entry: its button and a line), and the manual it opens inside Settings (58-manual.js):
   the topics in one card, each title a full-width row whose words unfold beneath it (the staff side's disclosure fold,
   .reveal, the tee turning as Board Settings' does), the words in a column a person can read on a desk. No hover tint:
   the press, the pointer and the focus ring answer. On paper, .manual-paper (the print block) carries every topic.
   These come before the 560px block: its phone rules for them, of the same weight, win by coming later. */
.manual-entry{display:flex; align-items:center; gap:14px; flex-wrap:wrap;}
.manual{max-width:780px;}
.manual-topics{background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); overflow:hidden;}
.manual-item + .manual-item{border-top:1px solid var(--hairline);}
.manual-toggle{
  width:100%; display:flex; align-items:center; justify-content:space-between; gap:12px;
  background:none; border:0; cursor:pointer; padding:14px 16px; text-align:left;
  font-family:'Baloo 2',sans-serif; font-size:17px; font-weight:600; line-height:1.3; color:var(--chalk);
}
.manual-toggle:focus-visible{outline-offset:-2px;}   /* the ring inside the card's rounded edge, which clips */
.manual-toggle-words{display:flex; align-items:center; gap:10px; min-width:0;}
.manual-toggle .sec-ico{flex:none; color:var(--brass);}
.manual-text{padding:0 16px 16px 45px;}   /* the words line up with the title's, past its icon */
.manual-body{font-family:'Inter',sans-serif; font-size:14.5px; line-height:1.6; color:var(--chalk-dim);}
.manual-body p{margin:0 0 10px;}
.manual-body h4{font-family:'Baloo 2',sans-serif; font-size:15.5px; font-weight:600; line-height:1.3; color:var(--chalk); margin:14px 0 6px;}
.manual-body ul, .manual-body ol{margin:0 0 10px; padding-left:22px;}
.manual-body li{margin:0 0 6px; padding-left:2px;}
.manual-body li::marker{color:var(--brass);}
.manual-body > :first-child{margin-top:0;}
.manual-body > :last-child{margin-bottom:0;}
.manual-ui{font-weight:600; color:var(--chalk);}   /* a name exactly as the app shows it */
.manual-mark{display:inline-block; vertical-align:-3px; margin:0 2px; color:var(--fairway-light);}   /* the app's own small marks */
.manual-foot{display:flex; justify-content:center; margin-top:18px;}
.manual-paper{display:none;}

/* ---------- Longest drive & closest to the pin (drive-pin, 2026-10-06; 37-drive-pin.js) ----------
   THE ROUND'S SHEET (a popup, .modal-contest: it grows out of the tap and goes back into it as every popup does; nothing
   moves of its own): each player on the card, a box per part of the distance (yards; feet and inches) and Yes / No, then
   the number pad and Done, every target a thumb's (52px, the hole bar's size). The box the pad types into has the gold
   ring; an answer picked is the gold fill. THE BUTTON on a contest hole's number line opens it again: it sits on that
   line (absolutely, on the right), so it takes no room and the hole still fits one screen (round-scroll). THE REVIEW's
   lines, a tap to the sheet. THE WEEK on the Scoring tab (its top two, the desk's every entry), THE SEASON's list, the
   Tonight card's lines and the desk's boxes on the card form and in Edit scores. Theme colours only, nothing under 12px. */
.modal.modal-contest{max-width:420px;}
.modal-contest .modal-sub{margin-bottom:8px;}
.contest-sec + .contest-sec{margin-top:12px; padding-top:10px; border-top:1px solid var(--hairline-light);}
.contest-sec-title{display:flex; align-items:center; gap:7px; font-family:'Baloo 2',sans-serif; font-size:17px; font-weight:600; color:var(--tracer);}
.contest-row{padding:8px 0;}
.contest-row + .contest-row{border-top:1px solid var(--hairline);}
.contest-row-head{display:flex; align-items:baseline; gap:10px; margin-bottom:6px;}
.contest-name{flex:1 1 auto; min-width:0; font-family:'Inter',sans-serif; font-size:15px; font-weight:700; color:var(--chalk); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.contest-line{display:flex; align-items:center; gap:10px;}
.contest-boxes{flex:1 1 auto; min-width:0; display:flex; gap:8px;}
.btn.contest-box{flex:1 1 0; min-width:0; min-height:52px; padding:0 8px; justify-content:center; align-items:baseline; gap:4px; border-radius:12px; background:var(--ink-2);}
.btn.contest-box.is-active{border-color:var(--tracer); box-shadow:0 0 0 1px var(--tracer);}
.contest-num{font-family:'IBM Plex Mono',monospace; font-size:24px; font-weight:600; line-height:50px; color:var(--chalk);}
.contest-box.is-empty .contest-num{color:var(--chalk-faint);}
.contest-unit{font-family:'Inter',sans-serif; font-size:13px; font-weight:600; color:var(--chalk-faint);}
.contest-yn{flex:none; display:flex; gap:6px;}
.contest-q{flex:none; width:118px; font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint); text-align:center; white-space:nowrap;}
.btn.contest-ans{width:56px; min-height:52px; padding:0; justify-content:center; border-radius:12px; font-size:16px;}
.btn.contest-ans.is-on{background:var(--fairway); border-color:var(--fairway); color:#20180F;}
/* The pad and Done are the sheet's foot: when the players don't all fit (a scramble's three or four on a phone) they
   stay in reach while the rows scroll above them, over the box's own padding (as a pushable popup's title sticks at
   its top); a sheet that fits is as before. */
.contest-foot{position:sticky; bottom:calc(0px - var(--x-pad-top)); z-index:2; margin:0 calc(0px - var(--x-pad-side)) calc(0px - var(--x-pad-top)); padding:12px var(--x-pad-side) var(--x-pad-top); background:var(--panel);}
.contest-pad{display:grid; grid-template-columns:repeat(3, minmax(0, 1fr)); gap:8px;}
.btn.contest-key{min-height:52px; justify-content:center; border-radius:12px; font-family:'IBM Plex Mono',monospace; font-size:24px; font-weight:600;}
.btn.contest-key.is-word{font-family:'Inter',sans-serif; font-size:15px;}
.btn.contest-key svg{margin:0;}
.modal-contest .modal-actions{margin-top:12px;}
.btn.contest-done{width:100%; min-height:52px; justify-content:center; font-size:16px; border-radius:12px;}
.wizard-hole-num.is-contest-round{position:relative;}
.wizard-contest-slot{position:absolute; top:0; bottom:0; right:0; display:flex; align-items:center;}
.btn.wizard-contest-pill{min-height:40px; padding:0 12px; border-radius:20px; gap:5px; border-color:var(--brass); background:var(--panel);
  font-family:'Inter',sans-serif; font-size:13px; font-weight:700; line-height:1; color:var(--tracer);}
.btn.wizard-contest-pill svg{color:var(--tracer);}
.wizard-review-contests{display:flex; flex-direction:column; gap:10px; margin:0 0 14px;}
.btn.wizard-review-contest{display:flex; flex-direction:column; align-items:stretch; gap:6px; width:100%; padding:12px; border-radius:12px; background:var(--panel); text-align:left; font-weight:500;}
.wizard-contest-title{display:flex; align-items:center; gap:7px; font-family:'Baloo 2',sans-serif; font-size:18px; font-weight:600; line-height:1.2; color:var(--tracer);}
.btn.wizard-review-contest .wizard-contest-title svg{color:var(--tracer);}
.wizard-contest-q{margin-left:auto; font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint); white-space:nowrap;}
.wizard-contest-line{display:grid; grid-template-columns:minmax(0, 1fr) auto 40px; align-items:baseline; gap:10px; font-family:'Inter',sans-serif; font-size:14px; color:var(--chalk-dim);}
.wizard-contest-line b{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600; color:var(--chalk);}
.wizard-contest-val{font-family:'IBM Plex Mono',monospace; font-weight:600; color:var(--tracer); white-space:nowrap;}
.wizard-contest-ans{text-align:center; font-weight:600; color:var(--chalk);}
.contest-card, .contest-season{background:var(--panel); border:1px solid var(--hairline-light); border-left:3px solid var(--tracer); border-radius:var(--radius); padding:12px 14px; margin:0 0 12px;}
.contest-season{margin-top:14px; border-left-color:var(--brass);}
.contest-part + .contest-part{margin-top:10px; padding-top:10px; border-top:1px solid var(--hairline);}
.contest-head{display:flex; align-items:center; gap:7px; font-family:'Baloo 2',sans-serif; font-size:18px; font-weight:600; line-height:1.2; color:var(--chalk);}
.contest-head svg{color:var(--brass);}
.contest-head-hole{font-family:'IBM Plex Mono',monospace; font-size:12.5px; font-weight:600; color:var(--brass); margin-left:2px;}
.contest-top{margin-top:4px; font-family:'Inter',sans-serif; font-size:14px; line-height:1.6; color:var(--chalk-dim);}
.contest-place{display:inline-block;}
.contest-top .contest-place{display:block;}   /* the week's card: a place a line (the season's list and Tonight run them on, dotted) */
.contest-top .contest-sep{display:none;}
.contest-place b{font-weight:600; color:var(--chalk);}
.contest-place-n{font-family:'IBM Plex Mono',monospace; font-weight:600; color:var(--brass);}
.contest-val{font-family:'IBM Plex Mono',monospace; font-weight:600; color:var(--tracer);}
.contest-sep{color:var(--chalk-faint);}
.contest-none, .contest-none-inline{font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-faint);}
.contest-none{margin-top:4px;}
.contest-list{margin-top:8px; border:1px solid var(--hairline); border-radius:10px; overflow:hidden;}
.contest-row-l{display:grid; grid-template-columns:30px minmax(0, 1fr) 96px 104px; align-items:center; gap:8px; padding:7px 10px; font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-dim);}
.contest-row-l + .contest-row-l{border-top:1px solid var(--hairline);}
.contest-row-l.is-head{background:var(--panel-2); font-size:12px; font-weight:600; color:var(--chalk-faint);}
.contest-row-l.is-running .contest-l-place{color:var(--brass);}
.contest-l-place{font-family:'IBM Plex Mono',monospace; font-weight:600; text-align:center;}
.contest-l-who{min-width:0; display:flex; flex-direction:column;}
.contest-l-who b{font-weight:600; color:var(--chalk); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.contest-l-team{font-size:12px; color:var(--chalk-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.contest-l-val{font-family:'IBM Plex Mono',monospace; font-weight:600; color:var(--chalk); white-space:nowrap;}
.contest-row-l.is-head .contest-l-val{font-family:'Inter',sans-serif;}
.contest-l-ans{white-space:nowrap;}
.contest-season-title{display:flex; align-items:center; gap:8px; font-family:'Baloo 2',sans-serif; font-size:18px; font-weight:600; line-height:1.2; color:var(--chalk);}
.contest-season-title svg{color:var(--brass);}
.contest-season-sub{margin-top:2px; font-family:'Inter',sans-serif; font-size:13px; color:var(--chalk-faint);}
.contest-season-week{padding:9px 0 2px;}
.contest-season-week + .contest-season-week{border-top:1px solid var(--hairline);}
.contest-season-head{font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-faint); margin-bottom:2px;}
.contest-season-head b{color:var(--chalk); font-weight:700;}
.contest-season-line{display:grid; grid-template-columns:150px minmax(0, 1fr); column-gap:10px; font-family:'Inter',sans-serif; font-size:14px; line-height:1.6; color:var(--chalk-dim);}
.contest-season-what{font-weight:600; color:var(--chalk-faint);}
.contest-season-line + .contest-season-line{margin-top:4px;}
.contest-season-top .contest-place{display:block;}   /* a place a line, as the week's card */
.contest-season-top .contest-sep{display:none;}
.tn-contests{display:flex; flex-direction:column; gap:4px; padding:8px 0 10px; border-top:1px solid var(--hairline);}
.tn-contest{display:flex; flex-wrap:wrap; align-items:baseline; column-gap:8px; font-family:'Inter',sans-serif; font-size:13.5px; line-height:1.5; color:var(--chalk-dim);}
.tn-contest svg{align-self:center; color:var(--brass);}
.tn-contest-what{font-weight:600; color:var(--chalk-faint); white-space:nowrap;}
.tn-block[data-action]:has(.contest-nudge .btn:active){transform:none;}
.contest-desk{margin-top:16px;}
.contest-desk-part + .contest-desk-part{margin-top:12px;}
.contest-desk-row{display:flex; flex-wrap:wrap; align-items:flex-end; gap:8px 12px; padding:8px 0; border-bottom:1px solid var(--hairline);}
/* a player's row on one line in the desk's popups (Team totals Edit, Edit scores; desk-looks): the name starts from 110px
   and the three-digit boxes are 64px, so closest to the pin's Feet, Inches and "On the green?" fit beside the name in a
   430px popup (it was 150 + 76 + 76: "On the green?" went onto a line of its own) */
.contest-desk-name{flex:1 1 110px; min-width:0; font-family:'Inter',sans-serif; font-size:14px; font-weight:600; color:var(--chalk); padding-bottom:9px;}
.contest-desk-box{display:flex; flex-direction:column; gap:4px; font-family:'Inter',sans-serif; font-size:12px; font-weight:600; color:var(--chalk-faint);}
.contest-desk-box input{width:64px;}
.contest-desk-box select{min-width:128px;}
.week-contests{margin-bottom:6px;}
.week-contests-hint{margin:-2px 0 14px;}

@media (max-width:760px){
  .dash-grid{grid-template-columns:repeat(2,1fr);}
  .form-grid{grid-template-columns:repeat(2,1fr);}
  .flip-row{grid-template-columns:30px 1fr 70px 70px;}
  /* The masthead is one compact brand bar here — logo and name on the left, the player finder
     and Player Access on the right — instead of the stack that took over half a phone screen
     (owner, 2026-09-24). Everything that measures it (contentTopOffset, the sticky tab row, the
     `masthead` view-transition group) reads its real box, so none of that needed to change. */
  .masthead{flex-wrap:nowrap; align-items:center; gap:10px; padding:10px 0;}
  .brand-block{flex:none; gap:10px;}
  .logo-lockup{padding-right:10px;}
  /* 34px tall here (46 wide; 30 tall and 40 wide at 560px and under): a small logo, so the mark without its
     lettering (logos-popup); the desk's 104px masthead keeps the full logo */
  .logo-img{height:34px; -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);}
  .brand-text p{display:none;}
  .brand-text h1{font-size:22px; line-height:1.15; white-space:nowrap;}
  .masthead-meta{flex:1 1 auto; min-width:0; display:flex; align-items:center; justify-content:flex-end; gap:6px; line-height:1.2;}
  .masthead-meta .mh-facts{display:none;}
  .player-lookup{margin-top:0; flex:0 1 220px; min-width:0;}
  #player-lookup-search{width:100%;}
  .mh-access{margin-top:0; flex:none;}
  /* iPhone Safari zooms the page in when a box with type under 16px is tapped, and stays zoomed.
     Here every text, number, date and search box is 16px (the hole boxes already were at phone
     width) — and so is every select, so a row that mixes the two keeps one height. Controls sized
     on purpose keep their own rules (the wizard's score steppers, Board Settings, the week strip).
     The finder keeps the bar's height (a little less padding for the taller line); at phone width
     its hint keeps its old size (the 560px block), which is all the bar has room for. */
  input[type=text], input[type=number], input[type=date], input[type=search], select{font-size:16px;}
  #player-lookup-search, #player-link-field{font-size:16px;}
  #player-lookup-search{padding-top:5px !important; padding-bottom:5px !important;}
}
/* The phone tab bar's skin (player-tabs-big) exists only while a phone's league page shows; anywhere else it is never drawn. */
.dock-skin{display:none;}
@media (max-width:560px){
  .logo-img{height:30px;}
  /* 19px is the most the one-line bar holds at 375: every pixel more comes out of the player
     finder, whose "Find a player…" already fills it exactly. */
  .brand-text h1{font-size:19px;}
  /* Player Access is its phone icon alone (it keeps its name for screen readers), a full 36px target */
  .masthead-meta .mh-label{display:none;}
  .mh-access .btn{width:36px; height:36px; padding:0; justify-content:center;}
  .mh-access .btn svg{width:17px; height:17px; margin-right:0 !important;}
  #player-lookup-search{height:36px; padding:7px 6px 7px 28px !important;}
  /* What is typed in the finder is 16px (760px block) so tapping it never zooms; its hint keeps the
     12.5px it had — at 16px "Find a player…" would be cut off in the room the bar leaves it. */
  #player-lookup-search::placeholder{font-size:12.5px;}
  /* With the Settings gear beside Player Access (dashboard-tidy, 2026-09-25) the finder is 80px wide at
     375: its hint ends in "…" ("Find …") instead of being cut off mid-letter. */
  #player-lookup-search{text-overflow:ellipsis;}

  /* Scorecard tickets stack: who, then league · team · week, the date, the course, and the
     scores in a row of their own with Delete at the end — instead of a narrow text column
     wrapping one word per line beside the scores. Same markup; the pieces are reordered. */
  .ticket{grid-template-columns:minmax(0,1fr); gap:12px; padding:14px 16px;}
  .ticket .tk-meta{display:flex; flex-wrap:wrap; row-gap:2px; margin-top:6px;}
  .ticket .tk-bit + .tk-bit::before{content:none;}
  .ticket .tk-meta .tk-link{order:1; flex-basis:100%; color:var(--chalk-dim); font-size:13px;}
  .ticket .tk-fmt{white-space:nowrap;}
  .ticket .tk-meta .tk-date{order:2;}
  .ticket .tk-meta .tk-holes{order:3;}
  .ticket .tk-meta .tk-holes::before{content:"\00a0·\00a0";}
  .ticket .tk-meta .tk-course{order:4; flex-basis:100%;}
  .ticket .tk-meta .tk-extra{order:5; flex-basis:100%;}
  .ticket .tk-scores{align-items:center; gap:22px; padding-top:12px; border-top:1px solid var(--hairline);}
  .ticket .tk-scores > div:last-child{margin-left:auto;}

  /* The paper card on a phone: input columns stay 36px (a real tap target) and the card scrolls
     sideways inside its own box when it is wider than the screen — the row labels stay pinned
     on the left while it does. 16px type in the boxes keeps iOS from zooming on every hole. */
  .pc{gap:4px 3px;}
  /* The card's own box is a stacking context (layers, 2026-10-03): here a card that scrolls sideways is a scroller WebKit
     hands to its compositor, and without one every hole box's inner text field was lifted out of it onto three layers of
     its own to scroll in step (a scrolling proxy, its clip and the box: about 130 layers with + New Scorecard open at 402
     wide, kept while its page is kept). Inside it they are drawn with the card; nothing looks or scrolls differently. */
  .pc-wrap{scroll-padding-left:56px; isolation:isolate;}
  .pc-lab{position:sticky; left:0; z-index:2; background:var(--panel); font-size:12px; padding-right:6px;}
  .pc-hn{font-size:12px;}
  .pc-c input{font-size:16px; padding:8px 0; height:40px;}
  .pc-sum:not(.pc-h), .pc-tot:not(.pc-h):not(.pc-blank){height:40px; line-height:38px; font-size:13.5px; padding:0 2px;}
  .pc-read{gap:3px 2px;}
  /* "TOTAL" over a read-only card is 42.8px wide at 12px (35.7 at the 10px it had): any less room and it
     spills, so every round's card in a popup scrolls sideways (a stray scrollbar under each one). 44px
     holds it; the nine hole columns keep 21px each in a 375 popup. */
  .pc-read{--pc-tot:44px;}
  .pc-v{font-size:13px; height:32px; line-height:30px;}
  .pc-read .pc-sum:not(.pc-h), .pc-read .pc-tot:not(.pc-h):not(.pc-blank){height:32px; line-height:30px; font-size:12.5px;}
  /* The player's review of a card (a read card that keeps its Gross / Net names, .pc-labelled) is a whole
     18-hole card across a 360-375px phone, never scrolled sideways: slimmer cells, and its block reaches
     8px further out each side. Its names and headers are 12px like all print (print-and-red): TOTAL, at
     .02em, is 40.4px, so its column is 42px; the card still needs only ~296px of the 335 a 375 phone gives it. */
  .pc-read.pc-labelled{--pc-cell:19px; --pc-sum:28px; --pc-tot:42px; gap:3px 1.5px;}
  .pc-read.pc-labelled .pc-lab{font-size:12px; padding-right:3px;}
  .pc-read.pc-labelled .pc-h{letter-spacing:.02em;}
  .wizard-review-card{margin-left:-8px; margin-right:-8px; padding:10px 8px 8px;}
  /* …so its row names need not be pinned (round-scroll, 2026-10-03). A pinned (sticky) box WebKit can't hand to its
     compositor makes it repaint the page's own tiles on every frame of a scroll up or down the page: "Check your card"
     did, all through a scroll. Never scrolled sideways, the names sit exactly where they did. */
  .wizard-review-card .pc-lab{position:static;}
  /* The celebration's line is two lines on a phone once the round is in ("Sending…" is one): room for both from the
     start, so Done never moves under the thumb as the save lands (wizard-finish). */
  .celebration-line{min-height:2.8em;}
  .scorecard-form{padding:16px 12px;}
  /* The New Scorecard form's first row: three across left the date box 95px wide ("2026-0"). League
     takes the row, then Date and Holes side by side (Front or Back under them); a league's Week and
     Team take a row each — "Week 3 · Nov 30, 2026" needs the width. */
  .form-grid.sc-form-top{grid-template-columns:repeat(2, minmax(0,1fr));}
  .sc-form-top > :first-child, .sc-form-top > :nth-child(4), .sc-form-top.is-linked > *{grid-column:1 / -1;}
  .scorecard-form .team-card, .modal-scorecard .team-card{padding:12px 10px;}
  .modal.modal-scorecard{padding:20px 14px;}
  .modal.modal-scorecard{--x-pad-top:20px; --x-pad-side:14px;}   /* that padding, for its × (player-popup) */

  /* Standings: rank | name with its secondary figure (weeks played, or W-L-T and the hole
     differential) on a line of its own under it | the ranking number, which never wraps. The
     header keeps #, Team/Player and Points; ties still print "–". */
  .standings-board .flip-row{grid-template-columns:30px minmax(0,1fr) auto; grid-template-areas:"rank name pts" "rank stat pts";}
  .standings-board .flip-row > .fc:nth-child(1){grid-area:rank;}
  .standings-board .flip-row > .fc:nth-child(2){grid-area:name; padding-bottom:2px; min-width:0;}
  .standings-board .flip-row > .fc:nth-child(3){grid-area:stat; text-align:left; padding:0 10px 11px; font-size:12px; color:var(--chalk-faint); white-space:nowrap;}
  .standings-board.is-team .flip-row > .fc:nth-child(3){padding-left:36px;}
  .standings-board .flip-row > .fc:nth-child(4){grid-area:pts; white-space:nowrap; padding-right:14px;}
  .standings-board .flip-row.head{grid-template-areas:"rank name pts";}
  .standings-board .flip-row.head > .fc:nth-child(3){display:none;}
  .standings-board .flip-row.head > .fc{padding-top:10px; padding-bottom:10px;}

  /* The staff schedule: the week (and a Playoff badge), then its date and time, then the three
     buttons — the old five columns were cut off at the right edge of a phone. */
  .sched-board .flip-row.head{display:none;}
  .flip-row.sched-row{grid-template-columns:auto minmax(0,1fr) auto; grid-template-areas:"week week badge" "date time time" "act act act";}
  .sched-row > .fc:nth-child(1){grid-area:week; padding-bottom:0;}
  .sched-row > .fc:nth-child(2){grid-area:date; text-align:left; padding:2px 0 0 10px; font-size:13px;}
  .sched-row > .fc:nth-child(3){grid-area:time; text-align:left; padding:2px 10px 0 12px; font-size:13px;}
  .sched-row > .fc:nth-child(4){grid-area:badge; padding-bottom:0;}
  .sched-row > .fc:nth-child(4):empty{display:none;}
  .sched-row > .fc.sched-act{grid-area:act; text-align:left; padding:8px 10px 12px 4px;}
  /* Board Settings' Week: each league's name over its own week box, which may take the row's width,
     so the whole "Week 4 · Jul 29, 2026" shows (beside "Wednesday Night Scramble" the box was left
     162px and read "Week 4 · Jul 29, 202"). */
  .bs-week-pick{flex-direction:column; align-items:flex-start; gap:4px;}
  .bs-week-pick > .mini-label{white-space:normal;}
  .bayboard-settings-body .bs-week-pick > .tee-wrap, .bayboard-settings-body .bs-week-pick select{max-width:100%;}
  /* Skins' Gross / Net toggle (league-list-skins): each half a full 44px to tap */
  .skins-toggle-btn{min-height:44px; min-width:92px;}
  /* the Present Board on a phone: one tile a row (bayboard-v2), like the Bay Board tab's copy */
  .bayboard-present-overlay .tv-grid{grid-template-columns:minmax(0,1fr); grid-auto-rows:auto;}
  /* Upcoming This Week: two lines a night, the league and its week across the top, then the day, the
     time and the teams on one dotted line; either ends in "…" before it would ever take a third */
  .upc-list{padding:2px 14px;}
  .upc-row{grid-template-columns:auto auto minmax(0,1fr); grid-template-areas:"name name name" "date time teams"; column-gap:0; row-gap:3px; padding:10px 0; font-size:12px;}
  .upc-name{grid-area:name;}
  .upc-date{grid-area:date;}
  .upc-time{grid-area:time;}
  .upc-teams{grid-area:teams; min-width:0; overflow:hidden; text-overflow:ellipsis;}
  .upc-time, .upc-teams{text-align:left;}
  .upc-time::before, .upc-teams::before{content:"\00a0·\00a0";}
  /* THE PLAYER LEAGUE'S TABS AT THE FOOT OF THE SCREEN (player-tabs-big; owner, 2026-09-26, with a recording of
     Apple Music: "on the phone I need the tabs to operate like this", "so that the screen isnt overly crowded on
     the phone and all tabs can be seen"; then on his iPhone: "it needs to feel more fluid", "needs to disappear
     more fluidly", "hold on the icon when its condensed and have it pop up the whole bar"; then "make the bar
     bigger and the small icon bigger when collapsed", "by like 30 percent"; then "a little bit smaller, more fluid
     and needs to operate like the tab bar on the staff side where it changes the page before you even decide to let
     go"). One floating rounded bar, 75px tall (83 before: about 10 % smaller), 8px in from the screen's sides and its
     foot 20px up from the screen's (on an iPhone: the rule after these); all six tabs at once, each a 26px drawing
     over its word ("Weekly Teams" reads "Teams"), each as wide as its word plus an equal share of the rest. The words
     are the last round's size less 10 %, never under 12: 12px at 375, 12.6px at 390, 13px at 402, 14px from 430. The
     chosen tab in the gold, which glides on the compositor here along the staff glide's own path, drawn by the desk
     rows' pieces so its 23px ends stay as round as CSS draws them (30-render-core.js: tabGlide; phone-gold-ends), so its
     own CSS glide is off. The bar's shape is drawn by the dock's skin under it (.dock-skin, a live
     element on the page's body: 35-player.js), so the bar itself is see-through here. Scrolled down the page the skin's
     pieces flow into one 68px round button at the bottom-left while the tabs shrink into it and fade and the button's
     31px drawing comes in: one shape, on a spring, by transforms and opacity alone, run by the compositor (dockGo;
     nothing is laid out again while it moves). Held down, the button pops the whole bar open under the finger. A finger
     slides the gold along the bar as it slides the staff rows' gold, the page changing the moment it crosses into
     another tab (tabs-smooth, 2026-09-27; owner: "make it like the staff bar and how that slides"; the separate lens is
     gone). It takes every touch that starts on it
     (no page scroll, no zoom, no iOS menu or magnifier from there). The league page keeps room at its foot for it, so
     nothing is ever left behind it. (The league page's column class in front: the bar's own rules come later in this
     file.) */
  .play-shell-wide .play-tabs, #sec-tabs > .tabs{position:fixed; top:auto; left:8px; bottom:calc(20px + env(safe-area-inset-bottom, 0px));
    flex-wrap:nowrap; width:calc(100vw - 16px); max-width:none; height:75px; margin:0; gap:0; border-radius:37.5px;
    background:transparent; border-color:transparent; touch-action:none; -webkit-touch-callout:none;
    transform-origin:34px 41px; will-change:transform, opacity;}
  .play-shell-wide .play-tabs.is-tucked, #sec-tabs > .tabs.is-tucked{pointer-events:none;}
  .play-shell-wide .play-tab, #sec-tabs > .tabs > .tab-btn{flex:1 1 auto; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
    padding:5px 2px; border-radius:23px; font-size:clamp(12px, calc(3.96vw - 2.88px), 14px); line-height:1.2;}
  .play-shell-wide .play-tab-ico, #sec-tabs .tab-ico{display:block; height:26px;}
  .play-shell-wide .play-tabs > .tab-glide, #sec-tabs > .tabs > .tab-glide{border-radius:23px; transition:none;}
  .play-tab-more{display:none;}
  /* THE STAFF SIDE'S MAIN TABS ARE THIS BAR TOO (staff-dock, 2026-10-03; the owner, on his iPhone: "make the staff side phone
     version of the interface have the tab bar at the bottom and operate like the customer side"): the rules above and
     below serve both bars, and the same dock tucks, opens, pops and slides them (35-player.js), so the two stay one
     thing. Said for the staff bar alone is only what its own desk rules make different: its wrapper keeps only
     its bottom margin (the bar is fixed at the foot; the page starts 14px under the masthead), its row never scrolls
     sideways here, its words are the customer bar's (Inter 600), and the chosen tab's small logo makes way for the
     tab's drawing, as on the customer bar. */
  #sec-tabs{padding:0; margin:0 0 14px;}
  #sec-tabs > .tabs{overflow:visible;}
  #sec-tabs > .tabs > .tab-btn{font-family:'Inter',sans-serif; font-weight:600;}
  #sec-tabs > .tabs > .tab-btn.active::before{content:none;}
  /* The skin: a left cap, a middle and a right cap in the bar's panel and hairline, and the bar's shadow handing
     over to the button's. Each piece is laid out once at the full bar's size and only ever moved by its transform,
     from the bottom-left (dockLook). Under the bar (39 to its 40), over the page, under every popup. */
  .dock-skin{display:block; position:fixed; left:8px; bottom:calc(20px + env(safe-area-inset-bottom, 0px)); width:calc(100vw - 16px); height:75px;
    z-index:39; pointer-events:none;}
  .dock-skin > *{position:absolute; bottom:0; transform-origin:0 100%; will-change:transform;}
  .dock-shade{left:0; width:100%; height:75px; border-radius:37.5px; box-shadow:0 10px 30px rgba(0,0,0,.5); will-change:transform, opacity;}
  .dock-shade-dot{left:0; width:68px; height:68px; border-radius:50%; box-shadow:0 10px 26px rgba(0,0,0,.5); opacity:0; will-change:opacity;}
  .dock-cap, .dock-mid{height:75px; background:var(--panel); border:1px solid var(--hairline);}
  .dock-cap{left:0; width:37.5px;}
  .dock-cap-l{border-right:0; border-radius:37.5px 0 0 37.5px;}
  .dock-cap-r{border-left:0; border-radius:0 37.5px 37.5px 0;}
  .dock-mid{left:36.5px; width:calc(100% - 73px); border-left:0; border-right:0;}
  .dock-ico{left:0; width:68px; height:68px; display:flex; align-items:center; justify-content:center; color:var(--fairway);
    opacity:0; transform-origin:50% 50%; will-change:transform, opacity;}
  .dock-hit{left:0; width:68px; height:68px; padding:0; border:0; border-radius:50%; background:transparent; cursor:pointer;
    pointer-events:none; touch-action:none; -webkit-touch-callout:none; -webkit-user-select:none; user-select:none; -webkit-tap-highlight-color:transparent;}
  .dock-skin.is-tucked .dock-hit{pointer-events:auto;}
  html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) .app{padding-bottom:calc(123px + env(safe-area-inset-bottom, 0px));}
  /* a tab switch lands the tab's first line 12px from the top of the screen (tabStickOffset), so a short tab's area
     is tall enough for the page to get there (the top bar's 200px allowance left it about 11px short), and tall
     enough that the © line under it starts below the screen's foot rather than peeking out under the bar */
  .play-shell-wide .play-tab-body{min-height:calc(100vh - 150px);}
  /* …and the corner notes and the toast rise above it there */
  html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) #save-status, html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) #offline-status{bottom:calc(105px + env(safe-area-inset-bottom, 0px));}
  html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) #app-toast{bottom:calc(115px + env(safe-area-inset-bottom, 0px));}
  /* ON AN IPHONE the bar is placed from the top of the page's box instead, at the line where Safari's small viewport
     ends (100svh: the page's height with Safari's toolbars showing), its foot 20px above that line. Safari keeps a strip at the foot of the screen
     for itself while its toolbars are tucked away (after a scroll down: exactly when the bar is tucked into its
     button): a touch there brings Safari's toolbars back and never reaches the page (the owner's iPhone, 2026-09-26:
     "it only does it when the safari bar is also popped up"; the button then sat 42pt above the screen's bottom edge,
     inside that strip). Placed this way the bar and its button stay where they are on the screen whichever way
     Safari's toolbars are (nothing moves as they come and go, and nothing waits on Safari to report its new height),
     20px above the line Safari's toolbar comes back to: on the owner's 402x874 iPhone 762pt down, 112pt above the
     screen's bottom edge (the hold worked at 100 with the toolbar showing). In a home-screen web app there is no
     toolbar, so 100svh is simply the page's height and the bar sits 20px up from its foot as it does everywhere else.
     The page's foot room adds what the page gains while Safari's toolbars are away (100lvh - 100svh), and the notes
     rise from the same line as the bar (their own height lifted by translate). Only WebKit on iOS knows
     -webkit-touch-callout; an older iPhone without the svh units keeps the rules above. (The bar went from 83 to 75px
     with its foot kept exactly on that line: 95 = 75 + 20.) */
  @supports (-webkit-touch-callout: none) and (height: 100svh){
    .play-shell-wide .play-tabs, #sec-tabs > .tabs, .dock-skin{top:calc(100svh - 95px - env(safe-area-inset-bottom, 0px)); bottom:auto;}
    html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) .app{padding-bottom:calc(123px + env(safe-area-inset-bottom, 0px) + 100lvh - 100svh);}
    html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) #save-status, html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) #offline-status{top:calc(100svh - 105px - env(safe-area-inset-bottom, 0px)); bottom:auto; translate:0 -100%;}
    html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) #app-toast{top:calc(100svh - 115px - env(safe-area-inset-bottom, 0px)); bottom:auto; translate:0 -100%;}
  }
  /* A POPUP ON A PHONE (logos-popup; the owner, on his iPhone 2026-10-03, with a screenshot of a player's popup: "look at
     how a player pops up on the phone, kinda doesnt look right"). It read as a page cut off: its top and its × under
     Safari's top bar, its foot under the bottom one, two side borders the whole height, the page's tab bar peeking out
     at both sides. Now it is a card inside the screen between Safari's bars: 12px from each edge of it (more where the
     iPhone's own safe area asks), as tall as its content up to all of that (the overlay's own box: never `vh`, which on
     an iPhone is the screen with Safari's bars put away), its four rounded corners in view, its content scrolling
     inside. It grows out of what was tapped and goes back into it, is pushed up and away and closes on the dim as ever. */
  .modal-overlay{padding:max(12px, env(safe-area-inset-top, 0px)) 12px max(12px, env(safe-area-inset-bottom, 0px));}
  .modal{max-height:100%;}
  /* …the page's dim calm all round it: where a tab bar sits at the foot of the screen (75px tall, its foot 20px up: a
     league page's, and on the staff side every page's, staff-dock), the dim deepens into the page's own dimmed colour
     over the bar's band, softly over the 48px above it, so nothing of the bar shows beside the box or under it. It is the
     same layer, so it comes, goes and follows a push with the dim. (On an iPhone the bar is placed from the top, 100svh:
     the @supports rule after this one.) */
  html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) .modal-overlay::before{background:linear-gradient(to bottom, rgba(10,10,8,.72) calc(100% - 147px - env(safe-area-inset-bottom, 0px)), color-mix(in srgb, rgb(10,10,8) 72%, var(--ink)) calc(100% - 99px - env(safe-area-inset-bottom, 0px)));}
  @supports (-webkit-touch-callout: none) and (height: 100svh){
    html:has(.play-shell-wide .play-tabs, #sec-tabs > .tabs) .modal-overlay::before{background:linear-gradient(to bottom, rgba(10,10,8,.72) calc(100svh - 147px - env(safe-area-inset-bottom, 0px)), color-mix(in srgb, rgb(10,10,8) 72%, var(--ink)) calc(100svh - 99px - env(safe-area-inset-bottom, 0px)));}
  }
  /* …its title row always in view, an iPhone sheet's way: on a popup that only shows something, the small mark, the title
     and the round × stay at the top while the content scrolls under them (the title row reaching the box's sides in the
     box's colour, its foot softening into the content over 8px); the line under the title scrolls with the content. It
     stays the grab zone a finger pushes the popup away from (30-render-core.js: pushGrabZone), now at any scroll; the
     grab bar stays over it. Forms and confirms are as they were. */
  .modal.modal-pushable > .modal-x-pin + h3{position:sticky; top:calc(0px - var(--x-pad-top)); z-index:2; background:var(--panel);
    margin-left:calc(0px - var(--x-pad-side)); margin-right:calc(0px - var(--x-pad-side)); padding-left:var(--x-pad-side); padding-right:46px;}
  .modal.modal-pushable > .modal-x-pin + h3::after{content:""; position:absolute; left:0; right:0; top:100%; height:8px; background:linear-gradient(var(--panel), transparent); pointer-events:none;}
  .modal.modal-pushable::before{z-index:3;}
  /* …and the player's popup less heavy, every word kept: the League place card hugs its two lines; a round's title has
     the card's width, its date on the line under it when the two don't fit beside each other, never broken itself. */
  .modal .team-card > .upcoming-team-row:only-child{padding:0; row-gap:2px;}
  .modal .team-card .th{flex-wrap:wrap; gap:6px 10px;}
  .modal .team-card .th .badge{flex:none; white-space:nowrap;}
  /* The player side uses the phone's width the staff side does (look 3, 2026-09-26; the owner: "make sure
     thats done on staff side as well"): the page's own 20px sides and no second gutter, so the column is
     335px at 375 (303 before) and 390 at 430 (358). The hole screen's bar keeps to the column too, its
     buttons as wide as the score boxes above them. A desk keeps the 16px. (.app in front: the column's
     and the bar's own rules come later in this file.) */
  .app .play-shell{padding-left:0; padding-right:0;}
  .app .wizard-bar{margin-left:0; margin-right:0; padding-left:0; padding-right:0;}
  /* The Scoring grids (player and staff): the gross and net columns hold two or three digits, so on a phone
     they give the players' names the room — "Ryan Cole HC 8" on one line (it broke onto two in 101px). */
  .score-grid{grid-template-columns:minmax(0,1fr) 64px 64px;}
  /* The hole screen's − / + and numbers keep layers of their own (round-scroll, 2026-10-03). Every tap presses a − / +
     (the .12s press) and pops its number (.28s), and for each motion WebKit made the button or the number a layer of
     its own (with the stepper's rounded clip twice over) and dropped it after. Now the four numbers and their eight
     buttons keep theirs (will-change), and these steppers no longer clip: the − and + draw the stepper's inner 11px
     corners themselves (its 12px less its 1px edge), the shape the clip cut. A tap makes and drops no layer; nothing
     looks different. A desk keeps the stepper as it was, and so do a scramble's totals (their step is not the round's
     own stacking context, where standing layers would lift what follows them). */
  .app .wizard-player-block .number-stepper{overflow:visible;}
  .app .wizard-player-block .ns-minus{border-radius:11px 0 0 11px;}
  .app .wizard-player-block .ns-plus{border-radius:0 11px 11px 0;}
  .app .wizard-player-block .number-stepper input, .app .wizard-player-block .ns-btn{will-change:transform;}
  /* The Owner's Manual (owners-manual): the topics' titles a size down, and their words use the card's whole width */
  .manual-toggle{padding:13px 14px; font-size:16px;}
  .manual-text{padding:0 14px 14px;}
  .manual-body{font-size:14px;}
  /* The round on a short phone (desk-fixes, 2026-10-05; the customers audit: on an iPhone SE with Safari's bars, 375x548,
     the second player's boxes sat under the Next bar on every hole and Next put the page back at its top; 375x635 hid 12px
     of them, 414x617 30px). The hole screen is 698px tall, so where the screen with Safari's bars showing (100svh, a
     height that never changes as the bars come and go) is under 712px, the round's spacing tightens by just as much as
     the screen is short: --round-short is that shortfall, 0 to 164px, and each gap below gives up its share of it (at
     548px and under, all of it: 163px in all). Only gaps change: every word, the boxes, their − / + and the bar's buttons
     keep their sizes. So the hole fits from 548px up, both players' boxes above the bar and the page exactly one screen
     (round-scroll); at 712px and over every value here is its own exactly (the owner's phones are 724px and over with
     Safari's bars), and a browser without svh keeps them all. The Cancel link and the team name tighten on every step of
     the round (the first two screens too) and the bar on the hole and the review alike, so nothing moves between steps. */
  @supports (height:100svh){
    .app .play-shell{--round-short:clamp(0px, calc(712px - 100svh), 164px);}
  }
  .app .play-back[data-action="player-wizard-cancel"]{margin-top:calc(0px - var(--round-short, 0px) * 12 / 164); margin-bottom:calc(14px - var(--round-short, 0px) * 12 / 164);}
  .app .play-back[data-action="player-wizard-cancel"] + .play-league-header{margin-bottom:calc(14px - var(--round-short, 0px) * 10 / 164);}
  .app .play-back[data-action="player-wizard-cancel"] + .play-league-header .play-next-week{margin-top:calc(4px - var(--round-short, 0px) * 3 / 164);}
  .app .wizard-hole:not(.wizard-review) .wizard-hole-num{margin-top:calc(6px - var(--round-short, 0px) * 6 / 164); margin-bottom:calc(18px - var(--round-short, 0px) * 6 / 164);}
  .app .wizard-hole:not(.wizard-review) .wizard-player-block{padding-top:calc(14px - var(--round-short, 0px) * 7 / 164); padding-bottom:calc(14px - var(--round-short, 0px) * 7 / 164); margin-bottom:calc(14px - var(--round-short, 0px) * 9 / 164);}
  .app .wizard-hole:not(.wizard-review) .wizard-so-far + .wizard-player-block{margin-top:calc(0px - var(--round-short, 0px) * 8 / 164);}
  .app .wizard-hole:not(.wizard-review) .wizard-player-name{margin-bottom:calc(10px - var(--round-short, 0px) * 7 / 164);}
  .app .wizard-hole:not(.wizard-review) .wizard-score .mini-label{margin-bottom:calc(5px - var(--round-short, 0px) * 2 / 164); line-height:calc(18.75px - var(--round-short, 0px) * 3.75 / 164);}
  .app .wizard-shell .wizard-hole-main{padding-bottom:calc(8px - var(--round-short, 0px) * 8 / 164);}
  .app .wizard-shell .wizard-bar{margin-top:calc(14px - var(--round-short, 0px) * 10 / 164); padding-top:calc(12px - var(--round-short, 0px) * 4 / 164); padding-bottom:calc(12px + env(safe-area-inset-bottom, 0px) - var(--round-short, 0px) * 6 / 164);}
  .app .wizard-shell .wizard-bar .app-copyright{margin-top:calc(10px - var(--round-short, 0px) * 6 / 164);}
  /* Longest drive & closest to the pin (drive-pin): the sheet a little less padded, so a player's two pin boxes and Yes /
     No sit on one line at 375 (each box 81px for "200 ft"); the desk's list of every entry in a phone's column */
  .modal.modal-contest{padding:20px 16px; --x-pad-top:20px; --x-pad-side:16px;}   /* that padding, for its foot */
  .contest-row-l{grid-template-columns:24px minmax(0, 1fr) 74px 54px; gap:6px; padding:7px 8px;}
  .contest-row-l.is-head .contest-l-ans{white-space:normal; line-height:1.25;}
  .contest-season-line{grid-template-columns:minmax(0, 1fr);}   /* the label over its places */
  /* The league form at 375 (form-trim): the season's start date and its bay time block each take the whole line, so
     "2026-12-03" and "5:00 PM - 8:00 PM" show whole; Holes and Standings share a line and Skins takes the next. */
  .league-when{flex-direction:column;}
  .league-holes-row{flex-wrap:wrap;}
  .league-holes-row > .field:nth-child(1), .league-holes-row > .field:nth-child(2){flex:1 1 0;}
  .league-holes-row > .field:nth-child(3){flex:1 1 100%;}
}
@media (prefers-reduced-motion: reduce){
  *{transition:none !important; animation-duration:.001s !important;}
}
:focus-visible{outline:2px solid var(--tracer); outline-offset:2px;}
/* ---------- Player mode — simplified, mobile-first, no admin chrome ---------- */
.play-shell{max-width:560px; margin:0 auto; padding:20px 16px 60px; position:relative;}
.play-shell-wide{max-width:760px;}
/* The big faded logo behind the player side is the page's one mark (.page-mark, shared with the staff
   side since logo-pass, 2026-09-25: the same size and strength its own ::before had). These boxes keep
   the layer they sat on above that old ::before (z-index 0); the page's mark is under everything, at -1. */
.play-masthead, .play-league-card, .play-footer, .play-back, .play-league-header, .play-tabs,
.play-sched-row, .team-card, .empty{position:relative; z-index:1;}
.play-masthead{display:flex; flex-direction:column; align-items:center; text-align:center; padding:26px 0 30px;}
.play-logo{width:150px; height:150px; background-color:var(--fairway); margin-bottom:10px;}
.play-tagline{font-family:'Baloo 2',sans-serif; font-size:26px; color:var(--chalk); margin-top:2px;}
.play-back{
  display:inline-flex; align-items:center; gap:6px; margin:0 0 14px; font-family:'Inter',sans-serif;
  font-size:14px; font-weight:600; color:var(--fairway-light); cursor:pointer; background:none; border:none; padding:6px 2px;
}
@media (hover:hover) and (pointer:fine){ .play-back:hover{text-decoration:underline;} }
.play-league-card{
  background:var(--panel); border:1px solid var(--hairline-light); border-radius:14px;
  padding:22px 18px; margin-bottom:12px; cursor:pointer;
  transition:border-color .15s ease, transform .12s ease;
  display:flex; align-items:center; gap:16px;
}
@media (hover:hover) and (pointer:fine){ .play-league-card:hover{border-color:var(--fairway-light);} }
/* The card's logo is the mark without its "Indoor Virtual Golf" lettering (owner, 2026-09-25, look 9: "you
   can remove the small lettering from the logo"; unreadable at 44px): the wizard's progress mark
   (--logo-mark, logo-mark.png), in the same 44px box and the same brass. */
.play-league-card-logo{
  flex:none; width:44px; height:44px; background-color:var(--brass);
  -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
.play-league-card h3{font-family:'Baloo 2',sans-serif; font-size:24px; margin:0; color:var(--chalk); line-height:1.15; flex:1;}
/* The players' site (play-site, 2026-10-06): a padlock on a league this phone hasn't opened, and the league's password, asked
   once. The box is 16px so an iPhone doesn't zoom into it; red only for a wrong password (a wait is the calm gold). */
.play-league-lock{flex:none; display:inline-flex; color:var(--chalk-faint);}
.play-unlock{position:relative; z-index:1; background:var(--panel); border:1px solid var(--hairline-light); border-radius:14px; padding:20px 18px;}
.play-unlock-title{display:flex; align-items:center; gap:8px; font-family:'Baloo 2',sans-serif; font-size:21px; line-height:1.2; color:var(--chalk); margin-bottom:6px;}
.play-unlock-title .lock-icon{color:var(--fairway-light);}
.play-unlock-sub{font-size:14px; line-height:1.45; color:var(--chalk-dim); margin-bottom:16px;}
.play-unlock-form{display:flex; flex-wrap:wrap; gap:10px;}
.play-unlock-form .play-unlock-input{flex:1 1 150px; width:auto; min-width:0; font-size:16px; padding:12px 14px;}
.play-unlock-go{flex:none; min-height:48px;}
.play-unlock-note{min-height:20px; margin-top:12px; font-size:14px; line-height:1.4; color:var(--danger);}
.play-unlock-note.is-calm{color:var(--fairway-light);}
.live-pill{
  flex:none; font-family:'Inter',sans-serif; font-size:12px; font-weight:700; letter-spacing:.04em;
  color:#20180F; background:var(--tracer); border-radius:6px; padding:5px 10px; animation:live-pulse 2s ease-in-out infinite;
}
@keyframes live-pulse{ 0%,100%{opacity:1;} 50%{opacity:.55;} }
/* Score-entry wizard: the player's name has to be unmistakable, since two people are
   entering into the same phone and a mixed-up card is a real hassle to unwind. */
.wizard-player-block{
  background:var(--panel); border:1px solid var(--hairline-light); border-radius:12px;
  padding:14px 16px; margin-bottom:14px;
}
.sc-player-name{
  font-family:'Baloo 2',sans-serif; font-size:19px; color:var(--tracer);
  margin-bottom:6px; line-height:1.15;
}
.wizard-player-name{
  font-family:'Baloo 2',sans-serif; font-size:24px; color:var(--tracer);
  margin-bottom:10px; line-height:1.15;
}
.wizard-confirm{
  background:var(--panel); border:1px solid var(--hairline-light); border-radius:12px;
  padding:18px 16px; margin-bottom:14px; text-align:center;
}
.wizard-confirm-title{font-family:'Baloo 2',sans-serif; font-size:22px; color:var(--chalk); margin-bottom:12px;}
.wizard-confirm-names{display:flex; flex-direction:column; gap:6px; margin-bottom:12px;}
.wizard-confirm-name{font-family:'Baloo 2',sans-serif; font-size:26px; color:var(--tracer); line-height:1.15;}
.wizard-confirm-empty{font-family:'Inter',sans-serif; font-size:14px; color:var(--chalk-faint);}
.wizard-confirm-hint{font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-dim);}
.wizard-confirm-hint b{color:var(--chalk);}
/* The round's first two screens (wizard-inputs, 2026-09-26; owner, look 7: "no just make it bigger"): "Is this your
   team?"'s two buttons, a scramble's Enter Total / Hole by Hole, Front 9 / Back 9 and Start · Hole 1 (Finish Round for
   a total) are as big as the hole screen's bar (52px, 16px words, centred), where they always were. "Wrong team" keeps
   its words on one line (a flex button is never narrower than its words) and "Yes, that's us →" takes the rest. The
   celebration's Done is the same size. */
.wizard-choice{display:flex; gap:10px;}
.wizard-choice .btn{flex:1 1 0; padding-left:12px; padding-right:12px;}
.wizard-choice .btn, .wizard-go, .celebration-done{justify-content:center; min-height:52px; font-size:16px; border-radius:12px;}
.wizard-confirm-actions{margin-top:14px;}
.wizard-confirm-actions .btn:not(.btn-primary){white-space:nowrap;}
.wizard-confirm-actions .btn-primary{flex-grow:2;}
.wizard-go{width:100%; margin-top:6px;}
.celebration-screen{
  display:flex; flex-direction:column; align-items:center; text-align:center;
  padding:60px 20px; animation:celebrationIn .4s cubic-bezier(.2,.9,.3,1.2);
}
@keyframes celebrationIn{from{opacity:0; transform:scale(.9) translateY(10px);} to{opacity:1; transform:scale(1) translateY(0);}}
.celebration-icon{margin-bottom:10px; display:flex; justify-content:center;}
.celebration-icon svg{margin-right:0 !important;}
.celebration-title{font-family:'Baloo 2',sans-serif; font-size:28px; color:var(--chalk);}
.celebration-team{font-family:'Inter',sans-serif; font-size:15px; color:var(--chalk-faint); margin-top:6px;}
.celebration-score{font-family:'IBM Plex Mono',monospace; font-size:56px; font-weight:700; color:var(--tracer); margin-top:14px;}
/* The line under the score (35-player.js: celebrationLineHtml): "Sending…", then what the number is ("Team score 31 · net
   best ball · 1st of 2 so far"). One line on a desk; on a phone it breaks into two even lines rather than leaving one word. */
.celebration-line{margin-top:6px; max-width:340px; font-family:'Inter',sans-serif; font-size:14px; line-height:1.4; color:var(--chalk-dim); text-wrap:balance;}
/* Done is a .btn (inline-flex), so its word sat at the left of the full-width button */
.celebration-screen .btn{justify-content:center;}
/* …and the app's gold main button (wizard-inputs; owner: "yes for after finish"), the width of the screen's column */
.celebration-done{width:100%; margin-top:20px;}
/* ---------- The round in the player's hand (2026-09-24) ----------
   THE HOLE SCREEN (owner: "yes but not overly big to where it feels invasive"). On a phone the hole
   step fills the screen: the team at the top, the hole number and its score boxes in the middle of
   what is left, and Back / Next (Finish Round on the last hole) as a bar pinned along the bottom,
   where a thumb already is — padded clear of the iPhone home indicator by the safe-area inset.
   Wider than a phone the same bar simply follows the boxes: pinned to the foot of a desktop window
   it would be a long way from them. The bar is the page's own --ink, opaque (round-scroll, 2026-10-03: it was
   frosted, --ink at 86 % over a 16px blur, which Safari draws again on every frame anything moves under it; over
   the plain page it looked exactly as it does now, and what scrolls under it is simply covered, like the tab bars).
   THE ROUND IS ITS OWN STACKING CONTEXT (round-scroll, 2026-10-03; .wizard-shell: the hole and the review). The
   page's watermark is fixed, so WebKit draws it on a layer of its own and the round's screen on another beside it.
   When anything on that screen started to move (a − / + pressed, a number popping, a mark filling) WebKit could no
   longer tell where it would go, so it drew everything painted after it (the Cancel link, the team name, the
   read-out line, the other boxes' inner parts) on layers of its own for the length of the motion and dropped them
   after, on every tap. In a stacking context of its own it doesn't; nothing looks different. */
.wizard-shell{isolation:isolate;}
/* …and inside it the Cancel link and the team name keep the page's own order (the z-index 1 every player box
   carries kept them over an old watermark that is gone), and a mark that is moving (its gold rising, its spring
   as the hole changes, the row's glide) is drawn over its neighbours while it moves. The marks never overlap, so
   nothing looks different, but the other seventeen are no longer each made a layer, with a mask, for a fill. */
.wizard-shell :is(.play-back, .play-league-header){z-index:auto;}
.wiz-logo:is(.is-filling, .is-arriving, .is-leaving){z-index:1;}
.wizard-hole-num{font-family:'Baloo 2',sans-serif; font-weight:600; font-size:40px; line-height:1.05; color:var(--chalk); text-align:center; margin:6px 0 18px;}
.wizard-scores{display:flex; gap:10px;}
.wizard-score{flex:1; min-width:0;}
.wizard-score .mini-label{margin-bottom:5px;}
.wizard-hole-input{text-align:center;}
.wizard-player-block .number-stepper{border-radius:12px;}
.wizard-player-block .ns-btn{width:44px; font-size:22px;}
.wizard-player-block .number-stepper input[type=number]{height:54px; padding:0 2px; font-size:24px; font-weight:600; text-align:center;}
/* A scramble's two totals (Enter Total) are set with − and + too now (wizard-inputs): the hole boxes' size */
.wizard-total .number-stepper{border-radius:12px;}
.wizard-total .ns-btn{width:44px; font-size:22px;}
.wizard-total .number-stepper input[type=number]{height:54px; padding:0 2px; font-size:24px; font-weight:600; text-align:center;}
/* No keyboard in the round (wizard-inputs, 2026-09-26; owner: "get rid of keyboard completely"). A box set by − and +
   alone (data-stepper="only": readonly, inputmode none, 35-player.js) is a display: a tap on its number goes to the
   stepper round it, so it is never focused for typing (no keyboard, caret, text menu or "Done" bar on any phone), and a
   quick second tap there never zooms the page. The one hidden line that reads each change out (#wizard-live). */
.number-stepper input[data-stepper="only"]{pointer-events:none; -webkit-user-select:none; user-select:none;}
.wizard-player-block .number-stepper, .wizard-total .number-stepper{touch-action:manipulation;}
.wizard-live{position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;}
/* A box with a problem (wizard-check, 2026-09-26; 35-player.js: wizardBoxWhy, paintWizardHole): red,
   with a few words after its name ("Gross · Max 15") on the one line the name always had, so nothing
   moves as they come and go. Red only for real problems: an empty box counts only once the card has
   been checked ("Missing"). A scramble's two total boxes the same (.wizard-total). */
.wizard-score .mini-label, .wizard-total label{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.wizard-why:not(:empty)::before{content:" · ";}
.wizard-score.is-problem .number-stepper, .wizard-total.is-problem .number-stepper{border-color:var(--danger); box-shadow:0 0 0 1px var(--danger);}
.wizard-score.is-problem .mini-label, .wizard-total.is-problem label{color:var(--danger);}
.wizard-bar{position:sticky; bottom:0; z-index:5; margin:14px -16px 0; padding:12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  background:var(--ink);}
.wizard-bar-buttons{display:flex; gap:10px;}
.wizard-bar-buttons .btn{justify-content:center; min-height:52px; font-size:16px; border-radius:12px;}
.wizard-bar-back{flex:1;}
.wizard-bar-next{flex:2;}
/* The review's bar (35-player.js: renderWizardReviewStep): what stops the save, above Back and Save
   round. Each slip is a row that goes to its hole; "Holes 4 and 7 still need a score" has a button for
   each hole, one row that scrolls sideways when there are many, so the bar never grows over the card. */
.wizard-notice{margin:0 0 10px; padding:10px 12px;}
.wizard-nine-switch{display:flex; width:100%; justify-content:center; margin:0 0 10px;}   /* a block, not an inline box: folded to nothing it leaves no empty line behind (wizard-feel) */
.wizard-missing-title{font-family:'Inter',sans-serif; font-size:14px; font-weight:600;}
.wizard-missing-holes{display:flex; gap:6px; margin:8px -12px 0; padding:0 12px 2px; overflow-x:auto; scrollbar-width:none;}
.wizard-missing-holes::-webkit-scrollbar{display:none;}
.wizard-missing-hole{flex:none; justify-content:center; min-height:38px; padding:7px 14px; font-size:13.5px;}
.wizard-problem{display:flex; align-items:center; gap:8px; width:100%; margin:0 0 6px; padding:8px 10px; text-align:left; font-weight:500; font-size:13.5px; line-height:1.35;}
.wizard-problem > b{flex:none; font-weight:700;}
.wizard-problem > span{flex:1; min-width:0;}
.wizard-problem:last-child{margin-bottom:0;}
/* THE REVIEW itself: "Check your card", then each scorer's card read-only with its row names (the paper
   card the rounds popups draw), the totals over it; any number is a tap back to its hole. */
.wizard-review-title{font-family:'Baloo 2',sans-serif; font-weight:600; font-size:30px; line-height:1.1; color:var(--chalk); text-align:center; margin:2px 0 4px;}
.wizard-review-sub{font-family:'Inter',sans-serif; font-size:13px; line-height:1.4; color:var(--chalk-dim); text-align:center; margin:0 0 14px;}
.wizard-review-card{padding:12px 12px 10px;}
.wizard-review-card .wizard-player-name{font-size:21px; margin-bottom:0;}
.wizard-review-tot{font-family:'IBM Plex Mono',monospace; font-size:13px; color:var(--chalk-dim);}
.wizard-review-tot b{color:var(--tracer); font-weight:700;}
.wizard-review-card .pc-wrap{margin:6px 0 0;}
.pc-read.pc-labelled.pc-18{grid-template-columns:max-content repeat(9, minmax(var(--pc-cell), 1fr)) minmax(var(--pc-sum), 1fr) minmax(var(--pc-tot), 1fr);}
.pc-read.pc-labelled.pc-9{grid-template-columns:max-content repeat(9, minmax(var(--pc-cell), 1fr)) minmax(var(--pc-tot), 1fr);}
.pc-jump{display:flex; align-items:center; justify-content:center; width:100%; padding:0; font-weight:400;}
.pc-jump.is-problem, .wizard-review-total.is-problem{border-color:var(--danger); box-shadow:0 0 0 1px var(--danger);}
.pc-jump.is-problem, .wizard-review-total.is-problem .wizard-review-num{color:var(--danger);}
.wizard-review-totals{display:flex; gap:10px; margin-top:8px;}
.wizard-review-total{flex:1; flex-direction:column; justify-content:center; gap:2px; padding:10px 8px; border-radius:12px; background:var(--ink-2);}
.wizard-review-num{font-family:'IBM Plex Mono',monospace; font-size:28px; font-weight:700; line-height:1.1; color:var(--tracer);}
@media (max-width:600px){
  /* .app keeps 80px under the page — while the hole screen is up the page is exactly one screen
     tall, so that is not there (a browser without :has() just scrolls those few px). The empty
     staff tab-row wrapper is gone on every player screen (#sec-tabs:empty, above). */
  .app:has(.wizard-shell){padding-bottom:0;}
  /* …nor is <body>'s own 100vh (round-scroll, 2026-10-03). On an iPhone 100vh is the screen with Safari's toolbar put
     away, so with the toolbar showing the hole screen could be scrolled by the toolbar's height: the page rose under
     the finger with the bar (pinned to the shell's foot, the shell being 100dvh), Safari put its toolbar away, the
     screen grew, and the page was laid out again with the hole and the bar jumping back down. While the round shows, the
     page is the screen it fills and no more: a hole that fits has nothing to scroll; anything taller than the phone
     (a small phone, a long review) scrolls as it always did. */
  body:has(.wizard-shell){min-height:0;}
  .wizard-shell{display:flex; flex-direction:column; min-height:100vh; min-height:100dvh; padding-bottom:0;}
  .wizard-shell .wizard-hole{flex:1; display:flex; flex-direction:column;}
  .wizard-shell .wizard-hole-main{flex:1; display:flex; flex-direction:column; justify-content:center; padding-bottom:8px;}
}
/* A half-finished round this phone is keeping: "Round in progress — Hole N · Resume / Discard"
   (player picker and the league page), and the same hint on that team's LIVE-banner button. */
.round-draft{position:relative; z-index:1; background:var(--panel); border:1px solid var(--fairway-light); border-radius:14px; padding:14px 16px; margin-bottom:14px;}
.round-draft-title{display:flex; align-items:center; font-family:'Baloo 2',sans-serif; font-size:20px; line-height:1.2; color:var(--chalk);}
.round-draft-meta{font-family:'Inter',sans-serif; font-size:13px; color:var(--chalk-faint); margin-top:4px;}
/* Scoring has closed, but this round started while it was open: it can still be finished (wizard-finish). Calm gold. */
.round-draft-note{font-family:'Inter',sans-serif; font-size:13px; font-weight:600; color:var(--fairway-light); margin-top:4px;}
.round-draft-actions{display:flex; gap:8px; margin-top:12px;}
.round-draft-actions .btn{flex:1; justify-content:center; min-height:44px; font-size:14.5px;}
.round-draft-actions .btn-primary{flex:2;}
.quick-log-resume{display:block; margin-top:3px; font-family:'Inter',sans-serif; font-size:12.5px; font-weight:600; color:var(--tracer);}
/* THE CELEBRATION: the flag is planted with a spring — it drops in, squashes as it lands, springs
   up past its size and settles (the same overshoot the popups and the tab highlight have), pivoting
   on the foot of the pole. The score counts up to its value in 35-player.js (playCelebration) and
   lands with a small bump. Reduced motion: both are simply there. */
.celebration-icon svg{transform-origin:29% 88%; animation:flagPlant .64s .1s both;}
@keyframes flagPlant{
  0%{transform:translateY(-36px) scale(.72) rotate(-10deg); opacity:0; animation-timing-function:cubic-bezier(.5,0,.9,.5);}
  30%{opacity:1;}
  46%{transform:translateY(0) scale(1.08,.88) rotate(4deg); animation-timing-function:cubic-bezier(.3,.7,.3,1);}
  68%{transform:translateY(-3px) scale(.97,1.05) rotate(-2deg); animation-timing-function:ease-in-out;}
  86%{transform:translateY(0) scale(1.01,.99) rotate(.6deg); animation-timing-function:ease-out;}
  100%{transform:none; opacity:1;}
}
.celebration-score{font-variant-numeric:tabular-nums;}
.celebration-score.is-counted{animation:scoreLand var(--popup-in) cubic-bezier(.3,.7,.3,1);}
@keyframes scoreLand{0%{transform:scale(1);} 45%{transform:scale(1.06);} 100%{transform:scale(1);}}
@media (prefers-reduced-motion: reduce){ .celebration-icon svg, .celebration-score.is-counted{animation:none;} }
.quick-log-banner{
  background:var(--fairway); border-radius:12px; padding:14px 16px; margin-bottom:16px;
}
.quick-log-banner-title{font-family:'Baloo 2',sans-serif; font-size:16px; color:#20180F; margin-bottom:10px;}
.quick-log-banner-teams{display:flex; flex-direction:column; gap:10px;}
.quick-log-team-btn{
  width:100%; background:#20180F; color:var(--fairway-light); border:none; border-radius:10px;
  padding:16px 18px; font-family:'Baloo 2',sans-serif; font-size:18px; font-weight:700;
  cursor:pointer; text-align:center; -webkit-tap-highlight-color:transparent;
}
.quick-log-team-btn:active{background:#0f0b07;}
.quick-log-team-btn:disabled{opacity:.45; cursor:not-allowed;}
/* The LIVE box's own team buttons (owner, 2026-09-25: "dont shrink actually make 5% bigger and include
   names small, make team name bigger"): 64px tall (61 before), the team's name 22px (18 before) and its
   players' first names small under it (35-player.js: renderQuickLogBanner). Scoped to the box: the
   celebration's Done and "+ Add Scorecard" wear the same class and keep their look. */
.quick-log-banner-teams .quick-log-team-btn{display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:64px; padding:10px 18px;}
.quick-log-team-name{display:block; max-width:100%; font-size:22px; line-height:1.15; overflow-wrap:break-word;}
.quick-log-team-players{display:block; max-width:100%; margin-top:2px; font-family:'Inter',sans-serif; font-size:13px; font-weight:500; line-height:1.3; color:var(--chalk-dim); overflow-wrap:break-word;}
.play-footer{text-align:center; margin-top:24px;}
.play-league-header{text-align:center; margin-bottom:14px;}
.play-league-header h2{font-family:'Baloo 2',sans-serif; font-size:25px; margin:0; color:var(--chalk);}
.play-next-week{font-family:'IBM Plex Mono',monospace; font-size:13px; color:var(--brass); margin-top:4px;}
.member-toolbar{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:14px;}
.member-toolbar input[type=text]{flex:1; min-width:200px;}
.member-toolbar select, .member-toolbar .tee-wrap{max-width:220px;}
/* The player league page's tab bar (tabs-and-morph, 2026-09-26; the owner: "remove the black bar behind the
   tabs like we did for staff side"): one rounded bar that hugs its tabs, like the staff bar. Its own fill is
   the opaque --panel, so whatever scrolls under it while it is pinned (6px from the top) never shows through
   its words, and there is nothing round it: no frosted box, no band, nothing else that takes a tap. The tabs
   are words on the bar's track, with no box of their own, and the gold highlight rides under them where it
   can be seen (feel 2): syncTabIndicator, the staff bar's spring over --glide. The chosen word turns dark the
   moment the tab is chosen, as the staff bar's does (desk-tabs-smooth, owner 2026-09-27; it waited for the gold
   before), and the word left turns light at once: both over the staff bar's .15s, a tap or a slide alike. If
   the bar ever needs two rows (a narrow window) it takes the full width and each row's tabs share it, so it
   is never wider than its tabs. A lot bigger since player-tabs-big (owner, 2026-09-26: "make the tab bar alot
   bigger"): 17px words in a 58px bar (13px in 47.5 before). Phones: the tabs are a bar at the foot of the
   screen instead, each tab a drawing over its word (the 560px block); a desk shows the words alone. */
.play-tabs{position:sticky; top:6px; z-index:40; display:flex; flex-wrap:wrap; gap:2px; width:fit-content; max-width:100%;
  margin:16px auto 18px; padding:4px; background:var(--panel); border:1px solid var(--hairline); border-radius:29px;}
.play-tab{
  position:relative; z-index:1; touch-action:manipulation;
  flex:1 1 auto; text-align:center; padding:14px 20px; border-radius:999px; font-family:'Inter',sans-serif;
  font-size:17px; line-height:20px; font-weight:600; white-space:nowrap; color:var(--chalk-dim); background:transparent; cursor:pointer;
  transition:color .15s ease, transform .12s ease;
}
.play-tab-ico{display:none;}
.play-tab.active{color:#20180F;}
/* A tab's own area is at least a screen tall, less the bar and the page's foot (tabs-and-morph, feel 3), so a
   short tab never clamps the page and pulls the pinned bar down: landingScroll opens it under the bar. */
.play-tab-body{min-height:calc(100vh - 200px);}
/* A tab's page once built stays built (smooth-nav, 2026-09-28; the owner: "need everything very very fluid and smooth"):
   each is a pane in the tab body (30-render-core.js: paneSwitch, panesSync; live only, never in the markup), the one
   shown an ordinary block, the others kept laid out at the body's own width (written on them: paneKeep) but out of the
   flow where they stand, with no height and clipped (nothing drawn, nothing a tap can reach; aria-hidden for a screen
   reader), so showing one again asks WebKit only to show it. Where a keyboard can Tab into it (a desk with a mouse) it
   is hidden as well; on a phone that costs the switch a restyle of the whole page for nothing (about 1 ms for Makis
   League's Scoring in WebKit). (No box round them is positioned for it: a positioned tab body cost the drill-in's
   morph about 2 ms in WebKit.) */
.tab-pane.is-kept{position:absolute; height:0; overflow:hidden;}
@media (hover:hover) and (pointer:fine){ .tab-pane.is-kept{visibility:hidden;} }
/* …and nothing in a kept page moves while it is out of sight (layers, 2026-10-03): a LIVE pulse kept running there made
   WebKit keep it on a layer of its own and redraw the screen on every frame of its pulse for nothing to see, and lifted
   every box of the page shown that was drawn after it onto layers of their own as well; the TV copy's strip, which
   rests while its page is kept (40-bayboard.js: boardTickerSync), kept a masked layer. Shown again, the pulse starts from
   its beginning and the strip takes its layer back, as on any freshly drawn page. */
.tab-pane.is-kept :is(.live-pill, .lc-live-dot){animation:none;}
.tab-pane.is-kept .tv-ticker-track{will-change:auto;}
.play-sched-row{
  display:flex; align-items:center; gap:14px; background:var(--panel); border:1px solid var(--hairline-light);
  border-radius:11px; padding:11px 14px; margin-bottom:8px;
}
.play-sched-row.is-next{border-color:var(--fairway-light);}
.play-sched-info{flex:1; min-width:0;}
.play-sched-label{font-family:'Inter',sans-serif; font-weight:600; font-size:15px; color:var(--chalk);}
.play-sched-time{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint); margin-top:2px;}
.play-sched-time > span{white-space:nowrap;} /* a narrow row breaks between the date and the time, never inside "5:00 PM – 7:00 PM" */
/* A week already played is a little faded (look 3; the owner: "with a little bit faded out"), never the
   half-opacity wash it was (its date at 2.4:1): its box sinks toward the page (--ink-2, the plain hairline)
   and its name drops to --chalk-dim, 10.3:1 against 14.9:1 for a week ahead, while its date keeps
   --chalk-faint, 6.1:1. The staff Schedule's played weeks match (.sched-row.is-past). */
.play-sched-row.is-past{background:var(--ink-2); border-color:var(--hairline);}
.play-sched-row.is-past .play-sched-label{color:var(--chalk-dim);}
.play-next-pill{font-family:'Inter',sans-serif; font-size:12px; font-weight:700; letter-spacing:.05em; color:#20180F; background:var(--fairway); border-radius:5px; padding:3px 8px; flex:none;}
/* A week on the player Schedule opens its course and matchups in a small popup out of the row (see 4, 70-modals.js:
   modalScheduleWeek): the row looks exactly as it did (no tint of its own), it just answers a tap with the shared press. */
.play-sched-row[data-action]{cursor:pointer;}
.modal.modal-week{max-width:380px;}
.week-info{margin:0 0 14px;}
.week-info .mini-label{margin-bottom:3px;}
.week-info-val{font-family:'Inter',sans-serif; font-size:15px; font-weight:600; line-height:1.45; color:var(--chalk);}
.week-info-val.is-quiet{font-weight:500; color:var(--chalk-faint);}
.week-info-note{font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-dim); margin-top:2px;}
.week-pair{display:inline-block; max-width:100%;}
.week-vs{font-weight:500; color:var(--chalk-faint);}
@media (max-width:480px){
  .play-league-header h2{font-size:21px;}
}

/* ---------- Sync banner: unsaved / conflict — persistent, unlike the toast ----------
   It comes down from the top edge instead of appearing in one frame. The banner section is only
   rebuilt when its wording actually changes (renderPass patches sections by HTML), so this can
   never replay on a same-view re-render — a poll or a typed score leaves the node alone. */
.sync-banner{position:sticky; top:0; z-index:1500; display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:12px 18px; font-family:'Inter',sans-serif; font-size:14px; color:var(--chalk); border-bottom:1px solid var(--hairline-light);
  animation:bannerIn var(--popup-in) var(--zoom-ease-in);}
@keyframes bannerIn{from{opacity:0; transform:translateY(-100%);} to{opacity:1; transform:translateY(0);}}
.sync-banner.is-warn{background:#3a2d10;}
.sync-banner.is-conflict{background:#3a1a16;}
.sync-banner .btn{flex:none;}
.change-row{display:grid; grid-template-columns:110px 1fr auto; gap:12px; padding:8px 0; border-bottom:1px solid var(--hairline); font-size:13px;}
.change-row:last-child{border-bottom:none; padding-bottom:0;}   /* the card's own padding closes the list */
.change-when{font-family:'IBM Plex Mono',monospace; color:var(--brass);}
.change-what{color:var(--chalk);}
.change-who{font-family:'IBM Plex Mono',monospace; color:var(--chalk-faint); white-space:nowrap;}
@media (max-width:560px){ .change-row{grid-template-columns:1fr;} }

/* ---------- League players step + locked grid inputs ---------- */
.lp-list{max-height:320px; overflow-y:auto; border:1px solid var(--hairline); border-radius:10px; padding:6px; margin-top:10px;}
.lp-row{display:flex; align-items:center; gap:10px; padding:7px 8px; border-radius:7px; cursor:pointer;}
@media (hover:hover) and (pointer:fine){ .lp-row:hover{background:var(--panel-2);} }
.lp-row input[type=checkbox]{width:18px; height:18px; flex:none; margin:0;}
.lp-name{flex:1; font-family:'Inter',sans-serif; font-size:14px; color:var(--chalk);}
.lp-meta{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint);}
input:disabled{opacity:.55; cursor:not-allowed;}
/* the Holes box once the league has scores (form-trim): greyed as a locked box is, its count kept */
#f-holes:disabled{opacity:.55; cursor:not-allowed;}
/* A value the WEEK decides — its course, its front/back nine (week setup, 2026-09-24). It sits
   where the field would, in the same box shape, but it isn't one: quieter text, no focus, and a
   padlock saying the week (Schedule → Edit) is where it changes. */
.locked-field{display:flex; align-items:center; gap:8px; background-color:var(--ink-2); border:1px solid var(--hairline); border-radius:8px;
  padding:9px 11px; font-family:'Inter',sans-serif; font-size:14px; line-height:1.35; color:var(--chalk-dim); cursor:default;}
.locked-field .lock-icon{color:var(--chalk-faint);}
.locked-field span{min-width:0; overflow-wrap:anywhere;}
/* Courses are set ahead of time (owner, 2026-09-25; 20-helpers.js: courseNudge). The player wizard
   never has a course box: a week without a course reads "Course · set by the front desk", a quiet
   read-only line with the same padlock. The desk gets "Course not set" with Set course on the
   Tonight card and the Week-by-Week view: a brass notice (a nudge, not an error), the button its own
   tap target — pressing it never presses the Tonight block around it. */
.wizard-course-note{display:flex; align-items:center; gap:8px; font-family:'Inter',sans-serif; font-size:13.5px; color:var(--chalk-faint); padding:2px 0;}
.course-nudge{display:flex; align-items:center; gap:10px; padding:9px 10px 9px 12px; margin:0 0 12px;
  background:rgba(183,147,95,.09); border:1px solid rgba(183,147,95,.55); border-radius:10px;
  font-family:'Inter',sans-serif; font-size:13px; line-height:1.35; color:var(--chalk);}
.course-nudge-text{flex:1; min-width:0;}
.course-nudge-title{font-weight:600;}
.course-nudge-sub{font-size:12.5px; color:var(--chalk-dim); margin-top:2px;}
.course-nudge .btn{flex:none;}
.tn-block[data-action]:has(.course-nudge .btn:active){transform:none;}
.course-fill-note{margin-top:7px;}
/* (inside the week form's .field, whose label rule it has to outrank) */
.field .course-rest{display:flex; align-items:flex-start; gap:9px; margin:10px 0 0; font-family:'Inter',sans-serif; font-size:13px; font-weight:500; line-height:1.35; color:var(--chalk-dim); cursor:pointer;}
.course-rest input[type=checkbox]{flex:none; margin:0;}
.course-rest-n{display:block; font-size:12px; font-weight:400; color:var(--chalk-faint); margin-top:2px;}

/* ---------- Quiet save status (corner note) ----------
   A notice is not a tap, so it rises and fades rather than growing out of anything — but on the
   app's own curve and durations (--popup-in going up, --popup-out going away), not a one-off. */
/* "Offline since 7:42 PM" (#offline-status, 10-state.js: syncOfflineBadge) is the save note's twin
   in the other bottom corner: the same quiet box on the same rise and fade, one rule for both. It
   sits above the Present Board (z-index 900), so the TV shows it too. */
#save-status, #offline-status{position:fixed; bottom:12px; font-family:'IBM Plex Mono',monospace; font-size:12px; letter-spacing:.02em;
  color:var(--chalk-faint); background:var(--panel); border:1px solid var(--hairline); border-radius:6px; padding:4px 9px;
  opacity:0; transform:translateY(6px); pointer-events:none; z-index:2999;
  transition:opacity var(--popup-out) ease, transform var(--popup-out) ease;}
#save-status{right:14px;}
#offline-status{left:14px; display:flex; align-items:center; color:var(--chalk-dim); border-color:var(--hairline-light);}
#save-status.is-visible, #offline-status.is-visible{opacity:1; transform:translateY(0);
  transition:opacity var(--popup-in) var(--zoom-ease-in), transform var(--popup-in) var(--zoom-ease-in);}
#save-status.is-saved{color:var(--fairway-light); border-color:var(--hairline-light);}

/* ---------- Pull to check (pull-refresh; owner, 2026-09-25, feel 6: "yes") ----------
   36-pull-refresh.js. On every player screen the browser's own pull-to-refresh is off: it reloaded the
   whole page (the loading screen, League Central, a round dropped to "Round in progress"). So the page
   no longer bounces at its top or its foot there either. A pull at the very top brings #pull-refresh down
   from above the top edge instead: a round bubble (the corner notes' panel and edge) holding the
   lettering-free mark (--logo-mark, as the wizard's progress row), whose gold (--tracer) rises from the
   bottom as the finger pulls (--pull-fill, 0 to 1 at the threshold) while the bubble follows the finger
   (--pull-y, from -64px to its resting place, --pull-top: under the sync banner when one shows). Past the
   threshold the mark gives a small bump (is-armed). Let go past it: the bubble settles at rest on the tab
   highlight's spring (is-checking) and the mark breathes on the loading pulse, started at its midpoint so
   the full gold carries straight on, until the check lands; then it bubbles away (is-leaving: a small
   swell, then shrinking and fading over --popup-in), or turns into a small note in calm words (is-note;
   is-drained: the gold runs out of the mark when the check didn't happen). Let go early: it springs back
   up (is-springing) and the gold runs out with it. Words: 12px mono, --chalk-dim, never red. Reduced
   motion (is-quiet): no mark and no movement, only the words. It never takes a tap. */
html:has(.play-shell){overscroll-behavior-y:none;}
#pull-refresh{position:fixed; top:var(--pull-top, 14px); left:0; right:0; z-index:2998; box-sizing:border-box;
  width:max-content; max-width:calc(100vw - 40px); min-width:44px; min-height:44px; margin:0 auto; padding:8px;
  display:flex; align-items:center; justify-content:center; gap:9px;
  background:var(--panel); border:1px solid var(--hairline-light); border-radius:22px; box-shadow:0 4px 14px rgba(0,0,0,.4);
  font-family:'IBM Plex Mono',monospace; font-size:12px; line-height:1.35; letter-spacing:.02em; color:var(--chalk-dim);
  transform:translateY(var(--pull-y, -64px)); visibility:hidden; pointer-events:none;}
#pull-refresh.is-on{visibility:visible;}
.pull-mark{position:relative; display:block; flex:none; width:26px; height:calc(26px * 283 / 386); background-color:var(--hairline-light);
  -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;}
.pull-mark::after{content:""; position:absolute; inset:0; background-color:var(--tracer); transform:translateY(calc((1 - var(--pull-fill, 0)) * 101%));}
.pull-text:empty{display:none;}
#pull-refresh.is-note{padding:8px 16px 8px 12px; animation:pullNote var(--popup-in) cubic-bezier(.3,.7,.3,1);}
#pull-refresh.is-quiet{padding:8px 16px;}
#pull-refresh.is-quiet .pull-mark{display:none;}
#pull-refresh.is-checking, #pull-refresh.is-springing{transition:transform var(--glide) cubic-bezier(.3,1.3,.45,1);}
#pull-refresh.is-springing .pull-mark::after, #pull-refresh.is-drained .pull-mark::after{transition:transform var(--glide) ease;}
#pull-refresh.is-armed .pull-mark{animation:pullArm var(--popup-in) cubic-bezier(.3,.7,.3,1);}
#pull-refresh.is-checking .pull-mark{animation:loadingPulse 1.4s ease-in-out -.7s infinite;}
#pull-refresh.is-leaving{animation:pullAway var(--popup-in) cubic-bezier(.3,.7,.3,1) forwards;}
@keyframes pullArm{0%{scale:1;} 45%{scale:1.16;} 75%{scale:.96;} 100%{scale:1;}}
@keyframes pullNote{0%{scale:.92;} 65%{scale:1.035;} 100%{scale:1;}}
@keyframes pullAway{0%{scale:1; opacity:1;} 35%{scale:1.1; opacity:1;} 100%{scale:.4; opacity:0;}}
@media (prefers-reduced-motion: reduce){
  #pull-refresh, #pull-refresh .pull-mark, #pull-refresh .pull-mark::after{animation:none !important; transition:none !important;}
}

/* ---------- Save confirmation toast ---------- */
#app-toast{
  position:fixed; bottom:22px; left:50%; transform:translateX(-50%) translateY(20px);
  background:var(--panel); border:1px solid var(--fairway-light); color:var(--chalk);
  font-family:'Inter',sans-serif; font-size:13.5px; font-weight:600; padding:10px 18px;
  border-radius:10px; box-shadow:var(--shadow); z-index:3000; opacity:0; pointer-events:none;
  transition:opacity var(--popup-out) ease, transform var(--popup-out) ease;
  max-width:min(420px, calc(100vw - 32px)); text-align:center;
  width:max-content;   /* pinned at left:50%, it could only shrink into half the screen: ~188px on a phone */
}
#app-toast.is-visible{opacity:1; transform:translateX(-50%) translateY(0);
  transition:opacity var(--popup-in) var(--zoom-ease-in), transform var(--popup-in) var(--zoom-ease-in);}
#app-toast.is-error{border-color:var(--danger); color:var(--chalk); background:#2a1613;}
/* the padlock before "Score entry for Week 4 closed at 8:00 PM…" (35-player.js: roundStartRefusal); the drawn marks carry their own gap */
#app-toast .toast-lock{display:inline-flex; vertical-align:middle; margin-right:7px;}
/* A notice with one action on it ("Scorecard closed — not saved · Undo"): the same box, rise and
   fade, laid out as a row — the words, then the button (the sync banner's btn btn-sm, so it has the
   shared press, tint and focus ring). It takes taps only while it shows. max-content: a box pinned
   at left:50% would otherwise only get half the screen to shrink into (three lines on a phone); the
   toast's max-width still caps it. */
#app-toast.has-action{display:flex; align-items:center; gap:14px; padding:7px 7px 7px 16px; text-align:left; width:max-content;}
#app-toast.has-action.is-visible{pointer-events:auto;}
#app-toast .toast-act{flex:none; white-space:nowrap;}

/* ---------- Print: standings/schedule/whatever's on screen, minus the app chrome ---------- */
.print-header{display:none;}
@media print{
  body{background:#fff !important; color:#000 !important;}
  .masthead, .tabs, .tabsub, .no-print, #app-toast, .modal-overlay, .missing-scores-banner,
  .quick-log-banner, #login-overlay, .week-strip select, .week-strip .styled-select, .week-strip .tee-wrap{display:none !important;}
  .page-mark{display:none !important;}   /* paper carries no watermark */
  /* Skins on paper: both boards under their own titles, as before the Gross / Net toggle (league-list-skins) */
  .skins-toggle{display:none !important;}
  .skins-view .skins-board, .skins-view .skins-board > .mini-label:first-child{display:block !important;}
  .league-body{min-height:0;}           /* a sub-tab's screen-tall area is for the screen only (tabs-and-morph) */
  .print-header{display:block !important; margin-bottom:18px; border-bottom:2px solid #000; padding-bottom:10px;}
  .print-header h2{font-family:'Baloo 2',sans-serif; font-size:22px; color:#000; margin:0 0 4px;}
  .print-header-date{font-family:'IBM Plex Mono',monospace; font-size:12px; color:#444;}
  .app, .league-header, .team-card, .flipboard, .flip-row, .score-grid{
    background:#fff !important; color:#000 !important; border-color:#999 !important; box-shadow:none !important;
  }
  .badge{border-color:#999 !important; color:#000 !important;}
  /* Week-by-Week Scoring on paper (desk-scoring): a player's card prints its figures dark on white (on screen
     they are near-white and would vanish from paper), the ball that counted bold with a dark edge */
  .play-holes .pc-v, .play-holes .pc-sum:not(.pc-h), .play-holes .pc-tot:not(.pc-h):not(.pc-blank){background:#fff !important; color:#000 !important; border-color:#999 !important;}
  .play-holes .pc-v.is-counted{border-color:#000 !important;}
  .score-fig{color:#000 !important;}   /* the desk's numbers (cards-only): near-white on screen */
  /* the tick has to survive onto paper — without this a printed board shows empty boxes */
  input[type=checkbox]{-webkit-print-color-adjust:exact; print-color-adjust:exact; border-color:#666 !important;}
  a, [data-action]{color:#000 !important; text-decoration:none !important;}
  /* The Owner's Manual on paper (owners-manual): every topic, open or not, numbered, black on white; the screen's
     list (.manual, no-print) never prints */
  .manual-paper{display:block;}
  .manual-paper-intro{font-size:13px; color:#000; margin:0 0 14px;}
  .manual-paper-topic{margin:0 0 16px;}
  .manual-paper-topic h3{font-family:'Baloo 2',sans-serif; font-size:17px; color:#000; margin:0 0 6px; break-after:avoid;}
  .manual-paper-topic h3 .sec-ico{color:#000; vertical-align:-3px; margin-right:7px;}
  .manual-paper .manual-body{font-size:13px; line-height:1.5; color:#000;}
  .manual-paper .manual-body h4{color:#000; break-after:avoid;}
  .manual-paper .manual-body li{break-inside:avoid;}
  .manual-paper .manual-body li::marker, .manual-paper .manual-ui, .manual-paper .manual-mark{color:#000;}
}

/* ---------- Branded loading screen ---------- */
.app-loading{
  min-height:70vh; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px;
}
/* The loading mark is the masthead logo's light gold (--tracer, owner 2026-09-24), so the first-open
   fly-in is one colour from the mark to the logo. */
.app-loading-logo{
  width:70px; height:70px; background-color:var(--tracer);
  animation:loadingPulse 1.4s ease-in-out infinite;
}
.app-loading-text{font-family:'Baloo 2',sans-serif; font-size:18px; color:var(--chalk-faint); letter-spacing:.02em;}
@keyframes loadingPulse{0%,100%{opacity:.4; transform:scale(.94);} 50%{opacity:1; transform:scale(1);}}
/* "Couldn't load league data": what went wrong, how to restore when that is the fix (the command
   in the mono face, never broken across lines), and when the screen tries again by itself. */
.load-error-text{text-align:center; max-width:400px; margin-top:0; line-height:1.5;}
.load-error-cmd{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-dim); white-space:nowrap;}
.load-retry-note{font-family:'IBM Plex Mono',monospace; font-size:12px; letter-spacing:.02em; color:var(--chalk-faint); text-align:center;}

/* ---------- The © line (owner, 2026-09-24: "copyright at the bottom of every page, very small") ----------
   copyrightMarkup (20-helpers.js): the logo mark, quiet the way a section heading carries it, then
   "© 2026 Mulligan's Virtual Golf LLC" in the app's faint text, very small. It is the last thing on
   every staff and player page, on the sign-in, load-error and reload screens, and inside the hole
   step's bar (that screen is exactly one phone tall, so the bar is its foot). On the Present Board
   it sits in a bottom corner, in the board's own margin, clear of the tiles. Never in a popup. At 12px,
   the app's smallest print (look 3, 2026-09-26: nothing a person reads is under 12px). */
.app-copyright{display:flex; align-items:center; justify-content:center; gap:5px; margin:40px 0 0;
  font-family:'Inter',sans-serif; font-size:12px; font-weight:400; line-height:1.4; letter-spacing:.01em;
  color:var(--chalk-faint); white-space:nowrap; position:relative; z-index:1;}
.app-copyright-mark{width:12px; height:12px; background-color:var(--chalk-faint); opacity:.55; -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);}   /* small: the mark without its lettering (logos-popup) */
.play-shell + .app-copyright{margin-top:0;}      /* the player page already ends in 60px of room */
.app-loading + .app-copyright{margin-top:24px;}
.wizard-bar .app-copyright{margin-top:10px;}
#login-overlay .app-copyright{flex:none; margin-top:18px;}
/* The TV: fixed in the bottom-right corner, inside the 3.2u bottom margin every layout keeps, a size
   off the screen unit so it stays small at any distance; it never takes a tile's room. */
.app-copyright-tv{position:fixed; right:calc(var(--u) * 4); bottom:calc(var(--u) * .7); margin:0; z-index:2;
  font-size:max(12px, calc(var(--u) * 1.15)); line-height:1; pointer-events:none;}
.app-copyright-tv .app-copyright-mark{width:1em; height:1em;}
/* a portrait screen or a phone scrolls the board: the line follows it instead, at the right */
@media (max-aspect-ratio: 1/1), (max-width: 760px){
  .app-copyright-tv{position:static; justify-content:flex-end; margin-top:18px;}
}
@media print{
  .app-copyright{color:#444 !important;}
  .app-copyright-mark{background-color:#444; -webkit-print-color-adjust:exact; print-color-adjust:exact;}
}

/* ---------- Motion policy (owner's Apple Music recording, 2026-09-22) ----------
   Content never fades or slides in. Tab switches are instant; only the highlight glides.
   Whatever you tap grows out of its own box into the next screen or popup, and coming back
   shrinks it into that box again with the screen underneath already in place. Same-view
   re-renders never animate.
   Screens are morphed by the browser: 30-render-core.js puts one `view-transition-name: zoom`
   pair on each navigation (the tapped card on one side, the screen it opens on the other) and
   the browser animates between the two snapshots on the compositor. On the STAFF side "the
   screen" is the CONTENT section (#sec-main) only: the masthead and the sticky tab row are the
   same either side of the move, so they are named too (`masthead`, `tabs`) and get groups that
   sit above the morph and never dim — watching the logo and the tab row unfold out of a league
   card read as "a whole new page opening top to bottom". On the PLAYER side there is no
   persistent chrome, so the document element is named and its image is the whole viewport. The
   Bay Board's presentation is the third kind: the fixed overlay itself is named and grows out of
   the Present button (and shrinks back into it), with no chrome groups — it covers them.
   The page's one watermark (.page-mark) is named on both sides of every staff and player drill-in
   and drill-out (`mark`) and holds perfectly still, like the chrome.
   These rules are the whole choreography — stacking, durations, the dimmed screen underneath,
   and the rounded card-shaped clip. Stacking order, bottom to top: the page being left or
   arrived at (root), the morphing content (zoom), the masthead, the page's watermark (mark), the
   tab row (sticky: it sits above the content in the live page too). ---------- */
:root{--zoom-in:.24s; --zoom-out:.2s; --glide:.3s; --popup-in:.28s; --popup-out:.12s; --zoom-ease-in:cubic-bezier(.2,.85,.25,1); --zoom-ease-out:cubic-bezier(.3,.7,.4,1);}
::view-transition-group(root){z-index:0;}
::view-transition-group(zoom){z-index:1; animation-duration:var(--zoom-in); animation-timing-function:var(--zoom-ease-in);}
html.vt-out::view-transition-group(zoom){animation-duration:var(--zoom-out); animation-timing-function:var(--zoom-ease-out);}
/* The card's own image is the SMALL end of the morph, and it stays the size it was captured at.
   The UA fits every captured image to the group's width, which stretched the card into a giant
   ghost of itself ("…Night Scramble · 18 holes · Click to open →") sweeping across the header of
   the screen growing behind it. It is also only on screen at that end: the cross-fade runs over
   40% of the motion, front-loaded going in and back-loaded coming out, so the CONTENT image —
   which keeps the UA's width fit, and is what scales into and out of the card — carries the rest
   of the move and is legible early. */
html.vt-in::view-transition-old(zoom), html.vt-out::view-transition-new(zoom){inline-size:auto; block-size:auto;}
::view-transition-old(zoom), ::view-transition-new(zoom){animation-duration:calc(var(--zoom-in) * .4);}
html.vt-out::view-transition-old(zoom), html.vt-out::view-transition-new(zoom){animation-duration:calc(var(--zoom-out) * .4); animation-delay:calc(var(--zoom-out) * .6);}
/* the morphing box is card-shaped: clipped, rounded */
::view-transition-image-pair(zoom){overflow:clip; border-radius:12px;}
/* The morph on the compositor (compositor-pass, 2026-09-27): once a transition is ready, each group that moves is carried
   by a transform alone, its pictures scaled back and its corners held at their radius (30-render-core.js:
   vtCompositeGroups), all from the top-left corner. The path, the curve and the length are the browser's own. */
html.vt-own::view-transition-group(*), html.vt-own::view-transition-old(*), html.vt-own::view-transition-new(*){transform-origin:0 0;}
/* The sections paint no background of their own — the page's colour comes from <body> — so their
   snapshots are transparent between the cards and rows, and the screen underneath showed through
   them: a double image inside the growing box, and (from a scrolled list) the old page's text
   through the masthead. The content and the masthead carry that colour for as long as a staff morph
   is running, and only then: a permanent background on the content would print as a black slab.
   The tab row's wrapper never does (floating-tabs, 2026-09-25): the bar is opaque on its own, and a
   colour on the wrapper is the band across the screen the owner had taken away, so while a screen
   morphs the page shows all round the bar, dimming with the rest of the page being left. */
html.vt-staff #sec-main, html.vt-staff #sec-masthead{background:var(--ink);}
/* The persistent chrome (staff): above the morph, and STATIC. Both states contain it, so each
   group holds two identical images — show only the new one (a cross-fade of identical images
   ghosts) and never fade or dim them. The tab row may legitimately travel from where it was
   stuck to its natural place at the top of the new screen; that box move is all they animate. */
::view-transition-group(masthead){z-index:2;}
::view-transition-group(tabs){z-index:4;}
::view-transition-group(masthead), ::view-transition-group(tabs){animation-duration:var(--zoom-in); animation-timing-function:var(--zoom-ease-in);}
html.vt-out::view-transition-group(masthead), html.vt-out::view-transition-group(tabs){animation-duration:var(--zoom-out); animation-timing-function:var(--zoom-ease-out);}
::view-transition-old(masthead), ::view-transition-old(tabs){animation:none; opacity:0;}
::view-transition-new(masthead), ::view-transition-new(tabs){animation:none; opacity:1;}
/* A screen that grows out of a control IN the masthead and shrinks back into it (Settings, out of the
   gear; 30-render-core.js: vt-from-masthead) rides OVER the masthead's picture: under it the page would
   surface from behind the masthead, and on the way back the gear would sink out of sight. It stays under
   the page's watermark and the tab row: root 0, masthead 1, the morph 2, mark 3, tabs 4. */
html.vt-from-masthead::view-transition-group(masthead){z-index:1;}
html.vt-from-masthead::view-transition-group(zoom){z-index:2;}
/* On a phone the staff bar is at the foot of the screen (staff-dock, 2026-10-03): the bar itself is the `tabs` group (still,
   as ever), and the dock's skin under it, the bar's pill and shadow (or its round button, tucked), is a still group of its
   own, `dock`, over the morph and the masthead and under the bar: the page grows and shrinks under the bar's pill as it
   does under the bar. Its two pictures are the same; the new one shows as it is. */
::view-transition-group(dock){z-index:3;}
::view-transition-old(dock){animation:none; opacity:0;}
::view-transition-new(dock){animation:none; opacity:1;}
/* The page's watermark (logo-pass, 2026-09-25): through every staff and player drill-in and drill-out
   it is as still as the chrome. Named `mark` on both sides (30-render-core.js: vtNameMark), it gets a
   group of its own whose two images are the same picture in the same place: no box move (the group's
   animation is off), no cross-fade, never dimmed. It sits over the morph and the masthead — their
   pictures are opaque --ink while they move, so under them it would vanish and come back — and under
   the tab row, whose bar covers it in the page too. Blended with lighten, its --ink-mixed colour shows only
   where the pictures under it are darker than it, the page's --ink, exactly as in the page, and never
   over a card, a row or a word, which cover it in the page. (The screen dimmed under the morph is
   darker than the mark too, so over it the mark keeps its own strength: it never dims.) The Present
   Board leaves it in the page, which dims under the growing board like the masthead and the tab row. */
::view-transition-group(mark){z-index:3; animation:none; mix-blend-mode:lighten;}
::view-transition-old(mark){animation:none; opacity:0;}
::view-transition-new(mark){animation:none; opacity:1;}
/* drill-in: the old screen stays put and dims under the growing card (filter is composited).
   Staff: the new screen also has a root image (everything outside the named sections) — it is
   hidden, because the zoom group covers the content area and the chrome groups cover the rest. */
html.vt-in::view-transition-old(root){animation:vt-dim var(--zoom-in) ease forwards; opacity:1;}
html.vt-in::view-transition-new(root){animation:none; opacity:0;}
@keyframes vt-dim{from{filter:brightness(1);} to{filter:brightness(.55);}}
/* drill-out: the destination screen is there at once, brightening under the shrinking card;
   the screen being left is inside the zoom group, so its root image is hidden. */
html.vt-out::view-transition-new(root){animation:vt-undim var(--zoom-out) ease; opacity:1;}
html.vt-out::view-transition-old(root){animation:none; opacity:0;}
@keyframes vt-undim{from{filter:brightness(.55);} to{filter:brightness(1);}}
/* A STAFF drill-in or out (tabs-and-morph, 2026-09-26; owner: "it shows grey space filling into the
   background at the top by the tab bar"): the page picture keeps its own colour, and what changes is drawn
   by two layers over the content column only (30-render-core.js: vtDimRect, named `dim` and `veil`). The
   strip round the floating tab bar, between the masthead and the content, and the page's side margins are
   the same empty page on both screens, so they show exactly as at rest for the whole morph, with the
   watermark at its own strength over them. The DIM's group is the black: 0 → .45 going in and .45 → 0
   coming out, on the same curve, which is brightness 1 → .55 and back, the dim the page always had. The
   VEIL's group is the page's colour, over content scrolled under the pinned bar that only one screen has:
   it fades that content away into the page going in (0 → 1) and up out of it coming out (1 → 0). Both sit
   over the page picture (root, 0: they come after it) and under the morphing content (1), the masthead
   (2), the watermark (3) and the tab bar (4). The player side and the Present Board keep the whole-page
   dim above. */
html.vt-staff.vt-in::view-transition-old(root){animation:none; filter:none;}
html.vt-staff.vt-out::view-transition-new(root){animation:none; filter:none;}
::view-transition-group(dim){z-index:0; background:#000; animation:none;}
::view-transition-group(veil){z-index:0; background:var(--ink); animation:none;}
html.vt-in::view-transition-group(dim){animation:vt-shade var(--zoom-in) ease forwards;}
html.vt-out::view-transition-group(dim){animation:vt-unshade var(--zoom-out) ease forwards;}
html.vt-in::view-transition-group(veil){animation:vt-veil-in var(--zoom-in) ease forwards;}
html.vt-out::view-transition-group(veil){animation:vt-veil-out var(--zoom-out) ease forwards;}
::view-transition-old(dim), ::view-transition-new(dim), ::view-transition-old(veil), ::view-transition-new(veil){animation:none;}
@keyframes vt-shade{from{opacity:0;} to{opacity:.45;}}
@keyframes vt-unshade{from{opacity:.45;} to{opacity:0;}}
@keyframes vt-veil-in{from{opacity:0;} to{opacity:1;}}
@keyframes vt-veil-out{from{opacity:1;} to{opacity:0;}}
/* First open (30-render-core.js: playBootMorph): the loading mark grows into the masthead logo (the
   league-list logo on the player side). Only the logo moves — the app is in place from the first
   frame, because content never fades in — and it is short. The logo starts exactly over the mark
   (--bx/--by/--bk, set per open), is there by 35 %, passes its own size at 65 % and settles: the
   dropdowns' popupBubble, on their curve and --popup-in. The gold mark rides the same path on the
   motion layer and is gone by 45 %, so the gold becomes the logo instead of blinking into it. */
.boot-land{animation:bootLand var(--popup-in) cubic-bezier(.3,.7,.3,1) both; transform-origin:50% 50%; will-change:transform;}
@keyframes bootLand{
  0%{transform:translate(var(--bx, 0px), var(--by, 0px)) scale(var(--bk, 1)); opacity:0;}
  35%{opacity:1;}
  65%{transform:translate(0px, 0px) scale(1.06);}
  100%{transform:translate(0px, 0px) scale(1); opacity:1;}
}
.boot-leave{animation:bootLeave var(--popup-in) cubic-bezier(.3,.7,.3,1) both; transform-origin:50% 50%; will-change:transform;}
@keyframes bootLeave{
  0%{transform:translate(0px, 0px) scale(1); opacity:1;}
  45%{opacity:0;}
  65%{transform:translate(var(--bx, 0px), var(--by, 0px)) scale(calc(var(--bk, 1) * 1.06));}
  100%{transform:translate(var(--bx, 0px), var(--by, 0px)) scale(var(--bk, 1)); opacity:0;}
}
/* The staff masthead logo is the loading mark's own gold now (masthead-gold), and it starts exactly
   over the mark — same shape, same colour — so it flies in solid from the first frame: the fade that
   turned the gold mark into the old white logo would only dim it mid-flight. (!important outranks
   the animation's opacity; the mark's ghost still fades on top of it, where it changes nothing.) */
.logo-img.boot-land{opacity:1 !important;}
/* The player picker's big logo too: it faded in from 0 (35 % there) while the mark's ghost faded out
   over it, so the flight dipped to ~82 % around 10 %. It flies in solid; the ghost still fades on top. */
.play-logo.boot-land{opacity:1 !important;}
/* a leaving popup is moved onto this layer so nothing in it can be tapped while it shrinks */
#zoom-over{position:fixed; inset:0; z-index:1300; overflow:hidden; pointer-events:none;}
#zoom-over:empty{display:none;}
/* Popups (owner, 2026-09-26: "it doesn't feel like the background is changing"): the box grows out of
   the control that opened it on popupBubble's shape and shrinks back into it (30-render-core.js:
   playModalIn / playModalOut), and the page behind recedes IN STEP with it: the backdrop — one layer, the
   dim (its blur went with player-popup) — comes in over the 65 % of --popup-in the box takes to reach full
   size, on the same curve, and goes over --zoom-out, the box's own way back, from however far it had come in
   (--backdrop-from). The overlay itself never fades: a fading parent made the box see-through over the
   page. A popup nothing opened (no tap) bubbles out of its own middle. */
.modal-overlay::before{animation:overlayFadeIn calc(var(--popup-in) * .65) cubic-bezier(.3,.7,.3,1);}
@keyframes overlayFadeIn{from{opacity:0;} to{opacity:1;}}
.modal{animation:popupBubble var(--popup-in) cubic-bezier(.3,.7,.3,1);}
/* Pushed up and away (owner, 2026-09-25: "push it back up like iphone notifications"; 30-render-core.js:
   popupPushStart). While a finger pushes a popup the page behind comes back in step with the box (the
   backdrop's own clock, pushDimTo; --push-dim only where a pseudo-element can't be animated), and goes back
   with it on a spring back (the box's own spring and time, so the two stay together; is-push-return for
   --push-dim); a push that makes it leaves through .modal-exit below, like every close. is-pushed stays on once
   a push has begun: taking it off would replay the backdrop's fade-in. (No text is selected in the grab zone,
   the only place a push starts: user-select is set there once, not on the whole popup at every push.) */
.modal-overlay.is-pushing .modal{will-change:transform;}
.modal-overlay.is-pushed::before{animation:none; opacity:var(--push-dim, 1);}
.modal-overlay.is-push-return::before{transition:opacity var(--glide) cubic-bezier(.3,1.3,.45,1);}
/* …and a popup a finger can push shows a small grab bar over its title on touch screens (it scrolls away
   with the title; nothing changes with a mouse). A finger on it lands on the title's grab zone under it. */
@media (hover:none) and (pointer:coarse){
  .modal.modal-pushable::before{content:""; position:absolute; top:8px; left:50%; width:36px; height:4px; margin-left:-18px; border-radius:2px; background:var(--hairline-light); pointer-events:none;}
}
.modal-overlay.modal-exit{pointer-events:none;}
.modal-overlay.modal-exit::before{animation:backdropOut var(--zoom-out) var(--zoom-ease-out) forwards;}
@keyframes backdropOut{from{opacity:var(--backdrop-from, 1);} to{opacity:0;}}
/* Same-view re-renders (draft edits, typed scores, polls) never replay the popup animation — nor cut
   short the one a popup is still coming in on (`is-coming-in`, live only, for --popup-in; a class no rule on
   any other element uses, so taking it off restyles the box and its backdrop alone) */
.app.no-anim .modal-overlay:not(.is-coming-in) .modal, .app.no-anim .modal-overlay:not(.is-coming-in)::before{animation:none !important;}
/* ---------- One popup becoming the next ----------
   A confirm opened from inside the league form, a round opened from the rounds list: the old box
   used to be cut in one frame while the new one grew out of the button. Now the old one is moved
   onto the motion layer and shrinks away (transform + opacity only) while the new one grows.
   The BACKDROP never moves: the leaving overlay's backdrop is dropped on the spot and the
   arriving one does not fade in, so the screen behind never flashes between the two. */
.modal-overlay.modal-swap-out{pointer-events:none;}
.modal-overlay.modal-swap-out::before{animation:none; opacity:0;}
.modal-overlay.modal-swap-out .modal{
  transition:transform var(--popup-out) ease, opacity var(--popup-out) ease;
  transform:scale(.96); opacity:0;
}
.modal-overlay.is-swap-in::before{animation:none;}
.course-popup .select-option-row{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
/* Dropdown popups (the select list, the calendar, the course list) BUBBLE out of the control
   they belong to (owner, 2026-09-23: the first version "doesn't match the bubbly animation we've
   been using") — transform only, origin on the edge that touches it (30-render-core.js:
   popupOriginFor). The rows are instant: the list is never staggered. A popup that is only
   re-positioned or re-filled keeps .is-open, so nothing replays; one leaving shrinks a little
   and goes. Without the classes — reduced motion, the test harness — the popup is simply
   there, which is what it did before. */
.popup-zoom{opacity:0; transform:scale(.72) translateY(var(--pop-dy, -6px)); transform-origin:0 0; will-change:transform;}
.popup-zoom.is-open{animation:popupBubble var(--popup-in) cubic-bezier(.3,.7,.3,1) both;}
/* The bubble: it comes up out of the control (--pop-dy points AT the control: negative when the
   popup hangs below it, positive when it had to open above), passes its full size at 65 % and
   settles back — the same overshoot the tab highlight has, encoded in the keyframes so the curve
   itself never has to overshoot. Opacity is done by 45 %, so the list is readable while it settles. */
@keyframes popupBubble{
  0%{transform:scale(.72) translateY(var(--pop-dy, -6px)); opacity:0;}
  45%{opacity:1;}
  65%{transform:scale(1.035) translateY(0);}
  100%{transform:scale(1) translateY(0); opacity:1;}
}
.popup-zoom.popup-exit{opacity:0; transform:scale(.94); pointer-events:none; transition:transform var(--popup-out) ease, opacity var(--popup-out) ease;}
@media (prefers-reduced-motion: reduce){ .popup-zoom{opacity:1; transform:none;} }

/* ---------- Disclosure panels: they unfold out of their own toggle ----------
   The Bay Board's Board Settings and the Leagues tab's archived list used to appear and vanish
   in one frame, shoving whatever sat under them (owner, 2026-09-23: "the bay board still drops
   down without a bubbly animation"). The wrapper is a one-row grid whose row runs 0fr → 1fr, so
   the page below SLIDES down instead of jumping, and the panel inside bubbles out of the toggle
   on the same keyframes the dropdowns use — origin at the toggle's centre on the panel's top
   edge (30-render-core.js: openRevealMotion → popupOriginFor). Closing folds back up and fades
   over --popup-out, and only then does the state flag flip (toggleReveal), because the render
   that flips it would otherwise delete the panel mid-fold. `.is-opening` is added ONLY to a
   panel that appeared in this pass, so a re-render while it is open (picking a week inside it,
   the 30s refresh) leaves it open and still. These rules are deliberately NOT under
   `.app.no-anim`: a disclosure is local UI state, so the view key never changes when it opens,
   and the no-anim class is on for exactly the render that opens it. */
.reveal{display:grid; grid-template-rows:0fr; transition:grid-template-rows var(--popup-in) var(--zoom-ease-in);}
.reveal > .reveal-inner{min-height:0; overflow:hidden;}
.reveal.is-open{grid-template-rows:1fr;}
.reveal.is-opening > .reveal-inner > *{animation:popupBubble var(--popup-in) cubic-bezier(.3,.7,.3,1) both; transform-origin:var(--reveal-origin, 50% top);}
.reveal.is-closing{grid-template-rows:0fr; transition:grid-template-rows var(--popup-out) var(--zoom-ease-out);}
.reveal.is-closing > .reveal-inner{opacity:0; transition:opacity var(--popup-out) ease;}

/* ---------- Lists that change while you look at them (2026-09-24) ----------
   32-list-motion.js: standings rows glide to their new rank and their numbers roll; quick-log
   buttons fold away as teams report; the Scorecards list settles on a search or Show more; a
   deleted row folds shut before the change renders. Only on the SAME view — navigation is never
   animated. Moves are transforms, arrivals transform + opacity; anything leaving folds on the
   Board Settings timings (inline: --popup-out on --zoom-ease-out, the .reveal.is-closing ones).
   The ticket list is a flex column so its spacing is a gap, never a collapsing margin: a card
   folding away there cancels exactly one gap and the list closes up with nothing left to jump. */
.ticket-list{display:flex; flex-direction:column; gap:14px; margin-bottom:14px;}
.ticket-list:empty{display:none;}
.ticket-list > .ticket{margin-bottom:0;}
/* an arriving card rises a few px and fades in, bubble-shaped (opaque by 45 %, a hair past its
   place at 65 %); `translate` so a move on the same card composes with it */
.list-rise{animation:listRise var(--popup-in) cubic-bezier(.3,.7,.3,1) both;}
@keyframes listRise{
  0%{opacity:0; translate:0 6px;}
  45%{opacity:1;}
  65%{translate:0 -1px;}
  100%{opacity:1; translate:0 0;}
}
/* A number counting (compositor-pass; 30-render-core.js: playCountStrip): the points rolling on a Standings row, the
   celebration's score. A window the size of the element's own line, over it (its own text is kept, out of sight), holding
   every number the count shows, one under the other; the strip jumps from line to line on the compositor. Live only. */
.count-strip{position:absolute; overflow:hidden; pointer-events:none;}
.count-strip-run{display:block; width:max-content;}
.count-strip-run.is-grid{display:grid;}
.count-strip-run > span{display:block; white-space:nowrap;}
/* A fold on the compositor (compositor-pass; 32-list-motion.js: flowPlan, flowPlay): a box whose edge moves is drawn by a
   skin of three pieces under its contents (its own paint lifted meanwhile), and a folding item's contents sit in a window
   that is scaled with it, scaled back inside. Live only, and gone when the fold lands. */
.flow-skin{position:absolute; z-index:-1; pointer-events:none; box-sizing:border-box; border-style:solid;}
.flow-clip{position:absolute; left:0; top:0; overflow:hidden; transform-origin:0 0;}
.flow-inner{box-sizing:border-box; transform-origin:0 0;}
/* a standings row in flight: the climber passes over the row it overtakes, and a lead row's
   translucent tint is laid on the page colour so the row underneath never shows through it */
.flip-row.is-moving{position:relative;}
.flip-row.lead.is-moving{background:linear-gradient(rgba(232,199,154,.08), rgba(232,199,154,.08)) var(--ink);}
[data-ghost]{pointer-events:none;}
/* ---------- Who moved since the last league night (35-player.js: standingsMoveMark; live-updates, see 6) ----------
   A small, quiet mark under a row's place: a drawn chevron and the places moved, up in the links' gold,
   down in the faint grey (never red: nothing is wrong). It is the row's last child, laid over the place
   column (the grid area of column 1 across every line the row has, so one rule serves the desk's one
   line and the phone's two; the end line is spelled out, since an absolutely placed child's "auto" end
   is the row's far edge) just below the row's middle, where the place column is empty: no row grows,
   nothing wraps and every cell stays where it was. Paper never shows it (no-print). */
.standings-board .flip-row{position:relative;}
.st-move{position:absolute; grid-column:1 / 2; grid-row:1 / -1; left:50%; top:calc(50% + 6px); transform:translateX(-50%);
  display:inline-flex; align-items:center; gap:1px; font-family:'Inter',sans-serif; font-size:12px; font-weight:600; line-height:12px; white-space:nowrap;}
.st-move.is-up{color:var(--fairway-light);}
.st-move.is-down{color:var(--chalk-faint);}
/* ---------- A round that lands while you watch (32-list-motion.js: listWash; live-updates, feel 7) ----------
   The TV's card-in wash (bayboard-tv: the tracer ring and gold glow of reportGlow over --report-glow,
   carried over a rebuild by --glow-ago), once, on a player's Standings row or Scoring card that just took a
   round in, while the row glides. A row's is drawn inside it (the board clips its edges and the rows
   touch); a card's glows round it like the TV's tile. Put on by script, never in the markup. */
.is-just-in{position:relative;}
.is-just-in::after{content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
  background:rgba(232,199,154,.10); box-shadow:inset 0 0 0 1.5px var(--tracer), inset 0 0 22px rgba(232,199,154,.3);
  animation:reportGlow var(--report-glow) ease-out both; animation-delay:calc(0ms - var(--glow-ago, 0ms));}
.team-card.is-just-in{z-index:2;}
.team-card.is-just-in::after{inset:-1px; background:none;
  box-shadow:0 0 0 1.5px var(--tracer), 0 0 26px 5px rgba(232,199,154,.45), inset 0 0 22px rgba(232,199,154,.22);}
@media (prefers-reduced-motion: reduce){ .is-just-in::after{animation:none !important; opacity:0;} }

/* ---------- The press: one answer to a tap, everywhere ----------
   Whatever you tap grows out of its own box — but it should also answer the finger while it is
   still under it. Every tappable control in the app (buttons, cards, tickets, chips, rows,
   option rows, calendar days, steppers, back links, tabs) dips to 97 % for as long as it is
   held, on the same .12s the tab pills have always used. Text fields and selects are left out
   on purpose: a box that moves while you are typing or aiming at an option reads as a mis-tap.
   Checkboxes have their own, deeper press (above) because .97 on an 18px box is half a pixel.
   Disabled controls never move. `touch-action:manipulation` keeps the tap on the same frame —
   without it the browser waits to see whether a second tap is coming.
   HOVER TINTS ARE A MOUSE'S ONLY (wizard-feel, 2026-09-26; owner, feel 4: "+ and − go back to
   normal when you let go"). A phone keeps :hover on whatever was tapped last, so a − / + stayed
   solid gold and a player's name stayed tinted after its popup closed. Every :hover rule in this
   file (29) now sits, where it always was, inside @media (hover:hover) and (pointer:fine): a real
   mouse. A finger gets the press while it is down, a stepper's own :active tint, and the number's
   pop, and everything is back to normal the moment it lifts. */
.tab-btn, .play-tab, .skins-toggle-btn, .btn, .league-card, .tn-block[data-action], .play-league-card, .ticket[data-action], .back-link, .play-back,
.tabsub button, .select-option-row, .dp-day, .dp-nav, .dp-today-btn, .dp-clear-btn, .ns-btn,
.quick-log-team-btn, .lp-row, .roster-chip button, .player-row-top button, .player-row-name,
.player-lookup-row, .bayboard-present-exit, .bayboard-settings-toggle, .manual-toggle, .pick-chip, .play-sched-row[data-action],
.upcoming-team-row[data-action], .date-field-trigger{touch-action:manipulation;}
.ticket[data-action], .back-link, .play-back, .tabsub button, .select-option-row, .dp-day, .dp-nav,
.dp-today-btn, .dp-clear-btn, .ns-btn, .quick-log-team-btn, .lp-row, .roster-chip button,
.player-row-top button, .player-row-name, .player-lookup-row, .bayboard-present-exit, .modal-x,
.bayboard-settings-toggle, .manual-toggle, .pick-chip, .play-sched-row[data-action], .upcoming-team-row[data-action], .date-field-trigger, .tee-date{
  transition:background-color .15s ease, border-color .15s ease, color .15s ease, transform .12s ease;
}
.tab-btn:active, .play-tab:active, .skins-toggle-btn:active, .btn:active, .league-card:active, .tn-block[data-action]:active, .play-league-card:active,
.ticket[data-action]:active, .back-link:active, .play-back:active, .tabsub button:active,
.select-option-row:active, .dp-day:active, .dp-nav:active, .dp-today-btn:active, .dp-clear-btn:active,
.ns-btn:active, .quick-log-team-btn:active, .lp-row:active, .roster-chip button:active,
.player-row-top button:active, .player-row-name:active, .player-lookup-row:active,
.bayboard-present-exit:active, .modal-x:active, .bayboard-settings-toggle:active, .manual-toggle:active, .pick-chip:active, .play-sched-row[data-action]:active,
.upcoming-team-row[data-action]:active, .date-field-trigger:active, .tee-date:active{transform:scale(.97);}
button:disabled:active, .select-option-row.disabled:active, .pick-chip.is-blocked:active{transform:none;}
/* A date box wearing its tee (.tee-wrap .tee-date) presses as one piece — the wrapper takes the
   press above, the box inside stays put in it — so the tee never slides across the box. */
.tee-date > .date-field-trigger:active{transform:none;}

/* ---------- Press and slide the tab bar ----------
   (owner, 2026-09-23: "make it so that you can click and slide the tab bar if you want and same on
   the player end".) The slide itself is in 30-render-core.js (tabDragDown → tabDrag); these are the
   three things it cannot do from JS.
   TOUCH: `.tabsub` — and `.tabs` at phone widths — scrolls sideways with a finger and has to keep
   doing it, so only the tab that is ALREADY chosen takes the touch away from that scroll; every
   other tab, and the gaps, stay the browser's to pan, and a plain tap there still clicks.
   `.play-tabs` never scrolls sideways (it wraps), so a sideways drag anywhere on it is ours while
   the page still pans vertically.
   THE PRESS: the shared :active dip would fire tab after tab under a sliding pointer, so the row's
   buttons hold still for as long as the drag lasts and the cursor says grabbing.
   THE LABELS: never selectable — a mouse drag across a tab row would otherwise paint them blue. */
.tabs, .tabsub, .play-tabs{-webkit-user-select:none; user-select:none;}
.play-tabs{touch-action:pan-y;}
.tabs .tab-btn.active, .tabsub button.active, .play-tab.active{touch-action:none;}
.tabs.is-dragging, .tabsub.is-dragging, .play-tabs.is-dragging,
.tabs.is-dragging .tab-btn, .tabsub.is-dragging button, .play-tabs.is-dragging .play-tab{cursor:grabbing;}
.tabs.is-dragging .tab-btn:active, .tabsub.is-dragging button:active, .play-tabs.is-dragging .play-tab:active{transform:none;}

/* ---------- Entering scores: little confirmations (owner's mockups #4 and #5, approved 2026-09-24) ----------
   THE ROUND'S PROGRESS (#4, the owner's version): under "Hole N", one small Mulligan's mark per hole —
   the owner's own logo without its "Indoor Virtual Golf" lettering (logo-mark.png, cut from
   logo-mask.png by tools/logo-mark.js; the standalone build carries it inside the page). Faint until
   every box on the hole has a number, then filled with the masthead logo's gold (--tracer); the hole
   you are on pops up, bigger and brighter, and that alone marks it (owner, 2026-09-25: no underline).
   24px marks (2026-09-25: "a tad bit bigger"), NINE at a time: .wiz-progress is a window exactly one
   nine wide (--page) that clips a track (.wiz-track) carrying the nines side by side; the markup's
   --nine says which one shows, so an 18-hole round slides from the front nine to the back nine at
   hole 10 and back at hole 9. Each nine keeps a little room around its marks (--pad-x, --pad-y) so
   a mark popping up or settling is never clipped. The gold RISES through a mark from the bottom as
   its hole is completed and the mark settles with a small bump (is-filling); on a move to another
   hole the new mark springs up while the old one settles back (is-arriving / is-leaving), and a move
   between the nines glides the track on the app's softer spring (is-gliding, wizGlide: from
   --from-nine to --nine over --glide, held on the old nine for --glide-delay first). All four classes
   are live-only (35-player.js); without them (reduced motion, the test harness) the row simply shows
   the right nine.
   THE NUMBER POPS (#5a): a − / + tap on a wizard stepper bubbles its number (nsPop, --popup-in). Its gold is the
   text's fill (-webkit-text-fill-color), not `color` (round-scroll, 2026-10-03): the same gold at every moment of
   the pop, pixel for pixel, but with `color` WebKit repainted the round screen's own (screen-sized) layer on every
   frame of the pop, changing none of its pixels.
   THE BOX FLASHES (#5b): on the staff paper card, the hole box that just got a complete score glows
   gold and settles while the cursor has already moved on (holeFlash, --hole-flash). */
:root{--logo-mark:url("/assets/logo-mark-small.png"); --logo-fill:.7s; --hole-flash:.55s;}
.wiz-progress{--w:24px; --gap:7px; --pad-x:6px; --pad-y:5px; --h:calc(var(--w) * 283 / 386);
  --page:calc(9 * var(--w) + 8 * var(--gap) + 2 * var(--pad-x));
  width:var(--page); margin:0 auto calc(14px - var(--pad-y)); overflow:hidden;}
/* tucked up under the hole number (its own 18px gap below); the marks sit where they always did */
.wizard-hole-num + .wiz-progress{margin-top:calc(-9px - var(--pad-y));}
/* THE TEAM'S SCORE SO FAR (totals-line, 2026-09-27; the owner's pick of the running-totals mockup, option A): one
   quiet line under the logos, "Thru 9 · 33 net best ball" (35-player.js: renderWizardSoFar; put right in place as a box
   changes, paintWizardSoFar). 13px grey words, the number 14px mono, one line. The line is 18px tall with its words
   or without (empty before the first hole is done; the mono number alone would make it 18.5), so the boxes under it
   never move as it fills or empties; nothing about it moves. */
.wizard-so-far{height:18px; margin:-2px 0 14px; text-align:center; font-family:'Inter',sans-serif; font-size:13px; line-height:18px; color:var(--chalk-faint); white-space:nowrap;}
.wizard-so-far b{font-family:'IBM Plex Mono',monospace; font-size:14px; font-weight:600; color:var(--chalk-dim);}
.wiz-track{display:flex; transform:translateX(calc(var(--nine, 0) * -1 * var(--page)));}
.wiz-track.is-gliding{animation:wizGlide var(--glide) cubic-bezier(.3,1.15,.4,1) backwards; animation-delay:var(--glide-delay, 0ms);}
@keyframes wizGlide{from{transform:translateX(calc(var(--from-nine, 0) * -1 * var(--page)));}}
.wiz-nine{display:flex; flex:none; gap:var(--gap); width:var(--page); padding:var(--pad-y) var(--pad-x);}
.wiz-logo{position:relative; display:block; flex:none; width:var(--w); height:var(--h); background-color:var(--hairline-light);
  -webkit-mask-image:var(--logo-mark); mask-image:var(--logo-mark);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
.wiz-logo::after{content:""; position:absolute; inset:0; background-color:var(--tracer); transform:translateY(101%);}
.wiz-logo.is-filled::after{transform:none;}
.wiz-logo.is-current{transform:scale(1.25); background-color:var(--chalk-faint);}
/* --fill-ago: a move to another hole redraws the row; a fill still rising carries on from where it had
   got to (35-player.js: carryWizardFills), together with the mark's own move if it has one */
.wiz-logo.is-filling{animation:wizFillSettle var(--logo-fill) cubic-bezier(.3,.7,.3,1) both; animation-delay:calc(0ms - var(--fill-ago, 0ms));}
.wiz-logo.is-filling::after{animation:wizFillRise var(--logo-fill) cubic-bezier(.5,0,.3,1) both; animation-delay:calc(0ms - var(--fill-ago, 0ms));}
.wiz-logo.is-filling.is-arriving{animation:wizFillSettle var(--logo-fill) cubic-bezier(.3,.7,.3,1) both, wizArrive var(--glide) cubic-bezier(.3,1.3,.45,1); animation-delay:calc(0ms - var(--fill-ago, 0ms)), 0ms;}
.wiz-logo.is-filling.is-leaving{animation:wizFillSettle var(--logo-fill) cubic-bezier(.3,.7,.3,1) both, wizLeave var(--glide) cubic-bezier(.3,1.3,.45,1); animation-delay:calc(0ms - var(--fill-ago, 0ms)), 0ms;}
/* the gold climbs the whole mark in the first 60 % (slow off the bottom so the rise is seen, soft at
   the top), then the mark gives its small settle: a bump past its size and back */
@keyframes wizFillRise{0%{transform:translateY(101%);} 60%, 100%{transform:translateY(0);}}
@keyframes wizFillSettle{0%, 58%{scale:1;} 76%{scale:1.12;} 90%{scale:.97;} 100%{scale:1;}}
.wiz-logo.is-arriving{animation:wizArrive var(--glide) cubic-bezier(.3,1.3,.45,1);}
.wiz-logo.is-leaving{animation:wizLeave var(--glide) cubic-bezier(.3,1.3,.45,1);}
@keyframes wizArrive{from{transform:none; background-color:var(--hairline-light);}}
@keyframes wizLeave{from{transform:scale(1.25); background-color:var(--chalk-faint);}}
.number-stepper input.ns-pop{animation:nsPop var(--popup-in) cubic-bezier(.3,.7,.3,1);}
@keyframes nsPop{
  0%{transform:scale(1);}
  35%{transform:scale(1.32); -webkit-text-fill-color:var(--tracer);}
  65%{transform:scale(.95);}
  100%{transform:scale(1);}
}
/* --flash-ago: the staff form redraws on every complete score, so a glow still fading on an earlier
   box is put back on the new box from where it had got to (60-scorecards.js: carryHoleFlashes) */
input.hole-flash{animation:holeFlash var(--hole-flash) cubic-bezier(.3,.7,.3,1); animation-delay:calc(0ms - var(--flash-ago, 0ms));}
@keyframes holeFlash{
  0%{background-color:rgba(232,199,154,.36); border-color:var(--tracer); transform:scale(1.07);}
  30%{transform:scale(.98);}
  55%{transform:scale(1); background-color:rgba(232,199,154,.16);}
}

/* ---------- Login gate (server version only) ---------- */
/* A column: the card centred in the room the © line leaves at the foot (its auto margins), the line
   under it; on a screen too short for both they simply stack and the overlay scrolls. */
#login-overlay{
  position:fixed; inset:0; z-index:2000; background:var(--ink);
  display:flex; flex-direction:column; align-items:center; justify-content:center; padding:20px; overflow-y:auto;
}
.login-card{
  background:var(--panel); border:1px solid var(--hairline-light); border-radius:16px;
  padding:36px 32px; width:100%; max-width:380px; text-align:center; box-shadow:var(--shadow);
  margin:auto 0;
}
.login-logo{
  width:90px; height:90px; margin:0 auto 14px; background-color:var(--fairway);
  -webkit-mask-image:var(--logo-mask); mask-image:var(--logo-mask);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}
.login-card h2{font-family:'Baloo 2',sans-serif; font-size:26px; margin:0 0 4px; color:var(--chalk);}
.login-sub{font-size:13px; color:var(--chalk-faint); margin:0 0 22px;}
.login-card input{margin-bottom:12px; text-align:center; font-size:16px;}
.login-error{margin-top:12px; font-size:13px; color:var(--danger); min-height:18px;}
/* a player who reached the staff sign-in (an address with /play taken off it): the way back to the players' site (play-site) */
.login-play{display:inline-block; margin-top:14px; font-size:13px; color:var(--fairway-light); text-decoration:none;}
/* (taps respond on the same frame — touch-action is in the shared press block above) */

/* ---------- The seeded playoffs (playoffs, 2026-10-07; 52-playoffs-view.js) ----------
   The desk's Playoffs tab and, on the player side, the same bracket read-only above the season table: rounds as columns on a
   desk (one under another on a phone), each group named (Semifinals, Final, Third place, Placement · places 5-8), each match
   two rows (seed, team, that night's score), the winner in gold, the team that went out quieter. Nothing here moves. */
.po-wrap, .po-player{margin-bottom:18px;}
.po-actions{display:flex; gap:8px; flex-wrap:wrap;}
.po-champ{display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 16px; padding:12px 16px; border:1px solid var(--brass); border-radius:var(--radius); background:rgba(232,199,154,.08);}
.po-champ-word{font-family:'Inter',sans-serif; font-size:12px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--brass);}
.po-champ-name{font-family:'Baloo 2',sans-serif; font-size:20px; color:var(--tracer);}
.po-rounds{display:grid; grid-template-columns:repeat(var(--po-cols, 3), minmax(0, 1fr)); gap:14px; align-items:start;}
.po-rounds[data-rounds="1"]{--po-cols:1;} .po-rounds[data-rounds="2"]{--po-cols:2;} .po-rounds[data-rounds="4"]{--po-cols:4;}
.po-round{display:flex; flex-direction:column; gap:10px; min-width:0;}
.po-round-head{display:flex; flex-direction:column; gap:2px; padding-bottom:6px; border-bottom:1px solid var(--hairline);}
.po-round-head b{font-family:'Baloo 2',sans-serif; font-size:16px; color:var(--chalk);}
.po-round-head span{font-size:12.5px; color:var(--chalk-faint);}
.po-group{background:var(--panel); border:1px solid var(--hairline); border-radius:10px; padding:10px 12px; display:flex; flex-direction:column; gap:8px;}
.po-group.is-champ{border-color:var(--brass);}
.po-group-name{display:flex; align-items:center; font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--chalk-faint);}
.po-group.is-champ .po-group-name{color:var(--brass);}
.po-match{display:flex; flex-direction:column; gap:3px; padding-top:6px; border-top:1px solid var(--hairline);}
.po-group-name + .po-match{border-top:none; padding-top:0;}
.po-team, .po-place, .po-seed-row{display:flex; align-items:center; gap:8px; min-width:0;}
.po-team{padding:3px 0; color:var(--chalk-dim);}
.po-team.is-win{color:var(--tracer); font-weight:700;}
.po-team.is-out{color:var(--chalk-faint);}
.po-seed{flex:none; min-width:22px; font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint);}
.po-name, .po-seed-name{flex:1; min-width:0; overflow-wrap:anywhere; font-size:14px;}
.po-score{flex:none; font-family:'IBM Plex Mono',monospace; font-size:14px;}
.po-how{font-size:12px; color:var(--chalk-faint);}
.po-how.is-wait{color:var(--fairway-light);}
.po-match-acts{display:flex; gap:6px; margin-top:4px;}
.po-match.is-open .po-team{color:var(--chalk-dim);}
.po-wait{display:flex; align-items:center; gap:6px; font-size:12.5px; color:var(--chalk-faint);}
.po-places{margin-top:18px; background:var(--panel); border:1px solid var(--hairline); border-radius:var(--radius); padding:12px 14px; display:flex; flex-direction:column; gap:6px;}
.po-places-head, .po-season-head{font-family:'Baloo 2',sans-serif; font-size:16px; color:var(--chalk);}
.po-season-head{margin:4px 0 10px;}
.po-place .po-seed{min-width:38px;}
.po-place.is-first{color:var(--tracer); font-weight:700;}
.po-seeds{display:flex; flex-direction:column; gap:2px; margin:10px 0; background:var(--panel); border:1px solid var(--hairline); border-radius:10px; padding:8px 12px;}
.po-seed-row{padding:5px 0; border-bottom:1px solid var(--hairline); color:var(--chalk-dim);}
.po-seed-row:last-child{border-bottom:none;}
.po-seed-row.is-in .po-seed-name{color:var(--chalk);}
.po-seed-row.is-tied{background:rgba(232,199,154,.06);}
.po-seed-num{flex:none; font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--chalk-faint);}
.po-seed-tag{flex:none; font-size:12px; padding:1px 7px; border:1px solid var(--fairway-light); border-radius:999px; color:var(--fairway-light);}
.po-seed-key{font-size:12.5px; color:var(--chalk-faint); margin:4px 0 12px;}
.po-seed-go{margin-top:8px;}
.po-up{flex:none; padding:4px 8px;}
/* the TV: a championship team's match name under its players; the champion over the leaderboard */
.tv-team-po{display:flex; align-items:center; margin-top:calc(var(--t) * .4); font-family:'Inter',sans-serif; font-weight:700;
  font-size:max(12px, calc(var(--t) * 1.35)); letter-spacing:.1em; text-transform:uppercase; color:var(--tracer);}
.lb-champ{display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:8px 0 12px; border-bottom:1px solid var(--hairline); color:var(--brass); font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;}
.lb-champ b{font-family:'Baloo 2',sans-serif; font-size:20px; letter-spacing:0; text-transform:none; color:var(--tracer);}
@media (max-width:560px){
  .po-rounds{grid-template-columns:1fr;}
  .po-seed-row{flex-wrap:wrap;}
  .po-seed-num{flex-basis:100%; padding-left:30px;}
}
