/*
 * Hand written and served straight from the assets binding: no build step, no generated
 * file. The tokens are the design handoff's visual language, which is the part of it that
 * still stands.
 */
:root {
  --bg: #14161a;
  --surface: #1c2027;
  --fg: #f2f3f0;
  --accent: #c8e24a;
  --accent-dim: #b4ce38;
  --muted: #8a929e;
  --muted-strong: #c6cbd2;
  --divider: #3a414b;
  /* fainter than a border: for separating list items that are already close together */
  --hairline: #23272e;
  --raised: 0 1px 2px rgba(0, 0, 0, 0.35), 0 8px 20px rgba(0, 0, 0, 0.3);
  --twitch: #9146ff;
  --warn: #f0b429;
  --danger: #e5484d;

  --font: "Rajdhani", system-ui, -apple-system, sans-serif;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  line-height: 1.5;
}

.shell {
  max-width: 60rem;
  margin: 0 auto;
  padding: 0 1.5rem 6rem;
}

/* the counter dropped its visible labels, so the numbers carry them for a screen reader */
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* the design uses tabular figures for every number the eye has to compare */
.tnum {
  font-variant-numeric: tabular-nums;
}

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

h1,
h2,
h3 {
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

h1 {
  font-size: 2.25rem;
  margin: 0 0 0.75rem;
}

/* only the front page: the other h1s are page titles and read as left-aligned labels */
h1.hero {
  text-align: center;
  margin-bottom: 0.35rem;
}

p.tagline {
  text-align: center;
  margin: 0;
  color: var(--muted-strong);
  font-size: 1.125rem;
}

h2 {
  font-size: 1.5rem;
  margin: 2.5rem 0 0.75rem;
}

h3 {
  font-size: 1.125rem;
  margin: 2rem 0 0.5rem;
}

header.site {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  padding: 1.1rem 0 0;
  font-size: 0.85rem;
}

header.site nav,
header.site .account {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

header.site .account {
  gap: 0.9rem;
}

/* the sign-out control is a form, and a form still brings its own margin */
header.site form {
  display: flex;
  margin: 0;
}

header.site .who {
  color: var(--muted);
  /* a long address must not push the button off the row */
  overflow-wrap: anywhere;
}

header.site nav a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
}

header.site nav a:hover {
  color: var(--fg);
}

main {
  padding-top: 2rem;
}

/*
 * The way out, above the page rather than under it. `main` already carries the space between it
 * and the header, so this only has to hold the heading off itself.
 */
nav.back {
  margin-bottom: 1.5rem;
  font-size: 0.85rem;
}

nav.back a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
}

nav.back a:hover {
  color: var(--fg);
}

/* what is left down there is the front page's privacy link, which is a footer and not an exit */
footer.site {
  margin-top: 4rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--divider);
  font-size: 0.85rem;
}

footer.site a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
}

footer.site a:hover {
  color: var(--fg);
}

.button {
  display: inline-block;
  border: 0;
  border-radius: 0;
  padding: 0.7rem 1.1rem;
  font: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  text-decoration: none;
  background: var(--accent);
  color: var(--bg);
}

.button.secondary {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--divider);
}

.button.twitch {
  background: var(--twitch);
  color: #fff;
}

.button.small {
  padding: 0.35rem 0.6rem;
  font-size: 0.8rem;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* the counter, which is the one number the whole page exists to show */
.counter {
  background: var(--surface);
  box-shadow: var(--raised);
  padding: 1.25rem 1.4rem 1.4rem;
  margin: 1.5rem 0;
}

/* the ask the whole block exists for, so it reads before the numbers do */
.counter .ask {
  margin: 0 0 0.2rem;
  font-size: 1.35rem;
}

.counter .note {
  margin: 0 0 1.1rem;
  /* one line on a wide screen, and never a lone word on its own line on a narrow one */
  text-wrap: pretty;
}

/* the jump lands on the heading, not with it flush against the top of the window */
#palkinnot {
  scroll-margin-top: 2rem;
}

/* an in-page jump that moves is a jump you can see the direction of, unless you asked otherwise */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

.counter .figure {
  font-size: 2.5rem;
  font-weight: 600;
  line-height: 1;
}

.counter .label {
  font-size: 0.8rem;
  color: var(--muted);
  font-style: italic;
  margin-bottom: 0.25rem;
}

.counter .row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  margin-bottom: 0.9rem;
}

.counter .goal {
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--muted-strong);
}

.bar {
  height: 0.5rem;
  background: var(--bg);
  margin-top: 1rem;
  overflow: hidden;
}

.bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
}

/* the reward list: the page's actual content now */
/*
 * The ways off the front page, under the tagline: the route, the sponsor and the stream. They
 * are one row of peers rather than a button apiece, because none of them is the ask. Twitch
 * keeps its colour so the one that leaves the site is the one that looks like it does.
 */
.ways {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.75rem;
  margin: 1rem 0 0;
  font-size: 0.95rem;
  font-weight: 600;
}

.ways a {
  color: var(--muted-strong);
  text-decoration: none;
  padding-bottom: 0.15rem;
  border-bottom: 2px solid transparent;
}

.ways a:hover,
.ways a:focus-visible {
  color: var(--fg);
  border-bottom-color: var(--accent);
}

.ways a.twitch {
  color: var(--twitch);
}

.ways a.twitch:hover,
.ways a.twitch:focus-visible {
  color: var(--twitch);
  border-bottom-color: var(--twitch);
}

/* inside the counter card, so the button sits under the figure and the rate under the goal */
.donate {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 1.4rem;
}

/* the rate sits on the button rather than above the fold as prose, so the cost of a
   kilometre is read at the moment of deciding to give */
.price {
  background: var(--bg);
  padding: 0.55rem 0.8rem;
  font-size: 0.95rem;
  color: var(--muted);
}

.price strong {
  color: var(--fg);
  font-weight: 600;
}

/* cards rather than rows, and no images: the amount is the only thing to look at.
   fixed column counts rather than auto-fit, because the tier count is a constant and every
   count it divides by leaves full rows instead of a half-empty last one */
/*
 * The rewards are a climb, not a menu, and the layout is what has to say so. A grid of equal
 * cards is exactly how a menu looks, so this is one column in ascending order with a rail
 * threaded through it: lit from the bottom up to the donor's own total, dim above. The lit part
 * claims every rung it passes, which is the cumulative rule drawn rather than explained.
 */
/*
 * The ladder and the panel that talks about it, side by side once there is room for both. The
 * right column is exactly the space the ladder used to leave empty, and a phone gets one column
 * with the ladder first, because the ladder is what the page is for.
 */
.rewards {
  display: grid;
  gap: 1.25rem;
  align-items: start;
}

/*
 * One column, and the panel goes first: on a phone what it says is either how far the next rung
 * is or why signing in is worth it, and neither is worth six rungs of scrolling to reach.
 *
 * Done with `order` rather than by reordering the markup, so the document keeps the ladder
 * first. That leaves visual order and focus order disagreeing on a narrow screen, which is the
 * screen where a keyboard is rarest; reordering the markup instead would have moved the same
 * disagreement onto the wide layout, where it is not.
 */
.rewards .panel {
  order: -1;
  /* the grid owns the spacing between the two, so the panel stops carrying its own */
  margin: 0;
}

@media (min-width: 52rem) {
  .rewards {
    grid-template-columns: minmax(0, 1fr) minmax(0, 19rem);
    gap: 0 3rem;
  }

  /* side by side, and the document order is the reading order again */
  .rewards .panel {
    order: 0;
  }
}

.ladder {
  /* the axis the rail runs down and every node centres on. Half the node width, so the node's
     left edge sits flush with the column. */
  --rail: 0.575rem;
  /* where the text column starts, and so where anything that must not touch the rail begins */
  --gutter: 2.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
 * Three columns rather than two: the name, the amount, and whatever there is to do about it.
 * Giving the amount a column of its own is what puts every amount under every other amount,
 * which two columns could not do, because a rung with a claim button and a rung with nothing
 * pushed their amounts to different places.
 */
.ladder li {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  column-gap: 0.75rem;
  padding: 0.75rem 0 0.75rem var(--gutter);
}

/* the rail: one segment per rung, abutting into a continuous line */
.ladder li::before {
  content: "";
  position: absolute;
  left: var(--rail);
  top: 0;
  bottom: 0;
  width: 2px;
  transform: translateX(-50%);
  background: var(--divider);
}

/* it starts and ends at a node rather than overshooting into the margin */
.ladder li:first-child::before {
  top: 50%;
}

.ladder li:last-child::before {
  bottom: 50%;
}

/* the node, centred on the rung's first line */
.ladder li::after {
  content: "";
  position: absolute;
  left: var(--rail);
  top: 50%;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 2px solid var(--divider);
  background: var(--bg);
  transform: translate(-50%, -50%);
}

/*
 * The wrapper exists for the narrow layout, where the amount and the button share a line under
 * the name. Here it dissolves, so its two children are grid items in their own right and can
 * line up with the rows above and below them.
 */
.ladder .meta {
  display: contents;
}

.ladder .name {
  font-weight: 600;
  /* the leader runs from the end of the name to the amount, so the flex is what gives the dots
     somewhere to grow into. Centred rather than sat on the baseline: a leader that joins two
     things should run through the middle of them, not along the bottom */
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/*
 * Leader dots, the way a menu or a table of contents joins a name to its price. Faint enough to
 * be a guide rather than a rule: the eye should follow them without reading them.
 */
.ladder .name::after {
  content: "";
  flex: 1;
  height: 0;
  border-bottom: 1px dotted var(--hairline);
}

.ladder .at {
  color: var(--muted-strong);
  font-size: 1.2rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  /* the column is as wide as the longest amount, so the euro signs stack rather than the digits */
  text-align: right;
}

/* reached: the rail below the mark is lit, and it lights every rung it has passed */
.ladder li.earned::before {
  background: var(--accent);
}

.ladder li.earned::after {
  border-color: var(--accent);
  background: var(--accent);
}

.ladder li.earned .at {
  color: var(--accent);
}

.ladder li.ahead .name,
.ladder li.ahead .at {
  color: var(--muted);
}

/*
 * Where the donor stands. No name and no node: it is a reading on the rail rather than another
 * reward, and the rail changes colour through it because this is exactly where the climb stops.
 */
/*
 * A band, so the mark is a different kind of object rather than a quieter rung. `--surface` is
 * what a panel is made of, so it is the language the page already has for "this is a block, not
 * a line of the list". Inset past the gutter with a gradient, the same way the tick is, so the
 * rail runs through it unbroken.
 */
.ladder li.you {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  /* the band hugs what it says: running it to the end of the column left the phrase stranded
     against the left edge with a field of nothing after it */
  width: fit-content;
  /* the band starts on the column edge, and its text is inset from that edge like any block's */
  padding: 0.6rem 0.9rem 0.6rem calc(var(--gutter) + 0.9rem);
  background-image: linear-gradient(
    to right,
    transparent var(--gutter),
    var(--surface) var(--gutter)
  );
}

.ladder .here {
  color: var(--muted);
  font-size: 0.85rem;
}

.ladder li.you::before {
  background: linear-gradient(to bottom, var(--accent) 50%, var(--divider) 50%);
}

.ladder li.you::after {
  top: 50%;
  left: var(--rail);
  width: 1.35rem;
  height: 1.35rem;
  border: 0;
  border-radius: 0;
  background: var(--accent);
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  transform: translate(-50%, -50%);
}

.ladder li.you .at {
  color: var(--accent);
  font-size: 1.05rem;
}

.ladder .claim {
  color: var(--bg);
  background: var(--accent);
  padding: 0.3rem 0.6rem;
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}

.ladder .claim:hover {
  background: var(--accent-dim);
}

.ladder .check {
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
}

/* the claim and the amount stop sharing a line once there is no room for both */
@media (max-width: 26rem) {
  .ladder li {
    grid-template-columns: 1fr;
    padding-top: 1rem;
    padding-bottom: 1rem;
  }

  /* the band already sets the mark apart, so it needs less extra room here than it did */
  .ladder li.you {
    padding-top: 1rem;
    padding-bottom: 1rem;
  }

  /* one column, so the amount and the button go back to sharing a line under the name */
  .ladder .meta {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.75rem;
    margin-top: 0.45rem;
  }

  /* nothing to lead to: the amount is on the next line down, not at the end of this one */
  .ladder .name::after {
    content: none;
  }

  /*
   * A rung is two lines at this width, and six of them with a lit button each read as one block
   * of noise rather than six items. So each gets a boundary: a short tick at the head of the
   * text column, which is enough to say "new item" without ruling the page into a table.
   *
   * Drawn as a background rather than a border-top, because a border spans the padding box and
   * would cut straight through the rail. This starts after the gutter, so the rail stays one
   * unbroken line and the tick only organises the text beside it.
   */
  /*
   * `:not(.you)` rather than letting the mark be overridden afterwards, because the tick and the
   * mark's band are the same property and whichever lands last wins. Saying here that the mark
   * is not a candidate is the only version of this that cannot be undone by source order.
   */
  .ladder li + li:not(.you) {
    background-image: linear-gradient(var(--hairline), var(--hairline));
    background-repeat: no-repeat;
    background-position: calc(var(--gutter) + 1rem) 0;
    background-size: 4.5rem 1px;
  }

  /* and nothing above the rung after the mark either: the foot of the band is that boundary */
  .ladder li.you + li {
    background-image: none;
  }
}

.note {
  color: var(--muted);
  font-size: 0.85rem;
  max-width: 68ch;
}

.panel {
  background: var(--surface);
  border: 1px solid var(--divider);
  padding: 1.1rem 1.25rem;
  margin: 1.25rem 0;
}

/* the donor's own page: what they can act on down the left, what we hold down the right */
.columns {
  display: grid;
  gap: 0 3rem;
  align-items: start;
}

@media (min-width: 52rem) {
  .columns {
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  }
}

/* a column starts at its own top, not two and a half rems below it */
.columns > div > h2:first-child {
  margin-top: 0.5rem;
}

/* the shipping form: the one place a donor types anything */
.stack {
  display: flex;
  flex-direction: column;
  gap: 1.2rem;
  margin: 1.5rem 0;
  max-width: 32rem;
}

.stack > label {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.9rem;
  color: var(--muted-strong);
}

.stack > label input {
  width: 100%;
  padding: 0.7rem;
  border: 1px solid var(--divider);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}

.stack input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.stack .button {
  align-self: flex-start;
  margin-top: 0.6rem;
}

/* six short values, so they are worth showing all at once rather than hiding in a menu */
.sizes {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.sizes legend {
  padding: 0;
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
  color: var(--muted-strong);
}

.sizes .options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.sizes input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.sizes span {
  display: block;
  min-width: 3.2rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--divider);
  background: var(--bg);
  text-align: center;
  font-weight: 600;
  cursor: pointer;
}

.sizes input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

.sizes input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.banner {
  border: 1px solid var(--divider);
  padding: 0.85rem 1rem;
  margin: 0.75rem 0;
  font-size: 0.9rem;
}

/* a confirmation is a toast, not a fact: the list below already says the reward is claimed,
   so this takes itself away rather than sitting there until the donor navigates off */
.banner.flash {
  max-height: 8rem;
  overflow: hidden;
  animation: flash-out 0.4s 5s forwards;
}

@keyframes flash-out {
  to {
    max-height: 0;
    margin-block: 0;
    padding-block: 0;
    border-color: transparent;
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .banner.flash {
    animation-duration: 1ms;
  }
}

.banner.warn {
  border-color: var(--warn);
  color: var(--warn);
}

.banner.danger {
  border-color: var(--danger);
  color: var(--danger);
}

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

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

th {
  color: var(--muted);
  font-weight: 500;
  font-size: 0.8rem;
}

.scroll {
  overflow-x: auto;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.75rem;
  margin: 1.25rem 0;
}

.stats div {
  border: 1px solid var(--divider);
  padding: 0.7rem 0.8rem;
}

.stats dt {
  color: var(--muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.stats dd {
  margin: 0.2rem 0 0;
  font-size: 1.25rem;
  font-weight: 600;
}

.voided {
  color: var(--muted);
  text-decoration: line-through;
}

/* the OBS browser source: transparent, bottom left, no site chrome */
body.overlay {
  background: transparent;
}

.hud {
  position: fixed;
  left: 2rem;
  bottom: 2rem;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 0.5rem;
  padding: 0.85rem 1.1rem;
  color: #fff;
}

.hud .figure {
  font-size: 1.75rem;
  font-weight: 600;
  color: var(--accent);
}

.hud .bar {
  width: 18rem;
  margin-top: 0.5rem;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 999px;
}

/* an earned reward is the way into the claim, so the whole card is the target */
.tiers li.claimable {
  box-shadow: inset 0 0 0 1px var(--accent), var(--raised);
}

.tiers li.claimable:hover,
.tiers li.claimable:focus-within {
  box-shadow: inset 0 0 0 2px var(--accent), var(--raised);
}

.tiers .claim {
  color: var(--accent);
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: none;
}

.tiers .claim::after {
  content: '';
  position: absolute;
  inset: 0;
}

/* what has been claimed, and what is already made */
.claims {
  margin: 1.25rem 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--divider);
  max-width: 32rem;
}

.claims li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.8rem 1rem;
  background: var(--surface);
}

.claims .name {
  font-weight: 600;
}

.claims .state {
  color: var(--muted);
  font-size: 0.85rem;
}

/* details already given, shown rather than asked for a second time */
.onfile {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-width: 32rem;
}

/* the label and its value; the child selector matters now that an address is several lines */
.onfile > div {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.onfile dt {
  color: var(--muted);
  font-size: 0.8rem;
}

/* an address reads as a parcel label: one line each, tight enough to read as one block */
.onfile dd {
  margin: 0;
  line-height: 1.45;
}

/* what this claim is for, named under the heading rather than as it */
p.claiming {
  margin: -0.4rem 0 1rem;
  font-size: 1.15rem;
  color: var(--muted-strong);
}

p.claiming strong {
  color: var(--accent);
  font-weight: 600;
}

/* the donor's own tips: two columns, so the amounts line up under each other */
.tips {
  max-width: 24rem;
}

.tips td:last-child {
  text-align: right;
}

.tips tfoot td {
  font-weight: 600;
  border-top: 1px solid var(--divider);
}

/* the admin tip row: two controls side by side without wrapping the table */
.actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.reassign {
  display: flex;
  gap: 0.3rem;
}

.reassign input {
  padding: 0.3rem;
  border: 1px solid var(--divider);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 0.8rem;
  min-width: 12rem;
}

/* the rider's work list: a phone, one hand, a marker in the other. Rows are big, the nickname
   is the biggest thing on them, and the button is a thumb target. */
.work {
  margin: 1.25rem 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.work li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1rem;
  background: var(--surface);
  box-shadow: var(--raised);
}

.work li.done {
  opacity: 0.5;
}

.work li > a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex: 1;
  text-decoration: none;
  color: var(--fg);
}

.work .what {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.work .who {
  font-size: 1.25rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.work .where {
  font-size: 0.85rem;
  color: var(--muted-strong);
  overflow-wrap: anywhere;
}

.work .count {
  color: var(--accent);
  font-weight: 600;
  white-space: nowrap;
}

.work form {
  margin: 0;
}

.work .button {
  white-space: nowrap;
}

/* the ledger search: one box, and the only way anybody finds a payment among hundreds */
.search {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1.25rem 0;
}

.search input {
  flex: 1 1 20rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--divider);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
}

.poll {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1.25rem 0;
}

.poll form {
  margin: 0;
}
/*
 * The route map: a server-rendered sketch with a real map laid over it.
 *
 * The sketch stays in the flow and gives the figure its height, so `#map` can fill it
 * absolutely and MapLibre gets a sized container from the first frame. It is faded in rather
 * than swapped in, because the style paints its own background the moment it initialises and
 * the sketch is the better picture until the tiles have actually arrived.
 *
 * `--w` and `--h` come from the generated viewBox, so the box is the drawing's own shape and
 * the map inherits it.
 */
.route {
  margin: 1.5rem 0 0;
}

/*
 * The map takes the whole column, and the height cap is what actually binds on a laptop: the
 * ride is a long north-south line, so the drawing is portrait and width follows from it. Two
 * figures underneath do not earn a column beside it.
 *
 * The cap stays under a full viewport on purpose. Scrolling over the map zooms it, so there has
 * to be page left above and below to scroll the page by.
 */
.map {
  position: relative;
  margin: 0 auto;
  width: min(100%, calc(82vh * var(--w) / var(--h)));
}

.map .sketch {
  display: block;
  width: 100%;
  aspect-ratio: var(--w) / var(--h);
  background: var(--surface);
  border: 1px solid var(--divider);
}

.map #map {
  position: absolute;
  inset: 0;
  opacity: 0;
  border: 1px solid var(--divider);
}

.map.live #map {
  opacity: 1;
}

/* once the tiles are up the sketch is only in the way, but it still owns the height */
.map.live .sketch {
  visibility: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .map #map {
    transition: opacity 0.25s ease-out;
  }
}

.map .land path {
  fill: #232830;
  stroke: #39414c;
  vector-effect: non-scaling-stroke;
  stroke-width: 1;
}

.map .track path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.map .track .end {
  fill: var(--accent);
  stroke: var(--bg);
  vector-effect: non-scaling-stroke;
  stroke-width: 2;
}

.map .places .pin {
  fill: var(--muted);
}

.map .places text {
  fill: var(--muted-strong);
  font-size: 13px;
  font-weight: 500;
  transform: translate(7px, 4.5px);
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* MapLibre ships its own chrome; only what clashes with a dark page is worth overriding */
.maplibregl-ctrl-attrib {
  font-size: 0.7rem;
}

.maplibregl-ctrl-attrib a {
  color: inherit;
}

/* a pair of figures centred under the map, not two panels stretched across the page */
.route .stats {
  grid-template-columns: repeat(auto-fit, minmax(8rem, 11rem));
  justify-content: center;
  margin: 1.25rem 0 0;
}

/* the coastline's licence asks to be named, and nothing else on the page has to be this quiet */
.credit {
  margin: 2rem 0 0;
  color: var(--muted);
  font-size: 0.75rem;
}
