/* ============================================================
   /sorting-race — 排序大賽

   A race broadcast: dark stage, saturated bars, everything
   else out of the way. Colour is used hard here, and every
   colour on the page means something:

     the bar ramp      a bar's colour IS its height, so a
                       sorted array is a clean blue → green →
                       gold → vermilion sweep and a shuffled
                       one is visibly noise
     white / hot pink   being read / just written. Deliberately
                       two colours the ramp never reaches, so
                       they can never read as a value
     the seven hues    the seven families of algorithm. Same
                       hue in the picker, on the lane edge, on
                       the bib, and on the winner's name

   WHAT THIS PAGE STILL DOES NOT DO:

     · no gradient anywhere. The stripe across the top is seven
       solid blocks, and the bar ramp is a data scale, not a
       decorative blend
     · no coloured buttons — a control is neutral, and pressing
       it inverts. Colour belongs to the data, not the chrome
     · no kicker label over a heading, no dot before a section
     · no wall of cards. Lanes are full-bleed bands with a
       coloured edge; the picker is hairline rows
     · no emoji, and no text under 13px

   The stage under the bars is dark in BOTH themes. Saturated
   colour on near-black is the entire point; the same colours
   on paper are mud.
   ============================================================ */

:root {
  color-scheme: dark;

  --paper: #131519;
  --paper-2: #1b1e24;
  --panel: #171a1f;

  --ink: #edebe5;
  --ink-2: rgba(237, 235, 229, 0.68);
  --ink-3: rgba(237, 235, 229, 0.46);

  --rule: rgba(237, 235, 229, 0.15);
  --rule-2: rgba(237, 235, 229, 0.34);

  --ui: "Noto Sans TC", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
        "PingFang TC", "Microsoft JhengHei", sans-serif;
}

:root[data-theme="light"] {
  color-scheme: light;

  --paper: #f5f3ed;
  --paper-2: #e9e6de;
  --panel: #fbfaf7;

  --ink: #191b1e;
  --ink-2: rgba(25, 27, 30, 0.7);
  --ink-3: rgba(25, 27, 30, 0.46);

  --rule: rgba(25, 27, 30, 0.16);
  --rule-2: rgba(25, 27, 30, 0.34);
}

/* ── the stage, and everything drawn on it ──────────────────
   Deliberately outside both theme blocks: the bars look the
   same whichever way the page is set, so there is one
   definition of what "being read" looks like, not two. */
:root {
  --stage: #0d0f13;
  --stage-wash: rgba(255, 255, 255, 0.075);
  --stage-mark: rgba(255, 255, 255, 0.6);

  /* The value ramp, low to high. Read by js/view/bars.js. */
  --ramp-0: #3b5bd0;
  --ramp-1: #1f9bc9;
  --ramp-2: #22b487;
  --ramp-3: #c9b62c;
  --ramp-4: #e2542c;

  --bar-read: #ffffff;
  --bar-write: #ff3d7f;
  --bar-sweep: #ffffff;
  --bar-empty: rgba(255, 255, 255, 0.15);

  /* The seven families. Chosen to hold up on paper and on
     near-black without being restated per theme. */
  --fam-exchange: #e2542c;
  --fam-insertion: #de8f13;
  --fam-selection: #7fae1b;
  --fam-divide: #16a982;
  --fam-distribution: #2f8fd8;
  --fam-network: #7b6ae0;
  --fam-novelty: #d4569a;

  --fam: var(--ink-3);
}

[data-family="exchange"]     { --fam: var(--fam-exchange); }
[data-family="insertion"]    { --fam: var(--fam-insertion); }
[data-family="selection"]    { --fam: var(--fam-selection); }
[data-family="divide"]       { --fam: var(--fam-divide); }
[data-family="distribution"] { --fam: var(--fam-distribution); }
[data-family="network"]      { --fam: var(--fam-network); }
[data-family="novelty"]      { --fam: var(--fam-novelty); }

* { box-sizing: border-box; }

/* An id or class rule with a display would otherwise beat the browser's own
   [hidden] rule, and the element would stay on screen with the attribute set. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}

/* Every number that changes gets tabular figures, or a counter ticking up
   shifts everything beside it and the whole row twitches. */
.stat-v, .lane-bib, .dial output, .result-n, .pick-count {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ── the stripe ───────────────────────────────────────────── */

/* Seven solid blocks, one per family, in the order they appear in the picker.
   Flat colour, no blend — this is a pennant, not a gradient. */
#stripe {
  flex: none;
  display: flex;
  height: 5px;
}

#stripe span { flex: 1 1 0; }

/* ── masthead ─────────────────────────────────────────────── */

.masthead {
  flex: none;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px 12px;
  padding-top: calc(14px + env(safe-area-inset-top, 0px));
  border-bottom: 1px solid var(--rule);
  background: var(--panel);
}

.masthead h1 {
  margin: 0;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.mast-line {
  margin: 4px 0 0;
  max-width: 62ch;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}

/* What the colours on the bars mean. The only place on the page where a
   coloured square appears next to words, and it earns it: without this the
   amber and the red are decoration rather than information. */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 7px 0 0;
  font-size: 13.5px;
  color: var(--ink-2);
}

.legend > span { display: inline-flex; align-items: center; gap: 6px; }

.sw {
  width: 11px;
  height: 11px;
  border-radius: 1px;
  display: inline-block;
}

.sw-read { background: var(--bar-read); border: 1px solid var(--rule-2); }
.sw-write { background: var(--bar-write); }

/* The ramp shown as itself: five blocks in the order the bars use them. */
.sw-ramp { display: inline-flex; width: auto; }
.sw-ramp i { width: 11px; height: 11px; display: block; }
.sw-ramp i:nth-child(1) { background: var(--ramp-0); }
.sw-ramp i:nth-child(2) { background: var(--ramp-1); }
.sw-ramp i:nth-child(3) { background: var(--ramp-2); }
.sw-ramp i:nth-child(4) { background: var(--ramp-3); }
.sw-ramp i:nth-child(5) { background: var(--ramp-4); }

.mast-right {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 34px;
  min-width: 40px;
  padding: 0 11px;
  border: 1.5px solid var(--rule-2);
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  text-decoration: none;
  cursor: pointer;
}

.chip:active, .chip:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ── the track ────────────────────────────────────────────── */

#track {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: grid;
  /* min-content, not a fixed height: an open scratch drawer makes a lane
     taller than its share of the screen, and the track scrolls rather than
     cutting the drawer in half. */
  grid-auto-rows: minmax(min-content, 1fr);
}

.lane {
  display: flex;
  flex-direction: column;
  min-height: 148px;
  padding: 10px 18px 12px;
  border-bottom: 1px solid var(--rule);
  border-left: 3px solid var(--fam);
}

.lane:last-child { border-bottom: 0; }

.lane-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 8px;
}

/* The bib number before the race, the finishing position after it. Same box,
   so the moment it changes is the moment that lane finished. */
.lane-bib {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 7px;
  border: 1.5px solid var(--rule-2);
  border-radius: 2px;
  font-size: 15px;
  font-weight: 700;
  border-color: var(--fam);
  color: var(--fam);
}

/* The winner's bib fills in with its family colour. It is the one filled
   colour block in the chrome, and it is the thing you look for. */
.lane.is-first .lane-bib { background: var(--fam); border-color: var(--fam); color: #0d0f13; }
.lane.is-out .lane-bib { border-style: dashed; border-color: var(--rule-2); color: var(--ink-3); }

.lane-name {
  font-size: 17px;
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lane-big {
  flex: none;
  font-size: 13.5px;
  color: var(--ink-3);
  white-space: nowrap;
}

.lane-stats {
  margin-left: auto;
  display: flex;
  align-items: baseline;
  gap: 14px;
  font-size: 13.5px;
  color: var(--ink-2);
  white-space: nowrap;
}

.stat-k { color: var(--ink-3); }
.stat-v { color: var(--ink); font-weight: 600; }

/* The number the race is actually decided on, in the lane's own colour. */
.lane-stats .stat:last-child .stat-v { color: var(--fam); font-weight: 700; }

.lane-canvas-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 52px;
  background: var(--stage);
  border-radius: 2px;
}

/* Absolute, and this is load-bearing: a canvas in normal flow contributes its
   own pixel height as min-content, the row grows to fit it, fitCanvas then
   resizes it to the taller row, and one lane eats the whole track. Out of flow
   it contributes nothing and the row is sized by the grid alone. */
.lane-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* The word that says what the lane is doing. It sits in the head row rather
   than over the bars: floated above the canvas it landed on top of the bib. */
.lane-say {
  flex: none;
  font-size: 13.5px;
  color: var(--ink-3);
  white-space: nowrap;
}

.lane.is-done .lane-say { color: var(--fam); }
.lane.is-out .lane-say { color: var(--bar-write); }

/* ── the drawer under a lane ──────────────────────────────── */

.lane-foot {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 8px;
  font-size: 13.5px;
  color: var(--ink-3);
}

.drawer-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 30px;
  padding: 0 10px;
  border: 1.5px solid var(--rule-2);
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
}

.drawer-btn:hover, .drawer-btn:active { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.drawer-btn svg { transition: transform 0.14s ease; }
.drawer-btn[aria-expanded="true"] svg { transform: rotate(180deg); }

.lane-aux {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  padding-top: 8px;
}

.aux {
  flex: 1 1 220px;
  min-width: 160px;
}

.aux-label {
  display: block;
  padding-bottom: 3px;
  font-size: 13px;
  color: var(--ink-2);
}

.aux-canvas {
  display: block;
  width: 100%;
  height: 46px;
  background: var(--stage);
  border-radius: 2px;
}

/* ── result strip ─────────────────────────────────────────── */

#result {
  flex: none;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  padding: 11px 18px;
  border-top: 1px solid var(--rule);
  border-left: 4px solid var(--fam);
  background: var(--paper-2);
  font-size: 15px;
  line-height: 1.5;
}

#result strong { font-size: 16px; color: var(--fam); }
#result span { color: var(--ink-2); }

/* ── the deck ─────────────────────────────────────────────── */

#deck {
  flex: none;
  border-top: 1.5px solid var(--rule-2);
  background: var(--panel);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.deck-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding: 11px 18px;
}

.deck-actions { border-bottom: 1px solid var(--rule); }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 16px;
  border: 1.5px solid var(--rule-2);
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  font-weight: 550;
  cursor: pointer;
  white-space: nowrap;
}

.btn:hover:not(:disabled), .btn:active:not(:disabled) {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.btn-strong { border-color: var(--ink); border-width: 2px; font-weight: 700; }

.btn:disabled { opacity: 0.38; cursor: default; }

.btn-toggle.is-on { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.btn-toggle { min-width: 62px; }

.dial {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
}

.dial-name { color: var(--ink-2); white-space: nowrap; }
.dial output { min-width: 5.5ch; font-size: 15px; font-weight: 600; }
.dial-sound output { min-width: 0; }

.dial-wide { flex-wrap: wrap; }

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 130px;
  height: 28px;
  background: transparent;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--rule-2);
}
input[type="range"]::-moz-range-track { height: 2px; background: var(--rule-2); }

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 22px;
  margin-top: -10px;
  border: 0;
  border-radius: 1px;
  background: var(--ink);
}
input[type="range"]::-moz-range-thumb {
  width: 15px;
  height: 22px;
  border: 0;
  border-radius: 1px;
  background: var(--ink);
}

.segments {
  display: inline-flex;
  border: 1.5px solid var(--rule-2);
  border-radius: 2px;
  overflow: hidden;
}

.segments button {
  min-height: 34px;
  padding: 0 11px;
  border: 0;
  border-left: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
}

.segments button:first-child { border-left: 0; }
.segments button.is-on { background: var(--ink); color: var(--paper); font-weight: 600; }

/* ── the picker ───────────────────────────────────────────── */

#picker {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.pick-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding: 13px 18px;
  border-bottom: 1.5px solid var(--rule-2);
  background: var(--panel);
}

.pick-head h2 { margin: 0; font-size: 19px; font-weight: 700; }
.pick-head-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pick-count { font-size: 14px; color: var(--ink-2); }

.pick-warn {
  flex: none;
  margin: 0;
  padding: 10px 18px;
  border-bottom: 1px solid var(--rule);
  background: var(--paper-2);
  font-size: 14px;
  line-height: 1.5;
}

.pick-body { flex: 1 1 auto; overflow-y: auto; padding-bottom: 24px; }

.fam {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--paper-2);
  padding: 10px 18px;
  font-size: 15px;
  font-weight: 700;
  color: var(--fam);
  border-bottom: 1px solid var(--rule);
  border-left: 4px solid var(--fam);
}

/* Hairline-divided rows, not a grid of cards. A row is a button so the whole
   width of it is the target, which matters far more on a phone. */
.pick-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  padding: 11px 18px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-left: 4px solid var(--fam);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.pick-row:hover { background: var(--paper-2); }
.pick-row.is-on { background: var(--paper-2); }

.pick-box {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 2px;
  border: 1.5px solid var(--rule-2);
  border-radius: 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.pick-row.is-on .pick-box { background: var(--fam); border-color: var(--fam); }
.pick-box svg { display: none; color: #0d0f13; }
.pick-row.is-on .pick-box svg { display: block; }

.pick-main { min-width: 0; flex: 1 1 auto; }

.pick-name { font-size: 16px; font-weight: 650; }
.pick-row.is-on .pick-name { color: var(--fam); }
.pick-name span { margin-left: 8px; font-size: 13.5px; font-weight: 400; color: var(--ink-3); }
.pick-tip { margin: 3px 0 0; font-size: 14px; line-height: 1.55; color: var(--ink-2); }

.tags { margin-top: 5px; display: flex; flex-wrap: wrap; gap: 6px; }
.tags:empty { display: none; }
.tag {
  font-size: 13px;
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 1px 7px;
}

.pick-row.is-off { opacity: 0.42; }

/* ── narrow screens ───────────────────────────────────────── */

@media (max-width: 720px) {
  .masthead { padding: 12px 14px 10px; padding-top: calc(12px + env(safe-area-inset-top, 0px)); }
  .masthead h1 { font-size: 19px; }
  .mast-line { font-size: 13.5px; }
  .legend { gap: 4px 12px; font-size: 13px; }
  .lane { padding: 9px 14px 10px; }
  .lane-name { font-size: 16px; }
  .lane-big { display: none; }
  .lane-stats { gap: 10px; font-size: 13px; }
  .deck-row { padding: 8px 14px; gap: 8px 10px; }
  .btn { min-height: 44px; padding: 0 13px; font-size: 14.5px; }

  /* Two dials to a row, then the starting order across the width. Stacked one
     per row this deck took two thirds of a phone screen and left the bars
     nowhere to be. */
  .deck-dials { display: grid; grid-template-columns: 1fr 1fr; align-items: center; }
  .dial { flex: 1 1 auto; min-width: 0; }
  .dial-wide, .dial-sound { grid-column: 1 / -1; }
  .dial-wide { flex-wrap: nowrap; gap: 10px; }
  .segments { flex: 1 1 auto; overflow-x: auto; }
  .segments button { flex: none; }
  input[type="range"] { flex: 1 1 60px; width: auto; min-width: 60px; }
  .dial output { min-width: 3.5ch; }
  .pick-head, .pick-row, .fam, .pick-warn { padding-left: 14px; padding-right: 14px; }
  #result { padding: 10px 14px; font-size: 14.5px; }
}

/* Under about 560px the read/write/compare counters are more numbers than
   there is room for; accesses is the one the race is decided on, so it stays. */
@media (max-width: 560px) {
  .lane-stats .stat.is-extra { display: none; }
}
