/* ==========================================================================
   ROSTER

   The palette is the Activity page's, token for token, because Roster is that
   page expanded rather than a second app — and because those values are
   measured. Do not "tidy" one of them without re-measuring: white check on the
   accent is 8.05 / 7.90, the grey ring 4.83 / 4.84, every type dot ≥ 6.4.

   🔴 THE TOKENS LIVE ON :root, NOT ON A WRAPPER. The Activity page learned this
   the hard way on 2026-08-19: html and body sit outside any wrapper, so with
   the tokens scoped to one the browser's own canvas showed through as an 8px
   WHITE band around the whole app, in dark mode. color-scheme has to be on the
   root element for the canvas to take its colour from the page.
   ========================================================================== */

:root {
  --ground: #fbfaf7;
  --card: #ffffff;
  --ink: #14201e;
  --muted: #5a6763;
  --hair: #e4e0d8;
  --hair-strong: #d3cdc1;
  --accent: #075a57;
  --accent-ink: #ffffff;
  --accent-wash: #e7f0ef;
  --quote-bg: #f5f4f0;
  --danger: #82550f;
  --field: #ffffff;
  --chip: #21302d;
  --chip-ink: #f4f7f6;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --ground: #101614;
    --card: #172120;
    --ink: #e8efed;
    --muted: #9fb0ab;
    --hair: #26332f;
    --hair-strong: #35443f;
    --accent: #4fc3bb;
    --accent-ink: #10201e;
    --accent-wash: #1b2c2a;
    --quote-bg: #1c2725;
    --danger: #e2b93b;
    --field: #101614;
    --chip: #e6edeb;
    --chip-ink: #131c1a;
  }
}

:root[data-theme='dark'] {
  --ground: #101614;
  --card: #172120;
  --ink: #e8efed;
  --muted: #9fb0ab;
  --hair: #26332f;
  --hair-strong: #35443f;
  --accent: #4fc3bb;
  --accent-ink: #10201e;
  --accent-wash: #1b2c2a;
  --quote-bg: #1c2725;
  --danger: #e2b93b;
  --field: #101614;
  --chip: #e6edeb;
  --chip-ink: #131c1a;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--ground);
}

body {
  color: var(--ink);
  font: 17px/1.45 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* 17px is not a default. It is the measured difference against Basecamp's
     feed, reported after a day of real use and signed off 2026-08-18. */
  -webkit-font-smoothing: antialiased;
  /* The notch, and the home indicator. */
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
    env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.wrap {
  max-width: 34rem;
  margin: 0 auto;
  padding: 0 14px 56px;
}

/* ---- the bar ----------------------------------------------------------- */
.bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 0 10px;
}
.bar h1 {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
  flex: 1;
}
.bar .today {
  color: var(--muted);
  font-size: 14px;
}

/* ---- the greeting ------------------------------------------------------
   Tab's block, same measurements, so the two apps read as one. */
.greeting {
  margin: 18px 0 16px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.greeting-words {
  min-width: 0;
}

/* The sky disc. Same 40px circle as a face, so the home screen has one round
   vocabulary rather than two.

   ⚠️ The ring is what stops the pale dawn sky dissolving into a pale card in
   light mode, and the dark night sky into the ground in dark mode. Inset, like
   .face, so it does not change the disc's footprint. */
.sky {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  display: grid;
  place-items: center;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 12%);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .sky {
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 16%);
  }
}
:root[data-theme='dark'] .sky {
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 16%);
}
/* 🔴 The svg carries its own width and height, but this is the belt: a
   viewBox-only icon dropped in a flex row has no intrinsic size and has
   already filled a whole screen once in this project. */
.sky svg {
  display: block;
  width: 40px;
  height: 40px;
}
.pagehead {
  font-size: 1.55rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 0 0 2px;
  line-height: 1.2;
}
.greeting-date {
  margin: 0;
  font-size: 0.88rem;
}
@media (max-width: 620px) {
  .pagehead {
    font-size: 1.35rem;
  }
}

/* ---- cards ------------------------------------------------------------- */
.card {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 14px;
  margin: 0 0 12px;
  overflow: hidden;
}
/* 🔴 BOTH SELECTORS, AND THE SECOND ONE WAS MISSING. `.card > h2` is a DIRECT
   child selector, so a heading inside `.card-head` — which is every card that
   names a column, "Team Roster" included — never matched it and fell back to
   the browser's own 25.5px h2. Measured 2026-08-25: `#team-heading` computed
   25.5px with no text-transform, sitting beside an 11px uppercase `.col-label`
   it is supposed to share a baseline with. It looked like a design choice and
   was a selector that missed. */
.card > h2,
.card-head > h2 {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
  padding: 12px 14px 8px;
}

/* A header row, so a column can be named over the column it names. */
/* ⚠️ THE LEFT 14px IS NOT DECORATION — IT IS THE CARD'S ONE INDENT.
   Everything else in this card is inset 14px: .person rows, and the h2 in
   every card that has a plain one. This row had padding-right only, so the
   heading started 1px inside the card border while the names under it started
   at 14 — a heading hanging off the left edge of its own list. Her report,
   2026-08-20. Measured before the fix: heading x=15, card x=14. */
.card-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 0 14px;
}
.card-head > h2 {
  flex: 1;
}
.col-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
.card-head + #people .person:first-child {
  border-top: 0;
}

/* ---- a person ---------------------------------------------------------- */
.person {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 14px;
  background: none;
  border: 0;
  border-top: 1px solid var(--hair);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.card > h2 + .person,
.person:first-child {
  border-top: 0;
}
.person:active {
  background: var(--accent-wash);
}

.face {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  overflow: hidden;
  /* ⚠️ inset rather than an outer border: a 1px outline would push every row
     1px wider and the ring is there to separate a PALE disc from a pale card,
     which an inset does just as well without moving anything. */
  box-shadow: inset 0 0 0 1px var(--face-ring, transparent);
  background: var(--face-bg, var(--quote-bg));
  color: var(--face-ink, var(--muted));
}
.face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ⚠️ display:block on all four, and it is not decoration. A flex CHILD is
   blockified automatically, but its own children are not: left inline, .name
   and .sub lay out side by side on one line and text-overflow does nothing at
   all — the name, the email and the clock all landed on top of each other. */
.who {
  flex: 1;
  min-width: 0;
  display: block;
}
.who .name {
  display: block;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.who .sub {
  display: block;
  font-size: 14px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.when {
  flex: 0 0 auto;
  display: block;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.when .clock {
  display: block;
  font-weight: 600;
}
.when .day {
  display: block;
  font-size: 12px;
  line-height: 1.2;
  color: var(--muted);
}

/* "Out" is the only state that gets a colour. In is the default and needs no
   decoration; a badge on every row would make the exception invisible. */
.tag {
  display: inline-block;
  vertical-align: baseline;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--quote-bg);
  color: var(--muted);
}
.tag.out {
  background: var(--accent-wash);
  color: var(--accent);
}

/* ---- announcements ----------------------------------------------------- */
.ann {
  padding: 12px 14px;
  border-top: 1px solid var(--hair);
  white-space: pre-wrap;
}
.ann:first-of-type {
  border-top: 0;
}
.ann .preset {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
  display: block;
  margin-bottom: 3px;
}

/* ---- a row that goes somewhere ----------------------------------------- */
.go {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 14px;
  border: 0;
  border-top: 1px solid var(--hair);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.go:first-child {
  border-top: 0;
}
.go:active {
  background: var(--accent-wash);
}
.go .label {
  flex: 1;
  font-weight: 600;
}
.go .value {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.go .chev {
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
}

/* ---- forms + buttons --------------------------------------------------- */
label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 5px;
}
input[type='email'],
input[type='password'],
input[type='date'],
input[type='text'],
textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--field);
  border: 1px solid var(--hair-strong);
  border-radius: 10px;
  padding: 11px 12px;
  margin: 0 0 14px;
}
input:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
textarea {
  min-height: 5rem;
  resize: vertical;
}

.btn {
  display: block;
  width: 100%;
  font: inherit;
  font-weight: 700;
  padding: 13px;
  border: 0;
  border-radius: 10px;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
}
.btn[disabled] {
  opacity: 0.55;
  cursor: default;
}
.btn.quiet {
  background: none;
  color: var(--accent);
  border: 1px solid var(--hair-strong);
}
.btn.danger {
  background: none;
  color: var(--danger);
  border: 1px solid var(--hair-strong);
}

.pad {
  padding: 14px;
}
.muted {
  color: var(--muted);
}
.small {
  font-size: 14px;
}
.note {
  color: var(--muted);
  font-size: 14px;
  padding: 0 2px 14px;
}
.err {
  color: var(--danger);
  font-size: 14px;
  padding: 0 0 12px;
}

/* ---- the sheet --------------------------------------------------------- */
dialog {
  border: 0;
  padding: 0;
  background: var(--card);
  color: var(--ink);
  width: min(34rem, 100vw);
  max-height: 88vh;
  border-radius: 16px 16px 0 0;
  margin: auto auto 0;
}
dialog::backdrop {
  background: rgb(0 0 0 / 45%);
}
.sheet-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  border-bottom: 1px solid var(--hair);
}
.sheet-head h2 {
  flex: 1;
  font-size: 19px;
  margin: 0;
}
.x {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 26px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

@media (prefers-reduced-motion: no-preference) {
  .person,
  .go {
    transition: background 90ms ease-out;
  }
}

/* ---- the picker -------------------------------------------------------- */

/* 🔴 [open], NOT the bare selector. `dialog { display: flex }` overrides the
   browser's own `display: none` on a CLOSED dialog, and the sheet renders
   permanently down the page — a trap that costs an afternoon because the
   markup and the script are both correct. The flex column is what lets the
   list scroll while the button stays put. */
#message-sheet[open] {
  display: flex;
  flex-direction: column;
}
.picker-list {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.picker-foot {
  flex: 0 0 auto;
  padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--hair);
  background: var(--card);
}
.picker-hint {
  margin: 8px 0 0;
  text-align: center;
}
.picker-note {
  padding: 12px 14px;
  border-top: 1px solid var(--hair);
}

/* The tick. A checkbox is the plainest control for "pick some people", and it
   is what every mail and messaging app uses.

   🔴 THE EMPTY RING IS --muted, NOT --hair-strong, AND IT IS MEASURED.
   --hair-strong is a HAIRLINE colour: against the card it runs 1.58 in light
   and 1.61 in dark, so the unticked circle is very nearly invisible on a phone
   — the first draft of the mockup had exactly that bug. --muted measures
   5.91 / 7.27, comfortably past the 3:1 a control needs. A ring the eye cannot
   find is not a control. contrast.test.mjs reads these two declarations back
   out of this file and fails if the ring is ever moved to a hairline. */
.pick {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  box-shadow: inset 0 0 0 1.5px var(--muted);
  color: transparent;
  font-size: 13px;
  font-weight: 700;
}
.person[aria-pressed='true'] .pick {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: none;
}

/* Listed, not hidden — the whole degraded path in this app names what is
   missing rather than dropping the person. Leah, today. */
.person[aria-disabled='true'] {
  cursor: default;
}
.person[aria-disabled='true']:active {
  background: none;
}
.person[aria-disabled='true'] .who .name {
  color: var(--muted);
}
/* The disabled ring stays faint ON PURPOSE, and it is the one place the low
   ratio is right: it says "not available" at a glance, and disabled controls
   are exempt from the contrast minimum. */
.person[aria-disabled='true'] .pick {
  box-shadow: inset 0 0 0 1.5px var(--hair-strong);
}

/* =========================================================================
   HQ HOME — the tab bar and the Home tab's cards.
   ========================================================================= */

/* ---- the tab bar --------------------------------------------------------
   🔴 FIXED TO THE BOTTOM, and the padding under .wrap is what keeps the last
   card from hiding behind it. On a phone the bar is the app's spine: it has to
   be reachable with a thumb without scrolling, which is the whole reason the
   cards moved off one very long screen in the first place.

   ⚠️ THE SAFE-AREA INSET IS ON THE BAR, NOT ONLY ON <body>. body's padding
   moves the whole document up off the home indicator; a position:fixed element
   is placed against the VIEWPORT and ignores it, so without this the bar sits
   under the indicator on every iPhone since the X. */
.tabs {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  background: var(--card);
  border-top: 1px solid var(--hair);
  padding-bottom: env(safe-area-inset-bottom);
}

/* The bar spans the viewport — it is the app's chrome — but its four buttons
   line up with .wrap, so on anything wider than a phone the tabs sit under the
   content rather than stretched across an empty screen. */
.tabs > .tab {
  max-width: calc(34rem / 4);
}
.tabs {
  justify-content: center;
}

.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  /* 52px + the icon and label is comfortably past the 44px tap target Apple
     asks for, which matters more here than anywhere else on the screen. */
  padding: 9px 4px 8px;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tab:active {
  background: var(--accent-wash);
}
/* 🔴 THE CURRENT TAB IS BOTH HEAVIER AND A DIFFERENT COLOUR. Colour alone is
   the thing a colour-blind reader loses, and on this bar it is the only
   indication of where you are. */
.tab.on {
  color: var(--accent);
  font-weight: 700;
}
.tab svg {
  /* ⚠️ Belt-and-braces beside the width/height attributes in the markup. A
     viewBox-only <svg> has no intrinsic size and fills its container; it
     happened once here and the sky disc took the whole screen. */
  width: 19px;
  height: 19px;
  flex: none;
}

/* The bar's own height, plus the inset, plus a card's margin. Without it the
   last card on a scrolled-to-the-end Home sits under the bar and looks cut. */
.wrap {
  padding-bottom: calc(64px + env(safe-area-inset-bottom));
}

/* ---- the Home cards ------------------------------------------------------
   They reuse .card, .card-head, .go, .who and .face from the roster, so the
   two tabs read as one app rather than as a card library bolted on. Only what
   is genuinely new is below. */

.home-body {
  padding-bottom: 2px;
}
.home-sub {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
  padding: 10px 14px 2px;
}

/* The count beside a heading. It is the one thing a card can still say when
   its rows have scrolled past. */
.count,
.chip {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--muted);
  background: var(--quote-bg);
  border-radius: 999px;
  padding: 2px 8px;
  flex: none;
}

/* A row on a Home card. .go gives it the padding, the hairline and the press
   state; this only fixes what a two-line row needs. */
.home-row {
  align-items: flex-start;
}
.home-row .who {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.home-row .name {
  font-weight: 600;
  /* 🔴 IT WRAPS TOO, for the same reason the sub-line does. `.who .name` is
     nowrap + ellipsis because a roster row holds ONE PERSON'S NAME and a long
     one should not push the clock off the row. A Home row holds a task title or
     a whole activity sentence — "Guillermo commented on Intro call prep" — and
     measured 2026-08-25 both were cut mid-word at the chevron. A title you
     cannot read is a row you cannot decide about. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}
.home-row .sub {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.35;
  /* 🔴 IT WRAPS, AND IT MUST. `.who .sub` is nowrap + ellipsis, which is right
     for a roster row — one person, one line — and wrong here: a Top Tasks
     sub-line carries the MODEL'S REASON, which is the entire point of that
     card, and Your Clients carries the consultant, the package and the end
     date. Measured 2026-08-25: the reason clipped at 297px of 603px, so half
     of every one of them was an ellipsis.
     The text is bounded — the reason is capped at 160 characters server-side —
     so it is allowed to run to two or three lines rather than clamped. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}
/* 🔴 THE CHEVRON MUST NOT DRIFT DOWN A TWO-LINE ROW. .go centres its children;
   these rows are top-aligned, so the chevron needs its own nudge to sit on the
   first line where the title is. */
.home-row .chev {
  align-self: flex-start;
  padding-top: 1px;
}

/* ⚠️ NOT A COLOUR ON ITS OWN. The dot says "there is a date here worth
   noticing"; the sub-line says which date, in words. Her standing rule for
   this card: never red, never a countdown, never "expiring". */
.mark {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  /* 🔴 --muted, NOT --hair-strong. That token is a HAIRLINE colour and runs
     1.58:1 against the card — right for a 1px line, and invisible as a 7px dot
     somebody is supposed to notice. --muted measures 5.91:1 light and 7.27:1
     dark, so the dot reads in both themes and in sunlight. Still neutral: the
     dot says "there is a date here", the sub-line says which. */
  background: var(--muted);
  flex: none;
  align-self: center;
}

/* Overdue is carried by the WORD in the sub-line. This is weight only — enough
   to find the row in a list of three, and nothing a reader could miss. */
.home-row.overdue .name {
  font-weight: 700;
}

.big {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  margin: 0;
  padding: 2px 14px 0;
}
.big small {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0;
}

.home-line {
  margin: 4px 0 0;
  padding: 0 14px;
  font-size: 14px;
}
.home-line .glyph {
  color: var(--muted);
}

.home-foot {
  margin: 0;
  padding: 10px 14px 12px;
  font-size: 13px;
  color: var(--muted);
}
.home-foot .link,
.link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}

.home-empty {
  margin: 0;
  padding: 4px 14px 12px;
  color: var(--muted);
  font-size: 14px;
}

.home-search {
  /* Full width inside the card's 14px indent, matching the rows under it. */
  display: block;
  width: calc(100% - 28px);
  margin: 4px 14px 8px;
  box-sizing: border-box;
}

/* The excerpt on a Recent Activity row — what was actually said.
   🔴 Visually quieter than the headline and NOT italic: these are other
   people's words, sometimes a client's, and italicising a whole quote makes a
   long one harder to read rather than more obviously a quote. The rule down the
   left is what marks it. */
.home-row .quote {
  margin-top: 4px;
  padding-left: 9px;
  border-left: 2px solid var(--hair-strong);
  color: var(--ink);
  font-size: 13px;
  line-height: 1.4;
}

/* Cash Collected's range chips, and Top Tasks' swap button. Both are controls
   on a card otherwise made of links, so both are unmistakably buttons. */
.chips {
  display: flex;
  gap: 6px;
  padding: 4px 14px 2px;
}
.chip-btn {
  border: 1px solid var(--hair-strong);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* 🔴 The chosen range is a FILL, not just a colour swap — on a phone in
   sunlight a tint alone disappears, and this is the control that says which
   number you are looking at. --chip / --chip-ink are the tokens the roster
   already uses for a solid pill, and they are contrast-checked in both themes. */
.chip-btn.on {
  background: var(--chip);
  border-color: var(--chip);
  color: var(--chip-ink);
}

/* ⚠️ A REAL BUTTON, NEVER A SWIPE. A swipe on a card full of links fires when
   somebody meant to scroll. 34px square clears the 44px target with the row's
   own padding around it. */
.swap {
  flex: none;
  align-self: center;
  width: 34px;
  height: 34px;
  margin-right: -6px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.swap:active {
  background: var(--accent-wash);
  color: var(--accent);
}
.swap[disabled] {
  opacity: 0.4;
}

/* ---- Recent Activity: the Activity tab's own colour, on Home --------------
   Her question, 2026-08-25: "any reason it doesn't look exactly like the
   Activity stream under the Activity tab?" There wasn't one. The face and the
   board pill are that page's two colour devices, same hash and same contrast
   floor, so one board and one person tint identically on both screens. */

/* 28px, not the roster's 40: an activity row is a sentence with a face beside
   it, and a 40px disc beside two lines of text becomes the loudest thing on a
   card that is meant to be read. */
.act-face {
  width: 28px;
  height: 28px;
  font-size: 11px;
  flex: none;
  align-self: flex-start;
  margin-top: 1px;
}

.board-pill {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  border-radius: 999px;
  padding: 1px 8px;
  /* 🔴 The pill's own colours are set inline, per board, and they are FITTED to
     4.5:1 against their own tint — never assumed. A hue sweep at one fixed
     lightness passes at 210° and fails at 60°. */
}

/* ---- folding a Home card --------------------------------------------------
   Her ask, 2026-08-25: Recent Activity, Your Clients and Top Tasks all fold.
   Same three rules the desktop cards run on — default expanded, remembered per
   browser, and the count stays on the header so a folded card still says
   something. */

/* 🔴 THE WHOLE HEADER IS THE CONTROL, not the chevron. A 16px chevron is under
   half the 44px a thumb needs, and this was asked for on a phone. As a <button>
   it needs the resets a <div> never did. */
.card-head.foldable {
  width: 100%;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* The header row was `padding: 0 14px` as a div; a tappable one needs real
     height above and below the words. */
  padding: 12px 14px 8px;
  -webkit-tap-highlight-color: transparent;
}
.card-head.foldable:active {
  background: var(--accent-wash);
}
/* The h2 keeps its own padding as a plain child of .card; inside the button the
   button now owns it, so cancel the double. */
.card-head.foldable > h2 {
  padding: 0;
}

.fold-chev {
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  flex: none;
  /* Points down when open, right when folded — the direction says which way
     the content went, not merely that something is foldable. */
  transform: rotate(90deg);
  transition: transform 0.15s ease;
}
@media (prefers-reduced-motion: reduce) {
  .fold-chev { transition: none; }
}
.home-card.folded .fold-chev {
  transform: rotate(0deg);
}

/* ⚠️ display:none, not [hidden] and not height:0. The UA's [hidden] rule is the
   weakest there is and loses to any display declaration — this project has
   already shipped that bug once. And a zero-height card still holds its rows in
   the tab order, so a folded card would eat every one of its links on the way
   past with a keyboard. */
.home-card.folded .home-body {
  display: none;
}
/* Folded, the header IS the card, so its bottom padding has to close it. */
.home-card.folded .card-head.foldable {
  padding-bottom: 12px;
}
