/* Live tab — page styles. Theme tokens live in ../assets/site.css. */

/* Gold, silver and bronze for the top three. A place is not a route,
   so the medals are metal rather than anything out of the --line-*
   palette. The same three fills in both themes: a medal does not
   change colour with the room, and each carries its number at enough
   contrast either way. The discs are unoutlined: a ring round a filled
   disc is a second edge on a shape that already has one, and silver
   still reads against the aged ground on its fill alone. */
:root {
  --medal-1: #d9a300;
  --medal-1-ink: #111111;
  --medal-2: #b9bcc0;
  --medal-2-ink: #111111;
  --medal-3: #9c5f26;
  --medal-3-ink: #ffffff;
}

/* The stat cards. The live tab's own figures moved into the table's
   totals row, where each one can be read against the column it sums;
   the team page next door still loads this file for these three
   rules. */
.tab-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

@media (max-width: 560px) {
  .tab-stats { grid-template-columns: 1fr; }
}

.tab-stat {
  border: 1px solid var(--sign-bg);
  border-radius: 2px;
  background: var(--sign-bg);
  box-shadow: var(--shadow);
  padding: 16px;
  display: grid;
  gap: 6px;
}

.tab-stat .stat-label { color: var(--sign-muted); }

.tab-stat-num {
  color: var(--sign-ink);
  font-weight: 900;
  font-size: 1.8rem;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* How fresh the board is. It used to be the hero's second black bar,
   set at hero-copy size — a signage bar carries a name or a sentence
   about the page, and a clock that changes every second is neither, so
   a timestamp on one was the largest thing under the title. It reads
   as a column label instead, on the same left edge and at the same
   size as the heads it sits over, right where the numbers it dates
   are. Tabular figures so the ticking seconds do not shuffle the line
   sideways. */
.tab-updated {
  /* The page has one label voice, so this is the heads' size, case and
     weight; the air under it is what keeps it from reading as a
     seventh column head, which it did at 10px. */
  margin: 0 0 var(--after-title, 24px);
  font-size: var(--t-micro);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.tab-table-card {
  /* sideways only. overflow-x alone turns the other axis into auto as
     well, and the FLIP rows' transforms give it something to scroll
     into — the card could be dragged up and down by hand. hidden
     blocks the gesture (clip cannot pair with auto; it computes to
     hidden anyway). */
  overflow-x: auto;
  overflow-y: hidden;
}

/* The board is on the page rather than in a box: a forty-row table is
   not a unit. Fixed columns so every figure sits on the same edge
   whatever is in the row above it. */
.tab-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 1.0625rem;
  font-variant-numeric: tabular-nums lining-nums;
}

/* the head is a line of labels over the heavy rule, not a black bar —
   the page already spends its black on the board at the top */
.tab-table th {
  text-align: right;
  font-size: var(--t-micro);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: transparent;
  padding: 0 14px 8px;
  border-bottom: 3px solid var(--text);
}
.tab-table th.tab-col-team,
.tab-table th.tab-col-last { text-align: left; }

.tab-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

/* the table keeps the page's own left and right edges */
.tab-table th:first-child,
.tab-table td:first-child { padding-left: 0; }
.tab-table th:last-child,
.tab-table td:last-child { padding-right: 0; }

/* Six columns: what identifies a row, what is read, then the figures,
   then the trailing text column. */
.tab-col-rank { width: 64px; text-align: right; }
.tab-col-points { width: 104px; text-align: right; }
.tab-col-behind { width: 96px; text-align: right; }
.tab-col-quests { width: 88px; text-align: right; }
.tab-col-last { width: 37%; }

/* one size across the row; weight says what outranks what */
td.tab-col-rank { font-weight: 800; }
td.tab-team { font-weight: 600; overflow-wrap: break-word; }
td.tab-col-points { font-weight: 800; }
td.tab-col-behind,
td.tab-col-quests,
td.tab-col-last { font-weight: 400; }

/* the top three wear medals; every other rank is the figure itself */
.rank-bullet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 28px;
  padding: 0 5px;
  border: 0;
  border-radius: 50%;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.rank-bullet--1 { background: var(--medal-1); color: var(--medal-1-ink); }
.rank-bullet--2 { background: var(--medal-2); color: var(--medal-2-ink); }
.rank-bullet--3 { background: var(--medal-3); color: var(--medal-3-ink); }

/* Last scored: when, which code, and what it was. The time is tabular
   and the name takes what is left, so a long quest name wraps under
   itself rather than under the clock. */
.tab-last {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.tab-last-at {
  flex: none;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.tab-last .code { flex: none; }
/* break-word rather than anywhere: anywhere makes the box's smallest
   possible width one character, and a flex item that may be one
   character wide is one, so every quest name came down the page a
   letter at a time */
.tab-last-name { min-width: 0; overflow-wrap: break-word; }

/* the totals close the table, under the columns they sum. With no
   standings there is nothing to sum, and the row would be a line of
   dashes under a rule hard against the head's. */
.tab-table.is-empty tfoot { display: none; }
.tab-table tbody tr:last-child td { border-bottom: 0; }
.tab-table tfoot td {
  border-top: 3px solid var(--text);
  border-bottom: 0;
  padding-top: 14px;
  font-weight: 800;
}

/* the word that leads a figure on a phone, where there is no column
   head beside it; on the page the head says it instead */
.tab-word { display: none; }

/* the message, then the one thing to do about it */
#tab-error {
  display: grid;
  gap: 10px;
  justify-items: start;
}

.tab-retry {
  border: 1px solid var(--accent-dark);
  border-radius: 2px;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-weight: 700;
  font-size: 0.88rem;
  padding: 6px 12px;
  cursor: pointer;
}
.tab-retry:active { transform: translate(1px, 1px); }

/* once team pages are open the name is a link to the team's own page */
.tab-team-link { color: var(--accent-dark, var(--accent)); text-decoration: underline; text-underline-offset: 2px; }

/* The one thing the table cannot carry: what is not on it yet. Set at
   the size the rows are read at rather than a fourth size of its own. */
/* The mark before it is what says this is a note about the board
   rather than another line of the board. It sits on the first line's
   own baseline box, so the sentence wraps under the words rather than
   under the icon. */
.tab-waiting {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 8px;
  align-items: start;
  margin: 20px 0 0;
  color: var(--muted);
  font-size: 1.0625rem;
  max-width: 34em;
}
.tab-waiting-mark {
  width: 1.05em;
  height: 1.05em;
  margin-top: 0.22em;   /* the cap height of the line beside it */
  flex: none;
}
.tab-recap { margin: 12px 0 0; color: var(--muted); font-size: 1.0625rem; }
.tab-recap a { font-weight: 700; }

/* ---------------- The phone ----------------
   Six columns need about 700px and there are 358 to play with. Two
   views, and the reader picks (`.tab-view`, kept in localStorage
   `scs2-tab-view`):

   Compact — one line a team: place, name, quests, points. Forty teams
   fit on two screens, which is what the board is for most of the day:
   where everybody is.

   Full — the same line, and under the name one more: what the team
   last scored, and how far behind the leader it is. The detail line
   never wraps; a long quest name is cut, because a row that grows to
   four lines is what made the old layout read as a pile.

   Both are the table's own rows laid out as grids: a row that is a
   grid is no longer a table row, and a table left to size itself
   around one takes its widest measure and hangs off the side of the
   phone. The roles on the markup keep it a table to a screen reader. */
.tab-bar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 var(--after-title, 24px);
}
.tab-bar-row .tab-updated { margin: 0; }
/* the switch is a phone's question; a desk has room for every column */
.tab-view { display: none; }

@media (max-width: 760px) {
  .tab-view {
    display: inline-flex;
    gap: 6px;
  }
  .tab-view button {
    min-height: 32px;
    padding: 0 12px;
    border: 1px solid var(--border-strong);
    border-radius: 2px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: var(--t-micro);
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background-color var(--duration-quick) var(--ease-smooth-out), color var(--duration-quick) var(--ease-smooth-out);
  }
  /* the one in use is filled with the sign's black and gives up its
     outline, the way a picked quest-list chip does */
  .tab-view button[aria-pressed="true"] {
    background: var(--sign-bg);
    border-color: var(--sign-bg);
    color: var(--sign-ink);
  }

  .tab-table,
  .tab-table thead,
  .tab-table tbody,
  .tab-table tfoot {
    display: block;
    width: 100%;
  }
  .tab-table { font-size: 1rem; }

  .tab-table thead tr,
  .tab-table tbody tr,
  .tab-table tfoot tr {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) 64px 72px;
    column-gap: 10px;
    align-items: center;
  }
  .tab-table thead th,
  .tab-table tbody td,
  .tab-table tfoot td {
    width: auto;
    padding: 0;
    border: 0;
  }
  .tab-table thead tr {
    padding-bottom: 7px;
    border-bottom: 3px solid var(--text);
  }
  .tab-table tbody tr {
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
  }
  .tab-table tbody tr:last-child { border-bottom: 0; }
  .tab-table tfoot tr {
    padding-top: 12px;
    border-top: 3px solid var(--text);
  }

  /* the place needs no head: a column of 1 to 43 says what it is */
  .tab-table th.tab-col-rank,
  .tab-table th.tab-col-behind,
  .tab-table th.tab-col-last { display: none; }
  .tab-table thead th { grid-row: 1; }
  .tab-table th.tab-col-team { grid-column: 2; }
  .tab-table th.tab-col-quests { grid-column: 3; }
  .tab-table th.tab-col-points { grid-column: 4; }

  /* every first line the disc's height, so a medal row is no taller
     than the ones under it */
  .tab-table td.tab-col-rank { grid-column: 1; grid-row: 1; text-align: center; line-height: 26px; }
  .tab-table td.tab-team { grid-column: 2; grid-row: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .tab-table td.tab-col-quests { grid-column: 3; grid-row: 1; color: var(--muted); }
  .tab-table td.tab-col-points { grid-column: 4; grid-row: 1; }
  .tab-table td.tab-col-behind,
  .tab-table td.tab-col-last { display: none; }
  .rank-bullet { min-width: 26px; height: 26px; font-size: 0.9rem; }
  .tab-table tfoot td.tab-col-quests { color: var(--text); }

  /* ---- full: a second line under the name ---- */
  .tab-table-card.is-full .tab-table tbody tr { row-gap: 3px; }
  .tab-table-card.is-full .tab-table td.tab-col-last,
  .tab-table-card.is-full .tab-table td.tab-col-behind {
    display: block;
    grid-row: 2;
    font-size: 0.8rem;
    line-height: 1.35;
    color: var(--muted);
  }
  .tab-table-card.is-full .tab-table td.tab-col-last { grid-column: 2 / span 2; min-width: 0; }
  .tab-table-card.is-full .tab-table td.tab-col-behind { grid-column: 4; text-align: right; }
  .tab-table-card.is-full .tab-last { gap: 6px; flex-wrap: nowrap; white-space: nowrap; overflow: hidden; }
  .tab-table-card.is-full .tab-last-name { overflow: hidden; text-overflow: ellipsis; }
  .tab-table-card.is-full .tab-last .code { font-size: 0.7rem; }
  .tab-table-card.is-full .tab-last-at { font-weight: 700; color: var(--text); }
  /* the gap to the leader, as a minus figure under the points it is
     taken from; the leader's own is left blank rather than a zero */
  .tab-table-card.is-full td.tab-col-behind .tab-word { display: none; }
  .tab-table-card.is-full td.tab-col-behind::before { content: '\2212'; }
  .tab-table-card.is-full tr.is-leader td.tab-col-behind { visibility: hidden; }
  .tab-table tfoot td.tab-col-behind,
  .tab-table tfoot td.tab-col-last { display: none !important; }

  .tab-word { display: none; }
}

/* the stat digits use the shared number pop-in in site.css */

/* a team new to the table rises in */
.tab-table tr.is-new { animation: t-rise var(--duration-fast) var(--ease-smooth-out) both; }
@media (prefers-reduced-motion: reduce) { .tab-table tr.is-new { animation: none; } }

/* ---------------- Skeleton reveal (transitions.dev 14) ----------------
   The first load pulses grey placeholders where the numbers and the
   table land; when the API first answers, the real content
   cross-fades in with a small blur. Adapted from the pattern: the
   content keeps its place in flow (the table's height is data-driven)
   and the skeleton overlays it. Tokens in site.css. */
.t-skel { position: relative; display: block; }
.t-skel-skeleton {
  position: absolute;
  inset: 0;
  z-index: 1;
  opacity: 1;
  filter: blur(0);
  transition:
    opacity var(--reveal-dur) var(--reveal-ease),
    filter  var(--reveal-dur) var(--reveal-ease);
}
.t-skel-content {
  position: relative;
  z-index: 2;
  opacity: 0;
  filter: blur(var(--reveal-blur));
  transition:
    opacity var(--reveal-dur) var(--reveal-ease),
    filter  var(--reveal-dur) var(--reveal-ease);
}
.t-skel.is-revealed .t-skel-skeleton {
  opacity: 0;
  filter: blur(var(--reveal-blur));
  pointer-events: none;
}
.t-skel.is-revealed .t-skel-content {
  opacity: 1;
  filter: blur(0);
}

/* the wait is open-ended, so the pulse loops until data lands */
.t-skel-skeleton.is-pulsing > * {
  animation: t-skel-pulse var(--pulse-dur) ease-in-out infinite;
}
@keyframes t-skel-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: var(--pulse-min); }
}

/* placeholder shapes: hard-edged grey bars, per the design system */
.tab-skel-bar,
.tab-skel-row {
  display: block;
  background: var(--border);
  border-radius: 2px;
}

/* an empty table is only a header row tall — the placeholder is a
   table of the same shape: ranks 1-10 as themselves, grey bars where
   the names and numbers go, an all-bar row for the unknown rest, and
   the totals row under them */
#tab-skel-table:not(.is-revealed) { min-height: 640px; }
.tab-skel-table td .tab-skel-bar { height: 14px; width: 100%; }
.tab-skel-table td.tab-col-rank .tab-skel-bar { width: 18px; margin-left: auto; }
.tab-skel-table td.tab-col-points .tab-skel-bar,
.tab-skel-table td.tab-col-behind .tab-skel-bar,
.tab-skel-table td.tab-col-quests .tab-skel-bar { width: 44px; margin-left: auto; }
@media (max-width: 760px) {
  #tab-skel-table:not(.is-revealed) { min-height: 560px; }
  .tab-skel-table td.tab-col-quests .tab-skel-bar { width: 28px; }
}

@media (prefers-reduced-motion: reduce) {
  .t-skel-skeleton,
  .t-skel-content { transition: none !important; }
  .t-skel-skeleton.is-pulsing > * { animation: none !important; }
}

/* ---------------- Frozen ----------------
   Partway through the day the tab stops in public. A sheet of ice
   settles over the page, the word Live is struck out and FROZEN is
   scrawled across the board in red marker. */

/* the ice sits over everything the page shows, and under the fixed
   chrome so the way out is never iced over */
.tab-ice {
  position: fixed;
  inset: 0;
  z-index: 1040;
  pointer-events: none;
  display: grid;
  place-items: center;
  animation: ice-settle 700ms var(--ease-smooth-out);
}
@keyframes ice-settle { from { opacity: 0; } }

.tab-ice-sheet {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* the frost itself: a cold wash with a blur, so the numbers underneath
   read as behind glass rather than hidden */
.tab-ice::before {
  content: '';
  position: absolute;
  inset: 0;
  backdrop-filter: blur(1.5px) saturate(0.75);
  -webkit-backdrop-filter: blur(1.5px) saturate(0.75);
  background: linear-gradient(160deg, rgba(198, 231, 245, 0.28), rgba(232, 246, 252, 0.16) 45%, rgba(170, 212, 232, 0.3));
}

.tab-ice-stamp {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 6px;
  width: min(560px, 82vw);
  transform: rotate(-7deg);
}

/* the marker: a heavy hand-drawn stroke, not a typeface */
.tab-ice-word { width: 100%; height: auto; overflow: visible; }
.tab-ice-word text {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 128px;
  font-weight: 900;
  letter-spacing: -0.04em;
  fill: none;
  stroke: var(--line-red);
  stroke-width: 7;
  stroke-linejoin: round;
  paint-order: stroke;
  opacity: 0.92;
}
.tab-ice-underline {
  fill: none;
  stroke: var(--line-red);
  stroke-width: 9;
  stroke-linecap: round;
  opacity: 0.88;
}
/* both are drawn on, left to right, as if written */
@media (prefers-reduced-motion: no-preference) {
  .tab-ice-word text {
    stroke-dasharray: 1400;
    stroke-dashoffset: 1400;
    animation: ice-write 900ms var(--ease-smooth-out) 200ms forwards;
  }
  .tab-ice-underline {
    stroke-dasharray: 560;
    stroke-dashoffset: 560;
    animation: ice-write 420ms var(--ease-smooth-out) 950ms forwards;
  }
}
@keyframes ice-write { to { stroke-dashoffset: 0; } }

.tab-ice-when {
  margin: 0;
  padding: 6px 12px;
  background: var(--sign-bg);
  color: var(--sign-ink);
  border-radius: 2px;
  font-size: 0.8rem;
  font-weight: 700;
  text-align: center;
  line-height: 1.4;
  transform: rotate(7deg); /* upright again, against the stamp's tilt */
  max-width: 100%;
}

/* the title: Live struck through, since it is not */
html.tab-frozen .tab-live-word {
  position: relative;
  opacity: 0.65;
}
html.tab-frozen .tab-live-word::after {
  content: '';
  position: absolute;
  left: -4%;
  right: -4%;
  top: 52%;
  height: 5px;
  border-radius: 2px;
  background: var(--line-red);
  transform: rotate(-2.5deg);
  transform-origin: 0 50%;
  animation: ice-strike 420ms var(--ease-smooth-out) both;
}
@keyframes ice-strike { from { transform: rotate(-2.5deg) scaleX(0); } }

/* the leaderboard keeps working underneath, just cold */
html.tab-frozen .tab-table-card,
html.tab-frozen .tab-waiting { filter: saturate(0.7); }

@media (prefers-reduced-motion: reduce) {
  .tab-ice { animation: none; }
  html.tab-frozen .tab-live-word::after { animation: none; }
}
