/* =============================================================================
   Divot Golf — design system
   -----------------------------------------------------------------------------
   Layers, in order: tokens → base → primitives → chrome → components → screens.
   Nothing below the token layer may use a raw colour: every value comes from a
   custom property, which is what lets one theme swap flip the whole app.
   ============================================================================= */

/* --- Tokens ---------------------------------------------------------------- */

:root {
  /* Light is the default because that's what a phone on a fairway in full sun
     needs. Dark is the signature look and is applied below. */
  --bg: #f6f8f6;
  --bg-elev: #ffffff;
  --surface: #ffffff;
  --surface-2: #edf1ee;
  --surface-3: #e3e9e5;
  --line: rgba(10, 20, 14, 0.13);
  --line-strong: rgba(10, 20, 14, 0.24);
  --text: #0e1512;
  --muted: #5c6b64;
  --faint: #616e67;

  --accent: #0a6e3c;
  --accent-hover: #085c32;
  --accent-ink: #ffffff;
  --accent-soft: rgba(10, 110, 60, 0.1);
  --accent-line: rgba(10, 110, 60, 0.3);

  --gold: #8f6206;
  --gold-soft: rgba(143, 98, 6, 0.12);
  --danger: #b3261e;
  --danger-ink: #ffffff;
  --danger-soft: rgba(179, 38, 30, 0.1);
  --warn: #8a5a08;
  --warn-soft: rgba(138, 90, 8, 0.12);

  /* Score relative to par. Golf's own convention — under par reads hot, over par
     reads cool — inverted here so the app's accent means "good". */
  --tone-eagle: #8f6206;
  --tone-birdie: #0a6e3c;
  --tone-bogey: #8a5a08;
  --tone-double: #b3261e;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 22px;
  --r-pill: 999px;

  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  --shadow-1: 0 1px 2px rgba(10, 20, 14, 0.06), 0 1px 3px rgba(10, 20, 14, 0.04);
  --shadow-2: 0 8px 24px rgba(10, 20, 14, 0.1);

  --dur: 140ms;
  /* Standard curve: at rest at both ends, 50% covered at 35% elapsed. The previous
     one packed the change into the front of the duration, which made short animations
     look like they were skipping frames rather than moving. */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  /* Pure decelerate, for things that arrive from off-screen and settle. */
  --ease-out: cubic-bezier(0, 0, 0.2, 1);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  --bar-h: 56px;

  color-scheme: light;
}

/* Dark tokens live in two selectors on purpose: the media query is what an OS
   preference (or a no-JS load) picks up, and the attribute is what the in-app
   toggle sets. The `:not([data-theme="light"])` guard means choosing light on a
   dark phone doesn't need the whole light palette repeated back. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0a0f0c;
    --bg-elev: #131a16;
    --surface: #131a16;
    --surface-2: #1c2621;
    --surface-3: #26332c;
    --line: rgba(255, 255, 255, 0.09);
    --line-strong: rgba(255, 255, 255, 0.2);
    --text: #f1f5f2;
    --muted: #9ba8a1;
    --faint: #8a9a93;

    --accent: #3fe08b;
    --accent-hover: #57e79a;
    --accent-ink: #06120c;
    --accent-soft: rgba(63, 224, 139, 0.14);
    --accent-line: rgba(63, 224, 139, 0.34);

    --gold: #f5c451;
    --gold-soft: rgba(245, 196, 81, 0.15);
    --danger: #ff6b5e;
    --danger-ink: #2a0703;
    --danger-soft: rgba(255, 107, 94, 0.14);
    --warn: #e7a93b;
    --warn-soft: rgba(231, 169, 59, 0.14);

    --tone-eagle: #f5c451;
    --tone-birdie: #3fe08b;
    --tone-bogey: #e7a93b;
    --tone-double: #ff6b5e;

    --shadow-1: none;
    --shadow-2: 0 12px 32px rgba(0, 0, 0, 0.5);

    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #0a0f0c;
  --bg-elev: #131a16;
  --surface: #131a16;
  --surface-2: #1c2621;
  --surface-3: #26332c;
  --line: rgba(255, 255, 255, 0.09);
  --line-strong: rgba(255, 255, 255, 0.2);
  --text: #f1f5f2;
  --muted: #9ba8a1;
  --faint: #8a9a93;

  --accent: #3fe08b;
  --accent-hover: #57e79a;
  --accent-ink: #06120c;
  --accent-soft: rgba(63, 224, 139, 0.14);
  --accent-line: rgba(63, 224, 139, 0.34);

  --gold: #f5c451;
  --gold-soft: rgba(245, 196, 81, 0.15);
  --danger: #ff6b5e;
  --danger-ink: #2a0703;
  --danger-soft: rgba(255, 107, 94, 0.14);
  --warn: #e7a93b;
  --warn-soft: rgba(231, 169, 59, 0.14);

  --tone-eagle: #f5c451;
  --tone-birdie: #3fe08b;
  --tone-bogey: #e7a93b;
  --tone-double: #ff6b5e;

  --shadow-1: none;
  --shadow-2: 0 12px 32px rgba(0, 0, 0, 0.5);

  color-scheme: dark;
}

/* --- Base ------------------------------------------------------------------ */

* {
  box-sizing: border-box;
}

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

body {
  font-family: var(--font);
  font-size: 16px; /* prevents iOS zoom-on-focus, spec §7 */
  line-height: 1.45;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

#app {
  max-width: 620px;
  margin: 0 auto;
  min-height: 100vh;
  background: var(--bg);
}

main {
  /* Bottom padding clears the fixed tab bar plus the iOS home indicator. */
  padding: var(--sp-4) var(--sp-4) calc(104px + env(safe-area-inset-bottom));
}

h1,
h2,
h3 {
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
}

h1 {
  font-size: 1.65rem;
  font-weight: 800;
  margin: 0 0 var(--sp-3);
}

h2 {
  font-size: 1.15rem;
  font-weight: 700;
  margin: var(--sp-5) 0 var(--sp-2);
}

h3 {
  font-size: 1rem;
  font-weight: 700;
  margin: var(--sp-4) 0 var(--sp-2);
}

p {
  margin: 0 0 var(--sp-3);
}

a {
  color: var(--accent);
  text-underline-offset: 2px;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Each screen arrives rather than appearing. Restarted from AppShell on every route
   change by removing and re-adding the class, which is the only way to replay a CSS
   animation on an element that never unmounts. */
main.screen-enter {
  animation: screen-enter 300ms var(--ease-out);
}

@keyframes screen-enter {
  from {
    opacity: 0;
    translate: 0 6px;
  }
}

/* A placeholder shaped like the content that's coming, rather than the word
   "Loading" followed by a jump. */
.skeleton,
.skeleton-row {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-color: transparent;
}

.skeleton-row {
  height: 55px;
}

.skeleton::after,
.skeleton-row::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--surface-3), transparent);
  translate: -100% 0;
  animation: shimmer 1.8s ease-in-out infinite;
}

@keyframes shimmer {
  to {
    translate: 100% 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    /* Without this, a looping animation becomes an endless run of zero-length
       iterations rather than stopping. */
    animation-iteration-count: 1 !important;
  }
}

/* --- Primitives: buttons --------------------------------------------------- */

button,
input,
select,
textarea {
  font-size: 16px;
  font-family: inherit;
}

button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: 12px 18px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition:
    background var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

button:active:not(:disabled) {
  transform: scale(0.98);
}

@media (hover: hover) {
  /* Every override of this rule must repeat `:not(:disabled)`. The pseudo-class
     inside :not() counts toward specificity, so this selector is (0,2,1) and a
     plain `.thing:hover` at (0,2,0) silently loses to it — which is how the
     wordmark ended up filled accent-green when tapped.

     The gate matters too: on a touch screen :hover sticks after a tap until you
     tap something else, and the app bar survives navigation — so a tapped logo
     stayed lit. No pointer, no hover styles, nothing left behind. */
  button:hover:not(:disabled) {
    background: var(--accent-hover);
  }
}

button.secondary {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

@media (hover: hover) {
  button.secondary:hover:not(:disabled) {
    background: var(--surface-3);
  }
}

button.ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--text);
}

@media (hover: hover) {
  button.ghost:hover:not(:disabled) {
    background: var(--surface-2);
  }
}

button.danger {
  background: var(--danger);
  border-color: transparent;
  color: var(--danger-ink);
}

@media (hover: hover) {
  button.danger:hover:not(:disabled) {
    background: var(--danger);
    filter: brightness(1.08);
  }
}

button.secondary.danger-quiet {
  background: var(--danger-soft);
  border-color: transparent;
  color: var(--danger);
}

button:disabled {
  opacity: 0.42;
  cursor: default;
}

button.linklike {
  background: none;
  border: none;
  color: var(--accent);
  font: inherit;
  font-weight: 700;
  text-decoration: underline;
  padding: 0;
  min-height: 0;
  cursor: pointer;
  width: auto;
}

@media (hover: hover) {
  button.linklike:hover:not(:disabled) {
    background: none;
  }
}

/* Square icon-only control, used across the app bar and compact rows. */
button.icon-btn {
  min-height: 40px;
  width: 40px;
  padding: 0;
  flex: 0 0 auto;
  background: transparent;
  border-color: var(--line);
  color: var(--text);
  border-radius: var(--r-md);
}

@media (hover: hover) {
  button.icon-btn:hover:not(:disabled) {
    background: var(--surface-2);
  }
}

button.icon-btn.is-current {
  color: var(--faint);
  border-color: transparent;
  cursor: default;
}

@media (hover: hover) {
  button.icon-btn.is-current:hover {
    background: transparent;
  }
}

.icon {
  display: block;
  flex: 0 0 auto;
}

/* --- Primitives: inputs ---------------------------------------------------- */

input,
select,
textarea {
  min-height: 48px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  width: 100%;
  background: var(--surface-2);
  color: var(--text);
  transition: border-color var(--dur) var(--ease);
}

@media (hover: hover) {
  input:hover,
  select:hover,
  textarea:hover {
    border-color: var(--line-strong);
  }
}

input::placeholder,
textarea::placeholder {
  color: var(--faint);
}

select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 17px) calc(50% + 1px),
    calc(100% - 12px) calc(50% + 1px);
  background-size: 5px 5px;
  background-repeat: no-repeat;
  padding-right: 34px;
}

/* Checkboxes opt out of the full-width text-input treatment above. */
input.checkbox,
input[type="checkbox"] {
  width: auto;
  min-height: auto;
  accent-color: var(--accent);
  flex: 0 0 auto;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

/* A visible label that stays put once the input is filled, rather than a
   placeholder that disappears the moment you type. */
.field-label,
.eyebrow {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.field-help {
  color: var(--muted);
  font-size: 0.875rem;
  margin: var(--sp-2) 0;
}

/* Help text that belongs to the control directly above it. */
.field-help-tight {
  margin: 2px 0 0;
}

/* --- Primitives: layout & surfaces ----------------------------------------- */

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.row {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
}

.row-between {
  justify-content: space-between;
}

.row-bottom {
  align-items: flex-end;
}

.grow {
  flex: 1;
  min-width: 0;
}

/* Two fields on one row where one carries the longer value. */
.grow-wide {
  flex: 1.4;
  min-width: 0;
}

.field-narrow {
  flex: 0 1 116px;
  min-width: 0;
}

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  margin: var(--sp-3) 0;
  box-shadow: var(--shadow-1);
}

.card > :last-child {
  margin-bottom: 0;
}

.card.card-danger {
  border-color: var(--danger);
  background: var(--danger-soft);
}

.card.card-warn {
  border-color: var(--warn);
  background: var(--warn-soft);
}

.card.card-accent {
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

/* A titled region of a long screen (Setup, Admin, Course Library). */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  margin: var(--sp-4) 0;
  box-shadow: var(--shadow-1);
}

.panel > h2,
.panel > h3 {
  margin-top: 0;
}

.panel > :last-child {
  margin-bottom: 0;
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

.panel-head h2,
.panel-head h3 {
  margin: 0;
}

.divider {
  height: 1px;
  background: var(--line);
  border: 0;
  margin: var(--sp-4) 0;
}

.muted {
  color: var(--muted);
  font-size: 0.9rem;
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.num {
  font-variant-numeric: tabular-nums;
}

/* Spacing utilities — these replace the inline style="margin-top:…" attributes
   that were scattered through the screens. */
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.m-0 { margin: 0; }
.text-danger { color: var(--danger); }
.text-warn { color: var(--warn); }
.text-accent { color: var(--accent); }
.center { text-align: center; }

/* --- Primitives: chips & badges -------------------------------------------- */

.badge,
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  background: var(--surface-2);
  color: var(--muted);
  white-space: nowrap;
}

.badge-live,
.chip-accent {
  background: var(--accent-soft);
  color: var(--accent);
}

.chip-gold {
  background: var(--gold-soft);
  color: var(--gold);
}

.chip-warn {
  background: var(--warn-soft);
  color: var(--warn);
}

.chip-danger {
  background: var(--danger-soft);
  color: var(--danger);
}

.chip-code {
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.12em;
  background: var(--accent-soft);
  color: var(--accent);
  padding-right: 7px;
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

/* Only the accent chip's dot breathes — that's the one that means "this round is
   being played right now". A pulsing dot on a neutral chip would say nothing. */
.chip-accent .dot,
.conn-banner .dot {
  animation: pulse-dot 2s var(--ease) infinite;
}

@keyframes pulse-dot {
  50% {
    opacity: 0.35;
    scale: 0.8;
  }
}

/* --- Chrome: app bar ------------------------------------------------------- */

.app-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  min-height: var(--bar-h);
  padding: var(--sp-2) var(--sp-4);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid var(--line);
}

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .app-bar,
  .tab-bar {
    background: var(--bg);
  }
}

.app-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text);
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  white-space: nowrap;
  background: none;
  border: none;
  padding: 0;
  min-height: 0;
  cursor: pointer;
}

@media (hover: hover) {
  button.app-brand:hover:not(:disabled) {
    background: none;
  }
}

.brand-mark {
  display: block;
  flex: 0 0 auto;
}

/* The mark is drawn once (components/Brand.ts) and coloured by the theme: the ball
   takes the surrounding text colour, the flight and turf take the accent. Setting
   `color` rather than fill/stroke lets each part keep its own paint attributes —
   they all reference currentColor. */
.brand-mark .mark-flight {
  color: var(--accent);
}

.brand-mark .mark-turf {
  color: var(--accent);
  opacity: 0.62;
}

/* --- The mark in motion ---------------------------------------------------- */

/* The mark is a freeze-frame of a strike, so the draw-on just plays the moment it
   already depicts: turf kicks up, the flight draws itself, the ball lands.

   The flight path carries `pathLength="1"`, which normalises its length — so the dash
   geometry below is 1, not a measured number that would silently strand itself if the
   curve were ever retuned.

   Two copies run this: the boot splash (index.html) via the keyframes here, and the
   app-bar wordmark via the Web Animations API (components/Brand.ts) — WAAPI there
   because re-adding a class won't replay a CSS animation on a second tap without
   forcing a reflow. Same shape, same timings; change one, change both.

   Reduced motion needs nothing special: the global block near the top of this file
   collapses these to one 0.01ms iteration, and every 100% keyframe is the mark at
   rest, so it lands there immediately. */

.splash {
  display: grid;
  place-items: center;
  min-height: 70vh;
  opacity: 0.5;
}

/* Both copies scale and rotate about their own centres rather than the SVG origin.
   For the wedge that lands on (15, 40) — the same pivot its static `rotate(-28 15 40)`
   already uses, so the animation resolves exactly onto the mark at rest. */
.splash-wedge,
.splash-ball,
.brand-mark .mark-wedge,
.brand-mark .mark-ball {
  transform-box: fill-box;
  transform-origin: center;
}

.splash-flight {
  stroke-dasharray: 1;
}

/* One cycle, then a beat of stillness before it repeats. A loop with no rest reads as
   a spinner, which would promise progress this can't actually measure. */
.splash-wedge,
.splash-flight,
.splash-ball {
  animation-duration: 2.6s;
  animation-timing-function: ease-out;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}

.splash-wedge {
  animation-name: mark-wedge;
}

.splash-flight {
  animation-name: mark-flight;
}

.splash-ball {
  animation-name: mark-ball;
}

/* Starts flatter and lower — still lying in the turf — then kicks up into the angle
   the static mark holds. */
@keyframes mark-wedge {
  0%,
  6% {
    transform: translate(4px, 3px) rotate(20deg);
    opacity: 0;
  }
  18%,
  100% {
    transform: none;
    opacity: 1;
  }
}

@keyframes mark-flight {
  0%,
  8% {
    stroke-dashoffset: 1;
  }
  48%,
  100% {
    stroke-dashoffset: 0;
  }
}

/* Arrives just after the flight lands, with a slight overshoot so it reads as caught
   rather than faded in. */
@keyframes mark-ball {
  0%,
  44% {
    transform: scale(0.3);
    opacity: 0;
  }
  58% {
    transform: scale(1.12);
    opacity: 1;
  }
  68%,
  100% {
    transform: none;
    opacity: 1;
  }
}

.app-bar-right {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.app-identity {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.app-me {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--muted);
  /* Tight enough that brand + name + code + both controls still fit a 390px phone;
     below that it's dropped entirely (see the narrow-phone block at the end) rather
     than clipped to an ellipsis that says nothing. */
  max-width: 10ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-code {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  background: var(--accent-soft);
  color: var(--accent);
  padding: 4px 9px;
  border-radius: var(--r-pill);
}

/* --- Chrome: tab bar ------------------------------------------------------- */

.tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  gap: var(--sp-1);
  max-width: 620px;
  margin: 0 auto;
  padding: 6px var(--sp-3) calc(6px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-top: 1px solid var(--line);
}

.tab-bar .tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  background: none;
  border: none;
  border-radius: var(--r-md);
  color: var(--muted);
  font-weight: 700;
  min-height: 54px;
  padding: 6px 4px;
}

@media (hover: hover) {
  .tab-bar .tab:hover:not(:disabled) {
    background: var(--surface-2);
  }
}

.tab-bar .tab.active {
  color: var(--accent);
  background: var(--accent-soft);
}

.tab-label {
  font-size: 0.7rem;
  letter-spacing: 0.01em;
}

/* --- Components: connection banner ----------------------------------------- */

.conn-banner {
  position: sticky;
  top: calc(var(--bar-h) + 4px);
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 8px 12px;
  border-radius: var(--r-pill);
  margin-bottom: var(--sp-3);
  font-size: 0.85rem;
  font-weight: 700;
  border: 1px solid transparent;
  animation: drop-in 320ms var(--ease-out);
}

@keyframes drop-in {
  from {
    opacity: 0;
    translate: 0 -10px;
  }
}

.conn-banner.online {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
}

.conn-banner.offline,
.conn-banner.error {
  background: var(--danger-soft);
  color: var(--danger);
}

.conn-banner.connecting {
  background: var(--warn-soft);
  color: var(--warn);
}

/* An inline message attached to the thing that produced it — unlike .conn-banner,
   which is pinned to the top because it describes the whole screen's state. */
.notice {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px 14px;
  border-radius: var(--r-md);
  font-size: 0.9rem;
  font-weight: 600;
  background: var(--surface-2);
  color: var(--text);
}

.notice-error {
  background: var(--danger-soft);
  color: var(--danger);
}

.notice-warn {
  background: var(--warn-soft);
  color: var(--warn);
}

.notice-ok {
  background: var(--accent-soft);
  color: var(--accent);
}

/* --- Components: banners --------------------------------------------------- */

.closed-banner,
.winner-banner,
.segment-banner {
  border-radius: var(--r-md);
  font-weight: 700;
  text-align: center;
}

.closed-banner {
  margin: var(--sp-3) 0 0;
  padding: 10px 14px;
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 0.9rem;
}

.winner-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin: var(--sp-3) 0;
  padding: 14px;
  border-radius: var(--r-lg);
  background: var(--gold-soft);
  color: var(--gold);
  font-size: 1.05rem;
  animation: winner-in 560ms var(--ease-out) backwards;
}

@keyframes winner-in {
  from {
    opacity: 0;
    scale: 0.96;
    translate: 0 6px;
  }
}

.segment-banner {
  margin: var(--sp-3) 0 0;
  padding: 9px 14px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.9rem;
}

/* --- Screen: home ---------------------------------------------------------- */

.hero {
  padding: var(--sp-5) 0 var(--sp-4);
}

.hero-mark {
  display: block;
  margin-bottom: var(--sp-3);
  color: var(--text);
}

.hero h1 {
  font-size: 2.1rem;
  margin: 0 0 var(--sp-2);
}

.hero p {
  color: var(--muted);
  margin: 0;
  max-width: 34ch;
}

/* Four letters, spaced out and centred — it looks like the thing being typed. */
.code-input {
  font-family: var(--font-mono);
  font-size: 1.85rem;
  font-weight: 700;
  letter-spacing: 0.42em;
  text-indent: 0.42em; /* keeps the glyphs optically centred despite the tracking */
  text-align: center;
  min-height: 64px;
  padding: 8px;
  background: var(--surface);
  border-width: 2px;
}

.code-input:focus {
  border-color: var(--accent);
  outline: none;
}

.action-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}

.action-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  min-height: 0;
  padding: var(--sp-4);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  color: var(--text);
  font-weight: 700;
  box-shadow: var(--shadow-1);
}

@media (hover: hover) {
  .action-card:hover:not(:disabled) {
    background: var(--surface-2);
    border-color: var(--accent-line);
  }
}

.action-card .icon {
  color: var(--accent);
}

.action-card small {
  display: block;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.3;
}

/* A remembered game, or a roster name to claim. */
.tap-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.tap-list button,
.list-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  text-align: left;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-weight: 700;
  padding: 12px 14px;
  box-shadow: var(--shadow-1);
}

@media (hover: hover) {
  .tap-list button:hover:not(:disabled) {
    background: var(--surface-2);
    border-color: var(--accent-line);
  }
}

.tap-list button.claimed {
  opacity: 0.55;
}

.list-main {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list-sub {
  display: block;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--muted);
}

.avatar {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 800;
}

.footer-links {
  display: flex;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

.footer-links button {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 600;
  min-height: 40px;
  padding: 0 var(--sp-2);
}

@media (hover: hover) {
  .footer-links button:hover:not(:disabled) {
    background: none;
    color: var(--text);
  }
}

/* --- Screen: scorecard ----------------------------------------------------- */

.course-line {
  margin: 0 0 var(--sp-3);
  color: var(--muted);
  font-size: 0.9rem;
}

.stat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.hole-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: var(--sp-3) var(--sp-3) var(--sp-4);
  margin: var(--sp-3) 0;
  box-shadow: var(--shadow-1);
}

.hole-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}

.hole-nav button {
  min-width: 56px;
  min-height: 56px;
  padding: 0;
  border-radius: var(--r-md);
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

@media (hover: hover) {
  .hole-nav button:hover:not(:disabled) {
    background: var(--surface-3);
  }
}

.hole-nav > div {
  flex: 1;
  min-width: 0;
  text-align: center;
}

.hole-number {
  font-size: 2.4rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}

.hole-number small {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.hole-meta {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: var(--sp-2);
  flex-wrap: wrap;
}

/* One tick per hole in the round — position at a glance, and a tap target free
   way to see how far in you are. */
.hole-rail {
  display: flex;
  gap: 3px;
  margin-top: var(--sp-3);
}

.hole-rail span {
  flex: 1;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  transition: background 260ms var(--ease);
}

.hole-rail span.done {
  background: var(--accent-line);
}

.hole-rail span.here {
  background: var(--accent);
}

.player-score-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
}

.player-score-row .who {
  flex: 1;
  min-width: 0;
  font-weight: 700;
}

.player-score-row .who small {
  display: block;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--muted);
}

.score-stepper {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.score-stepper button {
  min-width: 48px;
  width: 48px;
  min-height: 48px;
  font-size: 1.5rem;
  line-height: 1;
  border-radius: 50%;
  padding: 0;
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--text);
}

@media (hover: hover) {
  .score-stepper button:hover:not(:disabled) {
    background: var(--surface-3);
  }
}

.score-stepper .value,
.score-readonly {
  /* Same footprint either way, so rows stay aligned whether or not the score is
     yours to edit. */
  min-width: 52px;
  text-align: center;
  font-size: 1.7rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  transition: color var(--dur) var(--ease);
}

.score-stepper .value.pop {
  animation: score-pop 300ms var(--ease);
}

@keyframes score-pop {
  40% {
    scale: 1.18;
  }
}

.score-readonly {
  color: var(--muted);
  padding-right: var(--sp-2);
}

.tone-eagle { color: var(--tone-eagle); }
.tone-birdie { color: var(--tone-birdie); }
.tone-bogey { color: var(--tone-bogey); }
.tone-double { color: var(--tone-double); }

.team-group {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  box-shadow: var(--shadow-1);
}

.team-heading {
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: var(--sp-2) 0;
}

.team-group .player-score-row {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}

.team-group .player-score-row:last-child {
  border-bottom: none;
}

.wolf-panel {
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  padding: var(--sp-3) var(--sp-4);
  margin: var(--sp-3) 0;
}

.wolf-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

.wolf-choices button {
  flex: 1 1 auto;
}

/* --- Screen: leaderboard --------------------------------------------------- */

.lb {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* A standings row is a button (it opens the breakdown), so it has to opt out of the
   primary-button base above — otherwise it inherits accent-on-accent colouring and,
   worse, `button:hover` repaints the whole row green. */
.lb-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--sp-3);
  width: 100%;
  padding: 12px 14px;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  transition:
    background 480ms var(--ease),
    border-color 480ms var(--ease);
  /* The entrance uses `translate`, not `transform`, on purpose: useFlipList writes
     `transform` when the board reorders, and the two properties compose instead of
     one clobbering the other if a reorder arrives while a row is still entering. */
  animation: lb-enter 420ms var(--ease-out) backwards;
  animation-delay: calc(min(var(--i, 0), 8) * 60ms);
}

@keyframes lb-enter {
  from {
    opacity: 0;
    translate: 0 8px;
  }
}

/* Hover has to be stated for each state: `button:hover:not(:disabled)` outranks a
   single class, so without these the base rule's accent fill wins. */
@media (hover: hover) {
  button.lb-row:hover:not(:disabled) {
    background: var(--surface-2);
    border-color: var(--accent-line);
  }
}

/* These three keep their hover halves outside the (hover: hover) gate on purpose:
   each also carries the base-state selector, so wrapping them would take the tint
   away from phones entirely. On a touch device the hover half can still match via
   sticky hover, but it declares exactly the same values, so nothing moves. */
.lb-row.leader,
button.lb-row.leader:hover:not(:disabled) {
  background: var(--accent-soft);
  border-color: var(--accent-line);
}

/* A row that just changed places, held for as long as the component keeps the class
   on (~2.2s) and then transitioning back when it drops it.
   Transitions rather than a keyframe animation on purpose: adding a second animation
   to the shorthand above would restart the entrance one at the same time, so a row
   would re-enter every time it changed rank. */
.lb-row.moved-up,
button.lb-row.moved-up:hover:not(:disabled) {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.lb-row.moved-down,
button.lb-row.moved-down:hover:not(:disabled) {
  background: var(--surface-2);
}

.rank-move {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
  font-size: 0.75rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  /* The row it labels has already finished travelling by the time this mounts (the
     cue is delayed by FLIP_MS), so it arrives into a still frame. */
  animation: cue-in 220ms var(--ease-out) backwards;
}

@keyframes cue-in {
  from {
    opacity: 0;
    translate: 0 4px;
  }
}

.rank-move.up {
  color: var(--accent);
}

.rank-move.down {
  color: var(--faint);
}

.rank {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.rank.rank-1 {
  background: var(--gold-soft);
  color: var(--gold);
}

.rank.rank-2,
.rank.rank-3 {
  background: var(--accent-soft);
  color: var(--accent);
}

.lb-name {
  flex: 1;
  min-width: 0;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lb-points {
  font-size: 1.25rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.projected {
  margin-left: 5px;
  /* Halftone: present but clearly secondary to the confirmed score. */
  opacity: 0.6;
  font-size: 0.8em;
  font-weight: 700;
  color: var(--accent);
}

.projected-note {
  font-style: italic;
}

table.scorecard-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}

table.scorecard-table th {
  font-size: 0.7rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  text-align: left;
}

table.scorecard-table th,
table.scorecard-table td {
  padding: 8px 6px;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

table.scorecard-table td + td,
table.scorecard-table th + th {
  text-align: right;
}

table.scorecard-table tr:last-child td {
  border-bottom: none;
}

/* The course editor puts number inputs in cells; full-size fields would make an
   18-row table taller than the screen. */
table.scorecard-table input {
  min-height: 40px;
  padding: 6px 8px;
  text-align: right;
}

/* --- Don mode -------------------------------------------------------------- */
/* The app's easter egg (shared/donMode.ts), across the two screens it touches: the
   button that fires it, on Don's own breakdown page, and the robbery it sets off on
   the standings. Everything here is gold — the colour this app already reserves for
   first place — so a crowned row looks like the real thing rather than like a
   rendering fault.

   Nothing marks the standings before he fires it. An earlier version hid the trigger
   on his row behind a press-and-hold and tried to advertise it with a pulse on his
   rank badge; a wordless cue can't teach a gesture nobody expects, and it stayed
   undiscovered. The offer is made in words now, on a page that belongs to one player,
   which leaves the board itself with nothing to explain. */

.don-panel {
  border-color: var(--gold);
}

/* Hold-to-fire. Both selectors carry the base state, and the hover half is stated
   outside the (hover: hover) gate on purpose — it declares the same values, and
   `button:hover:not(:disabled)` is (0,2,1), so a plain `.don-fire` would silently lose
   the button its colour the moment a mouse rested on it. */
button.don-fire,
button.don-fire:hover:not(:disabled) {
  position: relative;
  overflow: hidden;
  width: 100%;
  margin-top: var(--sp-3);
  background: var(--gold-soft);
  color: var(--gold);
  font-weight: 800;
}

/* The charge, along the button's bottom edge. A track rather than a fill behind the
   label: the two themes' golds sit on opposite sides of the label's lightness — light
   theme's is a dark brown, dark theme's is a bright straw — so a label spanning a
   half-filled button cannot be legible in both. Grows in step with DON_HOLD_MS:
   change one, change the other. */
.don-fire::before {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 4px;
  background: var(--gold);
  transform-origin: left;
  scale: 0 1;
  /* Release snaps back quickly — an aborted hold shouldn't look like it half-counted. */
  transition: scale 160ms var(--ease);
}

/* This one keeps moving under prefers-reduced-motion, and not by accident: the global
   cutoff near the top of this file is a `*` rule, which never matches a pseudo-element.
   It is the right outcome either way — the bar isn't decoration, it's the only thing
   telling you how much longer to hold, and snapping it to full would leave you pressing
   a button with no idea when to stop. */
.don-fire:active::before {
  scale: 1 1;
  transition-duration: 800ms;
  transition-timing-function: linear;
}

.lb-row.crowned {
  position: relative;
  overflow: hidden;
}

.lb-row.crowned,
button.lb-row.crowned:hover:not(:disabled) {
  background: var(--gold-soft);
  border-color: var(--gold);
}

.crown {
  flex: 0 0 auto;
  color: var(--gold);
}

/* A slow gloss across the crowned row. Transform-only, so it stays on the compositor
   for the whole minute it runs — and it inherits the global reduced-motion cutoff,
   which stops it after one pass rather than looping. */
.lb-row.crowned::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 35%, var(--gold-soft) 50%, transparent 65%);
  translate: -100% 0;
  animation: shimmer 2600ms linear infinite;
  pointer-events: none;
}

/* A robbed row. The points cell has already counted itself down to nothing by the
   time this lands — this is the row noticing. */
.lb-row.robbed .lb-points {
  color: var(--faint);
  opacity: 0.55;
  transition:
    color 480ms var(--ease),
    opacity 480ms var(--ease);
}

/* A stolen total, in flight between two rows. Parked on the body rather than inside
   the list so the row's own `overflow: hidden` can't clip it, and out of the way of
   pointers so a token crossing the board can't eat a tap. */
.steal-token {
  position: fixed;
  z-index: 40;
  font-size: 1.25rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--gold);
  text-shadow: 0 1px 3px var(--bg);
  pointer-events: none;
  will-change: transform, opacity;
}

.don-banner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: var(--sp-3) 0;
  padding: 12px 14px;
  border-radius: var(--r-lg);
  background: var(--gold-soft);
  color: var(--gold);
  text-align: center;
  transition:
    background 400ms var(--ease),
    color 400ms var(--ease);
  animation: winner-in 560ms var(--ease-out) backwards;
}

.don-title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 800;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Out of the title's uppercase: the unit is a lowercase s, and "59S" reads as an
   abbreviation rather than as a clock. */
.don-clock {
  text-transform: none;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.don-banner small {
  font-size: 0.78rem;
  font-weight: 600;
  opacity: 0.85;
}

/* Louder while the points are in the air, then it settles into the reign banner. The
   two are the same element, so the change is a transition rather than one banner
   replacing another and shoving the board down a line mid-heist. */
.don-banner.is-robbery {
  background: var(--gold);
  color: var(--accent-ink);
}

/* The epitaph is quieter than the reign it follows — it's a caption, and it sits on
   screen until the player navigates away. */
.don-banner.is-over {
  background: var(--surface-2);
  color: var(--muted);
}

/* --- Screen: score breakdown ----------------------------------------------- */

.back-link {
  margin: 0 0 var(--sp-3);
  font-size: 0.9rem;
  font-weight: 700;
}

.back-link a {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  text-decoration: none;
}

@media (hover: hover) {
  .back-link a:hover {
    text-decoration: underline;
  }
}

/* A column of arithmetic: label (with an optional aside) on the left, figure on the
   right, and the answer marked out at the bottom. */
.calc {
  display: flex;
  flex-direction: column;
}

.calc-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  text-align: left;
  width: 100%;
}

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

.calc-label {
  flex: 1;
  min-width: 0;
  font-weight: 600;
}

.calc-label small {
  display: block;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.35;
}

.calc-value {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.calc-total {
  border-top: 2px solid var(--line-strong);
  border-bottom: none;
  margin-top: 2px;
  padding-top: 12px;
}

.calc-total .calc-label,
.calc-total .calc-value {
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--accent);
}

/* A row of the sum that is also a way into the detail behind it. */
button.calc-link {
  /* Centred rather than baseline-aligned like the static rows: these have a fixed
     min-height for the tap target, and a one-line label would otherwise sit at the
     top of it with the slack showing underneath. */
  align-items: center;
  background: none;
  border: none;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  min-height: 56px;
  padding: 10px 0;
  color: var(--text);
  cursor: pointer;
}

@media (hover: hover) {
  button.calc-link:hover:not(:disabled) {
    background: var(--surface-2);
  }
}

button.calc-link .calc-value {
  color: var(--text);
}

button.calc-link.is-open .calc-value .muted-points {
  color: var(--muted);
}

.muted-points {
  color: var(--muted);
}

/* Rows in a breakdown table that lead somewhere. */
tr.row-link {
  cursor: pointer;
}

@media (hover: hover) {
  tr.row-link:hover td {
    background: var(--surface-2);
  }
}

.row-link-chevron {
  display: inline-block;
  vertical-align: -2px;
  margin-left: 6px;
  color: var(--faint);
}

.lb-chevron {
  color: var(--faint);
  flex: 0 0 auto;
}

/* The round's name is the way into its breakdown. Given a tap target of its own so
   it reads as a control, without stealing the taps on the player rows below it. */
.round-name-link {
  cursor: pointer;
  border-radius: var(--r-sm);
  margin: calc(-1 * var(--sp-2));
  padding: var(--sp-2);
}

@media (hover: hover) {
  .round-name-link:hover {
    background: var(--surface-2);
  }
}

tr.row-empty td {
  color: var(--faint);
}

.cell-note {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

/* An 18-row breakdown with a result column is wider than a phone; it scrolls in its
   own box rather than making the page scroll sideways. */
.table-scroll {
  overflow-x: auto;
  margin: 0 calc(-1 * var(--sp-4));
  padding: 0 var(--sp-4);
}

/* --- Screen: round breakdown (whole field, hole by hole) -------------------- */

/* The hole a side won. Colour alone can't carry it — a fifth of men can't reliably
   separate these two — so the weight change and the .sr-only text below do the work
   and the tint is the fast path for everyone else. */
.grid-win {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 800;
  border-radius: var(--r-sm);
}

/* Gross and net are one reading of one hole; letting them break across two lines
   turns "4 3" into something that looks like two separate scores. */
.grid-table td {
  white-space: nowrap;
}

/* The result is the column people came for, so it wraps rather than sitting off the
   right edge behind a horizontal scroll like the other nowrap notes do. */
.contest-result {
  white-space: normal;
  min-width: 5rem;
}

/* A two-side match needs no more width than its numbers; without this the status
   column takes every spare pixel and leaves the sides huddled left of centre. */
.contest-table th:first-child,
.contest-table td:first-child {
  width: 1%;
}

/* Wolf's sides change every hole, so each hole is a block rather than a table row —
   see ShiftingSidesList for why a table couldn't hold it. */
.contest-hole {
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--line);
}

.contest-hole:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.contest-hole.is-undecided {
  color: var(--faint);
}

.contest-hole-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
  margin-bottom: 2px;
}

.contest-hole-num {
  font-weight: 700;
  font-size: 0.82rem;
  white-space: nowrap;
}

.contest-hole-note {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  text-align: right;
}

/* Label left, number right, on one line each — the number can never wrap away from
   the side it belongs to. */
.contest-side {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
  font-size: 0.88rem;
  padding: 2px 0;
}

.contest-side-value {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  white-space: nowrap;
}

.contest-side.is-won {
  color: var(--accent);
  font-weight: 700;
}

/* The net beside the gross, where handicap strokes moved it. */
.grid-net {
  color: var(--muted);
  font-weight: 600;
  margin-left: 4px;
}

/* A side's number is the headline; the balls behind it sit underneath, smaller. */
.grid-side-value {
  font-weight: 700;
  display: block;
}

.grid-side-balls {
  color: var(--muted);
  font-weight: 600;
  display: block;
  font-size: 0.72rem;
}

/* The ball the side actually counted — four-ball's better net. */
.grid-ball-counts {
  color: var(--text);
  font-weight: 800;
}

/* What the column above added up to, and what it was worth. */
.grid-table tfoot th,
.grid-table tfoot td {
  border-bottom: none;
  white-space: nowrap;
}

/* Separates the sums from the holes they sum, the way .calc-total does on the
   per-player breakdown. */
.grid-table tfoot tr:first-child th,
.grid-table tfoot tr:first-child td {
  border-top: 2px solid var(--line-strong);
}

/* The label is a `th` spanning Hole/Par/SI, so the first figure beside it isn't a
   `td + td` and would otherwise sit left while the rest of the row sits right. */
.grid-table tfoot td {
  text-align: right;
  font-weight: 700;
}

.grid-table tfoot .grid-points td {
  color: var(--accent);
  font-weight: 800;
}

/* Which run of holes these sides played, for a round whose partners change. */
.grid-section-title {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: var(--sp-3) 0 0;
}

/* With a column per player the grid always scrolls sideways, and losing the hole
   number to the left edge makes a row unreadable. */
.grid-table th:first-child,
.grid-table td:first-child {
  position: sticky;
  left: 0;
  background: var(--surface);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- Screen: setup --------------------------------------------------------- */

.player-edit-row {
  display: flex;
  gap: var(--sp-2);
  align-items: flex-end;
}

.remove-btn {
  min-width: 48px;
  width: 48px;
  padding: 0;
  flex: 0 0 auto;
  color: var(--muted);
}

/* One segment of a team round: its hole range, then the sides as visible groups.
   Players sit under the team they're on rather than in roster order — with a
   threesome or someone sitting out, a flat list forces you to read the grouping
   out of a column of dropdowns. A select per player rather than drag-and-drop,
   because this gets edited one-handed on a course. */
.segment-editor {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-top: var(--sp-3);
}

.segment-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}

.segment-head .muted {
  flex: 1;
}

.side-group {
  border-left: 3px solid var(--accent);
  padding: 6px 0 6px var(--sp-3);
  margin: var(--sp-3) 0;
}

.side-group-out {
  border-left-color: var(--line-strong);
}

.side-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin-bottom: var(--sp-1);
}

.side-name {
  font-weight: 700;
}

.side-count {
  flex: 1;
  color: var(--muted);
  font-size: 0.82rem;
}

.side-empty {
  color: var(--muted);
  font-size: 0.85rem;
  font-style: italic;
  margin: 2px 0;
}

.segment-player-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: 4px 0;
}

.segment-player-row > span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.segment-player-row select {
  flex: 0 0 auto;
  width: 9.5rem;
}

.place-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
}

.place-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.place-label {
  flex: 0 0 44px;
  font-weight: 700;
  color: var(--muted);
}

.share-wrap {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin: var(--sp-2) 0 var(--sp-3);
}

.share-status {
  color: var(--accent);
  font-size: 0.9rem;
  font-weight: 700;
}

.nested-config {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  margin-top: var(--sp-3);
}

.nested-config legend {
  padding: 0 6px;
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.rules-preview {
  margin-top: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  background: var(--surface-2);
}

.rules-preview summary {
  cursor: pointer;
  font-weight: 700;
}

.rules-preview ul {
  margin: var(--sp-2) 0 0;
  padding-left: 20px;
}

.rules-preview li {
  margin-bottom: 6px;
}

.rule-list {
  margin: var(--sp-2) 0 0;
  padding-left: 20px;
  font-size: 0.92rem;
}

.rule-list li {
  margin-bottom: 6px;
}

.tee-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.tee-tabs button {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 8px 14px;
}

/* --- Screen: admin --------------------------------------------------------- */

.usage-metric {
  margin-bottom: var(--sp-4);
}

.meter {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  overflow: hidden;
  margin: 6px 0 4px;
}

.meter-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--accent);
  transition: width var(--dur) var(--ease);
}

.meter-fill.warn {
  background: var(--warn);
}

.meter-fill.danger {
  background: var(--danger);
}

/* --- Narrow phones --------------------------------------------------------- */

@media (max-width: 379px) {
  main {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
  }

  .app-me {
    display: none;
  }

  .action-grid {
    grid-template-columns: 1fr;
  }

  .score-stepper button {
    min-width: 44px;
    width: 44px;
    min-height: 44px;
  }

  .score-stepper .value,
  .score-readonly {
    min-width: 42px;
    font-size: 1.5rem;
  }
}
