/* ===========================================================================
 * 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; }

/* --- THE 1280→1024 TWO-COLUMN CRUSH -----------------------------------
 *
 * MEASURED (2-child fixture, real Playwright, `#overviewMount`'s own
 * `grid-template-columns: minmax(0,1.3fr) minmax(0,1fr)`, app.css ~565):
 *
 *   viewport   left(.ovquick/.ovtiles/.ovalerts)   right(.ovchildren)
 *   1440px     634px                                488px
 *   1280px     517px                                445px
 *   1024px     261px                                445px  <- PINNED
 *
 * From 1280 to 1024 the RIGHT track does not move AT ALL — every one of the
 * 256px the viewport lost landed on the LEFT track alone, crushing it (and,
 * downstream, the request row's own hostname text — see the container query
 * below). `.ovchildren` already carries `min-width: 0` (app.css:9611) and
 * its OWN internal grid already declares `minmax(0, 1fr)`-style flexibility
 * — that is the part of the fix that is NOT missing. What actually pins the
 * track is that a `minmax(0, Nfr)` column's FLEXIBLE ('fr') sizing step
 * still consults an item's own CONTENT-based size when handing out the
 * available space, even once the item's own `min-width` is 0 — confirmed by
 * disabling that consultation directly: `contain: inline-size` alone (no
 * other change) took the track from 445px pinned to genuinely responsive.
 *
 * THE FIX IS SIZE CONTAINMENT PLUS AN EXPLICIT, ALREADY-CHOSEN FLOOR, not a
 * bigger `min-width: 0`. `380px` is not a new number — it is `.ovchildren`'s
 * own internal `minmax(min(100%, 380px), 1fr)` card floor a few lines below,
 * restated as the OUTER track's floor too, so the column can never render a
 * card narrower than this file already decided one card needs.
 *
 * GUARDED BEHIND THE SAME `>=900px` THE TWO-COLUMN BAND ITSELF IS (app.css's
 * own `#overviewMount` comment: "Scoped to >=900px on purpose: below it the
 * single column stacks in DOM order"). MEASURED FAILURE of an unguarded
 * version: at a real 320px phone width, `.ovchildren` has no competing
 * sibling column at all — it is one block in a single stacked layout — and a
 * bare 380px floor there is WIDER than the viewport itself, forcing the
 * whole document to scroll sideways (`document.documentElement.scrollWidth`
 * measured 390px against a 320px viewport). The floor only means anything
 * once there is a second track fighting it for space.
 */
@media (min-width: 900px) {
  #overviewMount .ovchildren {
    contain: inline-size;
    min-width: 380px;
  }
  /* A real pending decision owns the next full row after quick actions.
   * Truthful status tiles share the preceding row, keeping their headline
   * numbers above the fold. The child grid and chart follow the decision.
   * Without a pending ask, leave the original compact two-track layout. */
  #overviewMount.ov-has-pending-request > .ovquick {
    order: 19; grid-column: 1; grid-row: auto;
  }
  #overviewMount.ov-has-pending-request > .ovtiles {
    order: 20; grid-column: 2; grid-row: auto;
  }
  #overviewMount.ov-has-pending-request > .ovalerts {
    order: 21; grid-column: 1 / -1; grid-row: auto; width: 100%;
  }
  #overviewMount.ov-has-pending-request > .ovchildren {
    order: 22; grid-column: 1 / -1; grid-row: auto; width: 100%;
  }
}

/* app.css gives 900-1399px mounts one explicit content track. Its older
 * per-card grid-column: 2 rules can still create an implicit second track;
 * at 1024px that squeezed decisions to ~261px and put the child card first.
 * Keep the rail, but place the pending task and child detail in one track. */
@media (min-width: 900px) and (max-width: 1399.98px) {
  #overviewMount.ov-has-pending-request { grid-template-columns: minmax(0, 1fr); }
  #overviewMount.ov-has-pending-request > .ovquick,
  #overviewMount.ov-has-pending-request > .ovalerts,
  #overviewMount.ov-has-pending-request > .ovchildren,
  #overviewMount.ov-has-pending-request > .ovtiles {
    grid-column: 1;
    grid-row: auto;
  }
  #overviewMount.ov-has-pending-request > .ovquick    { order: 20; }
  #overviewMount.ov-has-pending-request > .ovalerts   { order: 21; }
  #overviewMount.ov-has-pending-request > .ovchildren { order: 22; }
  #overviewMount.ov-has-pending-request > .ovtiles    { order: 23; }
}

/* --- "TODAY AT A GLANCE" -------------------------------------------------
   Her right-hand column is a titled SECTION; ours started with a bare stack
   of cards and no word for what they were. Spans whatever column count
   .ovchildren's auto-fit resolves to. */
#overviewMount .ovchildren > .ovglance-title {
  grid-column: 1 / -1;
  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. */
#overviewMount .ovcard.hero { padding: 22px; gap: 12px; }
#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: 4px; }
#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 / +15 min (this pass does not own removing a working
 * control; 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 ---------------------------------------------------------------
   Left exactly where they are: test/reading-load-ui.test.js measures
   `tilesBelowFold` at 1440x900 and moving them under the requests card (where
   her Home, which has no tiles at all, would imply) would break the
   five-second rule. They get the CARD edge they should have had instead of
   the control edge, so they stop reading as grey boxes beside her soft
   cards. */
#overviewMount .ovtile {
  border-color: var(--border-subtle);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}

/* --- 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; }
