/* static/css/labscore.css */
:root {
  --bl-green: #1d9e75;   /* flask, primary actions, active nav */
  --bl-ink:   #2c2c2a;   /* body text */
  --bl-mint:  #e1f5ee;   /* row highlights, selected states */
  --bl-amber: #fac775;   /* needs-attention marker. Background only: it fails AA as text */
  --bl-green-dark: #147153; /* the AA-safe green for text on white */
  --line: #c9ced6;
  --ink-2: #5b6067;      /* metadata and column labels: 6.2:1 on white */
  --wash: #f7f9fa;       /* the board's header strip */
  --tap: 44px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 17px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--bl-ink);
  background: #fff;
}

main { padding: 16px; max-width: 720px; margin: 0 auto; }

/* Header: mark, LabScore, event name. Short, because the score form should be
   the tallest thing on the page. */
.chrome {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 16px; border-bottom: 2px solid var(--bl-green);
}
.chrome .mark { height: 28px; width: auto; }
.chrome .product { font-weight: 700; letter-spacing: 0.01em; }
.chrome .event { color: var(--bl-ink); opacity: 0.75; font-size: 15px; }
.chrome .signout { margin-left: auto; }
.chrome .signout button {
  background: none; border: 1px solid var(--line); border-radius: 6px;
  min-height: var(--tap); padding: 0 12px; color: var(--bl-ink); font-size: 15px;
}

/* Login is the one screen with the lockup. */
body.login main, body.plain main { max-width: 420px; padding-top: 32px; text-align: center; }
body.login .lockup { width: 235px; height: auto; }
body.login h1 { font-size: 34px; margin: 12px 0 2px; }
body.login .tagline { margin: 0 0 24px; opacity: 0.8; }
body.login form { text-align: left; }

/* The sign-in screen is the front door, so it carries the brand green rather
   than the plain white every other screen uses.

   Everything readable sits on the white card, which is not decoration: the
   lockup is a transparent PNG whose "Business" is set in --bl-ink, so on a green
   field it would all but disappear, and the form's labels and AA contrast are
   only guaranteed against white. The colour goes behind the card, never under
   the type.

   `margin: auto` on the card rather than centring the flex container: when the
   content grows taller than the viewport, which is what happens the moment a
   phone keyboard opens over this form, auto margins collapse and the top stays
   reachable. Centring with justify-content clips it instead.

   The error pages share `body.plain` and deliberately stay white. A judge who
   has just lost a score is not who this flourish is for. */
body.login {
  min-height: 100vh;
  display: flex;
  background: linear-gradient(165deg, var(--bl-green) 0%, #0f5c43 100%);
}
body.login main {
  margin: auto;
  width: 100%;
  padding: 24px 16px;
}
body.login .card {
  background: #fff;
  border-radius: 16px;
  padding: 28px 22px 30px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.16);
}

label { display: block; font-weight: 600; margin: 14px 0 4px; }
/* `input[type=text]` matches the ATTRIBUTE, not the effective type, so an input
   written without a type attribute matched nothing here and lost the 44px tap floor
   along with everything else. Selecting by exclusion means a field cannot silently
   opt out of the mobile rules by omitting an attribute. */
input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=submit]):not([type=button]),
select, textarea {
  width: 100%; min-height: var(--tap); font-size: 17px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 8px;
}

/* Every button that is not `.primary`, the sign-out or a stepper had no rule at
   all and rendered as a 21px browser default. That put "Freeze standings",
   "Lock scoring", "Save" and "Delete" at the same size, in the same shape, with
   no colour between them: the two controls that change what a whole room sees
   looked exactly like the one that saves a table number. `.primary` still wins
   every shared property here on specificity. */
button {
  /* var(--tap), not a smaller number of my own: these are real controls on a
     screen an organizer uses standing up, and 40px looked tidier while quietly
     breaking the floor the rest of the sheet keeps. */
  min-height: var(--tap); padding: 0 14px;
  font: inherit; font-size: 15px; font-weight: 600;
  color: var(--bl-ink); background: #fff;
  border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
}
/* Destructive actions are outlined, never filled: a filled red button invites
   the tap it is warning about. */
button.danger { color: #7d1f1d; border-color: #b5322f; }

/* No focus style existed anywhere in this sheet. It matters most on `.teamlist a`,
   which is an undecorated link on a 56px row, and on the steppers. */
:focus-visible {
  outline: 3px solid var(--bl-green-dark); outline-offset: 2px; border-radius: inherit;
}

/* For text that a screen reader needs and the eye does not. The scored state on
   the team picker is CSS plus an aria-hidden tick, so without this a judge using
   a screen reader hears every team as identical. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

button.primary, a.primary {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); width: 100%; margin-top: 18px;
  /* --bl-green-dark, not --bl-green: white on #1d9e75 is 3.39:1 and fails AA for
     button text. White on #147153 is about 5.97:1. The lighter green stays for
     borders and fills, where the 3:1 UI-component threshold applies. */
  background: var(--bl-green-dark); color: #fff; border: 0; border-radius: 10px;
  font-size: 18px; font-weight: 600; text-decoration: none;
}

.error, .flash, .statebanner { border-radius: 8px; padding: 10px 12px; margin: 12px 0; }
.error { background: #fdeaea; border: 1px solid #b5322f; color: #7d1f1d; text-align: left; }
.flash { background: var(--bl-mint); border: 1px solid var(--bl-green); list-style: none; }
/* Neither a success nor a failure: a round being closed is just how things are.
   Red said something had gone wrong, which sent judges looking for the organizer. */
.statebanner { background: #f3f4f6; border: 1px solid var(--line); text-align: left; }

/* A closed round on the judge's home screen: the same shape as the primary
   button so the list still reads as a list, with none of its affordance. */
.closedround {
  display: flex; align-items: center; gap: 12px;
  min-height: var(--tap); margin-top: 18px; padding: 0 14px;
  border: 1px dashed var(--line); border-radius: 10px;
  color: var(--bl-ink); font-size: 18px; font-weight: 600; background: #fafafa;
}
.closedround .state {
  margin-left: auto; font-size: 14px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; opacity: 0.65;
}

/* Team picker: big tap targets, one per team. */
.teamlist { list-style: none; padding: 0; margin: 0; }
/* `.row-static` is the same row with no link inside it, used when the round is
   closed so nothing on the page invites a tap that would 403. */
.teamlist a, .teamlist .row-static {
  display: flex; align-items: center; gap: 12px;
  min-height: 56px; padding: 10px 14px; margin-bottom: 8px;
  border: 1px solid var(--line); border-radius: 10px;
  text-decoration: none; color: var(--bl-ink); font-size: 19px;
}
.teamlist .row-static { opacity: 0.65; background: #fafafa; }
/* A judge walking the room is looking for what is LEFT. Filling the scored rows
   with mint and a green border made the finished ones the loud ones, so the eye
   landed on the work already done. The tick still marks them; it just stops
   shouting. Unscored rows keep the full ink weight and are what the page
   presents first. */
.teamlist a.scored { background: #fff; border-color: var(--line); opacity: 0.55; }
.teamlist .tablenum { margin-left: auto; opacity: 0.7; font-size: 16px; }
.teamlist .tick { color: var(--bl-green-dark); font-weight: 700; }

/* The score form. */
/* The table number was inheriting the h1's 34px/700, so "Zeta Table 6" read as
   one four-word name. The team is what the judge is confirming; the table number
   is how they found the table they are already standing at. */
h1 .tablenum { display: block; font-size: 15px; font-weight: 400; opacity: 0.7; }

.criterion { border-top: 1px solid var(--line); padding: 14px 0; }
.criterion h2 { font-size: 19px; margin: 0 0 2px; }
/* 16px, above the "of 25" hint. At 15px the descriptor, which tells a judge how
   to score, was set smaller than the hint, which tells them nothing they cannot
   see in the input's own max. */
.criterion .descriptor { margin: 0 0 10px; font-size: 16px; opacity: 0.85; }
.criterion details summary { min-height: var(--tap); display: flex; align-items: center;
  color: var(--bl-green-dark); font-weight: 600; cursor: pointer; }
.criterion .anchors { list-style: none; padding: 0; margin: 6px 0 0; font-size: 15px; }
.criterion .anchors li { display: flex; gap: 10px; padding: 4px 0; }
.criterion .anchors .pts { font-weight: 700; min-width: 2.2em; }

.stepper { display: flex; align-items: center; gap: 10px; }
.stepper button {
  width: 52px; height: 52px; font-size: 26px; line-height: 1;
  border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--bl-ink);
}
.stepper input {
  /* width and font-size collide with the input rule above. Its five :not()
     exclusions each add to specificity, so it now outranks a plain class+element
     selector like this one; without !important here the number box would stretch
     to full width at the 17px mobile default instead of staying a fixed 92x52. */
  width: 92px !important; height: 52px; text-align: center; font-size: 24px !important;
  font-weight: 700;
}
.stepper .hint { font-size: 15px; opacity: 0.7; white-space: nowrap; }

/* The total and the submit are pinned to the bottom of the screen. Before this
   the form was 1,674px tall on a 390px phone: the running total sat at y=1373
   and the first stepper at y=214, so there was no scroll position where a judge
   could see a score they were entering and the total it feeds. The submit was
   1,555px down, and "Back to the team list" sat 19px under it at 20px tall,
   which is a thumb's width from Submit and throws the whole form away. */
.scoreactions {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 10;
  padding: 10px 16px; background: #fff; border-top: 2px solid var(--bl-green);
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
}
/* The bar spans the window; its contents hold to the same measure as `main` so
   they line up with the form above on a laptop. */
.scoreactions-inner {
  display: flex; align-items: center; gap: 12px;
  max-width: 720px; margin: 0 auto;
}
.scoreactions-inner > * { margin: 0; }
.scoreactions .runningtotal { font-size: 19px; font-weight: 700; flex: 0 0 auto; }
.scoreactions .runningtotal .of { font-weight: 400; opacity: 0.7; }
.scoreactions .primary { width: auto; flex: 1 1 auto; margin-top: 0; max-width: 260px; }
/* Keeps the last of the form clear of the fixed bar. An explicit spacer rather
   than `form:has()`, which not every judge's phone will support. */
.scoreactions-spacer { height: 78px; }
/* What the computed criteria add, said where the total is, because a judge who
   reads "of 50" needs to know the other 50 is coming. */
.computed-note { font-size: 15px; opacity: 0.85; margin: 10px 0 0; }
.penalty { display: flex; gap: 12px; align-items: flex-start; margin-top: 16px;
  background: var(--bl-mint); padding: 12px; border-radius: 10px; }
/* The label is the tap target, not the box: a 44px row is easy to hit standing up,
   and a 44px checkbox looks broken. */
.penalty label { display: flex; align-items: center; min-height: var(--tap); margin: 0; }
.penalty input { width: 28px; height: 28px; flex: 0 0 28px; margin-top: 8px; }

/* Breadcrumbs and the back link every admin screen carries. Flex-wrap so the
   trail wraps rather than overflows on a phone, and the separator is a
   character, not a border, so it wraps cleanly with the text around it. */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px;
  margin: 0; padding: 0; font-size: 14px; opacity: 0.85;
}
.crumbs a { color: var(--bl-green-dark); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.crumbs .crumb-sep { opacity: 0.6; }
.backlink { margin: 4px 0 16px; }
.backlink a {
  display: inline-flex; align-items: center; min-height: var(--tap);
  color: var(--bl-green-dark); text-decoration: none; font-weight: 600;
}
/* The sibling links kept on each other's admin pages (Challenges, Teams,
   Assignments): one row, in the same place, below the crumbs. */
.lateral { margin: 0 0 16px; font-size: 15px; }
.lateral a { color: var(--bl-green-dark); font-weight: 600; }
.sub { margin: 0 0 16px; color: var(--ink-2); font-size: 15px; }
.challenge-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 8px 0; }

/* The assignment grid: one chip per team, wrapping, instead of a bold block per
   team. A filled row reads at a glance and a 22px box is hittable. */
.assigngrid { display: flex; flex-wrap: wrap; gap: 8px 12px; margin: 10px 0 0; }
.assigngrid label {
  display: flex; align-items: center; gap: 8px;
  flex: 1 1 260px; min-height: var(--tap); margin: 0; padding: 4px 10px;
  font-weight: 400; font-size: 15px;
  border: 1px solid var(--line); border-radius: 8px;
}
.assigngrid input[type=checkbox] { width: 22px; height: 22px; flex: 0 0 22px; margin: 0; }
/* Where :has() is unsupported the grid still works; it just does not tint. */
.assigngrid label:has(input:checked) { background: var(--bl-mint); border-color: var(--bl-green); }
.assigngrid label:has(input:disabled) { opacity: 0.6; }

/* Tables: leaderboard, coverage, admin lists. */
table { width: 100%; border-collapse: collapse; font-size: 16px; }
th, td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--line); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* The board: the main leaderboard and the per-round board.

   It reads as one object in a card rather than a bare grid. Rank and total are
   what a leaderboard is read for, so they carry the weight; the per-round
   breakdown is metadata. The total also carries a bar restating it, which is the
   fastest way to compare a field at a glance.

   The bar is decoration and says nothing the number does not. That is why a team
   with no score gets no bar at all: a zero-width bar is exactly the empty-looking
   zero that the spec's "never rank an unjudged team as zero" rule exists to
   prevent. */
.boardcard { border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
table.board { width: 100%; border-collapse: collapse; font-size: 16px; }
.board thead th {
  font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-2); background: var(--wash);
  padding: 10px 12px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.board td { padding: 12px; border-bottom: 1px solid #eceff2; vertical-align: middle; }
.board tbody tr:last-child td { border-bottom: 0; }
.board td.rank, .board th.rank { width: 56px; padding-right: 0; }
.rankmark {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 34px; height: 34px; padding: 0 6px; border-radius: 999px;
  font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums;
}
/* A disc is an award, so the templates only draw one on a row that is actually
   finished. On a part-scored team it would hand out a podium place the board has
   not finished deciding. All three meet AA against white text. */
.rankmark.medal { color: #fff; }
.rankmark.m1 { background: var(--bl-green-dark); }
.rankmark.m2 { background: #6b7280; }
.rankmark.m3 { background: #8a5a33; }
.board .teamname { font-size: 19px; font-weight: 600; }
.board .marker { font-size: 13px; color: var(--ink-2); font-style: italic; }
.board td.num { font-variant-numeric: tabular-nums; color: var(--ink-2); font-size: 15px; }
.board td.total { width: 150px; }
.board .totalnum {
  display: block; font-size: 22px; font-weight: 700; color: var(--bl-ink); line-height: 1.1;
}
.bar {
  display: block; height: 5px; border-radius: 999px;
  background: #e8ebee; margin-top: 6px; overflow: hidden;
}
.bar i { display: block; height: 100%; background: var(--bl-green); border-radius: 999px; }
/* Lighter for a part-scored team. Its number is real, but it is a total over
   fewer rounds, and a full-strength bar reads as "beaten" rather than "not judged
   yet", which is a different claim entirely. */
.bar.partial i { background: #9ed4bf; }

@media (max-width: 700px) {
  /* Dropping the round columns is what keeps Total on screen. Scrolling them
     sideways instead pushed the one number this page is opened for off the right
     edge, where a judge checking the board on their phone never saw it. */
  .board .round { display: none; }
  .board td, .board thead th { padding: 12px 10px; }
  .board td.total { width: auto; }
}
.badge { font-size: 13px; padding: 1px 6px; border-radius: 10px; background: var(--bl-mint); }
/* Metadata ("1 judge") and state ("incomplete") are not achievements, so they do
   not get the mint that marks a real score. */
.badge.meta { background: var(--wash); color: var(--ink-2); font-weight: 500; }
.badge.quiet {
  background: var(--wash); color: var(--ink-2);
  border: 1px solid var(--line); font-weight: 600;
}
.badge.zero { background: var(--bl-amber); }   /* background, never text colour */
.badge.penalty-marker { background: var(--bl-amber); }   /* why a total fell */

/* The team editor on a phone.

   As five columns at 390px it gave the name input 46px, about four visible
   characters, and the college 60px, which will not show "Montgomery College" let
   alone let anyone correct it. Below 700px each row becomes a card: table number
   and team name share a line, the college gets its own, and the two actions sit
   beneath. Above 700px it stays the table it was. */
@media (max-width: 700px) {
  table.teamedit, .teamedit tbody, .teamedit tr, .teamedit td { display: block; }
  /* The per-field labels already name every input, so the column headers would
     only repeat them once the row is stacked. */
  .teamedit thead { display: none; }
  .teamedit tr {
    display: flex; flex-wrap: wrap; gap: 0 10px;
    border: 1px solid var(--line); border-radius: 10px;
    padding: 10px 12px; margin-bottom: 12px;
  }
  .teamedit td { border: 0; padding: 0; }
  .teamedit label { margin: 0 0 2px; font-size: 13px; }
  /* 5.25em is the width this field takes unprompted; 4.2em is that less a fifth.
     A table number is one or two digits and never needed the room. */
  .teamedit .cell-table { flex: 0 0 4.2em; }
  .teamedit .cell-name { flex: 1 1 120px; }
  /* A 100% basis forces the wrap, so the college always starts its own line
     instead of being squeezed in beside a long team name. */
  .teamedit .cell-college { flex: 1 1 100%; margin-top: 10px; }
  .teamedit .cell-save,
  .teamedit .cell-delete { flex: 0 0 auto; margin-top: 12px; }
}

@media (min-width: 900px) {
  main { max-width: 960px; }
}
