/* ============================================================================
   ToloCourt — Brand Kit v2 · a Tolotech product
   Single source of truth for color, type, and core UI components.
   Reused across landing, player app, and owner console.

   Two brands, one foundation (Tolotech kit): ToloCourt shares the family's
   paper, ink, Geist and dot + mono labels, and owns one accent and one display face.
     - Paper      #FFFEFA = ground: page background, type on ink
     - Ink        #222222 = type, ink bands, and every primary action
     - Court blue #2459C6 = a marker, not a fill (~5% of a page): the dot, an
                            eyebrow, links, a live court, focus, a button's hover.
                            Light #8DB4FF on ink, deep #183F91 for small blue type.
     - Tints, not new colours: muted text is ink at 72%, hairlines ink at 13%.
     - Green belongs to ToloRun; the logo is ink or paper only, never blue.
   Type: Bricolage Grotesque 600 (display) · Geist (body) · Geist Mono (labels, numbers)
   Voice: confident, never loud · benefit-first · American-plain · effortless
   ========================================================================== */

:root {
  /* ---- Tolotech family (shared with ToloRun) ---- */
  --paper:#FFFEFA;
  --ink:#222222;
  --grey:#8A8580;                      /* micro labels only (3.6:1) */
  --ink-muted:rgba(34,34,34,.72);
  --ink-line:rgba(34,34,34,.13);
  --paper-muted:rgba(255,254,250,.72);
  --paper-line:rgba(255,254,250,.16);
  --surface:#F6F5F1;                   /* one step off paper: wells, hovered rows */
  --white:#FFFFFF;                     /* cards & app surfaces */

  /* ---- ToloCourt accent: Court blue ---- */
  --blue:#2459C6;                      /* 6.3:1 on paper */
  --blue-deep:#183F91;                 /* 9.6:1 · small blue type, hover on blue */
  --blue-light:#8DB4FF;                /* 7.7:1 on ink · the accent on dark */
  --mist:#E8EFFB;                      /* blue tint · live court, chips */
  --danger:#B23B2E;                    /* destructive UI only (not a brand color) */

  /* ---- Roles ---- */
  --action:var(--ink);                 /* primary buttons, selected chips and tabs */
  --action-hover:var(--blue);          /* primary turns Court blue on hover */

  /* ---- Pre-rebrand names, kept so existing page CSS resolves to the new palette ---- */
  --green:var(--blue);
  --green-700:var(--blue-deep);
  --volt:var(--blue-light);
  --volt-ink:var(--ink);
  --chalk:var(--paper);
  --slate:var(--ink-muted);
  --muted-dark:var(--paper-muted);
  --line:var(--ink-line);
  --line-dark:var(--paper-line);
  --cal:var(--paper);
  --muted-light:var(--ink-muted);
  --optic:var(--blue-light);
  --optic-ink:var(--ink);
  --clay:var(--danger);

  /* ---- Type ---- */
  --font-display: "Bricolage Grotesque", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  --font-body:    "Geist", system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  --font-mono:    "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- Radius / shadow (kit: btn 8 · card 14 · band 24) ---- */
  --r-btn: 8px; --r-card: 14px; --r-band: 24px; --r-pill: 999px;
  --shadow-sm: 0 1px 2px rgba(34,34,34,.05);
  --shadow:    0 26px 64px -36px rgba(34,34,34,.42);

  --maxw: 1140px;
}

* { box-sizing: border-box; }
/* Guard against any stray wide element shifting the page sideways on mobile.
   `clip` (not `hidden`) so it never creates a scroll container that would break
   position:sticky headers. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }

body {
  margin: 0;
  max-width: 100%;
  overflow-x: clip;
  overflow-wrap: break-word;     /* long unbroken strings wrap instead of overflowing */
  font-family: var(--font-body);
  font-weight: 400;
  color: var(--ink);
  background: var(--chalk);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---- App shell: native-feeling navigation + PWA polish ----
   Cross-document view transitions. Both the outgoing and incoming document must
   opt in; every page links this file, so every same-origin navigation animates.
   Browsers without support just navigate as before — purely additive.
   Elements that should persist rather than cross-fade (the page header, the
   bottom nav) carry a stable view-transition-name in each page's own <style>. */
@view-transition { navigation: auto; }

html {
  overscroll-behavior: none;                 /* no rubber-band or pull-to-refresh */
  -webkit-tap-highlight-color: transparent;  /* inherits — kills the grey tap flash */
}

/* Fixed bottom bars must clear the iOS home indicator. The player tab bar and the
   owner section nav already bake this into their own padding; this is for any
   other fixed bottom bar so the next one doesn't have to remember. */
.safe-bottom { padding-bottom: env(safe-area-inset-bottom, 0px); }

@media (prefers-reduced-motion: reduce) {
  /* Skip the navigation animation outright... */
  @view-transition { navigation: none; }
  /* ...and neutralise any same-document transition that gets added later. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ---- Layout ---- */
.container { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 22px; }
/* padding-block (not the `padding: … 0` shorthand) so an element carrying BOTH
   .container and .section keeps the container's horizontal gutters. */
.section   { padding-block: clamp(54px, 8vw, 100px); }

/* ---- Type ---- */
h1, h2, h3, h4 {
  font-family: var(--font-display); font-weight: 600;
  letter-spacing: -.025em; line-height: 1.04; margin: 0; color: var(--ink);
}
.display {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(36px, 5.8vw, 58px); line-height: 1.0; letter-spacing: -.03em;
}
.h2 { font-family: var(--font-display); font-weight: 600; font-size: clamp(26px, 3.3vw, 36px); letter-spacing: -.022em; }
.lede { font-size: clamp(16px, 1.5vw, 18px); line-height: 1.55; color: var(--slate); max-width: 54ch; }

/* eyebrow / label — Geist Mono 500 · 11px · UPPERCASE · +14%, ink at 72%.
   Section eyebrows (<p>) lead with the Court blue dot; small stat labels
   (<span class="eyebrow"> in dashboards) stay dot-free so a grid of KPIs isn't
   a grid of dots. */
.eyebrow {
  font-family: var(--font-mono); font-weight: 500; font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted); margin: 0;
}
p.eyebrow::before {
  content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--blue); margin-right: 10px; vertical-align: .08em;
}
.mono { font-family: var(--font-mono); font-weight: 500; }
.figure { font-family: var(--font-mono); font-weight: 500; letter-spacing: -.01em; }

/* ---- Buttons (radius 8 · padding 12×20 · weight 600) ----
   One primary per view: ink, turning Court blue on hover. The ghost is a hairline
   that darkens to ink. (.btn-green keeps its name for the markup that uses it.) */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 20px; border-radius: var(--r-btn);
  font-family: var(--font-body); font-weight: 600; font-size: 15px; line-height: 1;
  text-decoration: none; cursor: pointer; border: 1px solid transparent;
  transition: transform .12s ease, background .15s ease, border-color .15s ease, filter .15s ease;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
/* A <label> styled as a button (e.g. file-upload triggers) must not inherit the
   form-label top/bottom margin, or it sits lower than an adjacent <button>. */
label.btn { margin: 0; }
.btn-green { background: var(--action); color: var(--paper); }          /* the primary, on light */
.btn-green:hover { background: var(--action-hover); }
.btn-volt, .btn-optic { background: var(--blue-light); color: var(--ink); } /* the primary on ink */
.btn-volt:hover, .btn-optic:hover { background: var(--paper); }
.btn-outline { border-color: var(--line); color: var(--ink); background: transparent; }
.btn-outline:hover { border-color: var(--ink); color: var(--ink); }
.btn-outline-dark { border-color: var(--line-dark); color: var(--paper); background: transparent; }
.btn-outline-dark:hover { border-color: var(--paper); color: var(--paper); }
.btn-ink { background: var(--ink); color: var(--paper); }
.btn-ink:hover { background: var(--action-hover); }
.btn-lg { padding: 15px 26px; font-size: 16px; }
.btn-sm { padding: 10px 15px; font-size: 14px; }

/* ---- Cards / slots (radius 14 · 1px line) ---- */
.card {
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: 24px; box-shadow: var(--shadow-sm);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: rgba(34,34,34,.22); }

/* ---- Dark band (Ink) — conversion punctuation ---- */
.band-dark {
  background: var(--ink);
  color: var(--chalk); border-radius: var(--r-band);
}
.band-dark .eyebrow { color: var(--paper-muted); }
.band-dark p.eyebrow::before { background: var(--blue-light); }
.band-dark .lede, .band-dark .muted { color: var(--muted-dark); }
.band-dark h1, .band-dark h2, .band-dark h3 { color: var(--chalk); }

/* ---- Pills / chips ---- */
.pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: var(--r-pill);
  font-family: var(--font-body); font-weight: 600; font-size: 12px;
  border: 1px solid var(--line); background: var(--white); color: var(--slate);
}
.pill-soft { background: var(--mist); border-color: rgba(36,89,198,.18); color: var(--blue-deep); }

/* live / active signal — Court blue on light, Court blue light on dark */
.dot-live {
  width: 8px; height: 8px; border-radius: 50%; background: var(--blue); flex: none;
  display: inline-block; box-shadow: 0 0 0 0 rgba(36,89,198,.5); animation: pulse 2.1s infinite;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(36,89,198,.45); }
  70% { box-shadow: 0 0 0 8px rgba(36,89,198,0); }
  100% { box-shadow: 0 0 0 0 rgba(36,89,198,0); }
}
.band-dark .dot-live, .on-dark .dot-live { background: var(--blue-light); animation: pulse-volt 2.1s infinite; }
@keyframes pulse-volt {
  0% { box-shadow: 0 0 0 0 rgba(141,180,255,.45); }
  70% { box-shadow: 0 0 0 8px rgba(141,180,255,0); }
  100% { box-shadow: 0 0 0 0 rgba(141,180,255,0); }
}

/* ---- Logo: the lettered TOLOCOURT wordmark ----
   Lettered, never typed: the SVG is a mask filled with currentColor, so the mark
   is ink on paper and paper on ink wherever the surrounding colour rules say so.
   It is the textured artwork from the share cards; tolocourt-wordmark.svg is the
   clean version in the same 1170 x 274 box, should a use ever need it.
   Markup: <span class="wordmark" role="img" aria-label="ToloCourt"></span>.
   Height follows the font-size each page already sets on .wordmark. */
.wordmark {
  display: inline-block; flex: none; vertical-align: middle;
  height: .95em; aspect-ratio: 1170 / 274;
  background-color: currentColor;
  -webkit-mask: url(/tolocourt-wordmark-textured.svg) center / contain no-repeat;
          mask: url(/tolocourt-wordmark-textured.svg) center / contain no-repeat;
}
.lockup { display: inline-flex; align-items: center; gap: 10px; text-decoration: none; }
.lockup .wordmark { font-size: 21px; color: var(--ink); }
.on-dark .lockup .wordmark, .band-dark .lockup .wordmark { color: var(--paper); }

/* ---- Logo with the sports line: TOLOCOURT over PICKLEBALL · TENNIS · PADEL ----
   Same lettering and mask technique as .wordmark. The sports line is set to the
   wordmark's width so it stays readable at header size (the brand kit's
   tolocourt-logo-sports.svg keeps the artwork's original proportions for print).
   font-size sets the height of the whole lockup.
   Markup: <span class="wordmark-sports" role="img" aria-label="ToloCourt: pickleball, tennis, padel"></span>. */
.wordmark-sports {
  display: inline-block; flex: none; vertical-align: middle;
  height: 1em; aspect-ratio: 1170 / 365;
  background-color: currentColor;
  -webkit-mask: url(/tolocourt-lockup.svg) center / contain no-repeat;
          mask: url(/tolocourt-lockup.svg) center / contain no-repeat;
}
.lockup .wordmark-sports { font-size: 44px; color: var(--ink); }
.on-dark .lockup .wordmark-sports, .band-dark .lockup .wordmark-sports { color: var(--paper); }
@media (max-width: 760px) { .lockup .wordmark-sports { font-size: 38px; } }
@media (max-width: 360px) { .lockup .wordmark-sports { font-size: 30px; } }

/* ---- Utilities ---- */
.hr { height: 1px; background: var(--line); border: 0; margin: 0; }
a { color: var(--blue); }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; border-radius: 6px; }
.band-dark a, .on-dark a { color: var(--blue-light); }
/* ...but solid buttons keep their own label color (the link rule above would
   otherwise paint a light-blue button's text light blue — invisible on itself). */
.band-dark .btn-volt, .on-dark .btn-volt, .band-dark .btn-optic, .on-dark .btn-optic { color: var(--ink); }
.band-dark .btn-green, .on-dark .btn-green { color: var(--paper); }
.band-dark .btn-ink, .on-dark .btn-ink { color: var(--paper); }

/* ============================================================================
   v1.2 — Data-viz + motion layer (additive). Mobile-first, reduced-motion safe.
   ========================================================================== */
:root {
  --ease-out: cubic-bezier(.22,1,.36,1);     /* smooth settle: counts, draws, bars */
  --ease-spring: cubic-bezier(.34,1.56,.64,1); /* gentle overshoot: pops, taps */
  --dur-fast: 120ms; --dur: 420ms; --dur-slow: 900ms;

  --viz-track: #F0EFEB;                 /* empty bar/ring track on light */
  --viz-grid: rgba(34,34,34,.06);       /* hairline gridlines */

  /* Revenue ramp — ONE blue family, no rainbow. */
  --sport-1: #2459C6;  /* Court blue */
  --sport-2: #5C84D6;  /* blue tint up */
  --sport-3: #9DB5E6;  /* blue tint up x2 */
  --sport-4: #8A8580;  /* Grey — neutral "other" */
}

/* tabular figures so animated numbers never jitter width */
.figure, .stat b, .kpi-num, .donut-total b, .chart-total { font-variant-numeric: tabular-nums; }

/* reveal-on-scroll primitive (IntersectionObserver toggles .in) */
.reveal { opacity: 0; transform: translateY(8px); transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out); }
.reveal.in { opacity: 1; transform: none; }
/* Failsafe: content injected after the observer pass would otherwise stay
   invisible forever — force every .reveal visible 1.2s after it exists. */
.reveal { animation: reveal-fallback 0s 1.2s both; }
@keyframes reveal-fallback { to { opacity: 1; transform: none; } }

/* horizontal meter (utilization / capacity) */
.meter { height: 6px; border-radius: var(--r-pill); background: var(--viz-track); overflow: hidden; }
.meter > i { display: block; height: 100%; width: 0; border-radius: var(--r-pill); background: var(--green); transition: width var(--dur-slow) var(--ease-out); }
.meter.is-full > i { background: var(--danger); }
.meter.is-warm > i { background: #C98A2B; }

/* donut arcs */
.arc { stroke-dasharray: 0 999; transition: stroke-dasharray var(--dur-slow) var(--ease-out); }

/* skeleton shimmer */
.skel { background: linear-gradient(90deg,var(--viz-track) 25%,var(--surface) 37%,var(--viz-track) 63%); background-size: 400% 100%; animation: shimmer 1.4s ease infinite; border-radius: 8px; }
@keyframes shimmer { to { background-position: -200% 0; } }

/* branded empty state */
.empty { text-align: center; padding: 28px 18px; color: var(--slate); }
.empty svg { width: 46px; height: 46px; color: var(--green); opacity: .9; margin-bottom: 10px; }
.empty h4 { font-family: var(--font-display); font-weight: 700; color: var(--ink); margin: 0 0 4px; font-size: 16px; }
.empty p { margin: 0 0 14px; font-size: 14px; }

/* inline retry error (replaces alert()) */
.err-row { display: flex; gap: 10px; align-items: center; justify-content: space-between; background: rgba(178,59,46,.06); border: 1px solid rgba(178,59,46,.22); color: var(--danger); border-radius: 12px; padding: 10px 14px; font-size: 13px; }

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pop-in { 0% { transform: scale(.96); opacity: 0; } 60% { transform: scale(1.02); } 100% { transform: scale(1); opacity: 1; } }
@keyframes stamp { 0% { transform: scale(.3); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
@keyframes conf-fall { to { transform: translate(var(--dx),46px) rotate(var(--r)); opacity: 0; } }

/* ---- Motion kit v2 — dashboard entrances & feedback ----------------------
   .sg on a container: children rise in with a per-item stagger (JS sets --sgi
   per child, capped, then re-adds the class to replay on an explicit render).
   .tab-anim on a section: the same rise, used when a dashboard tab switches.
   .pop replays the stamp spring on a persistent element (e.g. bell badge).
   All of it is cosmetic only — content is never hidden without the class. */
@keyframes sg-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.sg > * { animation: sg-in .5s var(--ease-out) both; animation-delay: calc(var(--sgi, 0) * 48ms); }
.tab-anim { animation: sg-in .42s var(--ease-out) both; animation-delay: calc(var(--sgi, 0) * 55ms); }
.pop { animation: stamp .32s var(--ease-spring); }
/* Deepen the universal button press into a subtle spring squish. */
.btn:active { transform: translateY(1px) scale(.97); }
@media (prefers-reduced-motion: reduce) {
  .sg > *, .tab-anim, .pop { animation: none; }
  .btn:active { transform: none; }
}

/* button busy spinner — set aria-busy="true" */
.btn[aria-busy="true"] { color: transparent !important; position: relative; pointer-events: none; }
.btn[aria-busy="true"]::after { content: ""; position: absolute; width: 15px; height: 15px; border-radius: 50%; border: 2px solid #fff; border-right-color: transparent; animation: spin .7s linear infinite; }
.btn.ghost[aria-busy="true"]::after, .btn.danger[aria-busy="true"]::after, .btn.btn-outline[aria-busy="true"]::after { border-color: var(--green); border-right-color: transparent; }

@media (max-width: 560px) { .btn { min-height: 44px; } .theme-btn { width: 44px; height: 44px; } }

/* GLOBAL reduced-motion guard — every animated element also has a defined end state */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .skel { animation: none; background: var(--viz-track); }
  .reveal { opacity: 1; transform: none; }
  /* a frozen spinner looks broken — hide it and let the label show instead */
  .btn[aria-busy="true"] { color: inherit !important; }
  .btn[aria-busy="true"]::after { display: none; }
}

/* ---- Password field with show/hide eye ---- */
.pwwrap { position: relative; display: block; }
.pwwrap input { width: 100%; padding-right: 46px; }
.pweye { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); width: 36px; height: 36px;
  display: grid; place-items: center; border: 0; background: transparent; color: var(--slate); cursor: pointer;
  border-radius: 8px; padding: 0; }
.pweye:hover { color: var(--green); }
.pweye svg { width: 19px; height: 19px; }

/* ---- OAuth sign-in buttons (Google / Microsoft) ---- */
.oauth-sep { display: flex; align-items: center; gap: 12px; margin: 16px 0 12px;
  color: var(--slate); font-size: 12px; }
.oauth-sep::before, .oauth-sep::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.oauth-row { display: grid; gap: 9px; }
.oauth-btn { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%;
  padding: 11px 16px; border: 1px solid var(--line); border-radius: var(--r-btn); background: var(--white);
  color: var(--ink); font-family: var(--font-body); font-weight: 600; font-size: 14px; text-decoration: none;
  cursor: pointer; transition: border-color .15s ease, background .15s ease; }
.oauth-btn:hover { border-color: var(--green); }
.oauth-btn svg { width: 18px; height: 18px; flex: none; }

/* ---- Light/dark toggle button ---- */
.theme-btn { display: inline-grid; place-items: center; width: 38px; height: 38px; border-radius: 10px; border: 1px solid var(--line); background: transparent; color: var(--ink); cursor: pointer; padding: 0; transition: border-color .15s ease, color .15s ease; }
.theme-btn:hover { border-color: var(--green); color: var(--green); }
.theme-btn svg { width: 18px; height: 18px; }


/* ---- Optional features: hidden unless the server switches them on ----
   /api/config → features; each page tags <html> with f-<name> for every feature
   that's on (see OPTIONAL_FEATURES in server/app.js). Mark any block that belongs
   to one with data-feature="<name>". Pure CSS, so blocks rendered later by JS are
   covered too, and !important wins over a page re-showing a section. */
html:not(.f-tournaments)   [data-feature~="tournaments"],
html:not(.f-coaches)       [data-feature~="coaches"],
html:not(.f-waivers)       [data-feature~="waivers"],
html:not(.f-dupr)          [data-feature~="dupr"],
html:not(.f-push)          [data-feature~="push"],
html:not(.f-announcements) [data-feature~="announcements"],
html:not(.f-insights)      [data-feature~="insights"],
html:not(.f-matches)       [data-feature~="matches"],
html:not(.f-streaks)       [data-feature~="streaks"],
html:not(.f-weather)       [data-feature~="weather"] { display: none !important; }

