/* BikerVote ballot - the thing in your hand. Light plate surface, black
   rules, heavy numerals: a scrutineering tag, not a web card. */

:root{
  --plate:#EFEDE6; --ink:#0B0E10; --scrut:#F2C230;
  --flag:#C4341F; --steel:#7A848C; --line:#262E35;
  --ease-io:cubic-bezier(.4,0,.2,1);
}

/* Tell the browser this page is light on purpose - Samsung Internet and
   Chrome both offer a forced dark mode that would invert the plate look. */
:root{ color-scheme: light; }

@font-face{
  font-family:'ArchivoSubset';
  src:url('/static/fonts/archivo-900-subset.woff2') format('woff2');
  font-weight:900; font-display:swap;
  /* Only the glyphs the competition numbers use. */
  unicode-range:U+0030-0039,U+0041-005A;
}

*{ box-sizing:border-box; border-radius:0 !important; box-shadow:none !important; }

body{
  margin:0; background:var(--plate); color:var(--ink);
  font-family:'IBM Plex Sans',system-ui,-apple-system,sans-serif;
  font-size:16px; line-height:1.45;
  -webkit-text-size-adjust:100%;
  /* Clear the fixed submit bar so the last tag is never trapped under it. */
  padding-bottom:112px;
}

.wrap{ max-width:720px; margin:0 auto; padding:16px 12px 0; }

.eyebrow{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:11px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--steel); margin:0;
}

.masthead{ border-bottom:3px solid var(--ink); padding-bottom:12px; margin-bottom:22px; }
.masthead h1{
  font-family:'Archivo',system-ui,sans-serif; font-weight:900;
  text-transform:uppercase; letter-spacing:-.02em;
  font-size:clamp(26px,7vw,40px); line-height:1.05; margin:4px 0 0;
}
/* Fixed-height rail: a slow logo load must not shift the tag list. */
.marks{ display:flex; gap:16px; align-items:center; height:32px; margin-top:12px; }
.marks img{ height:32px; width:auto; filter:grayscale(1); opacity:.75; }

.cat-head{
  display:flex; justify-content:space-between; align-items:baseline;
  border-top:2px solid var(--ink); padding-top:8px; margin:22px 0 12px;
}
.cat-head h2{
  font-family:'Archivo',system-ui,sans-serif; font-weight:900;
  text-transform:uppercase; letter-spacing:-.02em; font-size:22px; margin:0;
}
.cat-head .count{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:var(--steel);
}

/* The signature element. A tag, not a card. */
.tag{
  display:flex; align-items:stretch; gap:0;
  border:2px solid var(--ink); margin-bottom:12px;
  background:var(--plate); cursor:pointer;
  min-height:76px;   /* matches the tag-photo variant's rendered height, so a
                         row with no photo does not sit shorter than one that
                         has the (always-reserved) photo slot filled in */
  -webkit-tap-highlight-color:transparent;
  transition:transform 120ms var(--ease-io);
}
.tag input{ position:absolute; opacity:0; width:1px; height:1px; }

.tag-number{
  flex:0 0 68px; display:flex; align-items:center; justify-content:center;
  background:var(--ink); color:var(--plate);
  font-family:'ArchivoSubset','Archivo',system-ui,sans-serif;
  font-weight:900; font-size:30px; letter-spacing:-.02em;
  /* The shipped subset is a variable font (fvar wght/wdth), not a static 900
     instance - font-weight alone will not push it to 900. Set the axes
     explicitly or the competition numbers render at the wrong weight. */
  font-variation-settings:"wght" 900, "wdth" 112;
  font-variant-numeric:tabular-nums;
  transition:background 120ms var(--ease-io), color 120ms var(--ease-io);
}
.tag-body{ flex:1 1 auto; padding:10px 12px; min-width:0; }
/* Single line, ellipsised: reserving the photo slot narrows this column,
   and a long model or owner name wrapping to a second line would make tag
   height depend on text content - the same raggedness as a missing photo,
   from a different cause. Truncating keeps every tag the same height. */
.tag-body .model{ font-weight:600; font-size:17px; display:block; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
.tag-body .owner{ color:var(--steel); font-size:15px; display:block; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }

/* Fixed box, rendered on every tag regardless of whether the bike has a
   photo (see ballot.html) - reserving the slot is what keeps a mixed list
   even, since mixed photo coverage is the likely case on the night. Same
   size whether or not the image ever arrives, so nothing here is
   size-dependent on image load. Bikes without a photo just show the plate
   placeholder colour. */
.tag-photo{
  flex:0 0 96px; width:96px; height:72px; align-self:center;
  margin-right:10px; background:#DCD9D1; overflow:hidden;
}
/* contain, not cover: the whole bike has to be visible. A photo cropped to
   fill the box can cut the bike out of its own picture, and recognising the
   bike they saw on the floor is the only reason the photo is here. The box
   stays a fixed size either way, so the list never reflows. */
.tag-photo img{ width:96px; height:72px; object-fit:contain; display:block; }

/* Selection: the number block flips to scrutineering yellow, plus a 3px
   inset rule. Nothing scales, nothing bounces. */
.tag:has(input:checked){ box-shadow:inset 0 0 0 3px var(--ink) !important; }
.tag:has(input:checked) .tag-number{ background:var(--scrut); color:var(--ink); }
.tag:active{ transform:scale(.99); }
.tag:focus-within{ outline:3px solid var(--scrut); outline-offset:2px; }

/* Fallback for iOS 15.0-15.3, which lack :has(). Requires the input to be
   the tag's first child (see ballot.html). */
.tag input:checked ~ .tag-number{ background:var(--scrut); color:var(--ink); }

.submit-bar{
  position:fixed; left:0; right:0; bottom:0;
  background:var(--plate); border-top:2px solid var(--ink);
  padding:12px 12px calc(14px + env(safe-area-inset-bottom));
}
.submit-inner{ max-width:720px; margin:0 auto; }
.hint{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--steel); margin:0 0 8px;
}
.btn{
  display:block; width:100%; min-height:52px;
  background:var(--scrut); color:var(--ink); border:2px solid var(--ink);
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:15px; letter-spacing:.1em; text-transform:uppercase; font-weight:700;
  cursor:pointer;
}
.btn:disabled{ background:var(--plate); color:var(--steel); cursor:default; }
.btn:focus-visible,input:focus-visible{ outline:3px solid var(--ink); outline-offset:2px; }

.error{ color:var(--flag); font-weight:600; }

/* Status pages are the only centred pages in the project. */
.status-page{ max-width:520px; margin:0 auto; padding:56px 16px; text-align:center; }
.status-page .rule{ width:56px; height:3px; background:var(--ink); margin:0 auto 22px; }
.status-page h1{
  font-family:'Archivo',system-ui,sans-serif; font-weight:900;
  text-transform:uppercase; font-size:clamp(22px,6vw,30px); margin:0 0 12px;
}
.status-page p{ margin:0 0 8px; }

/* Credit rail. On the light surface the marks are greyscale and small - a
   credit line at the edge of a scrutineering tag, which is exactly where a
   real one carries a sponsor. Fixed height so a slow load shifts nothing. */
.credit-rail{
  display:flex; gap:16px; align-items:center; justify-content:center;
  height:28px; margin:34px auto 22px;
}
.credit-rail img{ height:28px; width:auto; filter:grayscale(1); opacity:.7; }

.code-input{
  font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:24px;                 /* >=16px or iOS zooms on focus and stays */
  letter-spacing:.28em; text-align:center; text-transform:uppercase;
  width:100%; padding:14px 8px; min-height:52px;
  border:2px solid var(--ink); background:var(--plate); color:var(--ink);
}

/* Landing page signpost - a list of destinations, not a nav bar. */
.link-list{ margin-top:22px; text-align:left; }
.nav-link{
  display:flex; align-items:center; justify-content:center; min-height:52px;
  border:2px solid var(--ink); background:var(--plate); color:var(--ink);
  text-decoration:none; font-family:'IBM Plex Mono',ui-monospace,monospace;
  font-size:15px; letter-spacing:.1em; text-transform:uppercase; font-weight:700;
  margin-bottom:12px; -webkit-tap-highlight-color:transparent;
  transition:transform 120ms var(--ease-io);
}
.nav-link:last-child{ margin-bottom:0; }
.nav-link:active{ transform:scale(.99); }
.nav-link:focus-visible{ outline:3px solid var(--ink); outline-offset:2px; }

/* Public results, published state. Rank block flips to scrutineering
   yellow only for the winner - same "yellow means chosen" rule as the
   ballot's selected tag. Before publish, /results never reaches this
   markup at all (see voter.py) - it renders turnout only. */
.result-list{ list-style:none; margin:0 0 12px; padding:0; border-top:1px solid var(--line); }
.result-row{
  display:flex; align-items:center; gap:12px; padding:10px 0;
  border-bottom:1px solid var(--line);
}
.result-rank{
  flex:0 0 36px; height:36px; display:flex; align-items:center; justify-content:center;
  background:var(--ink); color:var(--plate);
  font-family:'IBM Plex Mono',ui-monospace,monospace; font-weight:700;
  font-variant-numeric:tabular-nums;
}
.result-row--winner .result-rank{ background:var(--scrut); color:var(--ink); }
.result-body{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; }
.result-model{ font-size:16px; }
.result-owner{ color:var(--steel); font-size:14px; }
.result-votes{
  font-family:'IBM Plex Mono',ui-monospace,monospace; font-weight:700;
  font-size:17px; font-variant-numeric:tabular-nums;
}

/* Turnout-only state, before publish. No bike names, no counts, no order
   that hints who is winning - see the /results route in voter.py. */
.result-turnout{
  font-family:'Archivo',system-ui,sans-serif; font-weight:900;
  font-size:clamp(40px,10vw,64px); margin:0 0 8px; font-variant-numeric:tabular-nums;
}

@media (prefers-reduced-motion:reduce){
  *{ animation:none !important; transition:none !important; }
}
