/* ============================================================================
   Growth & Marketing work — application styles.
   Values here are taken from the wireframes. Where a number looks oddly
   specific (10.5px, .14em) it is deliberate and comes from the design.
   ========================================================================== */

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

html, body { margin:0; padding:0 }
body {
  background: var(--mh-white);
  color: var(--text-body);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--text-link); text-decoration: none }
/* No hover underline anywhere by default. Since 15 September 2026 (Idea
   Foundry card "Link consistency", step two) a link is underlined only when it
   sits inside running text, and then at all times -- see .proselink beside
   .rowlink below. Rows, cards, tables and link-styled controls never
   underline; their colour, or the row tint, is the affordance. Before this an
   `a:hover { underline }` sat here and was overridden in eleven places, eight
   of them inline in views, so what a link did on hover depended on which
   screen it was on. */
button { font-family: inherit }
[hidden] { display: none !important }

/* ---------------------------------------------------------------- suite bar */
/* The artboards draw a 3px accent rule under the black bar. As a border it
   would cost 3px of height on every screen; as an inset shadow it paints inside
   the 48px the bar already has, so the layout below it does not move. */
.suitebar {
  height:48px; background:var(--mh-black); display:flex; align-items:center;
  gap:18px; padding:0 16px; flex:none;
  box-shadow:inset 0 -3px 0 var(--mh-blue);
}
.waffle { display:grid; grid-template-columns:repeat(3,3px); gap:2px; flex:none }
.waffle i { width:3px; height:3px; background:var(--mh-grey-500); display:block }
.suitebar .wordmark {
  font:var(--fw-black) 13px/1 var(--font-display);
  letter-spacing:.06em; color:var(--mh-white); white-space:nowrap;
}
.suitebar .spacer { flex:1 }
.avatar {
  width:28px; height:28px; border-radius:var(--radius-pill); flex:none;
  display:flex; align-items:center; justify-content:center;
  font:var(--fw-bold) var(--fs-tiny) var(--font-sans); color:var(--mh-white);
  /* --mh-blue carried white initials at 2.96:1. --mh-blue-deep is 7.9:1 and is
     already the navigation column, so the initials stay white and the colour
     stays in the family. Black on --mh-blue would also have passed at 7.08:1
     and was rejected: it makes the chip read as a warning. */
  background:var(--mh-blue-deep);
}
/* White on --mh-grey-500 is 3.54:1. --mh-grey-700 is 7.25:1. */
.avatar.guest { background:var(--mh-grey-700) }
.avatar.sm { width:22px; height:22px; font-size:10.5px; background:var(--mh-grey-200); color:var(--text-muted) }
.avatar.xs { width:20px; height:20px; font-size:10px;   background:var(--mh-grey-200); color:var(--text-muted) }
.avatar.ring { width:30px; height:30px; font-size:var(--fs-tiny); background:var(--mh-grey-200);
               color:var(--text-muted); border:1px solid var(--border-default) }

/* -------------------------------------------------------------- site header */
.siteheader {
  height:62px; border-bottom:1px solid var(--border-default); flex:none;
  display:flex; align-items:center; gap:14px; padding:0 20px; background:var(--mh-white);
}
.sitemark {
  width:34px; height:34px; background:var(--mh-black); color:var(--mh-white); flex:none;
  display:flex; align-items:center; justify-content:center;
  font:var(--fw-black) 13px var(--font-display); letter-spacing:.02em;
}
.sitetitle { font:var(--fw-bold) var(--fs-head)/1.2 var(--font-sans); color:var(--mh-black) }
.sitesub   { font:var(--fw-regular) var(--fs-label-lg)/1.2 var(--font-sans); color:var(--text-subtle) }
.headeractions { display:flex; align-items:center; gap:8px;
                 font:var(--fw-regular) var(--fs-note) var(--font-sans); color:var(--text-muted) }

/* Flat header chips: the wireframes use bare padded spans, not buttons. */

/* ------------------------------------------------------------------ shell */
.shell { display:flex; flex-direction:column; min-height:100vh }
.shell-body { display:flex; flex:1; min-height:0; align-items:stretch }

/* The column is dark, so three semantic tokens are rebound for its subtree and
   every rule below — and every inline style written against them, of which the
   sign-out button is one — resolves to the light values without being touched.
   Rebinding the semantics is deliberate; rebinding a palette token such as
   --mh-black would read as a lie two screens later. The border goes because on
   a filled column it is a seam rather than an edge. */
nav.sidenav {
  width:210px; flex:none; border-right:1px solid transparent;
  padding:16px 12px; display:flex; flex-direction:column; gap:2px;
  background:var(--nav-surface);

  /* The column re-grounds its own ink: a token that means "quiet grey" on white
     means "invisible" here. Anything inside that reads these gets the column's
     values for free — and anything that writes a colour of its own does not,
     which is worth remembering before setting one. */
  --text-muted:    rgba(255,255,255,.80);   /* 8.55:1 */
  --text-subtle:   rgba(255,255,255,.68);   /* 6.67:1, and the tier below
                                               --text-muted is a real one again:
                                               6.67 against 8.55. This value has
                                               been cut three times, all of them
                                               against a moving ground. .62 on
                                               the old navy measured 4.09:1 and
                                               missed the 4.5:1 floor. .68 made
                                               4.60:1 there — then 4.02:1 on the
                                               palette teal, the same ink missing
                                               the same floor on a lighter
                                               ground. .76 cleared it at 4.57:1
                                               and flattened the tier to .3.
                                               Darkening the ground was the fix
                                               the ink could not be: see
                                               --mh-navy in tokens.css. */
  /* .linkish on this column is --mh-blue-link #0563c1 at 2.11:1: rendered, full
     opacity, and absent from a screenshot of the page. Overridden here rather
     than on the element so the next link dropped into this column inherits a
     legible one instead of repeating the fault. --mh-blue itself is no help:
     2.66:1 here, the same as black. */
  --text-link:     var(--mh-blue-pale);     /* 8.63:1 */
  --border-subtle: rgba(255,255,255,.18);
}
/* The one uppercase label. Three sizes, one tracking — before this the app had
   eight trackings between 0.04em and 0.16em at five sizes, every one of them a
   small capitalised label and no two of them agreeing. */
.overline {
  font:var(--fw-bold) var(--fs-label) var(--font-sans); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--text-subtle);
}
.overline.lg { font-size:var(--fs-label-lg) }
.overline.sm { font-size:var(--fs-label-sm) }
nav.sidenav .overline { padding:6px 10px 8px }
/* The eight labels take --fs-body rather than --fs-copy: 13px was small for
   something a reader aims at all day. It reuses a size the scale already has
   instead of adding one, which is what tokens.css asks for. */
nav.sidenav a.nav {
  position:relative; padding:8px 10px 8px 13px; text-decoration:none; border-radius:var(--radius-md);
  font:var(--fw-regular) var(--fs-body) var(--font-sans); color:var(--text-muted);
  display:flex; align-items:center; gap:7px;
}
nav.sidenav a.nav:hover { background:rgba(255,255,255,.08); text-decoration:none }
/* The artboards fill the selected item with --mh-blue and set white on it,
   which measures 2.96:1 against 17.8:1 for the black-on-grey it replaces. So
   the fill is a tint of the column itself — white at 14%, 8.10:1, and the same
   device the artboards use for their own dividers — and --mh-blue takes the
   marker, which is the shape this already had.

   The marker measures 4.20:1 here and clears the 3:1 of WCAG 1.4.11, which it
   had never done: 2.66:1 on --mh-blue-deep and 2.21:1 on the palette teal, both
   accepted on the same reasoning — the selected item is also the only bold one,
   the only white one and the only one with a fill, so no state was ever carried
   by that 3px alone. The reasoning still holds; it is simply no longer needed. */
nav.sidenav a.nav.active {
  font-weight:var(--fw-bold); color:var(--mh-white); background:rgba(255,255,255,.14);
}
nav.sidenav a.nav.active::before {
  content:""; position:absolute; left:0; top:6px; bottom:6px; width:3px; background:var(--mh-blue);
}
nav.topnav a.nav .count { font-family:var(--font-mono); font-size:var(--fs-mono-sm); color:var(--mh-danger) }
/* Red text on this column is 1.69:1 at --mh-black and no better in red.
   There is no colour that fixes it: white
   alone loses the alarm and no red is legible as type on that ground. The fill
   is what makes the colour work, at 5.4:1 — the one place in the colour pass
   where something other than a colour had to change. */
nav.sidenav a.nav .count {
  font-family:var(--font-mono); font-size:var(--fs-mono-sm);
  background:var(--mh-danger); color:var(--mh-white);
  padding:1px 6px; border-radius:var(--radius-pill);
}

/* ------------------------------------------------------- the folded sidebar
   The column folds to a rail of icons on a toggle, and unfolds again (Neil,
   2 September 2026). Everything the fold does is here, on
   `nav.sidenav.collapsed`, so the toggle is one class on one element: no
   re-render, and nothing for a screen to remember. chrome.js keeps the state.

   The icons are in the DOM at both widths and hidden here while the labels are
   showing — the wide column is unchanged from the day the wireframes were
   drawn, and gains nothing from a picture beside a word it already reads.

   No transition on the width. It is a flex sibling of the canvas, so animating
   it relayouts every screen behind it — the capacity grid, the burn-up's
   viewBox — for eight frames, and this application animates nothing else that
   moves other people's layout. The fold is instant on purpose. */
.navhead { display:flex; align-items:center; gap:6px }
.navhead .overline { flex:1; min-width:0 }
.navtoggle {
  flex:none; width:28px; height:28px; padding:0; border:0; background:none;
  display:flex; align-items:center; justify-content:center;
  color:var(--text-subtle); cursor:pointer;
}
.navtoggle:hover { background:rgba(255,255,255,.08); color:var(--mh-white) }
.navicon { flex:none; display:none }
/* The toggle's own chevron is the exception, and it took a screenshot to see
   why: `.navicon` is hidden while the labels are showing, so the control that
   folds the column was an empty 28px button on every wide screen — present,
   aimable, and drawing nothing. The chevron IS this control's label. */
.navtoggle .navicon { display:block }
/* An item whose key this application has no icon for. Deliberately plain and
   deliberately visible: see the fallback note in components/icons.js. */
.naviconfallback {
  flex:none; display:none; width:20px; text-align:center;
  font:var(--fw-bold) var(--fs-body) var(--font-sans);
}
/* Was three inline styles on the div in chrome.js; the comment there records
   why the pin had to become a rule. */
.navbottom { margin-top:auto; padding:10px; border-top:1px solid var(--border-subtle) }
.navbottom.undernote { margin-top:0 }
/* `nav.sidenav a.nav` sets the padding for every item in the column and wins
   on specificity, so the foot's outward link has to out-rank it to keep the
   flush edge it had when this value was inline. */
nav.sidenav .navbottom a.nav { padding:6px 0 }
.navsignout { margin-top:4px }

/* 56px: 20px of icon centred in the 44px left by the padding, which is also the
   width that leaves the 3px active marker clear of the glyph. */
nav.sidenav.collapsed { width:56px; padding-left:6px; padding-right:6px }
nav.sidenav.collapsed .navlabel,
nav.sidenav.collapsed .overline,
nav.sidenav.collapsed .navnote { display:none }
nav.sidenav.collapsed .navicon,
nav.sidenav.collapsed .naviconfallback { display:block }
nav.sidenav.collapsed .navhead { justify-content:center }
nav.sidenav.collapsed a.nav { justify-content:center; padding:9px 0 }
nav.sidenav.collapsed .navbottom { padding:10px 0 }
nav.sidenav.collapsed .navbottom.undernote { margin-top:auto }
nav.sidenav.collapsed .navsignout { display:flex; width:100%; justify-content:center }
/* The count cannot sit beside a hidden label, and at 56px it cannot share the
   cell with the icon either: a two-digit pill measures 24px against a 20px
   glyph in a 43px item, so it covers the icon rather than sitting on it. Tried
   and rejected before this — the digits ringed in the column's ground, which
   reads as a red pill where an icon should be.

   So the rail keeps the FACT and drops the FIGURE: a dot on the icon's
   shoulder, with the number in the item's tooltip and in its accessible name,
   both built from the same count in chrome.js. The ring is the column's own
   ground, so the dot reads as sitting on the glyph rather than punched into it.

   `font-size:0` rather than `color:transparent`: the digits have to stop
   claiming space, not just stop being visible. They are still in the element,
   which is what a 9px dot has instead of a label. */
nav.sidenav.collapsed a.nav .count {
  position:absolute; top:6px; right:8px;
  width:9px; height:9px; padding:0; border-radius:var(--radius-pill);
  font-size:0; line-height:0; overflow:hidden;
  box-shadow:0 0 0 2px var(--nav-surface);
}

/* ------------------------------------------------- narrow-screen navigation
   The sidebar's replacement below 900px: the same eight screens in one
   horizontally scrolling row, plus the two controls that otherwise vanish with
   the sidebar. Hidden by default and shown by the media query at the foot of
   this file, so the two can never both be on screen. */
nav.topnav {
  display:none; align-items:stretch; gap:2px; flex:none;
  background:var(--mh-white); border-bottom:1px solid var(--border-default);
  padding:0 8px; overflow-x:auto; overscroll-behavior-x:contain;
  /* iOS draws an overlay scrollbar only while scrolling, so hiding the bar
     costs a phone nothing. What says there is more to the right is the next
     item being clipped by the edge, which is why nothing here snaps items to
     the boundary. */
  scrollbar-width:none;
}
nav.topnav::-webkit-scrollbar { display:none }
/* --fs-copy stays 13px here, where the sidebar's labels moved to --fs-body.
   Settled 31 August 2026: these are different surfaces read at different
   device sizes, and sharing the NAV array is not a reason to share a type
   size. The pixel of disagreement is deliberate — do not unify them. */
nav.topnav a.nav, nav.topnav button.nav {
  display:flex; align-items:center; gap:6px; white-space:nowrap; flex:none;
  /* 44px tall including the border: a finger-sized target, not a mouse one. */
  padding:13px 12px 11px; border:0; border-bottom:2px solid transparent;
  background:none; cursor:pointer; text-decoration:none;
  font:var(--fw-regular) var(--fs-copy) var(--font-sans); color:var(--text-muted);
}
/* The strip does not take the column's colour. It sits directly under the black suite bar and
   the white site header, and a third band would eat a phone screen — so it
   takes the accent at the scale it has, on the underline. The artboards are
   1440px only and drew none of this. */
nav.topnav a.nav.active {
  color:var(--mh-black); font-weight:var(--fw-bold); border-bottom-color:var(--mh-blue);
}
nav.topnav .quiet { color:var(--text-subtle); font-size:var(--fs-label-lg) }
nav.topnav .sep {
  flex:none; align-self:center; width:1px; height:18px; margin:0 6px;
  background:var(--border-default);
}
/* UNUSED since 1 September, kept on purpose for a future foot-of-sidebar note.
   Its `margin-top:auto` is no longer what holds Sign out at the foot of the
   column -- that moved onto the block itself in chrome.js when the rule's only
   user was removed -- so putting this class back will not silently take the
   layout over again. */
.navfoot {
  margin-top:auto; padding:10px; border-top:1px solid var(--border-subtle);
  font:var(--fw-regular) var(--fs-tiny)/1.5 var(--font-sans); color:var(--text-subtle);
}
.navnote {
  margin-top:22px; padding:12px 10px; border-top:1px solid var(--border-subtle);
  font:var(--fw-regular) var(--fs-tiny)/1.6 var(--font-sans); color:var(--text-subtle);
}

/* The board's task filters, one of the two things that render into .navnote.
   Every line of this was written inline in board.js, which is exactly how its
   selected row held --mh-black at 2.66:1 through a colour pass that re-grounded
   the whole column around it: an inline value is invisible to a stylesheet and
   inherits none of the token overrides nav.sidenav sets. Reachable now.

   The padding and the negative margin are on every row, selected or not. They
   cancel, so the label does not move when the fill appears, and the fill spans
   the same 12–197px band as a.nav.active rather than a narrower one inside it. */
.navfilters .overline { margin-bottom:10px }
.navfilters .f {
  display:flex; justify-content:space-between; gap:10px; text-decoration:none;
  padding:5px 10px; margin:0 -10px; color:var(--text-muted); border-radius:var(--radius-md);
}
.navfilters .f .n { color:var(--text-subtle) }
/* White on a 14% tint of the column — 5.58:1, the same device and the same
   measurement as the selected navigation item a few rows above it. */
.navfilters .f.on {
  color:var(--mh-white); background:rgba(255,255,255,.14);
  font-weight:var(--fw-semibold);
}
.navfilters .f.on .n { color:var(--mh-white) }
.navfilters .clear { display:inline-block; margin-top:8px }

main.canvas {
  flex:1; min-width:0; background:var(--surface-page);
  padding:26px 28px; display:flex; flex-direction:column; gap:20px;
}
main.canvas.paper { background:var(--mh-white); padding:0 }

/* ---------------------------------------------------------- the type scale
   Every size the application is allowed to set, as a class. The sizes
   themselves are tokens — see "The type scale" at the foot of tokens.css for
   why they exist and what each one is for.

   These carry size, weight, family and leading, and nothing else. Colour stays
   with .muted / .subtle / .ink so that a screen can say "smaller" without also
   having to restate "and grey", which is how six near-identical declarations
   grew out of one.

   Nothing here is allowed to be written inline in a view again:
   tools/check-inline-type.py fails the commit. */
.t-title-window {
  margin:0; font:var(--fw-extrabold) var(--fs-title-window)/1.02 var(--font-display);
  letter-spacing:-.03em; color:var(--mh-black);
}
.t-title-record {
  margin:0; font:var(--fw-extrabold) var(--fs-title-record)/1.05 var(--font-display);
  letter-spacing:var(--ls-tight); color:var(--mh-black);
}
.figure {
  font:var(--fw-extrabold) var(--fs-figure)/1 var(--font-display);
  letter-spacing:var(--ls-tight);
}
.t-head-lg { font:var(--fw-bold) var(--fs-head-lg)/1.3 var(--font-sans); color:var(--mh-black) }
.t-head    { font:var(--fw-bold) var(--fs-head)/1.25 var(--font-sans); color:var(--mh-black) }
.t-body    { font:var(--fw-regular) var(--fs-body)/1.6 var(--font-sans) }
.t-copy    { font:var(--fw-regular) var(--fs-copy)/1.6 var(--font-sans) }
.t-note    { font:var(--fw-regular) var(--fs-note)/1.5 var(--font-sans) }
.t-tiny    { font:var(--fw-regular) var(--fs-tiny)/1.5 var(--font-sans) }
.t-name    { font:var(--fw-semibold) var(--fs-copy)/1.35 var(--font-sans); color:var(--mh-black) }
.t-strong  { font-weight:var(--fw-semibold) }
.t-bold    { font-weight:var(--fw-bold) }
.ink       { color:var(--mh-black) }

/* ------------------------------------------------------------- page titles */
.pagehead { display:flex; align-items:flex-end; justify-content:space-between; gap:20px; flex-wrap:wrap }
h1.page {
  margin:0; font:var(--fw-extrabold) var(--fs-title-page)/1.05 var(--font-display);
  letter-spacing:var(--ls-tight); color:var(--mh-black);
}
.eyebrow { margin-bottom:6px }

/* The way back, on the two screens that have a parent to go back to: the
   project record and the request form. One treatment, and the page you are on
   is the black segment at the end of it — grey on both halves said "these are
   two links" on a line where only the first one is. */
.breadcrumb { font:var(--fw-regular) var(--fs-label-lg) var(--font-sans); color:var(--text-subtle) }
/* A border colour used as type: #c6c6c6 is 1.71:1 on white. It is a glyph a
   reader parses, not a rule, so it takes text ink. */
.breadcrumb .sep { color:var(--text-subtle); margin:0 4px }
.breadcrumb a { color:var(--text-subtle) }
.breadcrumb .here { color:var(--mh-black) }

/* -------------------------------------------------------------------- card */
/* CORNERS (11 September 2026, Idea Foundry card "UI enhancement: rounded
   corners", Neil's rule after three variants on the Feed): CONTAINERS take
   --radius-lg (8px), CONTROLS take --radius-md (4px), everywhere. A container
   is a bordered or filled surface that holds content -- cards, tiles, the
   board's columns, callouts and the window's two red notes, the composer, the
   post list, the toast, the sign-in card. A control is a thing you press or type into -- fields,
   buttons, chips, the checkbox, the navigation items. An inner surface is
   never rounder than its container, which the two sizes guarantee.

   What stays square, and why: the side panel, the site header and the top
   navigation are attached to the viewport's edge, not floating in the canvas;
   the digest's two halves and the internal-client window's hero and body are
   full-bleed bands; table rows, header bands and group rows are the inside of
   a container, not containers. Schedule and timeline bars keep their ends.
   Pills, dots, avatars and the radio stay round. .awayblock, chip-sized, keeps
   --radius-md.

   A container's first and last child inherit its corners, so a header band
   or an own-post rule at the top of a card follows the curve without the card
   clipping its overflow -- overflow:hidden would have turned the schedule's
   sticky label column into a fixed one. A table card clips anyway, because
   .tbl scrolls sideways, and a scrolling box clips to its own corners. */
.card { background:var(--surface-card); border:1px solid var(--border-default); border-radius:var(--radius-lg) }
.card > :first-child, .feedlist > :first-child { border-top-left-radius:inherit; border-top-right-radius:inherit }
.card > :last-child, .feedlist > :last-child { border-bottom-left-radius:inherit; border-bottom-right-radius:inherit }
.card.accent  { background:var(--mh-blue-pale); border-color:var(--mh-blue-pale) }
/* "My week" on My Work. Was solid black, which is hard on the eyes for a panel
   you look at every morning. The warm pair is already in the palette — the On
   hold pill and the Badge component use it — so this adds no new colour.
   Separate from .card.accent, which is the pale blue the project screen uses.
   .overline is overridden here because its --text-subtle grey measures 3.2:1
   against this background, and measured no better on the black it replaced. The
   warm ink is 5.0:1. Body text is --text-muted at 6.4:1, black at 18.6:1. */
.card.warm { background:var(--mh-warning-soft); color:var(--mh-black);
             border-color:var(--mh-warning) }
.card.warm .overline { color:var(--mh-warning-ink) }
/* Three paddings, plus none at all for a card that holds a table and lets its
   own rows do the spacing. .card-pad is the standard; .tight is a card in a
   dense column, where the standard one costs more than it gives; .projhero
   below is the hero. Eight different paddings used to be spread across seven
   files, four of them written inline. */
.card-pad { padding:18px 20px }
.card-pad.tight { padding:12px 14px }
.card-pad.wide { padding:26px 28px }
.card-head {
  display:flex; align-items:center; gap:12px; padding:14px 18px;
  border-bottom:1px solid var(--border-subtle);
}
.card-head .t { font:var(--fw-bold) var(--fs-head-sm) var(--font-sans); color:var(--mh-black) }
.card-head .sub { font:var(--fw-regular) var(--fs-label-lg) var(--font-sans); color:var(--text-subtle) }
.card-head .spacer { flex:1 }
/* A card-head inside a card that already has card-pad (#84, 24 September
   2026). The head's own 14px 18px is for a head that IS the card's top edge;
   inside card-pad it stacked on the card's 18px 20px, which set the heading
   18px in from the rows beneath it and 14px lower than the card's padding
   says. `flush` takes the head's padding off on every side but the rule
   below it, so the heading lines up with what is under it. First on the two
   Reference cards, then on every reference list screen that nests a head.

   `tblstrip` is the padded strip above a .tbl on those screens: a heading and
   a note, then the table outside the padding. card-pad's 20px sides set the
   note 2px in from the rows, whose inset is 18px (.tbl .th and .tbl .tr), so
   the strip takes 18px too and heading, note and rows share one edge on both
   sides. The bottom stays 6px, what each of them set inline before. */
.card-head.flush { padding:0 0 14px }
.card-pad.tblstrip { padding:18px 18px 6px }

/* --------------------------------------------------------------- stat tiles */
.tiles { display:grid; grid-template-columns:repeat(4,1fr); gap:14px }
.tile { background:var(--surface-card); border:1px solid var(--border-default); padding:18px 20px; border-radius:var(--radius-lg) }
.tile .label { font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-subtle); margin-bottom:8px }
.tile .value { font:var(--fw-extrabold) 26px/1 var(--font-display); letter-spacing:var(--ls-tight); color:var(--mh-black) }
.tile .sub   { font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-muted); margin-top:6px }

/* ------------------------------------------------------------------- table */
/* A scroll container at every width, not only on a phone. Each table below
   declares the width its own columns need, and above that this rule does
   nothing; below it, the table scrolls inside itself rather than widening the
   page and taking the heading and the filter chips with it. Same mechanism as
   .capgrid, which is why the capacity screen never had the fault. */
.tbl { display:block; width:100%; overflow-x:auto }
.tbl .th, .tbl .tr { display:grid; align-items:center; gap:0 }
/* One header band, shared with the capacity grid, which is not a .tbl but
   draws the same thing. It used to declare its own copy of this rule with
   12px of padding rather than 9px, so the capacity header stood 6px taller
   than every other header on the site and the two would have drifted the next
   time either was touched. */
.tbl .th, .capgrid .th {
  padding:9px 18px; background:var(--surface-header); border-bottom:1px solid var(--border-header);
  font:var(--fw-bold) var(--fs-label) var(--font-sans); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--header-ink);
}
.tbl .tr {
  padding:12px 18px; border-bottom:1px solid var(--border-subtle);
  font:var(--fw-regular) var(--fs-copy) var(--font-sans); color:var(--text-body);
  text-align:left; width:100%; background:none; border-left:0; border-right:0; border-top:0;
}
button.tr { cursor:pointer; transition:background var(--dur-fast) var(--ease-standard) }
button.tr:hover { background:var(--mh-grey-100) }
/* A row split into two targets — Tasks and My work, 15 September 2026, from
   the card "Navigate to projects from tasks". The row is a div that opens the
   task on click, the task name is a real button that does the same, and the
   project name is a real link to the project; see splitRow in teamTasks.js
   for why two controls rather than a handler nested in one button. Since
   step four of "Link consistency" the Team roster row is one too. The row
   takes the hover the button rows have; the ring goes on the two inner
   controls, which is where a keyboard lands (a:focus-visible already covers
   the link). The button leaves weight to `.tbl .tr .name` above and takes
   everything else from the row, so it reads exactly as the cell did. The link
   is only as wide as its text, so the hit area is the name and not the empty
   two-thirds of the column beside it. No underline, hovered or not, which
   since step two of "Link consistency" is simply what a link in a row does;
   it underlined on hover for an afternoon on 15 September 2026 and Neil
   dropped it the same day as inconsistent with the rest of the row, and that
   decision became the site-wide rule. */
.tr.split { cursor:pointer; transition:background var(--dur-fast) var(--ease-standard) }
.tr.split:hover { background:var(--mh-grey-100) }
/* .rowopen was .taskopen until step four of "Link consistency" (15 September
   2026), when the Team roster's row took the same shape: the person's name a
   real button that opens the workload panel, the "edit" control a real button
   that opens the record, and the row itself keeping its click. Until then
   "edit" was a span with a click handler inside a button row -- mouse-only,
   and the one control in the app a keyboard could not reach. */
.tr.split .rowopen {
  background:none; border:0; padding:0; margin:0; text-align:left; cursor:pointer;
  font-family:inherit; font-size:inherit; line-height:inherit; color:inherit; gap:8px;
}
.tr.split .rowopen:focus-visible { outline:2px solid var(--focus-ring); outline-offset:2px }
.tr.split a.rowlink { justify-self:start }
.tbl .tr .name { font-weight:var(--fw-semibold) }
.tbl .grouprow {
  display:flex; align-items:center; gap:10px; padding:12px 18px;
  background:var(--surface-band); border-bottom:1px solid var(--border-band);
}
.tbl .grouprow .g { font:var(--fw-bold) var(--fs-note) var(--font-sans); color:var(--mh-black) }
.tbl .grouprow .n { font:var(--fw-regular) var(--fs-mono-sm) var(--font-mono); color:var(--text-subtle) }
/* The person band on Team tasks: a .grouprow whose name is a drill into the
   workload panel, with the week's figure and meter at the right — the same
   cell the Capacity grid draws for that person and week. Wraps, because the
   role, the count and the figure will not share 375px.

   HEAVIER THAN THE OTHER GROUP ROWS, on Neil's report the day it shipped: with
   eight people and thirty rows the names were hard to pick out, because a
   13px bold name on a blue-050 wash sits too close to a 13.5px semibold task
   name on white. Three things separate them now, and each on its own was not
   enough. The band takes --surface-header, the fill the column header already
   uses for "a section begins here", with the header rule above and below it.
   The name takes the card-head size, so it is a heading and not a row. And a
   gap of card white opens above each band -- not the first, which sits under
   the column header -- so a group ends before the next one starts. An empty
   row was the alternative, and would have read as a row with nothing in it. */
.tbl .grouprow.personband {
  flex-wrap:wrap; row-gap:6px; margin-top:14px; padding:13px 18px;
  background:var(--surface-header);
  border-top:1px solid var(--border-header); border-bottom:1px solid var(--border-header);
}
.tbl .th + div .personband { margin-top:0; border-top:0 }
.tbl .grouprow.personband .g { font:var(--fw-bold) var(--fs-head-sm) var(--font-sans); color:var(--mh-black) }
.tbl .grouprow .persondrill { width:auto; flex:none }
.grouprow .wk { display:flex; align-items:center; gap:10px; flex:none }
.grouprow .wk .meter { width:120px }
.rowlink { color:var(--text-link) }
/* The two kinds of link, decided here and nowhere else (Idea Foundry card
   "Link consistency", 15 September 2026).

   .proselink -- a link inside running text: a Feed post, the sentence under a
   record that cannot be found, the note on the Team screen, the sign-in page.
   Underlined at all times, because in a paragraph colour alone is not enough
   to say which words are the link (WCAG 1.4.1), and link blue. Inherits size
   and weight from the sentence it is in. The Feed had its own version of this
   with a 2px underline offset no other link had; it uses this now.

   .blocklink -- an anchor whose content is a block: a row, a card, a wrapped
   button. Takes the colour of where it sits and, like every link that is not
   prose, never underlines. Step one of the card moved eight anchors onto it
   from `textDecoration: "none"` written inline (no visible change, checked by
   a computed-style digest before and after); step two removed the site-wide
   hover underline, so the class no longer has to say "none" itself -- the
   bare `a` rule at the top of the file does. Colour is set on the class alone
   so `.tbl .tr`, which is more specific, still decides the colour of a row
   that is a link.

   Anything else in link blue -- .rowlink, .linkish, the project name on a
   Board card -- is a link or a link-styled control in a row, card or table,
   and its colour is the whole affordance. */
.proselink { color:var(--text-link); text-decoration:underline }
.blocklink { color:inherit }
/* "OFF TRACK", outlined, beside a project name whose data argues with its
   flag. The colour is the severity and comes from the caller; everything else
   is the label scale. It was 9.5px at 0.06em written inline — a ninth size
   and a seventh tracking for one span. */
.flagchip {
  margin-left:8px; padding:1px 5px; white-space:nowrap;
  font:var(--fw-bold) var(--fs-label-sm) var(--font-sans);
  letter-spacing:var(--ls-overline); text-transform:uppercase;
  border:1px solid currentColor; border-radius:var(--radius-md);
}
/* Two mono sizes, not six. A date, a reference or a count in a row is
   --fs-mono; the same thing as a secondary annotation under something else is
   --fs-mono-sm. The numerals on the capacity grid are not this — they are a
   value being read rather than a note, and keep their own size. */
.mono { font-family:var(--font-mono); font-size:var(--fs-mono); color:var(--text-muted) }
.mono.sm { font-size:var(--fs-mono-sm) }
.mono.warn { color:var(--mh-danger) }
.emptyrow { padding:22px 18px; font:var(--fw-regular) var(--fs-copy) var(--font-sans); color:var(--text-subtle) }

/* ------------------------------------------------------- the four tables
   Every table's columns, and the width below which they stop making sense.
   Held here rather than inline on the elements for two reasons, both learnt
   the hard way: an inline grid-template-columns cannot be overridden by a
   media query, so the roster could not be made to stack at all; and a single
   shared floor of 640px was smaller than the fixed tracks these declare, so
   the tracks were squeezed past each other and the column headings printed on
   top of one another — on Projects and My work at every width below about
   1000px, phone or laptop.

   Each floor is the fixed tracks plus a first column worth having. */
.projectsgrid .th, .projectsgrid .tr {
  grid-template-columns:1fr 190px 160px 120px 130px 90px;   /* 690 fixed */
  min-width:940px;
}
.myworkgrid .th, .myworkgrid .tr {
  grid-template-columns:1fr 250px 120px 90px 110px 110px;   /* 680 fixed */
  min-width:930px;
}
.projtaskgrid .th, .projtaskgrid .tr {
  grid-template-columns:1fr 150px 110px 80px 100px;         /* 440 fixed */
  min-width:700px;
}
/* Team tasks reads across many projects, so its project column is wider than
   the one on a single project's own list. */
.teamtasksgrid .th, .teamtasksgrid .tr {
  grid-template-columns:1fr 220px 120px 80px 100px;         /* 520 fixed */
  min-width:780px;
}

/* ------------------------------------------------------------- team roster
   Six columns: who, how they sign in, role, contracted capacity, this week's
   commitment, open tasks.

   The roster is the one table that stacks rather than scrolling — see the media
   query further down — so the scroll it inherits from .tbl above only applies
   until the breakpoint. It is also where the inline-template fault was found:
   with the columns set in team.js the narrow layout could not collapse them at
   all, so the six tracks stayed at every width, the headings printed on top of
   one another and the rows ran off the card. */
.teamgrid .th, .teamgrid .tr {
  grid-template-columns:1fr 240px 150px 120px 120px 90px;   /* 720 fixed */
  min-width:960px;
}
.teamgrid .tr > * { min-width:0 }
/* Wraps rather than spills. A row carrying a role mark is avatar, name, "(you)",
   a badge and the edit link in one 1fr column, and at a laptop width that is
   wider than the column: before this the badge sat on top of the email beside
   it. The badge itself must not shrink or break (see .badge), and a truncated
   name is worse than a taller row, so what gives is the line. Only rows with a
   mark are ever tall enough to notice, and only when the window is narrow. */
.teamgrid .tname { display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px }
.teamgrid .t-edit { margin-left:auto; font:var(--fw-regular) var(--fs-tiny) var(--font-sans) }
/* Gutters only where the wide layout had them, and at the sizes it had: a cell
   whose neighbour it does not crowd is given none. */
.teamgrid .tname, .teamgrid .c-email { padding-right:18px }
.teamgrid .c-week { padding-right:14px }

/* One person taken off the team, in the collapsed restore list below the
   roster. Same reasoning as the roster's columns: laid out here so the narrow
   layout can reach it. */
.removedrow { display:flex; align-items:baseline; gap:10px; padding:8px 0;
              border-top:1px solid var(--border-subtle);
              font:var(--fw-regular) var(--fs-note) var(--font-sans) }
.removedrow .rm-name { color:var(--mh-black); min-width:170px }
.removedrow .rm-mail { flex:1; min-width:0 }
.removedrow .rm-when { font:var(--fw-regular) var(--fs-mono-sm) var(--font-mono) }
.removedrow .rm-back { font:var(--fw-regular) var(--fs-tiny) var(--font-sans) }

/* status dot + label */
.status { display:flex; align-items:center; gap:7px; font-size:var(--fs-label-lg); color:var(--text-muted) }
.dot { width:7px; height:7px; border-radius:var(--radius-pill); flex:none }
.dot.lg { width:8px; height:8px }
.sq { width:8px; height:8px; flex:none }

/* --------------------------------------------------------------- RAG pills */
/* One size. The .sm variant is gone: a status is the same fact in a table row
   as it is beside a title, and the two sizes only ever recorded which
   wireframe the screen came from. */
.pill {
  display:inline-flex; align-items:center; padding:4px 9px;
  font:var(--fw-bold) var(--fs-label) var(--font-sans); letter-spacing:var(--ls-pill);
  text-transform:uppercase;
}
/* Not a status. Since the project record stopped saying its status with a
   badge, this marks something a person or a record *is* — "heads the team",
   "manages the team", the specialisms a colleague covers. The rounded shape is
   what keeps it from being read as another status pill. */
.badge {
  display:inline-flex; align-items:center; gap:6px; padding:3px 9px;
  border-radius:var(--radius-pill); font:var(--fw-bold) var(--fs-tiny) var(--font-sans);
  letter-spacing:var(--ls-pill); text-transform:uppercase; line-height:1.4;
  /* Never wrap, never shrink. As a flex child in a squeezed cell the label was
     breaking inside its own pill — "HEADS THE / TEAM" — which grew the Team
     row from 55px to 77px at 375px. That is the same failure the email column
     was clipped to avoid (see .trunc below): a wrapping cell grows its row and
     drags the whole table with it. The width it refuses to give up goes into
     the table's own sideways scroll, which that card already has. */
  white-space:nowrap; flex:0 0 auto;
}

/* The "Heads the team" / "Manages the team" marks on the Team rows carry no
   rules of their own; `.rolemark` is a seam, not a style.
   From 28 August 2026 to 29 August this carried `display:none`, hiding them
   from everybody. Who sees them is now decided in team.js — your own row, plus
   every row if you may set them — and the reasoning for that lives beside the
   markup. Do not reintroduce a display rule here: it would silently override
   that logic, and the failure would look like the marks being broken. */

/* -------------------------------------------------------------------- board */
.board { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; align-items:start }
.boardcol {
  background:var(--mh-grey-200); padding:12px; display:flex; flex-direction:column;
  gap:10px; min-height:560px; border-radius:var(--radius-lg);
}
.boardcol > .head { display:flex; align-items:center; gap:8px; padding:0 2px 6px }
.boardcol > .head .t { font:var(--fw-bold) var(--fs-label-lg) var(--font-sans); color:var(--mh-black) }
.boardcol > .head .n { font-family:var(--font-mono); font-size:var(--fs-mono-sm); color:var(--text-subtle) }
.boardcol > .head .spacer { flex:1 }
.boardcol > .head .d { font-family:var(--font-mono); font-size:var(--fs-mono-sm); color:var(--text-muted) }
.taskcard {
  background:var(--surface-card); border:1px solid var(--border-default); border-radius:var(--radius-lg);
  padding:12px 14px; display:flex; flex-direction:column; gap:9px;
  text-align:left; cursor:pointer; width:100%;
  transition:box-shadow var(--dur-fast) var(--ease-standard);
}
.taskcard:hover { box-shadow:var(--shadow-md) }
.taskcard .t { font:var(--fw-semibold) var(--fs-copy)/1.35 var(--font-sans); color:var(--mh-black); text-wrap:pretty }
.taskcard .p { font:var(--fw-regular) var(--fs-tiny)/1.3 var(--font-sans); color:var(--text-link) }
/* Requesting team. Set as a label rather than a second link so it does not
   compete with the project name directly beneath it. align-self keeps the hit
   area to the text, since the card is a flex column and a button would
   otherwise stretch the full width. */
.taskcard .g { align-self:flex-start; background:none; border:0; padding:0; cursor:pointer;
               font:var(--fw-bold) var(--fs-label-sm) var(--font-sans); letter-spacing:var(--ls-overline);
               text-transform:uppercase; color:var(--text-subtle) }
.taskcard .g:hover { color:var(--mh-black) }
.taskcard .foot { display:flex; align-items:center; gap:8px }
.taskcard .foot .spacer { flex:1 }
.boardcol.dragover { outline:2px dashed var(--mh-blue); outline-offset:-2px }
.taskcard.dragging { opacity:.4 }

/* ------------------------------------------------------------------ meters */
.meter { height:8px; background:var(--track); position:relative }
.meter > i { display:block; height:100%; }
.meter .cap { position:absolute; top:-2px; bottom:-2px; left:100%; width:1px; background:var(--mh-grey-500) }
.meter.thin { height:5px }
.meter.hair { height:4px }
.meter.dark { background:var(--mh-grey-800); height:6px }

.legend { display:flex; gap:16px; font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-muted); flex-wrap:wrap }
.legend > span { display:flex; align-items:center; gap:6px }
.legend i { width:10px; height:10px; display:block }

/* ----------------------------------------------------------- capacity grid */
/* The window went from 4 weeks to 8 on 28 August, and eight columns do not fit
   every screen the four did. Between the stacking breakpoint and about 1200px
   the cells fell to 84px and the committed/capacity figure wrapped under its
   own percentage — measured, not guessed.

   So the columns have a floor and the card scrolls sideways when they do not
   fit. The floor is on the track rather than the cell because a `1fr` track
   will happily shrink below its content; `min-width` on the rows is what makes
   the scroll work, since each row is its own grid and they must all be the same
   width or the columns stop lining up with the header.

   Below 900px this is all moot — the grid stops being a grid and each row
   becomes a stacked block. See the media query further down. */
.capgrid { --cap-weeks:4; --cap-col:130px; overflow-x:auto }
.capgrid .th, .capgrid .tr, .capgrid .captotal {
  display:grid; grid-template-columns:240px repeat(var(--cap-weeks),minmax(var(--cap-col),1fr));
  min-width:calc(240px + var(--cap-weeks) * var(--cap-col));
}
.capgrid .tr { padding:16px 18px; border-bottom:1px solid var(--border-subtle); align-items:center }
.capgrid .who .n { font:var(--fw-semibold) var(--fs-copy) var(--font-sans); color:var(--mh-black) }
.capgrid .who .r { font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-subtle) }
.capcell { padding-right:18px }
.capcell .top { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:6px }
.capcell .top .v { font:var(--fw-bold) var(--fs-copy) var(--font-mono) }
.capcell .top .p { font:var(--fw-regular) var(--fs-label) var(--font-sans); color:var(--text-subtle) }
.captotal { align-items:center; padding:16px 18px;
            background:var(--mh-black); color:var(--mh-white) }
.captotal .l { font:var(--fw-bold) var(--fs-copy) var(--font-sans) }
.captotal .v { padding-right:18px; font:var(--fw-bold) var(--fs-head) var(--font-mono) }
/* The name of the column, carried on the cell itself, for the narrow layout
   where the column header is gone. Hidden at every width the header survives.

   Two screens share one declaration rather than each keeping its own, because
   they are the same idea and should not drift apart: `.capweek` is a week on
   the capacity grid, `.tkey` a field on the roster. Change this and both move. */
.capweek, .tkey { display:none; font:var(--fw-bold) var(--fs-label-sm) var(--font-sans);
           letter-spacing:var(--ls-overline);
           text-transform:uppercase; color:var(--text-subtle); margin-bottom:4px }

/* ---------------------------------------------------------------- absence
   A week reduced by a known absence, and a week lost to one entirely.

   The amber pair, not grey and not danger. Grey is taken: ragFor() returns it
   for a falsy capacity, which is what "no weekly capacity set" renders as, and
   an absent week must not look like a person nobody has configured. Danger is
   the alarm for over-committed, and being on holiday is not a fault. The
   warning pair is the firm's existing "worth noticing, not wrong" colour and
   needs no new token.

   Measured: --mh-warning-ink on --mh-warning-soft is 4.98:1, and
   --mh-danger-ink on the same fill is 7.67:1. Both pass AA at these sizes. */
.capcell.away .top .v { color:var(--mh-warning-ink) }
.awaynote { margin-top:5px; font:var(--fw-bold) var(--fs-label) var(--font-sans);
            letter-spacing:var(--ls-overline); text-transform:uppercase; color:var(--mh-warning-ink) }
.awayblock { display:flex; flex-direction:column; gap:3px; padding:9px 10px;
             border-radius:var(--radius-md);
             background:var(--mh-warning-soft) }
.awayblock .v { font:var(--fw-bold) var(--fs-note) var(--font-sans); color:var(--mh-warning-ink) }
/* Work planned into a week nobody is there for is a real problem, unlike the
   absence itself, so this half of the block carries the alarm colour. */
.awayblock .p { font:var(--fw-semibold) var(--fs-mono-sm) var(--font-mono); color:var(--mh-danger-ink) }
/* On the black totals row, where neither ink has the contrast to be read. */
.capaway { display:block; margin-top:3px; font:var(--fw-semibold) var(--fs-label) var(--font-sans);
           color:var(--mh-grey-400) }

.absencelist { display:flex; flex-direction:column }
.absencerow { display:flex; align-items:center; gap:12px; padding:9px 0;
              border-top:1px solid var(--border-subtle) }
.absencerow:first-child { border-top:0 }
.absencerow .n { font:var(--fw-semibold) var(--fs-copy) var(--font-sans); color:var(--mh-black) }
.absencerow .p { margin-top:2px; font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-subtle) }
.absencerow .when { flex:none; font-size:var(--fs-label-lg); color:var(--text-muted) }
/* Recorded, but too far out to be doing anything yet. Dimmed rather than
   hidden — it is still a fact somebody entered and may want to remove. */
.absencerow.future .n, .absencerow.future .when { color:var(--text-subtle) }

/* ------------------------------------------------------------------ digest */
.digest-split { flex:1; display:flex; background:var(--mh-white); min-height:0 }
.digest-main { flex:1; padding:34px 40px; border-right:1px solid var(--border-subtle); min-width:0 }
/* A front page, at the front-page size — the same 42px the internal-client window
   opens with. It was 44px, which was nothing but the number the wireframe
   happened to carry. */
.digest-main h1 {
  margin:8px 0 4px; font:var(--fw-extrabold) var(--fs-title-window)/1 var(--font-display);
  letter-spacing:-.03em; color:var(--mh-black);
}
.digest-lede { font:var(--fw-regular) var(--fs-body)/1.6 var(--font-sans); color:var(--text-muted); max-width:520px; margin-bottom:28px }
.digest-item { border-top:1px solid var(--border-default); padding:18px 0 20px; display:flex; align-items:baseline; gap:14px }
.digest-item .tag {
  font:var(--fw-bold) var(--fs-label) var(--font-mono); min-width:104px; flex:none;
  letter-spacing:var(--ls-overline); text-transform:uppercase;
}
.digest-item .head { font:var(--fw-bold) var(--fs-head-lg)/1.3 var(--font-sans); color:var(--mh-black); text-wrap:pretty }
.digest-item .body { font:var(--fw-regular) var(--fs-copy)/1.55 var(--font-sans); color:var(--text-muted); margin-top:5px; max-width:560px; text-wrap:pretty }
.digest-item .meta { font:var(--fw-regular) var(--fs-mono-sm) var(--font-mono); color:var(--text-subtle); margin-top:8px }
.digest-rail { width:330px; flex:none; padding:34px 26px; background:var(--surface-page); display:flex; flex-direction:column; gap:26px }
.railblock { border-top:1px solid var(--border-default); padding-top:20px }
.reqcard { background:var(--mh-white); border:1px solid var(--border-default); padding:12px 14px; margin-bottom:10px;
           border-radius:var(--radius-lg) }
.reqcard .n { font:var(--fw-semibold) var(--fs-copy)/1.35 var(--font-sans); color:var(--mh-black) }
.reqcard .m { font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-subtle); margin-top:4px }
.reqcard .actions { margin-top:10px; display:flex; gap:7px }

/* --------------------------------------------------------------- side panel */
.scrim { position:fixed; inset:0; background:rgba(0,0,0,.28); z-index:40; border:0; padding:0; cursor:default }
.panel {
  position:fixed; top:0; right:0; bottom:0; width:452px; z-index:41;
  background:var(--mh-white); border-left:1px solid var(--border-default);
  box-shadow:-8px 0 24px rgba(0,0,0,.12); display:flex; flex-direction:column;
}
.panel-head { padding:20px 26px 16px; border-bottom:1px solid var(--border-subtle);
              display:flex; align-items:flex-start; gap:14px; flex:none }
.panel-head .t { font:var(--fw-bold) var(--fs-head-lg)/1.3 var(--font-sans); color:var(--mh-black); text-wrap:pretty }
.panel-body { flex:1; overflow-y:auto; padding:22px 26px; display:flex; flex-direction:column; gap:18px }
/* A panel footer's action row may wrap when it leads with more than the usual
   two buttons, and a button never gives up its label to fit. The spacer, which
   pushes Delete to the right edge, wraps with it. */
.panel-actions { flex-wrap:wrap; row-gap:10px }
.panel-actions .btn { flex:none }
.panel-actions .spacer { flex:1 }
.panel-foot { padding:16px 26px; border-top:1px solid var(--border-subtle);
              display:flex; flex-direction:column; gap:14px; flex:none }
.iconbtn { width:28px; height:28px; flex:none; border:1px solid var(--border-default);
           background:var(--mh-white); border-radius:var(--radius-md); display:flex; align-items:center; justify-content:center;
           font:var(--fw-regular) var(--fs-head) var(--font-sans); color:var(--text-muted); cursor:pointer }
.iconbtn:hover { background:var(--mh-grey-100) }
.panelmeta { display:grid; grid-template-columns:1fr 1fr; gap:8px 16px }
.panelmeta div { font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-subtle) }
.panelmeta span { font-family:var(--font-mono); color:var(--text-muted) }
/* ------------------------------------------------------------------ burn-up */
/* Fixed drawing surface, scaled by CSS. Below its minimum the card scrolls
   rather than shrinking the labels into illegibility — same treatment as the
   wide tables. */
.burnup-wrap { overflow-x:auto }
.burnup { display:block; width:100%; min-width:560px; height:auto }
/* --text-subtle (#878787) measured 3.59:1 on white, which fails WCAG AA for
   small text. --text-muted (#54575c) is 7.25:1. */
.burnup-axis {
  font-family:var(--font-mono); font-size:var(--fs-mono-sm); fill:var(--text-muted);
}
.burnup-value {
  font-family:var(--font-mono); font-size:var(--fs-mono-sm); font-weight:var(--fw-bold);
}
/* The progress figure sits inside the remaining band, so it needs to hold up
   against a grey fill rather than white. */
.burnup-pct {
  font-family:var(--font-display); font-size:var(--fs-title-page); font-weight:var(--fw-bold);
  fill:var(--text-muted); letter-spacing:var(--ls-tight);
}
.burnup-pct-note {
  font-family:var(--font-sans); font-size:var(--fs-label); fill:var(--text-muted);
}

/* A task line inside the person panel — dense, clickable, opens the editor. */
.personrow {
  display:flex; align-items:center; gap:12px; width:100%; text-align:left;
  padding:8px 0; background:none; border:0;
  border-bottom:1px solid var(--border-subtle); cursor:pointer;
}
.personrow:hover { background:var(--mh-grey-100) }
.personrow .t { font:var(--fw-semibold) var(--fs-note)/1.35 var(--font-sans); color:var(--mh-black) }
.personrow .p { font:var(--fw-regular) var(--fs-mono-sm)/1.3 var(--font-sans); color:var(--text-link) }

/* The person cell on a capacity row, and the avatars, become drill-downs. */
.persondrill {
  background:none; border:0; padding:0; text-align:left; cursor:pointer; width:100%;
}
button.avatar { border:0; cursor:pointer; font:inherit }
button.avatar.ring { border:1px solid var(--border-default) }
button.avatar:hover { outline:2px solid var(--mh-blue); outline-offset:1px }

.dangerlink { background:none; border:none; cursor:pointer;
              font:var(--fw-semibold) var(--fs-label-lg) var(--font-sans); color:var(--mh-danger); padding:0 }

/* -------------------------------------------------------------------- forms */
.field { display:flex; flex-direction:column; gap:6px; font-family:var(--font-sans) }
.field > label { font:var(--fw-semibold) var(--fs-copy) var(--font-sans); color:var(--text-body) }
.field .hint { font-size:var(--fs-label-lg); color:var(--text-subtle) }
.field .hint.err { color:var(--mh-danger) }
.field input, .field select, .field textarea {
  font-family:var(--font-sans); font-size:var(--fs-head); color:var(--text-body);
  background:var(--mh-white); border:1px solid var(--border-default);
  border-radius:var(--radius-md); outline:none;
  transition:border-color var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard);
}
.field input, .field select { height:42px; padding:0 14px; width:100% }
.field select { padding-right:38px; appearance:none; cursor:pointer }
.field textarea { width:100% }
.field input[type="date"] { max-width:100% }
.field textarea { padding:10px 14px; line-height:1.5; resize:vertical }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color:var(--mh-blue); box-shadow:0 0 0 3px rgba(0,160,222,.18);
}
.field.invalid input, .field.invalid select, .field.invalid textarea { border-color:var(--mh-danger) }
.field.invalid input:focus, .field.invalid textarea:focus { box-shadow:0 0 0 3px rgba(192,57,43,.15) }
.selwrap { position:relative }
.selwrap .caret { position:absolute; right:14px; top:50%; transform:translateY(-50%);
                  pointer-events:none; font-size:.7rem; color:var(--text-muted) }
.fieldlabel { font:var(--fw-semibold) var(--fs-note) var(--font-sans); color:var(--mh-black); margin-bottom:8px }
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:18px }
/* Two page layouts that were carrying their templates inline, where no media
   query could reach them — the same fault as the roster's columns. Both stack
   below 900px; see the media query further down. */
.project-split { display:grid; grid-template-columns:1fr 320px; gap:18px; align-items:start }
.mywork-split  { display:grid; grid-template-columns:1.4fr 1fr; gap:18px; align-items:start }

/* The project header: title and description on the left, a rail of facts on the
   right, wrapping to one column when there is no room for two. The floor and
   the rail's width were both inline, so on a phone neither gave way — they
   overflowed a 285px card by 35px and 15px instead. */
.projhero { padding:26px 28px }   /* == .card-pad.wide; kept for the class name */
.projhero-row { display:flex; align-items:flex-start; gap:20px; flex-wrap:wrap }
.projhero-main { flex:1; min-width:320px }
.projhero-meta {
  width:300px; display:grid; grid-template-columns:1fr 1fr; gap:16px 14px;
  padding-left:24px; border-left:1px solid var(--border-subtle);
}

.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  font-family:var(--font-sans); font-weight:var(--fw-semibold); letter-spacing:.01em;
  line-height:1; height:42px; padding:0 20px; font-size:var(--fs-head);
  border-radius:var(--radius-md); border:2px solid transparent; cursor:pointer;
  transition:background var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard),
             border-color var(--dur-base) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.btn:active:not(:disabled) { transform:translateY(1px) }
.btn:disabled { opacity:.45; cursor:not-allowed }
.btn.primary   { background:var(--mh-black); color:var(--mh-white); border-color:var(--mh-black) }
.btn.secondary { background:transparent; color:var(--mh-black); border-color:var(--mh-black) }
/* Black label, not white. White on this blue measures 2.96:1 and fails AA for
   text; black is 7.08:1. The fill itself is 7.08:1 against the black header it
   now sits on, which is why the deeper blue was not the answer — that one is
   2.66:1 against the same header and the button would sink into it. */
.btn.accent    { background:var(--mh-blue); color:var(--mh-black); border-color:var(--mh-blue) }
.btn.ghost     { background:transparent; color:var(--mh-black); border-color:transparent }
.btn.ghost:hover:not(:disabled) { background:var(--mh-grey-200) }
/* Two sizes, not four. 42px is the call to action in a page body — the
   internal-client window's hero, the sign-in card, the request form. 34px is an
   action in a row or a card, which is every action on the team's own screens:
   the site header, a request card, the digest rail. What used to be here
   instead was a 27px .chip in the header, a 34px button on two screens and a
   26px .minibtn in the rail, all of them the same kind of thing. */
.btn.sm { height:34px; padding:0 14px; font-size:var(--fs-copy) }

/* choice chips (status / effort in the task panel) */
.chips { display:flex; gap:7px; flex-wrap:wrap }
.choice {
  display:flex; align-items:center; gap:7px; padding:7px 12px; cursor:pointer;
  border:1px solid var(--border-default); border-radius:var(--radius-md); background:var(--mh-white); color:var(--text-muted);
  font:var(--fw-semibold) var(--fs-label-lg) var(--font-sans);
  transition:background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.choice:hover { background:var(--mh-grey-100) }
.choice.on { background:var(--mh-black); border-color:var(--mh-black); color:var(--mh-white) }
/* 48px, not 52: seven effort chips (0.25 to 10, since 16 September 2026) plus
   six gaps must fit the task panel's 399px row on one line, and 52 put the 10
   chip alone on a second. The widest label, 0.25, needs about 34px. */
.choice.num { min-width:48px; justify-content:center; padding:9px 0; font:var(--fw-semibold) var(--fs-copy) var(--font-mono) }

.check { display:inline-flex; align-items:center; gap:10px; font-size:var(--fs-head);
         color:var(--text-body); cursor:pointer; font-family:var(--font-sans) }
.check input { position:absolute; opacity:0; width:0; height:0 }
.check .box { display:inline-flex; align-items:center; justify-content:center; width:20px; height:20px;
              border-radius:var(--radius-md); border:2px solid var(--border-default);
              background:var(--mh-white); color:var(--mh-white); flex-shrink:0;
              transition:background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard) }
.check input:checked + .box { background:var(--mh-black); border-color:var(--mh-black) }
.check .box svg { display:none }
.check input:checked + .box svg { display:block }
.check.radio .box { border-radius:var(--radius-pill) }
.check.radio .box i { width:10px; height:10px; border-radius:var(--radius-pill); background:var(--mh-black); display:none }
.check.radio input:checked + .box { background:var(--mh-white) }
.check.radio input:checked + .box i { display:block }
.check input:focus-visible + .box { box-shadow:0 0 0 3px rgba(0,160,222,.35) }

/* #84. The switch (Toggle in bits.js), after the Kumo switch on Cloudflare's
   dashboard, read off the live page on 24 September 2026: a 36 by 18 track, a
   white thumb the track's full height with a hairline shadow, a slide with no
   motion under reduced motion. Kept: all of that. Not kept, each for a rule
   this board already has. Its colours: Cloudflare's off track is #e5e5e5,
   1.26:1 on white, under the 3:1 a control's state needs; off here is
   --mh-grey-500 (3.59:1, the fill its token is kept for) and on is
   --mh-blue-deep (7.90:1), Neil's choice. Its corners, a 10px squircle where
   the browser has corner-shape and 5px where it does not: a control here takes
   --radius-md, 4px (README, "A corner has two sizes"), a pixel off Kumo's own
   fallback. Its 150ms, for --dur-fast (120ms).
   Its focus ring, for the board's own one below, the outline set off a filled
   control.

   While a change is saving the switch is aria-busy, not disabled: Chrome 153
   moves focus to the page body when a focused control is disabled and does not
   give it back, so a keyboard user would lose their place with every press. */
.switch { position:relative; display:inline-block; width:36px; height:18px; flex-shrink:0; cursor:pointer }
.switch input { position:absolute; opacity:0; width:0; height:0; margin:0 }
.switch .track, .switch .thumb { border-radius:var(--radius-md) }
.switch .track { position:absolute; inset:0; background:var(--mh-grey-500);
                 transition:background var(--dur-fast) var(--ease-standard) }
.switch .thumb { position:absolute; top:0; left:0; width:18px; height:18px; background:var(--mh-white);
                 box-shadow:0 0 1px .5px rgba(0,0,0,.12), 0 1px 2px rgba(0,0,0,.08);
                 transition:left var(--dur-fast) var(--ease-standard) }
.switch input:checked ~ .track { background:var(--mh-blue-deep) }
.switch input:checked ~ .thumb { left:18px }
.switch input:focus-visible ~ .track { outline:2px solid var(--focus-ring); outline-offset:2px }
.switch:has(input[aria-busy="true"]) { cursor:progress }
.switch input[aria-busy="true"] ~ .track, .switch input[aria-busy="true"] ~ .thumb { opacity:.6 }
@media (prefers-reduced-motion: reduce) {
  .switch .track, .switch .thumb { transition:none }
}

/* ------------------------------------------------------------------ focus
   One focus treatment, for everything a keyboard can reach.

   Before this only three things had one — a form field, a checkbox and the
   suite bar's links — and everything else fell back to whatever the browser
   draws by default. On the black bar, the navigation column and the black buttons
   that is close to invisible, which is the case that matters: somebody
   navigating by keyboard cannot see where they are.

   :focus-visible, not :focus, so a mouse click does not leave a ring behind.
   The offset is what makes it legible on a filled control — an outline flush
   against a black button reads as part of the button. */
.btn:focus-visible,
.filterchip:focus-visible,
.choice:focus-visible,
.iconbtn:focus-visible,
.linkish:focus-visible,
.navtoggle:focus-visible,
.dangerlink:focus-visible,
.addrow:focus-visible,
.taskcard:focus-visible,
.personrow:focus-visible,
.persondrill:focus-visible,
.tbl .tr:focus-visible,
summary:focus-visible,
a:focus-visible {
  outline:2px solid var(--focus-ring); outline-offset:2px;
}
/* Where the ring's own blue is the ground, or too near it: the navigation
   column, and the accent button on the black hero. */
nav.sidenav a:focus-visible,
nav.sidenav button:focus-visible,
.comms-hero a:focus-visible,
.comms-hero .btn:focus-visible,
.suitebar a:focus-visible {
  outline-color:var(--mh-white);
}

.callout { display:flex; gap:14px; border-radius:var(--radius-lg);
           padding:var(--space-4) var(--space-5); font-family:var(--font-sans); color:var(--text-body) }
.callout .title { font-weight:var(--fw-bold); font-size:var(--fs-head); margin-bottom:4px; color:var(--text-strong) }
.callout .msg { font-size:var(--fs-body); line-height:1.55 }
.callout.info    { background:var(--mh-grey-100); border-left:3px solid var(--mh-blue) }
.callout.accent  { background:var(--mh-blue-pale); border-left:3px solid var(--mh-blue-deep) }
.callout.success { background:#f2f4e6; border-left:3px solid var(--mh-olive) }
.callout.warning { background:#fbf3e2; border-left:3px solid var(--mh-warning) }
.callout.danger  { background:#f9e8e6; border-left:3px solid var(--mh-danger) }

.inlinewarn { background:var(--mh-warning-soft); padding:12px 14px; border-radius:var(--radius-lg);
              font:var(--fw-regular) 12.5px/1.5 var(--font-sans); color:var(--mh-warning-ink) }
.inlinewarn .linkish { color:inherit; text-decoration:underline; font-size:inherit }

/* The split preview's rows (card "Larger tasks", 16 September 2026): one per
   part, a RAG dot in the same rule as the capacity warning, the name and the
   week line under it. */
.splitpart { display:grid; grid-template-columns:7px 1fr; gap:10px; align-items:start;
             padding:9px 0; border-top:1px solid var(--border-subtle) }
.splitpart .dot { width:7px; height:7px; border-radius:50%; margin-top:6px }
.splitpart .n { font:var(--fw-semibold) var(--fs-note)/1.5 var(--font-sans); color:var(--text-body) }
.splitpart .w { font:var(--fw-regular) var(--fs-note)/1.5 var(--font-sans); color:var(--text-muted) }
.splitpart .flagline { font:var(--fw-regular) var(--fs-tiny)/1.5 var(--font-sans); color:var(--mh-warning-ink) }
.stepper { display:inline-flex; align-items:center; gap:10px }
.stepper .mono { min-width:1.5em; text-align:center }

/* --------------------------------------------------------- communication site */
.comms { max-width:none }

/* One gutter for the whole window, declared once and read by everything that
   sits against its edge — the header, the body, and the table rows, which set
   their own padding inline because their backgrounds run full width.
   It exists because the three views were drawn as three separate wireframes and
   two of them hard-coded their padding inline, where the media queries below
   could not reach it. The cards view narrowed on a phone and the other two did
   not, so switching tabs moved the whole page sideways. A custom property is
   reachable from an inline style; a padding rule is not. */
.comms { --gutter:48px }

/* Dark, for every view. The timeline's treatment was chosen as the one to keep:
   it marks the window as somewhere you are shown things rather than somewhere
   you work, and it now says that on whichever tab you open.
   The light-mode text colours do not survive the move — --text-muted measures
   2.9:1 on black — so the lede takes grey-400 at 12.3:1. The overline and
   tinynote already use grey-500, which is 5.9:1 here and needs no override.
   No bottom border: on black a grey-400 line is louder than the edge itself,
   and the step down to the body colour draws that edge anyway. */
.comms-hero { padding:40px var(--gutter) 34px;
              display:flex; align-items:flex-end; gap:40px; flex-wrap:wrap;
              background:var(--mh-black); color:var(--mh-white);

  /* This band re-grounds its own ink, the same device nav.sidenav uses, and for
     the opposite reason. --text-subtle was darkened to #666666 so it clears
     4.5:1 on the app's light surfaces — which made it WORSE here, on black:
     5.50:1 before, 3.66:1 after. A token tuned for one ground cannot serve the
     inverse of it. --mh-grey-400 is what the hero's own lede already uses, at
     11.0:1, so the overline now matches the sentence beneath it. */
  --text-subtle: var(--mh-grey-400);
}
.comms-hero h1 { margin:0 0 10px; font:var(--fw-extrabold) var(--fs-title-window)/1 var(--font-display);
                 letter-spacing:-.03em; color:var(--mh-white) }
.comms-hero .lede { font:var(--fw-regular) var(--fs-body)/1.6 var(--font-sans);
                    color:var(--mh-grey-400); max-width:620px }
/* The request form has no hero: it is a form reached from a page, not a front
   page, so it opens the way the project record does — a way back, then the
   title. This is that band. It reads --gutter like everything else on this
   window, so it stays on the same line as the hero above it and the form
   below it at every width. */
.crumbbar {
  display:flex; align-items:center; gap:14px;
  padding:22px var(--gutter); border-bottom:1px solid var(--border-default);
}
.comms-body { padding:26px var(--gutter) 44px; background:var(--surface-page); min-height:640px }
/* The list gives its side padding to its rows, whose backgrounds and hover
   states run the full width of the page. Only the sides: the vertical rhythm
   stays shared, which is what it was not while this was written inline. */
.comms-body.bleed { padding-left:0; padding-right:0 }
/* The timeline and the list draw their own rows rather than cards, and both were
   built on white. Only the background differs — the padding, and its response to
   the breakpoints, stays shared. */
.comms-body.plain { background:var(--mh-white) }
.filterrow { display:flex; gap:9px; margin-bottom:24px; flex-wrap:wrap }
/* SEMIBOLD, not regular. These chips are navigation — the internal-client window
   changes view through them and three internal screens filter through them —
   and they were 12px at weight 400, the smallest size in the app set at its
   lightest weight. Contrast was never the problem: --text-muted on white is
   7.25:1, comfortably over the 4.5:1 floor. Weight was. Beside a selected chip
   at 600 on a black fill, a 400 label read as disabled rather than available.

   600 and not 500, which was measured rather than assumed: Aptos ships 300,
   400, 600, 700, 800, 900 and has no 500, so `font-weight:500` renders
   identically to 400 — the same string of text measured 147.59px at both, and
   151.70px at 600. Asking for 500 would have changed nothing while looking
   like a change.

   It costs 13px across eleven chips, which wraps nothing: the widest row goes
   827px to 840px at 1440, and on a 375px screen that row already scrolls
   horizontally (917px of chips in a 343px viewport) so 13px is invisible. */
.filterchip { padding:7px 13px; background:var(--mh-white); border:1px solid var(--border-default);
              border-radius:var(--radius-md); color:var(--text-muted); font:var(--fw-semibold) var(--fs-label-lg) var(--font-sans); cursor:pointer;
              display:inline-flex; align-items:center; gap:7px }
/* No font-weight here any more: every chip is semibold, so selection is carried
   by the fill, which inverts to 21:1 and cannot be mistaken for anything else. */
.filterchip.on { background:var(--mh-black); border-color:var(--mh-black); color:var(--mh-white) }
/* Optional count inside a chip. Chips without one are unaffected — inline-flex
   with a single child lays out as the padded box it always was. The selected
   state inverts to white-on-black, where the subtle grey would disappear, so it
   gets its own colour rather than inheriting.
   --text-muted, not --text-subtle: #878787 on white measures 3.59:1 and fails
   AA for text this size, which this file already records twice — at .burnup-axis
   and at the work-in-hand group heading, both of which made the same swap. This
   one was missed. #54575c is 7.25:1 and still reads as the quieter half of the
   chip, because the label beside it is now semibold and this is not. */
.filterchip .n { font:var(--fw-regular) var(--fs-mono-sm) var(--font-mono); color:var(--text-muted) }
.filterchip.on .n { color:var(--mh-grey-400) }
.clientgrid { display:grid; grid-template-columns:repeat(3,1fr); gap:18px }
/* Flat, like every other card in both surfaces. This was the only card at
   rest with a shadow under it, which read as the internal-client window being built
   to a different spec rather than as emphasis. The board's task card still
   lifts on hover — that is feedback on a thing you can drag, not elevation. */
.clientcard { background:var(--mh-white); border:1px solid var(--border-default); padding:18px 20px;
            display:flex; flex-direction:column; gap:12px; border-radius:var(--radius-lg) }
.clientcard .top { display:flex; align-items:center; justify-content:space-between; gap:10px }
/* The requesting team, in the form the timeline and the list already share.
   It was 10.5px at 0.12em in grey here and 12px at 0.14em in black on the
   other two tabs — the same fact, told three ways inside one window. */
.clientcard .grp { font:var(--fw-bold) var(--fs-label-lg) var(--font-sans);
                 letter-spacing:var(--ls-overline);
                 text-transform:uppercase; color:var(--mh-black) }
.clientcard .rag { width:9px; height:9px; flex:none }
.clientcard .n { font:var(--fw-bold) var(--fs-head-lg)/1.28 var(--font-sans); color:var(--mh-black); text-wrap:pretty }
.clientcard .b { font:var(--fw-regular) var(--fs-copy)/1.55 var(--font-sans); color:var(--text-muted); text-wrap:pretty }
.clientcard .rule { height:1px; background:var(--border-subtle); margin:2px 0 }
.clientcard .who { display:flex; align-items:center; gap:8px;
                 font:var(--fw-regular) 12px var(--font-sans); color:var(--text-muted) }

/* Two-state signal, in words. Replaces a bare coloured square whose only
   explanation was a hover title. */
.clientcard .signal {
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font:var(--fw-bold) var(--fs-label) var(--font-sans);
  letter-spacing:var(--ls-overline); text-transform:uppercase;
}

/* "We need something from you." The only part of the internal-client window a
   requestor can act on, so it is the loudest thing on the card. */
.awaiting {
  margin:10px 0 2px; padding:10px 12px; border-radius:var(--radius-lg);
  background:var(--mh-danger-soft); border-left:3px solid var(--mh-danger);
}
.awaiting-head {
  font:var(--fw-bold) var(--fs-label) var(--font-sans); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--mh-danger-ink); margin-bottom:4px;
}
.awaiting-body {
  font:var(--fw-regular) var(--fs-note)/1.5 var(--font-sans); color:var(--mh-danger-ink);
}
.awaiting-inline {
  font:var(--fw-semibold) var(--fs-label-lg) var(--font-sans); color:var(--mh-danger-ink);
  margin-bottom:3px;
}

/* Said once at the top, so it is not something to spot by scrolling a grid. */
.withyou {
  margin-bottom:22px; padding:14px 18px; border-radius:var(--radius-lg);
  background:var(--mh-danger-soft); border-left:3px solid var(--mh-danger);
}
.withyou-head {
  font:var(--fw-bold) var(--fs-body) var(--font-sans); color:var(--mh-danger-ink);
  margin-bottom:4px;
}
/* Sign in / sign out in the top bar: an offer, not a call to action, so it is
   quieter than a button but still clearly clickable. */
/* 404 page. Deliberately plain — it exists to give an honest status code more
   than to be looked at. */
.notfound {
  max-width: 620px;
  padding: 56px 48px;
  font: var(--fw-regular) var(--fs-head)/1.65 var(--font-sans);
  color: var(--text-muted);
}
.notfound h1 {
  font: var(--fw-black) 34px/1.1 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--mh-black);
  margin: 8px 0 16px;
}
.notfound ul { padding-left: 20px; margin-top: 18px; }
.notfound li { margin-bottom: 10px; }
.notfound a { color: var(--mh-black); }

/* The no-JavaScript fallback. A class rather than a style attribute so the
   Content-Security-Policy can forbid inline styles outright — stylesheets load
   with scripting disabled, so nothing is lost. */
.noscript {
  padding: 40px;
  font: 400 15px/1.6 system-ui, sans-serif;
  color: #1a1a1a;
}

/* Separates the two sign-in routes without implying they are equivalent: the
   preferred one is above the rule, the fallback below it. */
.orrule {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 18px;
  color: var(--text-muted);
  font: var(--fw-bold) var(--fs-label)/1 var(--font-sans);
  letter-spacing: var(--ls-overline);
  text-transform: uppercase;
}
.orrule::before,
.orrule::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

.barlink {
  background: none;
  border: 0;
  padding: 6px 10px;
  margin-right: 4px;
  font: var(--fw-semibold) var(--fs-label-lg)/1 var(--font-sans);
  letter-spacing: var(--ls-overline);
  text-transform: uppercase;
  color: var(--mh-white);
  opacity: 0.72;
  cursor: pointer;
  /* Buttons carry none of their own; the "Marketing view" link is an anchor and
     would otherwise arrive underlined, which is what :hover is for. */
  text-decoration: none;
}
.barlink:hover { opacity: 1; text-decoration: underline; }
.barlink:focus-visible { outline: 2px solid var(--mh-white); outline-offset: 2px; opacity: 1; }

.withyou-team {
  font-weight: var(--fw-semibold);
  /* Distinguishes the team from the project name without a second colour. */
  letter-spacing: 0.01em;
}
.withyou-body {
  font:var(--fw-regular) var(--fs-copy)/1.55 var(--font-sans); color:var(--mh-danger-ink);
}

/* Whether the page is current. Quiet, but present on every item — not knowing
   is what makes people email to ask. */
.updated {
  margin-top:8px; font:var(--fw-regular) 11px var(--font-sans);
  color:var(--text-subtle);
}
.tbl .updated { margin-top:0 }

/* timeline */
/* The timeline and the list both lay out fixed columns that stop fitting well
   before a phone. Left alone they widen the page, and a page that scrolls
   sideways takes the header, the filters and the "waiting on you" banner with
   it — so the reader loses the parts that were fitting perfectly well in order
   to see the part that was not.
   Scrolling happens inside the container instead. No media query: the width the
   content needs and the width the layout changes at are unrelated numbers, and
   above the min-width this rule does nothing at all. */
.comms-scroll { overflow-x:auto }
.comms-scroll > * { min-width:var(--scroll-min,640px) }

/* ------------------------------------------------- 1h list, internal-client window
   A restyle of the shared .tbl, scoped so the internal app keeps its own
   denser treatment. Three things change the feel: the grid gives its width to
   the Stage sentence rather than the name, the group headings stop being grey
   bands and become rules, and the "we need something from you" flag moves out
   of the Stage column and under the project name.

   The flag and the row tint take the danger palette, the same soft red fill and
   ink as the "waiting on you" banner above the list. An accent-blue treatment
   was tried first, on the argument that the banner is the alarm and the row only
   points at it; two colours for one fact read as an oversight instead. One fact,
   one colour. Ink on the fill measures 7.03:1, the project name 17:1.        */
.worklist { --wash-flag:var(--mh-danger-soft) }

.worklist .th {
  padding:0 var(--gutter) 12px; background:none;
  border-bottom:1px solid var(--border-default);
  /* Mono, to sit with the dates below it. --text-muted rather than the site's
     usual --text-subtle overline grey, which measures 3.59:1 on white; this is
     7.26:1 and still reads as grey. */
  font:var(--fw-bold) var(--fs-label) var(--font-mono); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--text-muted);
}

/* "BANKING / 2". The count is the only number in the row, so it does not need
   the word "projects" to say what it counts. */
.worklist .grouprow {
  display:flex; align-items:baseline; gap:8px;
  margin-top:28px; padding:0 var(--gutter) 9px;
  background:none; border-bottom:1px solid var(--border-default);
}
.worklist .grouprow .g {
  font:var(--fw-bold) var(--fs-label-lg) var(--font-sans); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--mh-black);
}
.worklist .grouprow .sep,
.worklist .grouprow .n {
  font:var(--fw-regular) var(--fs-mono) var(--font-mono); color:var(--text-subtle);
}

.worklist .tr {
  padding:17px var(--gutter); align-items:start; border-bottom:0;
  font:var(--fw-regular) var(--fs-copy) var(--font-sans); color:var(--text-body);
}
/* Hairlines between rows, none after the last one in a group: the next group
   heading already draws a rule, and two rules 34px apart read as a mistake. */
.worklist .tr + .tr { border-top:1px solid var(--border-subtle) }

.worklist .nm .n {
  font:var(--fw-semibold) var(--fs-head)/1.35 var(--font-sans); color:var(--mh-black);
}
/* Black, not link blue: these rows do not go anywhere. */
.worklist .tr.done .nm .n,
.worklist .tr.done .when { color:var(--text-muted); font-weight:var(--fw-regular) }

.worklist .needsyou {
  margin-top:5px; display:flex; align-items:center; gap:5px;
  font:var(--fw-bold) var(--fs-label) var(--font-sans); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--mh-danger-ink);
}
.worklist .needsyou .arrow { font-size:12px; line-height:1; letter-spacing:0 }
.worklist .tr.flagged { background:linear-gradient(90deg,var(--wash-flag),transparent 62%) }

.worklist .stage { line-height:1.45 }
.worklist .updated {
  margin-top:5px; font:var(--fw-regular) var(--fs-mono-sm) var(--font-mono); color:var(--text-muted);
}
.worklist .when { font:var(--fw-regular) var(--fs-mono) var(--font-mono); color:var(--text-body) }
.worklist .who { color:var(--text-body) }
.worklist .who.none { color:var(--text-muted) }
/* The pill's cap height sits a shade below the name's; this puts them level. */
.worklist .tr .pill { margin-top:2px }
/* UNUSED, with `.footnote` below: the internal-client window's only footnote went on
   1 September. Both kept for the next one. */
.worklist .footnote { margin-top:34px; border-top:1px solid var(--border-subtle) }

.tl-head { display:grid; grid-template-columns:300px 1fr; border-bottom:1px solid var(--border-default); padding-bottom:10px }
.tl-months { display:grid; }
/* The practice-group heading. Same content as .worklist .grouprow .g on the
   list tab — the requesting team's name — so it takes the same treatment: an
   overline, not a bold caption. Size, weight and colour already agreed; it was
   the tracking and the case that did not. Change one of these and change the
   other, or the two tabs disagree about what a team name looks like. */
.tl-group { padding:20px 0 8px; font:var(--fw-bold) var(--fs-label-lg) var(--font-sans);
            letter-spacing:var(--ls-overline); text-transform:uppercase;
            color:var(--mh-black) }
.tl-row { display:grid; grid-template-columns:300px 1fr; align-items:center; padding:9px 0;
          border-top:1px solid var(--border-subtle) }
.tl-row .lbl { padding-right:24px }
.tl-row .lbl .n { font:var(--fw-semibold) var(--fs-copy) var(--font-sans); color:var(--mh-black) }
.tl-row .lbl .s { font:var(--fw-regular) var(--fs-tiny) var(--font-sans); color:var(--text-subtle) }
.tl-track { position:relative; height:34px }
.tl-bar { position:absolute; top:7px; bottom:7px; display:flex; align-items:center; padding:0 10px;
          font:var(--fw-semibold) 11px var(--font-sans); color:var(--mh-white); white-space:nowrap;
          overflow:hidden; min-width:8px }

/* ------------------------------------------------------------- schedule
   The team's sideways chart (views/schedule.js). One CSS variable is the whole
   scale: every left and width on the chart is `--sch-day × days`, written
   inline by the view as a calc(), so the density of the chart changes here and
   nowhere else. 5px a day is about 150px a month -- a year in 1800px, which
   scrolls inside the card; the label column stays put with position:sticky.

   Type on the chart is the label scale. A bar's label is 11px on white, the
   same treatment as the client window's bars (.tl-bar), so the two timelines
   agree about what a bar says. */
.sch { --sch-day:5px; padding:0 }
.sch-scroll { overflow-x:auto; padding-bottom:6px }
.sch-body { display:inline-block; min-width:100% }
.sch-row { display:flex; align-items:stretch; border-top:1px solid var(--border-subtle) }
.sch-row:first-child { border-top:0 }
.sch-label {
  flex:none; width:280px; padding:9px 18px; box-sizing:border-box;
  position:sticky; left:0; z-index:2; background:var(--surface-card);
  border-right:1px solid var(--border-default);
}
.sch-label .n { display:block; font:var(--fw-semibold) var(--fs-copy)/1.35 var(--font-sans);
                color:var(--mh-black); text-decoration:none; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.sch-label .s { margin-top:2px; font:var(--fw-regular) var(--fs-tiny)/1.3 var(--font-sans); color:var(--text-subtle);
                overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* The week grid, one hairline every seven days, phased to the Mondays by the
   view (background-position-x). Faint on purpose: the months carry their own
   lines and the weeks are a texture to read a bar against. */
.sch-track {
  flex:none; position:relative; height:46px; box-sizing:border-box;
  background:repeating-linear-gradient(to right, var(--border-subtle) 0 1px, transparent 1px calc(var(--sch-day) * 7));
  /* Its own stacking context, so the z-indexes on the marks below order the
     marks against each other and nothing else. Without it a due-date dot or
     a diamond at z-index:2 met the sticky label column at z-index:2 and won on
     document order, floating over the project names when the chart was
     scrolled sideways (Idea Foundry, "Z-order on task ticks"). The ticks and
     bars at z-index:1 already passed underneath, which is why only some marks
     showed the fault. */
  isolation:isolate;
}
.sch-head { background:var(--surface-header); border-bottom:1px solid var(--border-header) }
.sch-head .sch-label { background:var(--surface-header); padding-top:22px }
.sch-head .sch-track { height:44px; background:none }
.sch-month {
  position:absolute; top:0; bottom:0; box-sizing:border-box; padding:6px 0 0 8px;
  border-left:1px solid var(--border-header);
  font:var(--fw-bold) var(--fs-label) var(--font-sans); letter-spacing:var(--ls-overline);
  text-transform:uppercase; color:var(--header-ink); white-space:nowrap; overflow:hidden;
}
.sch-month:first-child { border-left:0 }
/* A named line's name, hung from the header at the line's x. Today's is the
   accent; a milestone's is ink. */
.sch-flag {
  position:absolute; bottom:4px; transform:translateX(-50%); padding:1px 6px; white-space:nowrap;
  background:var(--mh-black); color:var(--mh-white);
  font:var(--fw-semibold) var(--fs-label-sm) var(--font-sans);
}
.sch-flag.today { background:var(--mh-blue); }
.sch-group .sch-label { padding:16px 18px 6px }
.sch-group .sch-label .g { font:var(--fw-bold) var(--fs-label-lg) var(--font-sans); letter-spacing:var(--ls-overline);
                           text-transform:uppercase; color:var(--mh-black) }
.sch-group .sch-track { height:auto; min-height:1px; background:none }
/* The lines every row carries. Month boundaries are hairlines; a firm-wide
   milestone is a dashed ink line; today is the accent, solid, on top. */
.sch-mline { position:absolute; top:0; bottom:0; width:0; border-left:1px solid var(--border-default) }
.sch-vline { position:absolute; top:0; bottom:0; width:0; border-left:1px dashed var(--mh-black); z-index:1 }
.sch-todayline { position:absolute; top:0; bottom:0; width:0; border-left:2px solid var(--mh-blue); z-index:1 }
/* The bar: a link to the project, a target date written inside it. */
.sch-bar {
  position:absolute; top:7px; height:18px; display:flex; align-items:center; padding:0 8px;
  box-sizing:border-box; min-width:8px; overflow:hidden; text-decoration:none; z-index:1;
}
.sch-bar.faint { opacity:.45 }
.sch-bar:hover { filter:brightness(1.12) }
.sch-barlabel { font:var(--fw-semibold) 11px var(--font-sans); color:var(--mh-white); white-space:nowrap }
/* Task marks, in the lane under the bar: a tick the width of a working week
   at the planned week, a dot at the due date. Both are buttons that open the
   task, so both get a cursor and a hover. */
.sch-tick { position:absolute; top:31px; height:6px; border:0; padding:0; cursor:pointer; z-index:1 }
.sch-tick:hover, .sch-dot:hover { outline:2px solid var(--mh-blue); outline-offset:1px }
.sch-dot {
  position:absolute; top:29px; width:8px; height:8px; border-radius:50%; transform:translateX(-50%);
  border:0; padding:0; cursor:pointer; background:var(--mh-black); z-index:2;
}
.sch-dot.late { background:var(--mh-danger) }
/* A project's own milestone: a diamond on the bar lane, ink, with a white
   edge so it reads on any bar colour. Not a control; the title says what it is. */
.sch-diamond {
  position:absolute; top:11px; width:10px; height:10px; transform:translateX(-50%) rotate(45deg);
  background:var(--mh-black); outline:2px solid var(--mh-white); z-index:2;
}
/* The requestor's date of use (056): the same shape in the firm's blue, so a
   reader can tell the fixed point they were given from a note the team made. */
.sch-diamond.use { background:var(--mh-blue) }
/* The three marks again, at legend size. */
.legend .sch-key-tick { width:14px; height:5px; background:var(--mh-blue) }
.legend .sch-key-dot { width:8px; height:8px; border-radius:50%; background:var(--mh-black) }
.legend .sch-key-diamond { width:8px; height:8px; transform:rotate(45deg); background:var(--mh-black) }
.legend .sch-key-diamond.use { background:var(--mh-blue) }

/* --------------------------------------------------------------- sign-in */
.signin { min-height:100vh; display:flex; flex-direction:column; background:var(--surface-page) }
.signin-body { flex:1; display:flex; align-items:center; justify-content:center; padding:40px 24px }
.signin-card { width:100%; max-width:440px; background:var(--mh-white);
               border:1px solid var(--border-default); padding:26px 28px; border-radius:var(--radius-lg) }
.signin-card h1 { margin:0 0 10px; font:var(--fw-extrabold) var(--fs-title-page)/1.05 var(--font-display);
                  letter-spacing:var(--ls-tight); color:var(--mh-black) }

/* --------------------------------------------------------------- utilities */
.row { display:flex; align-items:center; gap:12px }
/* For a flex child that has to stop being one — the pick-up promise on a
   internal-client card, which is a sentence where its siblings are an avatar and a
   name. */
.block { display:block; line-height:1.5 }
.row.wrap { flex-wrap:wrap }
.spacer { flex:1 }
.stack { display:flex; flex-direction:column }
.muted { color:var(--text-muted) }
.subtle { color:var(--text-subtle) }
.small { font-size:var(--fs-note) }
/* Clip rather than wrap. A wrapping cell grows its row and drags every
   neighbouring column out of line, which reads as a layout fault; a clipped
   one does not. Only for content whose tail is predictable or recoverable
   from a title — never where the end of the string carries the meaning. */
.trunc { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* Wrap rather than clip, for a value where losing the tail loses the meaning.
   The roster's email column used .trunc, with the whole address on the cell's
   `title` — which is real mitigation for a mouse and none at all for a keyboard
   or a touch screen. It also clipped under the text-spacing overrides SC 1.4.12
   requires a page to survive: measured on 1 September, nine addresses lost their
   tails at line-height 1.5 / letter-spacing 0.12em, having fitted exactly at
   normal spacing. A grown row is a smaller cost than an unreadable address.
   `anywhere` rather than `break-word` so a long local part breaks too. */
.wrapany { min-width:0; overflow-wrap:anywhere }
.tinynote { font:var(--fw-regular) var(--fs-tiny)/1.6 var(--font-sans); color:var(--text-subtle) }

/* The line under a card's head, where a screen says what its list is for and
   how to treat it. It was .tinynote, which is the wrong class for a sentence
   somebody is meant to act on: 11.5px — the smallest size in the scale — of
   --text-subtle, which is #878787 at 3.59:1 on white and fails AA at any size,
   and with no measure it ran 818px wide inside an 860px card. Three things
   working against legibility at once.

   So: the prose size, the muted ink at 7.25:1, and a measure that stops well
   before the card edge. 560px is the figure .digest-item .body already uses for
   running text, so this adds no new number. */
.cardlede { margin:10px 0 6px; max-width:560px;
            font:var(--fw-regular) var(--fs-copy)/1.55 var(--font-sans); color:var(--text-muted) }

/* The taxonomy tag pickers in the project panel.
   NO max-height and NO overflow, which is the point of the class. They carried
   a 184px window, putting a scroller inside .panel-body's own scroller.
   Measured on production, 1 September 2026: 24 sector rows, 636px of content,
   184px visible — 71% of the list hidden, two thirds of the way down a form
   already 2.4 screens tall, behind an overlay scrollbar measuring 0px wide and
   therefore drawing no hint that it was there. At 375px the window took 28% of
   the visible panel height, so a vertical swipe had roughly a one-in-three
   chance of landing in it and scrolling the list instead of the form.
   The audience list has six rows and never reached the cap at all, so the one
   number was dead weight on one list and harmful on the other.

   It also quietly undid the fix directly above sectorTagList in panels.js: the
   sectors are ordered as a tree so no indented child appears without its
   parent, and a 6.9-row window over 24 rows put children on screen with their
   parent scrolled out of sight regardless. Scrolled 300px, the top row read
   "Banks & Lenders" with "Financial Services" gone. */
.taglist { display:flex; flex-direction:column; gap:6px; padding:2px 0 }
/* UNUSED -- see `.worklist .footnote`. Kept, not cruft. */
.footnote { padding:20px var(--gutter,40px); font:var(--fw-regular) var(--fs-label-lg) var(--font-sans); color:var(--text-subtle) }
/* A control that reads as a link: "Edit" on a reference row, "Remove" on a
   Feed post, "+ New project" on My work. Link blue and semibold at the
   smallest sentence size. The size was written as a bare 12.5px until
   15 September 2026 (Idea Foundry card "Link consistency", step three) --
   the same value as --fs-tiny, so putting it on the scale changed nothing
   visible, checked by a snapshot of all 179 instances on screen. Two things
   found on the way and worth knowing: containers in this app carry no size
   (type is set on leaves), so `font-size:inherit` here would have meant 16px
   for a third of these controls, and the t-tiny / t-strong utilities that
   nine call sites carried alongside this class never took effect, because
   this rule comes later in the file and its shorthand wins. They were
   removed from those call sites rather than made to work, since every one
   of them asked for what this rule already gives. */
.linkish { background:none; border:none; padding:0; cursor:pointer;
           font:var(--fw-semibold) var(--fs-tiny) var(--font-sans); color:var(--text-link) }
.addrow { padding:12px 18px; font:var(--fw-semibold) var(--fs-note) var(--font-sans); color:var(--text-link);
          background:none; border:none; cursor:pointer; text-align:left; width:100% }
.addrow:hover { background:var(--mh-grey-100) }

.toast {
  position:fixed; left:50%; bottom:26px; transform:translateX(-50%); z-index:60;
  background:var(--mh-black); color:var(--mh-white); padding:12px 18px;
  font:var(--fw-semibold) 13px var(--font-sans); box-shadow:var(--shadow-lg); max-width:min(560px,90vw);
  border-radius:var(--radius-lg);
}
.toast.err { background:var(--mh-danger) }

/* ------------------------------------------------------------ the team feed
   One column, newest first (059). `.feedlist` is the only node the poll
   repaints; the composer above it is never rebuilt, so an arriving post cannot
   disturb what somebody is typing. The reader's own posts carry a faint left
   rule so they find their place in the list. Tokens only. */
.feed { max-width:720px; display:flex; flex-direction:column; gap:18px }
/* The composer is pale blue (11 September 2026, Idea Foundry card "Background
   colour on feed composer panel"): white on white, it read as one surface with
   the list beneath it. The same ground and no-border treatment as .card.accent
   on the project record, and the same INK for the same reason: --text-subtle
   measures 3.98:1 on this blue, under the 4.5:1 floor, so the two small notes
   (the Enter hint and the counter) take --mh-blue-deep at 5.48:1, which is what
   that card already does for its own note. The textarea stays white -- that is
   what separates the box from the panel -- and the small avatar takes a white
   fill, because its grey (1.22:1 here) would vanish into the ground. */
.composer { display:flex; gap:12px; align-items:flex-start; padding:16px 18px;
  background:var(--mh-blue-pale); border:1px solid var(--mh-blue-pale); border-radius:var(--radius-lg) }
.composer .tinynote { color:var(--mh-blue-deep) }
.composer .avatar.sm { background:var(--mh-white) }
.composer-body { flex:1; min-width:0; display:flex; flex-direction:column; gap:8px }
.composer-input { width:100%; padding:10px 12px; resize:vertical; border-radius:var(--radius-md);
  font:var(--fw-regular) var(--fs-copy)/1.5 var(--font-sans); color:var(--text-body);
  background:var(--surface-card); border:1px solid var(--border-default) }
.composer-row { display:flex; align-items:center; gap:12px; flex-wrap:wrap }
.composer-row .composer-count { margin-left:auto }
.feedlist { display:flex; flex-direction:column; background:var(--surface-card); border:1px solid var(--border-default);
  border-radius:var(--radius-lg) }
.feedpost { display:flex; gap:12px; padding:14px 18px; border-top:1px solid var(--border-subtle) }
.feedpost:first-child { border-top:0 }
.feedpost.mine { box-shadow:inset 3px 0 0 var(--mh-blue-200) }
.feedmain { flex:1; min-width:0 }
.feedmeta { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap }
.feedmeta .name { font-weight:var(--fw-semibold) }
.feedmeta .feedremove { margin-left:auto }
.feedbody { margin-top:4px; white-space:pre-wrap; overflow-wrap:anywhere }
.feedfoot { display:flex; align-items:center; gap:14px; flex-wrap:wrap }
/* The filter row (11 September 2026): a person picker and a keyword box, side
   by side, and Clear once either is on. Both controls are the design system's
   own fields, so they take `.field`'s height and border; this only places them.
   The person picker keeps a fixed share so the search box, which is what most
   readers will reach for, gets the rest. Under 560px the two stack. */
.feedfilter { display:flex; gap:12px; align-items:flex-start; flex-wrap:wrap }
.feedfilter .field { margin:0 }
.feedfilter .feedfilter-by { flex:0 1 220px; min-width:160px }
.feedfilter .feedfilter-q { flex:1 1 240px; min-width:0 }
.feedfilter .feedfilter-clear { align-self:center; white-space:nowrap }
.feedfilter-note { display:flex; gap:14px; flex-wrap:wrap; margin-top:-8px }
@media (max-width:560px) {
  .feedfilter .feedfilter-by, .feedfilter .feedfilter-q { flex:1 1 100% }
}

/* ------------------------------------------------------------- files (060) */
/* Links to where the work's files live. The record's Files card and the task
   panel's Files section share one list (components/links.js), so one block of
   rules. A row is a link of two lines -- the label, then where it points --
   with Remove at the end; the requester's own link heads the list without a
   Remove, since it is edited on the project panel. Hairlines between rows, as
   the person panel's task rows have. The host line is small text on white, so
   it takes --text-muted (7.25:1) rather than --text-subtle (3.59:1). */
.linklist { display:flex; flex-direction:column }
.linkrow { display:flex; align-items:center; gap:12px; padding:8px 0; border-bottom:1px solid var(--border-subtle) }
.linkrow-a { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; text-decoration:none; color:inherit }
.linkrow .t { font:var(--fw-semibold) var(--fs-note)/1.35 var(--font-sans); color:var(--text-link); overflow-wrap:anywhere }
.linkrow .host { font:var(--fw-regular) var(--fs-tiny)/1.3 var(--font-sans); color:var(--text-muted) }
.linkrow .linkremove { flex:none }
.linkgroup { margin-top:12px; padding-bottom:4px }
.linkempty { padding:2px 0 6px }
.linkaddrow { display:block; padding:10px 0 0; text-align:left }
.linkadd { display:flex; flex-direction:column; gap:12px; padding-top:12px }

.loading { padding:40px 28px; font:var(--fw-regular) var(--fs-copy) var(--font-sans); color:var(--text-subtle) }

/* Narrow screens: the wireframes are desktop designs, but the app must not
   break on a laptop or tablet. Columns collapse rather than overflow. */
@media (max-width:1180px) {
  .tiles { grid-template-columns:repeat(2,1fr) }
  .board { grid-template-columns:repeat(2,1fr) }
  .clientgrid { grid-template-columns:repeat(2,1fr) }
}
@media (max-width:900px) {
  nav.sidenav { display:none }
  nav.topnav { display:flex }
  .digest-split { flex-direction:column }
  .digest-rail { width:auto; border-top:1px solid var(--border-default) }
  .digest-main { border-right:none }
  .comms { --gutter:24px }
  .grid2 { grid-template-columns:1fr }
  /* The rail goes under the work rather than beside it. At 375px the `1fr`
     track on the project screen collapsed to nothing and took the task table
     and the burn-up chart with it — they were rendering inside 42px. */
  .project-split, .mywork-split { grid-template-columns:1fr }
  /* The rail of facts goes under the title, full width, and loses the rule that
     separated the two columns.

     The row is told to stack rather than left to work it out from the numbers.
     It used to rely on the rail being wide enough to force a wrap — and when
     the project record stopped carrying `canvas.tight`, whose extra
     specificity had been holding a 28px gutter on a phone, the 24px this
     released was enough for the rail to fit beside the title. `flex:1` with
     `min-width:0` will then shrink the title column to nothing rather than
     wrap: it went to 3px wide and 607px tall, one character per line.
     A direction is a fact; a wrap is an arithmetic accident. */
  .projhero-row { flex-direction:column }
  .projhero-main { min-width:0; width:100% }
  .projhero-meta { width:auto; padding-left:0; border-left:0 }
  /* The grid stops being a grid: each row becomes a stacked block with the week
     named on every cell, since the column header is gone.

     `min-width` has to be unset with the template. The wide layout gives every
     row a floor of 240px plus 130px a week so the columns can scroll in step;
     left in place here it would force 1200px of horizontal overflow onto a
     phone, which is the one thing the stacked layout exists to prevent. */
  .capgrid { overflow-x:visible }
  .capgrid .th { display:none }
  .capgrid .tr, .capgrid .captotal { grid-template-columns:1fr; gap:14px; min-width:0 }
  .capgrid .who { padding-bottom:4px; border-bottom:1px solid var(--border-subtle) }
  .capcell, .captotal .v { padding-right:0 }
  .capweek { display:block }
  .captotal { gap:12px }
  .captotal .capweek { color:var(--mh-grey-500) }

  /* The roster collapses the same way and for the same reason: the header goes,
     every cell carries its own name, and nothing scrolls sideways.

     Two columns rather than the capacity grid's one, because these cells are
     short where a week's cell is a number and a bar. Pairing them keeps a
     person to about four lines instead of six, and costs nothing in width --
     half of a 375px screen is 165px, where the desktop layout gives Role 150px,
     Capacity 120px, This week 120px and Open 90px. Every field here is at least
     as wide as it is on a desktop; the email, spanning both columns, is wider.

     The name leads the block as its heading rather than sitting in the first
     cell, which is what `.who` does on the capacity grid. */
  .tbl.teamgrid { overflow-x:visible }
  .tbl.teamgrid .th { display:none }
  .tbl.teamgrid .tr {
    grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px 16px;
    padding:14px 16px; align-items:start; min-width:0;
  }
  .teamgrid .tname {
    grid-column:1 / -1; padding:0 0 10px;
    border-bottom:1px solid var(--border-subtle);
  }
  .teamgrid .c-email { grid-column:1 / -1 }
  .teamgrid .tname, .teamgrid .c-email, .teamgrid .c-week { padding-right:0 }
  .tkey { display:block }

  /* The restore list stacks too. Name and address take a line each; when and by
     whom shares the last line with the way back, which is the pairing that
     keeps the action next to what it undoes. */
  .removedrow { flex-wrap:wrap; gap:2px 10px; padding:10px 0 }
  .removedrow .rm-name { flex:1 0 100%; min-width:0; font-weight:var(--fw-semibold) }
  .removedrow .rm-mail { flex:1 0 100% }
  .removedrow .rm-when { flex:1 1 auto }

  .panel { width:100% }
}
@media (max-width:640px) {
  .tiles, .board, .clientgrid { grid-template-columns:1fr }
  main.canvas { padding:18px 16px }
  .digest-main { padding:24px 18px }
  .digest-rail { padding:24px 18px }

  /* Tag above the item rather than beside it — 104px of gutter is too much
     of a 375px screen. */
  .digest-item { flex-direction:column; gap:6px }
  .digest-item .tag { min-width:0 }

  /* The suite bar has five things competing for one line once the way back is
     on it: waffle, wordmark, "Marketing view", "Sign out", avatar. That is 496px
     of content in a 375px bar, and it pushed the page 60px sideways.

     What gives is the wordmark, and only for the reader who has the extra link
     — an internal client keeps it. The trade is a fair one: the person losing the firm's
     name off the bar is somebody who works there and is one tap from a page that
     says it twice. The waffle and the avatar still mark it as the firm's suite. */
  .suitebar.withback .wordmark { display:none }
  .suitebar.withback .barlink { padding:6px; margin-right:0 }

  /* The site header has three things competing for one line. */
  .siteheader { height:auto; min-height:56px; padding:10px 16px; flex-wrap:wrap; row-gap:8px }
  .sitesub { display:none }
  .suitebar { gap:12px }

  /* Three or four labels on one line is more than a phone holds. */
  .card-head { flex-wrap:wrap; row-gap:4px }
  .comms { --gutter:16px }

  /* The label column is 300px, which on a phone leaves 40-odd pixels of track
     inside the scroller — so the reader has to scroll sideways before seeing a
     single bar, and the chart is useless until they do. Halving it puts most of
     the three months on screen at rest, and the project names still fit on two
     lines at this size. */
  .tl-head, .tl-row { grid-template-columns:150px 1fr }
  .sch-label { width:150px; padding:9px 12px }
  .tl-row .lbl { padding-right:12px }

  /* ------------------------------------------------- the first screen
     A phone spent its whole first screen on chrome. The internal-client window
     opened with 48px of suite bar, a 294-349px hero and 111px of chips and
     tabs — 453-508px of an 812px screen before the first piece of work. A
     team list spent 188px on chrome and then 151px on eleven filter chips
     wrapped into three rows.

     The hero keeps its shape and loses the air it was drawn with at 1440.
     The note under the call to action goes: it says the button creates a
     project with status Requested, which is the first thing the form itself
     says, and on a phone it is a line of small grey type between the reader
     and the work. It is back at the next breakpoint up. */
  .comms-hero { padding:24px var(--gutter) 26px }
  .comms-hero .stack .tinynote { display:none }

  /* Eleven practice groups wrap to three rows and push the work off the
     screen. One row that scrolls, like the navigation above it — the same
     gesture, and the reader can see there is more without being shown all of
     it. The chips must not shrink or they compress into unreadable slivers
     rather than scrolling, and the row needs min-width:0 to be allowed to be
     narrower than its content inside the flex row it sits in. */
  .filterrow {
    flex-wrap:nowrap; overflow-x:auto; min-width:0;
    padding-bottom:2px;
  }
  .filterrow > * { flex:none }

  /* ------------------------------------------------------- tap targets
     A finger is not a mouse pointer. Everything that is a button or a chip
     reaches 44px on a phone — the size Apple's guidance asks for, and well
     above the 24px WCAG 2.2 AA floor. Type sizes do not change with it, so
     the difference between a page-level action and one in a row is still
     legible; only the box grows.

     What is not touched: a link inside a sentence. Those are exempt from the
     WCAG rule for good reason — growing them would overlap the words around
     them. `.tap` marks the ones that stand alone. */
  .btn, .btn.sm { height:44px }
  .filterchip { min-height:44px }
  .iconbtn { width:44px; height:44px }
  .barlink { min-height:44px }
  .addrow { min-height:44px }
  .choice { min-height:44px }
  nav.topnav a.nav, nav.topnav button.nav { min-height:44px }

  /* A hit area rather than a bigger control, for the ones whose size is the
     point: the roster's "edit", the digest's small controls, a clickable
     avatar. The pseudo-element takes the taps; nothing moves on screen. */
  .tap, .dangerlink, .breadcrumb a { position:relative }
  .tap::after, .dangerlink::after, .breadcrumb a::after {
    content:""; position:absolute; inset:-15px -10px;
  }
  .persondrill { min-height:44px }

  /* The board's assignee row is a row of drill-downs, so those grow for real.
     A hit area was tried first and was wrong: at a 6px gap the expanded areas
     of neighbouring avatars overlapped, so a tap between two of them would
     have opened the wrong person's workload — a wrong action rather than a
     missed one. 201 overlapping pairs on that screen alone. */
  .avatar.ring { width:44px; height:44px; font-size:var(--fs-copy) }

  /* Deliberately left small: the requesting team and the assignee avatar on a
     board card. Both are buttons inside a card that is itself a button, so a
     hit area around either would take taps meant for the task — a wrong
     action rather than a missed one. The card is the target and it is large;
     these are secondary affordances on it. */
  /* Only where the row sits beside something else — the internal-client window's
     view switch. `flex:1` on a child of `main.canvas`, which is a *column*
     flex container, grows it down the page instead of across: the Projects
     filter row came out 1436px tall. */
  .row > .filterrow { flex:1 1 auto }
}
