/* SnapSync coach portal.
   Brand colors from the SnapSync Sports lockup: crimson #9B1B30,
   light gray #C9CDD2, white. Ink #111418 and sports gray #6B7280 are the
   wordmark fills, used here for text. The page background is white pulled
   slightly toward the gray so a white card still separates.

   Width comes from the viewport, never the user agent. Under 480px is a
   phone (the Menu disclosure and 44px targets). 768px is a tablet
   portrait. 1024px and up is a desktop. Each number is explained at
   the rule that uses it. */

:root {
  --crimson: #9B1B30;
  /* Text and thin markers. Same as the brand crimson on a light page.
     Dark mode lifts this so a link is readable on the dark paper.
     Button fills stay --crimson, with white text. */
  --link: #9B1B30;
  --gray: #C9CDD2;
  --white: #ffffff;
  --ink: #111418;
  --sports: #6B7280;
  --paper: #f4f5f6;
  /* The film well. Darker than the page in both themes so the frame
     still reads as a screen. */
  --player: #111418;
  --veil: rgba(17, 20, 24, 0.4);
}

/* Dark surfaces. Chosen for contrast, not from a bench.
   Ink #f3f4f6 on paper #14161c is about 16:1, and on a card #1e222b
   about 14:1. Muted #c5cad3 on a card is about 9:1. The old sports
   gray #6B7280 on that card is about 3.3:1, under the 4.5:1 text
   minimum, so it is lifted. Brand crimson #9B1B30 on the paper is
   about 2:1, so links use #ff8b9d (about 8:1 on the paper, 7:1 on a
   card). White on the button crimson is about 8:1, so the button fill
   stays. Borders #6b7384 are about 3.8:1 on the paper and 3.3:1 on a
   card, which clears the 3:1 mark for a boundary. */
html[data-theme="dark"] {
  color-scheme: dark;
  --paper: #14161c;
  --white: #1e222b;
  --ink: #f3f4f6;
  --sports: #c5cad3;
  --gray: #6b7384;
  --link: #ff8b9d;
  --player: #050608;
  --veil: rgba(0, 0, 0, 0.62);
}

/* system follows the device. The same numbers as the dark block.
   A light device keeps :root. An explicit data-theme=light does not
   match this query, so it stays light even when the device is dark. */
@media (prefers-color-scheme: dark) {
  html[data-theme="system"] {
    color-scheme: dark;
    --paper: #14161c;
    --white: #1e222b;
    --ink: #f3f4f6;
    --sports: #c5cad3;
    --gray: #6b7384;
    --link: #ff8b9d;
    --player: #050608;
    --veil: rgba(0, 0, 0, 0.62);
  }
}

* { box-sizing: border-box; }

html { color: var(--ink); background: var(--paper); color-scheme: light; }
html[data-theme="light"] { color-scheme: light; }
html[data-theme="system"] { color-scheme: light dark; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  font-family: "Segoe UI", "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.45;
}

a { color: var(--link); }

.who { color: var(--sports); margin-right: 0.75rem; }

.count {
  display: inline-block;
  min-width: 1.2rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--crimson);
  color: var(--white);
  font-size: 0.8rem;
  line-height: 1.3;
  text-align: center;
}

.team-row { display: flex; gap: 0.5rem; align-items: center; margin: 0.4rem 0; }
.team-row input { flex: 1; max-width: 20rem; margin: 0; }

.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

.brand-bar { height: 4px; background: var(--crimson); }

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
  background: var(--white);
  border-bottom: 1px solid var(--gray);
}

.lockup { height: 40px; width: auto; display: block; }

/* The light lockup is dark text. Hide it when the page is dark, and
   show the light-text file instead. system follows the device, same
   cutoff as the color variables above. */
.lockup-dark, .splash-dark { display: none; }
html[data-theme="dark"] .lockup-light,
html[data-theme="dark"] .splash-light { display: none; }
html[data-theme="dark"] .lockup-dark,
html[data-theme="dark"] .splash-dark { display: block; }
@media (prefers-color-scheme: dark) {
  html[data-theme="system"] .lockup-light,
  html[data-theme="system"] .splash-light { display: none; }
  html[data-theme="system"] .lockup-dark,
  html[data-theme="system"] .splash-dark { display: block; }
}

.shell-tools { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* Sits in the top bar, not inside a Clerk mount. flex-shrink: 0 keeps
   Sign out on screen when the lockup is wide. z-index stays above any
   widget that still injects a layer over the header. */
.account-menu {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-left: auto;
  flex-shrink: 0;
  position: relative;
  z-index: 5;
}

.account-menu a {
  display: inline-block;
  white-space: nowrap;
  color: var(--link);
}

.inline-form { display: inline; }
/* Raw film in the library. A pill, not a clip count, so it cannot be
   read as Clips (0). Ink on paper is the same contrast as body text.
   Not a bench result. */
.raw-badge {
  display: inline-block;
  margin: 0 0.35rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  vertical-align: middle;
}

.shell-select select,
input[type="text"],
input:not([type]),
select,
.angle-label {
  font: inherit;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--gray);
  border-radius: 8px;
  padding: 0.45rem 0.6rem;
}

.nav {
  display: flex;
  gap: 1.25rem;
  padding: 0.6rem 1.25rem;
  background: var(--white);
  border-bottom: 1px solid var(--gray);
}

.nav a {
  color: var(--ink);
  text-decoration: none;
  padding-bottom: 0.15rem;
}

.nav a[aria-current="page"] {
  color: var(--link);
  border-bottom: 2px solid var(--link);
}

/* 87.5rem is 1400px at the 16px root. The old cap was 72rem (1152px),
   which left a 1280px laptop with a skinny column and a 1920px monitor
   mostly empty, so the game tables and the clip list looked scrunched.
   1400px fits a full queue table and a play list beside the film, and
   still insets the text on a 1920px window. A phone is under 480px, so
   this cap does not change that layout. Not a bench result. */
main {
  flex: 1 0 auto;
  width: 100%;
  max-width: 87.5rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 1.5rem;
}

/* Under the page, not over it. 0.75rem matches the build stamp so the
   two lines at the bottom are the same size. 11rem on the right is
   room for that stamp ("SnapSync 0.12 · abc1234"), which is fixed in
   the corner. Not a bench result. */
.site-footer {
  width: 100%;
  max-width: 87.5rem;
  margin: 0 auto;
  padding: 0.25rem 11rem 2rem 1.25rem;
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--sports);
}

.person-name { display: block; }

.person-email {
  display: block;
  color: var(--sports);
  font-size: 0.85rem;
}

.person-meta {
  display: block;
  margin-top: 0.15rem;
  color: var(--sports);
  font-size: 0.85rem;
}

.role-locked { font-weight: 600; color: var(--ink); }

.team-picks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
  margin: 0.4rem 0 0.8rem;
}

.team-picks label { display: flex; gap: 0.35rem; align-items: center; }

/* 44px is the same thumb target as the nav. The whole label is the
   hit area, so Light / Dark / System can be tapped on a phone.
   Not a bench result. */
.theme-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  margin: 0.4rem 0 0.8rem;
}

.theme-choices label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0;
  min-height: 44px;
}

.pending-invite {
  margin: 0.8rem 0 1.2rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--gray);
}

/* The join URL is long. 40rem keeps it in the same column as the form.
   180px is the QR segno draws at scale 4 for a join URL (about 45
   modules). White behind the code so a dark page still scans.
   Not a bench result. */
.invite-link-url { width: 100%; max-width: 40rem; }

.invite-link-result {
  margin: 1rem 0;
  padding: 0.9rem 1rem;
  border: 1px solid var(--gray);
  border-radius: 12px;
  background: var(--white);
}

.invite-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.6rem; }

.invite-qr { margin-top: 0.8rem; background: #ffffff; display: inline-block; padding: 0.4rem; }

.invite-qr svg { display: block; width: 180px; height: 180px; }

/* Bottom-right on every page, including the dashboard. Small and muted
   so it does not compete with the game list. Same string as /api/version. */
.build-stamp {
  position: fixed;
  right: 1rem;
  bottom: 0.65rem;
  margin: 0;
  z-index: 3;
  font-size: 0.75rem;
  color: var(--sports);
}

h1 { font-size: 1.7rem; line-height: 1.2; margin: 0.2rem 0 0.6rem; }

h2 { font-size: 1.05rem; margin: 0; }

.lede, .hint, .tech, .note, .metrics-note { color: var(--sports); }

.kicker {
  margin: 0;
  color: var(--link);
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.78rem;
}

/* 40rem keeps the intro near 70 characters. The page column is 1400px
   on a desktop; this sentence should not run the full width. The games
   list under it still uses the column. Not a bench result. */
.splash-block { text-align: center; max-width: 40rem; margin: 0 auto; padding: 1.5rem 0 1rem; }

.splash { height: 96px; width: auto; }

.cta, .btn {
  display: inline-block;
  font: inherit;
  text-decoration: none;
  border-radius: 8px;
  padding: 0.7rem 1.1rem;
  cursor: pointer;
}

/* .btn sets display, which would otherwise beat the hidden attribute. */
[hidden] { display: none !important; }

/* Dark mode sets --white to the card color (#1e222b) so cards stay
   dark. These buttons are filled with brand crimson, not a card, so
   the label has to stay #ffffff. The card color on #9B1B30 is under
   the 4.5:1 text minimum. White on that crimson is about 8:1 (see
   the dark-mode note at the top of this file). */
.cta, .btn-primary {
  background: var(--crimson);
  color: #ffffff;
  border: 1px solid var(--crimson);
}

.cta { font-size: 1.15rem; padding: 0.95rem 1.4rem; }

.btn-quiet {
  background: var(--white);
  color: var(--ink);
  border: 1px solid var(--gray);
}

.btn-primary:disabled {
  background: var(--gray);
  border-color: var(--gray);
  color: var(--white);
  cursor: not-allowed;
}

.metrics {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.75rem;
  margin: 1.5rem 0;
}

.metrics-note { grid-column: 1 / -1; margin: 0; }

.metric, .panel {
  background: var(--white);
  border: 1px solid var(--gray);
  border-radius: 12px;
  padding: 0.9rem 1rem;
}

.metric span { display: block; color: var(--sports); font-size: 0.85rem; }

.steps {
  display: flex;
  gap: 1rem;
  list-style: none;
  padding: 0;
  color: var(--sports);
}

.steps .on { color: var(--link); font-weight: 650; }

label { display: block; margin: 0.8rem 0 0.3rem; }

#game-name, #sync-method, #level, #opponent, #program-field, .angle-label { width: 100%; max-width: 36rem; }

.program-box, .team-label { margin: 0.4rem 0 0.8rem; }

.angle .file-list li { cursor: grab; }

/* The keep-open banner. Crimson border so it reads as a warning, not
   as the error list (that one is crimson text). The bar uses the same
   crimson. Height 0.85rem is large enough to see on a laptop at arm's
   length and still one line under the sentence. Not a bench result. */
.banner.stay {
  background: var(--white);
  border: 1px solid var(--link);
  color: var(--ink);
}

.banner.stay progress {
  display: block;
  width: 100%;
  height: 0.85rem;
  margin: 0.45rem 0 0.2rem;
  accent-color: var(--link);
}

#upload-progress { margin: 0.35rem 0 0; }

/* Resume upload. One card per angle. The pick block and the live
   block are not on screen together: resume.js hides the pickers
   when bytes start moving, so the coach does not see a grey button
   and a file input during the send. The bar height matches the
   upload banner (0.85rem). Not a bench result. */
.resume-angles { list-style: none; padding: 0; margin: 1rem 0; }

.resume-angle {
  background: var(--white);
  border: 1px solid var(--gray);
  border-radius: 12px;
  padding: 0.9rem 1rem 1rem;
  margin: 0 0 0.75rem;
}

.resume-angle h2 { margin: 0 0 0.35rem; font-size: 1.05rem; }

.resume-instruction { margin: 0.2rem 0 0.6rem; }

.resume-from { margin: 0.55rem 0 0; color: var(--sports); }

.resume-live-copy { margin: 0.2rem 0 0; }

.resume-angle progress {
  display: block;
  width: 100%;
  height: 0.85rem;
  margin: 0.45rem 0 0.2rem;
  accent-color: var(--link);
}

.resume-live-line { margin: 0.35rem 0 0; }

.resume-file { margin-top: 0.2rem; }

.game-state { font-weight: 650; }

dialog {
  border: 1px solid var(--gray);
  border-radius: 12px;
  padding: 1rem 1.2rem;
  max-width: 28rem;
  color: var(--ink);
}

dialog::backdrop { background: var(--veil); }

dialog .footer-actions { margin-top: 1rem; justify-content: flex-end; }

.levels { border: 1px solid var(--gray); border-radius: 12px; background: var(--white); padding: 0.6rem 1rem 1rem; }

.check { display: flex; align-items: center; gap: 0.45rem; margin: 0.35rem 0; }

.add-level { margin-top: 0.8rem; padding-top: 0.7rem; border-top: 1px solid var(--gray); }

.add-level-row { display: flex; gap: 0.5rem; align-items: center; max-width: 36rem; }

#custom-level { flex: 1; margin: 0; }

.angles { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }

.angle {
  background: var(--white);
  border: 1px solid var(--gray);
  border-radius: 12px;
  padding: 0.9rem 1rem 1rem;
  margin: 0;
}

.angle-head, .wizard-footer, .footer-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.drop {
  position: relative;
  border: 2px dashed var(--gray);
  border-radius: 12px;
  min-height: 140px;
  display: grid;
  place-items: center;
  text-align: center;
  gap: 0.4rem;
  padding: 1rem;
  margin-top: 0.6rem;
  background: var(--white);
}

.drop.hot { border-color: var(--link); }

/* The Choose files / Choose folder buttons open these. A no-JS submit
   cannot post the film (the inputs have no name), so the native control
   does not need to sit in the layout. */
.drop input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

.file-list { list-style: none; padding: 0; margin: 0.4rem 0 0; }

.file-list li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.15rem 0.75rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--gray);
}

.file-name { font-weight: 650; }

.file-meta, .script { color: var(--sports); }

.file-list li[data-state="blocked"] .file-name,
.warn-copy { color: var(--link); }

.banner {
  border-radius: 12px;
  padding: 0.7rem 1rem;
  margin: 0.6rem 0 1rem;
}

.banner.bad {
  background: var(--white);
  border: 1px solid var(--link);
  color: var(--link);
}

.banner ul { margin: 0; padding-left: 1.1rem; }

.wizard-footer {
  position: sticky;
  bottom: 0;
  background: var(--paper);
  border-top: 1px solid var(--gray);
  /* Right padding keeps Next clear of the build stamp in the corner. */
  padding: 0.8rem 11rem 0.8rem 0;
  margin-top: 1rem;
}

#run-bot { min-width: 16rem; }

html.js [data-step] { display: none; }

html.js [data-step].on { display: block; }

/* 14rem fits a label plus a short value ("Stanwood Youth Football")
   without the next card colliding. auto-fit then places two facts on
   a tablet and four or more on a desktop. Under 768px the narrow rule
   forces one column. Not a bench result. */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0.75rem;
  margin: 1rem 0;
}

.facts div {
  background: var(--white);
  border: 1px solid var(--gray);
  border-radius: 12px;
  padding: 0.7rem 0.8rem;
}

.facts dt { color: var(--sports); font-size: 0.85rem; }

.facts dd { margin: 0.15rem 0 0; }

.stages { list-style: none; padding: 0; margin: 0.5rem 0 0; }

.stages li { padding: 0.55rem 0; border-top: 1px solid var(--gray); }

.stages p { margin: 0.15rem 0 0; color: var(--sports); }

.state {
  display: inline-block;
  min-width: 5.5rem;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--sports);
}

.state-running { color: var(--ink, #111418); font-weight: 700; }

.state-done { color: var(--link); }

.state-failed { color: var(--link); font-weight: 700; }

.state-skipped { color: var(--sports); }

table.jobs { width: 100%; border-collapse: collapse; background: var(--white); }
/* Stand-in for the Clips link while the cut has not finished.
   Not a link. sports gray is the muted wordmark ink, on the white
   card. Not a bench result. */
.library-wait { color: var(--sports); cursor: default; }
.failure-detail { display: block; margin-top: 0.25rem; }

table.jobs th, table.jobs td {
  text-align: left;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--gray);
}

:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }

/* Program logo sits beside the game name. It is not drawn on the video. */
.clips-title { display: flex; align-items: center; gap: 0.6rem; }
.program-logo {
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: 6px;
  background: var(--white);
  flex: none;
}

/* Film on the left, play list on the right. 14rem holds the number,
   the start, the length, the fix icon, and Play once those words are
   an icon. 18rem is the cap below desktop so a 768px tablet keeps the
   film the wider column (about 420px) instead of splitting the window
   in half. The 1024px rule widens the list. Under 768px the list
   stacks under the film. One column of number / start / length.
   No quarters. Not a bench result. */
.viewer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(14rem, 18rem);
  gap: 1rem;
  align-items: start;
}

.clip-player {
  width: 100%;
  max-width: 100%;
  height: auto;
  max-height: 70vh;
  background: var(--player);
  border-radius: 8px;
}

.clip-transport { display: flex; gap: 0.5rem; margin-top: 0.6rem; }

/* Month and team on the library. They submit as a GET so the choice
   stays in the address bar. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-end;
  margin: 0 0 1rem;
}

.filters label { margin: 0; }

.filters select { display: block; margin-top: 0.2rem; min-width: 12rem; }

/* The cut-camera picker. A border groups the radios so they read as
   one choice on the upload page and inside a queue row. */
.primary-angle {
  border: 1px solid var(--gray);
  border-radius: 8px;
  margin: 0.6rem 0;
  padding: 0.4rem 0.7rem 0.5rem;
}
.primary-angle legend { font-weight: 650; padding: 0 0.3rem; }
.primary-angle label { display: block; margin: 0.25rem 0; }
.primary-angle .hint { margin: 0.3rem 0 0.2rem; }

.library-day { margin-bottom: 1rem; }

.library-day h2 { margin-bottom: 0.6rem; }

.edit-game { margin-top: 0.4rem; }

.edit-game summary { cursor: pointer; color: var(--link); }

.edit-game form {
  display: grid;
  gap: 0.15rem;
  max-width: 24rem;
  margin-top: 0.4rem;
}

.edit-game input { width: 100%; max-width: 24rem; }

.clip-menu {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 70vh;
  overflow: auto;
  background: var(--white);
  border: 1px solid var(--gray);
  border-radius: 8px;
}

.clip-menu button {
  width: 100%;
  display: grid;
  grid-template-columns: 3rem 1fr auto;
  gap: 0.5rem;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--gray);
  padding: 0.45rem 0.6rem;
  cursor: pointer;
}

.clip-menu button[aria-current="true"] {
  background: var(--paper);
  box-shadow: inset 3px 0 0 var(--link);
}

/* Number, start, and length share the row with Play and the fix icon.
   The icon is the way into the full-game editor for that play. It is
   not a bar: the old link spelled "Fix this clip" and, because every
   menu link was width 100%, that phrase took its own row on a phone
   and was clipped to "Fix thi" in the desktop list. */
.clip-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  border-bottom: 1px solid var(--gray);
}

.clip-row.is-current { background: var(--paper); box-shadow: inset 3px 0 0 var(--link); }

.clip-row > .clip-jump,
.clip-row > .fix-clip,
.clip-row > .clip-play { border-bottom: 0; }

.clip-jump {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) auto;
  gap: 0.5rem;
  align-items: center;
  padding: 0.45rem 0.6rem;
  text-decoration: none;
  color: var(--ink);
  min-width: 0;
}

.clip-num, .clip-time, .clip-len { white-space: nowrap; }

/* 2rem is a mouse target. The phone rule below grows every wrench to
   44px without growing the glyph into a bar. The words live on
   aria-label and title. The same control is the clip row, the clips
   header, the game page, the queue, the library, and Programs. */
a.fix-clip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 8px;
  color: var(--sports);
  text-decoration: none;
  vertical-align: middle;
  flex: none;
}

.clip-row > .fix-clip { margin-right: 0.15rem; }

a.fix-clip:hover,
a.fix-clip:focus-visible {
  color: var(--link);
  background: var(--paper);
}

/* 20px matches the width and height on the SVG. Those attributes, and
   the same rule in the page head, keep the glyph at 20px when this
   file is missing or still the previous image. A viewBox alone is
   drawn at 300 by 150. Not a bench result. */
.fix-icon {
  width: 20px;
  height: 20px;
  display: block;
}

.clip-row > .clip-play {
  width: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0.2rem 0.45rem 0.2rem 0;
  padding: 0.35rem 0.7rem;
  border: 0;
  border-radius: 8px;
  background: transparent;
  font: inherit;
  color: var(--link);
  cursor: pointer;
}

.clip-row > .clip-play:hover,
.clip-row > .clip-play:focus-visible { background: var(--paper); }

/* The old rule painted a crimson bar on whatever button was current.
   That button used to be the whole row. It is now the word Play, so
   the bar sat in front of the word and read as a stray mark. The row
   already carries the current-play bar. */
.clip-row > .clip-play[aria-current="true"] {
  box-shadow: none;
  background: var(--paper);
}

.clip-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }

.clip-menu a {
  width: 100%;
  display: grid;
  grid-template-columns: 3rem 1fr auto;
  gap: 0.5rem;
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--gray);
  padding: 0.45rem 0.6rem;
}

.clip-menu a[aria-current="true"] {
  background: var(--paper);
  box-shadow: inset 3px 0 0 var(--link);
}

/* Fix and label put the play list on the left. 14rem holds the play
   icon, the number, and the two times. 18rem is the cap below a
   laptop so a 768px tablet keeps the film the wider column. The
   1024px rule widens the list. The clips page stays film-then-list;
   these areas name the columns because the film is first in the
   markup. Under 768px the film stacks on top. Not a bench result. */
.fix-layout,
.label-editor.viewer {
  display: grid;
  grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr);
  grid-template-areas: "plays stage";
  gap: 1rem;
  align-items: start;
}
.fix-layout > .play-list,
.label-editor > .mark-column { grid-area: plays; }
.fix-layout > .viewer-stage,
.label-editor > .viewer-stage { grid-area: stage; }

/* Tables wider than the screen scroll inside this box instead of
   widening the page. */
.table-scroll {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.table-scroll table.jobs {
  width: max-content;
  min-width: 100%;
}

/* Menu control is for narrow screens. The links stay visible here. */
.nav-toggle { display: none; }

/* A closed <details> hides its body with content-visibility, so the
   header links had no height and spilled over the title. display:contents
   on the element does not fix that: Chrome still will not count the
   body. ::details-content is the box it actually hides. Showing that
   box puts Dashboard through Admin back in a row. .nav is a block so
   the row can use the full width; as a flex item it shrank to the word
   "Dashboard" and stacked the rest. 481px is one pixel past the 480px
   phone menu, so a 430px phone still uses the disclosure.
   Not a bench result. */
@media (min-width: 481px) {
  .nav { display: block; }
  .nav-menu:not([open])::details-content {
    content-visibility: visible;
    height: auto;
  }
  .nav-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 1.25rem;
  }
  .nav-links a { white-space: nowrap; }
}

.nav-menu:not([open]) > .nav-links,
.nav-menu[open] > .nav-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
}

.subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.9rem;
  margin: 0 0 1rem;
  padding-bottom: 0.15rem;
  border-bottom: 1px solid var(--gray);
}

.subnav a {
  color: var(--ink);
  text-decoration: none;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.subnav a[aria-current="page"] {
  color: var(--link);
  box-shadow: inset 0 -2px 0 var(--link);
}

.clip-num { font-weight: 700; }
.clip-time, .clip-len { color: var(--sports); font-variant-numeric: tabular-nums; }

/* Below a tablet portrait. 768px is the short side of a common tablet.
   One pixel under that, the player and the list stack and the metric
   cards go to one column: two columns on a 700px window squeeze the
   film. The phone menu is still the separate 480px rule. The old
   cutoff was 800px, which stacked a 768px tablet the same way as a
   phone. Not a bench result. */
@media (max-width: 767px) {
  .viewer, .fix-layout, .label-editor.viewer { grid-template-columns: 1fr; }
  /* Film first on a phone, list under it. The desktop areas put the
     list on the left; that order is too tall before the player. */
  .fix-layout,
  .label-editor.viewer { grid-template-areas: "stage" "plays"; }
  .clip-menu { max-height: none; }
  .angles, .metrics, .facts { grid-template-columns: 1fr; }
  .topbar { flex-wrap: wrap; }
  #run-bot { min-width: 0; }
  .wizard-footer { padding-right: 0; padding-bottom: 1.5rem; }
}

/* Tablet. 768px is that same portrait width, so the four Admin labels
   fit on one line and read as tabs instead of a wrapped list. width
   100% fills the column. min-width: max-content lets a team matrix
   that is still wider than the window scroll inside .table-scroll
   instead of squashing. Under 768px the base rule (max-content, at
   least 100%) is what a 375px phone keeps. Not a bench result. */
@media (min-width: 768px) {
  .subnav {
    flex-wrap: nowrap;
    gap: 0.15rem;
    align-items: flex-end;
    border-bottom: 1px solid var(--gray);
    padding-bottom: 0;
  }

  .subnav a {
    padding: 0.55rem 1rem 0.65rem;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: 8px 8px 0 0;
    margin-bottom: -1px;
    background: transparent;
  }

  .subnav a[aria-current="page"] {
    background: var(--white);
    border-color: var(--gray);
    color: var(--link);
    box-shadow: inset 0 2px 0 var(--link);
  }

  .table-scroll table.jobs {
    width: 100%;
    min-width: max-content;
  }
}

/* Desktop. 1024px is the small-laptop width: wide enough for the film
   and a 22rem play list side by side. 28rem is the list cap so a 1400px
   column still gives the film most of the width. The header inset uses
   the same 87.5rem column as main, plus main's 1.5rem padding, so the
   lockup lines up with the title on a 1920px monitor. On a 1280px
   window the calc drops under 1.5rem and max() keeps the padding.
   Not a bench result. */
@media (min-width: 1024px) {
  /* Clips stay film-then-list. Fix and label put the play list on the
     left, 18–24rem, so the film keeps the rest of a 1024px window.
     24rem is wide enough for a play icon and the two times. Not a
     bench result. */
  .viewer {
    grid-template-columns: minmax(0, 1fr) minmax(22rem, 28rem);
  }
  .fix-layout,
  .label-editor.viewer {
    grid-template-columns: minmax(18rem, 24rem) minmax(0, 1fr);
    grid-template-areas: "plays stage";
  }
  .fix-layout > .play-list,
  .label-editor > .mark-column { grid-area: plays; }
  .fix-layout > .viewer-stage,
  .label-editor > .viewer-stage { grid-area: stage; }

  .topbar,
  .nav {
    padding-left: max(1.5rem, calc((100% - 87.5rem) / 2 + 1.5rem));
    padding-right: max(1.5rem, calc((100% - 87.5rem) / 2 + 1.5rem));
  }

  /* Fit the table in the column. min-width: max-content (the tablet
     rule) was wider than a 1280px window, so Status sat past the edge.
     Sharing the width lets a long game name wrap and keeps every
     column on screen. A phone still uses max-content and scrolls.
     Not a bench result. */
  .table-scroll table.jobs {
    width: 100%;
    min-width: 100%;
  }

  main { padding-left: 1.5rem; padding-right: 1.5rem; }

  /* 11rem on the right stays: that is the room for the fixed build
     stamp, set on .site-footer above. Only the left edge moves so the
     copyright lines up with the title. */
  .site-footer { padding-left: 1.5rem; }
}

/* Phones are about 360–430 CSS pixels wide (a 375px SE, a 414px Plus,
   a 430px Pro Max). 480px is just past that, so the menu is closed
   before the header links wrap, and a 16px scrollbar does not push a
   430px phone back into the wide layout. Not a bench result.
   44px is the smallest control a thumb can hit reliably. */
@media (max-width: 480px) {
  body { overflow-x: hidden; }

  .lockup { max-width: 52vw; height: auto; max-height: 36px; }

  .topbar { padding: 0.55rem 0.9rem; gap: 0.5rem; }

  #account-link {
    max-width: 42vw;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .account-menu a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  main { padding: 1rem 0.9rem 1.25rem; }

  h1 { font-size: 1.45rem; overflow-wrap: anywhere; }

  .file-name, .person-name, .clips-title { overflow-wrap: anywhere; }

  /* The build stamp is fixed on a wide screen. On a phone it sits in
     the flow so it does not cover Save or the copyright line. */
  .build-stamp {
    position: static;
    padding: 0 0.9rem 0.75rem;
    text-align: right;
  }

  .site-footer { padding: 0.25rem 0.9rem 0.35rem; }

  .nav { display: block; padding: 0.2rem 0.9rem 0.45rem; }

  .nav-menu:not([open]) > .nav-links { display: none; }

  .nav-toggle {
    display: flex;
    align-items: center;
    min-height: 44px;
    cursor: pointer;
    font-weight: 650;
    list-style: none;
  }

  .nav-toggle::-webkit-details-marker { display: none; }

  .nav-menu[open] > .nav-links {
    display: flex;
    flex-direction: column;
    gap: 0;
    padding-bottom: 0.25rem;
  }

  .nav-links a {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  .btn, .cta, .clip-menu button, .fix-clip, .clip-menu a {
    min-height: 44px;
  }

  .btn, .cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .clip-transport, .footer-actions, .wizard-footer, .angle-head, .add-level-row, .team-row, .clip-actions {
    flex-wrap: wrap;
  }

  .clip-transport .btn { flex: 1 1 8rem; }

  .team-row input, .filters select, input, select, textarea { max-width: 100%; }

  .team-row input { width: 100%; }

  .filters { flex-direction: column; align-items: stretch; }

  .filters select, .filters label { min-width: 0; width: 100%; }

  .team-picks label { min-height: 44px; }

  input[type="checkbox"] { width: 1.5rem; height: 1.5rem; }

  dialog {
    width: calc(100vw - 1.5rem);
    max-width: calc(100vw - 1.5rem);
    margin: auto;
  }

  /* Every wrench, not only the one on a clip row. 44px is the same
     thumb target as Menu and Play. The glyph stays 20px so the hit
     area is the button, not a bar. Not a bench result. */
  a.fix-clip {
    width: 44px;
    min-width: 44px;
    height: 44px;
    min-height: 44px;
    flex: 0 0 44px;
    border-left: 0;
  }

  .clip-row .fix-icon { width: 20px; height: 20px; }

  .clip-row > .clip-play { min-width: 44px; min-height: 44px; }
}
