/* stylesheet
 *
 * Organized in cascade layers so specificity is predictable:
 *   tokens     — design decisions as custom properties (the only place raw values live)
 *   base       — element defaults shared by every page
 *   components — reusable pieces (.btn, .brand, .terms, ...)
 *   pages      — page-specific layout (auth split screen, clients, ...)
 *
 * New pages: build from tokens + components first; add to the pages layer
 * only what is truly unique to that page.
 */

@layer tokens, base, components, pages;

@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
}

/* ---------- tokens ---------- */

@layer tokens {
  /* body.light pins a page to light tokens even when the system prefers dark:
     values set on body override the :root dark overrides by inheritance */
  :root,
  body.light {
    /* color */
    --bg: #f6f7f9;
    --surface: #ffffff;
    --surface-hover: #f2f4f7;
    --surface-active: #eceef2;
    --text: #1a1d21;
    --muted: #6b7280;
    --border: #dfe3e8;
    /* one accent for the whole app: the brand's orange. Trackers used to carry
       a colour each; they don't any more, so everything that was tinted per
       tracker reads as this instead. */
    --accent: #f59e0b;
    --accent-strong: #d97706;
    --danger: #d93025;

    /* type */
    --font-sans: "Inter Variable", system-ui, -apple-system, "Segoe UI", sans-serif;
    --text-xs: .8rem;
    --text-sm: .925rem;
    --text-md: .95rem;
    --text-lg: 1.35rem;
    --text-xl: 1.55rem;

    /* shape & depth */
    --radius-sm: 8px;
    --radius-md: 14px;
    --shadow-sm: 0 1px 3px rgb(0 0 0 / .08);
    --shadow-md: 0 1px 2px rgb(0 0 0 / .04), 0 8px 24px rgb(0 0 0 / .06);
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #0f1115;
      --surface: #1f2329;
      --surface-hover: #252a31;
      --surface-active: #2b3138;
      --text: #e8eaed;
      --muted: #9aa0a6;
      --border: #2b3038;
      --danger: #f28b82;
      /* a shade up on dark, where the light-mode amber goes muddy */
      --accent: #fbbf24;
      --accent-strong: #f59e0b;
    }
  }

  /* body.dark pins dark the same way (set by the shell's theme toggle);
     keep these values in sync with the media query above */
  body.dark {
    --bg: #0f1115;
    --surface: #1f2329;
    --surface-hover: #252a31;
    --surface-active: #2b3138;
    --text: #e8eaed;
    --muted: #9aa0a6;
    --border: #2b3038;
    --danger: #f28b82;
    --accent: #fbbf24;
    --accent-strong: #f59e0b;
  }

  /* native UI (form controls, autofill, caret) follows the pinned theme too */
  body.light { color-scheme: light; }
  body.dark { color-scheme: dark; }
}

/* ---------- base ---------- */

@layer base {
  body {
    margin: 0;
    min-height: 100dvh;
    display: grid;
    place-items: center;
    background: var(--bg);
    font-family: var(--font-sans);
    color: var(--text);
  }

  h1 {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 500;
    letter-spacing: -.02em;
  }

  a {
    color: inherit;
    text-underline-offset: 2px;
  }

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

/* ---------- components ---------- */

@layer components {
  .btn {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .75rem;
    padding: .75rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: var(--text-md);
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
    box-shadow: var(--shadow-sm);
    transition: background .15s, border-color .15s, box-shadow .15s;

    svg { width: 18px; height: 18px; }

    &:hover {
      background: var(--surface-hover);
      box-shadow: 0 2px 8px rgb(0 0 0 / .1);
    }

    &:active { background: var(--surface-active); }
  }

  /* accent-filled variant; declared after .btn so its states win */
  .btn-primary {
    border-color: var(--accent);
    background: var(--accent);
    color: #fff;

    &:hover { background: var(--accent-strong); }
    &:active { background: var(--accent-strong); }
  }

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

  .field {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    text-align: left;

    label {
      font-size: var(--text-sm);
      font-weight: 500;
    }

    input,
    select {
      box-sizing: border-box;
      width: 100%;
      padding: .75rem 1rem;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      background: var(--surface);
      color: var(--text);
      font: inherit;
      font-size: var(--text-md);

      &::placeholder { color: var(--muted); opacity: .7; }
    }

    /* the display above outranks the UA sheet's [hidden], so restate it */
    &[hidden] { display: none; }
  }

  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .divider {
    display: flex;
    align-items: center;
    gap: .75rem;
    font-size: var(--text-xs);
    color: var(--muted);

    &::before,
    &::after {
      content: "";
      flex: 1;
      height: 1px;
      background: var(--border);
    }
  }

  .brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .55rem;
    font-size: var(--text-xl);
    font-weight: 600;
    letter-spacing: -.02em;

    /* the mark is fill="currentColor" throughout, so this colours it without
       tinting the wordmark beside it */
    svg {
      color: var(--accent);
      width: 30px;
      height: 30px;
    }
  }

  .subtitle {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--muted);
  }

  .terms {
    margin: 0;
    max-width: 24rem;
    font-size: var(--text-xs);
    line-height: 1.5;
    text-align: center;
    color: var(--muted);

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

/* ---------- pages: auth ---------- */

@layer pages {
  .auth-shell {
    box-sizing: border-box;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
  }

  .auth-columns {
    box-sizing: border-box;
    flex: 1;
    display: grid;
    grid-template-columns: minmax(0, 24rem) auto;
    align-items: center;
    justify-content: center;
    gap: clamp(3rem, 9vw, 10rem);
    width: 100%;
    max-width: 72rem;
    margin: 0 auto;
    padding: 2rem;
  }

  .auth-form {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;

    .btn { width: 100%; }

    h1 {
      margin-bottom: 1rem;
      font-size: 2rem;
      font-weight: 600;
      letter-spacing: -.03em;
    }

    .email-form {
      display: flex;
      flex-direction: column;
      gap: 1.1rem;
    }

    .auth-error {
      margin: 0;
      padding: .75rem 1rem;
      border: 1px solid var(--danger);
      border-radius: var(--radius-sm);
      font-size: var(--text-sm);
      color: var(--danger);

      &[hidden] { display: none; }
    }

    /* the ways in, stacked tighter than the surrounding rhythm so Google,
       the divider and the email form read as one group */
    .auth-choices {
      display: flex;
      flex-direction: column;
      gap: .75rem;

      &[hidden] { display: none; }
    }

    /* replaces .auth-choices once a sign-in link is on its way */
    .auth-notice {
      display: flex;
      flex-direction: column;
      gap: .5rem;
      padding: .9rem 1rem;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      background: var(--surface);
      font-size: var(--text-sm);
      color: var(--muted);

      p { margin: 0; }
      strong { color: var(--text); font-weight: 500; }

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

        &:hover { text-decoration: underline; }
      }

      &[hidden] { display: none; }
    }

    .terms {
      margin-top: .5rem;
      text-align: left;
    }
  }

  .auth-visual {
    display: grid;
    place-items: center;

    svg {
      /* paths are fill="currentColor", so this drives the whole mark */
      color: var(--accent);
      width: clamp(16rem, 28vw, 23rem);
      height: auto;
    }
  }

  .auth-footer {
    display: flex;
    justify-content: center;
    padding: 2rem;

    a {
      display: inline-flex;
      align-items: center;
      gap: .5rem;
      font-size: var(--text-sm);
      color: var(--muted);
      text-decoration: none;

      svg { width: 18px; height: 18px; }

      &:hover { color: var(--text); }
    }
  }

  @media (max-width: 56rem) {
    .auth-columns { grid-template-columns: min(24rem, 100%); }
    .auth-visual { display: none; }
  }
}

/* ---------- pages: app shell ---------- */

@layer pages {
  .shell {
    width: 100%;
    min-height: 100dvh;
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr);
  }

  .sidebar {
    box-sizing: border-box;
    position: sticky;
    top: 0;
    height: 100dvh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1rem .75rem;
    border-right: 1px solid var(--border);
    background: var(--surface);

    .brand {
      justify-content: flex-start;
      padding: .25rem .5rem;
      font-size: var(--text-lg);

      svg { width: 24px; height: 24px; }
    }
  }

  /* nav takes the leftover height, pinning the footer to the bottom */
  .sidebar-nav {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: .15rem;
  }

  .sidebar-nav a,
  .sidebar-footer a {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .6rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    font-weight: 450;
    color: var(--text);
    text-decoration: none;

    svg { width: 18px; height: 18px; color: var(--muted); }

    &:hover { background: var(--surface-hover); }
    &.active { background: var(--surface-active); font-weight: 550; }
  }

  .sidebar-footer {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    padding-top: .75rem;
    border-top: 1px solid var(--border);
  }

  .sidebar-account {
    position: relative;

    .account-trigger {
      box-sizing: border-box;
      width: 100%;
      display: flex;
      align-items: center;
      gap: .6rem;
      padding: .4rem .5rem;
      border: 0;
      border-radius: var(--radius-sm);
      background: none;
      color: inherit;
      font: inherit;
      text-align: left;
      cursor: pointer;

      &:hover { background: var(--surface-hover); }
      &[aria-expanded="true"] { background: var(--surface-active); }

      .chevron {
        flex: none;
        margin-left: auto;
        width: 16px;
        height: 16px;
        color: var(--muted);
      }
    }

    .account-menu {
      position: absolute;
      inset: auto 0 calc(100% + .5rem) 0;
      display: flex;
      flex-direction: column;
      gap: .1rem;
      padding: .3rem;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      background: var(--surface);
      box-shadow: var(--shadow-md);

      &[hidden] { display: none; }
    }

    .menu-item,
    .menu-item span {
      display: flex;
      align-items: center;
      gap: .6rem;
    }

    .menu-item {
      padding: .5rem .6rem;
      border: 0;
      border-radius: calc(var(--radius-sm) - 2px);
      background: none;
      color: var(--text);
      font: inherit;
      font-size: var(--text-sm);
      text-align: left;
      text-decoration: none;
      cursor: pointer;

      svg { width: 16px; height: 16px; color: var(--muted); }

      &:hover { background: var(--surface-hover); }

      span[hidden] { display: none; }
    }

    /* logout is a POST form; the wrapper must not become the menu's flex item */
    .account-menu form { display: contents; }

    .menu-item.danger {
      color: var(--danger);

      svg { color: var(--danger); }

      &:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }
    }

    .avatar {
      flex: none;
      width: 28px;
      height: 28px;
      border-radius: 50%;
      display: grid;
      place-items: center;
      background: var(--accent);
      color: #fff;
      font-size: var(--text-xs);
      font-weight: 600;
    }

    img.avatar {
      object-fit: cover;
      background: none;
    }

    .account-id {
      display: flex;
      flex-direction: column;
      min-width: 0;
    }

    .account-name {
      font-size: var(--text-sm);
      font-weight: 500;
    }

    .account-email {
      font-size: var(--text-xs);
      color: var(--muted);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }

  .shell-main {
    box-sizing: border-box;
    min-width: 0;
    padding: 2.5rem clamp(1.5rem, 5vw, 4rem);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.25rem;
  }

  /* narrow screens: icon-only sidebar */
  @media (max-width: 48rem) {
    .shell { grid-template-columns: auto minmax(0, 1fr); }

    .sidebar .brand span,
    .sidebar-nav a span,
    .sidebar-footer a span,
    .sidebar-account .account-id,
    .sidebar-account .chevron {
      display: none;
    }
  }
}

/* ---------- pages: trackers ---------- */

@layer pages {
  .page-head,
  .tracker-list,
  .new-tracker,
  .sheet-scroll {
    width: 100%;
  }

  .page-head {
    display: flex;
    flex-direction: column;
    gap: .35rem;
  }

  .back-link {
    font-size: var(--text-sm);
    color: var(--muted);
    text-decoration: none;

    &:hover { color: var(--text); }
  }

  /* ---- overview ---- */

  .tracker-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: .5rem;

    &[hidden] { display: none; }
  }

  /* the delete sits beside the link rather than inside it: an <a> can't
     legally contain a <button>, so the card owns the border and the link fills
     what is left of it */
  .tracker-card {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-sm);

    &:hover { background: var(--surface-hover); }
  }

  .tracker-card a {
    flex: 1;
    min-width: 0;
    display: flex;
    /* stretch, so the text column gets the full height the sparkline sets and
       can spread into it rather than bunching at the top */
    align-items: stretch;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 1.1rem 1.15rem;
    border-radius: var(--radius-md);
    text-decoration: none;
  }

  /* name at the top, followers at the foot, whatever sits between them spaced
     out across the rest — so the card breathes at any number of rows */
  .tracker-id {
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: .5rem;
  }

  .tracker-name {
    font-size: var(--text-md);
    font-weight: 550;
  }

  .tracker-desc {
    font-size: var(--text-sm);
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .tracker-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .4rem;
    font-size: var(--text-xs);
    color: var(--muted);

    /* the separator hangs off the following item, so it only ever appears
       between two of them — never trailing when the date is missing */
    span + span::before {
      content: "·";
      margin-right: .4rem;
    }
  }

  /* sits at the foot of the card, clear of the counts above it */
  .tracker-followers {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: var(--text-xs);
    color: var(--muted);
  }

  /* faces overlap by half a face, each ringed in the card's own background so
     the one in front reads as in front */
  .face-stack {
    display: flex;

    .face + .face { margin-left: -.4rem; }
  }

  .face {
    box-sizing: border-box;
    display: grid;
    place-items: center;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    box-shadow: 0 0 0 1.5px var(--surface);
    background: var(--surface-active);
    object-fit: cover;
    font-size: .6rem;
    font-weight: 600;
    text-transform: uppercase;
    color: var(--muted);
  }

  /* the card's hover fill would show through the rings, so they follow it */
  .tracker-card:hover .face {
    box-shadow: 0 0 0 1.5px var(--surface-hover);
  }

  /* there is one colour in the app now, so nothing sets --tracker-color any
     more and every use of it falls through to the accent. The variable is
     kept rather than inlined: it is still the name for "the colour of the
     thing being tracked", and a per-tracker palette could come back to it. */

  .tracker-name {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .45rem;
  }

  .tracker-dot {
    flex: none;
    width: .55rem;
    height: .55rem;
    border-radius: 50%;
    background: var(--tracker-color, var(--accent));
  }

  /* colour and visibility read as one settings row, wrapping to two on narrow
     screens rather than compressing either control */
  .tracker-settings {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 2rem;
  }

  .seg-pick {
    display: flex;
    align-items: center;
    gap: .75rem;
  }

  .pick-label {
    font-size: var(--text-sm);
    font-weight: 500;
  }

  /* the read-only tag on a card, shaped like a repository's */
  .pill {
    padding: .05rem .5rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: var(--text-xs);
    font-weight: 450;
    line-height: 1.5;
    color: var(--muted);
  }

  /* two named halves; the radio stays focusable but invisible and the label
     beside it is the control, so every state is driven off :checked */
  .seg {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;

    input {
      position: absolute;
      width: 1px;
      height: 1px;
      opacity: 0;
      pointer-events: none;
    }

    label {
      padding: .3rem .75rem;
      font-size: var(--text-sm);
      font-weight: 500;
      color: var(--muted);
      cursor: pointer;
    }

    label + input + label { border-left: 1px solid var(--border); }

    label:hover { background: var(--surface-hover); color: var(--text); }

    input:checked + label {
      background: var(--accent);
      color: #fff;
    }

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

  /* the card's glance-level read: a line, sized by the card rather than by the
     data, so every row lines up however long the series is */
  .spark {
    flex: none;
    align-self: center;
    /* sized to the block the heatmap used to occupy — 26 week columns across,
       7 day rows down — so the cards keep the height they had */
    width: clamp(10rem, 30vw, 18rem);
    height: 4.75rem;
    color: var(--tracker-color, var(--accent));

    svg { display: block; width: 100%; height: 100%; }
  }

  /* the contribution graph: weekday names down the left, month names across
     the top, and the grid itself filling column by column. All three share one
     cell size so the labels line up with the squares they name. */
  .cal {
    --cell: 11px;
    --cell-gap: 3px;

    display: grid;
    grid-template-columns: auto max-content;
    grid-template-areas:
      ".    months"
      "days grid";
    gap: .3rem .45rem;
    width: max-content;
  }

  .cal-months {
    grid-area: months;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: var(--cell);
    gap: var(--cell-gap);
    font-size: var(--text-xs);
    color: var(--muted);

    /* a month name is wider than the one column it is anchored to, so it is
       allowed to run right over the weeks that follow it */
    span {
      overflow: visible;
      white-space: nowrap;
      line-height: 1;
    }
  }

  .cal-days {
    grid-area: days;
    display: grid;
    grid-template-rows: repeat(7, var(--cell));
    gap: var(--cell-gap);
    align-items: center;
    font-size: var(--text-xs);
    line-height: 1;
    color: var(--muted);
  }

  .cal-grid {
    grid-area: grid;
    display: grid;
    grid-template-rows: repeat(7, var(--cell));
    grid-auto-flow: column;
    grid-auto-columns: var(--cell);
    gap: var(--cell-gap);
  }

  /* the squares before the first day and after the last hold the shape of the
     column without reading as a recorded zero */
  .hm-pad { visibility: hidden; }

  .cal-heading {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 450;
    text-transform: none;
    letter-spacing: normal;
    color: var(--text);
  }

  .cal-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
  }

  .cal-legend {
    display: flex;
    align-items: center;
    gap: .2rem;
    margin-left: auto;
    font-size: var(--text-xs);
    color: var(--muted);

    .hm-cell {
      width: var(--cell, 11px);
      height: var(--cell, 11px);
      margin: 0 1px;
    }
  }

  .hm-cell {
    border-radius: 2px;
    background: var(--surface-active);
    /* the legend sits outside .cal, so the size has a standalone fallback */
    width: var(--cell, 11px);
    height: var(--cell, 11px);

    &[data-level="1"] { background: color-mix(in srgb, var(--tracker-color, var(--accent)) 30%, var(--surface-active)); }
    &[data-level="2"] { background: color-mix(in srgb, var(--tracker-color, var(--accent)) 55%, var(--surface-active)); }
    &[data-level="3"] { background: color-mix(in srgb, var(--tracker-color, var(--accent)) 78%, var(--surface-active)); }
    &[data-level="4"] { background: var(--tracker-color, var(--accent)); }
  }

  /* the first-run target: centred, large, and the whole reason the slim add
     row stays hidden until a list exists */
  .empty-state {
    box-sizing: border-box;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .6rem;
    margin-top: 2rem;
    padding: 3rem 1.5rem;
    border: 1px dashed var(--border);
    border-radius: var(--radius-md);
    text-align: center;

    h2 {
      margin: 0;
      font-size: var(--text-md);
      font-weight: 550;
    }

    p {
      margin: 0;
      max-width: 26rem;
      font-size: var(--text-sm);
      color: var(--muted);
    }

    .btn { margin-top: .6rem; }

    &[hidden] { display: none; }
  }

  .empty-mark {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    margin-bottom: .3rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 14%, var(--surface-active));
    color: var(--accent);

    svg { width: 24px; height: 24px; }
  }

  /* hidden until the row it belongs to is hovered or focused, so the sheet
     stays quiet; :focus-within keeps it reachable by keyboard */
  .row-delete {
    flex: none;
    transition: opacity .12s;
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--muted);
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s;

    svg { width: 13px; height: 13px; }

    &:hover { background: var(--surface-active); color: var(--danger); }
    &:focus-visible { opacity: 1; }
  }

  /* the card's own padding already sets the tools in from the edge */
  .card-tools .row-delete { margin: 0; }

  /* both controls sit in the card's top right; the menu is anchored to this
     box, which is why it is the positioned one rather than the card */
  .card-tools {
    position: relative;
    flex: none;
    align-self: flex-start;
    display: flex;
    align-items: center;
    gap: .1rem;
    padding: .85rem .75rem 0 0;
  }

  .tracker-card {
    .row-delete,
    /* errors only — a save that works stays silent, so anything showing here
     is something the user needs to know about */
  .toast {
    position: fixed;
    left: 50%;
    bottom: 1.5rem;
    z-index: 10;
    transform: translateX(-50%);
    max-width: min(28rem, calc(100vw - 2rem));
    padding: .6rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    box-shadow: var(--shadow-md);
    font-size: var(--text-sm);
    color: var(--danger);

    &[hidden] { display: none; }
  }

  .card-menu-trigger { opacity: 0; }

    &:hover .row-delete,
    &:hover .card-menu-trigger { opacity: 1; }
  }

  /* the menu stays open once it is, so its trigger has to as well */
  .card-tools:has(.card-menu:not([hidden])) {
    .row-delete,
    .card-menu-trigger { opacity: 1; }
  }

  .card-menu-trigger {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--muted);
    cursor: pointer;
    transition: opacity .12s;

    svg { width: 14px; height: 14px; }

    &:hover { background: var(--surface-active); color: var(--text); }
    &:focus-visible { opacity: 1; }
    &[aria-expanded="true"] { background: var(--surface-active); color: var(--text); }
  }

  .card-menu {
    position: absolute;
    top: 100%;
    right: .5rem;
    z-index: 3;
    min-width: 10rem;
    display: flex;
    flex-direction: column;
    padding: .3rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    box-shadow: var(--shadow-md);

    &[hidden] { display: none; }
  }

  .menu-check {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .4rem .5rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    cursor: pointer;

    &:hover { background: var(--surface-hover); }

    input { accent-color: var(--accent); cursor: pointer; }
  }

  .sheet tr:hover .row-delete,
  .sheet th:focus-within .row-delete {
    opacity: 1;
  }

  /* the rule closes the list off, and the button centres under it so it reads
     as the end of the column rather than another item in it */
  .new-tracker {
    box-sizing: border-box;
    display: flex;
    justify-content: center;
    align-self: stretch;
    margin-top: .25rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--border);

    &[hidden] { display: none; }
  }

  /* the line runs up to the arrowhead on hover: one stroke, drawn once. The
     dash length just needs to exceed the path, so 40 covers it. */
  .trend-btn .trend-line {
    stroke-dasharray: 40;
    stroke-dashoffset: 0;
  }

  .trend-btn:hover .trend-line {
    animation: trend-draw .45s ease-out;
  }

  .trend-btn .trend {
    transition: transform .2s ease-out;
  }

  .trend-btn:hover .trend {
    transform: translateY(-1px);
  }

  @keyframes trend-draw {
    from { stroke-dashoffset: 40; }
    to { stroke-dashoffset: 0; }
  }

  @media (prefers-reduced-motion: reduce) {
    .trend-btn:hover .trend-line { animation: none; }
    .trend-btn .trend,
    .trend-btn:hover .trend { transition: none; transform: none; }
  }

  /* the sheet's name is an input that reads as the page heading until it is
     focused, so naming and renaming are the same gesture */
  .title-head {
    width: 100%;
    margin: 0;
  }

  .title-input {
    box-sizing: border-box;
    width: 100%;
    max-width: 28rem;
    margin-left: -.4rem;
    padding: .1rem .4rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--text-lg);
    font-weight: 500;
    letter-spacing: -.02em;

    &::placeholder { color: var(--muted); }
    &:hover { background: var(--surface-hover); }
    &:focus { background: var(--surface); border-color: var(--accent); outline: none; }
  }

  /* the description reads as secondary text until focused, same as the title */
  .desc-input {
    box-sizing: border-box;
    width: 100%;
    max-width: 34rem;
    margin-left: -.4rem;
    padding: .15rem .4rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: var(--text-sm);

    &::placeholder { color: var(--muted); opacity: .7; }
    &:hover { background: var(--surface-hover); }
    &:focus { background: var(--surface); border-color: var(--accent); color: var(--text); outline: none; }
  }

  /* ---- a tracker's sheet ---- */

  /* .shell is min-height:100dvh and .shell-main stretches to it, so flex:1
     hands the sheet the leftover height: rows fill from the top and the sheet
     keeps its shape while it is still short */
  .sheet-scroll {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
  }

  /* a grid of one today; later visualisations drop in beside it without this
     page being rearranged */
  .visuals {
    width: 100%;
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border);
  }

  .visuals-title {
    margin: 0 0 .85rem;
    font-size: var(--text-md);
    font-weight: 550;
  }

  .visual-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
    gap: .75rem;
  }

  .visual-card {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
    /* the strip is wider than a narrow column, so it scrolls rather than
       squashing the days out of shape */
    overflow-x: auto;

    h3 {
      margin: 0;
      font-size: var(--text-xs);
      font-weight: 550;
      text-transform: uppercase;
      letter-spacing: .06em;
      color: var(--muted);
    }

    /* a full year of columns is wider than most screens; it scrolls rather
       than compressing the squares out of shape */
    .cal { min-width: 0; }
  }

  .sheet {
    width: 100%;
    align-self: flex-start;
    border-collapse: collapse;
    font-size: var(--text-sm);

    th, td {
      padding: .4rem .6rem;
      text-align: left;
      font-weight: inherit;
      white-space: nowrap;
      border-bottom: 1px solid var(--border);
    }

    /* spreadsheet rule lines between every column except the trailing + */
    th:not(:last-child),
    td:not(:last-child) {
      border-right: 1px solid var(--border);
    }

    thead th {
      background: var(--surface-hover);
      font-size: var(--text-xs);
      font-weight: 550;
      color: var(--muted);
    }

    tbody tr:last-child th,
    tbody tr:last-child td {
      border-bottom: 0;
    }
  }

  .col-date {
    min-width: 8rem;
    color: var(--muted);
  }

  .col-value { min-width: 8rem; }

  /* only numeric columns tabulate and align right; text columns read as prose */
  .col-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  .col-add { width: 1%; }

  .sheet-hint {
    text-align: center;
    color: var(--muted);
  }

  /* badge then name, and the name column stays right-aligned when numeric */
  .col-head {
    display: flex;
    align-items: center;
    gap: .25rem;
  }

  .kind-toggle {
    flex: none;
    padding: .05rem .3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--surface);
    color: var(--muted);
    font: inherit;
    font-size: .6rem;
    font-weight: 700;
    letter-spacing: .02em;
    cursor: pointer;

    &:hover { border-color: var(--accent); color: var(--accent); }
  }

  /* the header name is an input that reads as plain text until focused, so
     renaming is just clicking it — no separate rename affordance */
  .col-title {
    min-width: 0;
    width: 100%;
    padding: .15rem .25rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--text-xs);
    font-weight: 550;
    text-align: inherit;

    &:hover { background: var(--surface); }
    &:focus { background: var(--surface); border-color: var(--accent); outline: none; }
  }

  /* the entry row reads as part of the sheet, so its inputs lose their chrome
     until focused */
  .sheet-entry {
    background: var(--surface-hover);

    input {
      box-sizing: border-box;
      width: 100%;
      padding: .25rem .35rem;
      border: 1px solid transparent;
      border-radius: var(--radius-sm);
      background: var(--surface);
      color: var(--text);
      font: inherit;
      font-size: var(--text-sm);

      &::placeholder { color: var(--muted); opacity: .7; }
      &:focus { border-color: var(--accent); outline: none; }
    }

    .col-num input { text-align: right; font-variant-numeric: tabular-nums; }
  }

  .btn-sm {
    padding: .3rem .6rem;
    font-size: var(--text-xs);
    box-shadow: none;
  }

  .add-toggle {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: var(--text-md);
    cursor: pointer;

    &:hover { background: var(--surface-active); color: var(--text); }
  }
}

/* ---------- pages: home ---------- */

@layer pages {
  /* feed down the middle, the rail beside it. The feed is capped rather than
     fluid: a line of text past about 40rem is tiring to read, and the rail
     would rather have the room anyway. */
  .home {
    width: 100%;
    display: grid;
    /* the pair is centred in whatever room the shell leaves rather than
       pinned left, so a wide window doesn't strand the rail out on its own
       with a field of empty between */
    grid-template-columns: minmax(0, 36rem) minmax(14rem, 17rem);
    justify-content: center;
    gap: 1.5rem 2rem;
    align-items: start;
  }

  .feed-col {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
  }

  .feed {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: .5rem;
  }

  /* a card rather than a row: person, what it was, the numbers, the shape of
     it. The parts stack down the card, so an uploaded picture later drops in
     beside the chart without the layout being rethought. */
  .feed-item {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
  }

  .feed-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1rem 1.15rem .65rem;
  }

  .feed-person {
    display: flex;
    align-items: center;
    gap: .6rem;
  }

  .person-id {
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.35;
  }

  .person-name {
    font-size: var(--text-sm);
    font-weight: 600;
  }

  .person-when {
    font-size: var(--text-xs);
    color: var(--muted);
  }

  /* the title carries the weight a Strava activity name does: it is the first
     thing read after who did it */
  .feed-title {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text);
    text-decoration: none;

    .tracker-dot { width: .5rem; height: .5rem; }

    &:hover { text-decoration: underline; }
  }

  /* label above, number below — a row of these reads as numbers rather than
     as a list of field names */
  .stat-row {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem 2rem;
  }

  .stat {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    min-width: 0;
  }

  .stat-label {
    font-size: var(--text-xs);
    color: var(--muted);
  }

  .stat-value {
    font-size: 1.15rem;
    font-weight: 550;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
  }

  /* where the map goes on Strava and the release notes go on GitHub */
  .chart {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    padding: .7rem .8rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
  }

  .chart-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    font-size: var(--text-xs);
  }

  .chart-label { font-weight: 550; }
  .chart-span { color: var(--muted); }

  /* bars sit on a shared baseline and grow up from it; the empty days keep
     their column so a gap reads as a gap rather than as a narrower fortnight */
  .chart-plot {
    display: flex;
    align-items: flex-end;
    gap: .25rem;
    height: 4.5rem;
  }

  .bar {
    position: relative;
    flex: 1;
    height: 100%;
    display: flex;
    align-items: flex-end;
    border-radius: 3px;
    background: color-mix(in srgb, var(--text) 6%, transparent);

    span {
      width: 100%;
      border-radius: 3px;
      background: color-mix(in srgb, var(--tracker-color, var(--accent)) 30%, transparent);
    }
  }

  /* the other days are context, drawn faint; this card's own day is the
     colour at full strength, so five cards off one tracker read as five
     different recordings rather than five copies of the same picture */
  .bar.mark span {
    background: var(--tracker-color, var(--accent));
  }

  /* and a tick under it, for anyone who can't tell the two strengths apart */
  .bar.mark::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -.35rem;
    width: .25rem;
    height: .25rem;
    border-radius: 50%;
    transform: translateX(-50%);
    background: var(--tracker-color, var(--accent));
  }

  .chart-plot { padding-bottom: .35rem; }

  .feed-more { align-self: center; }

  /* a bigger face than the tracker card's stack: here it is the row's anchor
     rather than one of several */
  .face-lg {
    width: 2rem;
    height: 2rem;
    font-size: var(--text-sm);
    box-shadow: none;
  }

  /* ---- the rail ---- */

  .rail {
    position: sticky;
    top: 2.5rem;
    display: flex;
    flex-direction: column;
    gap: .6rem;
  }

  .rail-title {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 550;
    color: var(--muted);
  }

  .live-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: .35rem;
  }

  .live-row {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
  }

  /* the dot is the live one: it pulses while the clock runs and sits still
     when the session is paused, so the rail reads at a glance */
  .live-row .tracker-dot { animation: live-pulse 2s ease-in-out infinite; }

  .live-row.paused {
    .tracker-dot { animation: none; opacity: .5; }
    .live-count { color: var(--muted); }
  }

  @keyframes live-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .35; }
  }

  @media (prefers-reduced-motion: reduce) {
    .live-row .tracker-dot { animation: none; }
  }

  .live-body {
    min-width: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: .1rem;
  }

  .live-who {
    font-size: var(--text-sm);
    font-weight: 500;
  }

  .live-what {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: var(--text-xs);
    color: var(--muted);
  }

  .live-paused {
    font-size: var(--text-xs);
    color: var(--muted);
  }

  .live-count {
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
  }

  .rail-empty {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--muted);

    &[hidden] { display: none; }
  }

  /* narrow: the rail goes above the feed rather than beside it — who is
     recording now is the shorter, more perishable of the two */
  @media (max-width: 64rem) {
    .home { grid-template-columns: minmax(0, 1fr); }

    .rail {
      position: static;
      order: -1;
    }
  }
}

/* ---------- pages: home, answering a recording ---------- */

@layer pages {
  /* the actions close the card off, ruled away from the recording itself the
     way Strava's kudos row is */
  .feed-actions {
    display: flex;
    align-items: center;
    gap: .25rem;
    margin-top: .15rem;
    padding-top: .5rem;
    border-top: 1px solid var(--border);
  }

  .react {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .35rem .55rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    cursor: pointer;

    svg { width: 17px; height: 17px; }

    &:hover { background: var(--surface-active); color: var(--text); }
  }

  /* liked: the heart fills rather than just colouring, so the state reads
     without comparing it to a neighbour */
  .react.on {
    color: var(--accent);

    svg { fill: currentColor; }
  }

  .react-count:empty { display: none; }

  .thread {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    margin-top: .3rem;
    padding-top: .6rem;
    border-top: 1px solid var(--border);

    &[hidden] { display: none; }
  }

  .comment-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: .55rem;

    &:empty { display: none; }
  }

  .comment {
    display: flex;
    gap: .5rem;
  }

  /* the comment's own face is smaller than the recording's: it is a reply to
     the row, not another row */
  .comment .face {
    width: 1.5rem;
    height: 1.5rem;
    font-size: var(--text-xs);
    box-shadow: none;
  }

  .comment-body {
    min-width: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: .1rem;
  }

  .comment-head {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--text-xs);
    color: var(--muted);
  }

  .comment-who {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text);
  }

  .comment-edited { font-style: italic; }

  .comment-text {
    font-size: var(--text-sm);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }

  /* the delete only appears on the comment it belongs to, and only on hover,
     so a thread isn't a column of crosses */
  .comment .row-delete {
    margin-left: auto;
    opacity: 0;
  }

  .comment:hover .row-delete,
  .comment .row-delete:focus-visible { opacity: 1; }

  .comment-form {
    display: flex;
    gap: .4rem;
  }

  .comment-input {
    flex: 1;
    min-width: 0;
    padding: .4rem .6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);

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

/* ---------- components: add activity, profile ---------- */

@layer components {
  /* the plus sits at the foot of the sidebar, above the account: the one
     control on every page that starts something rather than navigating */
  .add-activity { position: relative; }

  /* a row like the nav's, not a button: the sidebar reads as one list, and the
     plus alone carries the colour. It is the only coloured thing in there, so
     it is the only thing that starts something rather than going somewhere. */
  .nav-action {
    box-sizing: border-box;
    width: 100%;
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .6rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 450;
    text-align: left;
    cursor: pointer;

    svg {
      width: 18px;
      height: 18px;
      color: var(--accent);
    }

    &:hover { background: var(--surface-hover); }
  }

  /* opens upward — there is nothing below it but the account block */
  .add-menu {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + .4rem);
    z-index: 20;
    max-height: 60vh;
    overflow-y: auto;
    padding: .25rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    box-shadow: var(--shadow-md);

    &[hidden] { display: none; }
  }

  /* the way out of the list, ruled off from the trackers themselves */
  .menu-foot {
    margin-top: .25rem;
    padding-top: .4rem;
    border-top: 1px solid var(--border);
    color: var(--muted);
  }

  .profile-head {
    display: flex;
    align-items: center;
    gap: 1rem;
  }

  .profile-face {
    box-sizing: border-box;
    display: grid;
    place-items: center;
    width: 4rem;
    height: 4rem;
    border-radius: 50%;
    background: var(--surface-active);
    object-fit: cover;
    font-size: var(--text-xl);
    font-weight: 600;
    text-transform: uppercase;
    color: var(--muted);
  }

  .profile-id {
    display: flex;
    flex-direction: column;
    gap: .15rem;

    h1 { margin: 0; }

    p {
      margin: 0;
      color: var(--muted);
    }
  }

  /* narrow sidebar: the plus keeps its icon and loses its word, like the nav */
  @media (max-width: 48rem) {
    .nav-action span { display: none; }

    .add-menu {
      left: 0;
      right: auto;
      min-width: 12rem;
    }
  }
}

/* ---------- pages: record ---------- */

@layer pages {
  /* the page is centred in the shell's column rather than pinned left: it is
     one short form, and left-aligned it reads as the start of something wider
     that never arrives */
  .record-page {
    width: 100%;
    max-width: 44rem;
    align-self: center;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
  }

  /* one column, read top to bottom: what you did, how long it took, what it
     came to. The order is the order it is filled in. */
  .record {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
  }

  /* the tracker picker is a control, not a column of one */
  .record .field { max-width: 20rem; }

  /* the same badge the sheet's header carries, without the toggle: here it
     says what the column holds rather than offering to change it */
  .kind-mark {
    flex: none;
    padding: .05rem .3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--surface);
    color: var(--muted);
    font-size: .6rem;
    font-weight: 700;
    letter-spacing: .02em;
  }

  .field-label {
    font-size: var(--text-sm);
    font-weight: 500;
  }

  .field-note {
    font-size: var(--text-xs);
    color: var(--muted);

    &[hidden] { display: none; }
  }

  .input,
  .select {
    box-sizing: border-box;
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--text-md);

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

    &:disabled {
      background: var(--surface-active);
      color: var(--muted);
    }
  }

  /* the clock reads as an instrument rather than another form row: the count
     is the biggest thing on the page while it runs */
  .record-timer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .75rem;
    padding: .85rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);

    &[hidden] { display: none; }
  }

  /* running: the border takes the accent, so the state is legible from across
     the room and not only from the button labels */
  .record-timer.live {
    border-color: var(--accent);

    .timer-count { color: var(--accent); }
  }

  .timer-count {
    font-size: var(--text-xl);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }

  .timer-controls {
    display: flex;
    gap: .4rem;

    .btn[hidden] { display: none; }
  }

  /* throwing away a session is the one destructive thing on the page */
  .discard {
    border-color: transparent;
    background: none;
    box-shadow: none;
    color: var(--danger);

    &:hover { background: var(--surface-hover); }
  }

  .record-save {
    display: flex;
    align-items: center;
    gap: .75rem;

    .btn { min-width: 8rem; }

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