/* app.css — four palettes, each a light/dark pair. A palette declares --l-* (light) and --d-* (dark);
   the mode switch below maps one of those sets onto the live tokens, so themes and modes compose:
   [data-theme] picks the palette, [data-mode] forces light/dark, absent mode follows the OS. */

/* Register — school stationery: manila board, form paper, blue-black ink, one red stamp colour. Default. */
:root, :root[data-theme="register"] {
  --l-paper:#efeae0; --l-card:#fbf9f4; --l-card-2:#f3efe4; --l-band:#e5dece;
  --l-ink:#1b2026; --l-ink-2:#5f5a50; --l-ink-3:#857f71; --l-rule:#ddd5c6; --l-rule-2:#c3b9a3;
  --l-stamp:#9e2b25; --l-ochre:#7d5a12; --l-accent:#23507a; --l-green:#2c6a45;
  --l-shadow:0 1px 0 rgba(27,32,38,.05);
  --d-paper:#14161a; --d-card:#1e2329; --d-card-2:#262c33; --d-band:#212730;
  --d-ink:#e9e5db; --d-ink-2:#a9a398; --d-ink-3:#877f73; --d-rule:#2b3138; --d-rule-2:#3d454f;
  --d-stamp:#e0776a; --d-ochre:#c79a3e; --d-accent:#8ab8e0; --d-green:#6fae87;
  --d-shadow:none;
}
/* High contrast — built for low vision: AAA body text, borders instead of tints, shape-coded urgency. */
:root[data-theme="contrast"] {
  --l-paper:#ffffff; --l-card:#ffffff; --l-card-2:#ebebeb; --l-band:#dedede;
  --l-ink:#000000; --l-ink-2:#1a1a1a; --l-ink-3:#3a3a3a; --l-rule:#767676; --l-rule-2:#4a4a4a;
  --l-stamp:#b00000; --l-ochre:#6b4b00; --l-accent:#0b3d91; --l-green:#005c2e;
  --l-shadow:none;
  --d-paper:#000000; --d-card:#000000; --d-card-2:#1c1c1c; --d-band:#2e2e2e;
  --d-ink:#ffffff; --d-ink-2:#ededed; --d-ink-3:#c8c8c8; --d-rule:#8a8a8a; --d-rule-2:#b4b4b4;
  --d-stamp:#ff6b5e; --d-ochre:#ffc857; --d-accent:#7fb7ff; --d-green:#6fe0a0;
  --d-shadow:none;
}
/* Blueprint — cool drafting paper by day, a blueprint by night. */
:root[data-theme="blueprint"] {
  --l-paper:#e9eef3; --l-card:#f9fbfd; --l-card-2:#eef3f8; --l-band:#d6e1ea;
  --l-ink:#13212e; --l-ink-2:#465868; --l-ink-3:#6c7f92; --l-rule:#ccd7e1; --l-rule-2:#a9bacb;
  --l-stamp:#a03227; --l-ochre:#7a5a10; --l-accent:#1b4f8a; --l-green:#1f6b52;
  --l-shadow:0 1px 0 rgba(19,33,46,.05);
  --d-paper:#0c1622; --d-card:#152634; --d-card-2:#1d3040; --d-band:#16293a;
  --d-ink:#e0ebf5; --d-ink-2:#9db4c9; --d-ink-3:#7890a6; --d-rule:#23394d; --d-rule-2:#33526c;
  --d-stamp:#ff8272; --d-ochre:#d9a64a; --d-accent:#6fb4f5; --d-green:#63c39a;
  --d-shadow:none;
}
/* Chalkboard — notebook paper by day, a green board and chalk by night. */
:root[data-theme="chalk"] {
  --l-paper:#eef0ea; --l-card:#fdfdfa; --l-card-2:#f1f3ec; --l-band:#dfe4d8;
  --l-ink:#172019; --l-ink-2:#515c55; --l-ink-3:#78827b; --l-rule:#d8ddd3; --l-rule-2:#b7c0b2;
  --l-stamp:#9c2f2a; --l-ochre:#7a5c14; --l-accent:#2a5f5a; --l-green:#2f6b3e;
  --l-shadow:0 1px 0 rgba(23,32,25,.05);
  --d-paper:#16231e; --d-card:#1f3129; --d-card-2:#273b32; --d-band:#21352c;
  --d-ink:#eef3ea; --d-ink-2:#b0c0b4; --d-ink-3:#8a9a8e; --d-rule:#2b3e35; --d-rule-2:#41584c;
  --d-stamp:#f2897d; --d-ochre:#f0cd70; --d-accent:#8fd0c4; --d-green:#9fd6a0;
  --d-shadow:none;
}

/* mode switch: light is the base, dark applies under the OS preference unless light is forced,
   and [data-mode="dark"] forces it outright */
:root {
  --paper:var(--l-paper); --card:var(--l-card); --card-2:var(--l-card-2); --band:var(--l-band);
  --ink:var(--l-ink); --ink-2:var(--l-ink-2); --ink-3:var(--l-ink-3); --rule:var(--l-rule); --rule-2:var(--l-rule-2);
  --stamp:var(--l-stamp); --ochre:var(--l-ochre); --accent:var(--l-accent); --green:var(--l-green);
  --shadow:var(--l-shadow); --focus:var(--l-accent);
  color-scheme:light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) {
    --paper:var(--d-paper); --card:var(--d-card); --card-2:var(--d-card-2); --band:var(--d-band);
    --ink:var(--d-ink); --ink-2:var(--d-ink-2); --ink-3:var(--d-ink-3); --rule:var(--d-rule); --rule-2:var(--d-rule-2);
    --stamp:var(--d-stamp); --ochre:var(--d-ochre); --accent:var(--d-accent); --green:var(--d-green);
    --shadow:var(--d-shadow); --focus:var(--d-accent);
    color-scheme:dark;
  }
}
:root[data-mode="dark"] {
  --paper:var(--d-paper); --card:var(--d-card); --card-2:var(--d-card-2); --band:var(--d-band);
  --ink:var(--d-ink); --ink-2:var(--d-ink-2); --ink-3:var(--d-ink-3); --rule:var(--d-rule); --rule-2:var(--d-rule-2);
  --stamp:var(--d-stamp); --ochre:var(--d-ochre); --accent:var(--d-accent); --green:var(--d-green);
  --shadow:var(--d-shadow); --focus:var(--d-accent);
  color-scheme:dark;
}

/* Kid colours: keys, not hex, so appearance stays in CSS. Six hues spread around the wheel AND
   stepped in lightness, so they survive a colour-vision deficiency and a washed-out screen. Light and
   dark grounds get their own ramp; the contrast palette overrides all six for its white/black grounds.
   Kids are the only facet with a fill — nothing else in the UI may take one. */
:root {
  --kid-amber:#c07800; --kid-moss:#5c8a24; --kid-teal:#0d7a72;
  --kid-rose:#b32d55; --kid-violet:#6a3a9e; --kid-indigo:#23417c;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) {
    --kid-amber:#edb14f; --kid-moss:#a9d478; --kid-teal:#4ec9ba;
    --kid-rose:#f4849f; --kid-violet:#bd8ae8; --kid-indigo:#7fa9f2;
  }
}
:root[data-mode="dark"] {
  --kid-amber:#edb14f; --kid-moss:#a9d478; --kid-teal:#4ec9ba;
  --kid-rose:#f4849f; --kid-violet:#bd8ae8; --kid-indigo:#7fa9f2;
}
:root[data-theme="contrast"] {
  --kid-amber:#8a5000; --kid-moss:#3f6b12; --kid-teal:#00615c;
  --kid-rose:#9b1340; --kid-violet:#532a86; --kid-indigo:#123268;
}
@media (prefers-color-scheme: dark) {
  :root[data-theme="contrast"]:not([data-mode="light"]) {
    --kid-amber:#ffc357; --kid-moss:#b6e77f; --kid-teal:#57e0cf;
    --kid-rose:#ff9ab4; --kid-violet:#cfa4ff; --kid-indigo:#8fb8ff;
  }
}
:root[data-theme="contrast"][data-mode="dark"] {
  --kid-amber:#ffc357; --kid-moss:#b6e77f; --kid-teal:#57e0cf;
  --kid-rose:#ff9ab4; --kid-violet:#cfa4ff; --kid-indigo:#8fb8ff;
}
[data-kid-color="amber"] { --kid:var(--kid-amber); }
[data-kid-color="moss"] { --kid:var(--kid-moss); }
[data-kid-color="teal"] { --kid:var(--kid-teal); }
[data-kid-color="rose"] { --kid:var(--kid-rose); }
[data-kid-color="violet"] { --kid:var(--kid-violet); }
[data-kid-color="indigo"] { --kid:var(--kid-indigo); }

/* metrics. --ts scales text only; bands and tap targets keep their pixel heights so the sticky
   offset stays correct at every text size. */
:root {
  --ts:1;
  --fs-tab:calc(15.5px * var(--ts)); --fs-title:calc(15px * var(--ts)); --fs-body:calc(13.5px * var(--ts));
  --fs-meta:calc(11.5px * var(--ts)); --fs-chip:calc(12.5px * var(--ts)); --fs-btn:calc(13px * var(--ts));
  --fs-head:calc(12px * var(--ts)); --fs-label:calc(11px * var(--ts));
  --kidbar-h:52px; --head-h:calc(53px + env(safe-area-inset-top));
  --gutter:16px; --radius:9px; --tap:44px;
  /* The action row's height, which a clipped body's ghost tail runs into. NOT --band: that is the
     header's colour token, and naming this length --band made `background:var(--band)` invalid, so
     the sticky kid tabs went transparent once anything scrolled beneath them. */
  --action-h:44px;
  /* jog, pause, zip: nudge the card right and pause (the window to tap it back), zip it off the
     right edge, and only then fold the empty row away. The exit is the zip; the collapse is the
     aftermath. Not a strikeout: the operator read that as a visual artifact. */
  --jog:100ms; --hold:225ms; --zip:150ms; --collapse:150ms;
}
:root[data-size="large"] { --ts:1.15; }
:root[data-size="larger"] { --ts:1.3; }

*, *::before, *::after { box-sizing:border-box; }
/* The browser's own [hidden] rule is in the user-agent stylesheet, so ANY author rule that sets
   display beats it and the attribute silently does nothing. That is how the sign-in card stayed on
   screen after signing in: .join sets display:grid. Five separate ad-hoc patches for this had already
   accumulated below; this is the one rule that means hidden always hides. */
[hidden] { display:none !important; }
html, body { max-width:100%; overscroll-behavior-y:contain; }
body {
  margin:0; background:var(--paper); color:var(--ink);
  font:calc(15px * var(--ts))/1.42 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust:100%;
}
.sprite { position:absolute; width:0; height:0; overflow:hidden; }
.sr { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
.i { width:20px; height:20px; flex:0 0 auto; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; }
:focus-visible { outline:2px solid var(--focus); outline-offset:2px; border-radius:4px; }
button { font:inherit; color:inherit; -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; touch-action:manipulation; }

/* top level — the kid tabs, with just enough room kept at the right for the account button */
.head {
  position:sticky; top:0; z-index:5; display:flex; align-items:flex-end; gap:2px;
  padding-top:env(safe-area-inset-top); background:var(--band); border-bottom:1px solid var(--rule-2);
}
.head-in { display:flex; align-items:flex-end; gap:2px; width:100%; max-width:852px; margin:0 auto; padding:0 4px 0 8px; }
.iconbtn { flex:0 0 auto; display:flex; align-items:center; justify-content:center; width:var(--tap); height:var(--tap); padding:0; border:0; background:none; border-radius:8px; cursor:pointer; color:var(--ink-2); }
#user-btn { margin-bottom:3px; }
.iconbtn:hover { color:var(--ink); }

/* folder tabs: the selected one is cut out of the band in the list's own colour and breaks the rule
   beneath it, so it reads as attached to what is below */
.kidbar { flex:1 1 auto; min-width:0; display:flex; align-items:flex-end; gap:3px; height:var(--kidbar-h); }
.kidbar button {
  position:relative; flex:1 1 0; min-width:0; display:flex; align-items:center; justify-content:center; gap:4px;
  height:40px; margin-bottom:-1px; padding:0 4px; background:none; cursor:pointer;
  border:1px solid transparent; border-bottom:1px solid var(--rule-2); border-radius:9px 9px 0 0;
  color:var(--ink-2); font-size:var(--fs-tab); font-weight:500;
}
.kidbar button::before { content:""; position:absolute; inset:-3px 0 -4px; }
.kidbar button .kn { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The tab wears the CHILD's colour, not the theme accent. It used to wear --accent, which is one
   colour for the whole household, so the amber rail down a card had nothing to point back to and the
   colours never connected. Every tab carries it, not just the selected one: you cannot learn that
   Eleanor is teal from a control that shows one child's colour at a time. Unselected is dimmed, and
   the selection is still carried by height, background and weight, so colour is never the only
   signal. 5px on the selected tab and 3px elsewhere: at 3px against the band it read as a hairline
   rather than as a colour. The All tab has no child, so it keeps the theme accent. */
.kidbar button { box-shadow:inset 0 3px 0 color-mix(in srgb, var(--kid, var(--accent)) 55%, transparent); }
.kidbar button[aria-selected="true"] {
  flex:1.3 1 0; height:var(--kidbar-h); background:var(--paper); border-color:var(--rule-2); border-bottom-color:var(--paper);
  box-shadow:inset 0 5px 0 var(--kid, var(--accent)); color:var(--ink); font-weight:700;
}
.kav { flex:0 0 auto; width:19px; height:19px; border-radius:50%; object-fit:cover; background:var(--card-2); border:1px solid var(--rule-2); }
.kinit { display:flex; align-items:center; justify-content:center; font-size:calc(10px * var(--ts)); font-weight:700; color:var(--ink-2); }
/* the ring puts the colour on the face itself, which is the thing a reader actually identifies */
.kidbar button[data-kid-color] .kav { border-width:2px; border-color:color-mix(in srgb, var(--kid) 60%, transparent); }
.kidbar button[aria-selected="true"] .kav { border-color:var(--kid, var(--accent)); }
.kidbar .n { font-size:calc(12px * var(--ts)); font-variant-numeric:tabular-nums; color:var(--ink-3); }
.kidbar .n.due { color:var(--stamp); font-weight:700; }
.kidbar .n.cleared { display:flex; color:var(--green); }
.kidbar .n.cleared .i { width:15px; height:15px; stroke-width:2.6; }

main { max-width:852px; margin:0 auto; padding:0 var(--gutter) 32px; }

/* band 3 — filters: on the list surface, closed by a hairline so it reads as its own strip.
   Collapsed, it keeps only the active chips on one contained scroller, with the control bottom right. */
.filterpane { display:flex; flex-wrap:wrap; align-items:flex-end; gap:0 6px; padding:12px 0 10px; border-bottom:1px solid var(--rule); }
/* row gap 12 to 10 with the chip at 30: the two together are the 40px tap pitch, and the hit
   areas of neighbouring rows meet without overlapping */
.filterbar { flex:1 1 100%; min-width:0; display:flex; flex-wrap:wrap; align-items:center; gap:10px 5px; }
/* the tail is chrome, not facets: expanded it gets its own strip under the chips behind a hairline,
   collapsed it sits at the end of the row behind a divider. Either way it is label type, not pills,
   so nothing here can be mistaken for something that sets a filter. */
.fbar-tail { flex:1 1 100%; display:flex; align-items:center; justify-content:flex-end; gap:16px; margin-top:7px; padding-top:7px; border-top:1px solid var(--rule); }
.filterpane.collapsed .fbar-tail { flex:0 0 auto; margin-top:0; padding:0 0 0 10px; border-top:0; border-left:1px solid var(--rule); align-self:stretch; }
.filterpane.collapsed { padding:5px 0 6px; }
.filterpane.collapsed .filterbar {
  flex:1 1 0; flex-wrap:nowrap; gap:5px; overflow-x:auto; overscroll-behavior-x:contain; scroll-padding-inline:8px;
  scrollbar-width:none; padding:3px 0;
}
.filterpane.collapsed .filterbar::-webkit-scrollbar { display:none; }
.filterpane.collapsed .flabel { display:none; }
/* the search box: one phrase, its own row above the chips, its own clear button at a 40px target.
   The native cancel glyph is hidden because it is tiny and not on every browser. */
.fsearch { position:relative; flex:1 1 100%; display:flex; align-items:center; margin-bottom:10px; }
.fsearch input {
  flex:1 1 auto; min-width:0; min-height:40px; padding:0 44px 0 14px; border:1px solid var(--rule-2); border-radius:999px;
  background:var(--card); color:var(--ink); font:inherit; font-size:var(--fs-chip); -webkit-appearance:none; appearance:none;
}
.fsearch input::placeholder { color:var(--ink-3); }
.fsearch input::-webkit-search-cancel-button, .fsearch input::-webkit-search-decoration { -webkit-appearance:none; appearance:none; }
.fsearch .fclear { position:absolute; right:2px; top:50%; transform:translateY(-50%); display:inline-flex; align-items:center; justify-content:center; width:40px; height:40px; border:0; background:none; color:var(--ink-3); cursor:pointer; }
.filterpane.collapsed .fsearch { display:none; }
.filterpane:not(.collapsed) .qchip { display:none; } /* expanded, the box itself shows the phrase */
.qchip .cl { text-transform:none; }
/* a search hit: an ink underline over a faint ink wash, so it reads in every theme and never
   leans on a yellow fill */
mark.hit { background:color-mix(in srgb, var(--ink) 14%, transparent); color:inherit; text-decoration:underline; text-decoration-color:var(--ink); text-decoration-thickness:2px; text-underline-offset:2px; border-radius:2px; }
.filterpane.collapsed .flead { display:contents; } /* an emptied lead must not hold a gap in the row */
.filterpane.collapsed .filterbar .chip:not([data-active]) { display:none; }
/* the fade rides with the tail, so it always marks the scroller's right edge */
.filterpane.collapsed[data-overflow] .fbar-tail::before {
  content:""; flex:0 0 auto; align-self:stretch; width:24px; margin-left:-24px; pointer-events:none;
  background:linear-gradient(to right, rgba(0,0,0,0), var(--paper) 78%);
}
.fop {
  position:relative; display:inline-flex; align-items:center; gap:4px; min-height:30px; padding:0 1px;
  border:0; background:none; color:var(--ink-2);
  font-size:var(--fs-label); font-weight:700; text-transform:uppercase; letter-spacing:.08em;
  line-height:1; cursor:pointer; white-space:nowrap;
}
.fop::before { content:""; position:absolute; inset:-8px -6px; }
.fop:hover { color:var(--ink); }
.fop:hover .ft { text-decoration:underline; text-underline-offset:3px; }
.fop .i { width:13px; height:13px; stroke-width:2.6; }
.fop .n { font-variant-numeric:tabular-nums; letter-spacing:0; font-weight:600; color:var(--ink-3); }
#filters-toggle[aria-expanded="true"] .i { transform:rotate(180deg); }
#clear-filters .i { width:12px; height:12px; }
/* the group-by control arrived after the collapsed row was tuned and its label was eating the row:
   collapsed, the operations go icon-only and their words live on in their accessible names */
.filterpane.collapsed #groupby .ft, .filterpane.collapsed #clear-filters .ft { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
@media (max-width: 359px) { #clear-filters .ft, #groupby .ft { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); } }
.flabel { font-size:var(--fs-label); text-transform:uppercase; letter-spacing:.08em; color:var(--ink-3); padding-left:2px; }
.fgroup:not(:first-child) .flabel { border-left:1px solid var(--rule-2); padding-left:8px; }
.flead.first .flabel { border-left:0; padding-left:2px; } /* measured by markLeads: no divider at a row's edge */
.fgroup { display:contents; }
/* the label and the first chip are one flex item: a label at the end of a line, orphaned from its
   chips, read as belonging to the chips before it */
.flead { display:inline-flex; align-items:center; gap:5px; }
.chip {
  position:relative; display:inline-flex; align-items:center; gap:4px; min-height:30px; padding:0 9px;
  border:1px solid var(--rule-2); border-radius:999px; background:var(--card); color:var(--ink-2);
  font-size:var(--fs-chip); line-height:1; text-transform:lowercase; cursor:pointer; white-space:nowrap;
}
/* visual height stays dense (the filter bar used to waste vertical); the tap target is padded to 40px */
.chip::before { content:""; position:absolute; inset:-5px -2px; }
/* the count is a badge, not part of the word: smaller and set off, but no lighter than ink-3, which
   is the contrast floor for the low-vision reader */
.chip .n { margin-left:3px; font-size:.72em; font-weight:400; font-variant-numeric:tabular-nums; color:var(--ink-3); }
/* three states, none of them carried by hue: include is ringed and ticked, exclude is struck through
   with a dashed edge and its label rewritten to "not x" */
.chip .i { width:12px; height:12px; stroke-width:2.8; margin-left:-1px; }
.chip[data-state="in"] { background:var(--card-2); border-color:var(--ink); box-shadow:inset 0 0 0 1px var(--ink); color:var(--ink); font-weight:600; }
.chip[data-state="in"] .n { color:var(--ink-3); font-weight:400; }
/* exclude is a single slash across the whole chip, at a FIXED angle: a corner-to-corner line would
   get shallower as a chip gets wider and a row of them would read as a set of different angles.
   160deg holds the band at 20 degrees from horizontal at any width; the px stops keep it 2.2px thick. */
.chip[data-state="out"] {
  border-style:dashed; border-color:var(--ink-3); color:var(--ink-3);
  background-image:linear-gradient(160deg, transparent calc(50% - 1.1px), var(--slash, var(--ink-3)) calc(50% - 1.1px), var(--slash, var(--ink-3)) calc(50% + 1.1px), transparent calc(50% + 1.1px));
}
.chip[data-state="out"] .n { color:var(--ink-3); }

/* collapsible groups */
.group:not(.has-items) { display:none; }
.group { scroll-margin-top:var(--head-h); }
.group h2 {
  position:sticky; top:var(--head-h); z-index:2; display:flex; align-items:stretch; gap:2px;
  margin:0; padding:0; background:var(--paper);
}
.ghead {
  flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:7px; min-height:38px; padding:8px 2px 5px;
  border:0; background:none; cursor:pointer; text-align:left;
  font-size:var(--fs-head); font-weight:700; text-transform:uppercase; letter-spacing:.1em; color:var(--ink-2);
}
.ghead::after { content:""; flex:1 1 auto; height:1px; min-width:12px; background:var(--rule); }
.ghead .caret { width:15px; height:15px; stroke-width:2.4; color:var(--ink-3); }
.group.collapsed .ghead .caret { transform:rotate(-90deg); }
.gname { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.gcount { font-size:var(--fs-label); font-weight:600; letter-spacing:.04em; color:var(--ink-3); font-variant-numeric:tabular-nums; }
/* urgency is coded by shape as well as hue: filled, half, hollow, dotted */
.gmark { width:9px; height:9px; flex:0 0 auto; border:1.5px solid currentColor; border-radius:2px; }
.group[data-urgency="now"] .gmark { background:currentColor; }
.group[data-urgency="soon"] .gmark { background:linear-gradient(to right, currentColor 50%, transparent 50%); }
.group[data-urgency="noise"] .gmark { border-style:dotted; }
.group[data-day="past"] .gmark, .group[data-day="today"] .gmark { background:currentColor; }
.group[data-day="soon"] .gmark { background:linear-gradient(to right, currentColor 50%, transparent 50%); }
.group[data-day="past"] .ghead, .group[data-day="today"] .ghead { color:var(--stamp); }
.group[data-day="soon"] .ghead { color:var(--ochre); }
.group[data-urgency="now"] .ghead { color:var(--stamp); }
.group[data-urgency="soon"] .ghead { color:var(--ochre); }
.gnext { display:flex; align-items:center; justify-content:center; width:38px; min-height:38px; padding:0; border:0; background:none; color:var(--ink-3); cursor:pointer; }
.gnext:hover { color:var(--ink); }
.gnext .i { width:17px; height:17px; }
.group ul { list-style:none; margin:0 0 4px; padding:0; display:grid; gap:0; }
.row { position:relative; display:grid; grid-template-rows:1fr; padding-bottom:7px; }
/* a stack shows its depth at the edges: sheets peeking out from under the card that survived. Two
   sheets 8px proud of each other, drawn with the strong rule and a real shadow, and 14px of clear
   paper below: the earlier 7px hairline sheets with 3px to the next card read as no depth at all on
   a small screen. Kids own colour here, so depth is shadow, offset and rule, never hue. */
.row.stacked { padding-bottom:30px; }
.row.stacked::before, .row.stacked::after {
  content:""; position:absolute; left:8px; right:8px; bottom:22px; height:20px; z-index:0;
  border:1.5px solid var(--rule-2); border-top:0; border-radius:0 0 var(--radius) var(--radius); background:var(--card-2);
  box-shadow:0 2px 4px rgba(0,0,0,.14);
}
.row.stacked::after { left:16px; right:16px; bottom:14px; }
.row.stacked > .item { z-index:1; box-shadow:0 2px 6px rgba(0,0,0,.16); }
/* the stack mark on a card that is part of a stack is filled and ringed; a lone card's is an outline */
.row.stacked .stackbtn { color:var(--ink); background:var(--card-2); border:1.5px solid var(--rule-2); border-radius:50%; }
.row.stacked .stackbtn .i { fill:currentColor; }
/* the All view: a colour rail down the card and the child named in the meta, so a mixed list reads
   by child at a glance */
/* One mark, not two: the rail runs down the card and swells at the top into a block that holds the
   child's face, so the photo sits IN the colour rather than beside it. Full strength in the All view
   where it tells two children apart; a quiet trace on a single child's tab, where every card would
   wear the same stripe and the tab above already says whose list this is. */
.row[data-kid-color] > .item { padding-left:3px; }
.row[data-kid-color] > .item::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--kid); }
.allview .row[data-kid-color] > .item { padding-left:9px; }
.allview .row[data-kid-color] > .item::before { width:9px; }
/* the face sits proud of the plate: the circle breaks the block's edge down and to the right, with a
   ring of the card's own colour where it overhangs so it reads as a badge on top rather than a
   collision. The plate is the same object as the rail running out of its bottom left. */
.kidmark {
  position:relative; flex:0 0 auto; align-self:flex-start;
  width:34px; height:36px; margin:-9px 13px 0 -21px; z-index:1;
  background:var(--kid, var(--ink-3)); border-radius:0 0 9px 0;
}
/* Grown 21% from the bottom-right corner, which stays put, so the overhang into the card does not
   move. At 46px the circle's arc crosses the plate's bottom edge exactly where the rail's right edge
   is, so the blocky step that used to show at the bottom left closes into one continuous curve. */
.kidmark img, .kidmark .kinit {
  position:absolute; left:6px; top:0; width:46px; height:46px;
  border-radius:50%; object-fit:cover; background:var(--card); border:3px solid var(--card);
}
.kidmark .kinit { display:flex; align-items:center; justify-content:center; font-size:20px; font-weight:700; color:var(--kid, var(--ink-2)); }
/* the name hugs the face and the space sits after it: 13px moved from the mark's right margin to
   the name's, so the header keeps its width and the name reads as the photo's caption, not the tag's */
.kidtag { flex:0 0 auto; margin-right:13px; font-weight:650; color:var(--ink-2); }
/* one baseline for the row: text sits on it, marks and glyphs centre themselves against it */
.meta { align-items:baseline; }
.meta > * { line-height:1.15; }
.meta .clip, .meta .tag.urg { display:inline-flex; align-items:baseline; }
.meta .clip .i, .meta .tag.urg .gmark { align-self:center; }
.allview .meta { padding-top:9px; min-height:44px; }
.chip[data-group="kids"] .cl { text-transform:none; }
.chip .swatch { width:11px; height:11px; border-radius:50%; background:var(--kid, var(--ink-3)); flex:0 0 auto; }
.kidbar .alltab, .kidbar .alltab[aria-selected="true"] { flex:0 0 auto; min-width:46px; padding:0 6px; }
.kidbar .alltab .i { width:21px; height:21px; }
.folded { margin-top:4px; font-size:var(--fs-meta); color:var(--ink-3); overflow-wrap:anywhere; }
.folded .fs { font-weight:650; color:var(--ink-2); }

/* lift and drop. JS only feeds the live pointer offset into --dx/--dy; the transform itself is here. */
.row.lifted { z-index:7; pointer-events:none; }
.row.lifted > .item {
  transform:translate(var(--dx, 0px), var(--dy, 0px)) scale(1.04);
  border:2px solid var(--accent); box-shadow:0 12px 26px rgba(0,0,0,.34);
}
.row.drop-target > .item { outline:3px dashed var(--accent); outline-offset:-4px; background:var(--card-2); }
.row.drop-target > .item .title { text-decoration:underline; text-decoration-style:dashed; text-underline-offset:3px; }
body.dragging { cursor:grabbing; overflow-x:clip; }
/* the drag handle. touch-action none is viable on a 34px button where it was not on a three-line
   title, and it is what stops the browser claiming the touch for a scroll mid-press (the
   pointercancel the diagnostics kept recording). The button swells while the press is watched and
   stays swollen for the lift, so a finger knows it has been heard before the haptic lands. */
.stackbtn { touch-action:none; }
.stackbtn.pressing, .row.lifted .stackbtn { transform:scale(1.35); color:var(--accent); }
.stackbtn.pressing .i, .row.lifted .stackbtn .i { fill:currentColor; }
.draghint {
  position:fixed; top:0; left:0; right:0; z-index:9; display:none; padding:12px 14px;
  padding-top:calc(12px + env(safe-area-inset-top)); background:var(--accent); color:var(--paper);
  font-size:var(--fs-btn); font-weight:650; text-align:center; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.draghint[data-show] { display:block; }
.row > .item { min-height:0; }
.group.collapsed ul { display:none; }

/* item card: text is the open target, external-link top right, actions bottom right */
.item {
  -webkit-user-select:none; user-select:none;
  position:relative; display:grid; grid-template-columns:minmax(0,1fr) auto; background:var(--card);
  border:1px solid var(--rule-2); border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden;
}
.item-txt { grid-column:1 / -1; grid-row:3; min-width:0; display:block; width:100%; padding:2px 12px 8px; }
.meta { grid-column:1; grid-row:1; display:flex; align-items:baseline; gap:8px; padding:9px 2px 0 12px; font-size:var(--fs-meta); color:var(--ink-3); min-width:0; overflow:hidden; }
/* the tags are buttons: tapping one explains what it means, in the card */
.tag { position:relative; flex:0 0 auto; padding:0; border:0; background:none; font:inherit; font-size:var(--fs-meta); cursor:pointer; text-decoration:underline dotted; text-underline-offset:3px; text-decoration-color:var(--ink-3); }
.tag::before { content:""; position:absolute; inset:-7px -3px; }
.tag.aud { text-transform:uppercase; letter-spacing:.07em; font-weight:650; color:var(--ink-2); }
.tag.aud.direct { color:var(--accent); }
.tag.urg { text-transform:uppercase; letter-spacing:.07em; font-weight:700; display:inline-flex; align-items:center; gap:4px; }
.tag.urg .gmark { width:8px; height:8px; }
.tag.urg.now { color:var(--stamp); }
.tag.urg.now .gmark { background:currentColor; }
.tag.urg.soon { color:var(--ochre); }
.tag.urg.soon .gmark { background:linear-gradient(to right, currentColor 50%, transparent 50%); }
.tag.ask { text-transform:uppercase; letter-spacing:.07em; font-weight:650; color:var(--ochre); }
.btn.verb { -webkit-touch-callout:none; }
.meta .clip { flex:0 0 auto; display:inline-flex; align-items:center; gap:2px; }
.meta .clip .i { width:13px; height:13px; }
/* gloss bubble: placed over the tag it explains, flipped below when the card is near the top */
.bubble {
  position:fixed; left:var(--bx, 0); top:var(--by, 0); z-index:10; display:none;
  width:min(286px, calc(100vw - 16px)); padding:9px 11px; border:1px solid var(--ink-3); border-radius:10px;
  background:var(--card); color:var(--ink-2); font-size:var(--fs-meta); line-height:1.45;
  box-shadow:0 8px 22px rgba(0,0,0,.28);
}
.bubble[data-show] { display:block; }
.bubble b { display:block; color:var(--ink); font-size:var(--fs-btn); }
.bubble .dd { display:block; margin-top:5px; color:var(--ink-3); }
.bubble::after {
  content:""; position:absolute; left:var(--bax, 20px); margin-left:-7px; width:0; height:0;
  border:7px solid transparent; border-top-color:var(--ink-3); top:100%;
}
.bubble[data-below]::after { top:auto; bottom:100%; border-top-color:transparent; border-bottom-color:var(--ink-3); }
.meta .src { flex:0 0 auto; white-space:nowrap; }
.meta .from { flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.meta .when { flex:0 0 auto; margin-left:auto; font-variant-numeric:tabular-nums; white-space:nowrap; }
.meta .when.due { color:var(--stamp); font-weight:700; }
.meta .when.due::before { content:""; display:inline-block; width:6px; height:6px; margin-right:4px; border-radius:50%; background:currentColor; }
/* The title is a link, selectable like the body so one drag runs through both. The touch callout
   stays off: the share icon covers what the link menu would have offered, and that menu on every
   title would be noise. The padding is folded into negative margins so the layout is unchanged
   while the target grows to 30px on a one-line title and past 44px on two. */
.title { -webkit-user-select:text; user-select:text; -webkit-touch-callout:none; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; line-clamp:3; overflow:hidden; color:inherit; text-decoration:none; cursor:pointer; margin:-2px 0 -4px; padding:5px 0; font-size:var(--fs-title); font-weight:640; line-height:1.3; overflow-wrap:anywhere; }
.title:hover { text-decoration:underline; text-underline-offset:3px; }
.body { -webkit-user-select:text; user-select:text; cursor:auto; font-size:var(--fs-body); color:var(--ink-2); overflow-wrap:anywhere; white-space:pre-line; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; overflow:hidden; }
/* A body holding text back fades its last line away instead of stopping, so the card reads as
   continuing. The clamp's ellipsis would compete with that, so a faded body drops the clamp for a
   max-height in line units and a mask. A mask fades the text itself and needs to know nothing about
   what is behind it, so it holds in every palette, in dark mode, in contrast, and while the card's
   background changes for the done motion; an overlay of the background colour would be wrong in one
   of those. 55% keeps the first line whole and lets the second run out. */
/* The clipped body runs on past its two lines into the action row's band, where the tail ghosts
   out under the chevron: the operator pointed at that empty space and asked for the text to
   continue there, so "more text" and "this expands it" become one picture. The extension is a
   negative bottom margin the size of the band, so the card's height does not change; the buttons
   on the right are later in the DOM and paint over the ghost. The mask holds the two real lines
   whole and is down to a third by the band's top. */
.item[data-fade]:not(.open) .body {
  display:block; -webkit-line-clamp:none; line-clamp:none; height:calc(2.84em + var(--action-h)); margin-bottom:calc(-1 * var(--action-h)); /* a fixed height: the growth must equal the margin exactly, or a three-line body pulls the band up over its second line */
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(2.84em - .3em), rgba(0,0,0,.32) calc(2.84em + .5em), transparent calc(100% - .2em));
  mask-image:linear-gradient(to bottom, #000 calc(2.84em - .3em), rgba(0,0,0,.32) calc(2.84em + .5em), transparent calc(100% - .2em));
}
body.dense .item[data-fade]:not(.open) .body {
  height:calc(1.42em + var(--action-h));
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(1.42em - .3em), rgba(0,0,0,.32) calc(1.42em + .5em), transparent calc(100% - .2em));
  mask-image:linear-gradient(to bottom, #000 calc(1.42em - .3em), rgba(0,0,0,.32) calc(1.42em + .5em), transparent calc(100% - .2em));
}
/* the expand target is the whole empty left of the band, chevron included, up to the verb's gap */
.item[data-fade]:not(.open) .acts .chev { flex:1 1 auto; justify-content:flex-start; }
.item.open .title, .item.open .body { -webkit-line-clamp:none; line-clamp:none; display:block; }
.item.open .body { height:auto; max-height:none; -webkit-mask-image:none; mask-image:none; }
/* the link a message is actually about: a Zoom room, a store, a form */
.inlink { color:var(--accent); text-decoration:underline; text-underline-offset:2px; overflow-wrap:anywhere; }
.inlink:hover { text-decoration-thickness:2px; }
.item .ext { position:relative; grid-column:2; grid-row:1; display:flex; align-items:flex-start; justify-content:center; width:38px; min-height:30px; padding-top:6px; color:var(--ink-3); }
.item .ext::before { content:""; position:absolute; inset:-7px -3px; }
.item .ext:hover { color:var(--accent); }
.acts { grid-column:1 / -1; grid-row:5; display:flex; flex-wrap:wrap; justify-content:flex-end; align-items:center; gap:13px 6px; padding:0 10px 7px 12px; }
.acts .filelink { position:relative; display:inline-flex; align-items:center; justify-content:center; width:34px; min-height:34px; padding:0; border:0; background:none; color:var(--ink-3); cursor:pointer; }
.acts .filelink:hover { color:var(--ink); }
.acts .btn::before, .acts .filelink::before { content:""; position:absolute; inset:-4px -2px -8px; }
/* 14px stays inside the 16px gutter, so the nudge never opens a horizontal scroll */
.row.jogging > .item { background:var(--card-2); transform:translateX(14px); }
.row.jogging > .item .btn.done { opacity:1; color:var(--green); border-color:var(--green); }
/* the card leaves through the row's own edge, so the document never gains a horizontal scroll */
.row.zipping { overflow-x:clip; }
.row.zipping > .item { transform:translateX(110%); opacity:0; }
/* expansion: the chevron shows only when the card is holding something back */
.files { grid-column:1 / -1; grid-row:4; display:none; flex-wrap:wrap; gap:6px; padding:0 12px 4px; }
.item.open .files { display:flex; }
.filebtn { max-width:100%; }
.shots { display:grid; grid-template-columns:repeat(auto-fit, minmax(92px, 132px)); gap:6px; width:100%; }
.shot { position:relative; padding:0; border:1px solid var(--rule-2); border-radius:8px; overflow:hidden; background:var(--card-2); cursor:zoom-in; aspect-ratio:1; }
.shot img { width:100%; height:100%; object-fit:cover; display:block; }
.photoview { max-width:min(96vw, 900px); max-height:92vh; padding:8px; border:1px solid var(--rule-2); border-radius:12px; background:var(--card); color:var(--ink); }
.photoview::backdrop { background:rgba(8,10,12,.82); }
.photoview img { display:block; max-width:100%; max-height:82vh; margin:0 auto; border-radius:8px; object-fit:contain; }
.photoview p { margin:6px 2px 2px; text-align:center; }
.filebtn span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.acts .chev { display:none; margin-right:auto; }
.item[data-more] .acts .chev { display:inline-flex; }
.item.open .chev .i { transform:rotate(180deg); }
.item.open { box-shadow:0 2px 10px rgba(0,0,0,.12); }
.item.is-done { opacity:.55; }
.item.is-done .title { text-decoration:line-through; text-decoration-thickness:1px; }

.btn {
  position:relative; display:inline-flex; align-items:center; justify-content:center; gap:6px;
  min-height:34px; padding:0 11px; border:1px solid var(--rule-2); border-radius:7px; background:var(--card);
  color:var(--ink-2); font-size:var(--fs-btn); text-decoration:none; cursor:pointer;
}
.btn .i { width:16px; height:16px; }
.btn:hover { color:var(--ink); border-color:var(--ink-3); }
.btn.verb { color:var(--accent); font-weight:600; }
.btn.done { color:var(--ink); }
.btn.done[aria-pressed="true"] { color:var(--green); border-color:var(--green); }
.btn.primary { background:var(--ink); color:var(--paper); border-color:var(--ink); font-weight:600; }
.btn.wide { width:100%; min-height:42px; }
.sheet-acts .btn { min-height:42px; }
.btn[disabled] { opacity:.45; cursor:default; }

.empty { color:var(--ink-2); font-size:var(--fs-body); padding:24px 0; text-align:center; }
.empty button { margin-top:10px; }
.foot { max-width:852px; margin:0 auto; padding:4px var(--gutter) 28px; color:var(--ink-3); font-size:var(--fs-meta); text-align:center; }
.foot a { color:var(--ink-3); }
/* a stale source is named in the footer, in the ask colour rather than the due red: worth a look,
   not an alarm. The sheet lists every source with its age. */
.foot .stale, .health .stale { color:var(--ochre); font-weight:700; }
.health { list-style:none; margin:8px 0 0; padding:0; display:grid; gap:3px; font-size:var(--fs-meta); color:var(--ink-2); }
.health li { display:flex; justify-content:space-between; gap:8px; font-variant-numeric:tabular-nums; }

/* sheets */
dialog.sheet { border:1px solid var(--rule-2); background:var(--card); color:var(--ink); padding:10px 14px 14px; }
dialog.sheet::backdrop { background:rgba(8,10,12,.6); }
dialog.sheet h2 { margin:0; font-size:calc(15px * var(--ts)); font-weight:650; }
dialog.sheet h3 { margin:13px 0 6px; font-size:var(--fs-label); text-transform:uppercase; letter-spacing:.08em; color:var(--ink-3); font-weight:700; }
.sheet-head { display:flex; align-items:center; justify-content:space-between; gap:8px; margin:0 -8px 8px 0; }
.sheet-top { margin:8px 8px 0 auto; width:min(320px, calc(100vw - 16px)); max-height:calc(100dvh - 24px); overflow-y:auto; overflow-x:hidden; overscroll-behavior:contain; border-radius:12px; }
.sheet-bottom { margin:auto auto 0; width:100%; max-width:520px; border-radius:14px 14px 0 0; border-bottom:0; }
.dim { color:var(--ink-3); }
.small { font-size:var(--fs-meta); }
.who { display:flex; align-items:center; gap:10px; padding:2px 0 6px; }
.avatar { display:flex; align-items:center; justify-content:center; width:38px; height:38px; border-radius:999px; background:var(--card-2); border:1px solid var(--rule-2); color:var(--ink-2); }
.who-t { display:flex; flex-direction:column; min-width:0; }
.who-t strong { font-weight:650; }
.who-t .dim { font-size:calc(12px * var(--ts)); overflow:hidden; text-overflow:ellipsis; }
.seg { display:flex; gap:1px; margin:0; padding:0; border:1px solid var(--rule-2); border-radius:8px; background:var(--rule-2); overflow:hidden; }
.seg.grid { display:grid; grid-template-columns:1fr 1fr; }
.seg legend { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
.seg label { flex:1 1 0; min-width:0; display:flex; }
.seg input { position:absolute; opacity:0; pointer-events:none; }
.seg span { flex:1 1 auto; display:flex; align-items:center; justify-content:center; min-height:42px; padding:0 6px; background:var(--card); font-size:var(--fs-btn); color:var(--ink-2); text-align:center; cursor:pointer; }
.seg input:checked + span { background:var(--card-2); color:var(--ink); font-weight:650; box-shadow:inset 0 0 0 2px var(--accent); }
.seg input:focus-visible + span { outline:2px solid var(--focus); outline-offset:-2px; }
.row-opt { display:flex; align-items:center; gap:10px; min-height:var(--tap); font-size:calc(14px * var(--ts)); }
#me-refresh, #me-auth { margin-top:8px; }
/* kid editor: photo, display name and one-thumb reordering, sized for the settings panel */
.kidlist { list-style:none; margin:2px 0 0; padding:0; display:grid; grid-template-columns:minmax(0, 1fr); gap:8px; }
.kidlist > li { min-width:0; }
.kidrow { display:flex; align-items:center; gap:6px; }
.kphoto { position:relative; flex:0 0 auto; display:flex; align-items:center; justify-content:center; width:38px; height:38px; padding:0; overflow:hidden; border:1px solid var(--rule-2); border-radius:50%; background:var(--card-2); color:var(--ink-2); cursor:pointer; }
.kphoto img { width:100%; height:100%; object-fit:cover; }
.kphoto .i { width:17px; height:17px; }
.kname { flex:1 1 auto; min-width:0; height:40px; padding:0 8px; border:1px solid var(--rule-2); border-radius:7px; background:var(--paper); color:var(--ink); font:inherit; font-size:calc(14px * var(--ts)); }
.kmove { position:relative; flex:0 0 auto; display:flex; align-items:center; justify-content:center; width:38px; height:40px; border:1px solid var(--rule-2); border-radius:7px; background:var(--card); color:var(--ink-2); cursor:pointer; }
.kmove::before { content:""; position:absolute; inset:-2px -3px; }
.kmove[disabled] { opacity:.35; cursor:default; }
.kmove .i { width:16px; height:16px; }
/* the colour swatches: 44px targets under the name, each drawn from the same token the rail and
   the chips use. The current one wears an ink ring and a check; a colour another child already
   wears carries their initial in the corner. */
.kswatches { display:flex; flex-wrap:wrap; } /* no indent: six 44px targets must not be squeezed to fit */
.kswatch { position:relative; flex:0 0 auto; display:flex; align-items:center; justify-content:center; width:44px; height:44px; padding:0; border:0; background:none; cursor:pointer; }
.kswatch::before { content:""; width:26px; height:26px; border-radius:50%; background:var(--kid); border:2px solid var(--card); box-shadow:0 0 0 1px var(--rule-2); }
.kswatch[aria-pressed="true"]::before { box-shadow:0 0 0 3px var(--ink); }
.kswatch .i { position:absolute; width:14px; height:14px; stroke-width:3; color:var(--card); }
.kswatch .kused { position:absolute; right:4px; bottom:4px; min-width:15px; height:15px; padding:0 2px; border-radius:8px; background:var(--card); color:var(--ink); font-size:9px; font-weight:700; line-height:15px; text-align:center; box-shadow:0 0 0 1px var(--rule-2); }
.kclear { min-height:30px; padding:0 8px; margin:2px 0 0 44px; font-size:var(--fs-meta); }
.klive { margin:8px 0 0; min-height:1.2em; font-size:var(--fs-meta); color:var(--ink-2); }
/* the Advanced disclosure reuses the section head: same caret, same rule line, a 44px target */
.adv-head { width:100%; min-height:var(--tap); margin-top:10px; }
.adv-head[aria-expanded="false"] .caret { transform:rotate(-90deg); }
/* The sheet is a stack of panes, one per concern, on the paper surface behind a hairline: a
   surface change and a rule, never hue, so it reads in contrast and in dark. The pane heading
   keeps the label style; the labels inside a pane step down to sentence case at the meta size, so
   Theme, Mode and Text size read as one decision under Appearance rather than three sections. */
.pane { margin-top:7px; padding:6px 10px 9px; border:1px solid var(--rule); border-radius:10px; background:var(--paper); }
.pane > h3, dialog.sheet .pane h3 { margin:0 0 3px; }
.pane h4 { margin:7px 0 3px; font-size:var(--fs-meta); font-weight:600; color:var(--ink-3); }
.pane h3 + h4, .pane .who { margin-top:0; }
.pane .who { padding:0 0 2px; }
.pane .btn.wide + .health, .pane #me-role + .btn { margin-top:6px; }
/* rows inside a pane sit on the card colour so they stand off the paper they are on */
.userlist li, .invlist li { background:var(--card); }
/* accounts. Gated: no filters, no list, no footer, just the notice. Read-only: the editing surfaces
   in the sheet go rather than sit disabled; the card itself drops its controls at render. */
body[data-gated] .filterpane, body[data-gated] #sections, body[data-gated] .foot { display:none; }
body.readonly #kids-pane, body.readonly #me-refresh, body.readonly .mergelist .btn { display:none; }
.userlist, .invlist { list-style:none; margin:2px 0 0; padding:0; display:grid; gap:6px; }
.userlist li, .invlist li { display:grid; gap:2px; padding:8px 10px; border:1px solid var(--rule-2); border-radius:8px; background:var(--paper); font-size:var(--fs-body); }
.userlist .pt, .invlist .pt { font-weight:650; color:var(--ink); overflow-wrap:anywhere; }
.userlist .ps, .invlist .ps { font-size:var(--fs-meta); color:var(--ink-3); }
.userlist .scope { margin-top:4px; justify-self:start; font:inherit; font-size:var(--fs-meta); color:var(--ink-2); background:var(--card); border:1px solid var(--rule-2); border-radius:6px; padding:4px 6px; }
.invlist li[data-status="expired"] .pt, .invlist li[data-status="revoked"] .pt { color:var(--ink-3); text-decoration:line-through; }
.invlist li .btn { justify-self:start; margin-top:4px; min-height:32px; }
.invite { display:grid; gap:6px; }
.fld { display:grid; gap:4px; margin-top:6px; font-size:var(--fs-meta); color:var(--ink-3); }
.fld textarea { padding:8px 10px; border:1px solid var(--rule-2); border-radius:8px; background:var(--paper); color:var(--ink); font:inherit; font-size:calc(14px * var(--ts)); resize:vertical; }
.invlist a.pt { color:var(--ink); text-decoration:underline; text-underline-offset:2px; }
.fld input { min-height:40px; padding:0 10px; border:1px solid var(--rule-2); border-radius:8px; background:var(--paper); color:var(--ink); font:inherit; font-size:calc(14px * var(--ts)); }
/* the minted link: shown once, framed in the ask colour so the "copy it now" reads as an instruction */
.made { display:grid; gap:8px; margin-top:10px; padding:10px; border:1.5px solid var(--ochre); border-radius:8px; background:var(--card-2); }
.made p { margin:0; }
.invurl { display:block; font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:var(--fs-meta); color:var(--ink); overflow-wrap:anywhere; -webkit-user-select:all; user-select:all; }
/* the join page: the app's own tokens and type, one card, nothing else to do here */
.joinpage { display:grid; justify-items:center; align-content:start; min-height:100dvh; padding:32px var(--gutter); }
.join { width:min(420px, 100%); display:grid; gap:14px; padding:22px 20px; border:1px solid var(--rule-2); border-radius:12px; background:var(--card); box-shadow:var(--shadow); }
.join h1 { margin:0; font-size:calc(20px * var(--ts)); font-weight:650; }
.join p { margin:0; font-size:var(--fs-body); color:var(--ink-2); }
.join .btn { text-decoration:none; }
/* the sign-in screen is the join card, sitting in the app's own main column */
main > .signin { margin:28px auto 0; }
.gsi { min-height:44px; }
/* crop frame: a circle, because that is exactly how the avatar renders in the kid tab */
.cropwrap { display:flex; justify-content:center; padding:4px 0 8px; }
.cropframe {
  width:min(260px, 68vw); aspect-ratio:1; border-radius:50%; border:2px solid var(--rule-2);
  background:var(--card-2); touch-action:none; cursor:grab; display:block;
}
.cropframe:active { cursor:grabbing; }
.croprow { display:flex; align-items:center; gap:8px; margin-top:6px; }
.croprow .iconbtn { border:1px solid var(--rule-2); }
#crop-zoom { flex:1 1 auto; min-width:0; height:var(--tap); accent-color:var(--accent); }
#crop-center { flex:0 0 auto; }
/* the non-gesture route: pick a card from a list instead of dragging onto one */
.picklist { list-style:none; margin:6px 0 0; padding:0; display:grid; gap:6px; max-height:44vh; overflow-y:auto; overscroll-behavior:contain; }
.pick { display:flex; flex-direction:column; align-items:flex-start; justify-content:center; gap:2px; width:100%; min-height:54px; padding:8px 10px; text-align:left; }
.pick .pt { font-weight:650; color:var(--ink); overflow-wrap:anywhere; }
.pick .ps { font-size:var(--fs-meta); color:var(--ink-3); }
.glosslist { list-style:none; margin:0; padding:0; display:grid; gap:10px; max-height:62vh; overflow-y:auto; overscroll-behavior:contain; }
.glosslist h4 { margin:8px 0 0; font-size:var(--fs-label); text-transform:uppercase; letter-spacing:.08em; color:var(--ink-3); }
.gloss { border-left:3px solid var(--rule-2); padding-left:9px; font-size:var(--fs-meta); line-height:1.45; color:var(--ink-2); }
.gloss b { display:block; font-size:var(--fs-btn); color:var(--ink); }
.gloss .de { display:block; margin-top:3px; color:var(--ink-3); font-style:italic; }
.mergelist { list-style:none; margin:2px 0 0; padding:0; display:grid; grid-template-columns:minmax(0, 1fr); gap:6px; }
.mergelist > li { min-width:0; }
/* a taught rule is two wordings and a date match; side by side they were 176px of two-line 11.5px
   text at 320px, so each side gets its own line and the control sits under them */
.mergerow { display:flex; flex-direction:column; align-items:stretch; gap:6px; min-width:0; padding:8px 9px; border:1px solid var(--rule); border-radius:8px; background:var(--card-2); }
.mergerow .mt { min-width:0; font-size:var(--fs-meta); color:var(--ink-3); overflow-wrap:anywhere; }
.mergerow .mt b { display:block; font-weight:600; color:var(--ink-2); }
.mergerow .btn { align-self:flex-end; }
.reasons { display:flex; flex-wrap:wrap; gap:8px 6px; margin:8px 0 10px; }
.reasons .chip { min-height:38px; font-size:var(--fs-btn); }
dialog.sheet textarea { width:100%; padding:8px 10px; border:1px solid var(--rule-2); border-radius:8px; background:var(--paper); color:var(--ink); font:inherit; font-size:calc(14px * var(--ts)); resize:vertical; }
.sheet-acts { display:flex; justify-content:flex-end; gap:8px; margin-top:12px; padding-bottom:env(safe-area-inset-bottom); }

body.dense .body { -webkit-line-clamp:1; line-clamp:1; }
body.dense .row { padding-bottom:5px; }

/* high contrast leans on weight and border width, not tint, so it survives a washed-out console */
:root[data-theme="contrast"] .item,
:root[data-theme="contrast"] .btn,
:root[data-theme="contrast"] .chip,
:root[data-theme="contrast"] dialog.sheet { border-width:2px; }
:root[data-theme="contrast"] .chip[data-state="in"] { box-shadow:inset 0 0 0 2px var(--ink); }
:root[data-theme="contrast"] .chip[data-state="out"] { border-color:var(--ink); color:var(--ink); }
:root[data-theme="contrast"] .chip[data-state="out"] {
  --slash:var(--ink);
  background-image:linear-gradient(160deg, transparent calc(50% - 1.6px), var(--ink) calc(50% - 1.6px), var(--ink) calc(50% + 1.6px), transparent calc(50% + 1.6px));
}
:root[data-theme="contrast"] .ghead::after,
:root[data-theme="contrast"] .filterbar { border-bottom-width:2px; }
:root[data-theme="contrast"] .ghead { color:var(--ink); }
/* This palette trades tints for full-strength borders, so the dimmed colour-mix forms above work
   against it: here every tab's colour is at full strength and a little wider, and the selection is
   carried by height, background and weight rather than by the colour being brighter. */
:root[data-theme="contrast"] .kidbar button { box-shadow:inset 0 4px 0 var(--kid, var(--accent)); }
:root[data-theme="contrast"] .kidbar button[aria-selected="true"] { box-shadow:inset 0 6px 0 var(--kid, var(--accent)); }
:root[data-theme="contrast"] .kidbar button[data-kid-color] .kav { border-color:var(--kid); }
:root[data-theme="contrast"] .body, :root[data-theme="contrast"] .meta { color:var(--ink-2); }
:root[data-theme="contrast"] .item.is-done { opacity:.75; }

/* announced everywhere; shown only when motion is off, where it is the whole confirmation */
/* announced everywhere; visible when it carries an undo, and whenever motion is off */
.toast { position:absolute; left:0; top:0; width:1px; height:1px; margin:-1px; overflow:hidden; clip-path:inset(50%); }
/* the hidden state pins top:0 to keep it out of the layout; the visible one has to let go of it, or
   top:0 with bottom:16px stretches the toast down the whole screen */
.toast[data-visible] {
  position:fixed; inset:auto 0 16px 0; z-index:8; width:max-content; max-width:calc(100vw - 32px); height:auto;
  margin:0 auto; overflow:visible; clip-path:none; display:flex; align-items:center; gap:10px;
  padding:9px 12px; border:1px solid var(--ink-3); border-radius:10px;
  background:var(--card); color:var(--ink); font-size:var(--fs-btn); line-height:1.35; text-align:left;
  box-shadow:0 6px 18px rgba(0,0,0,.24);
}
.toast[data-visible] #toast-msg { min-width:0; }
#toast-undo { min-height:36px; }
@media (prefers-reduced-motion: no-preference) {
  .chip, .btn, .kidbar button, .gnext { transition:background-color .12s ease, color .12s ease, border-color .12s ease; }
  /* the transition lives on the jogging state only, so removing the class snaps the card back and
     the lifted card's live transform is never eased */
  .row.jogging > .item { transition:transform var(--jog) ease-out; }
  .row.zipping > .item { transition:transform var(--zip) ease-in, opacity var(--zip) ease-in; }
  .row { transition:grid-template-rows var(--collapse) ease-in, padding-bottom var(--collapse) ease-in, opacity calc(var(--collapse) - 60ms) ease-in; }
  .row.collapsing { grid-template-rows:0fr; padding-bottom:0; opacity:0; }
  .kidbar .n.cleared { animation:settle .45s ease-out; }
  .ghead .caret, .fop .i, .chev .i { transition:transform .15s ease; }
  .stackbtn { transition:transform .15s ease, color .15s ease; }
  html { scroll-behavior:smooth; }
}
@keyframes settle { from { opacity:0; transform:scale(.55); } to { opacity:1; transform:none; } }
@media (max-width: 360px) { .kidbar .kav { display:none; } .kidbar button { gap:3px; padding:0 3px; } }
@media (min-width: 560px) {
  .kidbar button { flex:0 1 auto; min-width:130px; }
}

/* the school-year calendar: the page an nycdoe card's link resolves to, because the DOE's own page
   has one URL for all of its events. Plain rows, because the value here is the whole year in order. */
.calpage { background:var(--paper); color:var(--ink); }
.calhead { position:sticky; top:0; z-index:5; display:flex; align-items:center; gap:10px; padding:calc(8px + env(safe-area-inset-top)) var(--gutter) 8px; background:var(--band); border-bottom:1px solid var(--rule-2); }
.calhead h1 { margin:0; font-size:var(--fs-tab); font-weight:700; }
.calhead .back { flex:0 0 auto; min-height:34px; padding:0 10px; }
.calmain { max-width:852px; margin:0 auto; padding:0 var(--gutter) 40px; }
.calmonth { margin:18px 0 6px; font-size:var(--fs-head); font-weight:700; text-transform:uppercase; letter-spacing:.1em; color:var(--ink-2); }
.calrow { display:grid; grid-template-columns:minmax(92px, 34%) 1fr; gap:10px; padding:9px 8px; border-bottom:1px solid var(--rule-2); scroll-margin-top:72px; }
.caldate { color:var(--ink-2); font-size:var(--fs-meta); font-variant-numeric:tabular-nums; }
.calbody { display:grid; gap:2px; min-width:0; }
.calevent { overflow-wrap:anywhere; }
.calmark { font-size:var(--fs-meta); color:var(--ink-3); text-transform:lowercase; }
.calrow[data-closure] { border-left:4px solid var(--stamp); padding-left:8px; }
.calrow[data-closure] .calevent { font-weight:650; }
.calrow[data-removed] .calevent { text-decoration:line-through; color:var(--ink-3); }
/* the row the card linked at, so a reader lands ON their event rather than near it */
.calrow[data-target] { background:var(--card-2); box-shadow:inset 0 0 0 2px var(--accent); border-radius:8px; }
