:root {
  /* THE BOARD'S COLOURS, and every one of them is a DEFAULT here rather than a
     decision. views/partials/head.ejs writes the state folder's own values over
     the top, in both themes, so a board wearing another state's palette is not
     something a stylesheet can cause.

     These were --ky, --ky-deep and --ky-tint, which was accurate when there was
     one state and became a Kentucky literal in shared code the moment there
     were two. 225 usages across 50 files, renamed as tokens — no rule changed.

     --brand is the accent: buttons, chips, links, focus rings.
     --brand-deep is its pressed state, --brand-tint a wash for light surfaces.

     --header IS SEPARATE FROM --brand ON PURPOSE. Kentucky's masthead is the
     brand blue and the two were the same value, so one variable did both jobs.
     They are not the same job. Tennessee's logo sits on black and its accent is
     orange; painting the buttons black to fix the masthead would make the site
     look switched off. */
  --brand: #0033A0;
  --brand-deep: #002273;
  --brand-tint: #E9EFFA;
  --header: #0033A0;
  --bg: #f4f6f9;
  --card: #ffffff;
  --ink: #14181f;
  --ink-2: #5b6673;
  --ink-3: #8b95a3;
  --line: #e2e7ee;
  --live: #d8232a;
  --win: #0033A0;
  --accent: #0033A0;
  --radius: 12px;
  --wrap: 720px;

  /* PANEL WASHES, WITH BOTH THEIR THEMES.
     Every one of these started life as a pale hex typed into one page's <style>
     block, which is fine until the page is looked at in dark mode: the wash
     stays pale, --ink flips to near-white, and the words go. Named here so a
     panel gets the right ground in both themes without any page having to know
     which theme it is in. */
  --wash: #f6f8fa;      /* a neutral aside */
  --warn-bg: #fff8e1;   /* worth reading before you press the button */
  --bad-bg: #fdecea;    /* something is wrong */
  --alert-bg: #fff7f6;  /* something needs a person */
  --note-bg: #faf8ff;   /* an explanation, not a warning */
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0e1116;
    --card: #171b22;
    --ink: #e8ecf1;
    --ink-2: #9aa5b2;
    --ink-3: #6d7887;
    --line: #262c36;
    --win: #7FB0FF;
    --accent: #7FB0FF;

    /* THE TWO THAT WERE MISSING, AND WHY TEXT WENT INVISIBLE.
       A coach, unprompted:

         "The color of some of the wording makes it invisible on PC. I have to
          highlight boxes with a left-click and drag to get the words to show."

       --brand-tint is a WASH FOR LIGHT SURFACES and it had no dark value, so it
       stayed #E9EFFA — very nearly white — in dark mode. Fifty-seven places use
       it as a background: the drop boxes, every notice, the admin bar, the
       upload panels. In dark mode --ink flips to near-white, so all of them
       became pale text on a pale ground. Invisible until you drag-selected it,
       which is exactly what he had to do.

       --brand was the same story one step quieter. #0033A0 is a deep navy meant to
       sit on white; on a dark card it is a link you can barely find. Forty-two
       rules colour text with it.

       Both are now dark-mode values of their own. Every rule that used them
       keeps working — nothing had to be hunted down and changed — because the
       bug was never in those rules. It was two variables that only had half
       their definitions. */
    --brand: #6f9dff;
    --brand-deep: #4d7fe0;
    --brand-tint: #1a2333;
    --header: #0b1a3a;

    /* Same story, same fix. Each is dark enough for near-white text to sit on,
       and keeps the hue that told you what kind of panel it was. */
    --wash: #1a1f27;
    --warn-bg: #33290d;
    --bad-bg: #3a1512;
    --alert-bg: #331a17;
    --note-bg: #221d33;
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  overflow-x: hidden;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 14px; }

/* ---------------------------------------------------------- header */
.site-header {
  background: var(--header);
  color: #fff;
  position: sticky; top: 0; z-index: 50;
  box-shadow: 0 1px 0 rgba(0,0,0,.15);
}
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 56px;
  padding-top: 6px; padding-bottom: 6px;
  transition: padding .18s ease;
}
.site-header.slim .header-inner { padding-top: 0; padding-bottom: 0; }
.brand { display: flex; align-items: center; gap: 10px; color: #fff; min-width: 0; }
.brand:hover { text-decoration: none; }

/* The logo is a tall, near-square badge, so it needs real height to read — at
   header size it would be a smudge. The masthead therefore opens big and
   shrinks to a slim bar once you start scrolling, which keeps the board itself
   at the top of a phone screen where it belongs.

   The image travels inside this stylesheet as a data URL rather than as a file
   in /static: it caches with the CSS instead of costing a second request on a
   phone at a stadium, and it can never 404 if an image fails to reach a deploy.
   96-colour PNG, ~11 KB, drawn at 200px tall so it stays sharp on retina. */
.brand-logo {
  display: block; flex: none;
  width: 119px; height: 104px;
  background: url("/brand/logo.png") no-repeat center;
  background-size: contain;
  transition: width .18s ease, height .18s ease;
}
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
/* Set by the small script in header.ejs once the page has scrolled past the
   masthead. Everything degrades to the tall state if that script never runs. */
.site-header.slim .brand-logo { width: 48px; height: 42px; }
.nav { display: flex; align-items: center; gap: 4px; min-width: 0; }
.cta-short { display: none; }
.nav a {
  color: #cfe0f2; font-size: 14px; padding: 7px 10px; border-radius: 8px; white-space: nowrap;
}
.nav a:hover { background: rgba(255,255,255,.1); text-decoration: none; }
.nav a.on { color: #fff; background: rgba(255,255,255,.14); }
.nav a.cta { background: #fff; color: var(--brand); font-weight: 650; }
.nav a.cta:hover { background: #e8f0fa; }
@media (max-width: 620px) {
  .nav a { padding: 7px 6px; font-size: 12.5px; }
  .nav a.cta { padding: 7px 9px; }
}
@media (max-width: 540px) {
  .cta-long { display: none; }
  .cta-short { display: inline; }
}
@media (max-width: 430px) {
  /* "Coaches" is three characters wider than the "Send" it replaced, and on a
     390-point phone the row was already close. Two pixels of gap and one of
     padding buy back more than the word costs, and the alternative — a nav that
     wraps or scrolls sideways — is worse than a slightly tighter one. */
  .nav { gap: 2px; }
  .nav a { padding: 7px 5px; font-size: 12px; }
  .nav a.cta { padding: 7px 8px; }
  .brand-logo { width: 84px; height: 74px; }
  .site-header.slim .brand-logo { width: 42px; height: 37px; }
  .nav { gap: 0; }
  .nav a { padding: 7px 5px; font-size: 12px; }
}

/* ---------------------------------------------------------- datebar */
.datebar {
  display: flex; align-items: center; gap: 8px;
  padding: 18px 0 10px;
}
.datebar-mid { flex: 1; text-align: center; }
.datebar h1 {
  margin: 0; font-size: 20px; letter-spacing: -.35px; font-weight: 700;
}
.today-link { font-size: 12px; color: var(--ink-2); }
.datenav {
  width: 38px; height: 38px; display: grid; place-items: center;
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  font-size: 20px; color: var(--ink-2); flex: none;
}
.datenav:hover { text-decoration: none; border-color: var(--accent); color: var(--accent); }

/* ---------------------------------------------------------- controls */
.controls { margin-bottom: 10px; }
.chips {
  display: flex; gap: 6px; overflow-x: auto; padding-bottom: 6px;
  scrollbar-width: none;
}
.chips::-webkit-scrollbar { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--card); border: 1px solid var(--line); color: var(--ink-2);
  border-radius: 999px; padding: 7px 13px; font-size: 13.5px; font-weight: 550;
  cursor: pointer; white-space: nowrap; font-family: inherit;
}
.chip:hover { border-color: var(--ink-3); }
.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.chip-n { font-size: 11.5px; opacity: .7; font-variant-numeric: tabular-nums; }
.controls-row { display: flex; gap: 8px; margin-top: 6px; }
#team-pick, #class-filter {
  font: inherit; font-size: 14px; padding: 9px 12px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--ink);
}
#class-filter { flex: none; }

/* Finding your own school is the most common first action on the board, so the
   picker sits above the date and gets the full width. */
.teampick { margin: 0 0 10px; }
#team-pick {
  width: 100%;
  font-size: 15px;
  padding: 11px 12px;
  font-weight: 550;
}
#team-pick:focus-visible, #class-filter:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
/* Shown only when JS hasn't loaded; the script hides it and navigates on change. */
.teampick { display: flex; gap: 8px; }
.teampick-go {
  font: inherit; font-size: 14px; font-weight: 600; padding: 0 16px;
  border: 1px solid var(--brand); border-radius: 10px;
  background: var(--brand); color: #fff; cursor: pointer; flex: none;
}

.live-note {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--ink-2); padding: 4px 2px 10px;
}

/* ---------------------------------------------------------- games */
.games { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.game {
  position: relative;
  /* WRAPPING, SO A NOTE GETS ITS OWN LINE INSTEAD OF EATING THE ROW.
     A note on a board row is a SECOND LINE — "Resumes Monday Night" under the
     matchup — and .gnote asked for one with flex-basis:100%. On a nowrap row
     that basis is not a line break, it is a claim on the SAME line: the note
     took the width, .teams (min-width:0) shrank, and .tname ellipsised the two
     school names down to nothing. Both schools and both scores vanished off a
     row that was otherwise perfectly correct.
     It looked fine for weeks because no board row had a note on it yet. */
  display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 9px 10px 9px 32px;
}
/* The matchup, the time and the chips stay on the first line and take what they
   need; only the note is allowed to wrap. Without this a narrow phone can drop
   the Fix chip onto a line of its own, which reads as a second row. */
.game > a.state, .game > .rowfix { flex-shrink: 0; }
.game.is-live { border-color: rgba(216,35,42,.45); }
.game.pinned { box-shadow: inset 3px 0 0 var(--accent); }

.star {
  position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; cursor: pointer; padding: 6px;
  font-size: 16px; line-height: 1; color: var(--line);
}
.star:hover { color: var(--ink-3); }
.star.on { color: #f0a500; }

.teams { flex: 1; min-width: 0; display: grid; gap: 3px; }
.row { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.tname {
  color: var(--ink); font-size: 15.5px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* Without these the long names (Christian Academy-Louisville) set the row's
     min-content width and push the whole card past the screen edge instead of
     ellipsising. Flex children don't shrink below min-content by default. */
  flex: 0 1 auto; min-width: 0;
}
.row.win .tname { font-weight: 700; color: var(--win); }
.tclass {
  font-size: 10.5px; color: var(--ink-3); border: 1px solid var(--line);
  border-radius: 4px; padding: 0 4px; flex: none; font-weight: 600;
}
/* Class chip and record travel together so the record never wraps away from
   the team it belongs to, and so an empty record collapses to nothing at all
   rather than leaving a gap the eye reads as a missing number. */
.tmeta { display: flex; align-items: baseline; gap: 6px; flex: none; }
.trec {
  font-size: 11.5px; color: var(--ink-3); font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.trec:empty { display: none; }
.row.win .trec { color: var(--ink-2); }
/* On a narrow phone the score and the team name are what matter. The class
   chip goes first because the record is the thing being added; losing both
   would make the feature invisible on the device most people read this on. */
@media (max-width: 380px) {
  .tmeta .tclass { display: none; }
}
.tscore {
  margin-left: auto; font-size: 19px; font-weight: 700; color: var(--ink-2);
  font-variant-numeric: tabular-nums; flex: none; min-width: 30px; text-align: right;
}
.row.win .tscore { color: var(--ink); }

.state {
  flex: none; width: 78px; display: flex; flex-direction: column;
  align-items: flex-end; justify-content: center; gap: 3px; text-align: right;
}
.pill {
  font-size: 11px; font-weight: 750; letter-spacing: .4px;
  border-radius: 5px; padding: 3px 7px; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 5px;
}
.pill.live { background: var(--live); color: #fff; }
.pill.final { background: var(--line); color: var(--ink-2); }
.pill.sched { background: transparent; color: var(--ink-2); border: 1px solid var(--line); font-weight: 600; }
.pill.off { background: #8b95a3; color: #fff; }
.state .sub { font-size: 11px; color: var(--ink-3); }
.state .ago { font-size: 10.5px; color: var(--ink-3); }

/* The admin's "Fix" chip on a board row.

   "i want to go to the board, find the way to change this easily"

   Only rendered for a logged-in admin, so a visitor never sees it. Deliberately
   quiet — the board's job is reading a score, and this sits to the side of the
   status rather than competing with it. */
/* A NOTE SOMEBODY TYPED ABOUT ONE GAME.
   Quiet on purpose. It sits under the matchup and must never compete with the
   score, which is the one thing the row exists to show. */
/* The note box on a coach's schedule row. NOT .cs-note — that name was already
   taken, in coach-schedule.ejs's own <style>, by the paragraphs of explanation
   all over that page. Giving an input the paragraphs' class put flex and a
   min-width on nine blocks of prose that wanted neither. Two things held the
   same name and meant different things by it. */
.cs-notebox { flex: 1 1 260px; min-width: 200px; }

.gnote {
  /* 0 0 100%, not 1 1 100%: it must OCCUPY a whole line rather than merely be
     willing to grow to one. The old basis let it share the first line with the
     matchup whenever the row had any slack. */
  flex: 0 0 100%;
  margin-top: 4px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted, #5c6675);
}

.rowfix {
  flex: none; align-self: center; margin-left: 8px;
  padding: 3px 9px; border: 1px solid var(--line); border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: .3px;
  color: var(--ink-3); text-decoration: none; white-space: nowrap;
}
.rowfix:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-tint); }
/* THE NOTE BOX ON A BOARD ROW. Shut by default and the same size as the Fix
   chip beside it, because the board is for reading: an input box open on forty
   rows is a form, not a scoreboard. Open, it takes the full width of the row so
   a sentence fits. */
/* ITS OWN LINE, ALWAYS, AND NOT BESIDE THE MATCHUP.
   Inline it cost about 64px, and .teams is the flexible child (flex:1,
   min-width:0) — so the chip took its width and .tname ellipsised both school
   names. Measured on a 390px phone: without the chip most names fit; with it
   inline, nearly every name in the state truncated. Fix stays where it has
   always been, because Fix alone leaves the names room. */
.rownote { flex: 0 0 100%; order: 9; margin: 5px 0 0; }
.game > .gnote { order: 8; }
.rownote > summary {
  display: inline-block; list-style: none; cursor: pointer;
  font-size: 11.5px; padding: 3px 8px; border: 1px solid var(--line);
  border-radius: 999px; color: var(--ink-3); background: var(--bg);
}
.rownote > summary::-webkit-details-marker { display: none; }
.rownote > summary:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-tint); }
.rownote[open] { width: 100%; }
.rownote form { display: flex; gap: 6px; align-items: center; margin: 6px 0 0; }
.rownote input[type=text] {
  flex: 1 1 auto; min-width: 120px; margin: 0; padding: 7px 9px;
  /* 16px or iOS zooms the whole board in when the box is tapped, which on a
     sideline is somebody losing their place in the list. */
  font-size: 16px; border: 1px solid var(--line); border-radius: var(--radius);
}
@media (max-width: 380px) { .rowfix { margin-left: 5px; padding: 3px 7px; } }

.dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--live);
  display: inline-block; flex: none; animation: pulse 1.6s infinite;
}
.pill.live .dot { background: #fff; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .dot { animation: none; } }

.no-match { text-align: center; color: var(--ink-2); padding: 30px 0; }

/* ---------------------------------------------------------- empty / cta */
.empty, .cta-band {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 28px 22px; text-align: center;
  margin: 16px 0;
}
.empty h2, .cta-band h2 { margin: 0 0 8px; font-size: 19px; letter-spacing: -.3px; }
.empty p, .cta-band p { margin: 0 auto 16px; color: var(--ink-2); max-width: 44ch; font-size: 14.5px; }
/* The ask on an empty team page. It is the whole point of the page, so it gets
   to look like a note rather than more body copy. */
.empty-ask {
  background: var(--bg); border-left: 3px solid var(--brand);
  border-radius: 8px; padding: 12px 14px; text-align: left;
}
.empty-ask strong { color: var(--ink); display: block; margin-bottom: 4px; }
.empty .btn + .btn { margin-left: 8px; }
.empty-note { font-size: 13px !important; opacity: .78; margin-bottom: 0 !important; }
@media (max-width: 420px) {
  .empty .btn { display: block; margin: 0 auto 8px !important; }
}
.btn {
  display: inline-block; background: var(--brand); color: #fff;
  border-radius: 10px; padding: 10px 18px; font-weight: 650; font-size: 14.5px;
  border: 0; cursor: pointer; font-family: inherit;
}
.btn:hover { background: var(--brand-deep); text-decoration: none; }
.btn.big { padding: 13px 26px; font-size: 16px; }
.btn.ghost { background: transparent; border: 1px solid var(--line); color: var(--ink); }

/* ---------------------------------------------------------- ads
   An ad on a phone must never be the reason somebody can't see a score.
   Every slot is given an EXACT size here rather than letting Google choose,
   because "auto" on a 640px-tall screen picks a 600px-tall ad. The <ins> is
   sized, so the ad is delivered at that size — nothing is clipped, hidden or
   covered, which keeps it within AdSense's rules as well as ours.

   Phone budget: a 50px banner up top is about 8% of the screen. The old
   setup could take 90%. */
.ad { margin: 12px 0; text-align: center; }
.ad-label {
  display: block; font-size: 9.5px; letter-spacing: .8px; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 4px;
}
.ad .adsbygoogle { display: block; margin: 0 auto; }
.feed-ad { list-style: none; }

/* Mobile first: small banners only. The footer unit on team and game
   pages shares the in-feed shape. */
.ad-leaderboard .adsbygoogle { width: 320px; height: 50px; }
.ad-infeed      .adsbygoogle,
.ad-footer      .adsbygoogle { width: 300px; height: 100px; }

/* The sidebar unit is a desktop shape and has no business on a phone. */
.ad-sidebar { display: none; }

/* ============================================== THE RAILS BESIDE THE SCORES

   "the ads should be on the right and left of these scores, not in the middle
    of the page."

   On a 1900px screen the board is a 720px column with roughly six hundred
   pixels of nothing down each side. The feed is the one place a sold unit
   interrupts what people came for; the gutters are the one place it cannot.

   ANCHORED TO THE COLUMN, NOT THE WINDOW. `left: calc(50% - half the column -
   the gap - the rail)` keeps each rail glued to the edge of the scores at every
   width, so they read as part of the board instead of drifting off to the
   corners of a big monitor where nobody is looking.

   FIXED, SO NOTHING SOLD COSTS NOTHING. Out of the flow entirely: no reserved
   column, no hole when a rail is empty, and the scores stay exactly where they
   are today. The markup is not even rendered when nothing is sold.

   THE BREAKPOINT IS ARITHMETIC. Two 300px rails and two 24px gaps either side
   of a 720px column need 1368px before anything is crowded, so 1400px is the
   floor and below it they are not drawn. A laptop keeps the board it has. */
.rails { display: none; }
@media (min-width: 1400px) {
  /* THE RAIL GROWS WITH THE GUTTER IT SITS IN.
     A fixed 300px was a third of the empty space on a 1920 screen and looked
     apologetic beside six hundred pixels of nothing. The width is now the
     gutter itself, less the gap and a margin, held between 300 (what fits at
     the 1400 floor) and 420 (past which a logo unit is just a big empty card).
     One variable, because the left rail's `left` has to know the same number. */
  .rails {
    display: block;
    /* WIDER, AND THE 48px IS LOAD-BEARING.
       The margin absorbs the scrollbar. `100vw` INCLUDES it; the `left`
       percentage below resolves against the viewport WITHOUT it, so the two
       disagree by however wide the scrollbar is and the left rail is the one
       that goes off the edge. At the 1400px floor the sum leaves about 8px of
       air — trim this number and the left rail silently stops being reachable
       on exactly the narrowest screens that draw it. */
    --railw: clamp(300px, calc((100vw - var(--wrap)) / 2 - 48px), 470px);
    --railgap: 24px;
  }
  .rail {
    position: fixed;
    top: 128px;
    width: var(--railw);
    z-index: 5;
    /* Never taller than the screen it is pinned to, and it scrolls itself if a
       sponsor sends a tall logo. A fixed element that runs off the bottom of
       the viewport cannot be scrolled to and its lower half is unreachable. */
    max-height: calc(100vh - 160px);
    overflow-y: auto;
    text-align: center;
  }
  .rail-l { left: calc(50% - (var(--wrap) / 2) - var(--railgap) - var(--railw)); }
  .rail-r { left: calc(50% + (var(--wrap) / 2) + var(--railgap)); }
  /* CLEAR THE PRESENTING BAND WHEN THERE IS ONE.
     128px was measured against a masthead with a thin line under it. The band
     is now 98px tall and the rails started level with it, so the word SPONSOR
     sat on the partner's own tint and the two placements ran into each other.
     `:has()` rather than a body class because the band's presence is a fact
     about the page the stylesheet can already see; a browser too old to support
     it falls back to 128px, which is exactly today's behaviour and not a
     broken one. */
  body:has(.presented-by) .rail { top: 232px; max-height: calc(100vh - 264px); }
  /* BIGGER THAN THE IN-FEED CARD, because a rail has the room and the logo is
     the thing being paid for. A sponsor who looks at his own placement should
     see a sign, not a footnote.
     AND IT IS THE HEIGHT THAT DOES THAT, NOT THE WIDTH. Widening the rail to
     420px made the card broader and no taller, so it still read as a strip
     laid on its side. `min-height` on the plate is what gives it presence: the
     unit holds its size whether the sponsor sends a tall badge or a flat
     wordmark, so a rail is a consistent sign rather than a box that shrinks
     around whatever artwork arrived. */
  /* "the ads on the side for the computer need to be more prominent"
     The gutter is six hundred pixels of nothing and the limit on how big this
     can be is the gutter, not the scoreboard — a rail is `position: fixed` and
     outside the flow, so every pixel it gains costs the board exactly none.
     That is the whole argument for rails over a unit in the feed, and it had
     not been spent. The plate is now 280 tall with a 240px mark: a proper
     advert rather than a logo in a box.
     The 2px border and the shadow are deliberate. Without them a big pale card
     beside a pale page reads as part of the furniture; a sponsor should be
     able to point at his placement and have somebody else see it too. */
  .rail-card { padding: 30px 22px 28px; gap: 16px; border-width: 2px;
               box-shadow: 0 2px 14px rgba(16,32,72,.09); }
  /* 190, NOT 280. The plate is a floor that a tall logo grows past, not a box
     every logo is poured into. A business whose mark is a flat wordmark — which
     is most of them, and is what a dealership will send — fills the width at
     whatever height its own proportions give, and a 280px floor left that
     sitting in a sea of white that read as a mistake rather than as an advert.
     A stacked logo still makes its own 250-odd on the way to the 240 ceiling. */
  .rail-card .sponsor-logo { padding: 24px; width: 100%; min-height: 190px; }
  .rail-card .sponsor-logo img { max-height: 240px; }
  .rail-card .sponsor-name { font-size: 24px; }
  .rail-card.has-logo .sponsor-name { font-size: 17px; }
  .rail-card .sponsor-line { font-size: 15.5px; }
  .rail .ad-label { font-size: 11px; letter-spacing: 1.4px; margin-bottom: 9px; }

}
/* Past this the gutter is wide enough that the rails can stand clear of the
   scores rather than hugging them. */
@media (min-width: 1700px) { .rails { --railgap: 32px; } }

@media (min-width: 700px) {
  .ad-leaderboard .adsbygoogle { width: 728px; height: 90px; }
  .ad-infeed      .adsbygoogle,
  .ad-footer      .adsbygoogle { width: 728px; height: 90px; }
  .ad-sidebar { display: block; }
  .ad-sidebar .adsbygoogle { width: 300px; height: 250px; }
}

/* Belt and braces. If a unit ever comes back bigger than it was asked for —
   a mis-set slot in the AdSense console, say — this stops it eating the page
   before anybody tells us.

   :not(.ad-sold) IS THE WHOLE POINT OF THIS SELECTOR, NOT A REFINEMENT.

   This clamp was written to defend the board against Google. Unscoped, it was
   also cutting the bottom off the sponsors we sold ourselves: a card with a
   logo runs about 160px, so the business name and their one line got clipped
   away and only the logo showed. Nothing errored, the page returned 200, and
   the element was in the DOM with a real bounding box — it was simply painted
   inside a box with overflow:hidden. I spent a round assuming my screenshot
   had cropped badly.

   A guard against a stranger's markup must not fire on our own. The clamp
   still applies to every network unit; a card a customer paid for is measured
   by its own contents. */
.ad:not(.ad-sold) { max-height: 120px; overflow: hidden; }
@media (min-width: 700px) { .ad:not(.ad-sold) { max-height: 300px; } }

/* ---------------------------------------------------------- team page */
.team-head {
  background: var(--brand); color: #fff; border-radius: var(--radius);
  padding: 20px; margin: 18px 0 14px;
}
.team-head h1 { margin: 0 0 6px; font-size: 24px; letter-spacing: -.5px; }
.team-meta { color: #a9c4e0; font-size: 14px; }
.record { display: flex; gap: 20px; margin-top: 14px; }
.record div { line-height: 1.2; }
.record b { display: block; font-size: 22px; font-variant-numeric: tabular-nums; }
.record span { font-size: 11px; color: #a9c4e0; text-transform: uppercase; letter-spacing: .6px; }
/* Sits in the coloured hero, so it borrows the hero's muted text rather than
   the page's. Quiet on purpose — it explains a number, it isn't a warning. */
.rec-gap { margin: 10px 0 0; font-size: 12.5px; color: #a9c4e0; line-height: 1.55; }
.rec-gap a { color: #fff; text-decoration: underline; text-underline-offset: 2px; }
/* A record we can only put a floor under. Muted rather than red: it is an
   incomplete number, not an error, and the table is full of them early in a
   season. `cursor: help` is the affordance that there is a title to read. */
.partial { color: var(--ink-3); cursor: help; }

.sched-table { width: 100%; border-collapse: collapse; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.sched-table td, .sched-table th { padding: 11px 12px; border-bottom: 1px solid var(--line); font-size: 14.5px; text-align: left; }
.sched-table th { font-size: 11px; text-transform: uppercase; letter-spacing: .6px; color: var(--ink-3); }
.sched-table tr:last-child td { border-bottom: 0; }
.res { font-weight: 750; font-variant-numeric: tabular-nums; }
.res.w { color: #1a7f37; }
.res.l { color: #b3261e; }
/* A game that is off. Not red — nothing has gone wrong and nobody lost; it is
   simply not happening, so it reads as quieter than the games around it rather
   than louder. Small caps for the same reason a result is bold: this column is
   scanned, not read. */
.res.ppd { color: var(--ink-3); font-weight: 650; font-size: 12.5px;
           letter-spacing: .04em; text-decoration: none; }

/* Reporting a score from the schedule.
   Somebody looking at a team page has already found the game — making them go
   back to the board and hunt for it is the difference between a score arriving
   and a score not arriving. So the Result column doubles as the way in.
   44px tall because it is tapped with a thumb, often in the dark. */
.add-score {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 34px; padding: 6px 11px;
  border: 1px solid var(--brand); border-radius: 999px;
  color: var(--brand); background: var(--brand-tint);
  font-size: 13px; font-weight: 700; white-space: nowrap;
  text-decoration: none;
}
.add-score:hover { background: var(--brand); color: #fff; text-decoration: none; }
.add-score .plus { font-size: 15px; line-height: 1; font-weight: 800; }
/* A game still to come. Reportable, because a date can be wrong or a game can
   be moved up — but quiet, so it doesn't read as a job that needs doing. */
.add-later {
  color: var(--ink-3); text-decoration: none; font-variant-numeric: tabular-nums;
  border-bottom: 1px dotted var(--line);
}
.add-later:hover { color: var(--brand); border-bottom-color: var(--brand); text-decoration: none; }
.sched-table td:last-child { text-align: right; }
.sched-hint {
  margin: 0 0 9px; font-size: 13px; color: var(--ink-2);
}

/* ---------------------------------------------------------- teams index */
.class-block { margin: 22px 0; }
.class-block > h2 { font-size: 17px; margin: 0 0 10px; letter-spacing: -.2px; }
.district { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px; margin-bottom: 8px; }
.district h3 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase;
  letter-spacing: .7px; color: var(--ink-3); }
.district ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 14px; }
.district a { font-size: 14.5px; }

/* ---------------------------------------------------------- forms */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px; margin: 16px 0;
}
.card h1 { margin: 0 0 6px; font-size: 22px; letter-spacing: -.4px; }
.lede { color: var(--ink-2); margin: 0 0 18px; font-size: 15px; }
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; font-weight: 650; margin-bottom: 5px; }
.field .hint { font-size: 12px; color: var(--ink-3); font-weight: 400; }
.field input, .field select, .field textarea {
  width: 100%; font: inherit; font-size: 16px; padding: 11px 12px;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--bg); color: var(--ink);
}
.field input:focus, .field select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.score-pair { display: grid; grid-template-columns: 1fr 64px; gap: 10px; align-items: end; }
.notice { padding: 13px 15px; border-radius: 10px; margin-bottom: 16px; font-size: 14.5px; }
.notice.ok { background: #e7f6ec; color: #12572c; }
.notice.bad { background: #fdecea; color: #8b1a13; }
/* A THIRD STATE: IT WORKED, AND IT IS NOT FINISHED.
   A sheet that has been read and not yet saved was wearing the green of a
   finished job, and three of Lexington Christian's four box scores were lost to
   exactly that. Amber says "still your turn" in a way a sentence cannot. */
.notice.warn { background: #fdf3e0; color: #7a4a05; }
.notsaved { background: #fdf3e0; color: #7a4a05; border-left: 4px solid #c98a12;
  padding: 11px 14px; border-radius: 0 8px 8px 0; margin: -6px 0 16px; font-size: 14.5px; }
@media (prefers-color-scheme: dark) {
  .notice.ok { background: #10301c; color: #7fd6a0; }
  .notice.bad { background: #3a1512; color: #f3a49c; }
  .notice.warn { background: #3a2c10; color: #f0c674; }
  .notsaved { background: #3a2c10; color: #f0c674; border-left-color: #b98a2a; }
}

/* ---------------------------------------------------------- admin */
.admin-grid { display: grid; gap: 10px; }
.sub-card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
.sub-card .who { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
/* Which sport, and which night. A score card with neither is a number you
   cannot check: 12-0 is an ordinary football result, an unusual soccer one and
   an impossible basketball one. */
.sub-card .sub-sport {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .6px;
  color: var(--ink-3); font-weight: 700; margin-bottom: 3px;
}
.sub-actions { display: flex; gap: 8px; margin-top: 10px; }
/* Why a card refused to publish, on the card. Red rather than grey: this is the
   one thing in the queue that needs a decision rather than a press. */
.sub-why {
  margin-top: 9px; padding: 7px 10px; border-radius: 7px;
  background: #fdecea; color: #a5281b; font-size: 12.5px; line-height: 1.55;
}
/* Several people said the same thing. Green rather than red, because this is
   the one thing on a card that makes a decision EASIER — four strangers typing
   the same score is better evidence than any single report. */
.sub-agree {
  margin-top: 9px; padding: 7px 10px; border-radius: 7px;
  background: #e7f6ec; color: #1c6b39; font-size: 12.5px; line-height: 1.5;
}
.sub-agree b { font-variant-numeric: tabular-nums; }
/* Publishing this would move the game backwards. Amber, not red: red on this
   page means "cannot publish", and this one can — it just should not be pressed
   without reading the sentence first. */
.sub-back {
  margin-top: 9px; padding: 7px 10px; border-radius: 7px;
  background: #fdf3d8; color: #7a5300; font-size: 12.5px; line-height: 1.55;
  font-weight: 600;
}
@media (prefers-color-scheme: dark) {
  .sub-agree { background: #12331f; color: #8fd9a8; }
  .sub-back { background: #382c07; color: #f0cf7a; }
}
/* The card the page just jumped to, so a run through sixteen of them has some
   sense of place. :target only matches the one named in the address bar. */
.sub-card:target { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-tint); }
/* Add the missing fixture and publish, in one press. The date sits inside the
   form rather than behind a link, because it is the one thing the site does not
   know and will not invent. */
.sub-add {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin-top: 10px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--brand-tint);
}
.sub-add label { font-size: 12.5px; color: var(--ink-2); font-weight: 600; }
.sub-add input[type="date"] {
  width: auto; padding: 5px 8px; font-size: 13px; min-height: 32px; margin: 0;
}
.sub-add .btn { padding: 6px 12px; font-size: 13px; min-height: 32px; }
/* Two schools met twice and the report does not say which night. One button per
   fixture, stacked rather than in a row: the label carries a date, both school
   names and the score already on it, and side by side those truncate into two
   things that look identical — which is the exact mistake the choice exists to
   prevent. */
.sub-pick {
  margin-top: 10px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--brand-tint);
}
.sub-pick-h { font-size: 12.5px; color: var(--ink-2); font-weight: 700; margin-bottom: 7px; }
.sub-pick form { margin: 0 0 6px; }
.sub-pick form:last-child { margin-bottom: 0; }
.sub-pick .btn {
  width: 100%; justify-content: flex-start; text-align: left;
  padding: 7px 11px; font-size: 13px; min-height: 34px; font-weight: 600;
}
.stat-row { display: flex; gap: 18px; margin: 14px 0; }
.stat-row div b { display: block; font-size: 22px; font-variant-numeric: tabular-nums; }
.stat-row div span { font-size: 11px; text-transform: uppercase; letter-spacing: .6px; color: var(--ink-3); }
.code-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.code-table td, .code-table th { padding: 7px 9px; border-bottom: 1px solid var(--line); text-align: left; }
.code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; letter-spacing: 1px; }
.inline-form { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.inline-form input, .inline-form select {
  font: inherit; font-size: 14px; padding: 6px 8px; width: 62px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--bg); color: var(--ink);
}
.inline-form select { width: auto; }

/* ---------------------------------------------------------- footer */
.site-footer { margin-top: 40px; padding: 26px 0 34px; border-top: 1px solid var(--line); }
.sponsor-strip { text-align: center; margin-bottom: 18px; }
.sponsor-label { display: block; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .8px; color: var(--ink-3); margin-bottom: 8px; }
.sponsor-logos { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.sponsor { background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 7px 14px; font-size: 13.5px; color: var(--ink); font-weight: 550; }
.foot-links { text-align: center; font-size: 13.5px; color: var(--ink-2); margin: 0 0 8px; }
.fine { text-align: center; font-size: 12px; color: var(--ink-3); max-width: 54ch;
  margin: 0 auto; line-height: 1.5; }

/* Admin score rows: keep one line on desktop, stack cleanly on a phone. */
.inline-form { flex-wrap: nowrap; }
.inline-form input { width: 54px; }
@media (max-width: 640px) {
  .code-table td, .code-table th { padding: 6px 4px; font-size: 13px; }
  .code-table tr { display: grid; }
  .inline-form { flex-wrap: wrap; }
  .card { padding: 15px; }
  .team-head { padding: 16px; }
  .record b { font-size: 19px; }
}

/* Uppercase the typed code without shouting in the placeholder. */
.field input.upper { text-transform: uppercase; }
.field input.upper::placeholder { text-transform: none; }

/* ---------------------------------------------------------- game page */
.crumb { font-size: 13px; margin: 16px 0 0; }
.state { text-decoration: none; }
.state:hover { text-decoration: none; }
.game:hover { border-color: var(--ink-3); }

.matchup {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px; margin: 12px 0 14px; display: grid; gap: 10px;
}
.matchup .side { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 12px; }
.mname { font-size: 18px; font-weight: 600; color: var(--ink); }
.side.win .mname { font-weight: 800; color: var(--win); }
.mmeta { grid-column: 1; font-size: 12px; color: var(--ink-3); }
.mscore {
  grid-row: 1 / span 2; grid-column: 2; font-size: 34px; font-weight: 800;
  color: var(--ink-2); font-variant-numeric: tabular-nums; line-height: 1;
}
.side.win .mscore { color: var(--ink); }
.mstate {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  padding: 8px 0; margin: 2px 0;
}
.mstate .sub { font-size: 12px; color: var(--ink-3); }

.sec {
  font-size: 13px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-3); margin: 22px 0 8px; font-weight: 700;
}
.sec .hint { text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 12px; }

.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.line-table {
  width: 100%; border-collapse: collapse; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  font-variant-numeric: tabular-nums;
}
.line-table th, .line-table td {
  padding: 9px 10px; border-bottom: 1px solid var(--line);
  text-align: center; font-size: 14px; white-space: nowrap;
}
.line-table th {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); font-weight: 700;
}
.line-table tr:last-child td { border-bottom: 0; }
.line-table .lname { text-align: left; font-weight: 600; width: 100%; white-space: normal; }
.line-table .tot { font-weight: 800; color: var(--ink); }
.stat-table .lname { font-weight: 500; }
.stat-table .lname .tclass { margin-left: 5px; font-weight: 600; }

.stand-table .lname {
  display: flex; align-items: center; gap: 8px;
  /* Let the table scroll sideways rather than stacking school names two-high. */
  white-space: nowrap;
}
.stand-table .rank {
  display: inline-grid; place-items: center; width: 20px; height: 20px; flex: none;
  border-radius: 5px; background: var(--bg); border: 1px solid var(--line);
  font-size: 11px; font-weight: 700; color: var(--ink-3);
}
.stand-table tbody tr:first-child .rank { background: var(--brand); border-color: var(--brand); color: #fff; }

/* ---------------------------------------------------------- box entry */
.line-table.entry td { padding: 6px; }
.line-table.entry input {
  width: 52px; text-align: center; font: inherit; font-size: 16px; padding: 7px 4px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.side-head {
  font-size: 15px; margin: 18px 0 8px; font-weight: 700; letter-spacing: -.2px;
}
.statline {
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 11px; margin-bottom: 7px;
}
.statline-cat {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-3); font-weight: 700; margin-bottom: 5px;
}
.statline > input[type="text"] {
  width: 100%; font: inherit; font-size: 16px; padding: 8px 10px; margin-bottom: 7px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--ink);
}
.statline-nums { display: flex; gap: 6px; flex-wrap: wrap; }
.statline-nums label {
  display: flex; align-items: center; gap: 4px; font-size: 11px;
  color: var(--ink-3); font-weight: 600;
}
.statline-nums input {
  width: 54px; font: inherit; font-size: 16px; padding: 6px 4px; text-align: center;
  border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------- legal pages */
.legal { max-width: 680px; padding-bottom: 10px; }
.legal h1 { font-size: 27px; letter-spacing: -.6px; margin: 26px 0 4px; }
.legal .legal-date {
  font-size: 12px; color: var(--ink-3); margin: 0 0 20px;
  text-transform: uppercase; letter-spacing: .06em;
}
.legal .legal-lede {
  font-size: 16.5px; color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-left: 3px solid var(--brand);
  border-radius: 10px; padding: 14px 16px; margin: 0 0 26px; line-height: 1.55;
}
.legal h2 {
  font-size: 19px; letter-spacing: -.3px; margin: 30px 0 8px;
  padding-top: 18px; border-top: 1px solid var(--line);
}
.legal h3 { font-size: 15.5px; margin: 20px 0 6px; }
.legal p, .legal li { font-size: 15.5px; line-height: 1.6; color: var(--ink-2); }
.legal p { margin: 0 0 12px; }
.legal ul { margin: 0 0 14px; padding-left: 20px; }
.legal li { margin-bottom: 6px; }
.legal strong { color: var(--ink); }
.legal code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13.5px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px;
}
.legal .legal-callout {
  background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid var(--live); border-radius: 10px;
  padding: 12px 15px; color: var(--ink); font-weight: 550;
}
.legal .legal-foot {
  margin-top: 30px; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 14px;
}
.legal-links { font-size: 12.5px; color: var(--ink-3); margin-top: 6px; }

/* ---------------------------------------------------------- group pages */
.meta-link { color: #cfe0f2; text-decoration: underline; text-underline-offset: 2px; }
.meta-link:hover { color: #fff; }
.group-switch { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 13px; }
.group-switch a {
  background: rgba(255,255,255,.12); color: #fff; border: 1px solid rgba(255,255,255,.2);
  border-radius: 7px; padding: 5px 11px; font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.group-switch a:hover { background: rgba(255,255,255,.22); text-decoration: none; }
.group-switch a.on { background: #fff; color: var(--brand); border-color: #fff; }
.district h3 a { color: var(--ink-3); }
.district h3 a:hover { color: var(--accent); }

/* ---------------------------------------------------------- team identity */
/* Colour bars and chips carry the school's real colour. Text never does —
   half of these schools are gold or columbia blue, which is unreadable on
   white, and a scoreboard's whole job is being scannable. */
.tbar {
  width: 3px; align-self: stretch; min-height: 15px; border-radius: 2px;
  flex: none; margin-right: 1px;
}
.tchip {
  width: 9px; height: 9px; border-radius: 3px; flex: none;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}

.team-id { display: flex; align-items: center; gap: 13px; }
.team-mark {
  width: 64px; height: 64px; flex: none; border-radius: 12px;
  background: rgba(255,255,255,.14); padding: 4px; object-fit: contain;
}
.team-mascot {
  margin: 3px 0 0; font-size: 13px; color: rgba(255,255,255,.78);
  text-transform: uppercase; letter-spacing: .08em; font-weight: 600;
}
/* A school-coloured header gets a stripe of its secondary colour. */
.team-head[style*="background"] { position: relative; overflow: hidden; }
.team-head[style*="background"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 5px;
  background: var(--team2, #fff); opacity: .85;
}

.side-mark {
  width: 34px; height: 34px; flex: none; border-radius: 8px;
  object-fit: contain; margin-right: 2px;
}
.matchup .side { grid-template-columns: auto 1fr auto; }
.matchup .side .mname { grid-column: 2; }
.matchup .side .mmeta { grid-column: 2; }
.matchup .side .side-mark { grid-row: 1 / span 2; grid-column: 1; }
.matchup .side .mscore { grid-column: 3; }

@media (max-width: 480px) {
  .team-mark { width: 52px; height: 52px; }
}

/* A school colour used as text. The two custom properties are set inline per
   element by teamInk(); each has already been contrast-corrected for its
   theme, so neither can fall below 4.5:1. */
.inked { color: var(--team-ink); }
.row.win .inked, .side.win .inked { color: var(--team-ink); }
@media (prefers-color-scheme: dark) {
  .inked, .row.win .inked, .side.win .inked { color: var(--team-ink-dark); }
}


/* ------------------------------------------------- report-a-score fast path */
/* The button under the score on a game page. Full width on a phone because it
   is the only action on the page and a coach is using one thumb. */
.report-now { margin: 14px 0 4px; text-align: center; }
.report-now .btn { display: block; width: 100%; }
.report-now .hint { display: block; margin-top: 6px; font-size: 12px; color: var(--ink-3); }
@media (min-width: 560px) {
  .report-now .btn { display: inline-block; width: auto; min-width: 260px; }
}

/* The matchup, shown as a fact rather than two dropdowns, when the form was
   reached from a game page. */
.known-game {
  background: var(--brand-tint); border: 1px solid #cddaf2;
  border-radius: var(--radius); padding: 11px 13px; margin-bottom: 18px;
  display: flex; flex-direction: column; gap: 2px;
}
.known-game strong { font-size: 16px; }
.known-game span { font-size: 12.5px; color: var(--ink-2); }
.known-game a { font-size: 12.5px; margin-top: 3px; }

/* Optional fields, folded away. Three more boxes at half time is three more
   reasons to give up. */
.about-you { margin: 4px 0 16px; }
.about-you > summary {
  cursor: pointer; font-size: 13.5px; color: var(--brand);
  font-weight: 600; padding: 4px 0;
}

.remember {
  display: flex; align-items: flex-start; gap: 7px;
  margin-top: 9px; font-size: 13px; color: var(--ink-2); line-height: 1.45;
}
.remember input { width: auto; margin: 2px 0 0; flex: none; }

/* Box score links in a team's schedule.
   Deliberately quiet: the result is the thing being read, and this sits beside
   it as an offer rather than competing with it. The "+ Box" variant is the one
   that matters — every part of the box score feature existed for weeks and no
   game in Kentucky had one, because nothing on the site ever pointed at it. */
.boxlink {
  display: inline-block; margin-left: 8px; font-size: 11px; font-weight: 700;
  letter-spacing: .3px; text-transform: uppercase; text-decoration: none;
  border: 1px solid var(--line); border-radius: 4px; padding: 1px 6px;
  color: var(--ink-3); vertical-align: middle;
}
.boxlink:hover { border-color: var(--brand); color: var(--brand); }
.boxlink.add { color: var(--ink-3); opacity: .8; }
.boxlink.add:hover { opacity: 1; }


/* A tournament round with no opponent drawn yet: the event and the gym, in
   place of a link to a school that does not exist. Deliberately quieter than a
   team name, because it is not one. */
.tname.tba { color: var(--ink-2); font-style: italic; font-weight: 600; }

/* TONIGHT, THEN THE NEXT NIGHT.
   "however many games there are tonight, then they start the list of tomorrows
   games." Each block under its own date, so a Friday game is never mistaken for
   a Wednesday one. */
.sb-next {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  font-size: 15px; margin: 26px 0 8px; padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
.sb-next a { color: var(--ink); text-decoration: none; }
.sb-next a:hover { text-decoration: underline; }
.sb-next span { font-size: 12.5px; color: var(--ink-3); font-weight: 400; }
.sb-none { font-size: 14px; color: var(--ink-2); margin: 0 0 14px; }

/* ------------------------------------------------------- sold sponsorships
   Businesses who paid this site directly, as opposed to the ad network. Held
   deliberately quieter than an AdSense unit: a sponsor is buying a scoreboard
   people trust, and the first thing that spends that trust is a logo competing
   with the scores. See lib/sponsors.js. */
/* THE PRESENTING PARTNER IS THE BIGGEST LINE ON THE RATE CARD AND LOOKED LIKE
   A FOOTNOTE.

     "the presenting sponsor right now is barely even visible"

   He is right, and the old note here was answering a question nobody asked.
   The fear was that a presenting sponsorship would grow into a banner and push
   the scores below the fold — so the band was held to a 13px line with a 34px
   logo, which at $24,500 against $10,000 for a statewide buy is a difference a
   buyer cannot see. A placement nobody notices is not restraint, it is a thing
   that will not sell twice.

   So: a real band. Taller, a 54px mark on a white plate, the business name at
   headline weight, and a brand-coloured rule along the top so it reads as a
   deliberate placement rather than a strip of page furniture.

   WHAT HAS NOT CHANGED IS THE RULE IT HAS TO OBEY. The scores still start in
   the top third of the screen, on a phone and on a computer, and
   tests/test-presentinglogo.js measures exactly that on both. The band is
   allowed to be as big as it likes right up to the point where it costs the
   board its place, and not one pixel past it. */
.presented-by {
  background: var(--brand-tint);
  border-top: 3px solid var(--brand);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  padding: 13px 0;
  color: var(--ink-2);
}
.presented-by .wrap {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
}
/* A label, not a sentence. "Kentucky High School Sports is presented by" in
   running text at 13px is four times the width of the thing it introduces and
   draws the eye first. Small, tracked and quiet, so the mark is what is read. */
.presented-by .pres-intro {
  font-size: 11px; font-weight: 700; letter-spacing: 1.3px; text-transform: uppercase;
  color: var(--ink-3);
}
.presented-by b { color: var(--ink); font-weight: 800; font-size: 19px;
                  letter-spacing: -.2px; }
.presented-by a { text-decoration: none; }
.presented-by a:hover b { text-decoration: underline; }
.presented-by em { font-style: normal; color: var(--ink-3); font-size: 12.5px; }
/* THE PRESENTING PARTNER'S MARK, NOT JUST HIS NAME.
   This stays a line rather than becoming a banner — the note in
   partials/header.ejs explains why, and it still holds: a presenting
   sponsorship that pushes the scores below the fold devalues itself inside a
   week. What a line can carry is the logo, which is the difference between
   reading a name and recognising a business. */
/* The link holds the plate AND the name, so it has to centre them against each
   other. Left to the row's `align-items: baseline` the name would sit on the
   bottom edge of the plate instead of in the middle of it. */
.presented-by .pres-mark { display: inline-flex; align-items: center; gap: 13px; }
.presented-by .pres-words { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.presented-by .pres-logo {
  background: #fff; border-radius: 7px; padding: 7px 13px; display: inline-flex;
  align-items: center; box-shadow: 0 1px 3px rgba(16,32,72,.14);
  /* flex: none, because .presented-by .wrap IS a flex row. Without it the plate
     is a flex item with no basis and an SVG inside it collapses to zero width —
     the logo loads, reports its natural size, and draws nothing at all. */
  flex: none;
}
/* AN EXPLICIT HEIGHT, NOT A MAXIMUM. A max-height gives a replaced element a
   ceiling and no size to work from, so an SVG with a viewBox and no intrinsic
   pixel width renders 0x0 inside a flex row. Height plus width:auto gives it
   the one dimension it needs and keeps the aspect ratio. */
.presented-by .pres-logo img { height: 54px; width: auto; max-width: 260px; display: block;
                               object-fit: contain; }
.presented-by em { font-size: 13.5px; }
@media (max-width: 699px) {
  /* Smaller on a phone, because the band is competing with the board for a
     screen a third the size — but still half again what it was. */
  .presented-by { padding: 11px 0; }
  .presented-by .pres-logo img { height: 42px; max-width: 190px; }
  .presented-by b { font-size: 16.5px; }
  .presented-by .pres-intro { font-size: 10px; letter-spacing: 1.1px; }
  .presented-by em { font-size: 12.5px; }
}

.ad-sold { display: flex; flex-direction: column; align-items: center; }
/* AND NOT DRAWN AT ALL ON A SCREEN WIDE ENOUGH FOR RAILS.
   "the ads should be on the right and left of these scores, not in the middle
    of the page." The sponsor in the feed slot is the same business who is on
   the left rail — see the slot note in server.js — so up there this unit would
   be him a second time, in the one place he was asked not to be. Below 1400px
   there are no rails and this is his only placement, which is why it exists.
   The AdSense unit in the same position is untouched: different element, and
   this rule does not match it.
   THIS RULE LIVES HERE, UNDER THE RULE IT OVERRIDES, AND NOT UP IN THE RAILS
   BLOCK WHERE IT WAS WRITTEN FIRST. Both are one class deep, so the one that
   comes later in the file wins — and `.ad-sold { display: flex }` is six
   hundred lines further down. The hide did nothing at all and the page looked
   exactly as it had. A media query does not raise specificity. */
@media (min-width: 1400px) { .ad-sold { display: none; } }
.sponsor-card {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  text-decoration: none; color: var(--ink); padding: 16px 18px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); width: 100%; max-width: 728px; text-align: center;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.sponsor-card:hover { border-color: var(--brand); box-shadow: 0 1px 6px rgba(23,48,122,.09); }
/* A LOGO IS THE POINT, SO GIVE IT ROOM AND A WHITE GROUND.
   Most business logos are drawn for white and a transparent PNG on a tinted
   card renders as dark-on-dark for half of them. The plate is not decoration,
   it is the thing that stops a sponsor's mark disappearing. */
.sponsor-logo {
  background: #fff; border-radius: 6px; padding: 10px 14px;
  display: flex; align-items: center; justify-content: center;
  max-width: 100%;
}
/* WIDTH 100%, NOT width:auto.
   An SVG with a viewBox and no width or height attribute — which is what a
   design studio exports — has an intrinsic width of exactly 300px in every
   browser. With `width: auto; max-width: 100%` it therefore drew at 300px and
   not one pixel more, so widening the rail to 420 bought a wider white plate
   around an unchanged logo and the unit looked emptier, not bigger. Filling the
   plate is what makes the artwork grow with it; max-height still caps a tall
   logo and the browser keeps the ratio. */
.sponsor-logo img {
  width: 100%; height: auto; max-height: 58px; max-width: 100%; display: block;
  object-fit: contain;
}

/* THE UNIT AMONG THE SCORES, ON EVERY SCREEN TOO NARROW FOR A RAIL.
   This is the only sold placement such a screen has — there is no gutter to put
   a rail in — so it carries the whole weight of what somebody bought. A 58px
   logo in a thin card read as a footnote between two game cards. It gets the
   room a sign needs: the card is the one thing on the page that is not a
   score, and it should look deliberate rather than squeezed in.
   THE BREAKPOINT IS 1399 AND NOT 699 ON PURPOSE. It used to stop at a phone,
   which left every laptop between 700 and 1400 — no rails yet, and a great
   many of the people who will actually open this — looking at the apologetic
   58px version. The rule is simply "no rail here, so this unit is the sign".
   min-height holds that size whether the artwork is a tall badge or a flat
   wordmark; without it the card collapses around whatever the sponsor sent. */
@media (max-width: 1399px) {
  /* THE UNIT LIES DOWN WHERE THERE IS NO GUTTER.
     "weve got to figure this out where it doesnt effect page view on a phone,
      but it needs to be super prominent on a computer"
     Those are two different shapes, not one shape at two sizes. A rail has a
     whole empty column and should fill it. A phone has only the reading column,
     so the same tall card sat between two scores and pushed the board off the
     screen — the sponsor got prominence by taking it from the thing people came
     for, which is how a scoreboard loses the readers the sponsor is paying for.
     On its side it is wide, unmissable and about a third of the height. */
  .ad-sold .sponsor-card {
    flex-direction: row; align-items: center; text-align: left;
    padding: 14px 16px; gap: 15px; min-height: 0;
  }
  .ad-sold .sponsor-logo {
    /* flex-basis, not width: the plate holds its size and the words take what
       is left, so a long business name cannot squeeze the logo to nothing. */
    flex: 0 0 136px; width: auto; min-height: 92px; padding: 10px 12px;
  }
  .ad-sold .sponsor-logo img { max-height: 76px; }
  .ad-sold .sponsor-words {
    display: flex; flex-direction: column; gap: 4px; min-width: 0;
  }
  .ad-sold .sponsor-name { font-size: 17px; }
  .ad-sold .sponsor-card.has-logo .sponsor-name { font-size: 14px; }
  .ad-sold .sponsor-line { font-size: 14px; }

  /* AND SHORTER AGAIN AT THE TOP OF A TEAM OR A REGION PAGE.
     "on a team or region page, it shouldnt be so tall"
     This one sits between the pickers and the team card — above the schedule,
     above the standings, the first thing under the dropdowns. It is the most
     valuable inch on the page precisely because it is before the content, so
     it has to cost the reader as little as possible. */
  .ad-band .sponsor-card { padding: 11px 13px; gap: 13px; }
  /* 156px, not 112. A plate narrow enough to keep the card short is also narrow
     enough to make a wordmark unreadable — the logo was rendering at a size
     where you could tell a logo was there and not read whose. The height is
     what Matt asked to come down, so the width is where the legibility has to
     come from, and the words beside it give up the space. */
  .ad-band .sponsor-logo { flex: 0 0 172px; min-height: 64px; padding: 7px 10px; }
  .ad-band .sponsor-logo img { max-height: 58px; }
  .ad-band .sponsor-name { font-size: 15.5px; }
  .ad-band .sponsor-card.has-logo .sponsor-name { font-size: 13px; }
  .ad-band .sponsor-line { font-size: 13px; }
}
/* The wrapper is a plain stack everywhere else, so a rail is unchanged. */
.sponsor-words { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.sponsor-name { font-size: 17px; font-weight: 700; letter-spacing: -.01em; line-height: 1.25; }
.sponsor-line { font-size: 13.5px; color: var(--ink-2); line-height: 1.4; }
/* With a logo the name is a caption, not the headline. */
.sponsor-card.has-logo .sponsor-name { font-size: 13.5px; font-weight: 600; color: var(--ink-2); }

/* ============================================================ SCORES JUST IN

   "If I'm scrolling, I don't want the page to jump on me. But maybe there is a
    red blinking box that floats as I scroll with the updated score"

   A game whose score has just changed does NOT move on the board. The board
   stays in its alphabet and this comes to the reader instead — fixed to the
   screen, so the page scrolls underneath it. Tapping it jumps to the game.

   WHY IT BLINKS SLOWLY, AND WHY THAT IS NOT A STYLE CHOICE.

   Content that flashes more than three times a second can trigger a seizure in
   a photosensitive reader. That is WCAG 2.3.1, it is the one accessibility rule
   whose failure mode is a hospital, and this is a high school sports board read
   by children on their own phones on a Friday night.

   The cycle below is 1.4s — about 0.7Hz, a quarter of the limit — and it swells
   between two reds rather than switching on and off, so there is no hard
   luminance flash at any point in it. It also stops after 40 seconds; see
   JI_BLINK in app.js. A box that blinks all night is a box somebody covers with
   their thumb.

   And anyone whose device asks for less motion gets the same red box, holding
   perfectly still, at the bottom of this block. */
.justin {
  position: fixed;
  left: 12px; right: 12px; bottom: 12px;
  /* Clear of the home bar on an iPhone, and of nothing at all elsewhere. */
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: none;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;   /* the gaps between cards stay clickable page */
}
.justin.on { display: flex; }

@media (min-width: 620px) {
  /* On a desktop the board is a 720px column in the middle of the window, so
     the corner is empty space rather than something this would cover. */
  .justin { left: auto; right: 18px; bottom: 18px; width: 300px; }
}

.ji-card {
  position: relative;
  pointer-events: auto;
  border-radius: 10px;
  overflow: hidden;
  background: var(--live);
  box-shadow: 0 3px 14px rgba(0, 0, 0, .28);
}

.ji-go {
  display: block; width: 100%;
  padding: 9px 30px 10px 12px;
  border: 0; background: transparent; cursor: pointer;
  text-align: left; font: inherit; color: #fff;
}
.ji-tag {
  display: block;
  font-size: 10.5px; font-weight: 800; letter-spacing: .09em;
  opacity: .9; margin-bottom: 3px;
}
.ji-line {
  /* centre, not baseline: the score is a one-line window with two numbers
     stacked inside it, and a clipped box has no baseline worth aligning to. */
  display: flex; align-items: center; gap: 8px;
  font-size: 14.5px; line-height: 1.45;
}
.ji-t {
  flex: 1 1 auto; font-weight: 600;
  /* A long school name wraps the card into a paragraph otherwise. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* WHAT THE SCORE DID, WHICH IS THE THING BEING REPORTED.

   "If they didn't know the score was 21-14 before and now it's 21-21, that
    doesn't really do much for me. They need to see the actual score change."

   The roll below shows it happening, and is over in under two seconds. This
   stays for the life of the card, so somebody who looks up late still learns
   what happened rather than reading a bare number. */
.ji-d {
  flex: 0 0 auto;
  font-size: 11.5px; font-weight: 800; letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
  padding: 1px 5px; border-radius: 4px;
  background: rgba(255, 255, 255, .22);
  /* IT ARRIVES WITH THE CHANGE IT DESCRIBES. Shown from the start, the badge
     sat beside the OLD number for the length of the hold — a card reading
     "+7  14" before rolling to 21, which is a contradiction on screen for the
     best part of a second. It fades in as the number moves. */
  opacity: 0; transition: opacity .4s ease .2s;
}
.ji-card.moved .ji-d { opacity: 1; }
.ji-d:empty { display: none; }

/* A ONE-LINE WINDOW WITH TWO NUMBERS IN IT.

   The old score sits in the window and the new one waits directly beneath.
   Sliding the pair up by exactly one line swaps them, which reads as the
   number changing — a cross-fade reads as two numbers arguing.

   Both children are the same height as the window, so -1.45em lands the second
   one exactly where the first was. em rather than px because the whole card
   scales with its font-size. */
/* THE WINDOW HOLDS STILL AND THE TRACK MOVES, and getting those the wrong way
   round is why this is spelled out. The first version put the clipping and the
   transform on the SAME element, so the window slid up 21px carrying its own
   contents with it — the old number stayed in view the whole time and crawled
   up out of the card. The transform was applying perfectly. It just moved the
   wrong thing, and every class and every measurement said it was working. */
.ji-s {
  flex: 0 0 auto; font-weight: 800; font-variant-numeric: tabular-nums;
  min-width: 1.4em; text-align: right;
  height: 1.45em; overflow: hidden;   /* the window: never transformed */
}
.ji-roll { display: block; }          /* the track: this is what slides */
.ji-was, .ji-now { display: block; height: 1.45em; line-height: 1.45em; }
.ji-card.moved .ji-line.rolls .ji-roll {
  transform: translateY(-1.45em);
  transition: transform .75s cubic-bezier(.22, .9, .25, 1);
}

.ji-x {
  position: absolute; top: 2px; right: 2px;
  width: 28px; height: 28px; padding: 0;
  border: 0; background: transparent; cursor: pointer;
  color: #fff; opacity: .75; font-size: 17px; line-height: 28px;
}
.ji-x:hover { opacity: 1; }
.ji-go:focus-visible, .ji-x:focus-visible { outline: 2px solid #fff; outline-offset: -3px; }

@keyframes ji-pulse {
  0%, 100% { background: #d8232a; box-shadow: 0 3px 14px rgba(0,0,0,.28), 0 0 0 0 rgba(216,35,42,.55); }
  50%      { background: #f0454c; box-shadow: 0 3px 14px rgba(0,0,0,.28), 0 0 0 8px rgba(216,35,42,0); }
}
.ji-card.blink { animation: ji-pulse 1.4s ease-in-out infinite; }

/* The row on the board, marked where it stands. It does not move; this is so
   that a tap on the alert lands somewhere that looks like what was tapped. */
.game.just-scored {
  border-color: rgba(216, 35, 42, .75);
  box-shadow: inset 3px 0 0 0 var(--live);
}

@media (prefers-reduced-motion: reduce) {
  .ji-card.blink { animation: none; }
  /* The roll is switched off in app.js for reduced motion — both slots are
     filled with the current score, so there is nothing stale to reveal. This
     is the belt as well as the braces. */
  .ji-card.moved .ji-line.rolls .ji-roll { transition: none; transform: none; }
  /* AND THE BADGE IS NOT CONDITIONAL ON AN ANIMATION THAT NEVER RUNS. With no
     roll there is no 'moved' class, so the rule above would have left +7 at
     opacity 0 — the one piece of information this build exists to add, hidden
     from precisely the readers who cannot watch it happen. Shown outright. */
  .ji-d { opacity: 1; transition: none; }
}
