/* ===========================================================================
 * Home (#overview) — DESIGN FIDELITY against the BINDING 2026-09-16 handoff.
 *
 * WHY THIS FILE EXISTS AT ALL, AND WHY IT IS NOT IN app.css.
 *
 * app.css is the shared multi-screen stylesheet, and every class this file
 * touches (.card, .arow, .pill, .badge, .sect, .ovtile, .avatar) is also worn
 * by Devices, Settings, Account, Activity, Requests and the child workspace.
 * Re-cutting them there would re-skin nine screens nobody measured. So the
 * Home-only corrections live here and EVERY selector is anchored to
 * `#overviewMount`, which no other screen has. The subset that genuinely
 * should be global — the card edge above all — is written up separately in
 * `.scratch/fidelity-appcss-overview.patch` for whoever owns app.css; if they
 * take it, the matching rules here become redundant and can be deleted.
 *
 * WHY IT IS A FILE AND NOT AN INJECTED <style>. It was an injected <style>
 * first, and it silently did nothing: static.js serves this app under
 * `style-src 'self'` with NO 'unsafe-inline' (src/static.js, and index.html's
 * own header comment says the same), so a JS-built <style> element lands in
 * the DOM with a null `.sheet` and not one rule applies. Measured, not
 * assumed — `#ovFidelityStyles` was present with 5088 characters of text and
 * `sheet === null`, and the rendered page was unchanged. A same-origin
 * stylesheet FILE is exactly what `'self'` permits, so js/overview.js appends
 * a <link> to this path instead. Nothing inline is added to index.html, so
 * static.test.js's inline-free assertion is untouched.
 *
 * HER NUMBERS. Read off http://127.0.0.1:8799/Home.dc.html at 1440x950 light
 * with the same getComputedStyle routine that read ours
 * (`.scratch/fid-overview-measure.mjs`). Her artboard renders 1:1 — no
 * transform — so these are directly comparable to ours:
 *
 *   card            #FFF · 1px rgba(22,35,63,.08) · r22 · pad 24
 *                   shadow 0 1px 2px rgba(22,35,63,.03),
 *                          0 6px 18px rgba(22,35,63,.04)
 *   request row     NO box: flex · gap 14 · align center · pad 0 0 14
 *                   border-bottom 1px rgba(22,35,63,.07)
 *   row avatar      34x34 · r50% · 14px/800
 *   row title       14px/700       row meta 12.5px/400
 *   Approve/Deny    34 tall · r999 · pad 8 14 · 12.5px/700
 *   count chip      r999 · 12px/800 · pad 0 7 · #FDF1DF on #C2760A
 *   status chip     r999 · 11.5px/800 · pad 4 11 · solid tint
 *   glance heading  16.5px/800
 *   glance avatar   32x32 · r50% · 13px/800      name 15px/800
 *   metric          ONE ROW: label 12.5/400 left, value 12.5/700 right
 *   bar             8px · r999 · track #EEF1F4
 *   nudge           #ECE8FB · r22 · pad 22 26 · gap 20 · NO border
 *   nudge icon      44x44 · r14 · white
 *   nudge CTA       r999 · pad 13 22 · FILLED navy, white text
 *
 * COLOURS ARE TOKENS, NOT HEXES, and the tokens already carry her exact light
 * values: --border-subtle IS rgba(22,35,63,.08), --surface-raised IS #F1F3F6,
 * --done-tint #E3F5EA, --waiting-tint #FDF1DF, --attention-tint #ECE8FB,
 * --radius-card 22px, --radius-pill 999px, --shadow-card her two-layer shadow.
 *
 * TWO DELIBERATE DIVERGENCES, both toward legibility, both listed in the
 * patch file: her muted grey is #8A93A3 and ours is --text-secondary #5B6472
 * (darker, so not adopted), and her amber chip text is #C2760A against our
 * --waiting-text #9D5E08 (ditto).
 *
 * NOTHING IN THIS FILE SETS display, visibility OR opacity ON AN ELEMENT JS
 * WRITES USER-FACING TEXT INTO. The only textContent writes in js/overview.js
 * are `av.textContent` (a favicon letter) and `whySum.textContent` (the
 * disclosure summary); neither is hidden, dimmed or masked here. There is no
 * fade, no scroll mask and no opacity rule on any control — the commit that
 * ghosted live controls earlier today (and ed3eaf68 before it) is the reason
 * this paragraph exists.
 * ======================================================================== */

/* --- CARD SHELL ----------------------------------------------------------
   The owner's ruling: CARD edges are .08 (--border-subtle), CONTROL edges are
   .16 (--line). Home's cards were wearing the CONTROL edge, which is why they
   read as hard boxes rather than her soft planes. Hers measures .08. */
#overviewMount .card {
  border-color: var(--border-subtle);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
#overviewMount .ovalerts { padding: 24px; }

/* --- SECTION TITLE -------------------------------------------------------
   Hers is title left, count chip hard right, and NO rule line between them.
   `.sect::after` is app.css's flexible gradient rule; it stays in the box (it
   is what pushes the chip right) and loses only its paint. */
#overviewMount .ovalerts > .sect { gap: 0; margin-bottom: 4px; }
#overviewMount .ovalerts > .sect::after { background: none; }
#overviewMount .ovalerts > .sect > .badge.attn {
  background: var(--waiting-tint);
  color: var(--waiting-text);
  font-size: 12px;
  font-weight: 800;
  line-height: 18px;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  min-width: 0;
}

/* --- THE REQUEST ROW -----------------------------------------------------
   Hers is not a card. Ours was a white, bordered, 14px-radius sub-card
   sitting INSIDE a white bordered card — the box-in-a-box the owner called
   "all sorts of spacing and border issues". This is her shape: no fill, no
   border, no radius, one hairline UNDER each row, and the last row carrying
   no rule so the card does not end on a line. */
#overviewMount .ovalerts .arow {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  border-bottom: 1px solid var(--border-subtle);
  padding: 0 0 14px;
  margin: 0 0 14px;
  gap: 14px;
  align-items: center;
}
#overviewMount .ovalerts .arow:last-of-type {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}
#overviewMount .ovalerts .arow .favicon {
  width: 34px; height: 34px;
  font-size: 14px; font-weight: 800; line-height: 21px;
  flex: none;
}
#overviewMount .ovalerts .arow .an { font-size: 14px; font-weight: 700; line-height: 21px; }
#overviewMount .ovalerts .arow .asub {
  font-size: 12.5px; font-weight: 400; line-height: 18.75px;
  color: var(--text-secondary);
}
/* `flex: 0 1 auto`, NOT `flex: none`.
   MEASURED FAILURE, 390x844: `flex: none` was here first, and it made the
   three-button group unshrinkable. `.arow` is `display:flex` with no
   flex-wrap (app.css), and `.an` carries `flex:1; min-width:0;
   overflow-wrap:anywhere` — so ALL of the squeeze landed on the text, which
   broke to ONE CHARACTER PER LINE. `tiktok.com` rendered as a ten-line
   vertical column. The group must stay able to shrink (its own
   `flex-wrap: wrap` then folds the buttons onto a second line) so the
   sentence keeps its width. The phone block below also lets the row itself
   wrap, which is what actually moves the buttons under the text. */
#overviewMount .ovalerts .arow .reqactions { margin-top: 0; gap: 8px; flex: 0 1 auto; }

/* THE CARD ITSELF IS QUERYABLE, not just the viewport. The existing phone
 * wrap rule below (`@media (max-width: 640px)`) only fires when the whole
 * BROWSER is narrow — but this card can be crushed to the same width, or
 * narrower, at ordinary DESKTOP viewports too, because it shares a two-track
 * grid with `.ovchildren` (see that section's own comment: at 1024px this
 * card measured 326px wide with a 1440px BROWSER window nowhere near phone
 * width). `.reqactions`'s three real buttons (`.iconbtn`, `flex: none`
 * apiece — app.css) cannot shrink, so on a `nowrap` row every pixel this
 * card loses comes off `.an` alone, which is how a real hostname
 * (`freshsite-audit-demo.test`) measured collapsing to a 0px-wide,
 * 1068px-tall column of single characters. app.css already uses container
 * queries elsewhere (`#timeWeekCard`), so this is not a new mechanism for
 * the portal — see household-usage.css's `husage-panel` for the identical
 * shape.
 *
 * `400px`, NOT the phone rule's `640px` — measured deliberately narrower.
 * At a real 1440px browser width THIS card's own column still only measures
 * ~634px (the 1.3fr track, minus the page rail/padding), which is already
 * under 640 for an ordinary 2-request household; reusing 640px here made
 * this rule fire on an otherwise-healthy 1440px desktop row too, flipping
 * `align-items` away from the owner-ruled `center` fidelity spec pins at
 * that exact width (`overview-design-fidelity-ui.test.js`). 400px is
 * comfortably under the measured 1024px break (326px, where `.an` collapsed
 * to 0) and comfortably above the measured "fine" widths (582px at 1280,
 * 634px at 1440) — the wrap treatment is for the genuinely crushed case, not
 * every card that is merely narrower than a phone-only number. */
#overviewMount .ovalerts {
  container-type: inline-size;
  container-name: ov-alerts-card;
}
@container ov-alerts-card (max-width: 400px) {
  #overviewMount .ovalerts .arow { align-items: flex-start; flex-wrap: wrap; }
  #overviewMount .ovalerts .arow .an { flex: 1 1 140px; }
  #overviewMount .ovalerts .arow .reqactions { flex: 1 1 100%; margin-top: 8px; }
}

/* --- HER FOOT LINK -------------------------------------------------------
   "View all requests →" is a 13px/700 arrow link in brand ink, NOT an
   outlined button — the single clearest difference between her action
   language and ours. It stays a real <button> so it keeps keyboard and
   screen-reader semantics; only its paint is a link's. It is NOT dimmed:
   full brand colour at 700, which is why it cannot read as disabled. */
#overviewMount .ovfootlink {
  display: inline-block;
  width: auto;
  min-height: 0;
  margin: 14px 0 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  text-align: left;
  font-size: 13px;
  font-weight: 700;
  line-height: 19.5px;
  color: var(--brand);
  cursor: pointer;
}
#overviewMount .ovfootlink:hover,
#overviewMount .ovfootlink:focus-visible { text-decoration: underline; }

/* --- LAYOUT V2: ONE STACK, 24px APART, AT EVERY WIDTH --------------------
 *
 * OWNER-REQUESTED, Tisha (product design) 2026-09-30, looking at the
 * two-child demo at desktop width: "No, now there is a lot of weird
 * spacing. ... Put the 'Today at a glance' section right under the new
 * buttons you created and make it span the entire width of the white area.
 * I.e Ben card on left, Maya beside it on the right on desktop. responsive
 * for mobile ... Remove the 4 little cards ... Requests right under Today
 * at a glance".
 *
 * WHAT SHE WAS LOOKING AT, measured on a copy of that demo before this
 * block (section gap = next top - previous bottom): at 1440x900 the gaps
 * were 22, 36, 18, 18, 18, 18px, the stat row sat between the banner and
 * the children, and the two child cards were STACKED one per row at the
 * full 1140px; at 768 and below the page was a plain block flow whose gaps
 * came from whatever margin each card happened to carry: 4, 0, 0, 16, 16,
 * 12, 0px.
 *
 * THE RULES, and each one is measured by
 * test/overview-layout-v2-ui.test.js:
 *   - ONE flex column, in DOM order: header, Recommended, the add-a-device
 *     nudge (when a child has none), the quick links, "Today at a glance",
 *     Requests, the "Not reported" disclosure, This week. DOM order is the
 *     visual order and the Tab order; no `order`, no grid placement, at any
 *     width. (The two-track "three bands" layout, its pending-request
 *     variant and the 1280->1024 crush repairs it needed went with it.)
 *   - The 24px (Homepage-Spec.md §2, "Gap between sections: 24px") is the
 *     column's `gap` and NOTHING ELSE: every section's own vertical margin
 *     is zeroed, because a flex gap does not collapse with a margin — a
 *     card's 16px bottom margin would silently make one gap 40px.
 *   - At most 1180px wide (§2, "Max content width: 1180px"). At 1440x900
 *     the content column is ~1140px, so this binds only on wider windows.
 *   - A section that renders NOTHING must not cost a gap. The AI consent
 *     host is an empty (or hidden-card-holding) div unless the parent asked
 *     to see the AI card; as a flex item it would still take a 24px gap on
 *     BOTH sides, a 48px dead band between the header and the banner. It is
 *     therefore display:none except while it really shows that card.
 *
 * The kids grid itself (the handoff's `repeat(auto-fit, minmax(340px,
 * 1fr))`, gap 20px) is `.ovchildren`'s own rule in app.css. */
#overviewMount {
  display: flex;
  flex-direction: column;
  gap: 24px;
  max-width: 1180px;
  min-width: 0;
}
#overviewMount > * { margin-top: 0; margin-bottom: 0; }
#overviewMount > .ai-filter-prompt-host { display: none; }
#overviewMount.ov-ai-detail > .ai-filter-prompt-host:has(> #aiFilterPrompt:not(.is-hidden)) { display: block; }

/* --- "TODAY AT A GLANCE" -------------------------------------------------
   Her glance section is a titled SECTION; ours started with a bare stack of
   cards and no word for what they were. Since LAYOUT V2 the heading sits
   ABOVE the card grid (`.ovglance-grid`), not inside it — see app.css's
   `.ovchildren` rule for the measured reason. */
#overviewMount .ovchildren > .ovglance-title {
  margin: 0;
  padding-left: 2px;
  font-family: var(--font-heading);
  font-size: 16.5px;
  font-weight: 800;
  line-height: 24.75px;
  letter-spacing: normal;
  color: var(--text-primary);
}

/* --- THE GLANCE CARD -----------------------------------------------------
   Hers is compact: a 32px ROUND face, a 15px name, the chip on the same line.
   Ours was a 54px rounded SQUARE with a 22px name, which is what made our
   card nearly twice the height of hers for the same information.
   COMPACT TILES — owner-requested, Tisha 2026-09-30 23:58Z: "I would like the
   kids tiles to be smaller and side by side on bigger screens. right now its
   a lot of wasted space and lots of scrolling." SPACE ONLY: no line of the
   card is dropped and no type size changes (nothing goes under its current
   size, no target under 44px). 16px padding and an 8px rhythm between the
   card's lines (were 22/12), device rows 6px top and bottom around their 44px
   Remote button (were 10-12), and the V2 controls link shares the action row
   (js/overview.js childCard()). Measured on the demo household (Ben: two
   devices, Maya: one; two pending requests) at 1440x900 — numbers in
   plans/LANE-home-layout-v2-20260930.md; pinned with headroom by
   test/overview-layout-v2-ui.test.js. */
#overviewMount .ovcard.hero { padding: 16px; gap: 8px; }
#overviewMount .ovcard.hero .avatar.lg {
  width: 32px; height: 32px;
  border-radius: 50%;
  font-size: 13px; line-height: 19.5px;
}
#overviewMount .ovcard.hero .ovc-head { align-items: center; gap: 12px; }
#overviewMount .ovcard.hero .ovc-name {
  font-size: 15px; font-weight: 800; line-height: 22.5px; letter-spacing: normal;
}
#overviewMount .ovcard.hero .ovc-state {
  font-size: 12.5px; line-height: 18.75px;
  color: var(--text-secondary);
  margin-top: 1px;
}

/* --- CHIPS ---------------------------------------------------------------
   Hers reserves the 999px pill for exactly this: a small status chip on a
   solid tint. Ours were 22px rounded rectangles — the CARD radius worn by a
   chip — which is why they read as tiny boxes rather than as labels. */
#overviewMount .pill {
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  font-weight: 800;
  padding: 4px 11px;
  line-height: 17.25px;
  letter-spacing: normal;
}
#overviewMount .pill.neutral { background: var(--surface-raised); color: var(--text-secondary); }
#overviewMount .pill.wait    { background: var(--waiting-tint);   color: var(--waiting-text); }
#overviewMount .pill.on      { background: var(--done-tint);      color: var(--done-text); }

/* --- THE METRIC ----------------------------------------------------------
   Hers is ONE line: label left, value right, both 12.5px. Ours stacked a 12px
   label above a 36px numeral; with a real number that was merely louder than
   hers, but with nothing reported it rendered a 36px em-dash that said
   nothing and was the largest thing on the card.

   The ID here deliberately beats app.css's `.ovcard.hero .ovc-time` and
   `.ovcard.hero .ovc-time.ovc-time-none` (both 0,3,0) on specificity. */
#overviewMount .ovc-metric {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
#overviewMount .ovc-metric .ovc-timelabel {
  font-size: 12.5px; line-height: 18.75px; color: var(--text-secondary);
}
#overviewMount .ovc-metric .ovc-time {
  font-size: 12.5px; font-weight: 700; line-height: 18.75px;
  letter-spacing: normal;
  color: var(--text-primary);
  text-align: right;
}
#overviewMount .ovc-metric .ovc-time.ovc-time-none {
  font-size: 12.5px; font-weight: 700; color: var(--text-secondary);
}

/* --- THE BAR -------------------------------------------------------------
   Her track is #EEF1F4; --surface-raised is #F1F3F6, the nearest token and
   within one step of it. */
#overviewMount .ovcard.hero .ovbar {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  overflow: hidden;
}

/* --- ACTIONS -------------------------------------------------------------
   Her primary is FLAT. Ours carried a cyan drop-shadow which on a white page
   reads as a halo under every button. Height and type are hers. The buttons
   themselves, and every destination behind them, are untouched — this is
   paint only. */
#overviewMount .ovc-actions { gap: 8px; margin-top: 0; }
#overviewMount .ovc-actions > .primary,
#overviewMount .ovquick .primary { box-shadow: none; }
#overviewMount .ovc-actions > .primary {
  min-height: 44px; font-size: 14px; font-weight: 800; padding: 12px 18px;
}
/* ONE PRIMARY ACTION PER CARD, PAINT ONLY — the card still offers Pause
 * now / Bedtime (its "+15 min" left the card in LAYOUT V2, owner-requested,
 * Tisha 2026-09-30; see this file's own header for why each one exists), but they
 * read as secondary utilities under "Open {name}'s controls" instead of
 * four buttons of near-identical weight. MEASURED before this rule: 800/14px
 * primary beside 700/14px ghost — one bold step, same size, same 44px
 * height — which is why a real screenshot of Ben's card (pause + bedtime +
 * primary) reads as four peers rather than one action and three helpers.
 * `min-height: 44px` is UNCHANGED and un-negotiable — WCAG's touch-target
 * floor applies to a secondary control exactly as much as a primary one;
 * everything here is font-size/weight/padding, never height. */
#overviewMount .ovc-actions > .ghost {
  min-height: 44px; font-size: 13px; font-weight: 600; padding: 10px 14px;
}
#overviewMount .ovquick .primary,
#overviewMount .ovquick .ghost {
  min-height: 50px; font-size: 14.5px; padding: 13px 22px; gap: 9px;
}

/* --- THE ADD-A-DEVICE NUDGE ----------------------------------------------
   Hers is a solid lavender plane: white icon tile, bold title over a muted
   sentence, filled pill hard right. Ours was a translucent, bordered, 14px
   strip with a ghost button. --attention-tint IS her #ECE8FB. */
#overviewMount .ovnudge {
  background: var(--attention-tint);
  border: 0;
  border-radius: var(--radius-card);
  padding: 22px 26px;
  gap: 20px;
  align-items: center;
}
#overviewMount .ovnudge-icon {
  flex: none;
  width: 44px; height: 44px;
  border-radius: 14px;
  background: var(--dusk);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
}
#overviewMount .ovnudge-icon svg { width: 20px; height: 20px; }
#overviewMount .ovnudge-body {
  flex: 1;
  min-width: 200px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
#overviewMount .ovnudge-title {
  font-size: 15px; font-weight: 800; line-height: 22.5px; color: var(--text-primary);
}
#overviewMount .ovnudge-txt {
  font-size: 14px; line-height: 21px; color: var(--text-secondary);
  flex: none; min-width: 0;
}
/* Filled from --text-primary / --dusk rather than a literal navy so the pair
   INVERTS correctly in dark (near-white plate, dark ink) instead of becoming a
   near-black button sitting on a near-black banner. */
#overviewMount .ovnudge .ovnudge-cta {
  flex: none;
  width: auto;
  min-height: 0;
  margin: 0;
  background: var(--text-primary);
  color: var(--dusk);
  border: 0;
  border-radius: var(--radius-pill);
  padding: 13px 22px;
  font-size: 14px;
  font-weight: 700;
  line-height: 21px;
  box-shadow: none;
}

/* --- TILES ---------------------------------------------------------------
   Home has none since LAYOUT V2 (owner-requested, Tisha 2026-09-30: "Remove
   the 4 little cards"), so the Home-only `.ovtile` edge that lived here is
   gone too. `.ovtile` itself is shared vocabulary (Settings, Kids usage,
   the child glance strip) and keeps its app.css paint. */

/* --- PHONE ---------------------------------------------------------------
   Her rhythm compresses; it does not change shape.

   NOTE THE ABSENCE OF `display:none`. The first draft hid the decorative
   nudge icon here. That is precisely the shape of commit ed3eaf68, which
   silenced a whole surface's confirmation and error text with ZERO test
   failures because the tests asserted textContent and never visibility. This
   tile holds no text and hiding it would have been harmless — but the habit
   is the hazard, so it keeps its box and loses only size. */
@media (max-width: 640px) {
  #overviewMount .ovalerts { padding: 18px; }
  #overviewMount .ovnudge { padding: 18px; gap: 14px; }
  #overviewMount .ovnudge-icon { width: 36px; height: 36px; border-radius: 11px; }
  #overviewMount .ovnudge-icon svg { width: 17px; height: 17px; }
  /* THE ROW ITSELF MUST WRAP. `flex-basis: 100%` cannot move a child onto a
     second line in a nowrap container — it only makes it fight the siblings
     for the one line there is, which is how the text ended up one character
     wide. `.an` takes the full first line, the actions take the second. */
  #overviewMount .ovalerts .arow { align-items: flex-start; flex-wrap: wrap; }
  /* `140px` basis, not `100%`: at 100% the sentence claimed a line of its own
     and left the 34px avatar stranded on the line above it. A basis wide
     enough to be worth reading keeps avatar and sentence together on line
     one, and only the button group wraps to line two. */
  #overviewMount .ovalerts .arow .an { flex: 1 1 140px; }
  #overviewMount .ovalerts .arow .reqactions { flex: 1 1 100%; margin-top: 8px; }
}

/* The link form of "Add a device" when it sits INSIDE the card's action row
   rather than at the foot of a card: `.ovfootlink`'s 14px top margin is for a
   card footer, and here it would push the link off the row's baseline. */
#overviewMount .ovc-actions > .ovc-addlink { margin: 0; align-self: center; }

/* ===========================================================================
 * GOODQA 95, 2026-09-26 — Homepage-Spec.md's own new surfaces.
 * ======================================================================== */

/* --- THE HEADER ROW: the date eyebrow and the greeting ------------------
   §3 put two (then, OWNER DECISION 2026-09-17, three) pills to the right of
   the greeting. The OWNER-REQUESTED MOVE of 2026-09-30 (Tisha) took them —
   and "Add a child" — out of the header; see THE QUICK LINKS below. */
#overviewMount .ovhead-row {
  flex: 1 1 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
/* --- THE QUICK LINKS, DIRECTLY ABOVE "TODAY AT A GLANCE" -----------------
   OWNER-REQUESTED, Tisha 2026-09-30: first "put all these quick link buttons
   under requests" — Send a check-in, Create a pop quiz, View check-ins &
   quizzes, Add a child, in that order — then, LAYOUT V2 the same evening,
   "Put the 'Today at a glance' section right under the new buttons you
   created". The row is the stack's section directly above the kids grid,
   starts on the content column's left edge and wraps; the buttons keep
   their existing paint (`.ghost .ovheadpill` for the three engage pills —
   the class keeps its old name so nothing that styles or finds them moves —
   and plain `.ghost` for Add a child). */
#overviewMount .ovquicklinks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  min-width: 0;
  margin: 0;
}
#overviewMount .ovquicklinks > button { width: auto; margin: 0; }
/* 44px touch-target floor (portal-shell-a11y.test.js) — the spec draws these
   pills 36-40px tall; the repo's own WCAG floor wins, disclosed in the WAL.
   SIZE ONLY — `.ghost` (app.css) already carries theme-correct
   background/border/text tokens for both themes; a second, hardcoded
   `#fff`/`--text-primary` pair here would look right in light mode and
   fail contrast in dark (measured: axe `color-contrast` on this exact
   class in dark mode before this comment was written). */
#overviewMount .ovheadpill {
  box-shadow: none;
  min-height: 44px;
  font-size: 13px;
  font-weight: 700;
  padding: 9px 16px;
}
/* A NAME LINK, NOT A BUTTON. `.ovc-name` already carries the card's real
   name typography (app.css); this strips the <button> chrome a name link
   otherwise inherits, so the only visible change from the old plain <div>
   is the hover/focus underline that says it is reachable. */
#overviewMount button.ovc-name {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  min-height: 0;
  box-shadow: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
#overviewMount button.ovc-name:hover,
#overviewMount button.ovc-name:focus-visible { text-decoration: underline; }

/* --- THE RECOMMENDED BANNER (§4) — one slim row, one item at a time ----
   One section of the LAYOUT V2 stack, straight under the greeting; this
   file only paints it. `--brand-tint`/`--brand-deep` ARE the spec's own
   light-blue tint (`#E8F1FA`) and label ink (`#1B4A73`) IN LIGHT MODE —
   using the tokens rather than those literal hexes is what keeps this
   readable in dark mode too (both tokens carry real dark-theme values;
   the literal hexes do not). */
#overviewMount .ovrec {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  background: var(--brand-tint);
  border: 0;
  border-radius: 16px;
  padding: 12px 16px;
}
#overviewMount .ovrec-label {
  flex: none;
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--brand-deep);
}
#overviewMount .ovrec-text {
  flex: 1 1 260px;
  font-size: 14px;
  line-height: 21px;
  color: var(--text-primary);
}
#overviewMount .ovrec-actions {
  display: flex;
  gap: 8px;
  flex: none;
}
/* SIZE ONLY on both — `.ghost`/`.primary` (app.css) already carry
   theme-correct paint; see `.ovheadpill`'s own comment above for the
   measured dark-mode failure a hardcoded override here would repeat. */
#overviewMount .ovrec-actions .ghost {
  box-shadow: none;
  min-height: 44px;
  padding: 8px 16px;
  font-size: 13px;
}
#overviewMount .ovrec-actions .primary {
  width: auto;
  margin-top: 0;
  box-shadow: none;
  min-height: 44px;
  padding: 8px 16px;
  font-size: 13px;
}

/* --- THE STATUS PILLS (§5.1/§5.2) — up to two real pills per card head --- */
#overviewMount .ovc-lockpill { flex: none; }
/* "Using {app} on {device}." — only ever present while a device is
   genuinely reporting; no layout of its own beyond the shared `.help`
   rhythm the "time unreported" sentence already uses. */
#overviewMount .ovc-nowusing { margin-top: 0; }

/* --- PER-DEVICE ROWS (§5.1) — name / real online-or-last-seen / Remote --- */
#overviewMount .ovc-devices {
  margin-top: 0;
  display: flex;
  flex-direction: column;
}
#overviewMount .ovc-devrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-top: 1px solid var(--border-subtle);
}
/* The REMOTE_MODAL_V2 twin of the row above (js/remote-modal.js buildRow(),
   `.rm-row`, shared with My Devices and Child Controls): the same compact
   6px on Home only — My Devices keeps its own 12px rows. */
#overviewMount .rm-row { padding: 6px 0; }
/* The V2 "{Child}'s controls ->" link as the first item of the action row
   (compact tiles): no extra top margin there, and it never shrinks. */
#overviewMount .ovc-actions > .rm-controls-link { margin: 0; flex: none; }
#overviewMount .ovc-devinfo { min-width: 0; }
#overviewMount .ovc-devname {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-primary);
}
#overviewMount .ovc-devstatus {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--text-secondary);
  margin-top: 2px;
}
#overviewMount .ovc-devstatus.is-online { color: var(--done-text); }
#overviewMount .ovc-devdot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--done-text);
  flex: none;
}
/* `--text-primary` / `--dusk`, NOT a literal navy + white — the SAME pair
   `.ovnudge .ovnudge-cta` above already uses, and for the reason that rule's
   own comment gives: the pair INVERTS correctly per theme (near-white plate
   / dark ink in dark mode) instead of a literal white-on-navy becoming a
   near-black-on-near-black button once `--text-primary` itself inverts. */
#overviewMount .ovc-remotebtn {
  flex: none;
  background: var(--text-primary);
  color: var(--dusk);
  border: 0;
  box-shadow: none;
  min-height: 44px;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 700;
}

/* --- THE EMPTY STATE (§7) — one centered card, replacing the dashed box -- */
#overviewMount .ovemptystate {
  max-width: 420px;
  /* `0 auto`: centred, and the stack's 24px gap (LAYOUT V2) is the space
     above it — its old `24px auto` would have doubled that to 48px. */
  margin: 0 auto;
  text-align: center;
  padding: 32px 28px;
  grid-column: 1 / -1;
}
#overviewMount .ovemptystate .sect { justify-content: center; font-size: 22px; }
#overviewMount .ovemptystate .sect::after { display: none; }
/* `--primary-cta-gradient` / `--ink` — the SAME token pair `.primary`
   (app.css) already pairs for exactly this reason: both invert per theme,
   where a literal `#1B4A73→#4FA8E0` + hardcoded white text would not. */
#overviewMount .ovemptystate .ovempty-cta {
  margin: 16px auto 0;
  display: inline-flex;
  background: var(--primary-cta-gradient);
  color: var(--ink);
  border: 0;
  border-radius: var(--radius-pill);
}
/* Light theme flips `--primary-cta-gradient` to a DARK blue pair, and dark
   ink on it is unreadable (seen in this pass's own first empty-state
   screenshot) — `.primary` (app.css ~1396) already carries this exact
   light-theme override for the same reason, restated for this one class. */
:root[data-theme="light"] #overviewMount .ovemptystate .ovempty-cta { color: #FFFFFF; }

/* --- #enrolBanner SUPPRESSION WHILE OVERVIEW IS OPEN (§1's removal list) --
   THE ONE DELIBERATE EXCEPTION to this file's own "every selector anchored
   to #overviewMount" rule (see the file header). `#enrolBanner` is global,
   page-wide chrome (app.js), outside `#overview` entirely, so it cannot be
   reached through that anchor. Keyed on whether the Overview SECTION is the
   visible screen rather than on a class js/overview.js toggles, because the
   module framework has no "left this screen" hook (core.js) — a toggled
   class would still be on the banner after the parent navigated away. Every
   other screen sees the real banner exactly as setEnrolBanner /
   paintEnrolmentBanner draw it. Plain `display:none`, not
   `visibility`/`opacity` — the same reasoning this file's own header gives
   for touching neither on any live control here. */
body:has(#overview:not(.hide)) #enrolBanner { display: none !important; }

/* PHONE: the quick links STACK, one full-width button per line, in DOM
   order. In the header, the pills shared one row to save height above the
   first child card; under the Requests card that saving no longer applies
   (they are below the kids), and four buttons sharing a 343px row squeezed
   "View check-ins & quizzes ›" onto two lines — measured on the approved
   preview's own 375px capture, where even three did. Full width also keeps
   every label on one line at the 320px floor. */
@media (max-width: 640px) {
  #overviewMount .ovquicklinks { flex-direction: column; align-items: stretch; }
}

/* --- THE TWO LARGE ACCOUNT-CHOICE CARDS (§1's removal list) --------------
   "Remove ... the two large 'ONE ACCOUNT CHOICE' cards (Parent Guard, AI
   filtering). They become a single slim 'Recommended' banner." The cards'
   JS (js/parent-guard.js, js/ai-filter.js) is untouched and is still what
   decides whether each choice is really pending and what a click really
   does — the slim banner READS their state and clicks their own buttons —
   so they are hidden here, not removed, and only inside #overview. Plain
   `display:none`, the same reasoning as #enrolBanner above; on any other
   screen the same shared node keeps whatever placement ai-filter.js gives
   it there. */
#overview #parentGuardPrompt,
#overview #aiFilterPrompt { display: none !important; }
/* ...EXCEPT the AI card AFTER the parent asked to turn the check on: the
   slim banner's "Turn on" reveals the real consent card (the one that says
   what is sent) rather than switching anything on — Homepage-Spec.md §4. */
#overview #overviewMount.ov-ai-detail #aiFilterPrompt:not(.is-hidden) { display: block !important; }

/* The add-child form carries five44px age pills. Keep its phone framing
   compact enough for both Save and Cancel above the navigation dock. */
@media (max-width: 620px) {
  #overview #profForm > p { margin-block: 2px; }
  #overview #profForm > h3 { margin-block: 2px; }
  #overview #profForm > .fld { margin-bottom: 4px; }
  #overview #profForm .chips { gap: 4px; margin-block: 4px; }
  #overview #profSaveBtn, #overview #profCancelBtn { display: inline-block; width: calc(50% - 4px); vertical-align: top; }
  #overview #profCancelBtn { margin-left: 4px; }
}
