/* bm-tracker styles.
 *
 * Bootstrap 5.3.8 handles the framework: typography, grid, forms, buttons,
 * tables, badges, alerts, dark mode. Everything here is the app's own
 * vocabulary, which no framework would know about.
 *
 * Colours are Bootstrap's CSS variables rather than literals, so the whole app
 * follows the light/dark switch for free.
 */

:root {
  --bm-accent: #0d6efd;
  --bm-accent-soft: rgba(13, 110, 253, 0.1);
  --bm-normal: #198754;
  --bm-hard: #dc3545;
  --bm-loose: #0d6efd;
  --bm-flame: #fd7e14;
  --bm-flame-glow: rgba(253, 126, 20, 0.6);
  --bm-warn: #997404;
  --bm-warn-soft: rgba(255, 193, 7, 0.18);
  --bm-danger-soft: rgba(220, 53, 69, 0.12);
  --bm-normal-soft: rgba(25, 135, 84, 0.12);
  --bm-normal-edge: rgba(25, 135, 84, 0.4);
  --bm-warn-edge: rgba(255, 193, 7, 0.5);
  --bm-danger-edge: rgba(220, 53, 69, 0.45);
  --bm-scrim: rgba(0, 0, 0, 0.45);
  --bm-rare: #6f42c1;
  --bm-legendary: #d39e00;
  --bm-on-accent: #fff;
  /* The text on an inverted surface. Two variables, never one twice: this
     used to be the same variable as the background, which made the badge
     tooltip invisible in both themes. */
  --bm-tooltip-text: #f8f9fa;
}

/* --- the log page ------------------------------------------------------ */

.bm-entries {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}

.bm-entries > li {
  align-items: flex-start;
  border-bottom: 1px solid var(--bs-border-color);
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  padding: 0.75rem 0;
}

.bm-entries__body {
  min-width: 0;
}

.bm-entries__remove {
  flex: 0 0 auto;
}

.feed__remove {
  margin-top: 0.5rem;
  text-align: right;
}

.bm-note {
  margin: 0.25rem 0 0.5rem;
  font-style: italic;
}

.bm-note--superseded {
  color: var(--bs-secondary-color);
}

/* The Bristol scale: a coloured dot, a name, and a plain-language
 * description, so the choice works without relying on colour alone. */
.bristol-option {
  align-items: center;
  cursor: pointer;
  gap: 0.75rem;
  /* 44px minimum touch target: the whole row is tappable, not just the dot. */
  min-height: 44px;
  padding: 0.25rem 0;
}

.bristol-option .form-check-input {
  flex: 0 0 auto;
  margin: 0;
}

.bristol-dot {
  align-items: center;
  border-radius: 50%;
  color: var(--bm-on-accent);
  display: inline-flex;
  flex: 0 0 auto;
  font-weight: 700;
  height: 2rem;
  justify-content: center;
  width: 2rem;
}

.bristol-hard { background-color: var(--bm-hard); }
.bristol-normal { background-color: var(--bm-normal); }
.bristol-loose { background-color: var(--bm-loose); }

/* --- dashboard --------------------------------------------------------- */

.tiles {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
  list-style: none;
  margin: 1rem 0;
  padding: 0;
}

.tile {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding: 0.75rem 0.5rem;
  text-align: center;
}

.tile__value {
  display: block;
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.1;
}

.tile__label {
  color: var(--bs-secondary-color);
  display: block;
  font-size: 0.8rem;
}

/* The streak flame. One glyph rather than a GIF per streak length: colour and
 * glow step up with the streak, and it themes for free in dark mode. */
.tile--flame {
  background-color: var(--bm-warn-soft);
  border-color: var(--bm-flame);
}

.tile--flame .tile__value {
  color: var(--bm-flame);
  text-shadow: 0 0 12px var(--bm-flame-glow);
}

.bristol-bars {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}

.bristol-bars > li {
  align-items: center;
  display: grid;
  gap: 0.5rem;
  /* dot, type, bar, count. Every track is minmax(0, ...) so a long value or a
     wide label shrinks rather than pushing the row wider than the screen. */
  grid-template-columns: 2rem 5rem minmax(0, 1fr) 2.5rem;
  min-height: 32px;
}

.bristol-bars__note {
  color: var(--bs-secondary-color);
  font-size: 0.82rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bar {
  background-color: var(--bm-normal);
  border-radius: var(--bs-border-radius-sm);
  display: block;
  height: 0.75rem;
  /* --w is a percentage of the busiest type, computed in the route, so a bar
     is never wider than its track however many of one type was logged. */
  min-width: 0.25rem;
  width: calc(var(--w) * 1%);
}

/* --- leaderboard ------------------------------------------------------- */

/* --- navigation --------------------------------------------------------- */

.app-topbar {
  position: sticky;
  top: 0;
  z-index: 10;
}

@media (max-width: 575.98px) {
  .app-topbar__right .btn {
    font-size: 0.78rem;
    padding: 0.2rem 0.55rem;
  }
}

/* A fixed bottom bar, thumb-reachable on a phone. The page reserves the height
 * so the last row of content is never hidden behind it. */
.app-tabs {
  background-color: var(--bs-body-bg);
  border-top: 1px solid var(--bs-border-color);
  bottom: 0;
  display: grid;
  /* `auto-fit`, not a fixed count: this bar held four tabs in a five-column
   * grid, so a phantom fifth column squeezed them all into the left four fifths
   * and left them looking left-aligned. This sizes to whatever is in the bar. */
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  left: 0;
  position: fixed;
  right: 0;
  z-index: 10;
}

body {
  padding-bottom: 4.25rem;
}

.app-tab {
  align-items: center;
  color: var(--bs-secondary-color);
  display: flex;
  flex-direction: column;
  font-size: 0.72rem;
  gap: 0.1rem;
  /* 44px minimum touch target. */
  min-height: 44px;
  justify-content: center;
  padding: 0.4rem 0.15rem;
  text-decoration: none;
}

.app-tab--on {
  color: var(--bs-primary);
  font-weight: 600;
}

.app-tab__glyph {
  font-size: 1.25rem;
  line-height: 1;
}

/* --- celebrations -------------------------------------------------------- */

.celebrate {
  background: linear-gradient(135deg, var(--bm-warn-soft), var(--bm-warn-soft));
  border: 1px solid var(--bm-warn-edge);
  border-radius: var(--bs-border-radius);
  margin: 0 0 1rem;
  padding: 0.85rem 1rem;
}

.celebrate__said {
  font-size: 1.05rem;
  margin: 0;
}

.celebrate__unlocked {
  margin: 0.5rem 0 0;
}

/* --- the achievements grid ---------------------------------------------- */

.achievement-grid {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
}

.achievement-tile {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.75rem;
}

.achievement-tile--on {
  background-color: var(--bm-warn-soft);
  border-color: var(--bm-warn-edge);
}

.achievement-tile__glyph {
  font-size: 1.5rem;
  line-height: 1;
}

.achievement-tile__name {
  font-weight: 600;
}

.achievement-tile__desc {
  color: var(--bs-secondary-color);
  font-size: 0.82rem;
}

.feed__points {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
}

/* --- Phase 9: the mobile pass ------------------------------------------- */

/* Phones get a slightly tighter page so a 44px touch target is not competing
 * with the edges for space. */
@media (max-width: 575.98px) {
  .container {
    padding-left: 0.85rem;
    padding-right: 0.85rem;
  }

  h1 {
    font-size: 1.5rem;
  }

  .tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .achievement-grid {
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  }

  /* Form controls get the same 44px minimum as the tab bar, so a thumb is not
   * aiming at a 28px box. */
  .form-control,
  .form-select,
  .btn {
    min-height: 44px;
  }

  .help__rules dt {
    margin-top: 0.75rem;
  }
}

/* Respect the notch on a phone, and stop the fixed tab bar sitting under the
 * home indicator. */
@supports (padding: max(0px)) {
  .app-tabs {
    padding-bottom: env(safe-area-inset-bottom, 0);
  }

  body {
    padding-bottom: calc(4.25rem + env(safe-area-inset-bottom, 0px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .celebrate,
  .app-tab {
    transition: none;
  }
}

.help__rules dt {
  font-weight: 600;
}

.help__rules dd {
  color: var(--bs-secondary-color);
  margin-bottom: 0.25rem;
}

.help__worked {
  padding-left: 1.1rem;
}


/* --- badges, and the tooltip that explains them ------------------------ */

.achievement-icons {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.achievement-icon {
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  cursor: help;
  display: inline-flex;
  line-height: 0;
  padding: 0.2rem;
  position: relative;
}

.achievement-icon svg {
  display: block;
  height: 30px;
  width: 30px;
}

.achievement-icon--common { border-color: var(--bs-secondary); }
.achievement-icon--uncommon { border-color: var(--bs-info); }
.achievement-icon--rare { border-color: var(--bm-rare); }
.achievement-icon--legendary { border-color: var(--bm-legendary); }

/* Shown on hover *and* on focus, so it is not mouse-only. */
.achievement-icon__tip {
  /* An inverse pair, not one variable twice: the text has to be the other
     colour from the background, and it has to stay that way when the theme
     flips. The text once read from the same variable as the background, which
     made the label invisible in both themes. */
  background-color: var(--bs-body-color);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  bottom: calc(100% + 0.4rem);
  box-shadow: var(--bs-box-shadow);
  color: var(--bm-tooltip-text);
  display: none;
  font-size: 0.78rem;
  font-weight: 400;
  left: 50%;
  line-height: 1.3;
  padding: 0.5rem 0.6rem;
  position: absolute;
  transform: translateX(-50%);
  width: max-content;
  max-width: 15rem;
  z-index: 20;
}

.achievement-icon__tip strong {
  display: block;
}

.achievement-icon:hover .achievement-icon__tip,
.achievement-icon:focus .achievement-icon__tip,
.achievement-icon:focus-within .achievement-icon__tip {
  display: block;
}

.achievement-icon:focus {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}

.badge-ico {
  display: inline-block;
  vertical-align: -0.35em;
}

.feed__more {
  margin: 1rem 0;
}

/* --- achievements page --------------------------------------------------- */

.achievement-tile--common { border-left: 3px solid var(--bs-secondary); }
.achievement-tile--uncommon { border-left: 3px solid var(--bs-info); }
.achievement-tile--rare { border-left: 3px solid var(--bm-rare); }
.achievement-tile--legendary { border-left: 3px solid var(--bm-legendary); }

.achievement-tile__glyph {
  display: block;
  height: 40px;
  margin-bottom: 0.25rem;
  width: 40px;
}

.achievement-tile__glyph--locked {
  opacity: 0.45;
}

.achievement-tile--on {
  background-color: var(--bm-warn-soft);
}

.achievement-tile .progress {
  height: 0.3rem;
  margin-top: 0.3rem;
}

/* --- feed cards ---------------------------------------------------------- */

.feed {
  display: grid;
  gap: 0.75rem;
}

.feed__card {
  border-left: 3px solid var(--bs-secondary);
}

.feed__card--note {
  border-left-color: var(--bs-info);
}

.feed__card--achievement {
  border-left-color: var(--bm-legendary);
}

.feed__head {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.35rem;
}

.feed__head--solo {
  margin-bottom: 0.15rem;
}

.feed__head--solo .feed__when {
  margin-left: 0;
}

.feed__who {
  font-weight: 600;
  text-decoration: none;
}

.feed__when {
  color: var(--bs-secondary-color);
  font-size: 0.78rem;
  margin-left: auto;
}

.feed__quote {
  font-size: 1.02rem;
  margin: 0 0 0.4rem;
}

/*
 * Free text is allowed to be one long unbroken run, and people paste URLs.
 *
 * A 286-character URL in a note made the entries page 19,624px wide against a
 * 1,365px viewport — the whole document scrolled sideways, header included,
 * because the note sat in a table cell and a table cell does not shrink below
 * its content. There was no `overflow-wrap` anywhere in this file, so nothing
 * anywhere had a way to break.
 *
 * `anywhere`, not `break-word`: the failure is scroll width rather than
 * clipping, and `break-word` only breaks a word that would already have
 * overflowed on its own — which in an auto-sized table cell is never, because
 * the cell grows instead. `anywhere` also lowers the min-content contribution,
 * which is what lets the cell shrink at all.
 */
.bm-note,
.feed__quote,
.entries__note,
.achievement-tile__desc,
.achievement-icon__tip {
  overflow-wrap: anywhere;
}

.feed__verb {
  color: var(--bs-secondary-color);
  font-size: 0.82rem;
  margin: 0 0 0.2rem;
}

.feed__award {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin: 0;
}

/* One prize per line: its picture, its name and the points
 * that prize alone earned. Sharing a line would push the
 * names apart, and one badge summing the run would say how
 * much the event was worth rather than what each prize was. */
.feed__award-one {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

/* The verb is a small grey aside and the name is the point of the card, so the
 * name is pulled back up to the body colour. Left in the verb's own colour and
 * size it read as part of the aside, and a feed of unlocks all looked alike
 * again — which is the thing the tooltip was supposed to stop. */
.feed__award-name {
  color: var(--bs-body-color);
  font-size: 0.95rem;
  font-weight: 600;
}

/* The badge and its explanation are the same component on the feed and on a
 * person's page, so they are the same component here. The feed grew its own
 * `badge-icon` classes instead, and nothing ever styled them: with no
 * `display: none` the tooltip was permanently inline, and with no positioning
 * it had nowhere to go but on top of the picture. The name beside the icon was
 * a second, always-visible copy of the tooltip's own first line.
 *
 * Reusing `achievement-icon` rather than styling `badge-icon` is the point:
 * one rule, and `badges.js` already measures and pulls both tooltips back
 * inside the window. */

.person__tier {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  margin: 1.1rem 0 0.4rem;
  text-transform: uppercase;
}

.feed__total {
  font-size: 1.6rem;
  font-weight: 700;
  margin: 0;
}

.feed__foot {
  margin-top: 0.3rem;
}


/* --- the all-entries table ---------------------------------------------- */

.entries th {
  font-size: 0.78rem;
  text-transform: uppercase;
}

.entries__note {
  max-width: 22rem;
}

/* Below the small breakpoint the same rows become cards. This is the pattern
 * the leaderboard already uses, for the same reason: six columns cannot fit a
 * phone, and a table that scrolls sideways makes you pan sideways to read a
 * single note. Nothing is hidden either way — every field is still shown, the
 * header just moves beside its value. */
@media (max-width: 575.98px) {
  .entries,
  .entries tbody,
  .entries tr,
  .entries td {
    display: block;
    width: auto;
  }

  .entries thead {
    display: none;
  }

  /* One line per entry: the date, the type, the labels, and Remove pinned to
   * the right. A note takes a line of its own, always, so where the text
   * lands does not depend on which optional fields that particular entry
   * happened to have.
   *
   * The row is a flex box rather than a stack, which is what makes the pinning
   * possible: `margin-left: auto` on the action pushes it to the end of the
   * first line whatever else is in the row. `order` moves the action ahead of
   * the note visually, and leaves it last in the DOM, so a screen reader still
   * reads the entry and then the button that removes it. */
  .entries tr {
    align-items: center;
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-left: 3px solid var(--bs-secondary);
    border-radius: var(--bs-border-radius);
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-bottom: 0.3rem;
    padding: 0.3rem 0.5rem;
  }

  .entries td {
    border: 0;
    display: inline-flex;
    font-size: 0.82rem;
    line-height: 1.35;
    padding: 0;
    vertical-align: middle;
  }

  /* The field name comes from the data-label the template already carries, so
   * the label and the value can never drift apart. It is a title attribute
   * here rather than visible text: a phone-width row has room for the value,
   * and the label is what the dot and the tag already say. */
  .entries td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    width: 1px;
  }

  .entries td:first-child {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }

  /* The note is the one field worth its own line, and it is the reason most
   * people open this page. Full basis, so it always wraps to a second line
   * rather than sharing one with the row above it. */
  .entries__note {
    flex: 1 1 100%;
    margin: 0;
    max-width: none;
    order: 2;
  }

  /* An empty cell is not said out loud either: `display: none` removes it from
   * the accessibility tree as well as from the screen. */
  .entries td:empty {
    display: none;
  }

  .entries .tag {
    font-size: 0.68rem;
  }

  .entries__action {
    margin-left: auto;
    order: 1;
  }

  /* Short enough to sit in a 32px row rather than making the row one. */
  .entries__action .btn {
    font-size: 0.68rem;
    line-height: 1.1;
    padding: 0.2rem 0.45rem;
  }
}

.pager {
  align-items: center;
  display: flex;
  gap: 0.5rem;
  justify-content: center;
  margin: 1rem 0;
}

.pager__pos {
  color: var(--bs-secondary-color);
  font-size: 0.85rem;
}

/* --- the bottom tab bar -------------------------------------------------- */

/*
 * The bar is `position: fixed`, which browsers do not always honour while a
 * touch scroll is settling: the bar can be painted outside the viewport for the
 * duration of the scroll and appear to vanish. Three things stop that.
 *
 * 1. `height` as well as `top`/`bottom`, so the box is explicit rather than
 *    stretched, which is where iOS is pickiest.
 * 2. A high `z-index` and its own background, so it is never painted under the
 *    content scrolling past it.
 * 3. The page reserves the bar's height in `padding-bottom`, so the last row of
 *    content never ends up underneath it and never needs scrolling to reveal.
 */
.app-tabs {
  box-sizing: border-box;
  height: calc(3.75rem + env(safe-area-inset-bottom, 0px));
  overflow: hidden;
  z-index: 1030;
}

@media (max-width: 575.98px) {
  .app-tabs {
    height: auto;
    min-height: calc(3.25rem + env(safe-area-inset-bottom, 0px));
  }
}

/* Badge tooltips are positioned in CSS and corrected by js/badges.js; this caps
 * the width on very narrow screens so it still fits if the script has not run
 * yet — it would otherwise flash wide on first paint. */
.achievement-icon__tip {
  max-width: min(15rem, calc(100vw - 2rem));
}


/* --- strain -------------------------------------------------------------- */

.strain-field legend {
  font-size: 1rem;
}

/* The three strain options sit in a row on a phone: it is one tap either way,
 * and a column of three would push the submit button off the screen. */
.strain-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
}

.strain-option {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0;
  min-height: 44px;
  align-items: center;
}

.strain-pill {
  border: 1px solid var(--bs-border-color);
  border-radius: 999px;
  font-size: 0.78rem;
  padding: 0.1rem 0.5rem;
  white-space: nowrap;
}

.strain-1 { background-color: var(--bm-normal-soft); border-color: var(--bm-normal-edge); }
.strain-2 { background-color: var(--bm-warn-soft); border-color: var(--bm-warn-edge); }
.strain-3 { background-color: var(--bm-danger-soft); border-color: var(--bm-danger-edge); }

/* --- icons ---------------------------------------------------------------- */

.ico {
  display: inline-block;
  height: 1.25em;
  vertical-align: -0.2em;
  width: 1.25em;
}

/* The date and time fields are left exactly as the platform draws them.
 *
 * This used to strip their chrome: `-webkit-appearance: none` on the input —
 * which is what actually removes the picker indicator in Chromium, hiding the
 * pseudo-element on its own does not — plus a drawn button standing in for it.
 * The reason was that Chromium draws the indicator with a font that is not
 * always installed, and the field showed an empty box rather than a calendar.
 *
 * Worth solving. Not worth solving that way. `::-webkit-calendar-picker-
 * indicator` is a WebKit-only selector and does nothing in Firefox, which
 * draws an indicator of its own and keeps it — so in every browser but
 * Chromium each field carried two icons for one control, and the one that was
 * not ours was the one the browser had already made correct.
 *
 * The indicator is the platform's own, in the user's locale and accessibility
 * settings, and it is the affordance that says this field opens something. It
 * stays, and nothing here overrides the control it belongs to. */

/* --- the top bar ---------------------------------------------------------- */

.app-topbar__inner {
  align-items: center;
  display: flex;
  gap: 0.5rem;
}

.app-topbar__brand {
  align-items: center;
  display: inline-flex;
  font-weight: 600;
  margin-right: auto;
}

/* A subscript, so it hangs off the baseline of the wordmark rather than
 * sitting beside it. Same size as a footnote, not a badge: this says the
 * software is unfinished, and a pill would say it louder than that. */
.app-topbar__beta {
  color: var(--bs-secondary-color);
  font-size: 0.55em;
  font-weight: 400;
  letter-spacing: 0.04em;
  margin-left: 0.25em;
  text-transform: lowercase;
  vertical-align: sub;
}

.app-topbar__flame {
  color: var(--bm-flame);
  height: 1.3em;
  margin-right: 0.35rem;
  width: 1.3em;
}

.app-drawer-toggle {
  align-items: center;
  color: var(--bs-body-color);
  display: inline-flex;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  text-decoration: none;
}

.app-drawer-toggle__glyph {
  height: 1.5em;
  width: 1.5em;
}

/* --- the drawer ----------------------------------------------------------- */

/* Closed by default; `#nav` in the URL is the only thing that opens it. */
.app-drawer {
  visibility: hidden;
}

.app-drawer:target {
  visibility: visible;
}

.app-drawer__scrim {
  background-color: var(--bm-scrim);
  inset: 0;
  opacity: 0;
  position: fixed;
  transition: opacity 0.18s ease-out;
  z-index: 1040;
}

.app-drawer:target .app-drawer__scrim {
  opacity: 1;
}

.app-drawer__panel {
  background-color: var(--bs-body-bg);
  border-right: 1px solid var(--bs-border-color);
  bottom: 0;
  display: flex;
  flex-direction: column;
  left: 0;
  max-width: 82vw;
  padding-bottom: env(safe-area-inset-bottom, 0);
  padding-top: env(safe-area-inset-top, 0);
  position: fixed;
  top: 0;
  transform: translateX(-100%);
  transition: transform 0.2s ease-out;
  width: 17rem;
  z-index: 1050;
}

.app-drawer:target .app-drawer__panel {
  transform: translateX(0);
}

.app-drawer__head {
  align-items: center;
  border-bottom: 1px solid var(--bs-border-color);
  display: flex;
  justify-content: space-between;
  min-height: 56px;
  padding: 0 0.75rem 0 1rem;
}

.app-drawer__title {
  font-weight: 600;
}

.app-drawer__close {
  align-items: center;
  color: var(--bs-body-color);
  display: inline-flex;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  text-decoration: none;
}

.app-drawer__list {
  list-style: none;
  margin: 0;
  overflow-y: auto;
  padding: 0.5rem 0;
}

.app-drawer__list a {
  align-items: center;
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  display: flex;
  min-height: 48px;
  padding: 0 1rem;
  text-decoration: none;
}

.app-drawer__list a:hover,
.app-drawer__list a:focus-visible {
  background-color: var(--bs-secondary-bg);
}

/*
 * `fixed`, not `absolute`. The skip link is off the top of the *document*
 * until it is focused, and a document-relative `top: 0.5rem` on a page that is
 * already scrolled put it at -343px from the top of the viewport — invisible to
 * the one person it exists for, who tabs first precisely because the page is
 * long enough to be worth skipping.
 */
.skip-link {
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  left: 0.5rem;
  padding: 0.5rem 0.75rem;
  position: fixed;
  top: -4rem;
  transition: top 0.15s;
  z-index: 1060;
}

.skip-link:focus {
  top: 0.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .app-drawer__panel,
  .app-drawer__scrim {
    transition: none;
  }
}

/* --- the feed, split by whose it is -------------------------------------- */

.feed__card--bm {
  border-left-color: var(--bm-normal);
}

.feed__card--day {
  border-left-color: var(--bs-secondary);
}

/* Your own cards are set apart, because the feed is two different things at
 * once: a record of what you did, and a glimpse of everyone else. */
.feed__card--mine {
  background-color: var(--bm-accent-soft);
}

/* Everything you did gets the same tint. Notes and achievements were reset to
 * transparent on the grounds that they were "other people's" cards too — which
 * is true, and also why a reader could not tell their own day from somebody
 * else's at a glance. The name says who; the tint says it is yours. */

.feed__bm {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
}

.feed__bm strong {
  font-variant-numeric: tabular-nums;
}

.feed__nothing {
  color: var(--bs-secondary-color);
  font-style: italic;
  margin: 0;
}

/* --- the log form, as cards ---------------------------------------------- */

/*
 * Every input here is a real input inside a label. The card is the styling of
 * the control rather than a div pretending to be one, which is what keeps the
 * whole thing keyboard-operable, arrow-key navigable, and announced correctly.
 * The input is transparent rather than `display: none`, because a hidden input
 * cannot take focus.
 */
.picks {
  border: 0;
  margin: 0 0 1.1rem;
  padding: 0;
}

.picks__legend {
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 0.45rem;
  padding: 0;
}

.picks__optional {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
  font-weight: 400;
}

.picks__grid {
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
}

.pick {
  display: block;
  margin: 0;
  position: relative;
}

.pick__input {
  height: 100%;
  inset: 0;
  margin: 0;
  opacity: 0;
  position: absolute;
  width: 100%;
  z-index: 1;
}

.pick__card {
  align-items: center;
  background-color: var(--bs-body-bg);
  border: 1.5px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  height: 100%;
  justify-content: center;
  padding: 0.55rem 0.5rem;
  text-align: center;
  transition: border-color 0.12s, background-color 0.12s, transform 0.12s;
}

.pick:hover .pick__card {
  border-color: var(--bs-secondary);
}

.pick__title {
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1.2;
}

.pick__hint {
  color: var(--bs-secondary-color);
  font-size: 0.7rem;
  line-height: 1.2;
}

.pick__icon {
  height: 1.35rem;
  margin-bottom: 0.1rem;
  width: 1.35rem;
}

.pick__input:checked + .pick__card {
  background-color: var(--bm-accent-soft);
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 1px var(--bs-primary) inset;
}

.pick__input:checked + .pick__card .pick__title {
  color: var(--bs-primary);
}

.pick__input:focus-visible + .pick__card {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* The Bristol cards carry their own band colour when chosen, so the scale is
 * readable at a glance without reading the number. */
.pick__card--hard.pick__input:checked + .pick__card,
.pick__input:checked + .pick__card--hard {
  background-color: var(--bm-danger-soft);
  border-color: var(--bm-hard);
  box-shadow: 0 0 0 1px var(--bm-hard) inset;
}

.pick__input:checked + .pick__card--normal {
  background-color: var(--bm-normal-soft);
  border-color: var(--bm-normal);
  box-shadow: 0 0 0 1px var(--bm-normal) inset;
}

.pick__input:checked + .pick__card--loose {
  background-color: var(--bm-accent-soft);
  border-color: var(--bm-loose);
  box-shadow: 0 0 0 1px var(--bm-loose) inset;
}

.pick__input:checked + .pick__card--nothing {
  background-color: var(--bs-secondary-bg);
  border-color: var(--bs-secondary);
  box-shadow: 0 0 0 1px var(--bs-secondary) inset;
}

.pick__card--nothing .pick__icon {
  color: var(--bs-secondary-color);
}

.pick__card--spicy {
  border-color: var(--bm-warn-edge);
  color: var(--bm-warn);
}

.pick__card--urgent {
  border-color: var(--bm-danger-edge);
  color: var(--bs-danger);
}

.pick__card--strain-1 { border-color: var(--bm-normal-edge); }
.pick__card--strain-2 { border-color: var(--bm-warn-edge); }
.pick__card--strain-3 { border-color: var(--bm-danger-edge); }

.pick__input:checked + .pick__card--spicy {
  background-color: var(--bm-warn-soft);
  border-color: var(--bm-warn);
  box-shadow: 0 0 0 1px var(--bm-warn) inset;
}

.pick__input:checked + .pick__card--urgent {
  background-color: var(--bm-danger-soft);
  border-color: var(--bs-danger);
  box-shadow: 0 0 0 1px var(--bs-danger) inset;
}

/*
 * "Nothing today" greys the per-BM fields out. `disabled` on the fieldset does
 * two things at once: the controls stop being submitted, and they stop being
 * focusable, so a keyboard user is not walked into a control that will be
 * thrown away.
 */
.picks:disabled {
  opacity: 0.4;
}

.picks:disabled .pick__input {
  cursor: not-allowed;
}

.log-form__time .form-control {
  max-width: 9rem;
}

.log-form__submit {
  min-height: 44px;
  padding: 0.5rem 1.5rem;
}

/* --- a logged BM, as a card ---------------------------------------------- */

.bm-cards {
  display: grid;
  gap: 0.6rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  list-style: none;
  margin: 0 0 1.2rem;
  padding: 0;
}

.bm-card {
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-left: 3px solid var(--bm-normal);
  border-radius: var(--bs-border-radius);
  padding: 0.55rem 0.7rem;
}

.bm-card__head {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.bm-card__type {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
}

.bm-card__note {
  font-size: 0.9rem;
  margin: 0.35rem 0 0.5rem;
}

.bm-card .tag {
  margin-right: 0.25rem;
}

.tag {
  border: 1px solid var(--bs-border-color);
  border-radius: 999px;
  display: inline-block;
  font-size: 0.72rem;
  padding: 0.05rem 0.5rem;
  white-space: nowrap;
}

.tag--spicy {
  background-color: var(--bm-warn-soft);
  border-color: var(--bm-warn-edge);
  color: var(--bm-warn);
}

.tag--urgent {
  background-color: var(--bm-danger-soft);
  border-color: var(--bm-danger-edge);
  color: var(--bs-danger);
}

.tag--effort {
  background-color: var(--bs-secondary-bg);
}

.tag--strain-1 { border-color: var(--bm-normal-edge); }
.tag--strain-2 { border-color: var(--bm-warn-edge); }
.tag--strain-3 { border-color: var(--bm-danger-edge); }

@media (prefers-color-scheme: dark) {
  .pick__card--spicy { color: var(--bm-warn); }
  .tag--spicy { color: var(--bm-warn); }
}

@media (prefers-reduced-motion: reduce) {
  .pick__card {
    transition: none;
  }
}

/* --- the when row on the log form ---------------------------------------- */

/* Date and time sit together at the top, because together they are the moment
 * being recorded and apart they are two questions. They wrap on a narrow
 * screen rather than shrinking to unreadable. */
.log-form__when {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 1.1rem;
}

.log-form__when > div {
  flex: 1 1 9rem;
  min-width: 0;
}

.log-form__when .form-label {
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 0.25rem;
}

.log-form__when input[type="time"] {
  max-width: none;
}

/* --- the leaderboard ------------------------------------------------------ */

.board__head {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.board__title {
  font-size: 1.5rem;
  margin: 0;
}

.board__year {
  align-items: center;
  display: flex;
  gap: 0.4rem;
}

.board__year .form-control {
  width: 6rem;
}

/* The admin people table. It carried the leaderboard's `board` class, which is
 * `display: grid` — a grid on a <table> throws away the table box model
 * entirely, so the columns collapsed and the whole thing sat in the left third
 * of the page. Its own class, and a plain full-width table. */
/*
 * Above a phone width the actions column is sized and right-aligned. Below it
 * the row becomes a card, one field per line, labelled from the `data-label`
 * the template already carries.
 *
 * This was previously `table-layout: fixed` with a 22rem actions column at
 * every width. On a 390px phone that column wanted most of the screen, so the
 * User and Status columns were squeezed into the few pixels left and their
 * contents overlapped on the left. It also pushed the action buttons — including
 * Reset data — off the right edge, which is why that button looked missing.
 */
/* The four account actions, on one line, so a row is not four times taller
 * than the person in it. Reset data stays below: it needs a typed confirmation,
 * and a text box does not belong in a row of buttons. */
.admin-actions {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.admin-actions form {
  margin: 0;
}

.admin-reset {
  align-items: center;
  display: flex;
  gap: 0.35rem;
  margin-top: 0.35rem;
  max-width: 22rem;
}

.admin-reset .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

.admin-people {
  table-layout: fixed;
  width: 100%;
}

.admin-people th:last-child,
.admin-people td:last-child {
  text-align: right;
  width: 26rem;
}

.admin-people th:nth-child(2) {
  width: 8rem;
}

@media (max-width: 767.98px) {
  .admin-people,
  .admin-people tbody,
  .admin-people tr,
  .admin-people td {
    display: block;
  }

  .admin-people thead {
    display: none;
  }

  .admin-people tr {
    border-bottom: 1px solid var(--bs-border-color);
    padding: 0.6rem 0;
  }

  .admin-people td {
    border: 0;
    padding: 0.35rem 0;
    text-align: left;
    width: auto;
  }

  .admin-people td::before {
    color: var(--bs-secondary-color);
    content: attr(data-label);
    display: block;
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    margin-bottom: 0.15rem;
    text-transform: uppercase;
  }

  .admin-people td:last-child {
    width: auto;
  }
}

.board {
  display: grid;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/*
 * One row per person, two lines tall. The rank, the name and the points are the
 * row; everything else is a labelled strip under it, so the number the page
 * exists to show is the largest thing on the page rather than the same size as
 * the four beside it.
 */
.board__row {
  align-items: baseline;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  column-gap: 0.7rem;
  display: grid;
  grid-template-areas:
    "rank who points"
    "stats stats stats";
  grid-template-columns: auto minmax(0, 1fr) auto;
  padding: 0.55rem 0.85rem;
  row-gap: 0.4rem;
}

.board__row.is-you {
  background-color: var(--bm-accent-soft);
  border-color: var(--bs-primary);
}

.board__rank {
  align-items: center;
  background-color: var(--bs-secondary-bg);
  border-radius: 50%;
  color: var(--bs-secondary-color);
  display: inline-flex;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  grid-area: rank;
  height: 1.65rem;
  justify-content: center;
  width: 1.65rem;
}

.board__row--podium .board__rank {
  background-color: var(--bs-warning-bg-subtle, var(--bm-warn-soft));
  color: var(--bm-warn);
}

.board__who {
  align-items: center;
  color: var(--bs-body-color);
  display: flex;
  flex-wrap: wrap;
  font-weight: 600;
  gap: 0.35rem;
  grid-area: who;
  min-width: 0;
  text-decoration: none;
}

.board__who:hover {
  text-decoration: underline;
}

.board__chip {
  background-color: var(--bs-secondary-bg);
  border-radius: 999px;
  color: var(--bs-secondary-color);
  font-size: 0.68rem;
  font-weight: 500;
  padding: 0.05rem 0.45rem;
}

.board__chip--you {
  background-color: var(--bs-primary);
  color: var(--bm-on-accent);
}

.board__points {
  align-items: baseline;
  display: flex;
  font-variant-numeric: tabular-nums;
  gap: 0.3rem;
  grid-area: points;
}

.board__points strong {
  font-size: 1.35rem;
  line-height: 1;
}

.board__points-label {
  color: var(--bs-secondary-color);
  font-size: 0.72rem;
}

.board__stats {
  border-top: 1px solid var(--bs-border-color);
  display: grid;
  gap: 0.3rem 1rem;
  grid-area: stats;
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  margin: 0;
  padding-top: 0.4rem;
}

.board__stats > div {
  display: flex;
  gap: 0.35rem;
  justify-content: start;
}

/* A column wide enough to stretch its label away from its
 * number: the pair read as two things, and the number sat
 * against the next column's label. Narrow columns are too
 * small for the gap to matter, and the numbers at their
 * right edge are the look there. */
@media (max-width: 767.98px) {
  .board__stats > div {
    justify-content: space-between;
  }
}

.board__stats dt {
  color: var(--bs-secondary-color);
  font-size: 0.72rem;
  font-weight: 400;
}

.board__stats dd {
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  margin: 0;
}

.board__empty {
  padding: 1rem 0;
}

/* --- appearance ----------------------------------------------------------- */

/*
 * Colour tokens.
 *
 * Every accent the app uses is a token rather than a literal, for two reasons.
 * One is the obvious one: a colour named in one place can be changed in one
 * place. The other is reader-mode extensions — Dark Reader and its relatives
 * parse a page's colours and rewrite them, and a page carrying a couple of
 * dozen inline rgba() values is a page the parser gets wrong. Everything below
 * resolves through a variable, and a variable is the easiest thing there is for
 * it to classify and override.
 *
 * They are defined on :root rather than on a dark selector so that *any* of
 * light, dark, or a reader-mode inversion lands on a palette that is already
 * coherent, rather than on a light palette with dark patches in it.
 */

[data-bs-theme="dark"] {
  --bm-accent: #6ea8fe;
  --bm-accent-soft: rgba(110, 168, 254, 0.16);
  --bm-normal: #4ade80;
  --bm-hard: #ff6b6b;
  --bm-loose: #6ea8fe;
  --bm-flame: #ffa94d;
  --bm-flame-glow: rgba(255, 169, 77, 0.55);
  --bm-warn: #ffd43b;
  --bm-warn-soft: rgba(255, 212, 59, 0.18);
  --bm-danger-soft: rgba(255, 107, 107, 0.16);
  --bm-normal-soft: rgba(74, 222, 128, 0.16);
  --bm-normal-edge: rgba(74, 222, 128, 0.45);
  --bm-warn-edge: rgba(255, 212, 59, 0.45);
  --bm-danger-edge: rgba(255, 107, 107, 0.45);
  --bm-scrim: rgba(0, 0, 0, 0.6);
  --bm-rare: #a78bfa;
  --bm-legendary: #ffd43b;
  --bm-on-accent: #212529;
  --bm-tooltip-text: #212529;
}

.settings-section {
  border-top: 1px solid var(--bs-border-color);
  margin-top: 1.5rem;
  padding-top: 1.1rem;
}

/* An explicit modifier rather than :first-of-type, which is per element type
 * and so matched the first <form> and the first <section> alike — taking the
 * divider above "Your details" with it. */
.settings-section--first {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

.settings-section__title {
  font-size: 1.05rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
}

.settings-section__submit {
  margin-top: 0.9rem;
  min-height: 44px;
  padding: 0.4rem 1.2rem;
}

/* Three choices, so three columns: a grid that reflowed put the third card on
 * a row of its own, which made a single decision look like two. */
.picks__grid--theme {
  gap: 0.5rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.pick__card--theme {
  gap: 0.35rem;
  padding: 0.7rem 0.6rem;
}

/* A two-tone swatch, drawn in the tokens rather than in literals, so each
 * preview is the theme it names rather than a picture of it. */
.theme-swatch {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-sm);
  display: block;
  height: 2.2rem;
  margin-bottom: 0.3rem;
  overflow: hidden;
  position: relative;
  width: 100%;
}

.theme-swatch--light { background-color: #f8f9fa; }
.theme-swatch--dark { background-color: #212529; }
.theme-swatch--auto {
  background: linear-gradient(105deg, #f8f9fa 0 50%, #212529 50% 100%);
}

.theme-swatch__pane {
  background-color: #0d6efd; /* literal: a picture of the theme */
  border-radius: 2px;
  bottom: 0.4rem;
  left: 0.4rem;
  position: absolute;
  width: 1.4rem;
  height: 0.3rem;
}

.settings-name {
  margin-bottom: 1rem;
  max-width: 26rem;
}

.settings-name__field {
  margin-bottom: 0.2rem;
}

.settings-list {
  border-top: 1px solid var(--bs-border-color);
  display: grid;
  padding-top: 0.8rem;
  gap: 0.3rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin: 0;
}

.settings-list > div {
  display: flex;
  gap: 0.4rem;
  justify-content: space-between;
}

.settings-list dt {
  color: var(--bs-secondary-color);
  font-weight: 400;
}

.settings-list dd {
  font-weight: 600;
  margin: 0;
}

/* --- the entries page header --------------------------------------------- */

/* Title, count, year and button on one line: the year is a control on this
 * page, not a section heading, and a block of its own pushed the rows a screen
 * down. */
.entries-bar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.6rem;
  margin-bottom: 0.9rem;
}

.entries-bar__title {
  font-size: 1.4rem;
  margin: 0;
}

.entries-bar__count {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
  margin-right: auto;
  white-space: nowrap;
}

/* The year control is one unbreakable span: a 390px phone will move the whole
 * thing to a second line, but never split the select away from its button. */
.entries-bar__year {
  align-items: center;
  display: inline-flex;
  gap: 0.35rem;
  white-space: nowrap;
}

.entries-bar__year-label {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
}

.entries-bar__year .form-select {
  width: auto;
}

/* --- focus --------------------------------------------------------------- */

/*
 * One focus ring for the whole app, in the app's own accent colour.
 *
 * Without this every control fell back to the user agent's default: a 1px
 * auto outline in near-black. That is invisible against the dark theme, it
 * disagrees with the cards' own focus treatment, and it is the single thing a
 * keyboard user is looking for.
 *
 * `:focus-visible` rather than `:focus` so a mouse click does not leave a ring
 * behind, but a Tab does. The two-px outline with an offset clears the 44px
 * touch targets rather than sitting inside them.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* The pick cards draw their own selected state, so the ring is drawn inside
 * the card rather than around it, or the two outlines read as a mistake. */
.pick__input:focus-visible + .pick__card {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* Bootstrap sets its own focus ring on form controls, which is fine, but it
 * overrides ours when both match; this restores the accent. */
.form-control:focus-visible,
.form-select:focus-visible {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
  outline: none;
}

/* --- tap targets --------------------------------------------------------- */

/*
 * 44px is the smallest target a thumb can be expected to hit, and the names on
 * the feed, the leaderboard and the people list are the main tap target on all
 * three. They were 19-24px tall, because they were laid out as inline text and
 * never given a height.
 *
 * `inline-flex` rather than `block`: a name sitting on its own line should not
 * become a 44px band of empty space, but it does need to be a box to put the
 * height on.
 */
.feed__who,
.board__who,
.people-row__name {
  align-items: center;
  display: inline-flex;
  min-height: 44px;
}

/* On the people list the name and its badges are the only way into a person,
 * so the link fills the row it sits in rather than being a 30px island. */
.people-row__name {
  margin: -0.5rem 0;
  padding: 0.5rem 0;
}

/* Inside a feed card the name is a header, but it is still the only way into
 * that person from the card, so it keeps the full 44. The card grows to suit
 * it rather than the other way round: a 44px row is a 44px row. */
.feed__head {
  min-height: 44px;
}


/* --- the reward screen --------------------------------------------------- */

.reward {
  inset: 0;
  position: fixed;
  z-index: 1080;
}

.reward::before {
  background-color: var(--bm-scrim);
  content: "";
  inset: 0;
  position: absolute;
}

.reward__panel {
  background-color: var(--bs-body-bg);
  border-radius: var(--bs-border-radius-lg);
  left: 50%;
  max-width: 24rem;
  padding: 1.25rem 1.25rem 1.1rem;
  position: absolute;
  text-align: center;
  top: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 2rem);
  box-shadow: var(--bs-box-shadow-lg);
}

.reward__lines {
  list-style: none;
  margin: 0 0 0.5rem;
  padding: 0;
  text-align: left;
}

.reward__line {
  align-items: baseline;
  border-bottom: 1px solid var(--bs-border-color);
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  padding: 0.35rem 0;
}

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

.reward__label {
  font-size: 0.92rem;
}

.reward__detail {
  color: var(--bs-secondary-color);
  display: block;
  font-size: 0.74rem;
}

.reward__points {
  color: var(--bm-normal);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  white-space: nowrap;
}

.reward__total {
  align-items: baseline;
  border-top: 2px solid var(--bs-border-color);
  display: flex;
  justify-content: space-between;
  margin: 0.3rem 0 0.8rem;
  padding-top: 0.6rem;
}

.reward__total-label {
  font-size: 1rem;
  font-weight: 600;
}

.reward__total-points {
  color: var(--bm-normal);
  font-size: 1.9rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1;
}

.reward__said {
  color: var(--bs-body-color);
  font-size: 0.95rem;
  margin: 0 0 0.7rem;
}

/* An unlock, as a card rather than a badge. It has a name, a sentence saying
 * what earns it, and a value, and a badge cannot hold any of that. */
.reward__unlocked {
  list-style: none;
  margin: 0 0 0.7rem;
  padding: 0;
  text-align: left;
}

.reward__award {
  align-items: center;
  background-color: var(--bm-warn-soft);
  border: 1px solid var(--bm-warn-edge);
  border-radius: var(--bs-border-radius);
  display: flex;
  gap: 0.6rem;
  margin-bottom: 0.4rem;
  padding: 0.45rem 0.6rem;
}

.reward__award:last-child {
  margin-bottom: 0;
}

.reward__award-icon {
  flex: 0 0 auto;
  height: 34px;
  width: 34px;
}

.reward__award-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}

.reward__award-name {
  font-size: 0.9rem;
}

.reward__award-desc {
  color: var(--bs-secondary-color);
  font-size: 0.76rem;
  line-height: 1.25;
}

.reward__award-points {
  color: var(--bm-warn);
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.reward__dismiss {
  min-height: 44px;
  padding: 0.5rem 2rem;
}

/* Hidden before its turn, and arrived into place. Both are opt-in on a class,
 * so the finished markup is the default and a blocked script still shows
 * everything. */
.reward__pending {
  opacity: 0;
  transform: translateY(6px);
}

.reward__arrive {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.32s ease-out, transform 0.32s ease-out;
}

.reward__arrive--total {
  transform: scale(1.06);
}

.reward__arrive--total.reward__total {
  transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .reward__pending {
    opacity: 1;
    transform: none;
  }

  .reward__arrive,
  .reward__arrive--total {
    transition: none;
  }
}

/* A tier the person has not earned the right to see yet. The count is honest
 * and the contents are not. */
.achievements__locked {
  font-size: 0.85rem;
  margin: -0.4rem 0 0.7rem;
}

.achievement-tile--hidden {
  align-items: center;
  border-left: 1px solid var(--bs-border-color);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  justify-content: center;
  min-height: 7rem;
  opacity: 0.55;
  padding: 0.6rem;
  text-align: center;
}

.achievement-tile__glyph--hidden {
  /*
   * A silhouette rather than a question mark. The tile keeps the dashed ring,
   * so it still reads as locked, and the badge inside is desaturated to a
   * shadow: you can see there is a shape to collect and roughly what kind of
   * thing it is, without the artwork doing the rewarding. Names and points
   * stay behind the threshold; only the picture comes early.
   */
  border: 1.5px dashed var(--bs-border-color);
  border-radius: 50%;
  filter: grayscale(1);
  height: 2.6rem;
  object-fit: contain;
  /* 0.4 was invisible. The silhouette is meant to say "there is a shape worth
   * having, in shadow" and at four tenths on a white card it said nothing at
   * all — a heart, a speech bubble and a quill all came out as the same
   * near-nothing. It has to be dim enough to read as not-yet and bright enough
   * to read as a drawing. */
  opacity: 0.62;
  width: 2.6rem;
}

.reward__unlocked-title {
  font-size: 0.95rem;
  font-weight: 700;
  margin: 0 0 0.45rem;
  text-align: left;
}
