:root {
  --bg: #ffffff;
  --fg: #1a1a1a;
  --dim: #6b7280;
  --line: #e4e4e7;
  --panel: #fafafa;
  --accent: #1d4ed8;
  --highlight: #eff6ff;
  --red: #b91c1c;
  --blue: #1d4ed8;
  --green: #15803d;
  --amber: #b45309;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #101014;
    --fg: #e8e8ea;
    --dim: #9096a1;
    --line: #2a2a31;
    --panel: #17171c;
    --accent: #7aa2ff;
    --highlight: #1b2233;
    --red: #f87171;
    --blue: #7aa2ff;
    --green: #4ade80;
    --amber: #fbbf24;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 2rem 1.5rem 4rem;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
}

@media (max-width: 30rem) {
  body { padding: 1.5rem 1rem 3rem; }
  h1 { font-size: 1.6rem; }
}

header, main { max-width: 1100px; margin: 0 auto; }

/* The title and the link to the repository sit on one line, and stack when
   there is no room for both */
header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: .5rem 1.5rem;
}

.button {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  flex: 0 0 auto;
  margin-top: .35rem;
  padding: .45rem .8rem;
  font-size: .82rem;
  font-weight: 600;
  color: var(--fg);
  text-decoration: none;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.button:hover { color: var(--accent); border-color: var(--accent); }
.button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.button svg { flex: 0 0 auto; width: 1rem; height: 1rem; fill: currentColor; }

/* Too narrow for the title and the link side by side, so they stack and centre
   rather than leaving the button stranded at the end of its own line */
@media (max-width: 30rem) {
  header { justify-content: center; text-align: center; }
  header > div { flex: 1 1 100%; }
  .button { margin-top: .25rem; }
}

h1 { font-size: 2rem; margin: 0 0 .35rem; letter-spacing: -.02em; }
h2 { font-size: 1rem; margin: 2.5rem 0 .5rem; text-transform: uppercase;
     letter-spacing: .08em; color: var(--dim); font-weight: 600; }
/* The banner cards name a role; everywhere else an h3 labels a block */
.role h3 { font-size: .95rem; margin: 0 0 .6rem; }
#panel-reroll h3, #panel-rosters h3 {
  font-size: .78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--dim);
  margin: 1.75rem 0 .5rem;
}
#panel-reroll h2 + h3 { margin-top: .75rem; }

.meta, .note { color: var(--dim); font-size: .85rem; margin: 0 0 1rem; }

/* Sits above everything, so it borrows the advice box's shape and takes the red
   the emblem spines already use, this being the one thing on the page that has
   to be read before anything below it is trusted */
.notice {
  margin: 0 0 1.5rem;
  padding: .7rem .9rem;
  font-size: .88rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--red);
  border-radius: 5px;
  background: var(--panel);
}

a { color: var(--accent); }
a:hover { text-decoration: none; }

main ul { color: var(--dim); font-size: .9rem; margin: 0 0 1.5rem; padding-left: 1.1rem; }
main li { margin-bottom: .4rem; }
main li strong { color: var(--fg); font-weight: 600; }
code { font-size: .85em; }

/* Closed by default, so a newcomer opens it once and everyone else scrolls past
   a single line. Native <details>, so the state and the keyboard both come free.
   */
.guide {
  margin-top: 2rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
}
.guide > summary {
  padding: .7rem 1rem;
  font-size: .85rem;
  font-weight: 600;
  color: var(--fg);
  cursor: pointer;
}
.guide > summary:hover { color: var(--accent); }
.guide > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.guide[open] > summary { border-bottom: 1px solid var(--line); }

.guide ol {
  margin: 0;
  padding: 1rem 1.25rem 1.25rem 2.4rem;
  font-size: .9rem;
  color: var(--dim);
}
.guide li { margin-bottom: .9rem; }
.guide li:last-child { margin-bottom: 0; }
/* The same left rule the advice boxes use, so a note reads as subordinate
   without introducing another treatment */
.guide .aside {
  margin: .4rem 0 0;
  padding-left: .7rem;
  border-left: 2px solid var(--line);
  font-size: .85rem;
}

/* A rule spanning the full width, with the selected tab lit on it, says the
   panel below belongs to that tab without boxing it in */
.tabbed { margin-top: 2rem; }

.tabs {
  display: flex;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
}
.tab {
  font: inherit;
  font-size: .85rem;
  font-weight: 600;
  color: var(--dim);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: .8rem 1.2rem;
  cursor: pointer;
}
/* Flush left, so the panel and everything above it share one edge */
.tab:first-child { padding-left: 0; }
.tab:hover { color: var(--fg); }
.tab.active { color: var(--fg); border-bottom-color: var(--accent); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Whatever a panel opens with, it clears the tab row */
.tabbed > section > :first-child { margin-top: 1.5rem; }

@media (max-width: 30rem) {
  /* Four names this long cannot share a row without looking like an accident,
     so each takes its own and the bar reads as a menu */
  .tabs {
    display: block;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--panel);
    overflow: hidden;
  }
  .tab {
    display: block;
    width: 100%;
    text-align: left;
    padding: .65rem 1rem;
    border-bottom: 1px solid var(--line);
    border-left: 2px solid transparent;
    margin-bottom: 0;
  }
  .tab:first-child { padding-left: 1rem; }
  .tab:last-child { border-bottom: 0; }
  /* The accent moves to the left edge here, so the underline it carries on
     desktop has to go back to being an ordinary divider */
  .tab.active {
    background: var(--highlight);
    border-bottom-color: var(--line);
    border-left-color: var(--accent);
  }
}

/* Banner */
/* min() lets the single-column case shrink below the track minimum; a bare
   minmax(340px, 1fr) cannot, and overflows the padding on a phone */
.roles {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
}

.role {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 1rem;
}

/* A coloured spine is enough to say which colour an emblem is; tinting the
   controls as well made three boxes of noise */
.slot {
  --edge: var(--line);
  border-left: 3px solid var(--edge);
  padding: .1rem 0 .1rem .7rem;
  margin-bottom: .8rem;
}
.slot:last-child { margin-bottom: 0; }
.slot.Red { --edge: var(--red); }
.slot.Blue { --edge: var(--blue); }
.slot.Green { --edge: var(--green); }

.slot-line { display: flex; align-items: center; gap: .5rem; }
.slot-line + .slot-line { margin-top: .25rem; }
/* A flex item's automatic minimum is its content width, wider than the basis,
   so it has to be cleared explicitly */
.slot-line > * { min-width: 0; }

/* The longest emblem name has to fit without truncating */
.slot-line select { flex: 1 1 auto; min-width: 0; }
.slot-line.sub select { font-size: .78rem; padding: .2rem .35rem; }

.slot .mult { flex: 0 0 auto; font-weight: 700; font-variant-numeric: tabular-nums; }
.slot .bonus {
  flex: 0 0 3.2rem;
  text-align: right;
  color: var(--dim);
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
}

select, input {
  font: inherit;
  font-size: .85rem;
  padding: .3rem .4rem;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 5px;
}
select:focus-visible, input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* Controls */
/* Belongs to the table below it, so it shares its left edge */
.controls { margin-bottom: .75rem; }
.controls .check {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .82rem;
  color: var(--dim);
  cursor: pointer;
}
.controls .check:hover { color: var(--fg); }

/* A switch, not a tick box: it changes what the table shows rather than
   recording a choice */
.controls .check input {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 2rem;
  height: 1.1rem;
  margin: 0;
  padding: 0;
  border-radius: 999px;
  transition: background .15s, border-color .15s;
}
.controls .check input::after {
  content: "";
  position: absolute;
  top: 50%;
  left: .15rem;
  width: .75rem;
  height: .75rem;
  border-radius: 50%;
  background: var(--dim);
  transform: translateY(-50%);
  transition: left .15s, background .15s;
}
.controls .check input:checked { background: var(--accent); border-color: var(--accent); }
.controls .check input:checked::after { left: calc(100% - .9rem); background: var(--bg); }
.controls .check input:focus-visible { outline-offset: 2px; }

/* Table */
/* Every roster is in the table, so it scrolls internally at roughly fifteen
   rows. The overflow also makes this the container the sticky header sticks to.
   */
.table-wrap {
  overflow: auto;
  max-height: 34rem;
  border: 1px solid var(--line);
  border-radius: 8px;
}

table { border-collapse: collapse; width: 100%; font-size: .85rem; }

th, td { padding: .4rem .6rem; text-align: left; white-space: nowrap; }

thead th {
  position: sticky;
  top: 0;
  /* A faded cell has an opacity, which makes it its own stacking context and
     lets it paint over a sticky header that has no z-index of its own */
  z-index: 1;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  font-weight: 600;
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--dim);
}

/* The filters are a second header row, so the line under the header belongs to
   them rather than to the labels above */
#head th { border-bottom: 0; }
#head-filters th { padding-top: .1rem; }
/* auto rather than 100%, because the dropdown arrow eats into the width from the
   inside: matched to the column exactly, the select clips a Team name the row
   below it shows in full. auto sizes it to its longest option, widening the
   column to suit, and the minimum keeps it filling a column widened by something
   else. */
#head-filters select.filter {
  display: block;
  width: auto;
  min-width: 100%;
  font-size: .75rem;
  padding: .2rem .3rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--fg);
}

th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--fg); }
th.sortable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
th.sorted { color: var(--accent); }

tbody tr:nth-child(even) { background: color-mix(in srgb, var(--panel) 55%, transparent); }
tbody tr:hover { background: var(--highlight); }

td.rank { color: var(--dim); font-variant-numeric: tabular-nums; }
td.dim { color: var(--dim); }
td.gain { color: var(--green); font-weight: 700; }
td.warn { color: var(--amber); font-weight: 700; }
td.loss { color: var(--red); font-weight: 700; }
/* A change that rounds to nothing is not a gain */
td.zero { color: var(--dim); }
td.changed { color: var(--fg); }

/* Every title combination for one roster, prefix down the side and suffix
   across. The grid shows which of the two choices is doing the work, which a
   ranked list of 56 flattens away. */
#roster-detail { margin-top: .25rem; }
#roster-detail h3 { margin-top: 1rem; }
#body tr { cursor: pointer; }
#body tr.open {
  background: var(--highlight);
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}
#body tr:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.matrix th.side {
  position: sticky;
  left: 0;
  z-index: 1;
  text-align: left;
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--dim);
  background: var(--panel);
  border-right: 1px solid var(--line);
}
/* The corner belongs to both axes, so it has to outrank each of them */
.matrix thead th:first-child {
  left: 0;
  z-index: 2;
  border-right: 1px solid var(--line);
}
/* The best pair is found by the other 55 receding, which is quieter than a
   colour and does not imply a status the figure does not carry */
.matrix td { color: var(--dim); }
.matrix td.best { color: var(--fg); font-weight: 700; }
/* The pair the roster table lists, when a different one wins at this column */
.matrix td.chosen { outline: 1px solid var(--accent); }

/* Compiled data */
/* An emblem stat the role's banner cannot hold this period */
.faded { opacity: .45; }

td.colour { font-size: .78rem; }

/* Rerolls */
.table-wrap.short { max-height: 22rem; }

/* One per row. Three across only fits at the widest sizes, so wrapping left the
   third stranded on its own line; a column also gives each name room not to
   truncate */
/* The cap belongs on the column, not the row: an auto grid column sizes to its
   content, so a max-width on the row overflows a narrower page */
.offers {
  display: grid;
  grid-template-columns: min(26rem, 100%);
  gap: .5rem;
  margin-bottom: 1rem;
}
.offer {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .8rem;
  color: var(--dim);
}
.offer > span { flex: 0 0 auto; white-space: nowrap; }
.offer select { flex: 1 1 auto; min-width: 0; }

/* Chrome and Firefox colour the group label; Safari and mobile pickers
   ignore it, which is why the label still names the colour in words */
#options optgroup.Red { color: var(--red); }
#options optgroup.Blue { color: var(--blue); }
#options optgroup.Green { color: var(--green); }
/* The label inherits down to its options, so they are put back */
#options optgroup option { color: var(--fg); }

.advice {
  margin: 0 0 1rem;
  padding: .7rem .9rem;
  font-size: .88rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--dim);
  border-radius: 5px;
  background: var(--panel);
}
.advice-label {
  display: block;
  font-size: .68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--dim);
  margin-bottom: .3rem;
}
.advice-text { margin: 0; }

.advice.gain { border-left-color: var(--green); }
.advice.warn { border-left-color: var(--amber); }
.advice.loss { border-left-color: var(--red); }

/* What to do and what it is worth, picked out of the sentence */
.advice .key { color: var(--fg); font-weight: 700; }
.advice .gain { color: var(--green); font-weight: 700; }
.advice .warn { color: var(--amber); font-weight: 700; }
.advice .loss { color: var(--red); font-weight: 700; }
.advice .zero { color: var(--dim); font-weight: 700; }

#roll-body tr { cursor: pointer; }
/* Rerolling has no outcomes to open, and changes nothing by definition, so it
   is the line the others are measured against rather than a rival to them */
#roll-body tr.reroll { cursor: default; }
#roll-body tr.reroll td { color: var(--dim); font-weight: 400; }
#roll-body tr.reroll:hover { background: none; }
#roll-body tr.offered td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
#roll-body tr.offered { background: var(--highlight); }
#roll-body tr.open {
  background: var(--highlight);
  /* Outside the box, the top edge lands under the sticky header and vanishes
     on the first row */
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}
#roll-body tr:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The click hint sits under the table it applies to, pointing at the detail
   that opens below it */
#roll-note { margin: .5rem 0 0; }
#detail { margin-top: .25rem; }

#banner-section { margin-bottom: .5rem; }

/* The optimiser panels price the banner, so while that runs their numbers are
   for the banner as it was. Dimming says so without moving anything, and the
   banner editor sits outside both panels and stays live throughout -- which is
   what the message says, since a dimmed panel on its own reads as locked. */
.optimiser { position: relative; }
.optimiser > *:not(.working) { transition: opacity .12s ease-out; }
.optimiser.busy > *:not(.working) {
  opacity: .3;
  /* a row expanded now would be showing outcomes about to be replaced */
  pointer-events: none;
}

.working {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding-top: 3.5rem;
}
.optimiser.busy .working { display: flex; }

/* The advice box's shape, since this is the panel speaking about its own state
   in the same voice it uses for everything else. Accent on the rule rather than
   one of the three the advice box reserves for a verdict */
.working-box {
  max-width: 24rem;
  padding: .7rem .9rem;
  font-size: .88rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 5px;
  background: var(--panel);
}
.working-label {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--dim);
  margin-bottom: .3rem;
}
.working-text { margin: 0; color: var(--dim); }

.spinner {
  flex: 0 0 auto;
  width: .8rem;
  height: .8rem;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse { 50% { opacity: .2; } }

/* Rotation is the kind of movement this setting is asking us to drop, but
   stopping dead reads as a spinner that has jammed and leaves a page that looks
   hung. A fade goes nowhere, so it can still say "working" */
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: pulse 1.3s ease-in-out infinite;
    border-color: var(--accent);
  }
}
