/*
 * The public fixture board.
 *
 * A page read by people who have no account, on whatever device they have, at
 * the side of a pitch. It shares the system's design tokens so it looks like
 * the same product as the desk behind it, and it shares nothing else: no
 * sidebar, no topbar, no theme switch, none of the shell's weight.
 *
 * PHONE FIRST, and not as a slogan — the layout is a single column with wide
 * tables in their own scrollers, and the desktop version is that same column
 * given a wider limit. A tournament's audience is holding phones.
 *
 * EVERY COLOUR IS A TOKEN, so the page follows the reader's own light or dark
 * setting without a second stylesheet. @see tokens.css
 *
 * @see public/views/board.php
 * @see public/assets/js/board.js
 */

/*
 * `hidden` wins, everywhere on this page.
 *
 * The browser's own rule is `[hidden] { display: none }`, which any class
 * carrying a `display` outranks — so an element given `display: flex` and then
 * hidden by attribute stays on screen. Half the things here are shown and hidden
 * that way, including a dialog, and finding it one element at a time is how one
 * of them is eventually missed. One rule, stated once. @see base.css
 */
[hidden] { display: none !important; }

.board-body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: Inter, "Noto Sans Thai", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
}

/* ---------------------------------------------------------------- header --- */

/*
 * Pinned to the top.
 *
 * It carries which tournament this is and whether anything is on air — the two
 * things a reader glances back at while scrolling a long list, and the two they
 * would otherwise have to scroll to the top to check.
 */
.board-head {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--brand-gradient, linear-gradient(135deg, #2457f5, #123eea));
  color: #fff;
  box-shadow: 0 6px 22px rgba(10, 25, 90, .22);
}

/*
 * Every row of the header on the same left edge.
 *
 * The three rows had grown their own padding — 16px for the title, 16 for the
 * live line, 12 for the jump links — so the tournament's name, the match on air
 * and the navigation each started at a slightly different place. Nothing about
 * that is visible as a mistake; it just makes a header look untidy in a way
 * nobody can point at. One rule, one edge.
 */
.board-head__bar,
.board-live,
.board-nav {
  max-width: 1120px;
  margin: 0 auto;
  padding-left: 18px;
  padding-right: 18px;
}

.board-head__bar {
  padding-top: 14px;
  padding-bottom: 11px;
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.board-head__id {
  /* Without this a long tournament name refuses to wrap and pushes the badge
     and the share button off the side of a phone. */
  min-width: 0;
  flex: 1;
}

.board-mark {
  display: block;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .18em;
  opacity: .78;
}

.board-title {
  margin: 2px 0 0;
  font-size: 19px;
  font-weight: 800;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.board-date {
  margin: 3px 0 0;
  font-size: 12.5px;
  opacity: .85;
}

/* The on-air badge. A dot that breathes, so it reads as "now" at a glance. */
.board-live {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-top: 2px;
  padding-bottom: 2px;
  font-size: 12.5px;
  font-weight: 700;
}

/* The lamp and its word are one object, so the dot is inside the pill. */
.board-live__label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 3px 11px 3px 9px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .2);
  border: 1px solid rgba(255, 255, 255, .38);
  font-size: 11px;
}

/* The club names take whatever room is left and wrap rather than truncate. */
.board-live__teams {
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * VS, and it breathes.
 *
 * The one mark between two club names on a header that also carries a title, a
 * date and a row of links — so it is given a badge and a pulse rather than
 * being left as a word to find. It pulses in step with the lamp beside it, so
 * the two read as one signal saying the same thing.
 */
.board-live__vs {
  display: inline-block;
  margin: 0 8px;
  padding: 1px 7px;
  border-radius: var(--radius-xs);
  background: rgba(255, 255, 255, .2);
  font-size: 11px;
  font-weight: 900;
  font-style: italic;
  letter-spacing: .06em;
  vertical-align: 1px;
  animation: board-vs-blink 1.6s ease-in-out infinite;
}

@keyframes board-vs-blink {
  0%, 100% { background: rgba(255, 255, 255, .18); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
  50%      { background: rgba(255, 255, 255, .46); box-shadow: 0 0 10px 0 rgba(255, 255, 255, .3); }
}

/*
 * GREEN, not red, and the reason is the schedule desk's.
 *
 * Red on a fixture list already means something else here — a withdrawn club, a
 * struck-out row — so a red light beside a match reads as a problem with it. A
 * green light with a pulse is what every recording lamp has said for fifty
 * years: this one is running.
 */
.board-live__dot {
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #35e08b;
  box-shadow: 0 0 0 0 rgba(53, 224, 139, .7);
  animation: board-pulse 1.6s ease-out infinite;
}

@keyframes board-pulse {
  70% { box-shadow: 0 0 0 9px rgba(53, 224, 139, 0); }
  100% { box-shadow: 0 0 0 0 rgba(53, 224, 139, 0); }
}

/* The theme switch and the buttons, kept together at the top right. */
.board-head__tools {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  flex-shrink: 0;
}

/*
 * The switch, on a coloured header.
 *
 * Its track is drawn from the page's surface tokens, which are near-white — on
 * the header's blue that reads as a bright slab. Softened to match the share
 * button beside it, with the same border, so the two read as one pair of
 * controls rather than as one control and one sticker.
 */
.board-head__tools .theme-switch-track {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .4);
}

.board-head__tools .theme-switch:hover .theme-switch-track {
  border-color: rgba(255, 255, 255, .75);
}

.board-act {
  flex-shrink: 0;
  padding: 6px 13px;
  border: 1px solid rgba(255, 255, 255, .4);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .12);
  color: #fff;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--duration-fast, 140ms) ease;
}

.board-act:hover { background: rgba(255, 255, 255, .24); }
.board-act.is-done { background: rgba(255, 255, 255, .34); }

/* The jump links, scrolled sideways rather than wrapped onto three lines. */
.board-nav {
  display: flex;
  gap: 6px;
  padding-top: 9px;
  padding-bottom: 11px;
  overflow-x: auto;
  scrollbar-width: none;
}

.board-nav::-webkit-scrollbar { display: none; }

.board-nav a {
  flex-shrink: 0;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .14);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}

.board-nav a:hover { background: rgba(255, 255, 255, .26); }


/*
 * Anchored sections start below the pinned header.
 *
 * Without this a jump link lands with its own heading hidden underneath the
 * bar, which reads as having jumped to the wrong place.
 */
[id^="board-"] { scroll-margin-top: 130px; }

/*
 * A jump link must clear the whole bar, and the bar is not one height.
 *
 * It grows a row when a match goes on air, and its title wraps on a narrow
 * screen — measured at 181px on a tablet, 194px on a phone and 233px on a small
 * one. A single figure would land some of those under the bar, which reads as
 * having jumped to the wrong section.
 */
@media (max-width: 700px) {
  [id^="board-"] { scroll-margin-top: 206px; }
}

@media (max-width: 400px) {
  [id^="board-"] { scroll-margin-top: 240px; }
}

/* ------------------------------------------------------------------ page --- */

.board {
  max-width: 1120px;
  margin: 0 auto;
  padding: 16px 12px 8px;
}

.board-card {
  margin-bottom: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm, 0 6px 18px rgba(40, 56, 120, .07));
  overflow: hidden;
}

.board-card__head {
  padding: 14px 16px 12px;
  border-bottom: 1px solid var(--divider);
  background: var(--surface-2);
}

/*
 * A heading with its totals on the same line.
 *
 * They wrap under it when there is no room, rather than squeezing the title —
 * the three counts are a summary of what is below and can afford a second line;
 * a tournament's category heading cannot.
 */
.board-card__head--stats {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 18px;
}

.board-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 16px;
}

.board-stat {
  font-size: 12.5px;
  color: var(--text-2);
  white-space: nowrap;
}

.board-stat b {
  margin-right: 4px;
  font-size: 17px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.board-card__head h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
}

.board-card__head p {
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--text-3);
}

.board-card__body { padding: 14px 16px 16px; }

.board-empty {
  margin: 6px 0;
  padding: 18px 14px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  color: var(--text-3);
  font-size: 13px;
  text-align: center;
}

/* What the page shows instead of a schedule: draft, missing, or no signal. */
.board-state {
  margin: 40px auto;
  max-width: 460px;
  padding: 30px 22px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm, 0 6px 18px rgba(40, 56, 120, .07));
  text-align: center;
}

.board-state__icon { font-size: 40px; line-height: 1; }
.board-state__title { margin: 12px 0 0; font-size: 17px; font-weight: 800; }
.board-state__note { margin: 8px 0 0; color: var(--text-3); font-size: 13.5px; }

/* ---------------------------------------------------------------- tables --- */

/*
 * Wide tables scroll inside themselves.
 *
 * The alternative is a page that slides sideways as a whole, which on a phone
 * takes the pinned header and every other section with it.
 */
.board-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.board-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  white-space: nowrap;
}

.board-table th {
  padding: 8px 9px;
  background: var(--surface-2);
  color: var(--text-3);
  font-size: 11.5px;
  font-weight: 700;
  text-align: center;
  border-bottom: 1px solid var(--border);
}

.board-table td {
  padding: 9px;
  text-align: center;
  border-bottom: 1px solid var(--divider);
}

.board-table tbody tr:last-child td { border-bottom: 0; }

.board-table .col-index { width: 44px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.board-table .col-num { width: 46px; font-variant-numeric: tabular-nums; }
.board-table .col-time { width: 58px; font-variant-numeric: tabular-nums; color: var(--text-2); }
.board-table .col-points { width: 58px; font-weight: 800; font-variant-numeric: tabular-nums; }

.board-table .col-name,
.board-table .col-team {
  text-align: left;
  white-space: normal;
  min-width: 128px;
}

/*
 * The category's own colour, on a stripe rather than on the text.
 *
 * The desk picks these colours freely, so a pale one used as ink would be
 * unreadable on white and a dark one invisible on black. A stripe is legible
 * whatever the colour is.
 */
.board-tone {
  display: inline-block;
  padding-left: 9px;
  border-left: 3px solid var(--tone, var(--brand));
  font-weight: 600;
}

.board-quiet { color: var(--text-3); }

/* ---------------------------------------------------------------- groups --- */

.board-block { margin-bottom: 18px; }
.board-block:last-child { margin-bottom: 0; }

.board-block__head {
  margin: 0 0 10px;
  padding-left: 10px;
  border-left: 4px solid var(--tone, var(--brand));
  font-size: 13.5px;
  font-weight: 800;
  color: var(--text-2);
}

/*
 * A group card, matching the one on the schedule desk.
 *
 * WHY THE FIGURES ARE NOT ON IT. The desk arrived at this shape and the reason
 * holds even harder in public: what somebody asks of a group table while a
 * tournament is running is "is my club still in, and who is top". Ten columns
 * of arithmetic beside every name answered a question nobody had asked and
 * buried the one they did. The league table is one press away instead.
 *
 * Cards in a row are the same height, because groups are rarely even — four
 * teams here, two there — and cards sized to their own contents leave the board
 * looking like a broken wall.
 *
 * Per row, NOT per page. The desk sets every row to the tallest card anywhere
 * on it, which it can afford because its details open in a window of their own.
 * Here they open in place, and one open card would have stretched every other
 * row on the page to match it — a screen of two-team groups with a hand's depth
 * of empty card under each.
 */
.board-standing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
  align-items: stretch;
}

.board-standing {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 13px 15px;
  border: 1px solid var(--border);
  border-top: 3px solid var(--tone, var(--brand));
  border-radius: var(--radius-md);
  background: var(--surface);
}

/* A group still being played reads as provisional. */
.board-standing:not(.is-complete) { border-style: dashed; border-top-style: solid; }

.board-standing__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 9px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--divider);
}

.board-standing__head h4 { margin: 0; font-size: 14px; font-weight: 800; }

/* The list takes the slack, so the button stays on the bottom edge. */
.board-standing__list {
  flex: 1 1 auto;
  align-content: start;
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.board-standing__line {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 7px;
  border-radius: var(--radius-xs);
  font-size: 13px;
}

.board-standing__pos {
  flex: 0 0 20px;
  text-align: center;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
}

.board-standing__team { min-width: 0; font-weight: 600; overflow-wrap: anywhere; }

/*
 * The teams going through.
 *
 * Marked rather than left to be read off the top: a place is only settled once
 * the group is finished, and a reader who assumes the top row has qualified
 * would pick the wrong club on any morning where the table is still moving.
 */
.board-standing__line.is-through { background: var(--success-soft); }
.board-standing__line.is-through .board-standing__team { color: var(--success); }
.board-standing__line.is-out { opacity: .8; }
.board-standing__line.is-out .board-standing__team { color: var(--danger); text-decoration: line-through; }
.board-standing__out { flex-shrink: 0; font-size: 11px; color: var(--danger); }

.board-standing__foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid var(--divider);
}

.board-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--brand);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.board-link:hover { text-decoration: underline; }

.board-tag {
  flex-shrink: 0;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.board-tag--done { background: var(--success-soft); color: var(--success); }
.board-tag--live { background: var(--brand-soft); color: var(--brand); }
.board-tag--wait { background: var(--warning-soft); color: var(--warning-ink); }
.board-tag--muted { background: var(--surface-2); color: var(--text-3); }
.board-tag--out { margin-left: 6px; background: var(--danger-soft); color: var(--danger); }
/* "แทน …": a replacement club, under its name — context, not a warning. */
.board-tag--replaces { display: block; width: max-content; margin-top: 3px; padding: 1px 8px; background: var(--surface-2); color: var(--text-3); font-weight: 600; }

/*
 * How a club reached the round — a note under the name, not a badge beside it.
 *
 * The other tags are STATUS and belong on the same line as the thing they
 * label. This one qualifies the name itself and is read once, so it drops
 * below on its own line in small plain type: on a fixture list of forty rows,
 * forty more pills would be the loudest thing on the screen and the least
 * important.
 *
 * The lot draw is the exception and is tinted, because a place decided by
 * chance is the one line on this board somebody will come and ask about.
 */
.board-tag--origin {
  display: block;
  margin: 2px 0 0;
  padding: 0;
  background: none;
  border-radius: 0;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
  white-space: normal;
}

.board-tag--origin.board-tag--lot {
  color: var(--success);
  font-weight: 600;
}

/* Side by side where there is room, stacked where there is not — the lot draws. */
.board-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr));
  gap: 12px;
  align-items: start;
}

.board-tie {
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface);
}

.board-tie__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--divider);
  font-size: 13px;
}

.board-tie__head strong { min-width: 0; overflow-wrap: anywhere; }

/* A team that finished in a qualifying place. */
.board-table tr.is-through { background: var(--success-soft); }
.board-table tr.is-through { background: color-mix(in srgb, var(--success) 7%, transparent); }
.board-table tr.is-out td { color: var(--text-3); text-decoration: line-through; }

.board-rank {
  display: inline-grid;
  place-items: center;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: var(--success);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
}

/* -------------------------------------------------------------- fixtures --- */

/* The find box and the category chips. */
.board-find {
  position: relative;
  margin-top: 10px;
}

.board-find__input {
  width: 100%;
  padding: 9px 34px 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  box-sizing: border-box;
}

.board-find__input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--focus-ring, rgba(36, 87, 245, .24));
}

.board-find__clear {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}

.board-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.board-chips:empty { margin-top: 0; }

.board-chip {
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.board-chip.is-on {
  border-color: var(--tone, var(--brand));
  background: var(--tone, var(--brand));
  color: #fff;
}

/* A round's heading, spanning the fixture table. */
.board-section td {
  padding: 9px 10px !important;
  background: var(--surface-2);
  text-align: left !important;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .04em;
  color: var(--text-2);
}

/* Each fixture wears its category's colour on its leading edge. */
.board-row td:first-child { box-shadow: inset 3px 0 0 var(--tone, var(--brand)); }
.board-row.is-played { background: var(--surface-2); }
.board-row.is-played { background: color-mix(in srgb, var(--text) 3%, transparent); }
.board-row .col-team.is-out { color: var(--text-3); text-decoration: line-through; }
.board-row .col-group { font-size: 12px; color: var(--text-2); }
.board-row .col-type { font-size: 12px; }

/*
 * The fixture that is on air.
 *
 * It breathes rather than sits in a fixed colour: the page is read while a
 * tournament is running, and the row that is happening NOW has to be findable
 * in a list of forty without hunting for it.
 *
 * The inset stripe is the category's colour on every other row; here it is
 * overridden in green, because the tint would otherwise sit over the wash and
 * hide the one row this whole treatment exists to point at. The desk does the
 * same thing for the same reason.
 */
.board-row.is-live {
  background: var(--success-soft);
  background: color-mix(in srgb, var(--success) 10%, transparent);
  animation: board-live-row 2.2s ease-in-out infinite;
}

.board-row.is-live td:first-child { box-shadow: inset 3px 0 0 var(--success); }
.board-row.is-live .col-team { font-weight: 700; }

@keyframes board-live-row {
  50% { background: color-mix(in srgb, var(--success) 22%, transparent); }
}

/*
 * The result, and it never leaves its cell.
 *
 * `max-width: 100%` with border-box is the whole fix for a fault that was
 * reported from an iPhone: the column is a fixed width, "กำลังแข่ง" is wider
 * than it, and with `nowrap` inherited from the table the red badge sat across
 * both club names — the row that matters most was the one you could not read.
 *
 * A number never needs the room; only the words do, and they are allowed to
 * wrap onto a second line rather than to push sideways.
 */
.board-score {
  display: inline-block;
  box-sizing: border-box;
  max-width: 100%;
  min-width: 54px;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.board-score--wait { background: transparent; color: var(--text-3); font-weight: 700; }

.board-score--live {
  background: var(--success);
  color: #fff;
  animation: board-onair-pulse 1.6s ease-in-out infinite;

  /* The only score that is words rather than digits, so the only one that has
     to be allowed to wrap. */
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.25;
}

/* The same ring the desk's on-air button wears. */
@keyframes board-onair-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(0, 129, 79, .5); }
  50%      { box-shadow: 0 0 0 6px rgba(0, 129, 79, 0); }
}

.board-table .col-score { width: 92px; }

/*
 * The little box under a result: a shoot-out, or a walkover.
 *
 * A BOX, NOT A FOOTNOTE. "1-1" in a knockout round is not the answer to who
 * went through, and the figures that are — 4-3 on penalties — were a line of
 * grey text under it. Given an outline of their own they read as what they are:
 * a second, deciding result.
 *
 * ON ONE LINE, label then figures. The column it lives in is 52px wide on a
 * phone, so the type is small and the box is allowed to wrap rather than to
 * push sideways — a second line is a box that still reads; an overflow is a box
 * sitting on top of a club's name.
 */
.board-subscore {
  /*
   * Block-level, so it takes a line of its own.
   *
   * As an inline box it stood BESIDE the score instead of under it — the cell
   * inherits `white-space: nowrap` from the table, so the two never wrapped and
   * the box ran out over the away club's name. Measured at 390px: four boxes,
   * all four outside their cell.
   */
  /*
   * As wide as the cell, so the two sit side by side.
   *
   * `width: fit-content` with `flex-wrap` resolved to the widest CHILD rather
   * than to the pair, which put the label above the figures at every phone
   * width — measured 41px tall where one line is 19. Letting the box take the
   * cell it is in gives the row somewhere to be.
   */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0 3px;
  white-space: nowrap;
  box-sizing: border-box;
  max-width: 100%;
  margin: 4px 0 0;
  padding: 2px 3px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  line-height: 1.25;
}

.board-subscore small {
  font-size: 8px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-3);
  white-space: nowrap;
}

.board-subscore b {
  font-size: 10px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-2);
}

/* A walkover has no second score, only the reason. */
.board-subscore--note {
  padding: 2px 7px;
  border-color: color-mix(in srgb, var(--warning) 40%, var(--border));
  background: var(--warning-soft);
  color: var(--warning-ink);
  font-size: 10px;
  font-weight: 700;
}

/* Bookings, only where there were any. */
.board-table .col-cards { width: 118px; }

.board-cards__side {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-right: 7px;
}

.board-cards__side small { color: var(--text-3); font-size: 10.5px; }

.board-book--yellow,
.board-book--red {
  display: inline-block;
  min-width: 15px;
  padding: 0 4px;
  border-radius: 3px;
  color: #fff;
  font-size: 10.5px;
  font-weight: 800;
  line-height: 16px;
}

.board-book--yellow { background: var(--warning); color: #4a2c00; }
.board-book--red { background: var(--danger); }

/* ------------------------------------------------------------------ ties --- */

.board-tie__rule {
  margin: 0;
  padding: 8px 12px;
  border-bottom: 1px solid var(--divider);
  font-size: 12px;
  color: var(--text-3);
}

/* --------------------------------------------------------------- bracket --- */

.board-bracket {
  margin-bottom: 14px;
  border: 1px solid var(--border);
  border-top: 3px solid var(--tone, var(--brand));
  border-radius: var(--radius-md);
  background: var(--surface);
  overflow: hidden;
}

.board-bracket:last-child { margin-bottom: 0; }

.board-bracket__head {
  padding: 11px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--divider);
}

.board-bracket__head span {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .16em;
  color: var(--text-3);
}

.board-bracket__head h3 { margin: 2px 0 0; font-size: 15px; font-weight: 800; }

/*
 * The rounds, left to right, scrolled as one strip.
 *
 * A knockout is read in the direction it is played. Stacking the rounds
 * vertically to fit a phone would lose that entirely, so the strip slides
 * instead — the same gesture as every other wide table here.
 */
.board-bracket__rounds {
  display: flex;
  gap: 12px;
  padding: 14px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.board-round {
  flex: 0 0 auto;
  width: 218px;
  display: flex;
  flex-direction: column;
  gap: 9px;

  /* The column's ties are centred against the round beside it, so a two-tie
     round sits opposite the middle of the four-tie round it came from. */
  justify-content: center;
}

.board-round h4 {
  margin: 0 0 2px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .05em;
  color: var(--text-3);
  text-align: center;
}

.board-box {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  padding: 7px 9px;
}

.board-box.is-played { border-color: var(--tone, var(--brand)); }
.board-box.is-played { border-color: color-mix(in srgb, var(--tone, var(--brand)) 45%, var(--border)); }

.board-box > small {
  display: block;
  margin-bottom: 4px;
  font-size: 10.5px;
  color: var(--text-3);
}

.board-box__side {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 0;
  font-size: 12.5px;
}

.board-box__side span { min-width: 0; overflow-wrap: anywhere; }
.board-box__side b { font-variant-numeric: tabular-nums; color: var(--text-3); }
.board-box__side.is-won { font-weight: 800; }
.board-box__side.is-won b { color: var(--success); }

/* Which of two level sides went through, said on the diagram that shows it. */
.board-box__pens {
  margin: 5px 0 0;
  padding-top: 4px;
  border-top: 1px dashed var(--border);
  font-size: 10.5px;
  font-weight: 700;
  text-align: center;
  color: var(--text-3);
}

.board-champion {
  margin: 0;
  padding: 11px 14px;
  border-top: 1px solid var(--divider);
  background: var(--surface-2);
  background: color-mix(in srgb, var(--tone, var(--brand)) 8%, transparent);
  font-size: 14.5px;
  font-weight: 800;
  text-align: center;
}

.board-champion.is-waiting { background: var(--surface-2); color: var(--text-3); font-weight: 700; }

/* ----------------------------------------------------------------- modal --- */

/*
 * The full league table, on request.
 *
 * SIZED TO THE VIEWPORT IT IS ACTUALLY IN. `dvh` is the height a phone's browser
 * leaves after its own toolbars, which `vh` is not — with `vh` a dialog as tall
 * as the screen has its last row and its close button underneath the address
 * bar, on the device most of these readers are holding. `vh` stays first as the
 * fallback for anything that does not know `dvh`.
 *
 * Its own scroll, so a group of sixteen is read by scrolling the table rather
 * than by growing a dialog taller than the screen.
 */
.board-scrim {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(4, 6, 12, .62);
  animation: board-fade var(--duration-base, 220ms) ease;
}

.board-modal {
  position: fixed;
  z-index: 81;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(680px, calc(100vw - 24px));
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: 0 30px 70px rgba(4, 6, 12, .5);
  animation: board-rise var(--duration-base, 220ms) var(--ease-out, ease);
}

@keyframes board-fade {
  from { opacity: 0; }
}

@keyframes board-rise {
  from { opacity: 0; translate: 0 10px; }
}

.board-modal__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 15px 17px;
  border-bottom: 1px solid var(--divider);
}

.board-modal__title { margin: 0; font-size: 16px; font-weight: 800; }
.board-modal__sub { margin: 3px 0 0; font-size: 12px; color: var(--text-2); }

/*
 * A close button big enough to hit with a thumb.
 *
 * 40px, which is the smallest target that is reliably tapped rather than
 * grazed — this dialog is opened and closed on phones far more often than on
 * anything else.
 */
.board-modal__close {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-2);
  font: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

.board-modal__close:hover { background: var(--surface-2); color: var(--text); }

.board-modal__close:focus-visible {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--focus-ring, rgba(36, 87, 245, .24));
}

/* The scrolling part: the table, never the head or the note. */
.board-modal__body {
  padding: 15px 17px;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

/* Said out loud, because the order of the table is not what decides it. */
.board-modal__note {
  margin: 12px 0 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-3);
}

/* The page behind a dialog does not scroll. */
.board-body.is-locked { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .board-scrim, .board-modal { animation: none; }
}

/* ----------------------------------------------------------------- rules --- */

.board-rules {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 13.5px;
  color: var(--text-2);
}

/* ---------------------------------------------------------------- footer --- */

.board-foot {
  max-width: 1120px;
  margin: 0 auto;
  padding: 6px 16px 28px;
  text-align: center;
}

.board-updated { margin: 0; font-size: 12px; color: var(--text-3); }
.board-updated.is-stale { color: var(--warning-ink); font-weight: 700; }

.board-foot__mark {
  margin: 8px 0 0;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .18em;
  color: var(--text-3);
  opacity: .7;
}

/* ------------------------------------------------------------- wider room --- */

/*
 * A fixture on a phone.
 *
 * The row is eight columns wide and a phone is not, so something scrolls. What
 * must NOT scroll is the fixture itself — home, result, away — because a table
 * where the away team starts off the right-hand edge answers none of the
 * question somebody opened the page with. Measured at 414px: the five leading
 * columns come to 342px at these widths and the category and bookings sit past
 * the edge, where they are one small swipe away.
 */
@media (max-width: 560px) {
  .board-standing-grid { grid-template-columns: 1fr; }
}

/*
 * The pinned header, made shorter on a phone.
 *
 * It is worth its space — which tournament, and what is on now — but at 233px on
 * a small screen it was taking two thirds of the view before a fixture appeared.
 */
@media (max-width: 600px) {
  /*
   * The tools take a line of their own.
   *
   * Beside the title they were four controls sharing a phone's width with a
   * tournament's name, and flexbox solved it by squeezing the name to one
   * character per line — a header that reads as a fault rather than as a
   * heading. Measured at 414px with both crowd buttons showing.
   */
  .board-head__bar { flex-wrap: wrap; }
  .board-head__id { flex: 1 1 100%; }
  .board-head__tools { width: 100%; justify-content: flex-start; }

  .board-head__bar,
  .board-live,
  .board-nav { padding-left: 14px; padding-right: 14px; }

  .board-head__bar { padding-top: 11px; padding-bottom: 9px; }
  .board-title { font-size: 17px; }
  .board-date { font-size: 11.5px; }
  .board-live { font-size: 11.5px; }
  .board-nav { padding-top: 8px; padding-bottom: 9px; }
  .board-nav a { padding: 4px 10px; font-size: 11.5px; }
}

@media (max-width: 600px) {
  /*
   * A fixed layout, and a width the columns are told rather than asked for.
   *
   * Left to itself the browser sizes a column to its longest club name, and
   * "ดาวรุ่งยางตลาด สปอร์ตคลับ" pushed the away column past the edge again —
   * `min-width` sets a floor, not a ceiling. Fixed layout makes the numbers
   * below the actual widths; they add up to the 600px the table is given, of
   * which the first five (350px) fit a 414px phone with room to spare and the
   * last three are the part that scrolls.
   */
  .board-table--fixtures {
    table-layout: fixed;

    /* 30+44+100+62+100+46+100+82 — the columns below, added up. Any slack
       here is shared out among them and widens the five that must fit. */
    width: 558px;  /* 30+44+99+68+99+46+90+82 */
  }

  /*
   * The widths below are the whole cell, padding included.
   *
   * Cells are content-box by default, so the first attempt at this had every
   * column come out 18px wider than it was told and the table reached 744px
   * instead of 600 — the away club was pushed off the edge again by the very
   * rule meant to keep it on. Measured, not assumed.
   */
  .board-table--fixtures th,
  .board-table--fixtures td {
    box-sizing: border-box;
    padding: 9px 5px;
  }

  .board-table--fixtures .col-index { width: 30px; }
  .board-table--fixtures .col-time { width: 44px; font-size: 12px; }
  .board-table--fixtures .col-team { width: 99px; overflow-wrap: anywhere; }

  /*
   * Wide enough for the shoot-out box to stay on one line.
   *
   * The ten pixels come out of ประเภท, which is past the edge on a phone and
   * reached by the same swipe either way — where the result is the column
   * somebody is actually reading.
   */
  .board-table--fixtures .col-score { width: 68px; }
  .board-table--fixtures .col-group { width: 46px; white-space: normal; }
  .board-table--fixtures .col-type { width: 90px; white-space: normal; }
  .board-table--fixtures .col-cards { width: 82px; white-space: normal; }
  .board-score { min-width: 46px; padding: 3px 6px; }
}

/*
 * Small phones — 360px and below, which is most Android handsets held in a case.
 *
 * The page gives back its own margins rather than taking them out of the
 * fixture, because what a reader came for is a club name, a time and a score,
 * and 24px of padding either side is 24px those three do not get. Measured: a
 * 360px screen leaves 289px inside a card at the wider padding and 324px here,
 * which is the difference between the away club fitting and not.
 */
@media (max-width: 400px) {
  .board { padding-left: 8px; padding-right: 8px; }
  .board-card__head, .board-card__body { padding-left: 10px; padding-right: 10px; }
  .board-head__bar,
  .board-live,
  .board-nav { padding-left: 12px; padding-right: 12px; }

  .board-table--fixtures { width: 512px; }  /* 26+40+86+68+86+42+90+74 */
  .board-table--fixtures .col-index { width: 26px; }
  .board-table--fixtures .col-time { width: 40px; }
  .board-table--fixtures .col-team { width: 86px; }
  .board-table--fixtures .col-score { width: 68px; }
  .board-table--fixtures .col-group { width: 42px; }
  .board-table--fixtures .col-type { width: 90px; }
  .board-table--fixtures .col-cards { width: 74px; }
  .board-table--fixtures td { padding: 8px 4px; }
  .board-score { min-width: 0; padding: 3px 4px; font-size: 12px; }
}

/*
 * The smallest screens still in use — 320px, an iPhone SE of the first
 * generation or an older Android.
 *
 * Nothing is dropped, everything is trimmed: 260px of columns into the 267px
 * such a screen leaves. Below this the fixture simply scrolls like any other
 * wide table, which is honest — there is a width at which three club names and
 * a score cannot all be shown, and pretending otherwise means showing them too
 * small to read.
 */
@media (max-width: 340px) {
  .board-table--fixtures { width: 448px; }  /* 22+34+73+64+73+38+82+62 */
  .board-table--fixtures .col-index { width: 22px; }
  .board-table--fixtures .col-time { width: 34px; font-size: 11px; }
  .board-table--fixtures .col-team { width: 73px; font-size: 12px; }
  .board-table--fixtures .col-score { width: 64px; }
  .board-table--fixtures .col-group { width: 38px; }
  .board-table--fixtures .col-type { width: 82px; }
  .board-table--fixtures .col-cards { width: 62px; }
  .board-table--fixtures td { padding: 8px 3px; }
  .board-score { font-size: 11.5px; padding: 3px 3px; }
}

@media (min-width: 700px) {
  .board-body { font-size: 15.5px; }
  .board-title { font-size: 23px; }
  .board-head__bar { padding: 18px 20px 14px; }
  .board-card__head, .board-card__body { padding-left: 20px; padding-right: 20px; }
  .board-table { font-size: 13.5px; }
}

/*
 * A reader who has asked for less movement gets less movement.
 *
 * The pulsing row and the breathing dot are decoration on top of information
 * that is already there in words and colour, so removing them costs nothing.
 */
@media (prefers-reduced-motion: reduce) {
  .board-live__dot,
  .board-live__vs,
  .board-row.is-live,
  .board-score--live {
    animation: none;
  }
}

/*
 * Printing.
 *
 * A fixture list gets pinned to a wall at every tournament, and the browser's
 * own print is how that happens. What goes on the wall is the tables — not the
 * navigation, not the find box, and not a coloured header bar that empties a
 * cartridge.
 */
@media print {
  .board-head, .board-nav, .board-find, .board-chips, .board-act, .board-foot__mark {
    display: none !important;
  }

  .board-body { background: #fff; color: #000; font-size: 11px; }
  .board-card { break-inside: avoid; box-shadow: none; border-color: #999; }

  /*
   * A heading goes with what it is the heading of.
   *
   * The same fault the organiser's sheet had: the cards refuse to be split, so
   * the browser broke directly after a title instead and printed it alone at
   * the foot of a page. This page is printed by visitors with no way to report
   * it, so it gets the fix at the same time rather than the next time somebody
   * happens to notice.
   */
  .board-section > h2,
  .board-card__head,
  .board-title {
    page-break-after: avoid;
    break-after: avoid;
  }
  .board-scroll, .board-bracket__rounds { overflow: visible !important; }
  .board-table { white-space: normal; }
}

/* --- โพสต์ขึ้นจอ LIVE ------------------------------------------------------- */

/*
 * The crowd's side of the broadcast: a message that goes on the screen, and
 * who everybody is cheering for.
 *
 * Both dialogs reuse .board-modal, which already sizes itself to the viewport
 * a phone actually has and scrolls its own body. What is added here is the
 * form inside one of them.
 */

.board-act--post {
  background: rgba(255, 255, 255, .3);
  border-color: rgba(255, 255, 255, .6);
}

.board-act--poll { background: rgba(255, 255, 255, .18); }

/* A wider dialog, because the club picker is a grid rather than a list. */
.board-modal--post { width: min(560px, calc(100vw - 24px)); }

.board-field-label {
  display: block;
  margin-bottom: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-2);
}

.board-post__text {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  line-height: 1.55;
  resize: vertical;
}

.board-post__text:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--focus-ring, rgba(36, 87, 245, .24));
}

.board-post__count {
  margin: 6px 0 0;
  font-size: 11.5px;
  color: var(--text-3);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Over the line limit is a refusal waiting to happen, so it is said early. */
.board-post__count.is-over { color: var(--danger); font-weight: 700; }

/* --- The club picker ------------------------------------------------------- */

.board-post__poll {
  margin-top: 14px;
  border: 1px solid color-mix(in srgb, var(--success) 45%, var(--border));
  border: 1px solid var(--success);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.board-post__pick {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 11px 12px;
  border: 0;
  background: var(--success-soft);
  color: var(--success);
  font: inherit;
  font-size: 13px;
  font-weight: 800;
  text-align: left;
  cursor: pointer;
}

.board-post__pick small { font-weight: 600; opacity: .85; overflow-wrap: anywhere; }

.board-post__teams {
  max-height: 260px;
  overflow-y: auto;
  padding: 10px 12px;
  border-top: 1px solid var(--divider);
}

.board-team-group + .board-team-group { margin-top: 12px; }

.board-team-group h4 {
  margin: 0 0 7px;
  font-size: 11.5px;
  font-weight: 800;
  color: var(--text-3);
}

.board-team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 6px;
}

.board-team {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
  overflow-wrap: anywhere;
}

/* A radio drawn rather than used: the whole tile is the target on a phone. */
.board-team__dot {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-radius: 50%;
}

.board-team.is-picked {
  border-color: var(--success);
  background: var(--success-soft);
  color: var(--success);
  font-weight: 700;
}

.board-team.is-picked .board-team__dot {
  border-color: var(--success);
  box-shadow: inset 0 0 0 3px var(--success-soft), inset 0 0 0 7px var(--success);
}

/* --- The share gate and the picture ---------------------------------------- */

/* The picture and the lock in front of it, as one block. */
.board-post__attach { margin-top: 16px; }

.board-post__gate {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 9px;
  margin-bottom: 9px;
  padding: 11px 12px;
  border: 1px solid var(--warning);
  border-radius: var(--radius-sm);
  background: var(--warning-soft);
  color: var(--warning-ink);
  font-size: 12.5px;
  font-weight: 700;
}

/* Unlocked: the same box, saying so, so the change is visible where it happened. */
.board-post__gate.is-open {
  border-color: var(--success);
  background: var(--success-soft);
  color: var(--success);
}

.board-post__lock { min-width: 0; overflow-wrap: anywhere; }

/*
 * A field that is greyed out rather than absent.
 *
 * It is the thing being worked towards; hiding it until the share happens made
 * the button above it look like it did nothing.
 */
.board-post__input:disabled {
  opacity: .45;
  cursor: not-allowed;
}

.board-post__share {
  margin-left: auto;
  padding: 7px 12px;
  border: 1px solid var(--warning);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--warning-ink);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}

.board-post__input { width: 100%; font: inherit; font-size: 12.5px; color: var(--text-2); }
.board-post__note { margin: 6px 0 0; font-size: 11.5px; color: var(--text-3); }

.board-post__says {
  margin: 12px 0 0;
  min-height: 18px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--brand);
}

.board-post__empty {
  margin: 0;
  padding: 18px;
  color: var(--text-3);
  font-size: 13px;
  text-align: center;
}

/* --- The dialog's own footer ----------------------------------------------- */

.board-modal__foot {
  display: flex;
  justify-content: flex-end;
  gap: 9px;
  padding: 12px 17px;
  border-top: 1px solid var(--divider);
}

.board-btn {
  min-height: 40px;
  padding: 0 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

.board-btn--send {
  border-color: var(--success);
  background: var(--success);
  color: #fff;
}

.board-btn--send[disabled] {
  opacity: .5;
  cursor: not-allowed;
}

/* --- The result ------------------------------------------------------------ */

.board-vote {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) 96px;
  align-items: center;
  gap: 9px;
  padding: 7px 0;
  font-size: 13px;
}

.board-vote__rank {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-3);
  font-size: 11px;
  font-weight: 800;
}

.board-vote__name { min-width: 0; overflow-wrap: anywhere; }

.board-vote__bar {
  grid-column: 2;
  height: 9px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
}

.board-vote__bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--brand);
  transition: width var(--duration-slow, 420ms) var(--ease-out, ease);
}

.board-vote__count {
  font-size: 11.5px;
  color: var(--text-3);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The one this visitor chose, so a result is also an answer about them. */
.board-vote.is-mine .board-vote__name { color: var(--success); font-weight: 800; }
.board-vote.is-mine .board-vote__rank { background: var(--success); color: #fff; }

/*
 * The list while the result is not announced.
 *
 * No rank column and no bar, because there is no order yet to show — the clubs
 * are listed as clubs. The only mark is on the row this visitor chose, which is
 * their own vote rather than anybody's standing.
 */
.board-vote--quiet { grid-template-columns: minmax(0, 1fr) auto; }
.board-vote--quiet .board-vote__count { color: var(--success); font-weight: 700; }
.board-vote.is-mine .board-vote__bar i { background: var(--success); }

@media (max-width: 560px) {
  /* The name over its own bar: three columns do not fit a phone. */
  .board-vote { grid-template-columns: 24px minmax(0, 1fr); }
  .board-vote__bar { grid-column: 1 / -1; }
  .board-vote__count { grid-column: 1 / -1; text-align: left; }
  .board-team-grid { grid-template-columns: 1fr; }
  .board-post__share { margin-left: 0; width: 100%; }
  .board-modal__foot .board-btn { flex: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .board-vote__bar i { transition: none; }
}

/* --- "เวลาจริง" under the printed kick-off ------------------------------------------
 *
 * Worked out once on the desk from the fixture on air; this page only draws
 * it. Red and pulsing so it is the first thing a club reads in the row, and
 * the printed time above it is kept — it says how late the day is running.
 */
.board-realtime {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 800;
  line-height: 1.15;
  color: #ff4d4f;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-shadow: 0 0 6px rgba(255, 77, 79, .55);
  animation: board-realtime-blink 1.4s ease-in-out infinite;
}
.board-realtime__label {
  display: block;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .04em;
  opacity: .85;
}
@keyframes board-realtime-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: .35; }
}
.board-row.is-live .board-realtime { color: #ffd166; text-shadow: 0 0 6px rgba(255, 209, 102, .55); }
@media (max-width: 640px) {
  .board-table--fixtures .col-time { width: 52px; }
  .board-realtime { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
  .board-realtime { animation: none; }
}

/* "แทน …" beside a replacement club in the group results. */
.board-standing__replaces { margin-left: 8px; font-size: 11px; font-weight: 600; color: var(--text-3); white-space: nowrap; }

/*
 * The (ถอนทีม) tag is not struck through with the name.
 *
 * The cell's line-through reaches every inline child; a tag drawn as an
 * inline BLOCK is an atomic box the decoration does not propagate into, so
 * the name keeps its strike and the tag reads whole.
 */
.board-tag--out { display: inline-block; text-decoration: none; }

/* Inside "แทน …": the replaced club's name is struck through; the word แทน is not. */
.replaces-name { text-decoration: line-through; text-decoration-color: currentColor; }

/* ---------------------------------------------------------------------------
 * Background music widget — a compact glass pill, bottom-left, that keeps out
 * of the way. Play/pause with a live equaliser, six dots to pick a track, and
 * a speaker to mute. Sized down for phones; respects the safe-area inset so it
 * never sits under an iPhone's home bar. @see music.js
 * ------------------------------------------------------------------------- */
.board-music {
  position: fixed;
  left: 14px;
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: calc(100vw - 28px);
  padding: 6px 10px;
  border-radius: var(--radius-pill, 999px);
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  border: 1px solid var(--border);
  box-shadow: 0 6px 22px rgba(8, 15, 40, 0.16);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0.62;
  transition: opacity 0.2s ease;
  user-select: none;
}
.board-music:hover,
.board-music:focus-within,
.board-music.is-playing { opacity: 1; }

/* Round buttons share one look. */
.board-music__btn,
.board-music__mute {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: #fff;
  background: var(--brand-gradient, linear-gradient(135deg, #2457f5, #123eea));
  -webkit-tap-highlight-color: transparent;
}
.board-music__mute {
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 14px;
  line-height: 1;
}
.board-music__btn:focus-visible,
.board-music__mute:focus-visible,
.board-music__dot:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* Play triangle when paused, equaliser when playing. */
.board-music__ico {
  width: 0; height: 0;
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent currentColor;
  margin-left: 2px;
}
.board-music.is-playing .board-music__ico { display: none; }

.board-music__eq { display: none; align-items: flex-end; gap: 2px; height: 14px; }
.board-music.is-playing .board-music__eq { display: inline-flex; }
.board-music__eq i {
  width: 3px;
  background: currentColor;
  border-radius: 1px;
  animation: board-eq 0.9s ease-in-out infinite;
}
.board-music__eq i:nth-child(1) { height: 40%; animation-delay: -0.2s; }
.board-music__eq i:nth-child(2) { height: 90%; animation-delay: -0.5s; }
.board-music__eq i:nth-child(3) { height: 60%; animation-delay: -0.1s; }
.board-music__eq i:nth-child(4) { height: 80%; animation-delay: -0.35s; }
@keyframes board-eq { 0%, 100% { transform: scaleY(0.35); } 50% { transform: scaleY(1); } }

/* The "ไข่ปลา" — one per track, filled for the one that is playing. */
.board-music__dots { display: inline-flex; align-items: center; gap: 6px; }
.board-music__dot {
  width: 9px;
  height: 9px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  background: color-mix(in srgb, var(--text-3) 45%, transparent);
  transition: transform 0.15s ease, background 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.board-music__dot:hover { background: var(--text-3); }
.board-music__dot.is-on {
  background: var(--brand);
  transform: scale(1.35);
}

@media (max-width: 640px) {
  .board-music { left: 10px; bottom: calc(10px + env(safe-area-inset-bottom, 0px)); gap: 6px; padding: 5px 8px; }
  .board-music__btn, .board-music__mute { width: 34px; height: 34px; }  /* bigger tap target on a phone */
  .board-music__dot { width: 11px; height: 11px; }                      /* easier to hit with a thumb */
}

@media (prefers-reduced-motion: reduce) {
  .board-music__eq i { animation: none; }
  .board-music__eq i:nth-child(odd) { height: 55%; }
}

/* ---------------------------------------------------------------------------
 * Header revisions (2026-09): the day/night switch pinned to the top-right
 * corner, a "คู่ปัจจุบัน" jump on the live line, and pinch-zoom locked like the
 * working screens. @see public/views/board.php
 * ------------------------------------------------------------------------- */

/* Zoom lock — the pan-x pan-y that stops the iOS pinch (the gesture events are
 * cancelled in board.php, the viewport line handles Android). Scrolling stays. */
.board-body { touch-action: pan-x pan-y; }

/* The switch sits in the corner of the centred bar, the same spot at every
 * width, so a narrow screen (where the tools row wraps below) does not carry it
 * off to the left. */
.board-head__bar { position: relative; }
.board-theme-corner {
  position: absolute;
  top: 12px;
  right: 18px;
  z-index: 3;
  margin: 0;
}
/* Keep the title clear of the corner switch, and leave room for the crowd
 * buttons beside it when the organiser turns them on. */
/*
 * THE RESERVED STRIP MUST BE AS WIDE AS THE SWITCH ACTUALLY IS.
 *
 * It was 52px against a switch 80px wide sitting 18px in from the edge, so on a
 * desktop — where the title and the tools share one line and the buttons run to
 * the right margin — "ส่งโพสต์ขึ้นจอ" ran 26px underneath the switch and the two
 * were drawn on top of each other. Measured at 1600 down to 700; a phone never
 * showed it, because there the tools wrap onto their own line below.
 *
 * Stated once as a variable so the strip and the switch cannot drift apart
 * again: 80 for the switch, 18 for its offset, 8 of air.
 */
.board-head__bar { --board-theme-space: 106px; }
.board-head__id { padding-right: var(--board-theme-space); }
.board-head__tools { padding-right: var(--board-theme-space); }

/* "คู่ปัจจุบัน" — a quiet pill at the end of the live line. */
.board-live__jump {
  margin-left: auto;
  flex: 0 0 auto;
  padding: 3px 12px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: var(--radius-pill, 999px);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.6;
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease;
}
.board-live__jump:hover { background: rgba(255, 255, 255, 0.28); }
.board-live__jump:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* The row it lands on gets a short glow, so the eye finds it after the scroll. */
.board-row.is-flash > td { animation: board-row-flash 1.6s ease-out 1; }
@keyframes board-row-flash {
  0%, 30% { background: color-mix(in srgb, var(--brand) 30%, transparent); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .board-row.is-flash > td { animation: none; }
}

@media (max-width: 640px) {
  .board-theme-corner { top: 11px; right: 14px; }
  .board-head__bar { --board-theme-space: 96px; }
}

/*
 * Below 600 the tools have the line to themselves.
 *
 * Measured: at 620 and above the title and the tools share one line and the
 * strip is what keeps the buttons out from under the switch; at 600 and below
 * the tools wrap onto their own line, the switch is no longer beside them, and
 * a strip reserved for it only pushes the row past the right edge of a phone —
 * which is how the page came to scroll sideways. 600 rather than the 640 used
 * above because 640 is still a shared line, and zeroing it there put the button
 * straight back under the switch.
 */
@media (max-width: 600px) {
  .board-head__tools { padding-right: 0; }
}

/* --- โฆษณา ------------------------------------------------------------------
 *
 * The sponsor panel: one picture at a time, floating over the page and staying
 * put as the reader scrolls.
 *
 * WHERE IT SITS depends on whether the screen has room to spare. A wide desktop
 * has a broad empty margin either side of the 1120px sheet, and that is where
 * the organiser asked for it — beside the list, out of the way of every word.
 * Anything narrower has no margin at all, so it takes the free bottom corner
 * instead: the right one, because the music player holds the left.
 *
 * THE PICTURE IS NEVER CROPPED — `contain`, always — and the card takes the
 * shape of whatever is in it, the height written by the script from the file's
 * own proportions and moving with the fade. The one thing that overrides that
 * is a very tall file on a short screen: the card stops at part of the screen's
 * height rather than becoming the screen.
 *
 * @see public/assets/js/ads.js
 */
.board-ad {
  position: fixed;
  z-index: 38;               /* over the page, under the music and the drawers */
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));

  /* Never more than a third of a small screen, whatever was asked for. */
  width: min(var(--ad-w, 200px), 34vw, 180px);

  border-radius: 14px;
  overflow: hidden;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border: 1px solid var(--border);
  box-shadow: 0 10px 30px rgba(8, 15, 40, .18);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);

  /* Opacity only: the desktop rule below centres it with a transform, and a
     keyframe that also wrote transform would throw that away. */
  animation: board-ad-in .45s ease both;
}

@keyframes board-ad-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/*
 * The margin beside the sheet, once there is one worth having.
 *
 * 1400px is where (screen − 1120) / 2 leaves something a picture can be read
 * in. Below it the card stays in the corner rather than being squeezed into a
 * strip or laid over the text.
 */
@media (min-width: 1400px) {
  .board-ad {
    left: 16px;
    right: auto;
    top: 50%;
    bottom: auto;
    transform: translateY(-50%);
    width: min(var(--ad-w, 200px), calc((100vw - 1120px) / 2 - 32px));
  }
}

.board-ad__stack {
  position: relative;
  width: 100%;
  height: 0;                 /* the script writes it from the first picture */
  max-height: 42vh;          /* a tall poster is not allowed to become the page */
  transition: height .38s ease;
}

.board-ad__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;       /* the whole picture, never a crop */
  opacity: 0;
  transition: opacity .6s ease;
}

.board-ad__img.is-on { opacity: 1; }

/*
 * The way out.
 *
 * Small, because it is not what the panel is for; big enough to hit with a
 * thumb, because a reader who cannot dismiss an advert covering the score is a
 * reader who leaves the page.
 */
.board-ad__close {
  position: absolute;
  top: 5px;
  right: 5px;
  z-index: 2;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(10, 14, 30, .55);
  color: #fff;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: .72;
  transition: opacity .2s ease;
  -webkit-tap-highlight-color: transparent;
}

.board-ad__close:hover,
.board-ad__close:focus-visible { opacity: 1; }

.board-ad__close:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .board-ad { animation: none; }
  .board-ad__stack,
  .board-ad__img { transition: none; }
}

/* A club's logo before its name on the fixtures (20 Oct 2026): a fixed small
   square, the picture contained — never stretched, never pushing the row. */
.board-logo {
  display: inline-block;
  width: 22px;
  height: 22px;
  margin-right: 8px;
  vertical-align: -5px;
  object-fit: contain;
  flex: none;
}
