/* A control's edge is --control-border, never --mist: --mist is 1.41:1 on white, and a
   button, a chip, a tick or a text box whose border is the only thing saying where it ends
   needs 3:1. --mist stays for decoration and fills. */
/* ============================================================================
   components.css — shell, primitives and every component
   First ported from Docs/ui-mockup.html; since the October 2026 modernisation pass the
   app and /dev/gallery are the design of record. Radii and shadows come from tokens.
   ============================================================================ */

/* ---------- shell ---------- */
.app{display:flex; min-height:100vh}
aside.nav{
  width:var(--nav-w); flex:0 0 var(--nav-w); border-right:1px solid var(--hair);
  padding:var(--s5) 0 0; position:sticky; top:0; height:100vh;
  background:var(--paper);
  /* A column so the foot stays put and only the links scroll between brand and foot. */
  display:flex; flex-direction:column;
}
.brand{padding:0 var(--s5) var(--s3); flex:0 0 auto}
.brandtag{display:block; margin-top:var(--s3)}
#navlinks{flex:1 1 auto; min-height:0; overflow-y:auto; padding-bottom:var(--s5)}
.navfoot{
  flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-start; gap:var(--s3);
  padding:var(--s3) var(--s5) var(--s5); border-top:1px solid var(--hair);
}
.navfoot .userbox{margin-top:0; align-self:stretch}
.logo{width:100%; max-width:170px; height:auto; display:block}
.logo-light{display:var(--logo-light)}
.logo-dark{display:var(--logo-dark)}
.themectl{
  display:inline-flex; gap:2px; padding:2px; border:1px solid var(--control-border);
  border-radius:var(--radius-s); background:var(--white);
}
.themectl button{
  padding:3px 9px; font-size:12px; background:transparent; border:0; cursor:pointer;
  color:var(--muted); font-family:var(--font-data); border-radius:var(--radius);
}
.themectl button:hover{color:var(--ink)}
.themectl button[aria-pressed="true"]{background:var(--blush); color:var(--on-blush); font-weight:500}
.nav-group{margin-top:var(--s5)}
.nav-group .eyebrow{padding:0 var(--s5) var(--s1); display:block}
.nav a{
  display:flex; justify-content:space-between; align-items:center; gap:8px;
  margin:1px var(--s3); padding:7px var(--s3); font-size:13.5px; text-decoration:none; color:var(--ink);
  border-radius:var(--radius-s); position:relative;
}
.nav a:hover{background:color-mix(in srgb,var(--white) 70%,transparent)}
/* The current page is a raised pill with a blush mark, not a 2px rule on the window edge. */
.nav a[aria-current="page"]{background:var(--white); box-shadow:var(--shadow-1); font-weight:600}
.nav a[aria-current="page"]::before{
  content:""; position:absolute; left:-6px; top:8px; bottom:8px; width:3px;
  border-radius:var(--radius-pill); background:var(--blush-deep);
}
.nav a .tag{font-size:11px; color:var(--muted)}

main{flex:1; min-width:0; padding:var(--s6) var(--s7) var(--s7); max-width:1180px}
.page-head{padding-bottom:var(--s2); margin-bottom:var(--s5)}
.page-head .row{display:flex; justify-content:space-between; align-items:flex-end; gap:var(--s4); flex-wrap:wrap}
.page-head p{margin:6px 0 0; max-width:64ch; color:var(--muted); font-size:14px}

/* ---------- primitives ---------- */
/* 36px tall, .sm 28px, .icon square. Every button has one height per size, so a row of them
   lines up without anyone padding one by hand — which is what the 253 inline styles were. */
.btn{
  border:1px solid var(--control-border); background:var(--white); border-radius:8px;
  padding:7px 14px; font-size:13.5px; font-weight:500; line-height:20px; cursor:pointer; white-space:nowrap;
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  transition:background .12s, border-color .12s, box-shadow .12s;
}
.btn:hover{border-color:var(--sage); box-shadow:var(--shadow-1)}
.btn.sm{padding:3px 10px; font-size:12.5px; border-radius:var(--radius-s)}
.btn.icon{padding:0; width:36px; height:36px}
.btn.icon.sm{width:28px; height:28px}
.btn svg{width:16px; height:16px; flex:0 0 16px}
.btn.sm svg{width:14px; height:14px; flex-basis:14px}
@media (prefers-reduced-motion:reduce){.btn{transition:none}}
/* A link dressed as a button looks like one: no underline, centred, and a box that a width
   applies to — `.wide` on an inline <a> did nothing and left its label underlined at the left. */
a.btn{text-decoration:none; text-align:center}
a.btn.wide{display:flex}
/* A revoked token's Revoke button was pixel-identical to a live one — same ink, same
   border, same pointer — and at 375 with the table scrolled you read five of them. Not
   opacity, which this design system forbids for making something recede: a dashed edge and
   the label's own colour say "nothing here to press". The text stays on --label, which
   measures AA, so it is still readable rather than merely dimmer. */
.btn:disabled{
  color:var(--label);
  border-style:dashed;
  border-color:var(--control-border);
  background:transparent;
  cursor:default;
}
.btn:disabled:hover{border-color:var(--control-border); box-shadow:none}
.btn.primary{background:var(--ink); color:var(--paper); border-color:var(--ink)}
.btn.primary:hover{background:var(--sage-deep); border-color:var(--sage-deep)}
/* Delete. Text and edge in --owed, never a filled ground: --on-owed on the dark theme's
   --owed is 2.3:1. Measured 5.9:1 on white, 7.0:1 on the dark --white. Disabled falls through to .btn:disabled above. */
.btn.danger:not(:disabled){color:var(--owed); border-color:var(--owed)}
.btn.danger:not(:disabled):hover{box-shadow:inset 0 0 0 1px var(--owed)}
.confirm{display:inline-flex; flex-wrap:wrap; align-items:center; gap:var(--s2)}
/* Read by a screen reader, not drawn: the board says where a card went after a keyboard move. */
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0}
.btn.ghost{background:transparent}
.btn.ghost:hover{background:var(--white)}
.pill{
  display:inline-flex; align-items:center; gap:5px; font-size:12px; font-weight:500; line-height:18px;
  padding:1px 9px; border-radius:var(--radius-pill); border:1px solid var(--control-border); white-space:nowrap;
}
/* Status pills carry a faint ground of their own hue as well as the edge, so they read at a
   glance down a column rather than only when you look for them. */
.pill.ok{color:var(--ok); border-color:color-mix(in srgb,var(--ok) 40%,transparent); background:color-mix(in srgb,var(--ok) 9%,transparent)}
.pill.owed{color:var(--owed); border-color:color-mix(in srgb,var(--owed) 45%,transparent); background:color-mix(in srgb,var(--owed) 9%,transparent)}
.pill.warn{color:var(--warn); border-color:color-mix(in srgb,var(--warn) 45%,transparent); background:color-mix(in srgb,var(--warn) 9%,transparent)}
.pill.sel{background:var(--blush); border-color:var(--blush-deep); color:var(--on-blush)}
.pill.quiet{color:var(--muted)}
.dot{width:6px; height:6px; border-radius:50%; background:currentColor; flex:0 0 6px}
/* A category's colour: soft ground, deep text of the same hue. The class comes from
   CategoryColour.TintFor; the colours from tokens.css, measured by TokenContrastTests. */
.tint-1{--tint-bg:var(--tint1-bg); --tint-fg:var(--tint1-fg)}
.tint-2{--tint-bg:var(--tint2-bg); --tint-fg:var(--tint2-fg)}
.tint-3{--tint-bg:var(--tint3-bg); --tint-fg:var(--tint3-fg)}
.tint-4{--tint-bg:var(--tint4-bg); --tint-fg:var(--tint4-fg)}
.tint-5{--tint-bg:var(--tint5-bg); --tint-fg:var(--tint5-fg)}
.tint-6{--tint-bg:var(--tint6-bg); --tint-fg:var(--tint6-fg)}
.tint-7{--tint-bg:var(--tint7-bg); --tint-fg:var(--tint7-fg)}
.tint-8{--tint-bg:var(--tint8-bg); --tint-fg:var(--tint8-fg)}
.pill.tinted{background:var(--tint-bg); color:var(--tint-fg); border-color:transparent}

.card{
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s5); box-shadow:var(--shadow-1);
}
.card.lift{box-shadow:var(--shadow-2)}
.card > h3:first-child,.card > .sec-head:first-child{margin-bottom:var(--s3)}
.grid{display:grid; gap:var(--s4)}
.grid > *{min-width:0}   /* a table's min-content width must not widen the page on a phone */
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g4{grid-template-columns:repeat(4,minmax(0,1fr))}
.row{display:flex; gap:var(--s3); align-items:center; flex-wrap:wrap}
.spread{display:flex; justify-content:space-between; align-items:center; gap:var(--s3)}
.stack{display:flex; flex-direction:column; gap:var(--s5)}
.sec-head{display:flex; justify-content:space-between; align-items:baseline; gap:var(--s4); margin-bottom:var(--s3)}
hr.rule{border:0; border-top:1px solid var(--hair); margin:var(--s5) 0}

/* A figure stays on one line — a minus sign is a break opportunity, and "−" alone on a line
   reads as a dash — and shrinks with its tile instead, so "−$9,780.00" fits four across. */
.stat{container-type:inline-size}
.stat b{display:block; font-size:min(28px, 18cqi); font-weight:600; line-height:1.1; letter-spacing:-.02em; white-space:nowrap}
.stat .eyebrow{display:block; margin-bottom:6px}
.stat small{font-size:12.5px; color:var(--muted); font-family:var(--font-data)}


/* ---------- capacity ring ---------- */
.ring{display:block}
.ring circle{fill:none; stroke-linecap:butt}
.ring .track{stroke:var(--ring-track)}
.ring .fill{stroke:var(--blush-deep); transition:stroke-dashoffset .45s ease}
.ring.full .fill{stroke:var(--sage-deep)}
/* An administrator's ring: one arc per place, coloured by whether the circle pays its way.
   An empty place is a thin arc in --control-border (3:1 on both grounds, measured): the bare
   track is 1.26:1 on white, and the empty seats are half of what the segments are for. Thin
   so it cannot be mistaken for a mum. */
.ring.tone-few{--tone:var(--ring-few)}
.ring.tone-building{--tone:var(--ring-building)}
.ring.tone-profit{--tone:var(--ring-profit)}
.ring[class*="tone-"] .fill{stroke:var(--tone)}
.ring.seg .track{stroke:var(--control-border); stroke-width:1.5px}
.ring.seg .fill{transition:none}
.net-loss{color:var(--owed)}
.net-profit{color:var(--ok)}
.gcard b.num{font-weight:600}
.key{display:inline-block; width:10px; height:10px; border-radius:var(--radius-pill); margin:0 var(--s1) 0 var(--s2); vertical-align:-1px}
.key-few{background:var(--ring-few)}
.key-building{background:var(--ring-building)}
.key-profit{background:var(--ring-profit)}
.ringwrap{position:relative; display:inline-grid; place-items:center}
.ringwrap .val{
  position:absolute; font-family:var(--font-data); font-variant-numeric:tabular-nums;
  font-size:13px; font-weight:600; letter-spacing:-.02em;
}
.ringwrap.lg .val{font-size:17px}
@media (prefers-reduced-motion:reduce){
  .ring .fill{transition:none}
  .msheet .panel{animation:none}
}

/* ---------- group board ---------- */
.board{display:grid; gap:var(--s3); grid-template-columns:repeat(auto-fill,minmax(232px,1fr))}
.gcard{
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s4); display:flex; gap:var(--s4); align-items:center; text-align:left;
  cursor:pointer; width:100%; box-shadow:var(--shadow-1); transition:box-shadow .15s, border-color .15s;
}
.gcard:hover{border-color:var(--control-border); box-shadow:var(--shadow-2)}
@media (prefers-reduced-motion:reduce){.gcard{transition:none}}
.gcard .who{min-width:0}
.gcard h3{font-size:15px}
.gcard .sub{margin-top:2px}
/* A circle the caller does not run: on the board for information, not a link. It recedes with
   a dashed edge and the paper ground, never with opacity, and does not lift on hover. */
.gcard.closed{cursor:default; background:var(--paper); border-style:dashed; border-color:var(--control-border); box-shadow:none}
.gcard.closed:hover{box-shadow:none}
.gcard.closed .pill{margin-top:6px}

/* ---------- password field ---------- */
/* The eye sits over the right end of the box, beside the label rather than in it (see
   PasswordToggle.razor). The box keeps room for it whether it shows dots or text. */
.pw{position:relative}
.pw > label{display:flex; flex-direction:column; gap:5px}
.pw input{padding-right:40px; width:100%}
.pw-toggle{position:absolute; right:4px; bottom:4px; border-color:transparent; background:transparent; box-shadow:none}
.pw-toggle:hover{border-color:var(--control-border)}
.pw-toggle .pw-hide,.pw-toggle[aria-pressed="true"] .pw-show{display:none}
.pw-toggle[aria-pressed="true"] .pw-hide{display:inline-flex}
.pw-toggle span{display:inline-flex}
.gallery-pw{max-width:320px}

/* ---------- tabs ---------- */
.tabs{display:flex; gap:var(--s5); border-bottom:1px solid var(--hair); margin-bottom:var(--s5)}
/* The selected tab's rule is --sage-deep: --blush-deep on --paper is 2.24:1, under the 3:1
   a non-text cue needs, and the rule is the main thing that says which tab is open. */
.tab{
  background:none; border:0; border-bottom:2px solid transparent; padding:0 0 10px;
  font-size:13.5px; font-weight:500; cursor:pointer; color:var(--muted); margin-bottom:-1px;
}
.tab:hover{color:var(--ink)}
.tab[aria-selected="true"],.tab[aria-current="page"]{color:var(--ink); border-bottom-color:var(--sage-deep); font-weight:600}
a.tab{text-decoration:none}
/* Blazor moves focus to the h1 on every navigation so a screen reader hears the new page. That
   is a programmatic focus on a non-control, not something the keyboard user tabbed to, so it
   draws no ring — the ring on load read as a selected heading. */
h1[tabindex="-1"]:focus{outline:none}

/* ---------- attendance grid ---------- */
.att th.wk{text-align:center; width:44px}
.att td.c{padding:4px; text-align:center}
.cell{
  width:32px; height:30px; border:1px solid var(--hair); border-radius:var(--radius-s);
  background:transparent; cursor:pointer; font-size:11px; font-weight:600; color:var(--muted);
  display:inline-flex; align-items:center; justify-content:center;
}
.cell:hover{border-color:var(--sage)}
.cell[data-s="P"]{background:var(--blush); border-color:var(--blush-deep); color:var(--on-blush)}
.cell[data-s="A"]{color:var(--owed); border-color:color-mix(in srgb,var(--owed) 40%,transparent)}
.cell[data-s="L"]{color:var(--warn); border-color:color-mix(in srgb,var(--warn) 45%,transparent)}
.cell[data-s="-"]{background:repeating-linear-gradient(135deg,transparent 0 4px,color-mix(in srgb,var(--muted) 35%,transparent) 4px 5px)}
.legend{display:flex; gap:var(--s4); flex-wrap:wrap; margin-top:var(--s3)}

/* ---------- allocation grid ---------- */
.alloc{font-size:13px}
.alloc td{padding:6px 8px; border:1px solid var(--hair); vertical-align:top}
.alloc th{border:1px solid var(--hair); padding:8px}
/* --label, not --sage: this is 12px, and --sage on --paper measures 4.39:1. The week
   column is a row header now, so it is a th that has to read like the td it replaced. */
.alloc .wkno{width:52px; color:var(--label); font-family:var(--font-data); font-size:12px; white-space:nowrap}
.alloc tbody th.wkno{padding:6px 8px; text-align:left; font-weight:400; letter-spacing:normal; text-transform:none}
.slot{
  width:100%; text-align:left; background:transparent; border:0; padding:2px 4px; cursor:pointer;
  border-radius:2px; font-size:12.5px; font-family:var(--font-data); line-height:1.35;
}
.slot:hover{background:color-mix(in srgb,var(--blush) 30%,transparent)}
.slot.empty{color:var(--owed); font-style:normal}
.alloc td.today{background:color-mix(in srgb,var(--blush) 16%,transparent)}

/* ---------- bars (reports) ---------- */
.bars{display:grid; gap:10px}
.bar{display:grid; grid-template-columns:132px 1fr 76px; gap:var(--s3); align-items:center; font-size:13px}
.bar .track{height:14px; background:var(--ring-track); border-radius:var(--radius); overflow:hidden; display:flex}
.bar .seg{height:100%}
.bar .seg.new{background:var(--sage)}
.bar .seg.ret{background:var(--blush-deep)}
.bar .lbl{font-family:var(--font-data); font-size:12.5px}
.bar .fig{font-family:var(--font-data); font-variant-numeric:tabular-nums; font-size:12.5px; text-align:right; color:var(--muted)}

/* ---------- charts ---------- */
.chart{
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s4) var(--s5) var(--s5); box-shadow:var(--shadow-1);
}
.chart-head{display:flex; justify-content:space-between; align-items:baseline; gap:var(--s4); flex-wrap:wrap; margin-bottom:var(--s2)}
.chart-head h3{font-size:15px}
.chart-scroll{overflow-x:auto; margin:0 calc(-1 * var(--s2)); padding:0 var(--s2)}
.chart-scroll svg{display:block; width:100%; min-width:460px; height:auto}
.grid-line{stroke:var(--hair); stroke-width:1}
.axis-text{font-family:var(--font-data); font-size:11px; fill:var(--muted); font-variant-numeric:tabular-nums}
.mark-label{font-family:var(--font-data); font-size:11px; font-weight:600; fill:var(--ink); font-variant-numeric:tabular-nums}
.hit{fill:transparent; cursor:default}
.hit:hover,.hit:focus-visible{fill:color-mix(in srgb,var(--sage) 9%,transparent)}
.lgnd{display:flex; gap:var(--s4); flex-wrap:wrap; margin-bottom:var(--s3)}
.lgnd span{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-data); font-size:12px; color:var(--muted)}
.lgnd i{width:10px; height:10px; border-radius:2px; flex:0 0 10px}
.twin{margin-top:var(--s3)}
.twin summary{
  font-family:var(--font-data); font-size:12px; color:var(--label); cursor:pointer;
  padding:2px 0; width:max-content;
}
.twin summary:hover{color:var(--ink)}
.twin .tablewrap{margin-top:var(--s2)}
.twin table{font-size:13px}
.hero{display:block; font-family:var(--font-data); font-size:28px; font-weight:600; line-height:1.05; letter-spacing:-.015em}
#tip{
  position:fixed; z-index:50; pointer-events:none; opacity:0; transition:opacity .1s;
  background:var(--ink); color:var(--paper); border-radius:var(--radius-s); padding:6px 9px;
  font-family:var(--font-data); font-size:12px; line-height:1.45; max-width:220px;
  font-variant-numeric:tabular-nums; box-shadow:var(--shadow-2);
}
#tip.on{opacity:1}
#tip b{font-weight:600}
@media (prefers-reduced-motion:reduce){#tip{transition:none}}

/* ---------- to-dos and notes ---------- */
.todo{display:flex; gap:var(--s3); align-items:flex-start; padding:var(--s3) 0; border-top:1px solid var(--hair)}
.todo:first-child{border-top:0; padding-top:0}
.todo .tbody{min-width:0; flex:1}
.todo .ttitle{font-weight:600; font-size:14px; line-height:1.35}
.todo .tmeta{display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap; margin-top:5px}
.todo.done .ttitle{text-decoration:line-through; color:var(--muted); font-weight:400}
.tick{
  width:24px; height:24px; flex:0 0 24px; margin-top:1px; border:1.5px solid var(--control-border);
  border-radius:50%; background:transparent; cursor:pointer; font-size:12px; font-weight:600;
  color:var(--muted); display:inline-flex; align-items:center; justify-content:center;
}
.tick[aria-checked="true"]{background:var(--blush); border-color:var(--blush-deep); color:var(--on-blush)}
.tick:hover{border-color:var(--sage)}
.overdue{color:var(--owed); font-weight:600}
tr.rowdone .name{text-decoration:line-through; color:var(--muted); font-weight:400}
/* A locked matrix cell recedes by its dashed edge, never by fading. Faded, the 11px/600
   tick measured 3.31:1 against the card in light (3.84:1 once .cell:disabled compounded
   on top) — under AA, and the fourth time this app has dimmed something below the floor.
   CLAUDE.md: a different ground, a dashed rule or a lighter weight. Full-strength
   --on-blush on --blush is ~12:1 in both themes. */
.cell.locked{cursor:not-allowed; background:var(--blush); border:1px dashed var(--blush-deep); color:var(--on-blush)}
.chatbox{width:30px}

/* ---------- to-do screen ---------- */
.todolayout{display:grid; grid-template-columns:264px minmax(0,1fr); gap:var(--s5); align-items:start}
.tside{display:flex; flex-direction:column; gap:var(--s4); position:sticky; top:var(--s4)}
.tgroup{display:flex; flex-direction:column; gap:2px}
.tgroup .eyebrow{margin-bottom:4px}
.tlist{
  display:flex; justify-content:space-between; align-items:flex-start; gap:var(--s2);
  width:100%; text-align:left; background:transparent; border:1px solid transparent;
  border-radius:var(--radius); padding:8px 10px; cursor:pointer;
}
.tlist:hover{background:var(--white); border-color:var(--hair)}
.tlist.on{background:var(--white); border-color:var(--blush-deep)}
.tlist .tl-name{font-weight:600; font-size:13.5px; display:block}
.tlist .tl-sub{display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin-top:3px}
.tlist .tl-count{font-size:12px; color:var(--muted); white-space:nowrap; padding-top:2px}
.tlist.myitems{border-color:var(--hair); background:var(--white)}
.tlist.myitems.on{border-color:var(--blush-deep)}
.tarch{display:flex; gap:7px; align-items:center; font-family:var(--font-data); font-size:12px; color:var(--muted)}

.ttools{display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap}
.ttools input[type=search]{width:240px}
.catpill{gap:6px; cursor:pointer; background:transparent}
.catpill i{width:8px; height:8px; border-radius:50%; flex:0 0 8px}
button.catpill:hover{border-color:var(--sage)}
button.catpill.sel{background:var(--blush); border-color:var(--blush-deep); color:var(--on-blush)}

.tgroupbox{
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s3) var(--s4); margin-top:var(--s4);
}
.tgrouphead{padding-bottom:var(--s2); border-bottom:1px solid var(--hair); margin-bottom:var(--s2)}
.tgrouphead h3{font-size:14px}
.tactions{display:flex; gap:6px; align-items:center; flex:0 0 auto}
/* The bell is an icon button now, not an emoji. Off is the struck-through outline in --muted;
   on is the plain bell in --warn on a faint ground — glyph and aria-pressed carry the state,
   never how faded it is. */
.btn.bell.on{color:var(--warn); background:color-mix(in srgb,var(--warn) 10%,transparent)}

.additem{
  display:flex; flex-direction:column; gap:var(--s2); margin-bottom:var(--s4);
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s3); box-shadow:var(--shadow-1);
}
.additem-row{display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center}
.additem-row input[type=text]{flex:1 1 240px}
.additem-row.more{padding-top:var(--s2); border-top:1px dashed var(--hair)}
.additem-row.more input[type=text]{flex:0 1 220px}
.additem-row .check{display:inline-flex; gap:8px; align-items:center; font-size:13.5px}

.board3{display:grid; grid-template-columns:repeat(auto-fit,minmax(212px,1fr)); gap:var(--s3)}
/* A column is a recessed tray; a card sits up out of it. */
.bcol{
  background:color-mix(in srgb,var(--paper) 60%,var(--white)); border:1px solid var(--hair);
  border-radius:var(--radius-l); padding:var(--s3);
}
.bcolhead{padding:0 var(--s1) var(--s2); margin-bottom:var(--s2)}
.bcard{
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s3); margin-bottom:var(--s2); box-shadow:var(--shadow-1);
}
.bcard:last-child{margin-bottom:0}
.bcard.done .ttitle{text-decoration:line-through; color:var(--muted)}
.bcard .ttitle{font-weight:600; font-size:13.5px; line-height:1.35}
/* The speakers board. A card being dragged keeps its colour and gains a dashed edge —
   fading is never how something recedes. The column it would land in is ringed in
   --control-border rather than filled: a blush ground under --ink text fails in dark mode,
   and a --blush-deep ring measured 2.15:1 on the light column — under the 3:1 a
   non-text cue needs. --control-border is 3.7:1 light and 4.1:1 dark on --paper. */
.bcard.speaker{background:var(--white); cursor:grab}
.bcard.speaker:active{cursor:grabbing}
.bcard.speaker .ttitle{display:block; text-decoration:none; color:var(--ink)}
.bcard.speaker .ttitle:hover{text-decoration:underline}
.bcard.speaker .chips{gap:4px; margin-top:var(--s2)}
.bcard.speaker.dragging{border:1px dashed var(--control-border); background:var(--paper); box-shadow:none}
.bcard.speaker:hover{box-shadow:var(--shadow-2)}
.spcard-head{display:flex; justify-content:space-between; align-items:flex-start; gap:var(--s2)}
.spcard-head .ttitle{min-width:0; padding-top:3px}
.bcard.speaker .movers{display:flex; gap:2px; flex:0 0 auto; margin:-2px -4px 0 0}
/* The arrow is the control's whole identity, so its edge only appears on hover; the glyph
   in --muted measures over 3:1 on the card in both themes. */
.bcard.speaker .movers .btn{border-color:transparent; color:var(--muted)}
.bcard.speaker .movers .btn:hover:not(:disabled){border-color:var(--control-border); color:var(--ink); background:var(--paper)}
.spcard-foot{display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap; margin-top:var(--s2)}
.speakerboard .bcol.dropping{outline:2px solid var(--control-border); outline-offset:-1px}

@media (max-width:900px){
  .todolayout{grid-template-columns:1fr}
  .tside{position:static; flex-direction:row; overflow-x:auto; gap:var(--s3); padding-bottom:var(--s2)}
  .tgroup{flex-direction:row; gap:var(--s2)}
  .tgroup .eyebrow{display:none}
  .tlist{width:auto; white-space:nowrap; border-color:var(--hair); background:var(--white)}
  .tlist .tl-sub{display:none}
  .tarch{white-space:nowrap}
  .board3{grid-template-columns:1fr}
  .ttools input[type=search]{width:100%}
}

.note-item .note-body{margin:0; font-size:14px; line-height:1.5}
.note-item.done .note-body{color:var(--muted); text-decoration:line-through}
/* Not opacity: .72 took .meta to 3.11:1 in light, the third time this file has faded text
   under AA. An answered note recedes by ground and rule instead, and its text stays legible. */
.note-card.is-done{background:color-mix(in srgb,var(--mist) 26%,var(--white)); border-style:dashed}
textarea{
  width:100%; border:1px solid var(--control-border); background:var(--white); color:var(--ink);
  border-radius:var(--radius-s); padding:8px 11px; font-family:var(--font-body); font-size:14px;
  line-height:1.5; resize:vertical;
}
.formgrid{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--s3) var(--s4)}
/* One field: its label and the error under it. The error used to sit INSIDE the label,
   which folded the message into the input's accessible name — "First name The first name is
   required." — so while a field was invalid a screen reader announced the error as part of
   what the field was called. It is a sibling now, with role="alert" so it is announced when
   it appears, and this wrapper is what keeps the pair in one grid cell. */
.field{display:flex; flex-direction:column; gap:5px}
.field > label{display:flex; flex-direction:column; gap:5px; align-items:flex-start}
.formgrid label,.formgrid .field{display:flex; flex-direction:column; gap:5px}
.field > label{display:flex; flex-direction:column; gap:5px; align-items:flex-start}
.formgrid input,.formgrid select{width:100%}
@media (max-width:760px){.formgrid{grid-template-columns:1fr}}

.timeline{display:grid; gap:0}
.tl-row{display:grid; grid-template-columns:84px 1fr; gap:var(--s4); padding:var(--s3) 0; border-bottom:1px solid var(--hair)}
.tl-row:last-child{border-bottom:0}
/* --label, not --sage: 12.5px text on --white. */
.tl-row .when{font-family:var(--font-data); font-size:12.5px; color:var(--label)}

.kv{display:grid; grid-template-columns:auto 1fr; gap:6px var(--s4); font-size:14px}
/* --label, not --sage: 12px text, and --sage on --paper is 4.39:1. */
.kv dt{font-family:var(--font-data); font-size:12.5px; color:var(--label); padding-top:1px}
.kv dd{margin:0}

/* Not italic: Plex is hosted upright only, and a synthesised oblique looks broken. */
.note{
  border-left:3px solid var(--blush-deep); padding:2px 0 2px var(--s3);
  color:var(--muted); font-size:13.5px;
}
.navtoggle{display:none}

@media (max-width:1000px){
  main{padding:var(--s5) var(--s5) var(--s7)}
  :root{--nav-w:200px}
  .g4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .g3{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* ---------- mobile navigation: sticky top bar + bottom tab bar + More sheet ---------- */
.mtop,.mtabs,.msheet{display:none}

@media (max-width:760px){
  .app{flex-direction:column}
  aside.nav{display:none}            /* the sidebar is desktop-only */

  .mtop{
    display:flex; align-items:center; justify-content:space-between; gap:var(--s3);
    position:sticky; top:0; z-index:20; background:var(--paper);
    border-bottom:1px solid var(--hair); padding:9px var(--s4);
  }
  .mtop{padding:11px var(--s4) 10px}
  /* A dashboard tile that jumps to a section lands its heading below the sticky bar, not under it. */
  section[id]{scroll-margin-top:calc(var(--s7) + var(--s4))}
  .wordmark{width:152px; height:auto; display:block}
  .wordmark.logo-light{display:var(--logo-light)}
  .wordmark.logo-dark{display:var(--logo-dark)}
  .mtop .eyebrow{text-align:right}

  .mtabs{
    display:flex; position:fixed; bottom:0; left:0; right:0; z-index:25;
    background:var(--white); border-top:1px solid var(--hair);
    padding-bottom:env(safe-area-inset-bottom);
  }
  .mtabs button{
    flex:1 1 0; min-width:0; background:none; border:0; cursor:pointer;
    display:flex; flex-direction:column; align-items:center; gap:3px;
    padding:9px 2px 10px; color:var(--muted); position:relative;
    font-family:var(--font-data); font-size:10.5px; letter-spacing:.02em;
  }
  .mtabs button svg{width:21px; height:21px; flex:0 0 21px}
  .mtabs button span{white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%}
  .mtabs button[aria-current="page"],
  .mtabs button[aria-expanded="true"]{color:var(--ink); font-weight:500}
  .mtabs button[aria-current="page"]::before,
  .mtabs button[aria-expanded="true"]::before{
    content:""; position:absolute; top:0; left:50%; transform:translateX(-50%);
    width:26px; height:2px; background:var(--blush-deep);
  }
  .mtabs .tabcount{
    position:absolute; top:5px; left:calc(50% + 9px); background:var(--blush);
    color:var(--on-blush); border-radius:var(--radius-pill); min-width:16px; padding:0 4px;
    font-size:9.5px; line-height:16px; font-weight:600; text-align:center;
  }

  .msheet{display:block; position:fixed; inset:0; z-index:30}
  .msheet[hidden]{display:none}
  .msheet .backdrop{position:absolute; inset:0; background:rgba(17,17,17,.42)}
  .msheet .panel{
    position:absolute; left:0; right:0; bottom:0; max-height:82vh; overflow-y:auto;
    background:var(--paper); border-top:1px solid var(--hair);
    border-radius:var(--radius-l) var(--radius-l) 0 0;
    padding:var(--s3) var(--s4) calc(var(--s5) + env(safe-area-inset-bottom));
    animation:sheetUp .19s ease-out;
  }
  @keyframes sheetUp{from{transform:translateY(100%)} to{transform:translateY(0)}}
  .msheet .grab{width:36px; height:4px; border-radius:2px; background:var(--mist); margin:2px auto var(--s3)}
  .msheet .sheet-head{display:flex; justify-content:space-between; align-items:center; margin-bottom:var(--s2)}
  .msheet h2{font-size:18px}
  .msheet .close{
    background:none; border:1px solid var(--control-border); border-radius:var(--radius-s); cursor:pointer;
    width:30px; height:30px; font-size:15px; line-height:1; color:var(--muted);
  }
  .msheet .grp{margin-top:var(--s4)}
  .msheet .grp .eyebrow{display:block; margin-bottom:var(--s2)}
  .msheet .links{display:grid; grid-template-columns:1fr 1fr; gap:var(--s2)}
  .msheet .links a{
    display:flex; justify-content:space-between; align-items:center; gap:6px;
    text-decoration:none; color:var(--ink); background:var(--white);
    border:1px solid var(--hair); border-radius:var(--radius); padding:11px var(--s3);
    font-family:var(--font-data); font-size:13.5px;
  }
  .msheet .links a[aria-current="page"]{border-color:var(--blush-deep); background:var(--blush); color:var(--on-blush)}
  .msheet .links a .tag{font-size:11px; color:var(--muted)}
  .msheet .links a[aria-current="page"] .tag{color:var(--on-blush)}
  .msheet .sheetfoot{
    display:flex; justify-content:space-between; align-items:center; gap:var(--s3);
    margin-top:var(--s5); padding-top:var(--s4); border-top:1px solid var(--hair);
  }
  body.sheet-open{overflow:hidden}

  /* room for the fixed tab bar */
  main{padding:var(--s5) var(--s4) calc(76px + env(safe-area-inset-bottom))}
  h1{font-size:25px}
  .g2,.g3,.g4{grid-template-columns:1fr}
  .bar{grid-template-columns:96px 1fr 64px}
  input[type=search]{width:100%}
  /* keep the mum's name visible while the eight weeks scroll */
  .att thead th:first-child,
  .att tbody td:first-child{
    position:sticky; left:0; z-index:1; background:var(--white);
    box-shadow:1px 0 0 var(--hair);
  }
  .att tbody tr:hover td:first-child{background:var(--white)}
  .tabs{overflow-x:auto; gap:var(--s4); scrollbar-width:none}
  .tabs::-webkit-scrollbar{display:none}
  .chart{padding:var(--s4)}
  .page-head .row{align-items:flex-start}
  .sec-head{flex-direction:column; gap:2px}
  td,th{padding:9px var(--s3)}
}
@media (max-width:420px){
  main{padding:var(--s4) var(--s3) var(--s6)}
  h1{font-size:22px}
  .stat b{font-size:min(23px, 18cqi)}
  .hero{font-size:24px}
  .bar{grid-template-columns:1fr; gap:2px}
  .bar .fig{text-align:left}
  .board{grid-template-columns:1fr}
  .chart-scroll svg{min-width:400px}
}

/* ---------- gaps found while porting ---------- */
@media (max-width:760px){
  /* The mock-up leaves the allocation grid's week column scrolling away with the
     locations, so on a phone you lose which week you are looking at. */
  .alloc thead th.wkno,
  .alloc tbody th.wkno{
    position:sticky; left:0; z-index:1; background:var(--white);
    box-shadow:1px 0 0 var(--hair);
  }
  /* 32x30 is under a comfortable touch target for someone holding a baby. */
  .att .cell{width:40px; height:40px}
  .att td.c{padding:3px}
}

/* ---------- sign-in and password reset ---------- */
main.plain{
  display:flex; align-items:center; justify-content:center;
  min-height:100vh; padding:var(--s5); max-width:none;
}
.authbox{
  width:100%; max-width:400px; display:flex; flex-direction:column; gap:var(--s3);
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius-l);
  padding:var(--s6) var(--s6) var(--s5); box-shadow:var(--shadow-2);
}
@media (max-width:420px){.authbox{padding:var(--s5) var(--s4); box-shadow:none}}
.authbox .logo{max-width:150px; margin:0 auto var(--s3)}
.authbox h1{font-size:26px; margin-bottom:var(--s3)}
.authbox .field{display:flex; flex-direction:column; gap:5px; margin-bottom:var(--s3)}
.authbox .field input{width:100%}
.authbox .checkfield{display:flex; gap:var(--s2); align-items:center; font-size:13.5px; margin-bottom:var(--s4)}
.btn.wide{width:100%; padding:9px 12px; font-size:14px}
.authfoot{margin-top:var(--s5); padding-top:var(--s4); border-top:1px solid var(--hair)}
.validation-message{color:var(--owed); font-size:12.5px; font-family:var(--font-data)}

/* ---------- Blazor's own chrome, themed ---------- */
/* Left unstyled it renders as default blue and looks broken in dark mode. */
/* ---------- the reconnect modal ----------
   Blazor's own, and it is a <dialog> — the rules here were written for the div it used to be,
   so they fought the element's own geometry. It is styled globally rather than in a
   ReconnectModal.razor.css because this project has no CSS isolation (see CLAUDE.md). The
   scoped file that existed was never linked from App.razor, so for seven phases the modal
   actually rendered in the browser's default grey: 1.04:1 in dark, effectively invisible.

   Blazor calls showModal(), so [open] decides visibility and ::backdrop is the scrim. */
#components-reconnect-modal{
  display:none; box-shadow:var(--shadow-2);
  border:1px solid var(--hair); border-radius:var(--radius);
  background:var(--white); color:var(--ink);
  padding:var(--s5); max-width:340px; width:calc(100% - var(--s5) * 2);
  text-align:center; font-family:var(--font-data); font-size:14px;
}
#components-reconnect-modal[open]{display:block}
#components-reconnect-modal::backdrop{background:rgb(17 17 17 / 45%)}
#components-reconnect-modal p{margin:0 0 var(--s3)}
#components-reconnect-modal p:last-of-type{margin-bottom:0}
/* Left to the browser these are near-white on near-white with an outset border — 1.04:1 in
   dark, which is a button nobody can see at the one moment it matters. */
#components-reconnect-modal button{
  margin-top:var(--s4); border:1px solid var(--sage-deep); border-radius:var(--radius-s);
  background:var(--sage-deep); color:var(--paper);
  padding:6px var(--s4); font-family:var(--font-data); font-size:13px; cursor:pointer;
}
#components-reconnect-modal button:hover{background:var(--ink); border-color:var(--ink)}

/* Blazor's own classes, which decide WHICH message shows inside the dialog. */
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible{display:none}
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible{display:block}
.components-rejoining-animation{display:none}
#blazor-error-ui{
  position:fixed; bottom:0; left:0; right:0; z-index:60; display:none;
  background:var(--owed); color:var(--on-owed); padding:10px var(--s4);
  font-family:var(--font-data); font-size:13px;
}


/* ---------- who is signed in ---------- */
/* The mock-up had a "Viewing as" role dropdown here. That was a demo affordance. */
.userbox{
  display:flex; flex-direction:column; align-items:flex-start; gap:2px;
  margin-top:var(--s3); padding:var(--s3);
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
}
.userbox .name{font-size:13.5px}
.userbox .meta{font-size:11.5px}
.userbox form{margin-top:6px}
.userbox .btn{padding:2px 9px; font-size:12px}
/* The way to the release notes. A link, so it underlines; --label keeps 11.5px at AA. In the
   sidebar it would otherwise pick up `.nav a` — a padded, indented menu row. */
.userbox .relnotes{display:inline; color:var(--label); margin:2px 0 0; padding:0; border-radius:0; text-decoration:underline}
.userbox .relnotes:hover{color:var(--ink); background:none}

/* ---------- release notes ---------- */
.releases{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s4); max-width:760px}
.release-head{display:flex; justify-content:space-between; align-items:baseline; gap:var(--s3); flex-wrap:wrap; margin-bottom:var(--s3)}
.release-head h2{font-size:19px}
.release ul{margin:0; padding-left:1.2em; display:flex; flex-direction:column; gap:6px; font-size:14px; line-height:1.5}

/* ---------- Phase 1 ---------- */
/* Outcome messages. The mock-up had no alert style; a pill at 11.5px was too easy to miss. */
.notice{
  margin:0 0 var(--s4); padding:10px 14px; border-radius:var(--radius); font-size:13.5px;
  font-family:var(--font-data); background:var(--white); border:1px solid var(--hair);
  border-left:4px solid var(--sage); box-shadow:var(--shadow-1);
}
.notice.ok{border-left-color:var(--ok); color:var(--ok)}
.notice.owed{border-left-color:var(--owed); color:var(--owed)}
a.name{text-decoration:none}
a.name:hover{text-decoration:underline}
/* The board card is a link now, not a button — it goes somewhere. */
a.gcard{text-decoration:none; color:inherit}
a.gcard .pill{margin-top:6px}
/* An inline editor sits above the table it edits. */
.editor{margin-bottom:var(--s5)}
.editor h3{margin-bottom:var(--s3)}
.formgrid label.check{flex-direction:row; align-items:center; gap:8px; align-self:end; padding-bottom:7px; white-space:nowrap}
.formgrid label.check input{width:auto}
/* Dates, money and names never wrap: the table scrolls inside .tablewrap instead. */
td.num,td .name,td.nowrap{white-space:nowrap}
.formgrid .span2{grid-column:1 / -1}
.headstats{display:flex; gap:var(--s5); align-items:center; flex-wrap:wrap; margin-bottom:var(--s5)}
.headstats > div + div{border-left:1px solid var(--hair); padding-left:var(--s5)}
.headstats .big{font-size:15px}
@media (max-width:760px){
  .headstats{gap:var(--s4)}
  .headstats > div + div{border-left:0; padding-left:0}
}

/* ---------- Phase 1c: attendance and money ---------- */
/* A pill that is a button keeps the pill's look, not the browser's grey. */
button.pill{background:transparent; cursor:pointer; font:inherit; font-size:11.5px}
button.pill:hover{border-color:var(--sage)}
/* A cell whose save did not land. The letter shows what was asked for; the dashes say it
   is not yet on the server. */
.cell.unsaved{border-style:dashed; border-color:var(--owed); color:var(--owed)}
/* Two different meanings shared this selector. An attendance cell is disabled for the
   moment its save is in flight — transient, and nobody reads a cell mid-flight, so fading
   is right there. A locked matrix cell is disabled permanently and is meant to be read,
   which is why it is excluded and styled above. */
.cell:disabled:not(.locked){cursor:default; opacity:.6}
.att th.wk .sub{display:block; font-weight:400; letter-spacing:0; text-transform:none}
tr.total td{background:color-mix(in srgb,var(--blush) 12%,transparent); font-weight:600}
/* A staff member's hourly rate, edited in its own table cell on Users. */
input.rate{width:7.5rem; text-align:right}

/* ---------- Phase 2: guest speakers and allocation ---------- */
/* A ticked set inside a form — categories, and the halls she travels to. The fieldset
   carries the grouping for a screen reader; the border is the card's, not the browser's. */
.pickset{border:0; padding:0; margin:var(--s5) 0 0}
.pickset legend{padding:0; margin-bottom:var(--s3)}
.chips{display:flex; flex-wrap:wrap; gap:8px}
/* A chip is a pill you can tick. The native checkbox stays in the tab order and keeps its
   focus ring on the label, so this is a real control, not a div that listens for clicks. */
.chip{
  display:inline-flex; align-items:center; gap:6px; font-size:12.5px; cursor:pointer;
  padding:4px 11px; border-radius:var(--radius-pill); border:1px solid var(--control-border); background:transparent;
}
/* A ticked category chip takes its category's tint, so the form shows the same colour the
   card will. The tick itself still says it is chosen; colour is never the only cue. */
.chip.on[class*="tint-"]{background:var(--tint-bg); color:var(--tint-fg); border-color:var(--tint-fg)}
.chip input{margin:0; width:auto}
.chip.on{background:var(--blush); border-color:var(--blush-deep); color:var(--on-blush)}
.chip:hover{border-color:var(--sage)}
.chip:focus-within{outline:2px solid var(--sage); outline-offset:2px}
/* One hall per row: the tick, then the day constraint it applies to. */
.availrow{gap:var(--s4); align-items:center}
.availrow .check{display:flex; align-items:center; gap:8px; min-width:190px}
.availrow .check input{width:auto}
.availrow input[type=text]{flex:1; min-width:0}
@media (max-width:640px){
  .availrow{align-items:stretch; flex-direction:column; gap:6px}
  .availrow .check{min-width:0}
}
/* The allocation grid's cell picker, opened under the cell it is filling. */
.picker{display:grid; gap:6px; max-height:340px; overflow:auto; padding:2px}
/* --card was never defined and --r2 is a ramp COLOUR, so these were transparent with square
   corners — in dark the only boundary a speaker option had measured 1.26:1, which is no
   boundary. --control-border is the token for a control's edge. */
.picker .opt{
  display:block; width:100%; text-align:left; font:inherit; cursor:pointer;
  background:var(--white); border:1px solid var(--control-border); border-radius:var(--radius-s); padding:8px 10px;
}
.picker .opt:hover{border-color:var(--sage); background:color-mix(in srgb,var(--blush) 22%,transparent)}
.picker .opt .sub{display:block}
/* A booked cell reads as two lines: who, and what she is doing. */
.slot .sub{display:block; margin-top:2px}
.slot[aria-expanded=true]{outline:2px solid var(--sage); outline-offset:-2px}
.alloc td.locked .slot{cursor:default}
.alloc td.locked .slot:hover{background:transparent}
/* A speaker who would have to be in two places at once. Shown, not hidden — the workbook's
   failure was a double-booking nobody could see.
   No opacity: the reason is the one line here that must be read, and fading it to .62 put
   12.5px text at 2.6:1. It recedes through a dashed border and a flat ground instead. */
.picker .opt.clash{cursor:not-allowed; border-style:dashed; background:transparent}
.picker .opt.clash:hover{border-color:var(--hair); background:transparent}
.picker .opt.clash .name{color:var(--muted); font-weight:500}
/* The picker sits in a row spanning a grid that scrolls sideways; sticky keeps it against
   the visible left edge instead of stranding it under column one. */
.pickerbox{position:sticky; left:0; width:min(760px,100%); padding:var(--s2) 0}
/* The Acuity tick, in the cell rather than only inside the picker. */
.slot .acuity{color:var(--ok); margin-left:5px; font-size:12px}
.pickerbox:focus-visible{outline:2px solid var(--sage); outline-offset:3px}


/* ---------- development clock banner ----------
   Only ever rendered when Dev:Today is set. Deliberately plain and loud: its whole job is
   to stop a shifted clock being read as the real one. */
.devclock{
  background:var(--blush); color:var(--on-blush); border-bottom:1px solid var(--blush-deep);
  font-family:var(--font-data); font-size:12.5px; line-height:1.5;
  padding:var(--s2) var(--s5); display:flex; gap:var(--s3); flex-wrap:wrap; align-items:baseline;
}
.devclock b{font-weight:600; letter-spacing:.04em; text-transform:uppercase; font-size:11px}
/* --on-blush is near-black in both themes; --muted is not, and on this blush ground in
   dark it reads at about 2:1. The whole banner stays on one colour. */
.devclock .sub{color:inherit; opacity:.75}
.devclock code{font-family:var(--font-data); font-size:12px}
/* On a phone the explanation costs a third of the screen. The two dates are the part that
   stops a shifted clock being misread; the rest is for whoever set it. */
@media (max-width:720px){
  .devclock{padding:var(--s2) var(--s4)}
  .devclock .sub{display:none}
}

/* The one-time MCP token, shown once and never again. Monospace so a transposed character
   is visible, selectable in one go, and wrapping rather than scrolling because somebody
   is about to copy the whole thing. */
.token{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:14px;
  line-height:1.6;
  padding:var(--s3);
  border:1px solid var(--control-border);
  border-radius:var(--radius);
  background:var(--paper);
  word-break:break-all;
  user-select:all;
  margin-bottom:var(--s3);
}

/* Read by a screen reader, not shown. For a column whose header would otherwise be empty.
   In flow rather than position:absolute: inside a .tablewrap, which is not positioned, an
   absolute box escapes the scroll container and widens the whole page on a phone. */
.vh{display:inline-block; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap}

/* ---------- allocation: list and calendar views ---------- */
/* The list's date is a row header; the base th is an uppercase eyebrow, which a date is not. */
.alloclist tbody th{
  font-family:var(--font-data); font-size:12.5px; font-weight:400; letter-spacing:normal;
  text-transform:none; color:var(--ink); padding:6px 8px; border:1px solid var(--hair);
}
.alloclist td{vertical-align:middle; font-family:var(--font-data); font-size:12.5px}
.alloclist tbody th.today{background:color-mix(in srgb,var(--blush) 16%,transparent)}

.calhead{align-items:center; margin-bottom:var(--s3)}
.calhead h2{font-size:18px}
.cal{table-layout:fixed; min-width:720px; font-size:12.5px}
.cal th{border:1px solid var(--hair); padding:8px}
.cal td{border:1px solid var(--hair); vertical-align:top; padding:6px; height:92px}
.cal tbody tr:hover{background:transparent}
/* Days either side of the month recede by ground, not by fading. */
.cal td.out{background:var(--paper)}
.cal td.out .calday{color:var(--muted)}
.cal td.today{background:color-mix(in srgb,var(--blush) 16%,transparent)}
.cal td.today .calday{font-weight:600; color:var(--ink)}
.calday{display:block; margin-bottom:4px; font-family:var(--font-data); font-size:12px; color:var(--label)}
.calwd{display:none}
.calitem + .calitem{border-top:1px dashed var(--hair); margin-top:4px; padding-top:4px}
.calwhen{display:block; font-family:var(--font-data); font-size:11.5px; color:var(--muted)}

/* A phone gets the month as a day-by-day agenda: only the days that have sessions. */
@media (max-width:640px){
  .cal{min-width:0}
  .cal thead{display:none}
  .cal,.cal tbody,.cal tr,.cal td{display:block; width:auto}
  .cal td{height:auto; border-width:0 0 1px; padding:var(--s2) var(--s3)}
  .cal td.out,.cal td.none{display:none}
  .calwd{display:inline}
}


/* ---------- component gallery (development only) ---------- */
.gallery-rings{gap:var(--s6)}
.gallery-item{text-align:center}

/* ---------- guest speakers ---------- */
.chips.tight{gap:4px}
.bcol-empty{padding:var(--s3) var(--s1); text-align:center; border:1px dashed var(--hair); border-radius:var(--radius)}
/* A filter row above a list or board: controls left, the count right. */
.toolbar{display:flex; gap:var(--s3); align-items:center; flex-wrap:wrap; justify-content:space-between; margin-bottom:var(--s4)}
/* Stacked rows inside a card, divided by a hairline. */
.listrow{padding:var(--s2) 0; border-top:1px solid var(--hair)}
.listrow:first-child{border-top:0; padding-top:0}
/* Three statuses, three columns — auto-fit wrapped Current under Leads at 1280. */
@media (min-width:901px){.speakerboard{grid-template-columns:repeat(3,minmax(0,1fr))}}

/* ---------- users ---------- */
/* The staff list: one row per person, the whole row a link to their page. */
.peoplelist{
  list-style:none; margin:0; padding:0; background:var(--white);
  border:1px solid var(--hair); border-radius:var(--radius); box-shadow:var(--shadow-1); overflow:hidden;
}
.peoplelist li + li{border-top:1px solid var(--hair)}
.person{
  display:grid; grid-template-columns:auto minmax(0,1fr) 130px 104px 150px auto; gap:var(--s4); align-items:center;
  padding:var(--s3) var(--s4); text-decoration:none; color:inherit;
}
.person:hover{background:color-mix(in srgb,var(--blush) 12%,transparent)}
.person:hover .name{text-decoration:underline}
.person .who{display:flex; flex-direction:column; min-width:0}
.person .pill{justify-self:start}
.person .who .sub{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.person .when{text-align:right}
.person .chev{font-size:20px; line-height:1; color:var(--muted)}
.avatar{
  width:36px; height:36px; border-radius:50%; display:inline-grid; place-items:center; flex:0 0 auto;
  background:var(--tint-bg); color:var(--tint-fg); font-family:var(--font-data); font-weight:600; font-size:13px;
}
.avatar.lg{width:56px; height:56px; font-size:19px}
.profilehead{display:flex; gap:var(--s3); align-items:center; flex-wrap:wrap; margin:calc(-1 * var(--s2)) 0 var(--s5)}
.danger-zone{border-color:color-mix(in srgb,var(--owed) 30%,var(--hair))}
@media (max-width:760px){
  .person{grid-template-columns:auto minmax(0,1fr) auto; row-gap:var(--s1)}
  .person .pill{grid-column:2; justify-self:start}
  .person .when{grid-column:2; text-align:left}
  .person .chev{grid-row:1; grid-column:3}
}

/* ---------- to-dos ---------- */
.thead{display:flex; justify-content:space-between; align-items:flex-start; gap:var(--s3); flex-wrap:wrap; margin-bottom:var(--s3)}
.todo{border-radius:var(--radius-s)}
.todo .tactions .btn{border-color:transparent; color:var(--muted)}
.todo .tactions .btn:hover{border-color:var(--control-border); color:var(--ink); background:var(--paper)}
.todo .tactions .btn.bell.on{color:var(--warn)}
.todo.editing .ttitle{color:var(--ink)}
.todo .ttitle:focus-visible{outline-offset:3px}
a.ttitle{text-decoration:none; color:inherit; display:block}
a.ttitle:hover{text-decoration:underline}
.tick .icon{width:13px; height:13px}
/* The editor opens inside the list, indented under the row it edits. */
.todo-editor{
  margin:0 0 var(--s3) calc(22px + var(--s3)); padding:var(--s4);
  background:color-mix(in srgb,var(--paper) 70%,var(--white)); border:1px solid var(--hair);
  border-left:3px solid var(--sage-deep); border-radius:var(--radius);
}
.board3 + .todo-editor,.todo-editor:has(+ .board3){margin-left:0}
.todo-editor + .board3{margin-top:var(--s4)}
/* A read-out's status: a ring, filled amber when started, rust when late. Decorative —
   the pills beside it say the same thing in words. */
.tdot{
  width:12px; height:12px; flex:0 0 12px; margin-top:5px; border-radius:50%;
  border:2px solid var(--control-border);
}
.tdot.going{border-color:var(--warn); background:color-mix(in srgb,var(--warn) 35%,transparent)}
.tdot.late{border-color:var(--owed)}
/* Who holds it: initials and a name, rather than "→ Rashelle". */
.whochip{display:inline-flex; align-items:center; gap:5px; font-family:var(--font-data); font-size:12px; color:var(--muted)}
.avatar.xs{width:18px; height:18px; font-size:9px}
/* The sidebar's progress: decoration under the name; the x/y beside it is what is read. */
.tl-bar{display:block; height:4px; margin-top:6px; border-radius:var(--radius-pill); background:var(--ring-track); overflow:hidden}
.tl-bar i{display:block; height:100%; width:var(--p); background:var(--sage); border-radius:inherit}
.tlist.on .tl-bar i{background:var(--sage-deep)}
.tlist .tl-main{min-width:0; flex:1}
/* The board: cards with the same tick, and arrows to move them as on the speakers board. */
@media (min-width:901px){.todoboard{grid-template-columns:repeat(3,minmax(0,1fr))}}
.todocard{cursor:grab}
.todocard[draggable="false"]{cursor:default}
.todocard.dragging{border:1px dashed var(--control-border); background:var(--paper); box-shadow:none}
.todocard .tmeta{display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap; margin-top:var(--s2)}
.tcard-head{display:flex; gap:var(--s2); align-items:flex-start}
.tcard-head .ttitle{padding-top:2px}
.tcard-foot{display:flex; justify-content:space-between; align-items:center; margin-top:var(--s2)}
.tcard-foot .movers{display:flex; gap:2px}
.tcard-foot .btn{border-color:transparent; color:var(--muted)}
.tcard-foot .btn:hover:not(:disabled){border-color:var(--control-border); color:var(--ink); background:var(--paper)}
.todoboard .bcol.dropping{outline:2px solid var(--control-border); outline-offset:-1px}
@media (max-width:760px){.todo-editor{margin-left:0}}

/* ---------- dashboards ---------- */
.stat b.owed{color:var(--owed)}
.stat b.warn{color:var(--warn)}
.stat.tile{display:flex; flex-direction:column; gap:2px; min-height:118px}
.stat.tile small{margin-top:auto; padding-top:var(--s2)}
a.stat.tile{text-decoration:none; color:inherit; transition:box-shadow .15s, border-color .15s}
a.stat.tile:hover{box-shadow:var(--shadow-2); border-color:var(--control-border)}
a.stat.tile:hover small{text-decoration:underline}
/* How far along: decoration under the figure; the sub-line says it in words. */
.meter{display:block; height:6px; margin-top:var(--s2); border-radius:var(--radius-pill); background:var(--ring-track); overflow:hidden}
.meter i{display:block; height:100%; width:var(--p); background:var(--sage); border-radius:inherit}
@media (prefers-reduced-motion:reduce){a.stat.tile{transition:none}}
a.linkpill{text-decoration:none; color:var(--ink)}
a.linkpill:hover{border-color:var(--sage); text-decoration:underline}
/* The week, one column a day. A session without a speaker carries a rust edge as well as
   its "No speaker" pill — the edge draws the eye, the words say why. */
.weekstrip{
  list-style:none; margin:0; padding:0; display:grid; gap:var(--s3);
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
}
.wsday{
  background:var(--white); border:1px solid var(--hair); border-radius:var(--radius);
  padding:var(--s3); box-shadow:var(--shadow-1); min-width:0;
}
.wsday.today{border-color:var(--sage-deep); box-shadow:0 0 0 1px var(--sage-deep), var(--shadow-1)}
.wsday.empty{background:transparent; box-shadow:none; border-style:dashed}
.wshead{display:flex; justify-content:space-between; align-items:baseline; margin-bottom:var(--s2)}
.wshead b{font-family:var(--font-data); font-size:13px; font-weight:600}
.wsday ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s2)}
.wsitem{
  display:flex; flex-direction:column; gap:2px; padding:var(--s2) var(--s2) var(--s2) 10px;
  border-left:3px solid var(--sage); border-radius:4px; background:color-mix(in srgb,var(--paper) 60%,var(--white));
}
.wsitem.missing{border-left-color:var(--owed)}
.wsitem .name{font-size:13.5px; text-decoration:none}
.wsitem a.name:hover,.wsitem a.speaker:hover{text-decoration:underline}
.wsitem .speaker{color:var(--ink); text-decoration:none}
.wsitem .pill{align-self:flex-start; margin-top:2px}
/* The reader's own session: a blush ground and a "You're facilitating" chip — the chip is
   the information, the ground only helps it stand out down a long phone column. 14% keeps
   --muted at AA on it in dark, where --blush stays light over a dark card. */
.wsitem.mine{background:color-mix(in srgb,var(--blush) 14%,var(--white)); box-shadow:inset 0 0 0 1px var(--blush-deep)}
/* The "No speaker" pill's 9% rust tint over that blush ground measured 4.26:1 in dark. On the
   plain card ground it is 5.9:1 light and 6.4:1 dark. */
.wsitem.mine .pill.owed{background:var(--white)}
/* Somebody else's session on a facilitator's dashboard: shown so she knows what is on, not a
   link. It recedes by a dashed edge and a lighter weight, never by opacity — the line saying
   whose it is has to be read. */
.wsitem.closed{background:transparent; border:1px dashed var(--control-border); padding-left:var(--s2)}
.wsitem.closed .name{font-weight:500; color:var(--muted)}
@media (max-width:760px){
  .weekstrip{grid-template-columns:1fr}
  .wsday.empty{display:none}
}

/* Circles not making money: a losing row carries a faint ground of the owed hue and a rule at
   its start, so the worst read as the worst down the column. The pill says why in words. */
tr.loss > td{background:color-mix(in srgb,var(--owed) 6%,transparent)}
tr.loss > td:first-child{box-shadow:inset 3px 0 0 var(--owed)}

/* ---------- the phone pass (October 2026) ----------
   Facilitators open this standing in a hall with a baby on one arm, so a phone is their main
   screen. What changed, and why: */
@media (max-width:760px){
  /* iOS zooms the whole page into any field under 16px on focus, and leaves it zoomed. */
  input[type=search],input[type=text],input[type=email],input[type=tel],input[type=number],
  input[type=date],input[type=time],input[type=password],input:not([type]),select,textarea{font-size:16px}
  /* Tap targets. 28px buttons and 22px toggles are a desktop pointer's size, not a thumb's. */
  .btn:not(.icon){min-height:40px}
  .btn.sm:not(.icon){min-height:36px; padding:6px 12px}
  .btn.icon.sm{width:36px; height:36px}
  .themectl button{padding:7px 12px; font-size:13px; min-height:36px}
  .tab{padding:8px 0 12px}
  button.catpill{min-height:32px}
  .tick{width:30px; height:30px; flex-basis:30px}
  .userbox .btn{padding:7px 14px; font-size:13px}
  .userbox .relnotes{display:inline-block; padding:6px 0}
  .msheet .sheetfoot .userbox{flex:1 1 auto}
  /* Four stat tiles stacked one per row cost a whole screen before "This week". Two by two,
     and a card's padding steps down with the screen. */
  .g4{grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--s3)}
  .stat.tile{min-height:0}
  .card{padding:var(--s4)}
  /* Twenty-odd circles on the board: a tighter card, so more than three fit a screen. */
  .gcard{padding:var(--s3) var(--s4); gap:var(--s3)}
  /* A wide table keeps its first column — whose row it is — while the rest scrolls. Opt-in,
     because not every table's first column is the one that names the row. */
  table.sticky1 thead th:first-child,
  table.sticky1 tbody td:first-child{position:sticky; left:0; z-index:1; background:var(--white); box-shadow:1px 0 0 var(--hair)}
  /* Opaque versions of the header ground and the row-hover tint, so the cell still matches its
     row while the others scroll under it. */
  table.sticky1 thead th:first-child{background:color-mix(in srgb,var(--paper) 55%,var(--white))}
  table.sticky1 tbody tr:hover td:first-child{background:color-mix(in srgb,var(--blush) 12%,var(--white))}
  /* A column nobody needs on a phone. The information is a tap away on the desktop view. */
  .hide-sm{display:none}
  td.topic{min-width:200px}
  /* Somebody else's session on a facilitator's week: where and when is enough. */
  .wsitem.closed .speaker{display:none}
  .release-head h2{font-size:17px}
}

/* ---------- your account ---------- */
/* The two Identity pages under /Account/Manage, re-dressed in the app's own fields: the
   scaffold's Bootstrap markup rendered unstyled, because Bootstrap was never loaded. */
.accountform{display:flex; flex-direction:column; gap:var(--s3); max-width:420px; margin-top:var(--s3)}
.accountform .field input{width:100%}
.accountform .btn{align-self:flex-start}
ul.notice{list-style:none}

/* ---------- term track and location rows (/circles) ---------- */
/* One lane for the whole term, weekends and all; a block inside it per session week; a
   marker for today. Positions are percentages computed by TermTrackGeometry. */
.termtrack{margin:0 0 var(--s4); padding:var(--s3) var(--s4) var(--s2); background:var(--white);
  border:1px solid var(--hair); border-radius:var(--radius); box-shadow:var(--shadow-1)}
.termtrack .head{display:flex; justify-content:space-between; align-items:baseline; gap:var(--s1) var(--s3); flex-wrap:wrap; margin-bottom:var(--s1)}
.termtrack .head b{font-weight:600}
.termtrack .lane{position:relative; height:20px; margin:30px 0 var(--s1); border-radius:var(--radius-pill);
  background:var(--ring-track); box-shadow:inset 0 0 0 1px var(--control-border)}
.termtrack .elapsed{position:absolute; inset:0 auto 0 0; border-radius:var(--radius-pill);
  background:color-mix(in srgb, var(--sage) 35%, transparent)}
.termtrack .wk{position:absolute; top:4px; bottom:4px; min-width:4px; border-radius:var(--radius-pill); overflow:hidden;
  background:var(--white); box-shadow:inset 0 0 0 1px var(--control-border)}
.termtrack .wk.done{background:var(--sage-deep); box-shadow:none}
.termtrack .wk .part{position:absolute; inset:0 auto 0 0; background:var(--sage-deep)}
.termtrack .today{position:absolute; top:-8px; bottom:-4px; width:2px; margin-left:-1px; background:var(--ink)}
.termtrack .today i{position:absolute; bottom:100%; left:50%; transform:translate(-50%,-2px); white-space:nowrap;
  font-style:normal; font-size:11px; font-weight:600; background:var(--ink); color:var(--white);
  padding:1px 7px; border-radius:var(--radius-pill)}
.termtrack .today.near-start i{left:-1px; transform:translate(0,-2px)}
.termtrack .today.near-end i{left:auto; right:-1px; transform:translate(0,-2px)}
.termtrack .nums{position:relative; height:16px}
.termtrack .nums span{position:absolute; transform:translateX(-50%); font-size:11px; color:var(--muted); font-family:var(--font-data)}
.termtrack .nums span.now{color:var(--ink); font-weight:600}
.termtrack .ends{display:flex; justify-content:space-between; font-size:11px; color:var(--muted)}
/* A term's circles, one row per location. The row names the place, so each card leads
   with when it meets. */
.locrow{padding:var(--s3) 0 var(--s4); border-top:1px solid var(--hair)}
.locrow:first-of-type{border-top:0; padding-top:0}
.locrow .lochead{display:flex; align-items:baseline; gap:var(--s1) var(--s3); flex-wrap:wrap; margin-bottom:var(--s3)}
.locrow .lochead h3{font-size:16px}
.gcard h4{font-family:var(--font-body); font-size:15px; font-weight:600; line-height:1.35; margin:0}

/* ---------- utilities ----------
   Last in the file on purpose, so a utility always beats a component rule of the same
   weight. Small, named, and few. They exist to retire inline style="" attributes — 253 of them, most
   a margin-top or a colour — so spacing comes from the scale rather than from whoever typed. */
.m0{margin:0}
.mt0{margin-top:0} .mt1{margin-top:var(--s1)} .mt2{margin-top:var(--s2)} .mt3{margin-top:var(--s3)}
.mt4{margin-top:var(--s4)} .mt5{margin-top:var(--s5)} .mt6{margin-top:var(--s6)} .mt7{margin-top:var(--s7)}
.mb0{margin-bottom:0} .mb1{margin-bottom:var(--s1)} .mb2{margin-bottom:var(--s2)} .mb3{margin-bottom:var(--s3)}
.mb4{margin-bottom:var(--s4)} .mb5{margin-bottom:var(--s5)} .mb6{margin-bottom:var(--s6)} .mb7{margin-bottom:var(--s7)}
.my3{margin:var(--s3) 0} .my4{margin:var(--s4) 0} .my5{margin:var(--s5) 0}
.ml2{margin-left:var(--s2)}
.gap0{gap:0} .gap1{gap:var(--s1)} .gap2{gap:var(--s2)} .gap3{gap:var(--s3)} .gap4{gap:var(--s4)}
.gap5{gap:var(--s5)} .gap6{gap:var(--s6)} .gap7{gap:var(--s7)}
.fs14{font-size:14px}
.muted{color:var(--muted)}
.ok-text{color:var(--ok)}
.normal{font-weight:400}
.mr2{margin-right:var(--s2)}
.wrap{flex-wrap:wrap}
.between{justify-content:space-between}
.end{align-items:flex-end}
.owing{color:var(--owed); font-weight:600}
.flat{border:0; box-shadow:none}
.start{align-items:start}
.grow{flex:1 1 auto; min-width:0}
.block{display:block}
.nowrap{white-space:nowrap}
.stat b.ok{color:var(--ok)}
