/* RAEM Workout Tracker — styles.
   Mobile-first: the base rules target phones (designed at 375px wide,
   the narrowest phone worth caring about), and the media queries at the
   bottom adjust things upward for larger screens.

   THE COLOUR VARIABLES BELOW ARE OVERWRITTEN AT RUNTIME. app.js sets
   --accent and --bg on <html> from whatever the member picked, which
   repaints every accent on the page at once. The values here are the
   fallbacks used before that happens, and for anyone who has never
   chosen. Nothing outside this block should hard-code an accent.

   THE DEFAULT IS BLACK AND ORANGE. Near-black background, #fc5200
   accent — the app's identity, and what every new member sees until
   they pick something else. The matching value lives in
   app/routers/api.py as THEME_COLORS[0]; change one, change both. */

:root {
  --bg: #0b0d10;
  --card: #14181d;
  --card-2: #1a1f26;
  --line: rgba(255, 255, 255, 0.07);
  --text: #f2f5f7;
  --muted: #97a1ab;
  --accent: #fc5200;
  --accent-soft: rgba(252, 82, 0, 0.22);
  --accent-faint: rgba(252, 82, 0, 0.1);
  --good: #3ddc97;
  --bad: #ff8a7a;
  --radius: 16px;
  /* Big enough for a thumb. Every tappable thing on this page is at
     least this tall and this wide. */
  --tap: 44px;
  /* The fixed bottom tab bar. Named here rather than repeated, because
     the page's bottom padding has to match it exactly or the last card
     ends up underneath the bar. */
  --tabbar-height: 58px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  min-height: 100%;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}

.shell {
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px calc(16px + env(safe-area-inset-left))
    calc(24px + env(safe-area-inset-bottom))
    calc(16px + env(safe-area-inset-right));
}

.card {
  width: 100%;
  max-width: 420px;
  background: var(--card);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: var(--radius);
  padding: 32px 24px;
  text-align: center;
}

.eyebrow {
  margin: 0 0 4px;
  font-size: 0.75rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 700;
}

h1 {
  margin: 0 0 12px;
  font-size: 1.75rem;
  line-height: 1.2;
  font-weight: 700;
}

.tagline {
  margin: 0 0 16px;
  font-size: 1.05rem;
  color: var(--text);
}

.detail {
  margin: 0;
  font-size: 0.9rem;
  color: var(--muted);
}

/* --- Sign-in page ---------------------------------------------------
   Buttons are full-width and 52px tall on purpose: that is comfortably
   above the ~44px minimum a thumb needs to hit reliably on a phone. */

.actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 20px 0;
}

.btn {
  display: block;
  width: 100%;
  min-height: 52px;
  padding: 14px 20px;
  border-radius: 12px;
  border: 1px solid transparent;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  /* Stops iOS Safari zooming the whole page when a button is tapped. */
  touch-action: manipulation;
  transition: opacity 0.15s ease, background 0.15s ease;
}

.btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn-primary {
  background: var(--accent);
  color: #14181d;
}

.btn-secondary {
  background: transparent;
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.18);
}

.btn-secondary:not(:disabled):hover {
  background: rgba(255, 255, 255, 0.06);
}

.status {
  margin: 0;
  min-height: 1.4em;
  font-size: 0.9rem;
  color: var(--muted);
}

/* The "you are in WhatsApp's browser" line on the sign-in page. Amber
 * rather than red: it is guidance, not a failure. [hidden] is spelled
 * out because a <p> is display:block by default and would otherwise
 * ignore the attribute. */
.notice {
  margin: 0 0 4px;
  padding: 10px 12px;
  border: 1px solid rgba(255, 196, 84, 0.35);
  border-radius: 10px;
  background: rgba(255, 196, 84, 0.08);
  color: #ffc454;
  font-size: 0.85rem;
  line-height: 1.45;
  text-align: left;
}

.notice[hidden] {
  display: none;
}

.status-error {
  color: #ff8a7a;
}

.status-ok {
  color: #79dfa1;
}

@media (min-width: 600px) {
  .card {
    padding: 48px 40px;
  }

  h1 {
    font-size: 2.25rem;
  }
}

/* =====================================================================
   THE DASHBOARD (index.html)
   =====================================================================
   Everything below is the signed-in home screen. The sign-in page and
   the small Strava result pages use the rules above and are untouched.

   Layout budget at 375px: 16px of padding each side leaves 343px of
   usable width. The 7-day grid divides that into 7 columns with 6px
   gaps, so a tile lands around 43px wide — which is why the tiles use
   repeat(7, 1fr) and never a fixed width. Nothing on this page is
   allowed to be wider than its container, so the page never scrolls
   sideways. */

.app-body {
  /* A gradient background needs something to paint onto; without the
     min-height it stops at the fold on a short page. */
  min-height: 100svh;
  background: var(--bg);
  background-attachment: fixed;
  /* THE BOTTOM PADDING IS THE TAB BAR'S FOOTPRINT. The bar is fixed, so
     it is out of the flow and would otherwise sit on top of the last
     card on the page. --tabbar-height is the bar itself; the safe-area
     inset underneath it is the iPhone's home indicator; the extra 16px
     is breathing room so the last card does not touch the bar. */
  padding: calc(12px + env(safe-area-inset-top))
    calc(16px + env(safe-area-inset-right))
    calc(var(--tabbar-height) + 16px + env(safe-area-inset-bottom))
    calc(16px + env(safe-area-inset-left));
  /* Belt and braces against horizontal scroll on small screens. */
  overflow-x: hidden;
}

/* --- Header ---------------------------------------------------------- */

.app-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 2px;
}

.app-header-left .eyebrow {
  margin: 0;
}

.app-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}

.app-header-right {
  display: flex;
  gap: 8px;
  flex: none;
}

.icon-btn {
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--text);
  font-size: 1.1rem;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.icon-btn:active {
  background: var(--card-2);
}

.icon-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.member-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 0.9rem;
  color: var(--muted);
}

/* The admin link uses .icon-btn, which was written for <button>. These
   two lines are everything an <a> needs to match it. */
a.icon-btn {
  text-decoration: none;
}

a.icon-btn[hidden] {
  display: none;
}

.app-main {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* --- Connect Strava call-to-action ----------------------------------- */
/* Only rendered when strava_connected is false. It is the loudest thing
   on the page on purpose: nothing else in the app works until it is
   done, so it should not look like a dismissible tip. */

.connect-card {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 64px;
  padding: 14px 16px;
  border-radius: var(--radius);
  background: linear-gradient(135deg, #fc5200, #ff8340);
  color: #14181d;
  text-decoration: none;
  font-weight: 700;
  touch-action: manipulation;
}

.connect-card[hidden] {
  display: none;
}

.connect-card-icon {
  font-size: 1.5rem;
  flex: none;
}

.connect-card-text {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}

.connect-card-text small {
  font-weight: 600;
  font-size: 0.8rem;
  opacity: 0.8;
}

.connect-card-arrow {
  margin-left: auto;
  font-size: 1.3rem;
  flex: none;
}

/* --- Hero: the big number -------------------------------------------- */

.hero {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 22px;
  padding: 22px 20px 20px;
  text-align: center;
  /* The accent glow behind the card. It redraws itself whenever the
     member changes their colour, because it is built from the same
     custom property. */
  box-shadow: 0 0 0 1px var(--accent-faint), 0 18px 40px -24px var(--accent-soft);
}

.hero-label {
  margin: 0 0 2px;
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
}

.hero-count {
  margin: 0;
  font-size: 4rem;
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.04em;
  /* Digits all the same width, so the number does not jiggle sideways
     when it ticks from 3 to 4. */
  font-variant-numeric: tabular-nums;
}

.hero-count-done {
  color: var(--accent);
}

.hero-count-slash {
  color: var(--muted);
  opacity: 0.45;
  margin: 0 2px;
  font-weight: 300;
}

.hero-count-target {
  color: var(--muted);
  font-size: 2.25rem;
}

.hero-bar {
  height: 8px;
  margin: 16px 0 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}

.hero-bar-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.hero-status {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.3;
}

.hero-status-good { color: var(--good); }
.hero-status-warn { color: var(--text); }
.hero-status-bad { color: var(--bad); }

/* --- The live stakes --------------------------------------------------- */
/* The [hidden] guards come FIRST, before the display rules underneath.
   A bare `display: flex` below would override the hidden attribute and
   pin an empty money line to a finished week — the same trap the
   celebration scrim fell into. */

.hero-stakes[hidden],
.hero-stakes-note[hidden] {
  display: none;
}

.hero-stakes {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 10px 0 0;
  padding: 9px 12px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
}

.hero-stakes-line {
  font-size: 0.95rem;
  font-weight: 800;
  line-height: 1.3;
}

/* Money at risk is the app's one genuinely uncomfortable number, so it
   wears the warning colour. Safe is quiet green — the point of the line
   is the relief, not a second celebration. */
.hero-stakes-owing .hero-stakes-line {
  color: var(--bad);
}

.hero-stakes-safe .hero-stakes-line {
  color: var(--good);
}

.hero-stakes-note {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  line-height: 1.4;
}

.hero-streak {
  margin: 8px 0 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}

.hero-streak[hidden] {
  display: none;
}

.hero-complete {
  box-shadow: 0 0 0 1px rgba(61, 220, 151, 0.25),
    0 18px 40px -24px rgba(61, 220, 151, 0.35);
}

/* --- Panels ----------------------------------------------------------- */

.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 14px;
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.panel-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
}

.panel-note {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
  flex: none;
}

/* --- The Monday-Sunday grid ------------------------------------------- */

.week-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  /* Seven tiles across a 375px phone is a tight budget. Working it
     backwards: 7 x 44px of thumb target is 308px, plus six 4px gaps is
     332px — but the body padding and the card padding together eat
     60px of the 375, leaving only 315. So the grid is pulled 12px out
     past the card's padding on each side, which buys back 24px and
     lands each tile at ~44px. Widen the gap or the padding and the
     tiles drop below the minimum again. */
  gap: 4px;
  margin: 0 -12px;
  padding: 0;
  list-style: none;
}

.day-tile {
  /* min-width:0 lets the tile shrink inside its grid column instead of
     forcing the row wider than the screen. This is the single line that
     keeps the page from scrolling sideways at 375px. */
  min-width: 0;
  width: 100%;
  min-height: 58px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 6px 2px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--muted);
  font-family: inherit;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.12s ease;
}

.day-tile:active {
  transform: scale(0.95);
}

.day-tile:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.day-label {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.day-value {
  font-size: 0.72rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  min-height: 1em;
}

/* A day that counted: filled with the member's own accent. */
.day-tile-hit {
  background: var(--accent);
  border-color: var(--accent);
  color: #0b0d10;
}

/* Today is outlined rather than filled, so "today" and "done" can both
   be true at once and still be told apart. */
.day-tile-today {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.day-tile-future {
  opacity: 0.45;
}

.day-detail {
  margin: 12px 0 0;
  font-size: 0.82rem;
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 1.2em;
}

.day-detail-empty {
  color: var(--muted);
}

.day-detail-line {
  display: block;
  /* Long Strava workout names wrap instead of stretching the card. */
  overflow-wrap: anywhere;
}

.day-detail-line small {
  display: block;
  color: var(--muted);
  font-size: 0.75rem;
}

/* --- 💰 The Ledger -----------------------------------------------------
   The headline is real money that has been paid in. Tapping it opens the
   table of who still owes what, which every member can see.

   BUILT ON <details>/<summary> RATHER THAN A CLICK HANDLER. The browser
   gives the open/close state, the keyboard support and the screen-reader
   announcement for free, and none of it can break when the JavaScript
   fails to load — the card still opens. */

.ledger-card {
  border-radius: var(--radius);
  background: var(--card);
  border: 1px solid rgba(255, 176, 32, 0.28);
  overflow: hidden;
}

.ledger-summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  cursor: pointer;
  /* The default triangle is drawn in a different place in every browser
     and cannot be styled in most of them. The "Who owes what" label on
     the right does that job better. */
  list-style: none;
}

.ledger-summary::-webkit-details-marker {
  display: none;
}

.ledger-icon {
  font-size: 1.4rem;
  flex: none;
}

.ledger-headline {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
  flex: 1 1 auto;
}

.ledger-headline small {
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
}

.ledger-headline strong {
  font-size: 1.5rem;
  font-weight: 800;
  color: #ffb020;
  font-variant-numeric: tabular-nums;
}

.ledger-open {
  flex: none;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.ledger-open::after {
  content: " ▾";
}

.ledger-details[open] .ledger-open::after {
  content: " ▴";
}

.ledger-body {
  padding: 0 16px 14px;
  border-top: 1px solid var(--line);
  margin-top: 2px;
  padding-top: 12px;
}

.ledger-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ledger-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
}

.ledger-row + .ledger-row {
  border-top: 1px solid var(--line);
}

.ledger-name {
  flex: 1 1 auto;
  font-weight: 700;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

/* Red, because it is a debt and softening it would be dishonest. The
   whole point of a shared table is that the number is not hidden. */
.ledger-owes {
  flex: none;
  font-weight: 800;
  font-size: 0.9rem;
  color: #ff6b6b;
  font-variant-numeric: tabular-nums;
}

.ledger-square {
  flex: none;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--muted);
}

/* The "past due" badge that used to sit here was removed on
   2026-08-04 along with the split it labelled — every member now shows
   one flat number. The style is gone rather than left orphaned: dead
   CSS is how a removed feature quietly comes back. */

.ledger-quarter {
  margin: 0 0 8px;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Overpaid. Deliberately NOT green-and-celebratory: it is money the
   group owes them back, which is a thing to fix, not a prize. */
.ledger-credit {
  flex: none;
  font-size: 0.8rem;
  font-weight: 700;
  color: #7dd3fc;
  font-variant-numeric: tabular-nums;
}

/* The [hidden] guard comes FIRST, before the display rule underneath —
   otherwise a bare `display: block` wins over the hidden attribute and
   the empty "Departed" heading is pinned to the card forever. */
.ledger-departed[hidden] {
  display: none;
}

.ledger-departed {
  display: block;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--line);
}

.ledger-departed-title {
  margin: 0 0 2px;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --- The money history, inside the Ledger card ------------------------
   EVERY PAYMENT AND EVERY ADJUSTMENT, FOR EVERY MEMBER. Collapsed by
   default and one tap deep, the same way the whole card is: The Board
   is where you go to see who is winning, and a list of transactions is
   a thing you open when you want it.

   Collapsed is NOT hidden. It is in the document, in the same card as
   the balances, on the tab everybody opens — which is what "the group
   can see it" has to mean if it is to mean anything. */

/* The [hidden] guard first, before any display rule below it —
   otherwise a bare `display: block` beats the hidden attribute and the
   empty-state line is pinned to the card forever. */
.ledger-history-empty[hidden] {
  display: none;
}

.ledger-history {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--line);
}

.ledger-history-summary {
  cursor: pointer;
  list-style: none;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  min-height: 32px;
  display: flex;
  align-items: center;
}

.ledger-history-summary::-webkit-details-marker {
  display: none;
}

.ledger-history-summary::after {
  content: "▸";
  margin-left: 6px;
  font-size: 0.8rem;
  letter-spacing: 0;
}

.ledger-history[open] .ledger-history-summary::after {
  content: "▾";
}

.ledger-history-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ledger-entry {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 0.8rem;
  line-height: 1.4;
}

.ledger-entry-icon {
  flex: none;
}

.ledger-entry-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Green for forgiven, amber for added. The direction of an adjustment
   is the thing worth reading fastest, and the sign alone is four pixels
   of difference between "you were let off $100" and "you owe $100
   more". */
.ledger-entry-amount {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.ledger-entry-forgiven {
  color: #34d399;
}

.ledger-entry-added {
  color: #ffb020;
}

.ledger-entry-paid {
  color: var(--text);
}

.ledger-entry-meta {
  display: block;
  font-size: 0.7rem;
  color: var(--muted);
}

.ledger-history-empty {
  display: block;
  margin: 8px 0 0;
  font-size: 0.75rem;
  color: var(--muted);
}

.ledger-fine {
  margin: 12px 0 0;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--muted);
}

/* --- Avatars ----------------------------------------------------------
   One element, two possible contents:

     * a member's initial, on their own accent colour (the default —
       everybody has one of these from the moment they join);
     * an <img> with their photo, when they have uploaded one or one was
       derived from Strava.

   The initial stays underneath the image rather than being replaced.
   So if the photo ever fails to load — offline, or the bytes were
   removed on another device — the letter is simply revealed again and
   the row still reads properly. There is no broken-image icon and no
   empty circle. */

.avatar {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: 0.9rem;
  color: #0b0d10;
  overflow: hidden;
  position: relative;
  /* Reset, so the same class works on a <button> as on a <span>. */
  padding: 0;
  border: none;
  font-family: inherit;
}

.avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}

/* The one in the header, next to your own name. A button, so it needs
   to look tappable without shouting. */
.avatar-sm {
  width: 28px;
  height: 28px;
  font-size: 0.78rem;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.avatar-sm:focus-visible,
.avatar-lg:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The preview inside the settings sheet: big enough to actually judge
   whether the crop landed on your face. */
.avatar-lg {
  width: 72px;
  height: 72px;
  font-size: 1.75rem;
}

.footnote {
  margin: 6px 0 0;
  text-align: center;
  font-size: 0.78rem;
  color: var(--muted);
}

.board-footnote {
  margin: 10px 0 0;
  font-size: 0.7rem;
  color: var(--muted);
  text-align: center;
}

/* =====================================================================
   THE BOTTOM TAB BAR
   =====================================================================
   Five destinations, fixed to the bottom of the screen where the thumb
   already is. The old design put settings behind a gear in the top-right
   corner — the hardest place on a phone to reach and the easiest to not
   notice.

   THE HEIGHT IS DECLARED ONCE, in --tabbar-height, because .app-body's
   bottom padding has to reserve exactly this much space. If they drift
   apart the bar starts covering the last card on the page.

   THE SAFE-AREA INSET IS PADDING, NOT MARGIN. On an iPhone the bottom
   strip of the screen belongs to the home indicator. Padding lets the
   bar's background run all the way to the physical edge while keeping
   the buttons above the indicator; a margin would leave a strip of page
   showing underneath, which looks like a rendering bug. */

.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  align-items: stretch;
  gap: 4px;
  padding: 4px 8px calc(4px + env(safe-area-inset-bottom));
  background: rgba(11, 13, 16, 0.92);
  border-top: 1px solid var(--line);
  /* Frosted, where the browser supports it: the page scrolling under a
     solid slab looks heavier than it needs to. */
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.tab {
  flex: 1;
  min-width: 0;
  /* WITH FIVE TABS A LONG LABEL MUST CLIP, NEVER OVERFLOW. An
     overflowing nowrap label would widen the page and turn the whole app
     into a horizontal scroller at 320px, which reads as a broken app.
     The two label media queries at the bottom of this file are what stop
     it ever coming to this. */
  overflow: hidden;
  /* Comfortably past the 44px thumb minimum, including the label. */
  min-height: 50px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 4px 2px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.tab-icon {
  font-size: 1.15rem;
  line-height: 1.1;
  /* Greyed out until selected, so the accent below is the only thing
     drawing the eye. Emoji ignore `color`, so this is the only way to
     make one look inactive. */
  filter: grayscale(1);
  opacity: 0.75;
}

.tab-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.tab-on {
  color: var(--accent);
  background: var(--accent-faint);
}

.tab-on .tab-icon {
  filter: none;
  opacity: 1;
}

.tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.tab-panel[hidden] {
  display: none;
}

.tab-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* A panel is focusable (tabindex="-1") so a tab switch can move the
   reading position into it. It must never draw a focus ring for that. */
.tab-panel:focus {
  outline: none;
}

/* =====================================================================
   THE BOARD TAB
   ===================================================================== */

/* --- "This week — the bet": the face-chip strip ------------------------
   The same ranked order as the old full-width leaderboard, in a
   deliberately different shape. The Kings Board directly below is also a
   ranked list of the same faces, and two of those stacked would read as
   one screen printed twice.

   flex-wrap, never a horizontal scroller: seven 44px chips do not fit
   across a 375px phone, so they wrap onto a second line. A row that
   scrolls sideways hides members off the edge of the group. */

.weekstrip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.weekchip {
  flex: 1 1 62px;
  /* Caps how wide a chip can get in a group of two or three, so they
     stay chips rather than stretching into full-width bars. */
  max-width: 84px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 8px 4px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: var(--card-2);
}

.weekchip-name {
  max-width: 100%;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.weekchip-count {
  font-size: 0.78rem;
  font-weight: 800;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Target already hit: green, and done arguing. */
.weekchip-done {
  border-color: rgba(61, 220, 151, 0.4);
  background: rgba(61, 220, 151, 0.1);
}

.weekchip-done .weekchip-count {
  color: var(--good);
}

/* Can no longer reach 4 days before Sunday — the only state on this
   strip that costs money. */
.weekchip-behind {
  border-color: rgba(255, 138, 122, 0.35);
}

.weekchip-behind .weekchip-count {
  color: var(--bad);
}

.weekchip-me {
  box-shadow: inset 0 0 0 1px var(--accent-soft);
}

/* --- 👑 The Kings Board ------------------------------------------------
   The composite: your average place across the four boards below. It is
   the headline of the tab, so it gets the only gold on the page. */

.kings-panel {
  box-shadow: 0 0 0 1px rgba(255, 196, 84, 0.12);
}

.kings {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.kings-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 14px;
  border: 1px solid transparent;
  background: var(--card-2);
}

/* Your own row, so you can find yourself without reading every name. */
.kings-row-me {
  border-color: var(--accent-soft);
  background: var(--accent-faint);
}

/* First place. Deliberately quiet — a soft gold edge and a crown, not a
   trophy ceremony every time somebody opens the app. */
.kings-row-first {
  border-color: rgba(255, 196, 84, 0.45);
  background: linear-gradient(
    90deg,
    rgba(255, 196, 84, 0.14),
    rgba(255, 196, 84, 0.04)
  );
}

.kings-row-first.kings-row-me {
  box-shadow: inset 0 0 0 1px var(--accent-soft);
}

/* Last place: the live 🦥. A faint edge and slightly dimmed, and that is
   the whole treatment. The badge is the joke; making the row shout as
   well would turn a running gag into a punishment, and the person
   wearing it is still in the group. */
.kings-row-slacker {
  border-color: rgba(151, 161, 171, 0.3);
  background: rgba(151, 161, 171, 0.06);
}

.kings-row-slacker .kings-name {
  color: var(--muted);
}

.kings-rank {
  flex: none;
  min-width: 30px;
  font-size: 0.85rem;
  font-weight: 800;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.kings-row-first .kings-rank {
  font-size: 1.05rem;
  color: #ffc454;
}

.kings-words {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

/* A flex row rather than a plain span, so the badges after the name (the
   🦥 and the PENDING chip) keep their full width and the ellipsis falls
   on the NAME instead of eating them from the right. */
.kings-name {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
}

.kings-name-text {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.kings-sloth {
  flex: none;
  font-size: 0.85rem;
}

/* The four ranks that made the average. On screen so the number above is
   checkable rather than something the app just asserts — the same
   reason the weekly board shows the minutes that broke a tie. */
.kings-breakdown {
  font-size: 0.7rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.kings-avg {
  flex: none;
  min-width: 38px;
  text-align: right;
  font-size: 0.9rem;
  font-weight: 800;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* --- The four category boards ------------------------------------------
   <details> cards. Four full lists would be a scroll marathon on a
   phone, and four closed doors would be useless — so a collapsed card
   still names its leader. */

.category-stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.category {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 4px 14px 10px;
}

.category-head {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tap);
  padding: 6px 0;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  /* The default triangle is tiny and sits in the wrong place on iOS.
     ::marker is removed and the whole row is the target instead. */
  list-style: none;
}

.category-head::-webkit-details-marker {
  display: none;
}

.category-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 10px;
}

.category-heading {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}

.category-title {
  font-size: 0.95rem;
  font-weight: 700;
}

.category-subtitle {
  font-size: 0.7rem;
  color: var(--muted);
}

/* Who is winning, readable without opening the card. */
.category-leader {
  margin-left: auto;
  flex: 0 1 auto;
  min-width: 0;
  text-align: right;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Once it is open the leader is on the first row anyway, so repeating it
   in the header is noise. */
.category[open] .category-leader {
  display: none;
}

/* A quiet "open me" cue that flips when it is open. Built from a border
   rather than an emoji so it inherits the text colour and never renders
   as a different glyph on a different phone. */
.category-head::after {
  content: "";
  flex: none;
  margin-left: 8px;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.15s ease;
}

.category[open] .category-head::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.category-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.category-row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  border-radius: 12px;
  border: 1px solid transparent;
  background: var(--card-2);
}

.category-row-me {
  border-color: var(--accent-soft);
  background: var(--accent-faint);
}

.category-row-first .category-rank {
  color: #ffc454;
}

.category-rank {
  flex: none;
  min-width: 24px;
  font-size: 0.78rem;
  font-weight: 800;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.category-name {
  flex: 1;
  min-width: 0;
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.category-value {
  flex: none;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Said out loud on the Furnace card: a hand-logged workout has no
   calories to report, so it adds nothing there. Better to explain it
   than to field the question every quarter. */
.category-footnote {
  margin: 8px 2px 2px;
  font-size: 0.68rem;
  color: var(--muted);
}

/* --- The Settings tab --------------------------------------------------
   This used to be a bottom sheet that slid up over the page. It is now
   an ordinary tab: same four sections, same controls, nothing over
   anything. A sheet is a thing you have to know is there; a tab is a
   thing you can see.

   Each section is its own card so the four kinds of setting — who you
   are, what reaches your phone, how it looks, the plumbing — are visibly
   four things rather than one long run of controls. */

.settings-section {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 4px 14px 16px;
}

.settings-section-title {
  margin: 12px 0 4px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
}

.settings-label {
  margin: 14px 0 8px;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
}

/* Sentence-case explanatory text under a section title. Distinct from
   .settings-label, which is an all-caps heading for a control. */
.settings-note {
  margin: 0 0 12px;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}

/* --- 📜 The Admin record ----------------------------------------------
   A RECORD, NOT A TOOL. No controls beyond "show older", nothing
   tappable in a row, nothing that looks like it could be edited. It
   should read like a printed receipt: quiet, plain, and obviously not
   something the reader is being invited to change.

   The rows are deliberately low-contrast next to everything else in the
   app. This is the least exciting thing on the screen and it earns its
   place by being trustworthy rather than by being noticed. */

.audit-empty[hidden],
.audit-list[hidden] {
  display: none;
}

.audit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.audit-entry {
  display: flex;
  gap: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.audit-entry:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.audit-icon {
  flex: none;
  font-size: 1rem;
  line-height: 1.5;
}

.audit-text {
  min-width: 0;
  flex: 1 1 auto;
}

.audit-summary {
  display: block;
  font-size: 0.84rem;
  line-height: 1.45;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* "Littos · 2h ago". Who and when, under what. Both matter and neither
   is the headline — the sentence is. */
.audit-meta {
  display: block;
  margin-top: 2px;
  font-size: 0.7rem;
  color: var(--muted);
}

.audit-empty {
  display: block;
  margin: 0 0 4px;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}

/* --- Photo and username, in the Profile section ------------------------ */

.profile-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.profile-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.profile-hint {
  width: 100%;
  font-size: 0.72rem;
  color: var(--muted);
}

/* A quiet outlined button. Used for the photo actions and the username
   save, so those three read as one group and none of them competes
   with the accent-filled buttons elsewhere. */
.btn-line {
  min-height: var(--tap);
  padding: 0 14px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.btn-line:not(:disabled):hover {
  background: rgba(255, 255, 255, 0.06);
}

.btn-line:disabled {
  opacity: 0.5;
  cursor: default;
}

.btn-line:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn-line-quiet {
  color: var(--muted);
}

.btn-line-accent {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-faint);
  /* It is an <a>, not a <button>, so it needs the button's own
     vertical centring spelled out. */
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.btn-line-wide {
  width: 100%;
  margin-top: 14px;
}

.btn-line[hidden] {
  display: none;
}

/* --- The install callout, in Notifications ----------------------------- */
/* On an iPhone in a Safari tab the nudge switch cannot work at all, and
   an explanation printed under a greyed-out button is an explanation
   nobody reads. So it gets a panel, above the switch, in the accent
   colour — friendly instructions rather than an error. */

.callout {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin: 4px 0 12px;
  padding: 12px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--accent-soft);
  background: var(--accent-faint);
}

.callout[hidden] {
  display: none;
}

.callout-icon {
  font-size: 1.35rem;
  line-height: 1.2;
  flex: none;
}

.callout-text {
  min-width: 0;
}

.callout-text strong {
  display: block;
  font-size: 0.92rem;
  margin-bottom: 3px;
}

.callout-text p {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--muted);
}

.callout-text p strong {
  display: inline;
  color: var(--text);
  font-size: inherit;
}

/* --- The Account row --------------------------------------------------- */

.account-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card-2);
}

.account-icon {
  font-size: 1.35rem;
  flex: none;
}

.account-text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  min-width: 0;
  flex: 1;
}

.account-text strong {
  font-size: 0.95rem;
}

.account-text small {
  font-size: 0.78rem;
}

.account-ok {
  color: var(--muted);
}

.account-warn {
  color: var(--accent);
}

.account-email {
  margin: 10px 2px 0;
  font-size: 0.76rem;
  color: var(--muted);
  word-break: break-all;
}

.name-row {
  display: flex;
  gap: 8px;
}

.name-row input {
  flex: 1;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 12px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
}

.name-row input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Off screen but still read by a screen reader and still clickable by
   script — which is exactly what the hidden file input needs. Using
   display:none would stop the browser opening the picker at all. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.swatch {
  width: var(--tap);
  height: var(--tap);
  border-radius: 12px;
  border: 2px solid transparent;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  padding: 0;
}

.swatch-bg {
  border: 1px solid var(--line);
}

.swatch-on {
  /* A ring rather than a tick, so it reads on every colour including
     the pale ones. */
  box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--text);
}

.swatch:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

/* Stop the page behind the sheet from scrolling under it. */
body.sheet-open {
  overflow: hidden;
}

/* --- The nudge card, inside the sheet ---------------------------------- */
/* Deliberately quiet when off and accent-lit when on, so the state is
   readable at a glance without having to read the word. */

.nudge-card {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--tap);
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.nudge-card[hidden] {
  display: none;
}

.nudge-card:disabled {
  opacity: 0.55;
  cursor: default;
}

.nudge-card-on {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-faint);
  background: var(--accent-faint);
}

.nudge-icon {
  font-size: 1.35rem;
  flex: none;
}

.nudge-text {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}

.nudge-text strong {
  font-size: 0.95rem;
}

.nudge-text small {
  font-size: 0.78rem;
  color: var(--muted);
}

.nudge-state {
  margin-left: auto;
  flex: none;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.nudge-card-on .nudge-state {
  color: var(--accent);
}

/* --- Larger screens ---------------------------------------------------- */
/* This is a phone app; on a laptop it becomes a centred phone-width
   column rather than a stretched-out mess. */

@media (min-width: 560px) {
  .app-body {
    max-width: 520px;
    margin: 0 auto;
  }

  .sheet {
    max-width: 520px;
    margin: 0 auto;
  }

  .hero-count {
    font-size: 4.75rem;
  }
}

/* ======================================================================
   👑 THE ROYALTY PACK
   ====================================================================== */
/* One gold, used everywhere, so the crown, the decree bar, the locked
   swatch and the confetti are obviously the same idea. It is a fixed
   colour rather than var(--accent) on purpose: the whole point of the
   gold is that it does NOT belong to whoever picked a theme. */

:root {
  --gold: #d4af37;
  --gold-bright: #f5d76e;
  --gold-faint: rgba(212, 175, 55, 0.14);
  --gold-line: rgba(212, 175, 55, 0.4);
}

/* --- The crown overlay ------------------------------------------------- */
/* Sits on the avatar itself, so it follows the face onto every screen
   that draws one. Tucked over the top-right rather than centred above:
   centred would collide with the row above it in a tight list. */

.avatar-crown {
  position: absolute;
  /* Slightly outside the circle, which is what makes it read as a crown
     ON somebody rather than as a sticker over their face. */
  top: -7px;
  right: -5px;
  font-size: 0.72rem;
  line-height: 1;
  /* The avatar clips its own contents so the photo stays round; the
     crown has to escape that, so it is drawn without being clipped by
     using a shadow-free, non-overflowing offset instead. */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
  pointer-events: none;
}

/* .avatar sets overflow:hidden to keep photos round, which would cut the
   crown in half. Only the king's avatar relaxes it, and the photo stays
   round because the <img> has its own border-radius. */
.avatar-king {
  overflow: visible;
  box-shadow: 0 0 0 2px var(--gold);
}

.avatar-king img {
  border-radius: 50%;
}

.avatar-lg .avatar-crown {
  top: -10px;
  right: -6px;
  font-size: 1.1rem;
}

/* --- ⏸ PENDING, the paused chip ----------------------------------------
   ONE STYLE, FIVE SCREENS: the week strip, the Kings Board, the four
   category boards and the Ledger all print the same chip beside the same
   name, because it is the same fact in all five places. See pausedChip
   in app/static/app.js.

   DELIBERATELY THE QUIETEST THING ON THE ROW. Grey text, grey hairline,
   no fill, no colour of its own — it sits at the muted end of the same
   language the 🦥 and the crown use, and it is meant to be read second,
   after the name. It marks somebody whose Strava account was suspended
   through no fault of theirs; a red badge would turn an administrative
   pause into an accusation.

   The [hidden] guard comes FIRST, before the display rule below. A bare
   `display: inline-block` further down would beat the hidden attribute
   outright — that is exactly how a celebration card nobody could dismiss
   once shipped. Nothing here sets [hidden] today (app.js appends the
   chip or does not build it at all), and the guard is stated anyway so
   the next person to reach for it gets the working behaviour. */

.paused-chip[hidden] {
  display: none;
}

.paused-chip {
  display: inline-block;
  /* Baseline-nudged so it sits level with the name rather than riding
     on top of it — the chip is smaller than the text it follows. */
  vertical-align: 1px;
  margin-left: 2px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--muted);
  font-size: 0.58rem;
  font-weight: 700;
  /* Letter-spaced small caps: it reads as a status stamp rather than as
     a word somebody typed into their own name. */
  letter-spacing: 0.06em;
  line-height: 1.6;
  white-space: nowrap;
  /* Never the thing that pushes a name into an ellipsis. */
  flex: none;
}

/* On the week strip it gets its own line under the count, because the
   chip there is 84px wide at most and .weekchip-name ellipsises anything
   that overflows — a badge glued to the name would be the first thing
   cut off. Smaller to match a card that is already 0.68rem. */
.weekchip > .paused-chip {
  margin-left: 0;
  font-size: 0.5rem;
  padding: 0 5px;
}

/* --- 📜 The King's Decree, on My Week ----------------------------------
   THE SAME PROCLAMATION THE BOARD PINS, IN THE PLACE PEOPLE ACTUALLY
   LOOK. Deliberately SLIMMER than the Board's version: this is the home
   tab, and the headline here is your own count. It needles, it does not
   take over.

   The [hidden] guard comes FIRST, before the display rule underneath —
   a bare `display: flex` below would beat the hidden attribute and pin
   an empty gold bar to the top of the tab for the whole group. */

.home-decree[hidden] {
  display: none;
}

.home-decree {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 12px;
  padding: 9px 13px;
  border-radius: var(--radius);
  background: var(--gold-faint);
  border: 1px solid var(--gold-line);
  border-left-width: 3px;
  font-size: 0.86rem;
  line-height: 1.45;
}

.home-decree-mark {
  flex: none;
  font-size: 0.95rem;
}

.home-decree-text {
  flex: 1 1 auto;
  color: var(--gold-bright);
  font-weight: 700;
  /* A decree can be 100 characters of anything somebody typed, so it
     has to be allowed to wrap rather than stretch the layout. */
  overflow-wrap: anywhere;
}

.home-decree-by {
  flex: none;
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 700;
  white-space: nowrap;
}

/* --- The reign counter, on My Week ------------------------------------- */

.reign-banner {
  margin: 0 0 12px;
  padding: 10px 14px;
  border-radius: var(--radius);
  background: var(--gold-faint);
  border: 1px solid var(--gold-line);
  color: var(--gold-bright);
  font-weight: 700;
  font-size: 0.92rem;
  text-align: center;
  letter-spacing: 0.01em;
}

/* --- The King's Decree ------------------------------------------------- */
/* Pinned above everything on The Board. Gold on near-black, with a left
   rule, so it reads as a proclamation rather than as another card. */

.decree {
  margin: 0 0 14px;
  padding: 12px 14px 14px;
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--gold-faint), rgba(212, 175, 55, 0.04));
  border: 1px solid var(--gold-line);
  border-left-width: 3px;
}

.decree-eyebrow {
  margin: 0 0 6px;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--gold);
  font-weight: 800;
  display: flex;
  gap: 6px;
  align-items: center;
}

.decree-text {
  margin: 0;
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--gold-bright);
  line-height: 1.35;
  /* A decree is at most 100 characters, but nothing stops it being 100
     characters with no spaces in it. */
  overflow-wrap: anywhere;
}

.decree-form {
  margin: 0 0 16px;
}

.decree-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.decree-row input {
  flex: 1;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 12px;
  border-radius: 12px;
  border: 1px solid var(--gold-line);
  background: var(--card);
  color: var(--text);
  font: inherit;
}

.decree-row input:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}

/* --- The locked gold swatch -------------------------------------------- */
/* SHOWN TO EVERYBODY, TAPPABLE BY ONE PERSON. A prize nobody can see is
   not a prize — so it keeps its full size and its gold, and wears a
   padlock until the day it is earned. */

.swatch-royal {
  display: grid;
  place-items: center;
  font-size: 1rem;
  line-height: 1;
  border: 2px solid var(--gold-bright);
  background: linear-gradient(145deg, #f5d76e, #b8860b) !important;
  color: rgba(0, 0, 0, 0.75);
}

.swatch-locked {
  cursor: not-allowed;
  /* Dimmed rather than greyed out: it must still obviously be the gold
     one, or the padlock is guarding nothing recognisable. */
  opacity: 0.55;
}

/* ======================================================================
   🎉 THE CELEBRATION
   ====================================================================== */

.confetti-canvas {
  position: fixed;
  inset: 0;
  /* Above the celebration card, which is the only thing it should ever
     be seen in front of. */
  z-index: 90;
  /* Must never eat a tap: the card underneath has to stay dismissible. */
  pointer-events: none;
}

.celebration-scrim[hidden] {
  display: none;
}

.celebration-scrim {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 24px;
  /* The dim. Heavy enough that the app behind it stops competing for
     attention, light enough that it is obviously still there. */
  background: rgba(4, 6, 9, 0.82);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  cursor: pointer;
}

.celebration-card {
  width: 100%;
  max-width: 340px;
  text-align: center;
  padding: 28px 22px 22px;
  border-radius: 22px;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--gold-line);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(255, 255, 255, 0.03) inset;
}

/* --- 🧾 The sombre card ------------------------------------------------
   THE SAME CARD, WEARING A COMPLETELY DIFFERENT FACE. The Monday results
   card rides the celebration queue because the queue solves the hard
   problem (Monday happens whether or not the app is open), but it is not
   a celebration: it goes to the person who owes $100 as well as the one
   who hit 6/4.

   So: dark slate instead of gold, a flat border instead of a glow, and —
   handled in app.js, not here — no confetti at all. Every gold-coloured
   rule above is overridden below rather than being made conditional,
   because a card that was 80% celebratory would read as a bug. */

.celebration-card-somber {
  background: linear-gradient(180deg, #1a1f27, #12161c);
  border-color: rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.celebration-card-somber .celebration-title {
  color: #e2e8f0;
  /* Slightly wider than a ceremony title, and not shouting. A receipt
     is read, not announced. */
  font-weight: 800;
  letter-spacing: 0.04em;
}

.celebration-card-somber .celebration-art {
  /* The gold drop shadow under the emoji is the single most celebratory
     thing on the card. Off. */
  filter: none;
  font-size: 2.6rem;
}

.celebration-card-somber .celebration-body {
  color: var(--muted);
  font-size: 0.86rem;
  /* The body is a run of "Name ✅ 4 · Name 💸 $50" fragments, so it needs
     to be allowed to wrap onto as many lines as it takes. */
  line-height: 1.6;
}

/* Slides up from below rather than fading in: a fade reads as a page
   loading, and this is supposed to read as something arriving. */
.celebration-card-in {
  animation: celebration-rise 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes celebration-rise {
  from {
    opacity: 0;
    transform: translateY(38px) scale(0.94);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* The PREVIEW chip. The [hidden] guard comes first for the same reason
   the scrim's does: a bare `display: inline-block` below would override
   the hidden attribute and pin the label to every real celebration. */
.celebration-preview[hidden] {
  display: none;
}

.celebration-preview {
  display: inline-block;
  margin: 0 0 10px;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px dashed var(--gold-line);
  background: rgba(0, 0, 0, 0.25);
  color: var(--gold);
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.celebration-art {
  display: block;
  font-size: 3.4rem;
  line-height: 1;
  margin-bottom: 10px;
  filter: drop-shadow(0 4px 10px rgba(212, 175, 55, 0.35));
}

.celebration-face {
  margin: 0 auto 14px;
}

.celebration-title {
  margin: 0 0 8px;
  font-size: 1.15rem;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold-bright);
}

.celebration-body {
  margin: 0;
  color: var(--text);
  font-size: 0.95rem;
  line-height: 1.45;
}

.celebration-more {
  margin: 14px 0 0;
  font-size: 0.78rem;
  color: var(--muted);
}

.celebration-hint {
  margin: 16px 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.75;
}

/* ======================================================================
   🏛️ THE HALL OF FAME
   ====================================================================== */

.hall-panel {
  margin-top: 4px;
}

.hall-record {
  margin: 0 0 12px;
  padding: 9px 12px;
  border-radius: 12px;
  background: var(--gold-faint);
  border: 1px solid var(--gold-line);
  color: var(--gold-bright);
  font-size: 0.85rem;
  font-weight: 700;
}

.hall-list {
  display: grid;
  gap: 10px;
}

.hall-season {
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--card-2);
  border: 1px solid var(--line);
}

.hall-quarter {
  margin: 0 0 4px;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted);
  font-weight: 800;
}

.hall-champion {
  margin: 0 0 10px;
  font-size: 1.02rem;
  font-weight: 800;
  color: var(--gold-bright);
}

.hall-belts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}

.hall-belt {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 0.85rem;
  color: var(--muted);
}

.hall-belt strong {
  color: var(--text);
  font-weight: 700;
  text-align: right;
  overflow-wrap: anywhere;
}

/* The slacker is recorded, not shouted. Same size as everything else,
   at the bottom, in the quiet colour. */
.hall-slacker {
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-size: 0.82rem;
  color: var(--muted);
}

/* =====================================================================
   📣 THE FEED
   =====================================================================
   A column of cards, one per workout, with a row of reaction chips at
   the bottom of each.

   THE CARD IS A LONG-PRESS TARGET, which drives two rules here:

     touch-action: manipulation   stops the browser deciding a held
                                  finger means "zoom", which would eat
                                  the gesture on iOS.
     -webkit-touch-callout: none  stops iOS Safari popping its own
                                  "copy / share" bubble over the top of
                                  our picker after half a second.

   user-select is off for the same reason: a long press on selectable
   text turns into a text selection with drag handles, and the reaction
   picker never gets a look in. */

.feed-head {
  margin-bottom: 2px;
}

.feed-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.feedcard {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px 12px 10px;
  touch-action: manipulation;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Your own workouts, marked the same quiet way your row is marked on
   every board: a left edge in your accent colour, not a different card. */
.feedcard-mine {
  border-left: 3px solid var(--accent);
}

.feedcard-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.feedcard-who {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  /* A tap target that is a whole row of a card still has to be a
     comfortable one. */
  min-height: var(--tap);
  padding: 2px 4px 2px 0;
  border-radius: 12px;
  cursor: pointer;
}

.feedcard-names {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.feedcard-name {
  font-size: 0.92rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.feedcard-when {
  font-size: 0.72rem;
  color: var(--muted);
}

/* The ✅ / ⚪ chip. A real button: tapping it reveals the reason the
   workout counted or did not, which is the question it exists to
   prompt. */
.feedcard-mark {
  flex: none;
  min-width: var(--tap);
  min-height: var(--tap);
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: transparent;
  font-size: 1rem;
  cursor: pointer;
  touch-action: manipulation;
}

.feedcard-mark-yes {
  background: rgba(61, 220, 151, 0.1);
  border-color: rgba(61, 220, 151, 0.25);
}

.feedcard-title {
  margin: 8px 0 2px;
  font-size: 0.95rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.feedcard-stats {
  margin: 0;
  font-size: 0.8rem;
  color: var(--muted);
}

/* The [hidden] guard comes FIRST, before the display rule underneath —
   the same pattern as every other collapsible line on this page. */
.feedcard-reason[hidden] {
  display: none;
}

.feedcard-reason {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--card-2);
  font-size: 0.76rem;
  color: var(--muted);
}

/* ---- The reaction chips ---- */

.feedcard-reactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
}

.rchip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 34px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* MINE IS FILLED IN, and in my own accent colour. "Did I already react
   to this?" has to be answerable at a glance from across a gym. */
.rchip-on {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text);
  font-weight: 700;
}

.rchip-emoji {
  font-size: 0.95rem;
  line-height: 1;
}

.rchip-count {
  font-variant-numeric: tabular-nums;
}

.rchip-add {
  min-width: 34px;
  justify-content: center;
  color: var(--muted);
  font-size: 1rem;
  font-weight: 700;
}

.rchip:focus-visible,
.feedcard-mark:focus-visible,
.member-open:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.member-open {
  cursor: pointer;
}

/* ---- Paging ---- */

.feed-empty[hidden],
.feed-more[hidden],
.feed-end[hidden] {
  display: none;
}

.feed-empty,
.feed-end {
  margin: 14px 0 0;
  text-align: center;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Usually invisible: the scroll watcher fires before anybody reaches
   it. It stays a real button for a keyboard, and for a browser with no
   IntersectionObserver. */
.feed-more {
  width: 100%;
  min-height: var(--tap);
  margin-top: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

/* =====================================================================
   👤 ONE MEMBER'S HISTORY
   =====================================================================
   A full-screen slide-over. It deliberately covers the tab bar: it has
   its own back button, and half a navigation bar peeking out from under
   an overlay is how people tap the wrong one. */

.memberview[hidden] {
  display: none;
}

.memberview {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

/* While it is open the page underneath must not scroll — on a phone
   that reads as the app losing its place. */
.body-locked {
  overflow: hidden;
}

.memberview-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(10px + env(safe-area-inset-top)) 12px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--card);
}

.memberview-back {
  flex: none;
  min-width: var(--tap);
  min-height: var(--tap);
  border: 1px solid var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--text);
  font-size: 1.1rem;
  cursor: pointer;
  touch-action: manipulation;
}

.memberview-titles {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.memberview-name {
  font-size: 1rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.memberview-note {
  font-size: 0.74rem;
  color: var(--muted);
}

.memberview-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 12px calc(20px + env(safe-area-inset-bottom));
}

/* =====================================================================
   🔥 THE EMOJI PICKER
   =====================================================================
   A bottom sheet, because it is opened by a thumb and answered by the
   same thumb without the hand moving. Six buttons, one row, each one
   past the 44px minimum. */

.picker-scrim[hidden] {
  display: none;
}

.picker-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.picker-sheet {
  width: 100%;
  max-width: 520px;
  padding: 14px 14px calc(16px + env(safe-area-inset-bottom));
  border-radius: 20px 20px 0 0;
  border-top: 1px solid var(--line);
  background: var(--card);
  animation: picker-up 160ms ease-out;
}

@keyframes picker-up {
  from {
    transform: translateY(18px);
    opacity: 0.4;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.picker-title {
  margin: 0 0 10px;
  font-size: 0.8rem;
  color: var(--muted);
  text-align: center;
  overflow-wrap: anywhere;
}

.picker-row {
  display: flex;
  justify-content: space-between;
  gap: 6px;
}

.picker-emoji {
  flex: 1;
  min-width: var(--tap);
  min-height: 52px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card-2);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

/* The one I already gave, so tapping it again reads as "take it back"
   rather than as a second, identical choice. */
.picker-emoji-on {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.picker-clear[hidden] {
  display: none;
}

.picker-clear {
  width: 100%;
  min-height: var(--tap);
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

/* =====================================================================
   📎 EVIDENCE: THE CHIP AND THE LIGHTBOX
   =====================================================================
   A hand-typed workout cannot be saved without a photo of the watch
   face or the app summary. The chip on the card is how the other six
   people get to look at it, and the lightbox is where they look.

   QUIET, NOT LOUD. This is a receipt, not a trophy: a small outlined
   chip that says the proof exists and can be opened, sitting below the
   stats and above the 🔥 row. Making it shout would turn "he showed his
   working" into an accusation that somebody had to. */

.feedcard-evidence {
  display: inline-block;
  min-height: 32px;
  margin: 2px 0 0;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.76rem;
  cursor: pointer;
  touch-action: manipulation;
}

.feedcard-evidence:active {
  background: var(--card-2);
}

/* The [hidden] guard comes FIRST, before the display rule underneath —
   without it `hidden` loses and the overlay covers the app forever. */
.shot-scrim[hidden] {
  display: none;
}

.shot-scrim {
  position: fixed;
  inset: 0;
  /* Above the picker (40) and the tab bar: a photo opened full screen
     is the only thing on screen while it is open. */
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: calc(16px + env(safe-area-inset-top)) 12px
    calc(16px + env(safe-area-inset-bottom));
  background: rgba(0, 0, 0, 0.88);
  cursor: zoom-out;
}

/* CONTAINED, NEVER CROPPED, and never blown up past its own size: this
   is a screenshot, and the numbers on it are the entire point. `contain`
   is the CSS half of the same decision the server makes when it resizes
   the longest side to 1280px without cropping. */
.shot-image {
  max-width: 100%;
  max-height: 82vh;
  object-fit: contain;
  border-radius: 10px;
  background: #000;
}

.shot-caption {
  margin: 0;
  max-width: 92%;
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.8rem;
  text-align: center;
  overflow-wrap: anywhere;
}

/* For a mouse. A thumb closes it by tapping anywhere at all. */
.shot-close {
  position: absolute;
  top: calc(10px + env(safe-area-inset-top));
  right: 12px;
  width: var(--tap);
  height: var(--tap);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.4);
  color: #fff;
  font-size: 1rem;
  cursor: pointer;
}

/* =====================================================================
   FIVE TABS ON A NARROW PHONE
   =====================================================================
   Five is the ceiling and it is tight. At 375px each button gets about
   71px; at 320px — an iPhone SE, the narrowest screen this has to
   survive — about 60px. THE HEIGHT NEVER MOVES: 50px, comfortably past
   the 44px thumb minimum, at every width.

   WHAT GIVES IS THE LABEL, in two small steps, and .tab clips rather
   than overflows (see the overflow rule on .tab above). A label that
   overflowed instead would push the whole page into a horizontal
   scroll, which on a phone reads as the app being broken.

   THE STEPS ARE DELIBERATELY GENTLE, and that is measured rather than
   guessed: at 320px each button is 63px wide and "The Board" — the
   longest label in the set — renders 47px at 0.63rem and 44px at
   0.6rem. Both fit with room to spare, so there is no reason to shrink
   the text to the point where somebody has to squint at it. */
@media (max-width: 389px) {
  .tabbar {
    gap: 2px;
    padding-left: 6px;
    padding-right: 6px;
  }

  .tab-label {
    font-size: 0.63rem;
    letter-spacing: -0.01em;
  }

  .tab-icon {
    font-size: 1.05rem;
  }
}

/* 320px: an iPhone SE. The gaps go rather than the type. */
@media (max-width: 340px) {
  .tabbar {
    gap: 0;
    padding-left: 2px;
    padding-right: 2px;
  }

  .tab {
    padding-left: 0;
    padding-right: 0;
  }

  .tab-label {
    font-size: 0.6rem;
    letter-spacing: -0.015em;
  }
}

/* =====================================================================
   ⚔️ THE ARENA
   =====================================================================
   Peer-to-peer side challenges. The tab is a stack of small blocks in
   priority order, and the one thing it always shows is the CTA.

   The [hidden] guards come FIRST in every pair, before the display
   rule that would otherwise win — the lesson the celebration scrim
   taught, applied to seven more collapsible things. */

.arena-block[hidden] {
  display: none;
}
.arena-block {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.arena-block-title {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.arena-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.arena-fine {
  margin: 2px 0 0;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--muted);
}

/* --- The CTA, which is also the empty state ---------------------------
   NEVER HIDDEN, at full width, with the accent border it earns. The one
   thing this tab is for is starting a fight, so the button that starts
   one is the most prominent element on it whether the Arena is empty or
   full. Same shape as the Strava connect card on My Week, deliberately:
   members already know what that shape does. */
.arena-cta {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 62px;
  padding: 14px 16px;
  border: 1px solid var(--accent);
  border-radius: 16px;
  background: var(--accent-faint);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.arena-cta-icon {
  font-size: 1.5rem;
  line-height: 1;
}

.arena-cta-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.arena-cta-text strong {
  font-size: 1rem;
}

.arena-cta-text small {
  font-size: 0.76rem;
  color: var(--muted);
}

.arena-cta-arrow {
  color: var(--accent);
  font-size: 1.1rem;
}

/* --- A face + name chip, used on every Arena row --------------------- */
.arena-face {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.arena-face-name {
  font-size: 0.86rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- The live face-off card ------------------------------------------
   TWO SIDES AND A "vs", with the leader lifted out of the background.
   Marking the leader rather than drawing the two identically is the
   whole value of a live score: you should be able to tell who is
   winning without comparing two numbers yourself. */
.arena-duel {
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--card, #12161b);
}

.arena-duel-mine {
  border-color: var(--accent);
}

.arena-duel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 0.88rem;
}

.arena-duel-stake {
  flex: none;
  font-weight: 800;
  color: var(--accent);
}

.arena-faceoff {
  display: flex;
  align-items: center;
  gap: 8px;
}

.arena-side {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 4px;
  border-radius: 12px;
  text-align: center;
}

/* Ahead, right now. Not a trophy — the week is not over. */
.arena-side-leading {
  background: var(--accent-faint);
}

.arena-side .arena-face {
  flex-direction: column;
  gap: 4px;
  text-align: center;
}

.arena-score {
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}

.arena-vs {
  flex: none;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.arena-duel-foot {
  margin: 10px 0 0;
  font-size: 0.76rem;
  color: var(--muted);
  text-align: center;
}

/* --- A pending challenge, and an open debt --------------------------
   Same row shape for both: a face, a line of text, and the buttons the
   person looking is actually allowed to press. */
.arena-pending,
.arena-debt {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 10px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card, #12161b);
}

.arena-duel-mine,
.arena-debt-mine {
  border-color: var(--accent);
}

.arena-pending-what,
.arena-debt-line {
  flex: 1;
  min-width: 120px;
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.arena-pending-actions {
  display: flex;
  gap: 8px;
  flex: none;
}

/* Every button in here is a thumb target at 320px, which is the whole
   reason they are allowed to wrap onto a second line. */
.arena-pending .btn-line,
.arena-debt .btn-line {
  min-height: 44px;
  padding: 6px 14px;
}

/* --- The records board ---------------------------------------------- */
.arena-records {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.arena-record {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 6px;
  border-radius: 10px;
}

.arena-record-me {
  background: var(--accent-faint);
}

.arena-record-rank {
  flex: none;
  width: 1.4em;
  font-size: 0.8rem;
  font-weight: 800;
  color: var(--muted);
  text-align: right;
}

.arena-record .arena-face {
  flex: 1;
}

.arena-record-value {
  flex: none;
  font-size: 0.86rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* --- The history ---------------------------------------------------- */
.arena-history {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.arena-entry {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--line);
}

.arena-entry:last-child {
  border-bottom: 0;
}

.arena-entry-mark {
  flex: none;
  font-size: 1rem;
  line-height: 1.3;
}

.arena-entry-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.arena-entry-text strong {
  font-size: 0.86rem;
  line-height: 1.35;
}

.arena-entry-text small {
  font-size: 0.74rem;
  color: var(--muted);
}

/* A decline is in the history on purpose (declining has a social
   price), and it is drawn quieter than a result — it is a thing that
   did not happen. */
.arena-entry-declined .arena-entry-text strong {
  color: var(--muted);
  font-weight: 600;
}

.arena-history-empty[hidden] {
  display: none;
}
.arena-history-empty {
  margin: 10px 0 0;
  font-size: 0.8rem;
  color: var(--muted);
  text-align: center;
}

/* =====================================================================
   ⚔️ THE COMPOSE SHEET
   =====================================================================
   A bottom sheet, exactly like the emoji picker: opened by a thumb and
   answered by the same thumb without the hand moving. The [hidden]
   guard comes before the display rule, as it must. */

.compose-scrim[hidden] {
  display: none;
}
.compose-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(4, 6, 8, 0.72);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.compose-sheet {
  width: min(520px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
  border-radius: 20px 20px 0 0;
  background: var(--card, #12161b);
  border-top: 1px solid var(--line);
}

.compose-title {
  margin: 0 0 8px;
  font-size: 1.05rem;
  font-weight: 800;
}

.compose-label {
  margin: 8px 0 0;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* 44px minimum on every control, and 16px text — anything smaller makes
   iOS Safari zoom the whole page in when the field is focused, which
   then leaves the sheet half off screen. */
.compose-input {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font: inherit;
  font-size: 16px;
}

.compose-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.compose-note {
  margin: 10px 0 0;
  font-size: 0.74rem;
  line-height: 1.4;
  color: var(--muted);
}

.compose-actions {
  display: flex;
  gap: 10px;
  margin-top: 6px;
}

.compose-actions .btn-line {
  flex: 1;
  min-height: 48px;
}

/* Respect a member who has asked their phone to stop animating things. */
@media (prefers-reduced-motion: reduce) {
  .hero-bar-fill,
  .day-tile {
    transition: none;
  }

  /* The celebration card still appears, and still says what they won.
     It just does not move. confetti.js checks the same setting and
     fires nothing at all. */
  .celebration-card-in {
    animation: none;
  }

  /* The picker still slides into view instantly instead of sliding. */
  .picker-sheet {
    animation: none;
  }
}

/* The "What's new" release card: a readable left-aligned feature list.
   The [hidden] guard is load-bearing — see the celebration-scrim lesson. */
.celebration-bullets[hidden] {
  display: none;
}
.celebration-bullets {
  text-align: left;
  margin: 12px 0 4px;
  padding-left: 1.1em;
  display: grid;
  gap: 8px;
  max-height: 46vh;
  overflow-y: auto;
}
.celebration-bullets li {
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--text-soft, #cbd5e1);
}

/* The Rules card on My Week - quiet until opened. */
.rules-card {
  margin: 18px 0 6px;
  background: var(--card, #12161b);
  border-radius: 14px;
  padding: 4px 16px;
}
.rules-summary {
  cursor: pointer;
  padding: 12px 0;
  font-weight: 600;
  list-style: none;
}
.rules-summary::-webkit-details-marker { display: none; }
.rules-list {
  margin: 0 0 12px;
  padding-left: 1.1em;
  display: grid;
  gap: 8px;
}
.rules-list li {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-soft, #cbd5e1);
}

/* ---------------------------------------------------------------------
   ⚖️ The admin verdict sheet, and the button on a card that opens it.

   THE [hidden] GUARDS COME FIRST, before any display rule underneath —
   a styled element with `display:` set will otherwise ignore the hidden
   attribute entirely and sit on the screen forever. That exact bug
   shipped once here (a celebration card nobody could dismiss), so every
   new element in this file states the guard first.

   The sheet itself reuses .picker-scrim / .picker-sheet, so it slides
   up from the bottom exactly like the emoji picker and needs no
   animation or layout of its own.
   --------------------------------------------------------------------- */

.feedcard-override[hidden] {
  display: none;
}

.feedcard-override {
  width: 100%;
  min-height: var(--tap);
  margin: 8px 0 0;
  border: 1px dashed var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

.feedcard-override:hover {
  color: var(--text);
  border-style: solid;
}

.verdict-now {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--bg);
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.verdict-label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.78rem;
  color: var(--muted);
}

.verdict-reason {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  resize: vertical;
}

.verdict-photo {
  width: 100%;
  min-height: var(--tap);
  margin-top: 8px;
  border: 1px dashed var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.84rem;
  cursor: pointer;
}

.verdict-error[hidden] {
  display: none;
}

.verdict-error {
  margin: 10px 0 0;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--bad, #f87171);
}

.verdict-go {
  width: 100%;
  min-height: var(--tap);
  margin-top: 12px;
  border: 0;
  border-radius: 12px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.verdict-go:disabled {
  opacity: 0.6;
  cursor: default;
}

/* The second figure in The Pot's headline: what the group still owes.
   The pot is gold because it is money in hand; this one is deliberately
   NOT gold — it is a debt, not a prize, and two identical gold numbers
   side by side would read as one number split in half.

   It sits right-aligned against the "Who owes what" label so the two
   figures bracket the row rather than crowding the left edge. Below
   360px the label is hidden by the rule underneath, which leaves the
   two numbers room on the narrowest phones in the group. */
.ledger-headline-owed {
  flex: none;
  text-align: right;
  margin-left: auto;
  padding-right: 10px;
}

.ledger-headline-owed strong {
  font-size: 1.15rem;
  color: var(--text);
}

@media (max-width: 360px) {
  .ledger-summary .ledger-open {
    display: none;
  }
}

/* =====================================================================
   📅 THE CALENDAR — where the $25 came from
   =====================================================================
   THE ONE JOB OF THIS SCREEN: a member looks at it and sees which days
   they worked out, which they missed, and why they owe what they owe.
   Every rule below serves that and nothing else.

   ⚠️ COLOUR IS NEVER THE ONLY SIGNAL. Every state is a colour AND a
   border shape AND a mark AND a word in the legend. Some of these
   members read this on a phone in bright sun outside a gym, and
   colour-blind members exist. If a state below ever becomes "just
   change the background", the screen has broken for somebody.

   ⚠️ EVERY [hidden] GUARD COMES FIRST, before the display rule it is
   guarding. A styled element ignores the hidden attribute otherwise,
   and this app has already shipped a celebration card nobody could
   dismiss for exactly that reason.

   375px IS THE BUDGET. The seven-column maths is written out over
   .calendar-grid; widen a gap or a padding and the cells drop back
   under the thumb minimum. */

/* --- The way in, from My Week ---------------------------------------- */

.calendar-open[hidden] {
  display: none;
}

.calendar-open {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 62px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.calendar-open:active {
  background: var(--card-2);
}

.calendar-open:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.calendar-open-icon {
  font-size: 1.4rem;
  line-height: 1;
  flex: none;
}

.calendar-open-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.calendar-open-text strong {
  font-size: 0.98rem;
}

.calendar-open-text small {
  font-size: 0.76rem;
  color: var(--muted);
}

.calendar-open-arrow {
  color: var(--accent);
  font-size: 1.1rem;
  flex: none;
}

/* --- The slide-over ---------------------------------------------------
   The same shape as the member-history slide-over, deliberately: it
   covers the tab bar because it carries its own back button. */

.calendarview[hidden] {
  display: none;
}

.calendarview {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.calendarview-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(10px + env(safe-area-inset-top)) 12px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--card);
  flex: none;
}

/* The month pager. FIXED, not scrolled: paging through months is the
   whole navigation of this screen, and a control that scrolls away
   while you look at the last week of a month is one you have to hunt
   for. */
.calendar-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--card);
  flex: none;
}

.calendar-step[hidden] {
  display: none;
}

.calendar-step {
  flex: none;
  min-width: var(--tap);
  min-height: var(--tap);
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card-2);
  color: var(--text);
  font: inherit;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

.calendar-step:active {
  background: var(--accent-faint);
}

.calendar-step:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The month name keeps the middle whatever the arrows do, so it does
   not slide sideways when "next" hides itself in the current month. */
.calendar-month {
  flex: 1;
  margin: 0;
  text-align: center;
  font-size: 1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.calendarview-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 10px calc(16px + env(safe-area-inset-bottom));
}

.calendar-summary {
  margin: 0 0 10px;
  font-size: 0.84rem;
  color: var(--muted);
  line-height: 1.45;
}

.calendar-summary strong {
  color: var(--text);
}

.calendar-summary-owed {
  color: var(--bad);
}

/* --- The legend ------------------------------------------------------
   Not decoration. It is the contract that colour never carries a
   meaning on its own: five states, five marks, five words. */

.calendar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin: 0 0 12px;
  padding: 10px;
  list-style: none;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  font-size: 0.7rem;
  color: var(--muted);
}

.calendar-legend li {
  display: flex;
  align-items: center;
  gap: 5px;
}

.calkey {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 6px;
  font-size: 0.66rem;
  font-weight: 800;
  line-height: 1;
  flex: none;
}

.calkey-hit {
  background: var(--accent);
  border: 1px solid var(--accent);
  color: #0b0d10;
}

.calkey-short {
  border: 1px dashed var(--bad);
  color: var(--bad);
}

.calkey-empty {
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--muted);
}

.calkey-paused {
  border: 1px solid var(--line);
  color: var(--muted);
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, 0.14) 0 3px,
    transparent 3px 7px
  );
}

.calkey-before {
  border: 1px dotted var(--line);
  color: var(--muted);
  opacity: 0.6;
}

/* --- Mon..Sun, written once above the weeks -------------------------- */

.calendar-dow,
.calendar-grid {
  /* THE 375px BUDGET, worked backwards and MEASURED, not guessed.
     375 wide, less 2 x 10px of panel padding = 355; less the week
     block's 2 x 8px padding and its 2 x 1px border = 337; less six 3px
     gaps = 319, so 45.6px a cell. Every cell is 56px tall.
     On a 320px iPhone SE that lands at 39 x 56.

     THAT IS THE SAME BUDGET THE WEEK GRID ON MY WEEK ALREADY LIVES ON
     — its tiles measure 37 x 58 at 320px — so this is not a new
     compromise, it is the one the app already shipped and the group
     already uses. Seven columns of 44px cannot fit inside 320px
     however the padding is arranged; height carries the target
     instead. Widen a gap or a padding here and even this stops being
     true. */
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* THE 9px IS NOT ARBITRARY — it is the week block's 8px of padding
   plus its 1px border, which is exactly how far the day cells inside
   it are inset. Without it the seven letters sit nine pixels left of
   the seven columns they name, and a calendar whose weekday header is
   off by a third of a cell is a calendar you cannot read a date off. */
.calendar-dow {
  padding: 0 9px;
  margin-bottom: 4px;
}

.calendar-dow li {
  text-align: center;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* --- One week: seven days and the bill they add up to ---------------- */

.calendar-weeks {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* THE WEEK IS THE BILLING UNIT, SO IT IS THE VISUAL UNIT. The seven
   days a penalty was worked out from sit inside one bordered block
   with that penalty printed along the bottom of it. That adjacency IS
   the explanation this whole screen exists to give. */
.calendar-week {
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 8px 8px 0;
  background: var(--card);
}

/* A week nobody was playing in recedes rather than shouting. */
.calendar-week-before_join {
  opacity: 0.55;
}

.calendar-week-billed {
  border-color: rgba(255, 138, 122, 0.35);
}

.calendar-week-clear {
  border-color: rgba(61, 220, 151, 0.3);
}

/* --- One day ---------------------------------------------------------
   A button, not a div: it is tappable, so it has to be reachable by
   keyboard and announced as a control. */

.calendar-day {
  min-width: 0;
  width: 100%;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 4px 1px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--muted);
  font-family: inherit;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.12s ease;
}

.calendar-day:active {
  transform: scale(0.94);
}

.calendar-day:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.cal-num {
  font-size: 0.62rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

.cal-mark {
  font-size: 0.8rem;
  font-weight: 800;
  line-height: 1;
}

.cal-mins {
  font-size: 0.58rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  min-height: 0.8em;
}

/* A day that counted: filled with the member's own accent, and marked
   with a tick as well — the fill on its own is a colour, and a colour
   on its own is not allowed to be the message here. */
.calendar-day-qualified {
  background: var(--accent);
  border-color: var(--accent);
  color: #0b0d10;
}

/* Recorded, and it did not count. THE MOST IMPORTANT STATE ON THIS
   SCREEN: a member who trained for 18 minutes and got nothing has to
   see the workout AND see that it fell short, or the only explanation
   left is that the app lost it. So the cell keeps its minutes, wears a
   DASHED border — a different shape, not just a different colour — and
   carries its own mark. */
.calendar-day-short {
  background: transparent;
  border: 1px dashed var(--bad);
  color: var(--bad);
}

.calendar-day-empty {
  color: var(--muted);
}

/* Not a miss: it has not happened yet. */
.calendar-day-future {
  opacity: 0.35;
}

/* Not a miss either: they were not in the group. Dotted rather than
   dashed — a third border shape for a third meaning. */
.calendar-day-before_join {
  background: transparent;
  border: 1px dotted var(--line);
  opacity: 0.45;
}

/* A pause LAYERS OVER whatever else the day was, because a member can
   train during a pause and the day has to say both things: you worked
   out, and this week was not billed. Stripes rather than a colour, so
   it still reads on top of the accent fill. */
.calendar-day-paused {
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, 0.16) 0 3px,
    transparent 3px 7px
  );
}

/* Today is outlined rather than filled, so "today" and "counted" can
   both be true at once and still be told apart — the same trick the
   week grid on My Week uses. */
.calendar-day-today {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The day the member has tapped, so the detail stuck to the bottom of
   the panel is obviously about one particular cell. */
.calendar-day-picked {
  box-shadow: inset 0 0 0 2px var(--text);
}

/* Days from the months either side. They must be DRAWN — they are part
   of the week the money was billed against — but they are not part of
   the month being read. */
.calendar-day-spill {
  opacity: 0.4;
}

/* --- The bill, under the seven days it came from --------------------- */

.calendar-bill {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px -8px 0;
  padding: 8px 10px;
  border-top: 1px solid var(--line);
  font-size: 0.74rem;
}

/* min-width:0 is what actually lets the ellipsis happen inside a flex
   row — without it the range refuses to shrink and pushes the money
   off the right-hand edge on a narrow phone. */
.calendar-bill-range {
  min-width: 0;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Why this week is unusual, said out loud whatever the money did — a
   paused week that was still billed has to be able to say both. */
.calendar-bill-note {
  flex: none;
  padding: 2px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card-2);
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 700;
  white-space: nowrap;
}

.calendar-bill-verdict {
  margin-left: auto;
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.calendar-bill-count {
  color: var(--text);
}

.calendar-bill-money {
  padding: 2px 8px;
  border-radius: 999px;
  font-weight: 800;
}

.calendar-bill-billed .calendar-bill-money {
  background: rgba(255, 138, 122, 0.16);
  color: var(--bad);
}

.calendar-bill-clear .calendar-bill-money {
  background: rgba(61, 220, 151, 0.14);
  color: var(--good);
}

.calendar-bill-open .calendar-bill-money {
  background: var(--accent-faint);
  color: var(--accent);
}

/* Quieter than the open-week chip, and deliberately so: "still to come"
   is not money on the line, and an orange badge on a week nobody has
   had a chance at reads as a warning about nothing. */
.calendar-bill-tocome .calendar-bill-money {
  background: var(--card-2);
  color: var(--muted);
}

.calendar-bill-paused .calendar-bill-money,
.calendar-bill-before_join .calendar-bill-money {
  background: var(--card-2);
  color: var(--muted);
}

/* --- The detail, stuck to the bottom ---------------------------------
   The answer appears where the thumb already is, whichever week was
   tapped. A detail line at the top of a long month is an answer you
   have to scroll back up to read. */

.calendar-detail[hidden] {
  display: none;
}

.calendar-detail {
  flex: none;
  border-top: 1px solid var(--line);
  background: var(--card);
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  font-size: 0.82rem;
  max-height: 40vh;
  overflow-y: auto;
}

.calendar-detail-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
}

.calendar-detail-day {
  font-weight: 700;
}

.calendar-detail-tag {
  font-size: 0.7rem;
  color: var(--muted);
  margin-left: auto;
  flex: none;
}

.calendar-detail-line {
  display: block;
  margin-top: 6px;
  overflow-wrap: anywhere;
}

.calendar-detail-line small {
  display: block;
  color: var(--muted);
  font-size: 0.75rem;
}

.calendar-detail-empty {
  color: var(--muted);
  margin: 6px 0 0;
}

.calendar-fine {
  margin: 14px 0 0;
  font-size: 0.7rem;
  color: var(--muted);
  text-align: center;
}

/* A member who has asked their phone to stop animating things. */
@media (prefers-reduced-motion: reduce) {
  .calendar-day {
    transition: none;
  }
}

/* Wider than a phone. The calendar is a fixed overlay, so without this
   a 48px cell would stretch across a 27-inch monitor. Capped and
   centred, which is what every other panel in this app does. */
@media (min-width: 560px) {
  .calendarview-head,
  .calendar-nav,
  .calendarview-body,
  .calendar-detail {
    width: 100%;
    max-width: 560px;
    margin-left: auto;
    margin-right: auto;
  }

  .calendar-day {
    min-height: 66px;
  }

  .cal-num {
    font-size: 0.72rem;
  }

  .cal-mark {
    font-size: 0.95rem;
  }

  .cal-mins {
    font-size: 0.66rem;
  }
}

/* An iPhone SE is 320px wide. The seven columns still have to fit, so
   the panel padding gives a little rather than the cells dropping out
   of reach of a thumb. */
@media (max-width: 360px) {
  .calendarview-body {
    padding-left: 6px;
    padding-right: 6px;
  }

  .calendar-legend {
    gap: 4px 8px;
    font-size: 0.65rem;
  }

  .calendar-bill {
    font-size: 0.7rem;
  }
}
