/* fh-shell.css - one shell for every module page.
   Created September 1, 2026 (Lane 4, Phase 1).

   WHY THIS FILE EXISTS
   --------------------
   Twenty-two module pages each carried their own header, their own number of stat
   tiles, their own bottom bar and their own word for "the list of records". The
   August 31 UI review measured it: two different page headers, seven names for the
   same list, stat tiles in counts of none, two and three, and a primary action that
   moved around the bottom bar. A user learned the vocabulary once per module.

   This stylesheet is the visual half of the fix. fh-shell.js is the behavior half.
   The mock at _documents/ui-review-2026-08-31/mock.html is the target, and its CSS
   is where most of these rules came from.

   TWO RULES THIS FILE OBEYS
   -------------------------
   1. EVERY BRAND COLOR IS A TOKEN. Not one violet literal. --brand, --brand-bright,
      --brand-deep and --brand-rgb are set by brand-apply.js from the client's own
      primary, so the hero, the rings, the tabs, the chips, the buttons and the
      bottom nav recolor for a client with no further work. The fallbacks in the
      var() calls are F&H's own purple, used only when no client brand has loaded.
   2. THE STOPLIGHT IS FIXED ON EVERY CLIENT. Red means "require attention" and
      amber means "coming up" whatever color the client picked, because risk has to
      read the same everywhere. Those four are literals on purpose.

   NAMING. Every class here starts fhs-. The module pages already own .card, .chip,
   .empty, .stat, .tabs, .search and .btn, and an unprefixed rule in a stylesheet
   that loads after their inline <style> would silently restyle their innards. The
   prefix is what keeps this additive.

   SCOPE. Everything is scoped to body:has(#scroll), the same test fh-theme.css uses
   to mean "a module page". index.html and the Executive Dashboard carry header.bar
   too and are deliberately untouched. */

/* #cdView is here for one reason: inside the builder's console the client home is a
   PREVIEW, and its palette is set on that box rather than on the page, so the
   operator's own screens keep F&H violet. Custom properties inherit downward, so the
   tokens have to be re-declared at the box for the value set there to be the one the
   tiles inside it read. On a client's own phone the same box is their whole screen
   and this changes nothing. TD-19, September 4, 2026. */
body:has(#scroll), body.fhs-host, #cdView{
  --fhs-b:var(--brand,#5B2BD6);
  --fhs-bb:var(--brand-bright,#7138EE);
  --fhs-bd:var(--brand-deep,#3F1C9C);
  --fhs-brgb:var(--brand-rgb,91,43,214);
  /* THE INK ON A BRAND FILL. brand-apply.js works out which of near-black and
     near-white is more readable on this client's mid tone and publishes it as
     --brand-on; the same question for the accent is --accent-on. Every place a
     label or an icon sits on a brand-colored fill reads this instead of writing
     #fff, because #fff on a safety-yellow client is 1.9:1 and unreadable. The
     fallback is white, which is right for F&H's own violet. */
  --fhs-b-on:var(--brand-on,#fff);
  --fhs-a-on:var(--accent-on,#fff);
  /* THE COUNTER-SHADE ON A BRAND PANEL. brand-apply.js works out the smallest wash
     that puts --brand-on over 4.5:1 on the worst pixel a word can land on, and it is
     the opposite of the ink: black under white words, white under dark ones. The
     fallback is F&H violet's own answer, so a page that somehow loads without
     brand-apply.js gets the same panel it would have had with it. */
  --fhs-scrim:var(--brand-scrim,rgba(0,0,0,.26));
  /* Amber, readable on a dark panel. The stoplight amber above is tuned for ink on
     white; on a dark tile it is the same hue lifted until it clears the floor. */
  --fhs-amb-dk:#F3C47E;
  /* The same stoplight amber as an r,g,b triplet, so a translucent amber fill is
     written as the token rather than as three numbers that happen to match it. */
  --fhs-amb-rgb:245,183,0;
  --fhs-ink:var(--ink,#16181F);
  --fhs-ink2:var(--ink-2,#4A4C66);
  /* The tone this replaced was measured at 4.16:1 on the home's own background on
     September 4,
     2026, which is under the 4.5 body line. It carries the area counts and every
     tile's fact line, so it is the second most read tone on the screen. Darkened
     until it clears, and no further: it still has to read as the quiet one. */
  --fhs-ink3:var(--ink-3,#585F73);
  --fhs-line:var(--line,rgba(20,22,45,.09));
  --fhs-surface:var(--surface,#fff);
  /* fixed stoplight, every client */
  --fhs-red:#E12D3D;
  --fhs-amb:#F5B700;
  --fhs-amb-ink:#714B00;
  --fhs-grn:#00A85A;
  --fhs-shadow:0 10px 26px -18px rgba(var(--fhs-brgb),.55),0 1px 3px rgba(20,22,45,.06);
}

/* ============================================================================
   DARK MODE, AND THE BUG BRYANT REPORTED ON SEPTEMBER 4, 2026
   ============================================================================
   "Dark text on dark tiles in dark mode." Measured on that date, on a Basin Ridge
   home in a real browser: a tile's own name came out at 1.01:1. Not close, not
   marginal: the same color as the tile it was printed on.

   The cause is one missing half of a pair. The tokens above read the app's own
   --surface, which dashboard.css redefines per theme, and the app's own --ink, which
   dashboard.css DOES NOT DEFINE AT ALL. So the surface followed the theme into the
   dark and the ink stayed on the literal fallback written beside it, which is a
   near-black picked for white paper. Every fallback in that block is a light-mode
   answer, so every one of them is wrong in the dark, and each was wrong quietly.

   The fix is the pair completed rather than a color patched. This block gives the
   same four ink tones their dark-mode values, and the stoplight amber the lifted
   version it needs on a dark panel.

   DARK IS THE DEFAULT AND LIGHT IS THE OVERRIDE in this product: dashboard.css puts
   the dark values on :root and the light ones on :root[data-theme="light"]. So the
   selector here is :not([data-theme="light"]), which covers a page that has not been
   told either way as well as one explicitly set to dark. Writing it the other way
   round would leave the default state on the light-mode inks, which is the state a
   page is in before the theme script has run.

   tests/verify_td19_contrast_dom.js measures every word and every glyph on both home
   screens in both themes and holds the 4.5:1 and 3:1 floors. */
:root:not([data-theme="light"]) body:has(#scroll),
:root:not([data-theme="light"]) body.fhs-host,
:root:not([data-theme="light"]) #cdView{
  --fhs-ink:#EEF0F6;        /* titles, near-white */
  --fhs-ink2:#B6BED2;       /* area names and second-rank labels */
  --fhs-ink3:#A7AFC4;       /* fact lines, a readable gray-blue */
  --fhs-line:rgba(255,255,255,.09);
  /* Amber that means "late" has to keep meaning it on a dark tile, so it is the
     lifted version rather than the ink-on-white one. */
  --fhs-amb-ink:var(--fhs-amb-dk);
}

/* ============================================================================
   THE HEADER
   Back chevron, the area label over the module's noun, the client's logo.

   fh-theme.css already gives header.bar the brand gradient, the hairline weave
   and the client emblem watermark on every module page. That treatment is kept
   verbatim; what changes here is what the bar SAYS. It used to carry the client's
   company name in 48px serif, which is the right thing on a home screen and the
   wrong thing on a working page: a page you opened by tapping a word should say
   that word back to you.

   #barTitle stays in the DOM and stays the module's own element, because every
   module's setTitle() writes to it and none of that code is touched. fh-shell.js
   reads it and mirrors it into #fhsTitle. Hidden with visibility rather than
   display so the module's own layout math, which measures it, still works. */
body:has(#scroll) header.bar #barTitle{
  position:absolute;
  width:1px;
  height:1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}
body:has(#scroll) header.bar h1.fhs-title,
body:has(#scroll) header.bar .fhs-title{
  margin:0;
  position:relative;
  z-index:1;
  flex:1;
  min-width:0;
  color:#fff;
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-weight:800;
  font-size:17px;
  letter-spacing:-.01em;
  line-height:1.22;
  /* two lines at most, then it clips rather than pushing the logo off the bar */
  overflow:hidden;
}
body:has(#scroll) header.bar .fhs-title small{
  display:block;
  font-family:var(--mono,'IBM Plex Mono',ui-monospace,monospace);
  font-weight:500;
  font-size:9.5px;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:rgba(255,255,255,.82);
  margin-bottom:2px;
  /* IT WRAPS AT A SPACE; IT IS NEVER CUT. DESKTOP-1, October 7, 2026 (the DEMO-1 walk). This was
     nowrap with an ellipsis, and at 390 wide, with the language button and the signed-in name on
     the same bar, the area came out as "COMPLIANCE PROGRAMS AND REC...", which is half a word and
     reads as broken. The area name now takes a second line when it needs one, breaking only
     between words. The two lines are short (9.5 pixels), so the bar grows by one line at most. */
  white-space:normal;
  overflow-wrap:normal;
  word-break:normal;
  line-height:1.3;
}
/* The chevron is always there now. On the module's own home it goes back to the
   dashboard; on a sub-screen the module's own #back handler is still what runs.
   fh-shell.js decides which; this only says what it looks like. */
body:has(#scroll) header.bar .fhs-back{
  position:relative;
  z-index:1;
  width:44px;
  height:44px;
  min-width:44px;
  min-height:44px;
  border-radius:12px;
  display:flex;
  align-items:center;
  justify-content:center;
  flex:0 0 auto;
  color:#fff;
  background:rgba(255,255,255,.14);
  border:1px solid rgba(255,255,255,.24);
}

/* THE MODULE HERO RETIRES. The header says the area and the noun, so a second
   headline block at the top of the page body says it again in different words,
   which is findings 3 and 4 of the review sitting one above the other.
   This rule hides it for the one frame between the module drawing it and
   fh-shell.js taking it out of the document, so it never flashes. Taking it out is
   what actually retires it: a block hidden with display:none keeps its h1 in the
   outline, and the page then has two h1s that disagree.
   Scoped to .fhs-shelled, which fh-shell.js puts on the body when it mounts, so a
   page whose script does not load renders exactly as it did before this file
   existed. The explanatory paragraph inside is not hidden, it is moved to About. */
body.fhs-shelled:has(#scroll) #scroll.fhs-home > .home-head{
  /* SCOPED TO THE MODULE'S OWN HOME, and to a DIRECT child of #scroll.
     Several modules reuse .home-head for a sub-screen heading: the title of the record
     you opened, or "Mark this action done". Those are the page saying what screen you
     are on, which is the opposite of the duplication this rule removes. */
  display:none;
}

/* ============================================================================
   THE TABS: Records, Reports, About. Three, in that order, on every module.

   "Records" is the word everywhere. The review counted seven names for the same
   list across the estate (REPORTS, ANALYSES, INSPECTIONS, CERTIFICATION BOARD,
   MEETINGS, OVERDUE, POLICIES), and one vocabulary is item I of the proposal. The
   mock's frame 4 draws Insurance with Policies / Requirements / About; that frame
   predates item I in the same document and Records wins, which is also what the
   Lane 4 work order asks for by name. */
.fhs-tabs{
  display:flex;
  gap:6px;
  margin:0 0 12px;
  background:color-mix(in srgb,var(--fhs-surface) 55%,transparent);
  border:1px solid var(--fhs-line);
  border-radius:12px;
  padding:4px;
}
.fhs-tab{
  flex:1;
  text-align:center;
  font-size:12.5px;
  font-weight:600;
  padding:0;
  /* 44, not the 38 the mock draws. Every control in this estate that a finger
     lands on is 44px, because the hand pressing it is wearing gloves in a truck,
     and audit_mobile.js measures it. The mock was drawn at desk scale. */
  min-height:44px;
  border-radius:9px;
  border:0;
  background:none;
  color:var(--fhs-ink2);
  cursor:pointer;
  font-family:inherit;
}
.fhs-tab[aria-selected="true"]{
  background:linear-gradient(135deg,var(--fhs-bb),var(--fhs-b));
  color:#fff;
  box-shadow:0 6px 14px -8px rgba(var(--fhs-brgb),.7);
}

/* ============================================================================
   THE STAT STRIP: exactly three, always in the same order.

   Red "Require attention", amber "Active" or "Coming up", neutral "Closed this
   year" or "Total". The review's finding 6 was that some modules showed two tiles
   with a third orphaned onto the next row and nothing told the eye which number
   was the one to worry about. Three in a fixed order fixes both: the leftmost is
   always the one that needs you.

   The numbers still come from each module's own engine. fh-shell.js asks the page
   for them; it does not compute them. */
.fhs-stats{
  display:grid;
  grid-template-columns:1fr 1fr 1fr;
  gap:8px;
  margin:0 0 12px;
}
.fhs-st{
  background:var(--fhs-surface);
  border:1px solid var(--fhs-line);
  border-radius:14px;
  padding:10px 11px 9px;
  box-shadow:var(--fhs-shadow);
  position:relative;
  overflow:hidden;
  min-width:0;
}
/* UI-ACCENT-1, September 23, 2026: the three tiles used to carry a 4px bar down the left in
   red, amber and green (and a gray one when a tile had no state). Status is a leading dot now,
   in front of the label, in the same color family, and the number keeps its own color. The
   dot is 8px, the Operations Center's size, and it reads the --status-dot-* tokens from
   fh-theme.css, which point at the approved pill rings on a light page, so it clears 3:1 on the
   white tile. The fallback after each token is this sheet's own fixed stoplight, so the dot
   keeps its meaning on a page that loads this sheet without the theme; no new literal is
   added here, because verify_shell.js holds this file's colors to a fixed list. A tile with
   no state class gets no dot. */
:is(.fhs-st.fhs-attention,.fhs-st.fhs-active,.fhs-st.fhs-closed) .fhs-l::before{
  content:'';
  display:inline-block;
  width:var(--status-dot-size,8px);
  height:var(--status-dot-size,8px);
  border-radius:50%;
  margin-right:6px;
  vertical-align:.02em;
}
.fhs-st .fhs-n{
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-size:22px;
  font-weight:800;
  line-height:1;
}
.fhs-st .fhs-l{
  font-size:10.5px;
  color:var(--fhs-ink3);
  margin-top:3px;
  line-height:1.25;
}
.fhs-st.fhs-attention .fhs-l::before{background:var(--status-dot-stop,var(--fhs-red))}
.fhs-st.fhs-attention .fhs-n{color:var(--fhs-red)}
.fhs-st.fhs-active .fhs-l::before{background:var(--status-dot-caution,var(--fhs-amb))}
.fhs-st.fhs-active .fhs-n{color:#C27A00}
.fhs-st.fhs-closed .fhs-l::before{background:var(--status-dot-safe,var(--fhs-grn))}
.fhs-st.fhs-closed .fhs-n{color:var(--fhs-ink)}
/* OFFLINE-4: a tile with nothing to count yet keeps its label and holds no number, and a tile
   counted from a saved copy is quieter than a live one. The line under the row says which. */
.fhs-st.fhs-held .fhs-n{color:var(--fhs-ink3)}
.fhs-st.fhs-stale .fhs-n{opacity:.78}
.fhs-fresh{margin:-4px 2px 12px;font-size:12px;line-height:1.4;color:var(--fhs-ink3)}
.fhs-fresh b{font-weight:700;color:var(--fhs-ink)}
.fhs-fresh[data-kind="pending"],.fhs-fresh[data-kind="missing"]{color:var(--fhs-ink)}

/* ============================================================================
   SEARCH AND STATUS CHIPS, on every Records tab.

   Finding 9: filters existed only where a module author thought of them, and
   there was no search anywhere in the product. The box is on every Records tab
   from Phase 1; Phase 6 puts Postgres full-text behind it. Until then it filters
   the records already on the page, which is the honest half and is useful on its
   own for a client with two hundred JSAs. */
.fhs-search{
  display:flex;
  align-items:center;
  gap:8px;
  background:var(--fhs-surface);
  border:1px solid var(--fhs-line);
  border-radius:12px;
  padding:0 12px;
  margin:0 0 10px;
  box-shadow:var(--fhs-shadow);
}
.fhs-search svg{
  width:15px;
  height:15px;
  flex:0 0 auto;
  color:var(--fhs-b);
}
.fhs-search input{
  flex:1;
  min-width:0;
  border:0;
  outline:none;
  background:none;
  color:var(--fhs-ink);
  font:inherit;
  font-size:14px;
  /* 44px so a gloved thumb can land on it, matching every other control here */
  min-height:44px;
}
.fhs-search input::placeholder{color:var(--fhs-ink3)}
.fhs-chips{
  display:flex;
  gap:6px;
  margin:0 0 10px;
  flex-wrap:wrap;
}
.fhs-chip{
  font-size:11.5px;
  font-family:inherit;
  padding:0 14px;
  min-height:44px;      /* same rule as the tabs above */
  min-width:44px;       /* TD-29: the "All" chip measured 43px wide in real Chromium, one
                           pixel under the 44px press rule on nine module pages at once */
  border-radius:22px;
  border:1px solid rgba(var(--fhs-brgb),.25);
  background:var(--fhs-surface);
  color:var(--fhs-ink2);
  cursor:pointer;
}
.fhs-chip[aria-pressed="true"]{
  background:linear-gradient(135deg,var(--fhs-bb),var(--fhs-b));
  color:#fff;
  border-color:transparent;
}
/* A record the search or the chips filtered out. Set on the module's own card
   element, which is why this is one property and nothing else: the card itself
   is the best thing in the product and the review says keep it unchanged. */
.fhs-hidden{display:none !important}
.fhs-nohits{
  padding:26px 16px;
  text-align:center;
  color:var(--fhs-ink3);
  font-size:13px;
}

/* ============================================================================
   THE BOTTOM BAR: one primary action, fixed bottom right, always.

   Finding 7: the primary action was sometimes left, sometimes right, sometimes
   absent, and sometimes sat beside a second button of equal weight. A user should
   be able to reach for the bottom right corner with a gloved thumb and always get
   "new thing".

   The module's own #actionbar element is KEPT and restyled rather than replaced.
   It already carries the safe-area padding, the max-width centering and the
   fh-theme.css rule that keeps it up on an empty module, and all of that is still
   right. What changes is that its contents are sorted: one primary on the right,
   everything else behind More. */
body:has(#scroll) .actionbar.fhs-bar{
  justify-content:flex-end;
  gap:9px;
  border-top:0;
  background:linear-gradient(to top,var(--bg,#F3F4FA) 62%,transparent);
  backdrop-filter:none;
  box-shadow:none;
}
body:has(#scroll) .actionbar.fhs-bar .fhs-primary{
  flex:0 1 auto;
  min-height:52px;
  padding:0 26px;
  border:0;
  border-radius:14px;
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-weight:700;
  font-size:15px;
  color:#fff;
  cursor:pointer;
  background:linear-gradient(135deg,var(--fhs-bb),var(--fhs-b) 60%,var(--fhs-bd));
  box-shadow:0 14px 28px -14px rgba(var(--fhs-brgb),.8),inset 0 1px 0 rgba(255,255,255,.18);
}
body:has(#scroll) .actionbar.fhs-bar .fhs-more{
  flex:0 0 auto;
  min-width:52px;
  min-height:52px;
  border-radius:14px;
  border:1.5px solid rgba(var(--fhs-brgb),.35);
  background:var(--fhs-surface);
  color:var(--fhs-b);
  font:inherit;
  font-weight:700;
  font-size:15px;
  cursor:pointer;
}
/* The More sheet. Everything the module used to put in the bottom bar beside the
   primary: export, broker request, self assessment, client rules, the digest. */
.fhs-sheet{
  position:fixed;
  inset:0;
  z-index:40;
  display:flex;
  align-items:flex-end;
  justify-content:center;
  background:rgba(12,14,32,.45);
}
.fhs-sheet[hidden]{display:none}
.fhs-sheet .fhs-sheet-in{
  width:100%;
  max-width:720px;
  background:var(--fhs-surface);
  border-radius:20px 20px 0 0;
  padding:16px 16px calc(20px + env(safe-area-inset-bottom,0px));
  box-shadow:0 -18px 44px -20px rgba(20,22,45,.5);
}
.fhs-sheet h2{
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-size:12px;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--fhs-ink3);
  margin:0 0 12px;
}
.fhs-sheet .fhs-sheet-list{display:flex;flex-direction:column;gap:9px}
.fhs-sheet .fhs-sheet-list > *{
  width:100%;
  min-height:50px;
  border-radius:12px;
  border:1.5px solid var(--fhs-line);
  background:var(--fhs-surface);
  color:var(--fhs-ink);
  font:inherit;
  font-size:15px;
  font-weight:600;
  cursor:pointer;
}

/* A RECORD DETAIL'S MORE. DESIGN-FIX-1, September 28, 2026 (design review finding 08). The
   labeled button that opens the detail's More actions sheet sits at the left of the module's own
   bar and takes only the room its word needs, so the one primary and the one common secondary keep
   the rest. In the sheet, a removal is kept last and set apart by a gap, in the danger ink, so it
   is never the item a thumb lands on by habit. */
body:has(#scroll) .actionbar .fhs-dmore{flex:0 0 auto;min-width:64px;padding-left:16px;padding-right:16px}
.fhs-detail-sheet .fhs-sheet-list > [data-fhs-more="remove"]{margin-top:10px;color:var(--fh-signal-red-text)}

/* ============================================================================
   THE PANELS, AND WHAT WAS THE ABOUT TAB.

   Finding 4 of the August review moved each page's sentence headline and its
   explanatory paragraph into an About tab. SUITE-FIX-1 (October 1, 2026) retired that
   tab at Bryant's request: the description is deleted, and the few sentences a reader
   needs sit at the foot of the Records tab instead (below). */
.fhs-panel[hidden]{display:none}
.fhs-about-lead{
  font-size:14px;
  line-height:1.5;
  color:var(--fhs-ink2);
  background:var(--fhs-surface);
  border:1px solid var(--fhs-line);
  border-radius:14px;
  padding:15px 16px;
  box-shadow:var(--fhs-shadow);
  margin:0 0 12px;
}
/* SUITE-FIX-1, October 1, 2026: the About tab is gone, and with it .fhs-about-moved (nothing is
   moved any more). .fhs-about-lead stays: the Profile sheet uses it. What a reader needs from the old
   About copy is now one note at the foot of the Records tab, #fhsRecNote, drawn by paintRecNote. */
.fhs-rec-note{
  font-size:12.5px;
  line-height:1.5;
  color:var(--fhs-ink2);
  margin:18px 2px 8px;
}
/* A tab or the whole tab bar is hidden by the [hidden] attribute (paintReportsTab); the bar is a flex
   box, which would otherwise win over the user-agent rule for [hidden]. */
.fhs-tabs[hidden], .fhs-tab[hidden]{ display:none !important }

/* A checklist row that is also the way to go and do the thing. The empty state
   offers ONE button; a prerequisite is a row here rather than a second button
   competing with it, and the row itself is the tap target so nothing is lost. */
.fhs-steps li[data-edit],
.fhs-steps li[data-add]{
  cursor:pointer;
  color:var(--fhs-b);
  font-weight:600;
  min-height:44px;
}
.fhs-steps li[data-edit]::after,
.fhs-steps li[data-add]::after{
  content:'\203A';
  margin-left:auto;
  font-size:19px;
  line-height:1;
  color:var(--fhs-b);
}

/* The section rule the home page uses, reused above each About block so the tab
   does not read as a wall of text. */
.fhs-sech{
  display:flex;
  align-items:center;
  gap:10px;
  margin:14px 2px 8px;
}
.fhs-sech h3{
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-weight:700;
  font-size:11.5px;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--fhs-ink2);
  white-space:nowrap;
  margin:0;
}
.fhs-sech .fhs-rule{
  flex:1;
  height:1px;
  background:linear-gradient(90deg,var(--fhs-b),transparent);
}

/* ============================================================================
   THE EMPTY STATE: one sentence, one button, prerequisites as a checklist.

   Finding 8: "Add your first policy" and "Set your legal entity name first" were
   two buttons side by side, and the second is a prerequisite for the first. New
   clients live in empty states for their first week. One button, and the order
   shown as a checklist under it rather than as a competing tap target. */
.fhs-empty{
  background:var(--fhs-surface);
  border:1px solid var(--fhs-line);
  border-radius:16px;
  padding:20px 16px;
  text-align:center;
  box-shadow:var(--fhs-shadow);
}
.fhs-empty .fhs-ring{
  width:52px;
  height:52px;
  border-radius:50%;
  margin:0 auto 10px;
  display:grid;
  place-items:center;
  background:radial-gradient(circle at 50% 38%,rgba(var(--fhs-brgb),.18),rgba(var(--fhs-brgb),.04));
  box-shadow:0 0 0 1px rgba(var(--fhs-brgb),.35),inset 0 0 0 1px rgba(255,255,255,.6);
}
.fhs-empty .fhs-ring svg{width:24px;height:24px;color:var(--fhs-b)}
.empty > img.fhs-module-icon{
  display:block;
  width:80px;
  height:80px;
  object-fit:contain;
  border-radius:0;
  margin:0 auto 14px;
  background:transparent;
  box-shadow:none;
  filter:drop-shadow(0 2px 2px rgba(15,20,40,.22));
}
:root:not([data-theme="light"]) .empty > img.fhs-module-icon{
  filter:drop-shadow(0 0 1px rgba(235,240,255,.65)) drop-shadow(0 3px 3px rgba(0,0,0,.35));
}
.fhs-empty b{
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-size:15px;
}
.fhs-empty p{
  font-size:13px;
  color:var(--fhs-ink2);
  line-height:1.45;
  margin:6px 0 12px;
}
.fhs-steps{
  text-align:left;
  list-style:none;
  font-size:12.5px;
  margin:0 auto;
  padding:0;
  max-width:290px;
}
.fhs-steps li{
  padding:7px 0;
  border-bottom:1px solid var(--fhs-line);
  display:flex;
  gap:8px;
  align-items:center;
  color:var(--fhs-ink2);
}
.fhs-steps li:last-child{border-bottom:0}
.fhs-steps li::before{
  content:'';
  width:14px;
  height:14px;
  border-radius:50%;
  border:2px solid #C9C4DE;
  flex:0 0 auto;
}
.fhs-steps li.fhs-done::before{background:var(--fhs-grn);border-color:var(--fhs-grn)}
.fhs-steps li.fhs-done{color:var(--fhs-ink3)}

/* ============================================================================
   ROOM AT THE BOTTOM.

   Every module page already pads .scroll by 120px plus the safe area, which was
   sized for a full-width bar of one or two buttons. The shell's bar is one pill
   and is no taller, so that padding still holds. What is new is the tab strip at
   the top of the Records panel, which scrolls with the content the way the mock
   draws it, so nothing needs to be pushed down for it. */
body:has(#scroll) .fhs-top{
  margin:0 0 2px;
}
/* The Reports panel lists what used to sit in the bottom bar, plus whatever the
   module offers as a report. It is deliberately plain: these are actions, not a
   second dashboard. */
.fhs-report-list{display:flex;flex-direction:column;gap:9px}
.fhs-report-list > *{
  width:100%;
  min-height:52px;
  border-radius:12px;
  border:1.5px solid var(--fhs-line);
  background:var(--fhs-surface);
  color:var(--fhs-ink);
  font:inherit;
  font-size:15px;
  font-weight:600;
  text-align:left;
  padding:0 16px;
  cursor:pointer;
  box-shadow:var(--fhs-shadow);
}
.fhs-note{
  font-size:12.5px;
  color:var(--fhs-ink3);
  line-height:1.45;
  margin:10px 2px 0;
}

/* A phone in a truck, with gloves on. Every control a finger lands on in this file
   is at least 44 by 44, which is what audit_mobile.js and verify_taps.js measure.
   Type gets smaller on a narrow screen; the press target does not. */
@media(max-width:430px){
  .fhs-tab{font-size:12px}
  .fhs-st .fhs-n{font-size:20px}
  .fhs-st .fhs-l{font-size:10px}
}

/* ============================================================================
   WHAT A TAB ACTUALLY HIDES.

   The Records tab IS the module's own rendered content, so switching to Reports
   or About cannot mean "render something else": it means hide the module's own
   children and show one panel. Everything the shell owns is excluded by id, and
   the module's children are hidden by being everything else. Written that way
   round on purpose: a module that renders a new kind of block tomorrow is hidden
   correctly without this rule knowing the block exists. */
body:has(#scroll) #scroll.fhs-off-records > *{
  display:none !important;
}
body:has(#scroll) #scroll.fhs-off-records > #fhsTop,
body:has(#scroll) #scroll.fhs-off-records > #fhsReports:not([hidden]){
  /* :not([hidden]) is load-bearing. display:block with !important beats the sheet
     rule that hides a [hidden] element, so without it BOTH panels showed at once
     and the Reports tab printed the About text underneath its own. */
  display:block !important;
}
/* SUITE-FIX-1, October 1, 2026: WHILE THE PAGE'S OWN LIST HAS NO ANSWER. The module's own
   content under the tabs is covered by the one line in #fhsListWait (fh-shell.js paintListWait), so
   an empty state drawn from a list nobody has read never shows. Same shape as the tab rule above:
   everything the module draws is hidden by being everything that is not the shell's (or marked
   data-fhs-keep: the Safety Library's own search box, on the one page whose shell has none). Records tab
   only; on Reports the off-records rule already has the floor. The stamp line says the same thing
   while this is up, so it steps aside rather than printing it twice. */
body:has(#scroll) #scroll.fhs-list-wait:not(.fhs-off-records) > *:not(#fhsTop):not(#fhsListWait):not(.fhs-panel):not([data-fhs-keep]){
  display:none !important;
}
body:has(#scroll) #scroll.fhs-list-wait #fhsFresh[data-kind="pending"],
body:has(#scroll) #scroll.fhs-list-wait #fhsFresh[data-kind="missing"]{
  display:none !important;
}
.fhs-list-wait-box{
  margin:6px 2px 18px;
  padding:14px 14px 12px;
  border-radius:12px;
  background:var(--fhs-surface);
  border:1px solid var(--fhs-line);
}
.fhs-list-wait-box p{margin:0;font-size:14px;line-height:1.45;color:var(--fhs-ink)}
.fhs-list-wait-box .fhs-lw-s{margin-top:6px;font-size:13px;color:var(--fhs-ink3)}
.fhs-report-figures .fhs-report-wait{grid-column:1 / -1;margin:0}

/* The stat strip and the filters belong to the record list, so they go with it. */
body:has(#scroll) #scroll.fhs-off-records #fhsStats,
body:has(#scroll) #scroll.fhs-off-records .fhs-search,
body:has(#scroll) #scroll.fhs-off-records .fhs-chips{
  display:none !important;
}

/* A figure the Reports tab shows rather than an action it offers. Same tile shape
   as the stat strip, because it is the same kind of thing: a number with a label.
   It is here and not there because the three tiles answer "what needs me today"
   and an average score does not. */
.fhs-report-figures{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:8px;
  margin:0 0 14px;
}
.fhs-report-figures .fhs-fig{
  background:var(--fhs-surface);
  border:1px solid var(--fhs-line);
  border-radius:14px;
  padding:12px 13px;
  box-shadow:var(--fhs-shadow);
}
.fhs-report-figures .fhs-fig b{
  display:block;
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-size:22px;
  font-weight:800;
  line-height:1;
  color:var(--fhs-ink);
}
.fhs-report-figures .fhs-fig span{
  display:block;
  font-size:11px;
  color:var(--fhs-ink3);
  margin-top:4px;
}

/* ============================================================================
   THE BOTTOM NAV, Phase 3.

   Home, New, Records, Expiring, Profile on a phone, and the field tier gets Today,
   New, My Records, Profile. Finding 1 of the review is that there was no navigation
   between modules at all: every module opened in its own browser tab and there was no
   way from one to another except back to the dashboard. Modules open in this tab now,
   so the browser's own Back works, and this is what makes going FORWARD possible.

   IT IS ONLY ON THE MODULE'S OWN HOME. On a sub-screen the module's own bar is in
   charge and the offline layer spends most of its code stopping you from leaving a
   half-filled form; a nav that invited you to would be working against it.

   The height is a token because two other things have to make room for it: the action
   bar sits on top of it rather than under it, and the scroll area has to end above
   both. Everything below reads --fhs-nav-h rather than repeating the number. */
body:has(#scroll), body.fhs-host{
  /* 68 is the nav's own box: a 52px control with 7 above and 9 below. The safe area
     is INSIDE the token rather than added by the nav alone, because the action bar
     sits on top of the nav and has to be pushed up by the whole of it. Measured in a
     browser at 390px: with the token at 64 the bar overlapped the nav by five pixels,
     which is a five pixel strip of a button nobody can press. Re-measured at 69. */
  --fhs-nav-h:calc(69px + env(safe-area-inset-bottom,0px));
}
.fhs-nav{
  position:fixed;
  left:0;
  right:0;
  bottom:0;
  z-index:26;
  display:flex;
  justify-content:space-around;
  max-width:720px;
  margin:0 auto;
  padding:7px 4px calc(9px + env(safe-area-inset-bottom,0px));
  min-height:var(--fhs-nav-h);
  box-sizing:border-box;
  background:color-mix(in srgb,var(--fhs-surface) 92%,transparent);
  backdrop-filter:blur(12px);
  border-top:1px solid var(--fhs-line);
}
.fhs-nav .fhs-nav-i{
  flex:1 1 0;
  min-width:0;
  min-height:52px;
  border:0;
  background:none;
  cursor:pointer;
  font:inherit;
  font-size:10.5px;
  font-weight:600;
  color:var(--fhs-ink3);
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:3px;
  padding:0 2px;
}
.fhs-nav .fhs-nav-i i{
  display:flex;
  width:44px;
  height:26px;
  border-radius:13px;
  align-items:center;
  justify-content:center;
}
.fhs-nav .fhs-nav-i svg{width:20px;height:20px}
.fhs-nav .fhs-nav-i.fhs-on{color:var(--fhs-b);font-weight:700}
.fhs-nav .fhs-nav-i.fhs-on i{background:rgba(var(--fhs-brgb),.13)}
/* BRIGHTER IN THE DARK (visual handoff, September 11, 2026). The mid tones above
   read on white and disappear on a dark panel. Inactive goes to a fixed cool gray;
   the selected icon takes the client's own brightened tint and the pill its color
   at .24, so every client's navigation stays in their palette (the reviewed purple
   theme lands on the approved lavender values by construction). Light is untouched. */
:root:not([data-theme="light"]) .fhs-nav .fhs-nav-i{color:var(--fhs-ink2)}
/* TD-29: the handoff supplied a fixed cool-gray literal for the inactive icons; the
   ink2 token resolves to the same family from the token system, which verify_shell's
   no-literal rule protects and every client theme inherits correctly. */
:root:not([data-theme="light"]) .fhs-nav .fhs-nav-i.fhs-on{color:var(--fhs-bb)}
:root:not([data-theme="light"]) .fhs-nav .fhs-nav-i.fhs-on i{background:rgba(var(--fhs-brgb),.24)}

/* MAKING ROOM. The action bar was fixed to the bottom of the viewport and carried the
   safe-area padding itself; with a nav under it, it sits on top of the nav and the nav
   carries the safe area instead. The scroll area's own bottom padding is set by each
   module page at 120px plus the safe area, which was right when the bar was the last
   thing on screen; it needs the nav's height on top of that now. */
body.fhs-has-nav:has(#scroll) .actionbar{
  bottom:var(--fhs-nav-h);
  padding-bottom:12px;
}
body.fhs-has-nav:has(#scroll) .scroll{
  padding-bottom:calc(120px + var(--fhs-nav-h));
}

/* PW-1 (phone finding, September 14, 2026): the same room has to be made in the client suite's
   OWN full-screen scroll containers, not only on a module page's .scroll. A .pageview (z-index 200)
   and an .overlay (z-index 150) both sit UNDER the nav (z-index 260 on the host, kept there on
   purpose so the nav stays reachable with a sheet or a panel open, see the sheet rule below). With
   nothing reserving the nav's height at their foot, their last controls sat under the bar and could
   not be tapped: the branded share screen's "Save cards to a folder" and its send controls were the
   ones the walkthrough caught. The client HOME already reserves it (fh-home.css .fhh-home); this is
   the rest of the pattern. --fhs-nav-h already carries the safe-area inset, so it is not added
   twice. */
body.fhs-has-nav .pageview,
body.fhs-has-nav .overlay{
  padding-bottom:calc(var(--fhs-nav-h) + 18px);
}

/* A SHEET STOPS ABOVE THE NAV rather than covering it. Opening New and then wanting
   Profile is an ordinary thing to do, and with the scrim over the nav the second tap
   landed on the scrim and did nothing. Measured, not guessed: a click on the Profile
   entry with the New sheet open was intercepted by the sheet for thirty seconds. */
body.fhs-has-nav .fhs-sheet{
  bottom:var(--fhs-nav-h);
}
body.fhs-has-nav .fhs-sheet .fhs-sheet-in{
  padding-bottom:20px;
}

/* THE NEW PICKER. Big targets, grouped by area, in area order. */
.fhs-new-list{display:flex;flex-direction:column;gap:2px;max-height:62vh;overflow:auto}
.fhs-new-item{
  display:flex;
  align-items:center;
  gap:10px;
  min-height:56px;
  padding:0 14px;
  border-radius:12px;
  border:1.5px solid var(--fhs-line);
  background:var(--fhs-surface);
  color:var(--fhs-ink);
  text-decoration:none;
  font-weight:600;
  font-size:15px;
  margin-bottom:7px;
}
.fhs-new-item .fhs-new-t{
  margin-left:auto;
  font-size:11px;
  font-weight:500;
  color:var(--fhs-ink3);
}
.fhs-new-item.fhs-here{border-color:rgba(var(--fhs-brgb),.45)}
.fhs-new-ico{
  width:36px;height:36px;flex:0 0 auto;display:grid;place-items:center;position:relative;
}
.fhs-new-ico svg{width:22px;height:22px;color:var(--fhs-b)}
.fhs-new-ico img{display:none;width:36px;height:36px;object-fit:contain}
.fhs-new-ico.has-img svg{display:none}
.fhs-new-ico.has-img img{display:block;filter:drop-shadow(0 1px 1px rgba(15,20,40,.22))}
:root:not([data-theme="light"]) .fhs-new-ico.has-img img{
  filter:drop-shadow(0 0 1px rgba(235,240,255,.65)) drop-shadow(0 2px 2px rgba(0,0,0,.3));
}
/* ICON-WIRE-2 (September 29, 2026): painted art on card faces, choice tiles and type faces
   (FHShell.artImg). The block keeps its own size, tint and selected state as the frame; the
   image fills it contained, so nothing is cropped and the art's transparent margin stays.
   The line SVG shows until the image has loaded and again if it fails. */
.fhs-art{position:relative}
.fhs-art>img.fhs-art-img{display:none}
.fhs-art.has-img>svg{display:none}
.fhs-art.has-img>img.fhs-art-img{display:block;width:100%;height:100%;object-fit:contain;
  filter:drop-shadow(0 1px 1px rgba(15,20,40,.22))}
:root:not([data-theme="light"]) .fhs-art.has-img>img.fhs-art-img{
  filter:drop-shadow(0 0 1px rgba(235,240,255,.65)) drop-shadow(0 2px 2px rgba(0,0,0,.3));
}

/* ===================================================================
   DESKTOP, 1024px AND UP
   ===================================================================
   Lane 4, Phase 5, September 1, 2026.

   WHAT CHANGES AND WHAT DOES NOT. The phone layout is a single column with a
   bar along the bottom. On a laptop that column sits in the middle of a wide
   screen with nothing either side of it, which is the fault the review names.
   So above 1024px the bar becomes a sidebar down the left, the search box and
   the primary button move up into the header where there is room for them, and
   the record list uses the width it has been given.

   THE RECORD CARD IS UNCHANGED. So is every module's own markup. Everything
   here is either a rule in this file or a node the shell already owns being
   moved by the shell, which is why a module page needs no desktop code of its
   own and cannot drift from this one.

   THE SIDEBAR IS F&H NAVY, NOT THE CLIENT'S BRAND. It is chrome: the frame the
   product is delivered in, the same on every client, the way a title bar is.
   The client's color is inside it, on the logo, the current entry, the tabs,
   the tiles and the buttons. A sidebar in the client's own color and a hero in
   the same color under it reads as one enormous block of one hue, and the
   review's frame 5 does not do that either.
   =================================================================== */
@media (min-width:1024px){

  body:has(#scroll), body.fhs-host{
    --fhs-side-w:264px;
    /* F&H NAVY, WRITTEN DOWN, AND THAT IS DELIBERATE.
       The first version read var(--navy-1), which is the right instinct and the
       wrong token: dashboard.css redefines --navy-0 through --navy-line to LIGHT
       grays under data-theme=light, so in light mode the sidebar came out pale
       with white text on it and was, measurably, invisible.

       Navy here is chrome, not palette. It is the frame the product is delivered
       in: the same on every client, the same in both themes, the same as the
       stoplight is the same. So it is fixed, in the same way and for the same
       reason, and tests/verify_shell.js allows these three the way it allows the
       three stoplight colors. */
    --fhs-navy:#0A0E25;
    --fhs-navy-2:#111942;
    --fhs-navy-line:#161F51;
  }

  /* The bar down the bottom is a phone control. On a laptop it would be a strip
     of five buttons across an otherwise empty foot of screen. */
  body.fhs-has-nav .fhs-nav{ display:none; }

  .fhs-side{
    position:fixed; top:0; left:0; bottom:0; width:var(--fhs-side-w);
    z-index:60;
    display:flex; flex-direction:column;
    background:var(--fhs-navy);
    border-right:1px solid var(--fhs-navy-line);
    overflow-y:auto; overscroll-behavior:contain;
    padding:0 0 14px;
  }
  .fhs-side[hidden]{ display:none !important; }

  /* The client's mark and their name, in the serif the suite uses for a company
     name everywhere else. The name wraps rather than truncating: a client whose
     name is four words is not a client whose name should end in an ellipsis. */
  .fhs-side-brand{
    display:flex; align-items:center; gap:11px;
    padding:18px 16px 16px;
    border-bottom:1px solid var(--fhs-navy-line);
  }
  .fhs-side-brand img{
    width:40px; height:40px; flex:0 0 auto;
    border-radius:10px; object-fit:contain;
    background:#f7f5f2; padding:3px;
  }
  .fhs-side-brand .fhs-side-name{
    font-family:var(--fh-font-serif,'FH Serif',Georgia,serif);
    font-size:17px; line-height:1.18; font-weight:600;
    color:#fff; letter-spacing:-.01em;
    min-width:0; overflow-wrap:anywhere;
  }

  .fhs-side-group{ padding:14px 12px 2px; }
  .fhs-side-group h2{
    margin:0 0 7px 6px;
    font-family:var(--fh-font-label,'FH Condensed','Arial Narrow',sans-serif);
    font-size:10.5px; font-weight:600; letter-spacing:.18em; text-transform:uppercase;
    color:rgba(255,255,255,.44);
  }
  .fhs-side a.fhs-side-i,
  .fhs-side button.fhs-side-i{
    display:flex; align-items:center; gap:10px;
    width:100%;
    /* 44 is the house minimum and it is the minimum here too, even with a mouse:
       the same page is opened on a touchscreen laptop. */
    min-height:44px;
    padding:0 10px; margin:0 0 2px;
    border:0; border-radius:10px;
    background:none; color:rgba(255,255,255,.78);
    font:inherit; font-size:13.5px; font-weight:600; text-align:left;
    text-decoration:none; cursor:pointer;
  }
  .fhs-side a.fhs-side-i:hover,
  .fhs-side button.fhs-side-i:hover{ background:var(--fhs-navy-2); color:#fff; }
  .fhs-side .fhs-side-i > svg{ width:18px; height:18px; flex:0 0 auto; opacity:.8 }
  .fhs-side-ico{
    width:26px;height:26px;flex:0 0 auto;display:grid;place-items:center;position:relative;
  }
  .fhs-side-ico svg{width:18px;height:18px;opacity:.8}
  .fhs-side-ico img{display:none;width:26px;height:26px;object-fit:contain}
  .fhs-side-ico.has-img svg{display:none}
  .fhs-side-ico.has-img img{
    display:block;filter:drop-shadow(0 0 1px rgba(235,240,255,.65)) drop-shadow(0 2px 2px rgba(0,0,0,.35));
  }
  /* THE ACTIVE ENTRY. UI-ACCENT-1, September 23, 2026: this was a wash fading out to
     the right with a 3px rail down the left (TD-19). It is the selected recipe now, the
     same as the Operations Center rail and the Master Dashboard: a flat 7 percent tint of
     the client's brand over the sidebar navy, and a hairline in the brand all the way
     round. The tint is weaker than the old wash was under the label, so the white label
     sits on a darker ground than before and gains contrast. The brand is read through
     --fhs-brgb because the preview sets it on #cdView, below :root. */
  .fhs-side .fhs-side-i.fhs-on{
    position:relative;
    background:linear-gradient(rgba(var(--fhs-brgb),.07),rgba(var(--fhs-brgb),.07)),var(--fhs-navy);
    box-shadow:inset 0 0 0 1px rgba(var(--fhs-brgb),.45);
    color:#fff;
  }
  .fhs-side .fhs-side-i.fhs-on > svg,
  .fhs-side .fhs-side-i.fhs-on .fhs-side-ico svg{ opacity:1; color:var(--fhs-bb) }
  .fhs-side-foot{ margin-top:auto; padding:10px 12px 0; border-top:1px solid var(--fhs-navy-line) }

  /* The page moves over to make room. Both the module pages and the home. */
  body:has(#scroll).fhs-desk,
  body.fhs-host.fhs-desk{ padding-left:var(--fhs-side-w); }
  body.fhs-desk header.bar,
  body.fhs-desk .fhs-nav{ left:var(--fhs-side-w); width:auto; }

  /* The header gains a row for the search box and the primary button, which on
     a phone live at the top of the list and at the bottom of the screen. */
  .fhs-deskbar{
    display:flex; align-items:center; gap:10px;
    margin-left:auto; flex:0 1 auto; min-width:0;
  }
  .fhs-deskbar:empty{ display:none }
  /* THE SEARCH BOX GIVES WAY FIRST, AND THE PAGE NAME KEEPS A FLOOR. DESKTOP-1, October 7, 2026.
     At 1024 a field page carries the back chevron, the language button, the search box, the More
     button, a New button as long as "+ Report an incident" and the signed-in name on one bar, and
     measured there the page name was squeezed to 0 pixels and the name was pushed past the right
     edge (it was 44 pixels before the More button joined the row, which is no better). The box
     shrinks from 270 to 112 before anything else moves, and the name never goes under 96. */
  body.fhs-desk .fhs-deskbar .fhs-search{ margin:0; width:auto; flex:0 1 270px; min-width:112px; }
  body.fhs-desk:has(#scroll) header.bar .fhs-title{ min-width:96px; }
  body.fhs-desk .fhs-deskbar .fhs-search input{ min-height:44px }
  body.fhs-desk .fhs-deskbar .btn,
  body.fhs-desk .fhs-deskbar button{ min-height:44px; white-space:nowrap }

  /* With the primary lifted into the header the fixed bottom bar has nothing in
     it, so it stops taking a strip of the screen. It comes back the moment the
     window is narrowed, because the shell puts the button back. */
  body.fhs-desk .actionbar:empty{ display:none }

  /* DESKTOP-1, October 7, 2026 (DEMO-1 finding 6, and Bryant's decision that on a wide screen the
     menu moves into the page header). THE BOTTOM BAR DOES NOT FLOAT ON A LAPTOP.
     Measured before this: on all 26 module homes at 1366 wide, the More button stayed behind in
     the bottom bar, so the rule above never fired and a 720-pixel strip with a white fade sat over
     the cards, centered on the window rather than the page. On 11 and 13 to 18 the New button
     stayed with it, because those pages name a primary that has no .primary class. fh-shell.js now
     lifts both into the header row (placeMore, and .fhs-primary in paintDesk's list).
     :empty alone was not enough to retire the bar, either: seven pages write their bar in their
     own markup, and the line breaks between its buttons are text nodes that stay behind when the
     buttons leave. A bar with no element left in it that a person could see is retired too. */
  body.fhs-desk .actionbar.fhs-bar:not(:has(> :not([hidden]))){ display:none }
  /* A sub-screen keeps its own bar at the foot (Save, Continue, Submit: the end of a form), and
     it is centered on the page column, not on the window under the sidebar. Its sheet as well. */
  body.fhs-desk:has(#scroll) .actionbar{ left:var(--fhs-side-w); }
  body.fhs-desk:has(#scroll) .fhs-sheet{ padding-left:var(--fhs-side-w); }
  /* The More button in the header wears the header's own control recipe, the one the back
     chevron and the language button use. The ink is the header's own (--fh-on-brand, which
     brand-apply.js works out per client), so a pale brand gets dark dots rather than white ones
     on yellow. */
  body.fhs-desk .fhs-deskbar .fhs-more{
    flex:0 0 auto;
    min-width:44px; min-height:44px;
    padding:0 12px;
    border-radius:10px;
    border:1px solid rgba(255,255,255,.3);
    background:rgba(255,255,255,.14);
    color:var(--fh-on-brand,#fff);
    font:inherit; font-weight:700; font-size:17px; line-height:1;
    cursor:pointer;
  }
  body.fhs-desk .fhs-deskbar .fhs-more:hover{ background:rgba(255,255,255,.22) }
  /* And the New button looks the same on every page. Before this the pages whose primary is a
     plain .btn would have arrived in the header as a white phone button, flex:1 and all. */
  body.fhs-desk .fhs-deskbar .fhs-primary{
    flex:0 0 auto;
    padding:0 16px;
    border-radius:10px;
    border:1px solid rgba(255,255,255,.3);
    background:var(--fhs-b);
    color:var(--fhs-b-on);
    font-family:var(--disp,'Archivo',system-ui,sans-serif);
    font-weight:700; font-size:13px;
    box-shadow:none;
    cursor:pointer;
  }
  /* Off the Records tab the header row stands down the way the bottom bar does on a phone:
     the New button has nothing to add to on Reports, and the search box has no list to filter. */
  body.fhs-desk:has(#scroll.fhs-off-records) .fhs-deskbar > *{ display:none }
  /* The More sheet, opened from the header, is a menu that drops down under its button.
     fh-shell.js sets the two numbers when it opens; these are only the fallbacks. */
  body.fhs-desk .fhs-sheet.fhs-sheet-pop{ background:rgba(12,14,32,.22); }
  /* A sheet stops above the phone's nav so the nav stays reachable. There is no nav on a
     laptop, and stopping short left an undimmed strip along the foot of the screen. */
  body.fhs-desk.fhs-has-nav .fhs-sheet{ bottom:0; }
  body.fhs-desk .fhs-sheet.fhs-sheet-pop .fhs-sheet-in{
    position:absolute;
    top:var(--fhs-pop-top,92px);
    right:var(--fhs-pop-right,24px);
    width:320px;
    max-width:calc(100vw - 48px);
    max-height:calc(100vh - var(--fhs-pop-top,92px) - 16px);
    overflow-y:auto;
    border-radius:14px;
    padding:14px;
    box-shadow:0 18px 44px -16px rgba(20,22,45,.5);
  }

  /* THE WIDTH. Every module page caps its own .app at 720px, which is right on a
     phone and is the fault itself on a laptop: a phone column in the middle of the
     screen. The cap is lifted here rather than in twenty-two files, and the record
     card is not touched at all; it is the room around it that changes.

     This rule outranks the module's own .app on specificity alone, so it works on
     every page whatever order the stylesheets happen to load in. */
  body.fhs-desk .app{ max-width:1180px; }
  /* NOT margin:auto on #scroll. .app is a column flex container, and an auto
     cross-axis margin on a flex item makes it size to its content instead of
     filling the line: the record list rendered 485px wide in a 1016px column and
     sat off center. .app carries the cap; #scroll fills it. Measured, after the
     first version of this rule did exactly that. */
  body.fhs-desk #scroll{ width:100%; }
  body.fhs-desk .fhh-home{ max-width:1180px; margin-left:auto; margin-right:auto; }
  /* NOT A MULTI-COLUMN LIST. Every module renders its own record markup and the
     wrapper around the cards differs from page to page, so a column rule written
     here would find the right element on some pages and the wrong one on others.
     Capping the width is the part that is true on all twenty-two. */

  /* The home's tiles have four across to use instead of three. */
  body.fhs-host.fhs-desk .fhh-tiles{ grid-template-columns:repeat(4,1fr) }
  body.fhs-host.fhs-desk .fhh-hero .fhh-big{ font-size:23px }
  /* And the work sentence under it grows with it, keeping the same step down. TD-23. */
  body.fhs-host.fhs-desk .fhh-hero:not(.fhh-slim) .fhh-sub{ font-size:16px }
}

/* Below 1024 the sidebar is not merely hidden, it is not built: paintSide()
   removes the node. This rule is the belt to that braces, for the instant
   between a resize and the next frame. */
@media (max-width:1023px){
  .fhs-side{ display:none !important }
}

/* ===================================================================
   THE BAR AND THE SIDEBAR ON THE CLIENT HOME
   ===================================================================
   The client home is not a document that scrolls the window: it is a
   .pageview, which dashboard.css gives position:fixed, inset:0 and
   z-index:200. Everything the shell adds to the body is a sibling of that
   view, so at z-index 26 the bottom bar was DRAWN, in the right place, at the
   right size, and painted underneath the page. Every geometric check passed:
   the entries were there, they measured 44 pixels, they did not overlap the
   last tile. None of that is worth anything if the thing on top of them is
   the page.

   tests/verify_home_dom.js now asks elementFromPoint what is actually at the
   middle of each entry, which is the only question that answers this.
   =================================================================== */
body.fhs-host .fhs-nav{ z-index:260; }
body.fhs-host .fhs-side{ z-index:260; }
/* Pickers are also body-level siblings of the z-index:200 client view. Keep the
   sheet above the view but below the navigation, which remains available while
   the sheet is open. */
body.fhs-host .fhs-sheet{ z-index:250; }
@media (min-width:1024px){
  /* The view is fixed to the viewport, so padding the body moves nothing. The
     room for the sidebar has to be made inside the view itself. */
  body.fhs-host.fhs-desk{ padding-left:0; }
  body.fhs-host.fhs-desk .pageview{ padding-left:var(--fhs-side-w); }
  /* DELETE-ALL-1: the delete and remove dialog sits in the middle of a laptop window */
  .fhs-sheet.fhs-confirm{ align-items:center; }
  .fhs-confirm .fhs-confirm-in{ border-radius:20px; }
}

/* THE LANGUAGE TOGGLE. TD-27, September 9, 2026.

   One button on the header bar, saying the language the tap will switch TO. It sits before
   the wordmark so the brand stays the last thing on the bar, and it is flex:0 0 auto so the
   title beside it keeps every pixel it had.

   NOT SCOPED TO body:has(#scroll) like the rest of the header rules in this file, and that is
   deliberate: the client home draws the same bar and has no #scroll, and a toggle that styled
   itself on a module page and arrived unstyled on the home would be worse than no toggle. */
header.bar .fhs-lang{
  position:relative;
  z-index:1;
  flex:0 0 auto;
  /* 44px, which is the estate's tap-target floor and is checked by verify_shell.js and
     verify_desktop.js. The first draft of this rule said 36 and both suites said no. Bryant
     asked for a SMALL button and this is still small: it is narrow, and the height is the
     same as the back chevron beside it, which is what a thumb in a glove needs. */
  min-height:44px;
  padding:0 13px;
  border-radius:10px;
  font-family:var(--disp,'Archivo',system-ui,sans-serif);
  font-weight:700;
  font-size:12.5px;
  letter-spacing:.01em;
  white-space:nowrap;
  color:#fff;
  background:rgba(255,255,255,.14);
  border:1px solid rgba(255,255,255,.24);
  cursor:pointer;
}
header.bar .fhs-lang:hover{ background:rgba(255,255,255,.22); }
header.bar .fhs-lang:active{ transform:translateY(1px); }
/* On a narrow phone the bar already wraps (dashboard.css at 778). Keep the button from
   being the thing that forces the wrap by letting it shrink its padding first. */
@media (max-width:420px){
  header.bar .fhs-lang{ padding:0 9px; font-size:12px; }
}


/* PW-5 (phone finding, September 14, 2026): SEAMLESS SCREEN-TO-SCREEN.
   Tapping a nav entry is a full page load (each module is its own document), and two things made the
   change ugly: the browser showed the OLD screen while the new one loaded, and the entry fade below
   (from opacity .55) held the incoming page see-through long enough that a capture read it as the
   previous screen bleeding through. Where the browser supports it, a cross-document view transition
   fixes both at once: it holds the old screen, lets the new one paint COMPLETE, and crossfades
   between the two, so there is never a half-painted or see-through frame. Every page loads this file,
   so every page opts in, which is what a cross-document transition needs on both ends.
   PERF-2, October 1, 2026: THE CROSSFADE IS RETIRED, and the census is why. A module page now holds
   its content until it has settled, so the crossfade was dissolving the old screen into the new
   page's bare chrome: the in-between frames read as a white flash (the census counts them as blank)
   and every navigation settled later. Measured on the same six client-home taps, twice each, with
   only this rule changed: torn frames 122 to 98, blank frames 10 to 1, total settle 15.7 s to 13.5 s.
   Chrome already keeps the old screen up until the new page paints (paint holding), and the entry
   fade that made PW-5's ghosting is gone, so there is still never a see-through or half-painted
   frame; the page's own reveal is the one fade. */

/* TD-29, 11 Sep 2026: THE ENTRY FADE, retired by PERF-2 on October 1, 2026. It faded the whole page
   from .82 on every load in a browser without view transitions. A module page now holds its content
   until it has settled and fades that content in once (the inline fh-hold style and brand-apply.js),
   so this one would have faded the same content a second time. */

/* PW-5: THE DEPLOY RELOAD, covered by the same kind of soft transition. offline-sync.js adds
   .fhs-leaving to <html> just before it reloads for a new build, so the screen fades out instead of
   snapping to a bare white repaint. It changes only what the eye sees, never WHEN the reload happens
   (that is still gated on a safe moment). Reduced motion gets no fade. */
@media (prefers-reduced-motion: no-preference){
  html.fhs-leaving{ opacity:0; transition:opacity .16s ease-in; }
}

/* SUITE-2 item 5: THE FOCUS LINE. A module opened from a Needs attention row on the Executive
   Dashboard says what it is showing and offers the way back to everything. Brand-tinted and
   quiet, because it is a note about the list rather than an alarm, and the button is 44
   pixels because a thumb is what taps it. */
.fhs-focus{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:4px 0 12px; padding:10px 12px;
  border-radius:12px; background:rgba(var(--fhs-brgb,91,43,214),.08); border:1px solid rgba(var(--fhs-brgb,91,43,214),.22); }
.fhs-focus-t{ flex:1 1 160px; min-width:0; font-size:13.5px; font-weight:600; color:var(--fhs-ink,#16181F); line-height:1.35; overflow-wrap:anywhere; }
.fhs-focus-x{ flex:none; min-height:44px; padding:0 14px; border-radius:10px; border:1.5px solid var(--fhs-b,#5B2BD6);
  background:transparent; color:var(--fhs-bd,#3F1C9C); font:inherit; font-size:13px; font-weight:700; cursor:pointer; }

/* DELETE-ALL-1, October 7, 2026: THE DELETE AND REMOVE DIALOG, AND THE CONTROL UNDER A LIST ROW.
   One sheet for every deletion and removal in the suite (fh-shell.js confirmDelete). It rises from the
   foot of a phone like the More sheet and sits in the middle of a laptop. Words are 14 px and up, the
   box is 16 px so a phone does not zoom into it, and every button is at least 48 px tall. It sits above
   every other sheet, because it is only ever opened by a tap on one of them. */
body .fhs-sheet.fhs-confirm, .fhs-sheet.fhs-confirm{ z-index:1000; }   /* above every modal and sheet on both dashboards (their highest is 261) */
/* A menu sheet stops above the bottom nav so the nav stays usable; this one is a question that has to be
   answered or canceled, so it covers the nav too, and nothing behind it can be tapped while it is up. */
body.fhs-has-nav .fhs-sheet.fhs-confirm{ bottom:0; }
body.fhs-has-nav .fhs-sheet.fhs-confirm .fhs-sheet-in{ padding-bottom:calc(20px + env(safe-area-inset-bottom,0px)); }
.fhs-confirm .fhs-confirm-in{ max-width:560px; box-sizing:border-box; }
.fhs-confirm h2{ font-family:var(--disp,'Archivo',system-ui,sans-serif); font-size:18px; line-height:1.3;
  letter-spacing:0; text-transform:none; color:var(--fhs-ink,var(--ink,#16181F)); margin:2px 0 10px; overflow-wrap:anywhere; }
.fhs-confirm .fhs-confirm-b p{ margin:0 0 10px; font-size:14.5px; line-height:1.45; color:var(--fhs-ink2,var(--ink-2,#4A4C66)); overflow-wrap:anywhere; }
.fhs-confirm .fhs-confirm-q{ display:block; margin:6px 0 6px; font-size:14px; font-weight:700; color:var(--fhs-ink,var(--ink,#16181F)); }
.fhs-confirm .fhs-confirm-in-box{ display:block; width:100%; box-sizing:border-box; min-height:48px; padding:10px 12px;
  border-radius:12px; border:1.5px solid var(--fhs-line,var(--line,rgba(20,22,45,.18))); background:var(--fhs-surface,var(--surface,#fff));
  color:var(--fhs-ink,var(--ink,#16181F)); font:inherit; font-size:16px; }
.fhs-confirm .fhs-confirm-a{ display:flex; flex-wrap:wrap; gap:10px; justify-content:flex-end; margin-top:14px; }
.fhs-confirm .fhs-confirm-a button{ flex:1 1 140px; min-height:48px; max-width:100%; border-radius:12px; font:inherit; font-size:15px; font-weight:700; cursor:pointer; }
.fhs-confirm .fhs-confirm-no{ border:1.5px solid var(--fhs-line,var(--line,rgba(20,22,45,.18))); background:var(--fhs-surface,var(--surface,#fff)); color:var(--fhs-ink,var(--ink,#16181F)); }
.fhs-confirm .fhs-confirm-go{ border:2px solid var(--fh-signal-red-text); background:var(--fhs-surface,var(--surface)); color:var(--fh-signal-red-text); }
.fhs-confirm .fhs-confirm-go:disabled{ opacity:.45; cursor:not-allowed; }
/* On a laptop it sits in the middle of the window rather than rising from the foot (the rule is in the
   desktop block above, with the rest of the 1024 px layout). */
/* The control under a list row: its own small button, set apart under the row, in the danger ink, 44 px
   tall with 13 px words, in a row that wraps rather than scroll sideways. A search that hides a card
   hides the control under it too. */
.fhs-delrow{ display:flex; flex-wrap:wrap; justify-content:flex-end; gap:8px; margin:-3px 2px 16px; max-width:100%; }
.rec-card.fhs-hidden + .fhs-delrow, .fhs-hidden + .fhs-delrow{ display:none; }
.fhs-del{ min-height:44px; max-width:100%; padding:8px 14px; border:1px solid var(--line-2,rgba(20,22,45,.14)); border-radius:var(--r-sm,10px);
  background:var(--surface,#fff); color:var(--fh-signal-red-text); font-family:var(--disp,inherit); font-weight:700; font-size:13px;
  line-height:1.2; white-space:normal; text-align:center; cursor:pointer; }
