/* ============================================================
   Mahjmuddle - App Stylesheet
   Warm, welcoming palette with sculptural typography moments
   and smooth motion effects inspired by Obsidian Assembly.
   ============================================================ */

:root {
  --color-primary: #0F6E56;
  --color-primary-dark: #0B4F3E;
  /* The LOGO's pink (#FF61B0, sampled from public/images/logo.png).
     This was #FF00AE, a hot magenta about 51 away - clearly a different
     colour, and the one league pages use, so 29 sites did not match the mark
     in their own header. --color-brand-pink already matched; this did not. */
  --color-accent: #FF61B0;
  --color-accent-dark: #F57C00;

  /* Mahj Muddle platform brand colors. NOT overridden per-franchise — used
     on platform-admin pages and the public landing where there's no
     franchise context. Per-franchise pages should prefer var(--color-accent)
     which the franchise can customize via Site Settings. */
  --color-brand-pink: #FF61B0;
  --color-brand-pink-dark: #B2447B;
  --color-brand-pink-soft: #FFE9F4;

  --color-bg: #FAF8F3;
  --color-surface: #FFFFFF;
  --color-surface-alt: #F4F1EA;
  --color-border: #E8E3D6;

  --color-text: #1A1F1A;
  --color-text-muted: #6B6D63;
  --color-text-inverse: #FAF8F3;

  --color-error: #C0392B;
  --color-error-bg: #FADBD8;

  /* BOTTOM TAB BAR (v3.1.1). Fixed PLATFORM colours, deliberately independent
     of --color-primary, which head.ejs sets per league. See the .tabbar block
     for the full reasoning and the measured contrast ratios. */
  --tabbar-bg: #F4F1E8;
  --tabbar-border: #C4BEAC;
  --tabbar-inactive: #4E5147;
  --tabbar-active: #00701F;
  --tabbar-active-wash: rgba(0, 112, 31, 0.08);

  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-display: 'Georgia', 'Times New Roman', serif;

  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 24px;

  --shadow-sm: 0 1px 2px rgba(26, 31, 26, 0.06);
  --shadow-md: 0 4px 16px rgba(26, 31, 26, 0.08);
  --shadow-lg: 0 20px 60px rgba(26, 31, 26, 0.12);

  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--color-text);
  background: var(--color-bg);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--color-primary); text-decoration: none; }
a:hover { color: var(--color-primary-dark); }

/* ============================================================
   NAV
   ============================================================ */
.platform-nav,
.app-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: #FAF5EB;
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 4px solid #6F8265;
}

.platform-nav__inner,
.app-nav__inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 16px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

/* Variant of platform-nav__inner used only on the platform landing page.
   Establishes a positioning context for the decorative SVG that sits
   between the logo and the CTA. */
.platform-nav__inner--with-decoration {
  position: relative;
}

/* Decorative intertwined-loops SVG. Absolutely positioned so it doesn't
   take up flex space (logo and CTA stay where they are). Hidden below
   ~700px viewport width to avoid cramping the mobile header. */
.platform-nav__decoration {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 500px;
  height: 70px;
  pointer-events: none;
  z-index: 0;
}

.platform-nav__inner--with-decoration .platform-nav__brand,
.platform-nav__inner--with-decoration .platform-nav__links {
  position: relative;
  z-index: 1; /* keep logo/CTA above the decoration */
}

@media (max-width: 700px) {
  .platform-nav__decoration { display: none; }
}

.platform-nav__brand,
.app-nav__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  color: var(--color-text);
  font-size: 18px;
}

.platform-nav__logo,
.app-nav__logo {
  color: var(--color-primary);
  font-size: 20px;
}

/* Text wordmark shown in the nav when a brand has no logo uploaded yet.
   Added v3.70.22.

   color: inherit IS THE WHOLE POINT, and it is not a style choice. The
   sibling .app-nav__logo sets color: var(--color-primary), and a color on
   this child element beats the white the dark-header override applies to
   the parent .app-nav__brand - so a brand using the default header
   (#0F6E56) and a league on the default primary (#0F6E56) would have
   rendered its own name in exactly its own background color. Contrast
   ratio 1.00: invisible. That is the DEFAULT state of every new
   white-label brand until a logo is uploaded, so it would have been the
   first thing a customer saw.

   Same fix, same reason, as .logo-tm below: inherit the nav's computed
   auto-contrast color rather than pinning one. */
.app-nav__wordmark {
  color: inherit;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.1;
  max-width: 280px;
  overflow-wrap: anywhere;
  word-break: break-word; /* fallback for Safari before 15.4, which ignores overflow-wrap: anywhere */
}

.app-nav__name {
  font-family: var(--font-display);
  letter-spacing: -0.01em;
}

/* Height-constrained with free width, which is right for the platform brand's
   circular mark (3600x3600, so 100px tall renders 100px wide). A white-label
   brand may upload a WIDE horizontal wordmark instead, and at 100px tall that
   can run several hundred pixels across and crowd the nav links - on mobile it
   would push the hamburger, which sits at margin-left:auto, off the edge.
   max-width bounds the other axis; object-fit: contain then letterboxes inside
   whichever dimension binds first, so a circle stays a circle and a wordmark
   shrinks rather than distorting. No network league changes: 100px is well
   under the cap. Added v3.70.22. */
.app-nav__logo-img {
  height: 100px;
  width: auto;
  max-width: 280px;
  object-fit: contain;
}

/* Trademark superscript next to the logo mark. align-self keeps it at the
   top of the flex brand row (top-right of the logo). color:inherit lets it
   pick up the nav's auto-contrast — white on dark/branded headers (e.g. the
   green Longview header), dark text on the light default — instead of a fixed
   gray that washes out. */
.logo-tm {
  align-self: flex-start;
  font-size: 1rem;
  font-weight: 700;
  color: inherit;
  margin-left: -2px;
  margin-top: 4px;
  line-height: 1;
}

.platform-nav__links,
.app-nav__links {
  display: flex;
  align-items: center;
  gap: 20px;
}

/* PLATFORM NAV OVERFLOW. Eighteen destinations in one non-wrapping row need
   about 1470px. The hamburger only takes over below 720px, so on any ordinary
   laptop the row simply ran off the page - Data Purge, Help, the profile link,
   the version and Logout were all past the right edge, and because the band
   paints to the WINDOW edge rather than the content width it looked like the
   green stopped short. Every item added since (Brands, Team) pushed it further.

   Wrapping is the fix: the row folds to a second line instead of overflowing,
   and nothing is unreachable at any width. Scoped to .platform-nav__links
   deliberately - the league nav has far fewer items and is not changed here.
   min-width: 0 belongs here for the same reason: a flex item defaults to a
   content-based minimum, so without it the row will not shrink and flex-wrap
   never gets a chance to act. */
.platform-nav__links {
  flex-wrap: wrap;
  justify-content: flex-end;
  row-gap: 10px;
  min-width: 0;
}

.platform-nav__links a,
.app-nav__links a {
  color: var(--color-text);
  font-size: 15px;
  font-weight: 500;
  transition: color 0.2s;
}

.platform-nav__links a:hover,
.app-nav__links a:hover {
  color: var(--color-primary);
}

/* Active state on platform-nav links — pink underline marks the current
   section. JS in platform-nav.ejs adds the class based on URL path.
   Pink is hardcoded because platform pages don't have franchise context
   to pull --color-accent from. */
.platform-nav__link--active {
  color: var(--color-brand-pink-dark) !important;
  border-bottom: 2px solid var(--color-brand-pink);
  padding-bottom: 4px;
  margin-bottom: -2px; /* compensate so layout doesn't shift */
}

.app-nav__logout {
  color: var(--color-text-muted) !important;
}

.app-nav__install {
  font-size: 16px;
  text-decoration: none;
  opacity: 0.7;
  transition: opacity 0.15s;
}
.app-nav__install:hover {
  opacity: 1;
}

/* Hide install link when running as installed PWA */
@media (display-mode: standalone) {
  .app-nav__install {
    display: none !important;
  }
}

/* ============================================================
   MOBILE NAV — hamburger toggle
   ============================================================ */
.app-nav__toggle {
  display: none; /* hidden on desktop */
  background: transparent;
  border: 0;
  padding: 8px;
  cursor: pointer;
  flex-direction: column;
  gap: 5px;
  align-items: center;
  justify-content: center;
}

.app-nav__toggle-bar {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--color-text);
  border-radius: 2px;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

@media (max-width: 720px) {
  .app-nav__inner,
  .platform-nav__inner {
    flex-wrap: wrap;
    padding: 12px 16px;
    gap: 12px;
  }

  .app-nav__logo-img {
    height: 56px; /* smaller logo on mobile */
    max-width: 160px; /* a wide wordmark must not push the hamburger off-screen */
  }

  /* Same reason as the image cap: the text wordmark must not push the
     hamburger off the edge on a narrow screen. */
  .app-nav__wordmark {
    font-size: 17px;
    max-width: 160px;
  }

  .app-nav__toggle {
    display: flex; /* visible on mobile */
    margin-left: auto;
  }

  /* Collapse menu by default on mobile */
  .app-nav__links,
  .platform-nav__links {
    display: none;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 0;
    order: 3; /* drop below brand + toggle */
    padding-top: 0;
    margin-top: 8px;
    border-top: 1px solid var(--color-border);
  }

  .app-nav.is-nav-open .app-nav__links,
  .platform-nav.is-nav-open .platform-nav__links {
    display: flex;
  }

  .app-nav__links a,
  .platform-nav__links a {
    display: block;
    padding: 14px 4px;
    font-size: 16px;
    border-bottom: 1px solid var(--color-border);
    text-align: left;
    width: 100%;
    box-sizing: border-box;
  }

  /* Normalize the Join button to look like a regular nav link on mobile */
  .app-nav__links .btn,
  .app-nav__links .btn--small,
  .platform-nav__links .btn {
    display: block;
    background: transparent;
    color: var(--color-text);
    border: 0;
    padding: 14px 4px;
    font-size: 16px;
    font-weight: 500;
    border-radius: 0;
    text-align: left;
    width: 100%;
    box-sizing: border-box;
    border-bottom: 1px solid var(--color-border);
  }

  /* Style the install icon and inline text spans (admin name, version) */
  .app-nav__install,
  .platform-nav__links span,
  .app-nav__links span {
    display: block;
    padding: 12px 4px;
    font-size: 14px;
    border-bottom: 1px solid var(--color-border);
    text-align: left;
    width: 100%;
    box-sizing: border-box;
  }

  /* Last visible item — no border */
  .app-nav__links > *:last-child,
  .platform-nav__links > *:last-child {
    border-bottom: 0;
  }

  /* Animate hamburger to "X" when open */
  .app-nav.is-nav-open .app-nav__toggle-bar:nth-child(1),
  .platform-nav.is-nav-open .app-nav__toggle-bar:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
  }
  .app-nav.is-nav-open .app-nav__toggle-bar:nth-child(2),
  .platform-nav.is-nav-open .app-nav__toggle-bar:nth-child(2) {
    opacity: 0;
  }
  .app-nav.is-nav-open .app-nav__toggle-bar:nth-child(3),
  .platform-nav.is-nav-open .app-nav__toggle-bar:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
  }
}

/* ============================================================
   HERO
   ============================================================ */
.hero {
  padding: 80px 24px 64px;
  text-align: center;
  position: relative;
  overflow: hidden;
}

/* Subtle repeating mahjong-tile pattern in the background. Inline SVG via
   data URI so there's no asset to host. 4% opacity = present but never
   competing with content. The pattern's stroke uses the brand green;
   franchise-specific accent via var() inside SVG isn't possible (data URIs
   can't reference page CSS vars), so the green is hardcoded — acceptable
   because every franchise inherits this base color anyway. */
.hero::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='160' viewBox='0 0 120 160'><rect x='10' y='10' width='80' height='120' rx='6' fill='none' stroke='%23008E2C' stroke-width='2'/><rect x='14' y='14' width='72' height='112' rx='4' fill='none' stroke='%23008E2C' stroke-width='0.5'/><circle cx='50' cy='70' r='6' fill='%23FF2E99'/><circle cx='40' cy='55' r='3' fill='%23008E2C'/><circle cx='60' cy='55' r='3' fill='%23008E2C'/><circle cx='40' cy='85' r='3' fill='%23008E2C'/><circle cx='60' cy='85' r='3' fill='%23008E2C'/></svg>");
  background-repeat: repeat;
  opacity: 0.12;
  pointer-events: none;
  z-index: 0;
}

.hero__inner {
  position: relative;
  z-index: 1;
  max-width: 800px;
  margin: 0 auto;
}

.hero--platform {
  padding-top: 120px;
}

.hero__tag {
  display: inline-block;
  padding: 6px 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 24px;
}

.hero__title {
  font-family: var(--font-display);
  font-size: clamp(40px, 7vw, 72px);
  line-height: 1.05;
  font-weight: 400;
  letter-spacing: -0.02em;
  margin: 0 0 20px;
}

.hero__sub {
  font-size: clamp(16px, 2vw, 20px);
  color: var(--color-text-muted);
  max-width: 600px;
  margin: 0 auto 32px;
}

/* Season and size, just above the Join buttons. Deliberately quiet - this is
   context for a decision, not a statistic competing for attention. It replaced
   a row of large cards that pushed the schedule an entire screen down. */
.hero__facts {
  font-size: 14px;
  color: var(--color-text-muted);
  margin: 0 0 14px;
  letter-spacing: 0.01em;
}

/* The operator's own paragraph. Left-aligned deliberately - the hero above it
   is centred, but centred prose is hard to read past a couple of lines and
   this is meant to be read, not glanced at. pre-line keeps the line breaks
   they typed without letting any markup through, since the value is escaped
   in the template. */
.hero__description {
  font-size: 16px;
  line-height: 1.65;
  color: var(--color-text);
  /* fit-content, not a fixed width. Left-aligned text in a centred 620px box
     looks correct for a paragraph and visibly OFF-CENTRE for a single short
     line, which starts at the box's left edge rather than under the heading.
     Shrinking the box to its content centres the short case and still caps a
     real paragraph at a readable measure. */
  width: fit-content;
  max-width: 620px;
  margin: -16px auto 32px;
  text-align: left;
  white-space: pre-line;
}

.hero__cta {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}

/* ============================================================
   GROUP TEXT (match detail)
   The sms: scheme only does anything on a device that can actually send a
   text, so the button and the bolts are hidden unless the pointer is coarse
   (touch). Keying on pointer rather than width is more accurate than a
   breakpoint - a narrow desktop window still can't send an SMS.
   ============================================================ */
.sms-legend,
.sms-bolt,
.sms-group { display: none; }

@media (pointer: coarse) {
  .sms-legend {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--color-text-muted);
    margin: 2px 0 10px;
  }
  .sms-legend svg { width: 12px; height: 12px; color: var(--color-primary); flex-shrink: 0; }

  .sms-bolt {
    /* The base rule above hides all three sms elements on non-touch devices.
       The legend and group button get their display restored here; the bolt
       needs it too, or it stays hidden even on a phone. */
    display: inline-block;
    width: 15px;
    height: 15px;
    color: var(--color-primary);
    vertical-align: -2px;
    margin-left: 4px;
    flex-shrink: 0;
  }

  .sms-group { display: block; margin-top: 16px; }
  .sms-group__note {
    font-size: 12.5px;
    color: var(--color-text-muted);
    line-height: 1.45;
    margin: 8px 0 0;
  }
}

/* ============================================================
   NEXT MATCH CARD (hero, signed-in players)
   ============================================================ */
.next-match {
  display: block;
  max-width: 380px;
  margin: 0 auto 24px;
  padding: 16px 20px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  text-align: left;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
a.next-match:hover {
  border-color: var(--color-primary);
  transform: translateY(-1px);
}
.next-match__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}
.next-match__label {
  font-size: 13px;
  color: var(--color-text-muted);
}
.next-match__badge {
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.next-match__badge--accent {
  background: rgba(15, 110, 86, 0.12);
  color: var(--color-primary-dark);
}
.next-match__badge--warning {
  background: rgba(180, 95, 0, 0.14);
  color: #8A4700;
}
.next-match__when {
  font-size: 19px;
  font-weight: 600;
  margin: 0 0 6px;
  line-height: 1.3;
}
.next-match__where {
  font-size: 14px;
  color: var(--color-text-muted);
  margin: 0;
}
.next-match--empty {
  border-style: dashed;
  text-align: center;
}
@media (max-width: 480px) {
  .next-match { padding: 14px 16px; }
  .next-match__when { font-size: 17px; }
}

/* ============================================================
   STATS
   ============================================================ */

.my-matches {
  padding: 0 24px 40px;
}

.my-matches__inner {
  max-width: 720px;
  margin: 0 auto;
}

.my-matches__card:hover {
  background: var(--color-surface-alt) !important;
  transform: translateY(-1px);
  transition: all 0.15s ease;
}

/* One or two cards would otherwise stretch the full 1000px and look stranded.
   A new league often shows only Current Season, so narrow the row to suit. */
/* Upcoming matches on the public league page. Deliberately plain - a list of
   times, not cards. The value here is that the text is unique per league and
   changes weekly; dressing it up would only push the rest of the page down. */
.upcoming { padding: 48px 20px 8px; }
.upcoming__inner { max-width: 620px; margin: 0 auto; text-align: center; }
.upcoming__list {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  border-top: 1px solid var(--color-border);
}
.upcoming__item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 2px;
  border-bottom: 1px solid var(--color-border);
  text-align: left;
}
.upcoming__when { font-size: 15px; color: var(--color-text); }
.upcoming__seats {
  font-size: 13px;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.upcoming__note {
  font-size: 13px;
  color: var(--color-text-muted);
  margin: 14px 0 0;
}
@media (max-width: 520px) {
  /* Stack rather than squeeze - a long weekday plus a time plus a seat count
     does not fit on one line on a small phone. */
  .upcoming__item { flex-direction: column; gap: 2px; }
}


/* KEPT, despite the public league page no longer using them. The admin
   dashboard, the revenue page and the platform league-detail page all render
   .stats / .stats__inner grids. Deleting these as "dead CSS" when the public
   page stopped using them collapsed three admin pages - caught in testing.
   The --n1 / --n2 modifiers ARE gone: they existed only to narrow the public
   row when cards were hidden, and nothing else ever used them. */
.stats {
  padding: 40px 20px;
}
.stats__inner {
  max-width: 1000px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

.stat {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 32px 24px;
  text-align: center;
  transition: transform 0.4s var(--ease-out-expo),
              box-shadow 0.4s var(--ease-out-expo);
}

.stat:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

.stat__value {
  display: block;
  font-family: var(--font-display);
  font-size: 48px;
  font-weight: 400;
  color: var(--color-primary);
  letter-spacing: -0.02em;
  line-height: 1;
  margin-bottom: 8px;
}

.stat__label {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  font-weight: 500;
}

/* Platform brand accent for the "headline" stat on a page (your earnings,
   your cut, your share). Pink because these are platform-level pages
   without franchise branding context. Used sparingly — at most one per
   stat group, on the metric that matters most. */
.stat--brand-accent {
  border: 2px solid var(--color-brand-pink);
}

.stat--brand-accent .stat__value {
  color: var(--color-brand-pink-dark);
}

.stat--brand-accent .stat__label {
  color: var(--color-brand-pink-dark);
}

/* ============================================================
   SECTIONS
   ============================================================ */
.section-tag {
  display: inline-block;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  font-weight: 500;
  margin-bottom: 16px;
}

.section-title {
  font-family: var(--font-display);
  font-size: clamp(32px, 5vw, 56px);
  line-height: 1.1;
  font-weight: 400;
  letter-spacing: -0.02em;
  margin: 0 0 20px;
}

.section-sub {
  font-size: clamp(16px, 2vw, 20px);
  color: var(--color-text-muted);
  max-width: 600px;
}

/* ============================================================
   HOW IT WORKS
   ============================================================ */
.how {
  padding: 48px 16px;
  background: var(--color-surface-alt);
}

@media (min-width: 768px) {
  .how { padding: 80px 24px; }
}

.how__inner {
  max-width: 1200px;
  margin: 0 auto;
}

.how__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  margin-top: 32px;
}

.how__step {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 24px 20px;
  transition: transform 0.4s var(--ease-out-expo);
}

@media (min-width: 768px) {
  .how__grid {
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 32px;
    margin-top: 48px;
  }
  .how__step { padding: 40px 32px; }
}

.how__step:hover {
  transform: translateY(-4px);
}

.how__num {
  font-family: var(--font-display);
  font-size: 40px;
  color: var(--color-accent-dark);
  font-weight: 400;
  display: block;
  margin-bottom: 16px;
  letter-spacing: -0.02em;
}

.how__step h3 {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 400;
  margin: 0 0 12px;
}

.how__step p {
  color: var(--color-text-muted);
  margin: 0;
}

/* ============================================================
   AUTH PAGES
   ============================================================ */
.auth-main {
  min-height: calc(100vh - 80px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
}

.auth-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 40px;
  width: 100%;
  max-width: 440px;
  box-shadow: var(--shadow-md);
}

.auth-card--wide {
  max-width: 560px;
}

.auth-card__header {
  text-align: center;
  margin-bottom: 32px;
}

.auth-card__tag {
  display: inline-block;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  font-weight: 500;
  margin-bottom: 12px;
}

.auth-card__title {
  font-family: var(--font-display);
  font-size: 36px;
  line-height: 1.1;
  font-weight: 400;
  letter-spacing: -0.02em;
  margin: 0 0 8px;
}

.auth-card__sub {
  color: var(--color-text-muted);
  margin: 0;
}

.auth-card__footer {
  text-align: center;
  margin-top: 24px;
  font-size: 14px;
  color: var(--color-text-muted);
}

/* ============================================================
   FORMS
   ============================================================ */
.form {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

@media (max-width: 480px) {
  .form__row { grid-template-columns: 1fr; }
}

.form__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  border: none;
  padding: 0;
  margin: 0;
}

.form__label {
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text);
}

.form__hint {
  font-size: 12px;
  color: var(--color-text-muted);
  font-weight: 400;
}

.form__field input[type="email"],
.form__field input[type="password"],
.form__field input[type="text"],
.form__field input[type="tel"],
.form__field input[type="date"],
.form__field input[type="datetime-local"],
.form__field input[type="time"],
.form__field input[type="number"],
.form__field textarea,
.form__field select {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 16px; /* 16px MINIMUM, NOT A STYLE CHOICE (v3.25.0).
     iOS Safari auto-zooms the page when a field smaller than 16px is focused.
     At 15px it did, and the zoom is what made the bottom tab bar look "cut in
     half": the bar is laid out at full page width, so once the visible area
     narrows the right-hand tabs fall off the edge of the screen.
     Caught from a real report - the diagnostics panel recorded vvScale=1.067
     on the profile edit form, where every previous sample had read exactly 1.
     Do not lower this below 16px. */
  color: var(--color-text);
  background: var(--color-surface);
  transition: border-color 0.2s, box-shadow 0.2s;
}

/* time and number were missing from the list above until v3.68.4, so the
   Time box beside a styled Date box rendered at the browser default - a
   sliver on a phone, and under the 16px iOS auto-zoom threshold the comment
   above is about. Players complained. The explicit min-height is for iOS,
   which draws these two as native pickers and does not always honour
   padding as a height contribution the way it does for text. */
.form__field input[type="time"],
.form__field input[type="number"] {
  min-height: 48px;
}

.form__field input:focus,
.form__field textarea:focus,
.form__field select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(15, 110, 86, 0.12);
}

.form__radio-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}

.form__radio {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s;
}

.form__radio:hover {
  border-color: var(--color-primary);
  background: var(--color-surface-alt);
}

.form__radio input[type="radio"] {
  margin-top: 4px;
  accent-color: var(--color-primary);
}

.form__radio:has(input:checked) {
  border-color: var(--color-primary);
  background: var(--color-surface-alt);
}

.form__radio-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.form__radio-body strong {
  font-weight: 600;
}

.form__radio-body span {
  font-size: 13px;
  color: var(--color-text-muted);
}

/* ============================================================
   BUTTONS
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 24px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.2s var(--ease-out-expo),
              background 0.2s,
              color 0.2s,
              border-color 0.2s,
              box-shadow 0.2s;
  white-space: nowrap;
}

.btn:hover {
  transform: translateY(-1px);
}

.btn:active {
  transform: translateY(0);
}

.btn--primary {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}

.btn--primary:hover {
  background: var(--color-primary-dark);
  color: var(--color-text-inverse);
  box-shadow: var(--shadow-md);
}

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

.btn--ghost:hover {
  border-color: var(--color-text);
  background: var(--color-surface);
}

/* Accent-outlined button. Uses the franchise's accent color (or pink on
   platform pages). For "secondary positive" CTAs that are equal-weight
   alternatives to the primary action — like "Sign In" next to "Register". */
.btn--accent-outline {
  background: transparent;
  border-color: var(--color-accent);
  color: var(--color-accent-dark);
  border-width: 1.5px;
}

.btn--accent-outline:hover {
  background: var(--color-accent);
  color: var(--color-text-inverse);
  border-color: var(--color-accent);
}

.btn--full { width: 100%; }

.btn--small {
  padding: 8px 16px;
  font-size: 14px;
}

.btn--large {
  padding: 16px 32px;
  font-size: 17px;
}

/* ============================================================
   ALERTS
   ============================================================ */
.alert {
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  margin-bottom: 20px;
  font-size: 14px;
}

.alert--error {
  background: var(--color-error-bg);
  color: var(--color-error);
  border: 1px solid rgba(192, 57, 43, 0.2);
}

/* ============================================================
   CONTAINER / MAIN
   ============================================================ */
.app-main {
  padding: 48px 24px;
}

.container {
  max-width: 1000px;
  margin: 0 auto;
}

/* ============================================================
   REVEAL ANIMATIONS
   Elements with .reveal fade+slide in when scrolled into view
   ============================================================ */
/* FAIL-OPEN (v3.5.1). This used to be a bare `.reveal { opacity: 0 }`, which
   made the hidden state the DEFAULT and put the only way out of it in
   JavaScript. On the platform league-detail page every panel below the stat
   cards - platform fee, pricing model, the terms pins - was sitting in the
   HTML at opacity 0 and simply never appeared. Nothing errored; the content
   was just unreachable.
   Now the hidden state only applies once app.js has marked the document with
   .js-reveal, so if the script never runs, never parses, or is blocked, the
   page renders plainly instead of losing half its content. Content becoming
   invisible must never be the default. */
.js-reveal .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.8s var(--ease-out-expo),
              transform 0.8s var(--ease-out-expo);
  transition-delay: var(--reveal-delay, 0s);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger children of a reveal group */
.reveal:nth-child(1) { --reveal-delay: 0s; }
.reveal:nth-child(2) { --reveal-delay: 0.08s; }
.reveal:nth-child(3) { --reveal-delay: 0.16s; }
.reveal:nth-child(4) { --reveal-delay: 0.24s; }
.reveal:nth-child(5) { --reveal-delay: 0.32s; }

/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  /* Must match the .js-reveal selector above or this override loses. */
  .js-reveal .reveal { opacity: 1; transform: none; }
}

/* ============================================================
   FORM EXTRAS (for application form + admin forms)
   ============================================================ */
.form-section-title {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--color-text);
  margin: 16px 0 4px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--color-border);
}

.form-section-title:first-child {
  margin-top: 0;
}

.form__field textarea {
  resize: vertical;
  min-height: 80px;
  line-height: 1.5;
}

.form__hint--success {
  color: #1b7a3a;
}

.form__hint--error {
  color: var(--color-error);
}

.form__footer-note {
  text-align: center;
  color: var(--color-text-muted);
  font-size: 13px;
  margin: 16px 0 0;
}

/* Slug input with prefix */
.slug-input {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  overflow: hidden;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.slug-input:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(15, 110, 86, 0.12);
}

.slug-input__prefix {
  padding: 12px 14px;
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  font-size: 15px;
  font-family: 'SF Mono', Monaco, Consolas, monospace;
  border-right: 1px solid var(--color-border);
  white-space: nowrap;
}

.slug-input input {
  flex: 1;
  border: none !important;
  box-shadow: none !important;
  padding: 12px 14px;
  /* 16px for the same reason as .form__field above - this one is on the public
     application form, which people fill in on their phones. flex:1 with
     min-width:0 means the wider text cannot push the prefix; the input just
     takes whatever space is left. */
  font-size: 16px;
  font-family: inherit;
  background: transparent;
  min-width: 0;
}

.slug-input input:focus {
  outline: none;
  box-shadow: none !important;
}

/* ============================================================
   ALERTS - additional variants
   ============================================================ */
.alert--info {
  background: #E8F4FD;
  color: #1A5490;
  border: 1px solid #B8DAEE;
}

/* ============================================================
   EMPTY STATE
   ============================================================ */
.empty-state {
  text-align: center;
  padding: 60px 24px;
  color: var(--color-text-muted);
  background: var(--color-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-lg);
}

/* ============================================================
   PLATFORM ADMIN
   ============================================================ */
.pa-tabs {
  display: flex;
  gap: 4px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 4px;
  margin-bottom: 32px;
  /* Allow horizontal scrolling on narrow screens so the rightmost tabs
     don't get clipped off the edge on mobile. fit-content keeps the
     container compact on desktop where all tabs fit; max-width caps
     the visual width so the scroll behavior kicks in if needed. */
  width: fit-content;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.pa-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text-muted);
  transition: background 0.2s, color 0.2s;
  /* Prevent each tab from shrinking when the container scrolls — they
     should keep their natural size and overflow horizontally. */
  flex-shrink: 0;
  white-space: nowrap;
}

.pa-tab:hover {
  background: var(--color-surface-alt);
  color: var(--color-text);
}

.pa-tab.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}

.pa-tab__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 7px;
  background: var(--color-surface-alt);
  color: var(--color-text);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}

.pa-tab.is-active .pa-tab__count {
  background: rgba(255, 255, 255, 0.25);
  color: var(--color-text-inverse);
}

/* Application list */
.pa-app-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.pa-app-card {
  display: block;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 20px 24px;
  color: var(--color-text);
  transition: transform 0.3s var(--ease-out-expo),
              border-color 0.2s,
              box-shadow 0.3s var(--ease-out-expo);
}

.pa-app-card:hover {
  transform: translateY(-2px);
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
  color: var(--color-text);
}

.pa-app-card__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 8px;
}

.pa-app-card__head h3 {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  margin: 0;
}

.pa-app-card__city {
  font-size: 13px;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.pa-app-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  font-size: 13px;
  color: var(--color-text-muted);
  font-family: 'SF Mono', Monaco, Consolas, monospace;
}

.pa-app-card__sub {
  margin-top: 8px;
  font-size: 14px;
  color: var(--color-text);
}

/* Application detail view */
.pa-detail {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 32px;
  margin: 32px 0;
}

.pa-detail__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px;
  margin-bottom: 16px;
}

.pa-detail__label {
  display: block;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  font-weight: 500;
  margin-bottom: 4px;
}

.pa-detail__value {
  font-size: 15px;
  color: var(--color-text);
  font-weight: 500;
}

.pa-detail__section {
  margin-top: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--color-border);
}

.pa-detail__section p {
  margin: 8px 0 0;
  line-height: 1.6;
  white-space: pre-wrap;
}

/* Review sections (approve/reject forms) */
.pa-review {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 32px;
  margin-bottom: 20px;
}

.pa-review h2 {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 400;
  margin: 0 0 20px;
}

.pa-review--danger {
  border-color: rgba(192, 57, 43, 0.25);
}

.pa-review--danger h2 {
  color: var(--color-error);
}

/* Approved confirmation card */
.pa-approved-card {
  background: var(--color-surface-alt);
  border-radius: var(--radius-md);
  padding: 20px 24px;
  margin-bottom: 20px;
}

.pa-approved-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 8px 0;
  border-bottom: 1px solid var(--color-border);
  flex-wrap: wrap;
}

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

/* Franchise list */
.pa-franchise-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
}

.pa-franchise-card {
  display: block;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 20px 24px;
  color: var(--color-text);
  transition: transform 0.3s var(--ease-out-expo),
              border-color 0.2s,
              box-shadow 0.3s var(--ease-out-expo);
  text-decoration: none;
}

.pa-franchise-card:hover {
  transform: translateY(-2px);
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
  color: var(--color-text);
}

.pa-franchise-card h3 {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 400;
  margin: 0 0 4px;
}

.pa-franchise-card__url {
  font-family: 'SF Mono', Monaco, Consolas, monospace;
  font-size: 13px;
  margin: 4px 0;
}

.pa-franchise-card__meta {
  font-size: 13px;
  color: var(--color-text-muted);
  margin: 0;
}

/* ============================================================
   MATCHES
   ============================================================ */
.matches-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  margin-bottom: 32px;
  flex-wrap: wrap;
}

.match-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Match card (used in list) */
.match-card {
  display: flex;
  gap: 20px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 20px;
  color: var(--color-text);
  transition: transform 0.3s var(--ease-out-expo),
              border-color 0.2s,
              box-shadow 0.3s var(--ease-out-expo);
}

.match-card:hover {
  transform: translateY(-2px);
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
  color: var(--color-text);
}

.match-card__date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 60px;
  padding: 8px 12px;
  background: var(--color-surface-alt);
  border-radius: var(--radius-sm);
  text-align: center;
  flex-shrink: 0;
}

.match-card__day {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  font-weight: 500;
}

.match-card__num {
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1.1;
  font-weight: 400;
  letter-spacing: -0.02em;
}

.match-card__month {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}

.match-card__body {
  flex: 1;
  min-width: 0;
}

.match-card__top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 4px;
}

.match-card__time {
  font-size: 14px;
  font-weight: 500;
}

.match-card__status {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}

.match-card__status--open {
  background: #FFF8E1;
  color: #F57C00;
}

.match-card__status--confirmed {
  background: #E8F5E9;
  color: #0F6E56;
}

.match-card__status--completed {
  background: #E3F2FD;
  color: #1565C0;
}

.match-card__status--cancelled {
  background: #FAFAFA;
  color: #999;
}

.match-card__location {
  font-size: 15px;
  margin-bottom: 10px;
}

.match-card__players {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.match-card__player {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--color-text);
}

.match-card__player--empty {
  color: var(--color-text-muted);
}

.match-card__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-primary);
  color: white;
  font-size: 11px;
  font-weight: 600;
  flex-shrink: 0;
}

.match-card__avatar--empty {
  background: var(--color-border);
  color: var(--color-text-muted);
}

.match-card__rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  font-size: 10px;
  font-weight: 600;
  color: var(--color-text-muted);
}

/* ============================================================
   MATCH DETAIL
   ============================================================ */
.back-link {
  display: inline-block;
  font-size: 14px;
  color: var(--color-text-muted);
  margin-bottom: 24px;
}

.back-link:hover {
  color: var(--color-primary);
}

.match-detail {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 40px;
}

.match-detail__status {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  margin-bottom: 16px;
}

.match-detail__date {
  font-family: var(--font-display);
  font-size: clamp(28px, 4vw, 40px);
  line-height: 1.1;
  font-weight: 400;
  letter-spacing: -0.02em;
  margin: 0 0 4px;
}

.match-detail__time {
  font-size: 20px;
  color: var(--color-text-muted);
  margin: 0 0 28px;
}

.match-detail__info {
  border-top: 1px solid var(--color-border);
  padding-top: 24px;
  margin-bottom: 28px;
}

.match-detail__row {
  margin-bottom: 16px;
}

.match-detail__row-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 16px;
  margin-top: 16px;
}

.match-detail__players {
  border-top: 1px solid var(--color-border);
  padding-top: 24px;
  margin-bottom: 28px;
}

.match-detail__player-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 12px;
}

@media (max-width: 480px) {
  .match-detail__player-grid { grid-template-columns: 1fr; }
}

.match-detail__player {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.match-detail__player--empty {
  border-style: dashed;
  color: var(--color-text-muted);
}

.match-detail__badge {
  display: inline-block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 2px 6px;
  margin-left: 4px;
  color: var(--color-text-muted);
}

.match-detail__actions {
  border-top: 1px solid var(--color-border);
  padding-top: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Success alert */
.alert--success {
  background: #E8F5E9;
  color: #0F6E56;
  border: 1px solid rgba(15, 110, 86, 0.2);
}

/* ============================================================
   SCORES
   ============================================================ */
.score-table {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.score-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 16px 20px;
}

.score-row--confirmed {
  border-color: rgba(15, 110, 86, 0.2);
  background: #FAFFF9;
}

.score-row__player {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
}

.score-row__score {
  display: flex;
  align-items: center;
  gap: 10px;
}

.score-row__value {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 400;
  letter-spacing: -0.02em;
  min-width: 60px;
  text-align: right;
}

.score-row__badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}

.score-row__badge--confirmed {
  background: #E8F5E9;
  color: #0F6E56;
}

.score-row__badge--pending {
  background: #FFF8E1;
  color: #F57C00;
}

.score-row__actions {
  display: flex;
  gap: 12px;
  padding: 8px 0 16px;
  align-items: flex-start;
}

.score-dispute {
  flex: 0;
}

.score-dispute summary {
  cursor: pointer;
  white-space: nowrap;
}

.score-dispute__form {
  margin-top: 12px;
  display: flex;
  gap: 8px;
  align-items: flex-end;
}

.score-dispute__form .form__field {
  flex: 1;
}

/* ============================================================
   LEADERBOARD
   ============================================================ */
.leaderboard-note {
  color: var(--color-text-muted);
  font-size: 14px;
  margin-bottom: 24px;
}

.leaderboard-table {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.lb-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  /* Tightened from 16px/20px: with stats in a 2x2 grid the row no longer
     needs the extra vertical room. */
  padding: 10px 18px;
  transition: transform 0.3s var(--ease-out-expo),
              box-shadow 0.3s var(--ease-out-expo);
}

.lb-row:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.lb-row--me {
  border-color: var(--color-primary);
  background: #FAFFF9;
}

/* Champion accent on the #1 row. Uses the franchise's accent color for the
   border so each muddle's first-place row reflects their brand. The
   background stays a neutral cream (works across all accent hues). */
.lb-row--first {
  border-left: 4px solid var(--color-accent);
  background: var(--color-surface-alt);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.lb-row--first .lb-row__value {
  color: var(--color-accent-dark);
}

.lb-row__rank {
  min-width: 40px;
  text-align: center;
}

.lb-row__medal {
  font-size: 24px;
}

.lb-row__position {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--color-text-muted);
}

.lb-row__player {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
}

/* Stats sit in a 2-column grid so up to four figures occupy two rows
   instead of stacking four deep — this is what keeps the row compact. */
.lb-row__stats {
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-content: end;
  justify-items: end;
  align-content: center;
  column-gap: 14px;
  row-gap: 1px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.lb-row__proposals {
  color: var(--color-accent-dark);
  font-weight: 600;
  cursor: help;
}

.lb-row__diversity {
  color: var(--color-primary);
  font-weight: 600;
  cursor: help;
}

.lb-row__ineligible {
  display: inline-block;
  margin-top: 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #856404;
  background: rgba(255, 193, 7, 0.18);
  padding: 1px 6px;
  border-radius: 3px;
  cursor: help;
}

.lb-row__score {
  min-width: 80px;
  text-align: right;
}

.lb-row__value {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--color-primary);
}

@media (max-width: 580px) {
  .lb-row { flex-wrap: wrap; }
  .lb-row__stats { column-gap: 10px; font-size: 11px; }
}

/* ============================================================
   FRANCHISE ADMIN
   ============================================================ */
.admin-nav {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-bottom: 32px;
  padding-bottom: 0;
  border-bottom: 1px solid var(--color-border);
}

.admin-nav__link {
  padding: 10px 16px 14px;
  border-radius: 0;
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text-muted);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px; /* overlap the container's bottom border */
  transition: color 0.2s, border-color 0.2s;
}

.admin-nav__link:hover {
  background: transparent;
  color: var(--color-text);
}

.admin-nav__link--active {
  background: transparent;
  color: var(--color-accent-dark);
  border-bottom-color: var(--color-accent);
}

.admin-nav__link--active:hover {
  background: transparent;
  color: var(--color-accent-dark);
  border-bottom-color: var(--color-accent);
}

/* Player list */
.admin-player-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.admin-player-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 16px 20px;
}

.admin-player-card__info {
  margin-bottom: 4px;
}

.admin-player-card__name {
  display: flex;
  align-items: center;
  gap: 12px;
}

.admin-player-card__edit {
  margin-top: 8px;
}

.admin-player-card__edit summary {
  cursor: pointer;
}

.admin-edit-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  margin-top: 12px;
  padding: 16px;
  background: var(--color-surface-alt);
  border-radius: var(--radius-sm);
}

.admin-edit-form .form__field {
  min-width: 140px;
  flex: 1;
}

.admin-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  cursor: pointer;
  padding: 8px 0;
}

.admin-checkbox input { accent-color: var(--color-primary); }

.admin-edit-actions {
  display: flex;
  gap: 8px;
}

/* Season cards */
.admin-season-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 20px 24px;
  margin-bottom: 12px;
}

.admin-season-card__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.admin-season-card__header h3 {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}

.admin-season-card details {
  margin-top: 12px;
}

.admin-season-card summary {
  cursor: pointer;
}

/* Revenue rows */
.revenue-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}

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

.revenue-row__player {
  display: flex;
  align-items: center;
  gap: 12px;
}

.revenue-row__status {
  flex-shrink: 0;
}

/* ============================================================
   PLATFORM BILLING TABLE
   ============================================================ */
.billing-table {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.billing-header {
  display: grid;
  grid-template-columns: 2fr 1.2fr 0.8fr 0.8fr 1fr 1fr;
  gap: 8px;
  padding: 12px 20px;
  background: var(--color-surface-alt);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  font-weight: 600;
}

.billing-row {
  display: grid;
  grid-template-columns: 2fr 1.2fr 0.8fr 0.8fr 1fr 1fr;
  gap: 8px;
  padding: 16px 20px;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  align-items: center;
  color: var(--color-text);
  transition: background 0.2s;
}

a.billing-row:hover {
  background: var(--color-surface-alt);
  color: var(--color-text);
}

.billing-row--total {
  background: var(--color-surface-alt);
  font-weight: 600;
  border-top: 2px solid var(--color-border);
}

.billing-row__franchise {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.billing-row__sub {
  font-size: 12px;
  color: var(--color-text-muted);
  display: block;
}

.billing-row__cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 15px;
}

.billing-row__cell--highlight {
  color: var(--color-primary);
  font-weight: 600;
}

@media (max-width: 768px) {
  .billing-header { display: none; }
  .billing-row {
    grid-template-columns: 1fr 1fr;
    gap: 8px 16px;
  }
  .billing-row__franchise {
    grid-column: 1 / -1;
  }
}

/* ============================================================
   BBS PER-GAME SCORING
   ============================================================ */
.totals-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
  margin-top: 12px;
}

.totals-cell {
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.totals-name {
  font-size: 13px;
  color: var(--color-text-muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.totals-score {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 400;
}

.game-block {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 20px 24px;
}

.game-block__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.game-block__title {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  margin: 0;
}

.game-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.game-form__section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.radio-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 15px;
}

.radio-option:hover { border-color: var(--color-primary); }

.radio-option input { accent-color: var(--color-primary); }

.bonus-checkboxes {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  background: var(--color-surface-alt);
  border-radius: var(--radius-sm);
}

.game-block__details {
  padding: 12px 16px;
  background: var(--color-surface-alt);
  border-radius: var(--radius-sm);
  margin-bottom: 12px;
}

.game-block__details p { margin: 0 0 4px; }
.game-block__details p:last-child { margin: 0; }

.game-block__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* ============================================================
   Password visibility toggle (added by app.js)
   ============================================================ */
.pw-toggle-wrap {
  position: relative;
  display: block;
  width: 100%;
}
.pw-toggle-wrap input[type="password"],
.pw-toggle-wrap input[type="text"] {
  width: 100%;
  padding-right: 44px; /* leave room for the eye button */
}
.pw-toggle-btn {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: 0;
  padding: 6px 8px;
  cursor: pointer;
  color: #888;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.pw-toggle-btn:hover,
.pw-toggle-btn:focus {
  color: var(--color-primary, #0F6E56);
  background-color: rgba(15, 110, 86, 0.08);
  outline: none;
}
.pw-toggle-btn:focus-visible {
  box-shadow: 0 0 0 2px rgba(15, 110, 86, 0.3);
}

/* ============================================================
   AD BANNER — operator-managed affiliate ads on franchise homepage.
   Sits between the header and the hero. 5:1 aspect ratio, rotates
   through enabled ads. Hidden entirely if no ads exist (the EJS
   wrapper doesn't render the container in that case).
   ============================================================ */
.ad-banner {
  position: relative;
  width: 100%;
  /* 5:1 aspect ratio. aspect-ratio is well-supported in modern browsers;
     padding-top fallback would be 20% if needed. */
  aspect-ratio: 5 / 1;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  overflow: hidden;
  /* Subtle so the banner is unobtrusive — players notice but it doesn't
     dominate the page. */
}

.ad-banner__slot {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  /* All slots are stacked on top of each other. The active one is
     visible; others are hidden via opacity. Cross-fade animation when
     rotation swaps the active slot. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.5s ease-in-out;
}

.ad-banner__slot.is-active {
  opacity: 1;
  visibility: visible;
  /* z-index so the active slot is clickable; inactive slots can't
     intercept clicks even with visibility:hidden as a fallback. */
  z-index: 2;
}

.ad-banner__img {
  width: 100%;
  height: 100%;
  /* contain rather than cover so the operator's image isn't cropped —
     they expect what they uploaded to be visible in full. Excess space
     shows the .ad-banner background. */
  object-fit: contain;
  display: block;
}

.ad-banner__title {
  position: absolute;
  left: 12px;
  bottom: 8px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 12px;
  padding: 3px 8px;
  border-radius: 4px;
  pointer-events: none;
}

.ad-banner__label {
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  background: rgba(255, 255, 255, 0.85);
  padding: 2px 6px;
  border-radius: 3px;
  z-index: 3;
  pointer-events: none;
}

/* ============================================================
   MATCH TYPE BADGES
   Retro / Party / Vintage / Power. Rendered by
   views/partials/match-type-badge.ejs; definitions live in
   services/match-types.js.

   Power is a red OUTLINE, not a solid fill: solid red is this app's
   error/cancelled treatment, and a player scanning the match list would
   read it as a warning about the match rather than a description of it.
   Retro shares that outline construction so the two ends of the
   spectrum look like a pair.
   ============================================================ */
.mt-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  vertical-align: middle;
}
.mt-badge__confetti { flex: none; display: block; }
.mt-badge--party { padding-left: 6px; }

.mt-badge--retro {
  background: var(--color-surface);
  color: #185FA5;
  border: 1px solid #85B7EB;
}
.mt-badge--party {
  background: #E1F5EE;
  color: #0F6E56;
}
.mt-badge--vintage {
  background: #EEEDFE;
  color: #534AB7;
}
.mt-badge--power {
  background: var(--color-surface);
  color: var(--color-error);
  border: 1px solid var(--color-error);
  font-style: italic;
}

.mt-blanks {
  font-size: 11px;
  color: var(--color-text-muted);
  white-space: nowrap;
  vertical-align: middle;
}

/* Row wrapper so the badge and chip sit together above a match's details */
.mt-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

/* ---- Propose form picker ---- */
.mt-options { display: block; }
.mt-option {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin-bottom: 8px;
  cursor: pointer;
}
.mt-option:last-child { margin-bottom: 0; }
.mt-option input[type="radio"] { margin-top: 3px; flex: none; }
.mt-option__body { min-width: 0; }
.mt-option__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mt-option__desc {
  display: block;
  font-size: 13px;
  color: var(--color-text-muted);
  line-height: 1.6;
  margin-top: 4px;
}
.mt-option:has(input:checked) {
  border-color: var(--color-primary);
  background: rgba(15, 110, 86, 0.05);
}

/* On a phone the four full descriptions push date, time and location off
   the screen. Clamp the unselected ones to two lines - enough to reach a
   natural resting point, unlike one line which cuts mid-thought - and let
   the selected one expand. Pure CSS, no JS. The full text is always in the
   DOM, so screen readers are unaffected. */
@media (max-width: 640px) {
  .mt-option__desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .mt-option:has(input:checked) .mt-option__desc {
    display: block;
    -webkit-line-clamp: none;
    overflow: visible;
  }
}

/* Join screen: the full description, framed in the type's own colour */
.mt-callout {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px;
  margin: 12px 0;
}
.mt-callout--retro   { border-color: #85B7EB; }
.mt-callout--party   { border-color: #9FE1CB; }
.mt-callout--vintage { border-color: #CECBF6; }
.mt-callout--power   { border-color: var(--color-error); }
.mt-callout__desc {
  font-size: 13px;
  color: var(--color-text-muted);
  line-height: 1.65;
  margin: 6px 0 0;
}


/* =========================================================
   BOTTOM TAB BAR (v2.117.0) - phones only.
   Hidden by default and only switched on under 768px, so a
   desktop browser never shows a fixed bar across the bottom.
   ========================================================= */
.tabbar { display: none; }
.tabsheet { display: none; }

/* STOP THE RUBBER-BAND BOUNCE DRAGGING THE TAB BAR (v3.22.0).
 *
 * Diagnosed from a real report, not guessed at. An operator on an iPhone 16
 * Pro Max, in the INSTALLED app, opened Matches, scrolled up at the top of the
 * page and watched the bottom bar float away. The display diagnostics panel
 * caught the moment: scrollY = -131, so the document had been dragged 131px
 * past its own top and was mid-bounce.
 *
 * .tabbar is position:fixed, which pins it to the LAYOUT viewport - and during
 * an iOS overscroll the whole document, fixed elements included, is dragged
 * with the bounce. The bar visibly detaches and slides.
 *
 * SCOPED TO THE INSTALLED APP ON PURPOSE. On the root element this property
 * also suppresses Android Chrome's pull-to-refresh, which is the same
 * mechanism. Applied globally it would take a working gesture away from every
 * Android user in a browser tab - people who never had the iOS bounce problem
 * and gain nothing from the fix. Inside a standalone app the trade is worth
 * it; in someone's browser tab it is not.
 *
 * HONEST LIMIT: the reported gap was 449px and only 131 of that was the
 * bounce. The same sample showed the visual viewport 318px shorter than the
 * layout viewport - Safari chrome, not a keyboard, she was not typing. That
 * part is iOS positioning fixed elements against a viewport the user cannot
 * fully see, and CSS cannot reach it. Expect the problem to shrink, not
 * vanish. The diagnostics panel is still on her account to measure whether
 * it did. */
@media (display-mode: standalone) {
  body { overscroll-behavior-y: contain; }
}
/* The class is set in head.ejs from navigator.standalone OR the media query,
   because on iPhone the media query alone is not the dependable signal. Same
   rule, two routes in - whichever the device honours, the fix lands. */
html.is-standalone body { overscroll-behavior-y: contain; }

@media (max-width: 768px) {
  /* CONTRAST (v3.1.1). Players reported the ribbon was too light. Three
     separate causes, all fixed here, none of which was only the text colour:

       1. The bar had no visible EDGE. --color-border (#E8E3D6) is 1.28:1
          against white and the shadow was 4%, so on a white page the bar did
          not read as a surface at all - just words floating at the bottom.
          Now a warm tinted background plus a darker rule and a firmer shadow.

       2. The ACTIVE item was DIMMER than the inactive ones. --color-primary
          (#0F6E56) is 4.28:1 on white - below AA - while the muted grey was
          5.26:1. The one item meant to stand out was the faintest thing here.

       3. 10px labels. Even a passing ratio is hard at that size, and this
          app's players skew older than a typical mobile audience.

     DELIBERATELY NOT --color-primary. head.ejs sets that per league from
     franchise.primary_color, so the active tab's legibility would depend on a
     colour set elsewhere. Clint's decision: the tab bar always uses platform
     colours. --tabbar-active is the brand green darkened for this component
     only (6.3:1 vs the brand green's 4.3:1) - readability beats an exact
     shade in a 12px label.

     The active state is ALSO carried by a 3px top rule and a tint, not colour
     alone, so it survives any future palette change and reads for anyone with
     a colour vision deficiency. */
  .tabbar {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 900;
    background: var(--tabbar-bg);
    border-top: 1px solid var(--tabbar-border);
    /* Sit above the iPhone home indicator rather than under it. The top
       padding lives on the item, so its 3px active rule can sit flush against
       the bar's own border. */
    padding: 0 0 calc(8px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -3px 12px rgba(0, 0, 0, 0.09);
  }
  .tabbar__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 5px 2px 4px;
    background: none;
    /* Transparent rule on every item so the active one gaining a coloured
       rule does not shift the others down by 3px. */
    border: none;
    border-top: 3px solid transparent;
    border-radius: 0;
    cursor: pointer;
    text-decoration: none;
    color: var(--tabbar-inactive);
    /* 44px is the smallest comfortable tap target; 48 keeps that true now the
       icon and label are each a size larger. */
    min-height: 48px;
    box-sizing: border-box;
    justify-content: center;
    font-family: inherit;
  }
  .tabbar__item.is-active {
    color: var(--tabbar-active);
    border-top-color: var(--tabbar-active);
    background: var(--tabbar-active-wash);
  }
  .tabbar__item.is-active .tabbar__label { font-weight: 700; }
  .tabbar__icon { font-size: 21px; line-height: 1; }
  .tabbar__label { font-size: 12px; letter-spacing: 0.01em; }

  /* Keep the bar from covering the end of the page. Bar is now 1px rule +
     48px item + 8px padding = 57px, so 66px leaves the same slack as before. */
  body { padding-bottom: calc(66px + env(safe-area-inset-bottom, 0px)); }

  .tabsheet { display: block; position: fixed; inset: 0; z-index: 950; }
  .tabsheet[hidden] { display: none; }
  .tabsheet__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.4); }
  .tabsheet__panel {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    background: #fff;
    border-radius: 14px 14px 0 0;
    padding: 8px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.16);
  }
  .tabsheet__grip {
    width: 38px; height: 4px; border-radius: 2px;
    background: var(--color-border);
    margin: 4px auto 10px;
  }
  .tabsheet__link {
    display: block;
    padding: 14px 4px;
    font-size: 16px;
    color: var(--color-text);
    text-decoration: none;
    border-bottom: 1px solid var(--color-border);
  }
  .tabsheet__link--muted { color: var(--color-text-muted); }
  .tabsheet__close {
    display: block; width: 100%;
    margin-top: 12px; padding: 12px;
    background: none; border: 1px solid var(--color-border);
    border-radius: 8px; font-size: 15px; font-family: inherit;
    color: var(--color-text); cursor: pointer;
  }
  @media (prefers-reduced-motion: reduce) {
    .tabsheet__panel { transition: none; }
  }
}

/* Hand-off disclosure: the summary is a TOGGLE, so its label has to change
   when the panel opens - otherwise it reads "Hand off host" directly above a
   submit button saying the same thing, and it is not obvious which one acts. */
.handoff-toggle__open { display: none; }
details[open] > .handoff-toggle .handoff-toggle__closed { display: none; }
details[open] > .handoff-toggle .handoff-toggle__open { display: inline; }
/* Hide the native disclosure triangle, which looks wrong on a button. */
.handoff-toggle::-webkit-details-marker { display: none; }

/* Replace-me and hand-off-host are sibling blocks that each used to draw their
   own top border. With both visible that produced two rules a few pixels apart,
   and the bordered summary button sat directly on the lower one - Clint spotted
   it. One divider for the group: the first block gets no rule, each subsequent
   one does. */
.match-actions-block {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--color-border);
}
/* The SECOND and later blocks drop the rule entirely - otherwise there are
   still two lines, which is the bug. One divider opens the actions area; the
   blocks inside it are separated by space alone. */
.match-actions-block + .match-actions-block {
  border-top: none;
  padding-top: 0;
  margin-top: 18px;
}
/* Give a bordered control room to breathe below the rule, rather than having
   its outline touch it. */
.match-actions-block > details > summary {
  margin-top: 2px;
}

/* ---------------------------------------------------------------------
   PAYMENTS RECONCILIATION.

   Replaces an eight-column table. The three payment kinds are a breakdown
   of one number, and columns of small integers gave no hint of that - the
   headings read as one run-on phrase and the figure the page exists for
   (off-platform) looked exactly like the ones nobody needs.

   One row per league: identity left, a mix bar in the middle, the total
   right. The gap is the only coloured text on a clean row, so scanning
   the page means looking for amber.
   --------------------------------------------------------------------- */
.recon-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin: 18px 0 20px;
}
.recon-tile {
  background: var(--color-surface-alt);
  border-radius: 10px;
  padding: 14px 16px;
}
.recon-tile__label {
  font-size: 12.5px;
  color: var(--color-text-muted);
}
.recon-tile__value {
  font-size: 26px;
  font-family: var(--font-display);
  line-height: 1.25;
}
.recon-legend {
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: 12.5px;
  color: var(--color-text-muted);
  margin-bottom: 10px;
}
.recon-key {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  margin-right: 5px;
}
.recon-list {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  overflow: hidden;
}
.recon-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 14px 16px;
}
/* One divider per pair rather than a border on every row, so the last row
   does not draw a rule against the container's own edge. */
.recon-row + .recon-row {
  border-top: 1px solid var(--color-border);
}
.recon-row__id {
  flex: 1;
  min-width: 0;
}
.recon-row__name {
  font-weight: 600;
  font-size: 15px;
}
.recon-row__meta {
  font-size: 12.5px;
  color: var(--color-text-muted);
  margin-top: 2px;
}
.recon-row__mix {
  width: 200px;
  flex: none;
}
.recon-bar {
  display: flex;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--color-surface-alt);
}
.recon-bar > span {
  display: block;
  height: 100%;
}
.recon-bar__empty {
  width: 100%;
  background: var(--color-surface-alt);
}
.recon-bar__caption {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12px;
  color: var(--color-text-muted);
  margin-top: 5px;
}
.recon-gap {
  color: #B45309;
  font-weight: 600;
}
.recon-row__total {
  width: 54px;
  flex: none;
  text-align: right;
}
.recon-row__count {
  font-size: 19px;
  font-family: var(--font-display);
  line-height: 1.2;
}
.recon-row__unit {
  font-size: 11px;
  color: var(--color-text-muted);
}
/* Phones: the bar and total drop below the name rather than squeezing it. */
@media (max-width: 640px) {
  .recon-tiles {
    grid-template-columns: 1fr;
    gap: 8px;
  }
  .recon-row {
    flex-wrap: wrap;
    gap: 10px;
  }
  .recon-row__id {
    flex: 1 1 100%;
  }
  .recon-row__mix {
    flex: 1 1 auto;
    width: auto;
  }
}

/* ---------------------------------------------------------------------
   GROUP-TEXT CHIP on the admin matches list.

   Touch-only, exactly like .sms-group on the match page: an sms: link does
   nothing with a mouse, so showing it on a desktop browser would be a
   control that silently fails. This is also why it cannot be tested from a
   desktop browser at all.
   --------------------------------------------------------------------- */
.sms-chip {
  display: none;
}
@media (pointer: coarse) {
  .sms-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.8em;
    font-weight: 600;
    line-height: 1;
    padding: 6px 12px;
    border-radius: 14px;
    border: 1px solid var(--color-primary);
    color: var(--color-primary);
    background: var(--color-surface);
    text-decoration: none;
    white-space: nowrap;
  }
}
