/* F&H STAC visual theme preview, August 18, 2026.
   Additive only. Load after dashboard.css and page-level styles.
   Removing this one stylesheet restores the prior presentation. */

:root{
  /* PERF-2: each brand face is followed by its measured fallback (fh-theme-fonts.css), so a page
     drawn before the font file arrives sits where the brand face would have. */
  --fh-font-body:'FH Sans','FH Sans Fallback','FH Sans Fallback Android',system-ui,-apple-system,'Segoe UI',sans-serif;
  --fh-font-display:'FH Sans','FH Sans Fallback','FH Sans Fallback Android',system-ui,-apple-system,'Segoe UI',sans-serif;
  --fh-font-label:'FH Condensed','FH Condensed Fallback Android','Arial Narrow',sans-serif;
  --fh-font-serif:'FH Serif','FH Serif Fallback','FH Serif Fallback Android',Georgia,serif;
  --fh-font-mono:'FH Mono','FH Mono Fallback','Cascadia Code',ui-monospace,monospace;

  /* EVERY CHAIN ENDS IN A LITERAL, AND THAT IS NOT BELT AND BRACES.
     Only index.html links dashboard.css. The 18 module pages, osha-301 and the Executive
     Dashboard each carry their own tokens inline and define a different subset, so a chain that
     bottoms out in another custom property bottoms out in NOTHING on most of the estate.

     Measured in the browser on 2026-08-19, before this change:
       module pages          8 of these tokens resolved to empty
       osha-301.html         6
       Executive Dashboard   4
       index.html            0, which is why it looked fine everywhere I first checked

     An empty custom property is not "no rule". The declaration using it becomes invalid at
     computed-value time, which for an inherited property means it INHERITS. That is how the
     primary button on every module page ended up with body text color on a purple fill:
     --fh-on-brand resolved to nothing, so color fell back to the inherited near-black, and the
     page's own correct .btn.primary{color:#fff} had already been overridden. It is also why the
     elevation scale did nothing on modules: all three shadows are built from --fh-edge-hi.

     The literals below are the F&H dark-theme values. They are last in each chain, so a client
     palette and dashboard.css both still win ahead of them. */
  --fh-canvas:var(--navy-0,var(--bg,#060815));
  --fh-panel:var(--surface,var(--navy-2,#0F1639));
  --fh-panel-hi:var(--surface-hi,var(--surface-2,#131B47));
  --fh-panel-low:var(--surface-3,var(--recess,#0B1130));
  --fh-line:var(--navy-line,var(--line,#1E2748));
  --fh-line-strong:var(--ctl-line,var(--line-2,#2A3560));
  --fh-text:var(--text,var(--ink,#dfe3ee));
  --fh-muted:var(--text-dim,var(--ink-2,#8b93ad));
  --fh-quiet:var(--soon,var(--ink-3,#7e88a6));
  --fh-brand:var(--brand-500,var(--brand,var(--purple-bright,#7914ED)));
  --fh-brand-strong:var(--brand-700,var(--brand-deep,var(--purple,#5F0EBD)));
  --fh-brand-deep:var(--brand-900,var(--purple-dark,#400A7E));
  --fh-brand-bright:var(--brand-300,var(--brand-bright,var(--purple-bright,#7138EE)));
  --fh-brand-soft:var(--brand-100,var(--brand-soft,var(--ctl-bg,#1A2251)));
  /* White last, because the default brand is F&H violet and violet is dark. When brand-apply.js
     has run, --brand-on is computed for that client's mid tone and wins ahead of this. */
  --fh-on-brand:var(--brand-on,var(--brand-ink,var(--metal-hi,#ffffff)));
  --fh-recess:var(--recess,var(--surface-3,#0B1130));

  /* Operational stoplight colors are deliberately separate from the unchanged
     F&H navy and purple brand palette. These are for state, risk, and urgency. */
  --fh-signal-green:#39E58C;
  --fh-signal-green-text:#8AF4B9;
  --fh-signal-green-wash:color-mix(in srgb,var(--fh-signal-green) 15%,transparent);
  --fh-signal-yellow:#FFD43B;
  --fh-signal-yellow-text:#FFE17B;
  --fh-signal-yellow-wash:color-mix(in srgb,var(--fh-signal-yellow) 14%,transparent);
  --fh-signal-red:#FF5D68;
  --fh-signal-red-text:#FFABB2;
  --fh-signal-red-wash:color-mix(in srgb,var(--fh-signal-red) 14%,transparent);
  --fh-signal-on-red:#2B0508;

  --fh-edge:color-mix(in srgb,var(--fh-text) 10%,transparent);
  /* A literal, not a token. This is a white sheen along a top edge; it is not brand colored
     and it never was. Pointing it at --metal-hi made all three shadows collapse on the 18
     module pages, because only dashboard.css defines that token and only index.html links
     dashboard.css. The elevation scale was doing nothing on most of the estate. */
  --fh-edge-hi:color-mix(in srgb,#ffffff 11%,transparent);
  --fh-shadow-color:color-mix(in srgb,var(--fh-canvas) 76%,transparent);
  --fh-shadow-1:0 1px 0 var(--fh-edge-hi) inset,0 10px 24px -20px var(--fh-shadow-color);
  --fh-shadow-2:0 1px 0 var(--fh-edge-hi) inset,0 20px 48px -30px var(--fh-shadow-color);
  --fh-shadow-3:0 1px 0 var(--fh-edge-hi) inset,0 30px 72px -34px var(--fh-shadow-color);
  --fh-focus:color-mix(in srgb,var(--fh-brand) 28%,transparent);
  --fh-hover:color-mix(in srgb,var(--fh-brand) 8%,var(--fh-panel));
  --fh-radius-sm:10px;
  --fh-radius-md:14px;
  --fh-radius-lg:20px;
  --fh-speed:160ms;

  --disp:var(--fh-font-display);
  --body:var(--fh-font-body);
  --mono:var(--fh-font-mono);

  /* UI-ACCENT-1, September 23, 2026: EMPHASIS, SELECTION AND STATUS, WITHOUT A STRIPE.
     Bryant asked for the colored lines down the left of tiles and cards to go, because they
     read as AI generated. The Operations Center retired its stripes on September 22 and wrote
     these names and recipes into its css/tokens.css. They are copied here, name for name, so
     the two properties render as one system. What changes is the accent and the base: the
     Ops Center uses its lavender, the Dashboard uses --fh-brand, which is (144,90,207) on the
     Master Dashboard (the IN SUITE chip's family) and follows the client on their own pages.

       emphasis        box-shadow:var(--emph-edge),var(--emph-shadow)
       inside a card   box-shadow:var(--emph-edge),var(--emph-shadow-nested); no fill of its own
       selected        background:var(--select-wash),var(--select-base); box-shadow:var(--select-edge)
       status          a leading dot, var(--status-dot-size), in the status color, plus a
                       hairline in the status tone where the whole surface is the message

     THE SELECTED WASH IS LAID ON A BASE, AND THE BASE IS CHOSEN SO TEXT NEVER LOSES CONTRAST.
     Dark: 7 percent of the accent over --fh-canvas, the darkest ground, composes darker than
     the rows it sits among, so light text on it gets more contrast, not less. Light: 4 percent
     over white composes lighter than those rows, so dark text gets more. verify_accent_lines.js
     works that sum out from these declarations rather than trusting this comment. */
  --emph-line:color-mix(in srgb,var(--fh-brand) 34%,transparent);
  --emph-edge:inset 0 1px 0 var(--fh-edge-hi),inset 0 0 0 1px var(--emph-line);
  --emph-shadow:0 20px 48px -30px var(--fh-shadow-color);
  --emph-shadow-nested:0 10px 24px -20px var(--fh-shadow-color);
  --emph-title-weight:600;
  --select-pct:7%;
  --select-tint:color-mix(in srgb,var(--fh-brand) var(--select-pct),transparent);
  --select-wash:linear-gradient(180deg,var(--select-tint),var(--select-tint));
  --select-base:var(--fh-canvas);
  --select-line-pct:45%;
  --select-line:color-mix(in srgb,var(--fh-brand) var(--select-line-pct),transparent);
  --select-edge:inset 0 0 0 1px var(--select-line);
  --status-dot-size:8px;
  --success-line:color-mix(in srgb,var(--fh-signal-green) 34%,transparent);
  --warning-line:color-mix(in srgb,var(--fh-signal-yellow) 34%,transparent);
  --danger-line:color-mix(in srgb,var(--fh-signal-red) 38%,transparent);
  --info-line:color-mix(in srgb,var(--fh-brand) 34%,transparent);
  /* A dot is a graphic, so it is held to 3:1 against what it sits on. On a dark ground the
     signal colors clear that easily (7.8, 9.1 and 5.0 on their own washes). On a light one
     they do not: signal yellow is 1.65 and signal green 2.76 on their washes. So the light
     block points the dots at the pill rings Bryant approved in STATUS-1b, which were measured
     for exactly this job (3.10, 4.30 and 6.08 on the washes, 3.50, 4.68 and 7.05 on white).
     The pill tokens themselves are read here, never changed. */
  --status-dot-safe:var(--fh-signal-green);
  --status-dot-caution:var(--fh-signal-yellow);
  --status-dot-stop:var(--fh-signal-red);
}

:root[data-theme="light"]{
  --fh-edge:color-mix(in srgb,var(--fh-text) 13%,transparent);
  /* THE LIGHT BLOCK IS WHERE THIS ACTUALLY BIT. The dark declaration above had a fallback and
     this one did not, and every module page runs with data-theme="light", so on 18 of the 24
     pages --fh-edge-hi collapsed and took --fh-shadow-1, -2 and -3 down with it. Only
     index.html was fine, because only index.html links dashboard.css, which is the one file
     that defines --metal-hi. Fixing the dark declaration alone changed nothing and the count
     of unresolved tokens stayed at four, which is how this second copy was found. */
  --fh-edge-hi:color-mix(in srgb,var(--metal-hi,#ffffff) 74%,transparent);
  --fh-shadow-color:color-mix(in srgb,var(--fh-text) 18%,transparent);
  --fh-signal-green:#00A85A;
  --fh-signal-green-text:#006B38;
  --fh-signal-green-wash:color-mix(in srgb,var(--fh-signal-green) 11%,transparent);
  --fh-signal-yellow:#F5B700;
  --fh-signal-yellow-text:#714B00;
  --fh-signal-yellow-wash:color-mix(in srgb,var(--fh-signal-yellow) 14%,transparent);
  --fh-signal-red:#E12D3D;
  --fh-signal-red-text:#9D1523;
  --fh-signal-red-wash:color-mix(in srgb,var(--fh-signal-red) 10%,transparent);
  --fh-signal-on-red:#ffffff;
  /* STATUS-1, September 22, 2026: the pill colors Bryant picked ("Option 6, brighter").
     Expired and Expiring soon are solid fills, Valid stays a tint, so the two states that need
     action are the loudest thing on the row. Measured: label on fill 4.72, 10.64 and 8.31 to 1;
     ring on the white card 7.05, 4.68 and 3.50 to 1. Pill only: notes, badges and stripes keep
     the --fh-signal-* colors above. Light only: the dark block is untouched. */
  --fh-pill-stop-ring:#B5001B;
  --fh-pill-stop-ink:#FFFFFF;
  --fh-pill-stop-fill:#E4152F;
  --fh-pill-caution-ring:#A86400;
  --fh-pill-caution-ink:#2A1700;
  --fh-pill-caution-fill:#FFC21F;
  --fh-pill-safe-ring:#009E4C;
  --fh-pill-safe-ink:#00461F;
  --fh-pill-safe-fill:#9EF0C8;
  /* UI-ACCENT-1: the selected wash on a light ground. 4 percent over white, so a selected row
     composes lighter than the rows around it and its dark text gains contrast. The accent is
     carried by the hairline. --select-tint and --select-wash are declared once, in the dark
     block, and read these two values on the same element. */
  --select-pct:4%;
  --select-base:#ffffff;
  --status-dot-safe:var(--fh-pill-safe-ring);
  --status-dot-caution:var(--fh-pill-caution-ring);
  --status-dot-stop:var(--fh-pill-stop-ring);
}

html{
  font-family:var(--fh-font-body);
  font-variant-numeric:tabular-nums lining-nums;
  text-rendering:optimizeLegibility;
}
body,
button,
input,
select,
textarea{
  font-family:var(--fh-font-body);
}
body{
  color:var(--fh-text);
}
h1,h2,h3,h4,
.wordmark,
.pv-title,
.ttl,
.home-head h1,
.step-q,
.d-head h1,
.cd-brandname,
.cd-exec-h{
  font-family:var(--fh-font-display);
  letter-spacing:-.025em;
}
.mono,
.case,
.clock,
[data-count],
[data-value],
.stat .n,
.metric,
.metric-value,
.fnum,
.kv dd,
td{
  font-variant-numeric:tabular-nums lining-nums;
}
.mono,
.case,
.step-eyebrow{
  font-family:var(--fh-font-mono);
}
.subtitle,
.sec-head h2,
.list-lbl,
.step-eyebrow,
.card h3,
.field > label,
.field label,
.as-dr-section,
.as-fld label,
.badge,
.stbadge,
th{
  font-family:var(--fh-font-label);
  font-weight:600;
  letter-spacing:.105em;
  text-transform:uppercase;
}

:where(a,button,input,select,textarea,[contenteditable="true"]):focus-visible{
  outline:3px solid var(--fh-focus);
  outline-offset:3px;
}

/* Application chrome */
header.bar,
.bar,
.pv-bar,
.toolbar{
  border-color:var(--fh-line);
  background:color-mix(in srgb,var(--fh-panel) 88%,transparent);
  box-shadow:var(--fh-shadow-1);
  backdrop-filter:blur(18px) saturate(130%);
}
header.bar{
  border-radius:var(--fh-radius-lg);
  padding:16px 18px;
}
.wordmark{
  font-size:clamp(22px,2.5vw,29px);
  font-weight:750;
  letter-spacing:-.03em;
}
.subtitle{
  font-size:11px;
  color:var(--fh-muted);
}
.clock .t{
  font-family:var(--fh-font-mono);
  font-size:19px;
  letter-spacing:-.035em;
}
.seal,
.brand-logo{
  box-shadow:0 0 0 1px var(--fh-line),0 10px 28px -18px var(--fh-brand);
}
.iconbtn,
.navlink,
.signout-btn,
.back,
.bar .back,
.pv-back{
  min-height:44px;
  border-color:var(--fh-line);
  background:var(--fh-panel-low);
  box-shadow:0 1px 0 var(--fh-edge-hi) inset;
  transition:border-color var(--fh-speed) ease,background var(--fh-speed) ease,transform var(--fh-speed) ease;
}
.iconbtn:hover,
.navlink:hover,
.signout-btn:hover,
.back:hover,
.bar .back:hover,
.pv-back:hover{
  border-color:var(--fh-brand);
  background:var(--fh-hover);
}

/* Page hierarchy */
.sec-head{
  margin-top:38px;
  margin-bottom:16px;
}
.sec-head h2{
  font-size:12px;
  letter-spacing:.16em;
  color:var(--fh-muted);
}
.sec-head .rule{
  background:linear-gradient(90deg,var(--fh-brand),color-mix(in srgb,var(--fh-brand) 8%,transparent),transparent);
}
/* UI-ACCENT-1: the page head is the card that matters on a module page, and it said so with a
   4px brand bar down its left edge (a ::after). It says so now with the emphasis recipe: the
   accent hairline all the way round and the lifted shadow, no bar. */
.home-head{
  position:relative;
  border:0 !important;
  border-radius:var(--fh-radius-lg) !important;
  background:linear-gradient(145deg,var(--fh-panel-hi),var(--fh-panel)) !important;
  color:var(--fh-text) !important;
  box-shadow:var(--emph-edge),var(--emph-shadow) !important;
  overflow:hidden;
}
.home-head::before{
  opacity:.2 !important;
}
.home-head h1{
  font-size:clamp(28px,4.4vw,38px);
  font-weight:760;
  line-height:1.04;
  color:var(--fh-text) !important;
}
.home-head p,
.home-head .sub{
  color:var(--fh-muted) !important;
}
.step-q,
.d-head h1{
  font-size:clamp(25px,5vw,34px);
  font-weight:760;
  line-height:1.08;
}
/* UI-ACCENT-1: the eyebrow lost its 3px brand rule. It is a label, and the uppercase mono at
   weight 600 above already marks it as one; the rule was decoration on top of that. */
.step-eyebrow{
  color:var(--fh-text);
  font-size:11px;
}

/* Material surfaces */
.tile,
.cd-tile,
.as-card,
.stat,
.card,
.rec-card,
.sum,
.panel,
.modal,
.auth-card,
.choice,
.optrow,
.person{
  border-color:var(--fh-line);
  background:linear-gradient(155deg,var(--fh-panel-hi),var(--fh-panel));
  box-shadow:var(--fh-shadow-1);
}
.tile,
.cd-tile,
.as-card,
.rec-card,
.choice,
.optrow{
  transition:transform var(--fh-speed) ease,border-color var(--fh-speed) ease,box-shadow var(--fh-speed) ease,background var(--fh-speed) ease;
}
.tile:hover,
.cd-tile:hover,
.as-card:hover,
.rec-card:hover,
.choice:hover,
.optrow:hover{
  transform:translateY(-2px);
  border-color:color-mix(in srgb,var(--fh-brand) 58%,var(--fh-line));
  background:linear-gradient(155deg,color-mix(in srgb,var(--fh-brand) 6%,var(--fh-panel-hi)),var(--fh-panel));
  box-shadow:var(--fh-shadow-2);
}
.stat{
  padding:15px 16px;
}
.stat .n{
  color:var(--fh-text);
  font-family:var(--fh-font-mono);
  font-size:clamp(27px,4vw,34px);
  font-weight:650;
  line-height:1;
  letter-spacing:-.055em;
}
.stat .l{
  margin-top:8px;
  color:var(--fh-muted);
  font-size:11px;
  line-height:1.35;
}

/* Dashboard modules */
.featured{
  gap:16px;
}
/* ONE INK PER CARD, DECLARED BESIDE THE FILL. August 25, 2026.

   THE BUG THIS ENDS, AND IT SHIPPED IN BOTH THEMES AT ONCE. Measured on index.html
   in a real browser, sampling the painted pixel rather than reading declarations:

     light   title, body and arrow  #ffffff on rgb(250,247,254)   contrast 1.06
     dark    the icon glyph         #9342F1 on rgb(147,66,241)    contrast 1.05

   Both are invisible, not merely low, and both had the same cause: THE FILL AND THE
   THINGS DRAWN ON IT WERE DECIDED IN DIFFERENT FILES UNDER DIFFERENT CONDITIONS.

     - This sheet filled the card with the brand and set its text to --fh-on-brand,
       with NO theme guard on either rule.
     - dashboard.css replaces that fill in light mode with a near-white brand tint,
       at a higher specificity, and never touches the text, because the text rules
       live here and name the children. So light mode kept the on-brand WHITE text
       and lost the brand fill from under it.
     - The August 19 pass on this same problem fixed h3, p and the arrow by
       ENUMERATING them. .op-action-ico was not on that list, so the icon kept
       dashboard.css's color:var(--purple-bright), which in dark mode resolves to
       the same value as --fh-brand. The glyph was drawn in exactly the card's color.

   THE FIX IS NOT MORE ENUMERATION. A hand-kept list of children is what let the icon
   through, and the next child added would go the same way. The card now declares
   --fh-card-ink next to its own fill, and everything drawn on the card reads that one
   variable. Change the fill, change the ink on the line below it.

   .cd-exec is deliberately NOT given a light-mode ink: it keeps its brand fill in both
   themes, so it keeps the on-brand ink. Only the cards dashboard.css actually lightens
   get the light ink, and these two blocks are where that has to stay mirrored. */
/* RENAMED BY TD-21, September 4, 2026, and the rule is unchanged. TD-21 removed the
   home screen at Bryant's direction, so .op-action, the five tiles, became .op-act, the
   six actions on the client-first stage: same component, same brand fill from this
   sheet, 36px icon square instead of 44px. Every selector below follows it.
   TWO MEMBERS RETIRE RATHER THAN FOLLOW, because their subject went with the tiles.
   .op-action .oa-sub and .op-action-txt p were the sub-line and the live fact line under
   a tile title; TD-20 took both off at Bryant's word ("remove the measured facts too")
   and a stage action is a label and an arrow with nothing under it.
   AND .op-today IS DELIBERATELY NOT HERE. It is not a brand-filled card: dashboard.css
   washes it violet at .16 and its ink is --text on that wash, which is why it reads as
   the day rather than as a seventh front door. It was outside these lists before, as
   .op-action.op-today, for the same reason. */
.action,
.op-act,
.cd-exec{
  border:1px solid color-mix(in srgb,var(--fh-brand) 55%,var(--fh-line));
  background:var(--fh-brand);
  box-shadow:0 1px 0 var(--fh-edge-hi) inset,0 24px 54px -30px var(--fh-brand);
  /* Brand fill, so the ink is the color computed to be readable on that fill, and the
     icon chip is a scrim of that same ink. A brand-colored chip disappears here, which
     is exactly what it was doing. */
  --fh-card-ink:var(--fh-on-brand);
  /* 22%, chosen by measuring the rendered pixel, not by eye. It is a fraction of
     --fh-on-brand rather than a fixed color, so it inverts with the brand: a pale client
     brand makes --fh-on-brand dark and the scrim darkens with it.

     WAS 12%, AND 12% WAS MEASURED WRONG. The earlier numbers here (18% -> 2.94, 12% -> 3.19,
     1.53 clear of the card) came from sampling the chip's exact CENTER pixel, where a 22px
     glyph sits inside a 44px chip. That pixel is an antialiased sliver of the glyph, so white
     was being compared against a reading that already had white blended into it. The chip
     itself was never that color. verify_card_contrast_dom.js now takes the most common color
     in an inset patch instead, and on the honest reading 12% left the chip only 1.23 from the
     card, under the 1.4 the shape needs to be seen at all.

     Both rules bind, and the F&H violet is the worst case for both:
       raise it  and white-on-chip falls   (3.02 at 24%, 2.94 at 26%)
       lower it  and chip-on-card collapses (1.38 at 18%, 1.24 at 12%)
     20% to 24% satisfies every brand in the suite. 22% is the middle of that window:
     F&H violet reads 3.15 on the chip and 1.46 clear of the card, and the palest and
     darkest brands tested sit further inside the window than that, not closer to the edge. */
  --fh-chip-bg:color-mix(in srgb,var(--fh-on-brand) 22%,transparent);
  --fh-chip-ink:var(--fh-on-brand);
}
:root[data-theme="light"] .action,
:root[data-theme="light"] .op-act{
  /* dashboard.css turns these two into a near-white brand tint in light mode, so the ink
     is the page's own, and the chip can carry the brand at full strength with the
     on-brand glyph. That is the shape the client tiles and .cd-exec-ico already use, and
     it is what gives the icon its definition back on a pale card. */
  /* --text belongs to dashboard.css, not to this sheet, so it carries a fallback like every
     other foreign token here. Without one, a day when dashboard.css stops defining --text
     leaves color:var(--fh-card-ink) invalid at computed-value time, the color INHERITS, and
     the card goes quietly unreadable. That is the exact failure this file's header records at
     the top, and verify_theme_wiring.js caught it in this very block. */
  --fh-card-ink:var(--text,#16181F);
  --fh-chip-bg:var(--fh-brand);
  --fh-chip-ink:var(--fh-on-brand);
}
.action h3,
.op-act,
.cd-exec-h{
  color:var(--fh-card-ink);
}
.action p,
.cd-exec-s{
  /* WAS 76%, WHICH FAILED FOR EVERY BRAND INCLUDING F&H's OWN. Measured on the flat fill, the
     worst client cleared only 3.40 at 76% and F&H itself sat at 4.17, under the 4.5 body-text
     line. Raising the opacity does not rescue it either: the same worst case is 3.91 at 88% and
     4.34 at full strength, because the binding constraint is the fill and not the alpha. So the
     sub-line carries the same color at full strength, and the hierarchy is left to size and
     weight, which is where hierarchy belongs on a colored fill. */
  color:var(--fh-card-ink);
}
.statgrid{
  gap:14px;
}
.tile{
  text-align:left;
  min-height:154px;
  padding:21px 18px 17px;
}
.tile .ring{
  width:46px;
  height:46px;
  margin:0 0 20px;
  border-radius:var(--fh-radius-md);
  background:var(--fh-panel-low);
  box-shadow:0 0 0 1px var(--fh-line),0 10px 24px -18px var(--fh-shadow-color);
}
.tile .label,
.cd-name,
.as-cname{
  font-family:var(--fh-font-display);
  font-size:15px;
  font-weight:700;
  letter-spacing:-.015em;
}
.tile .sub,
.cd-desc,
.as-cdesc{
  color:var(--fh-muted);
  line-height:1.45;
}
.badge,
.stbadge,
.chip,
.pill,
.flag{
  border-color:var(--fh-line);
  box-shadow:0 1px 0 var(--fh-edge-hi) inset;
}
.badge.ai{
  color:var(--fh-text);
  border-color:var(--fh-brand);
  background:var(--fh-brand-soft);
}

/* Operational status, risk, and urgency. The label and icon remain present, so
   the interface never depends on color alone. */
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag){
  display:inline-flex!important;
  align-items:center;
  gap:7px;
  border:1.5px solid color-mix(in srgb,currentColor 72%,var(--fh-line))!important;
  background:color-mix(in srgb,currentColor 14%,var(--fh-panel))!important;
  box-shadow:0 1px 0 var(--fh-edge-hi) inset,0 0 14px -10px currentColor;
  font-weight:750!important;
  filter:saturate(1.22);
}
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)::before{
  content:"";
  width:7px;
  height:7px;
  flex:none;
  border-radius:50%;
  background:currentColor;
  box-shadow:0 0 0 3px color-mix(in srgb,currentColor 15%,transparent),0 0 10px -1px currentColor;
}
/* THE MARKER HAS TO SAY SOMETHING THE COLOR DID NOT ALREADY SAY.
   The brief requires status meaning to be redundant through a label, a marker or an icon AND
   color, because these screens are read in direct sunlight by people some of whom are colorblind.
   A dot that is the same circle in all three states is the color drawn twice, not redundancy, and
   this palette makes that worse than usual: measured on the dark surface, signal green and signal
   yellow sit at a 1.15 luminance ratio to each other, which is the pair a red-green colorblind
   reader separates least well.

   So each state gets its own silhouette, readable with the color removed entirely:
     safe     a circle,   which is what a dot already meant
     caution  a diamond,  a square turned 45 degrees
     stop     a square,   the heaviest shape, and the one that reads as a full stop
   The words stay where they were. This is a third channel, not a replacement for either.

   clip-path rather than a glyph on purpose: a glyph would depend on the font loading, and the
   font is the one thing in this stylesheet that can be slow on a rig with two bars. */
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--caution"]::before,
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--warn"]::before{
  border-radius:2px;
  clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%);
  width:9px;
  height:9px;
}
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--high"]::before,
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--danger"]::before,
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--stop"]::before{
  border-radius:2px;
  clip-path:none;
  width:8px;
  height:8px;
}
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--safe"]{
  color:var(--fh-signal-green-text)!important;
  border-color:var(--fh-signal-green)!important;
  background:var(--fh-signal-green-wash)!important;
}
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--caution"],
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--warn"]{
  color:var(--fh-signal-yellow-text)!important;
  border-color:var(--fh-signal-yellow)!important;
  background:var(--fh-signal-yellow-wash)!important;
}
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--high"],
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--danger"],
:is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--stop"]{
  color:var(--fh-signal-red-text)!important;
  border-color:var(--fh-signal-red)!important;
  background:var(--fh-signal-red-wash)!important;
}
/* STATUS-1: the approved pill colors, light theme only. filter:none because the measured
   ratios above are the painted colors; the saturate(1.22) on the shared rule would push them off. */
:root[data-theme="light"] :is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--safe"]{
  color:var(--fh-pill-safe-ink)!important;
  border-color:var(--fh-pill-safe-ring)!important;
  background:var(--fh-pill-safe-fill)!important;
  filter:none;
}
:root[data-theme="light"] :is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--caution"],
:root[data-theme="light"] :is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--warn"]{
  color:var(--fh-pill-caution-ink)!important;
  border-color:var(--fh-pill-caution-ring)!important;
  background:var(--fh-pill-caution-fill)!important;
  box-shadow:0 1px 2px rgba(0,0,0,.14),0 0 12px -4px var(--fh-pill-caution-fill);
  filter:none;
}
:root[data-theme="light"] :is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--high"],
:root[data-theme="light"] :is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--danger"],
:root[data-theme="light"] :is(.stbadge,.chiprow,.riskchip,.bandchip,.bpill,.ol-flag,.rec-tag)[style*="--stop"]{
  color:var(--fh-pill-stop-ink)!important;
  border-color:var(--fh-pill-stop-ring)!important;
  background:var(--fh-pill-stop-fill)!important;
  box-shadow:0 1px 2px rgba(0,0,0,.14),0 0 12px -4px var(--fh-pill-stop-fill);
  filter:none;
}
.badge.live{
  color:var(--fh-signal-green-text);
  border-color:var(--fh-signal-green);
  background:var(--fh-signal-green-wash);
  box-shadow:0 0 14px -8px var(--fh-signal-green);
}
.badge.soon{
  color:var(--fh-signal-yellow-text);
  border-color:var(--fh-signal-yellow);
  background:var(--fh-signal-yellow-wash);
  box-shadow:0 0 14px -8px var(--fh-signal-yellow);
}
/* THE RECORD LEADS, ITS STATUS FOLLOWS. DESIGN-FIX-1, September 28, 2026 (design review finding
   06). Seven modules put a record's name (.ty) and its status pill side by side in the card's top
   row. At 390 pixels a long status ("MAY NOT DRIVE, 5 items outstanding") took most of that row,
   Cole Barrett's name broke onto two lines, and the warning was read before the person was.
   Now the row wraps: when the name and the pill both fit, nothing changes, and the name grows to
   fill the row so the pill still sits at the right; when they do not, the pill moves to its own
   line directly under the name, left-aligned and whole. The words, colors and marker of the pill
   are untouched. Scoped with :has to a row that holds a name, so a row of a case number and a pill
   keeps its layout; a browser without :has keeps the old row. */
.rec-card .top:has(> .ty){flex-wrap:wrap;row-gap:6px}
.rec-card .top > .ty{flex:1 1 auto;min-width:0}
.rec-card .top:has(> .ty) > .stbadge{margin-left:0;max-width:100%}

/* UI-ACCENT-1: a status note keeps its color, carried by the wash, the hairline in the status
   color and a leading dot, and loses the 4px left rule. Every .note on a module page is a flex
   row with a 10px gap, so the dot is simply its first item. */
.note.safe{
  color:var(--fh-signal-green-text)!important;
  border:1px solid var(--fh-signal-green);
  background:var(--fh-signal-green-wash)!important;
}
.note.warn,
.note.caution{
  color:var(--fh-signal-yellow-text)!important;
  border:1px solid var(--fh-signal-yellow);
  background:var(--fh-signal-yellow-wash)!important;
}
.note.danger,
.note.high,
.note.stop{
  color:var(--fh-signal-red-text)!important;
  border:1px solid var(--fh-signal-red);
  background:var(--fh-signal-red-wash)!important;
}
:is(.note.safe,.note.warn,.note.caution,.note.danger,.note.high,.note.stop)::before{
  content:"";
  flex:0 0 auto;
  width:var(--status-dot-size);
  height:var(--status-dot-size);
  border-radius:50%;
  margin-top:.42em;
}
.note.safe::before{background:var(--status-dot-safe)}
:is(.note.warn,.note.caution)::before{background:var(--status-dot-caution)}
:is(.note.danger,.note.high,.note.stop)::before{background:var(--status-dot-stop)}
:is(.note.safe,.note.warn,.note.caution,.note.danger,.note.high,.note.stop) b{
  color:inherit!important;
}
.badge.sev-Low{
  color:var(--fh-signal-green-text)!important;
  border-color:var(--fh-signal-green)!important;
  background:var(--fh-signal-green-wash)!important;
}
.badge.sev-Medium{
  color:var(--fh-signal-yellow-text)!important;
  border-color:var(--fh-signal-yellow)!important;
  background:var(--fh-signal-yellow-wash)!important;
}
.badge.sev-High,
.badge.sev-Critical{
  color:var(--fh-signal-red-text)!important;
  border-color:var(--fh-signal-red)!important;
  background:var(--fh-signal-red-wash)!important;
}
.sdot.on[data-n="1"],
.sdot.on[data-n="2"]{
  background:var(--fh-signal-green)!important;
  box-shadow:0 0 0 3px var(--fh-signal-green-wash),0 0 14px -3px var(--fh-signal-green);
}
.sdot.on[data-n="3"]{
  background:var(--fh-signal-yellow)!important;
  box-shadow:0 0 0 3px var(--fh-signal-yellow-wash),0 0 14px -3px var(--fh-signal-yellow);
}
.sdot.on[data-n="4"],
.sdot.on[data-n="5"]{
  background:var(--fh-signal-red)!important;
  box-shadow:0 0 0 3px var(--fh-signal-red-wash),0 0 14px -3px var(--fh-signal-red);
}
.as-config,
.auth-card .auth-alt a{
  color:var(--fh-text);
  text-decoration:underline;
  text-decoration-color:var(--fh-brand);
  text-underline-offset:3px;
}

/* Tables and dense records */
.table-wrap,
.table-responsive{
  border:1px solid var(--fh-line);
  border-radius:var(--fh-radius-md);
  background:var(--fh-panel);
  box-shadow:var(--fh-shadow-1);
  overflow:auto;
}
table{
  border-collapse:separate;
  border-spacing:0;
  color:var(--fh-text);
}
th{
  color:var(--fh-muted);
  background:var(--fh-panel-low);
  font-size:11px;
  text-align:left;
}
th,
td{
  border-color:var(--fh-line);
  padding:12px 14px;
}
td{
  font-size:13px;
}
tbody tr{
  transition:background var(--fh-speed) ease;
}
tbody tr:hover{
  background:var(--fh-hover);
}

/* Forms */
.field > label,
.field label,
.as-fld label{
  color:var(--fh-muted);
  font-size:11px;
}
.ctl,
.field input,
.field select,
.field textarea,
.as-fld input,
.as-fld select,
.as-fld textarea,
.srcwrap textarea{
  min-height:46px;
  border-color:var(--fh-line-strong);
  border-radius:var(--fh-radius-sm);
  background:linear-gradient(180deg,var(--fh-recess),var(--fh-panel-low));
  color:var(--fh-text);
  box-shadow:0 1px 0 var(--fh-edge) inset,0 1px 0 var(--fh-edge-hi);
  transition:border-color var(--fh-speed) ease,box-shadow var(--fh-speed) ease,background var(--fh-speed) ease;
}
.ctl:hover,
.field input:hover,
.field select:hover,
.field textarea:hover,
.as-fld input:hover,
.as-fld select:hover,
.as-fld textarea:hover,
.srcwrap textarea:hover{
  border-color:color-mix(in srgb,var(--fh-brand) 45%,var(--fh-line-strong));
}
.ctl:focus,
.field input:focus,
.field select:focus,
.field textarea:focus,
.as-fld input:focus,
.as-fld select:focus,
.as-fld textarea:focus,
.srcwrap textarea:focus{
  outline:none;
  border-color:var(--fh-brand);
  box-shadow:0 0 0 4px var(--fh-focus),0 1px 0 var(--fh-edge-hi) inset;
}
.btn,
.addbtn,
.photo-add,
.as-preview-btn,
.as-include-btn,
.cd-share{
  min-height:44px;
  border-radius:var(--fh-radius-sm);
  transition:transform var(--fh-speed) ease,border-color var(--fh-speed) ease,background var(--fh-speed) ease,box-shadow var(--fh-speed) ease;
}
.btn,
.addbtn,
.photo-add,
.as-preview-btn{
  border-color:var(--fh-line-strong);
  background:linear-gradient(180deg,var(--fh-panel-hi),var(--fh-panel));
  color:var(--fh-text);
  box-shadow:0 1px 0 var(--fh-edge-hi) inset,0 8px 22px -20px var(--fh-shadow-color);
}
.btn:hover,
.addbtn:hover,
.photo-add:hover,
.as-preview-btn:hover{
  border-color:var(--fh-brand);
  background:var(--fh-hover);
}
/* THE FILL IS FLAT ON PURPOSE, AND THE REASON IS ARITHMETIC.
   brand-apply.js computes --brand-on as onColor(brand-500): the readable color ON THE MID TONE.
   This rule used to paint that text over linear-gradient(--brand-700, --brand-900), the two
   darkest steps of the ramp, which is a surface --brand-on was never chosen for. Measured against
   six plausible client primaries, the text-to-fill contrast at the dark end of that gradient was:

     F&H violet 14.69   safety yellow 2.25   hi-vis green 2.18
     cyan 2.17          orange 1.68          deep navy 1.45

   F&H's own violet is dark, so onColor returns near-white and it read correctly. Every brand that
   normalizes to a lighter mid tone gets near-black text, and near-black on --brand-900 is invisible.
   Nobody saw it because no client has been provisioned.

   Filling with --brand-500 honors the contract by construction, because that is the surface
   --brand-on was computed for. Same six brands, flat fill: 6.31, 10.45, 11.12, 9.82, 6.06, 4.34.
   The raised look is kept by the inset highlight and the drop shadow rather than by a dark end.

   KNOWN EDGE: a mid-blue primary lands at 4.34, just under the 4.5 body-text threshold. That is a
   property of the fill color itself, not of this rule; pure black would only reach 4.70. Closing
   it properly means brand-apply.js emitting a per-surface on-color, which is application code and
   is not this stylesheet's to change. */
.btn.primary,
.btn.pri,
.as-include-btn.add,
.cd-share{
  border-color:var(--fh-brand);
  background:var(--fh-brand);
  color:var(--fh-on-brand);
  box-shadow:0 1px 0 var(--fh-edge-hi) inset,0 14px 32px -20px var(--fh-brand);
}
.btn:active,
.addbtn:active,
.photo-add:active,
.as-preview-btn:active,
.as-include-btn:active,
.cd-share:active{
  transform:translateY(1px);
}

/* Status and decision displays. UI-ACCENT-1: these carried a 4px left rule in the hairline's
   own neutral color on every page that loads this sheet. The rule is gone; the full hairline
   and the resting shadow stay, which is the inside-a-card recipe with a neutral line, because
   a neutral stripe did not say "brand". Status colors live on .note.safe and friends above. */
.verdict,
.note,
.msg,
.banner{
  border:1px solid var(--fh-line);
  box-shadow:var(--fh-shadow-1);
}
.verdict .vlabel{
  font-size:23px;
  font-weight:760;
}
.verdict .vcol,
.verdict .vbasis{
  line-height:1.55;
}
.prog{
  height:6px;
  background:var(--fh-panel-low);
}
.prog i{
  background:linear-gradient(90deg,var(--fh-brand-strong),var(--fh-brand));
}

/* Empty and first-use states */
.empty,
.cd-empty{
  border:1px dashed var(--fh-line-strong);
  border-radius:var(--fh-radius-lg);
  background:linear-gradient(145deg,var(--fh-panel-hi),var(--fh-panel));
  box-shadow:var(--fh-shadow-1);
  padding:48px 24px;
}
.empty h2,
.empty-t{
  color:var(--fh-text);
  font-family:var(--fh-font-display);
  font-size:21px;
  font-weight:740;
  letter-spacing:-.02em;
}
.empty p,
.empty-b{
  color:var(--fh-muted);
  max-width:54ch;
  margin-left:auto;
  margin-right:auto;
}
.empty .btn,
.empty .addbtn{
  width:auto;
  min-width:190px;
  margin-top:18px;
}

/* Authentication */
#authGate{
  background-color:var(--fh-canvas);
  background-image:
    radial-gradient(90% 70% at 50% 0%,color-mix(in srgb,var(--fh-brand) 28%,transparent),transparent 62%),
    linear-gradient(180deg,var(--fh-panel-low),var(--fh-canvas));
}
.auth-card{
  position:relative;
  max-width:410px;
  border-radius:24px;
  padding:36px 32px 30px;
  overflow:hidden;
  box-shadow:var(--fh-shadow-3),0 0 0 1px var(--fh-edge-hi) inset;
}
.auth-card::before{
  content:"";
  position:absolute;
  inset:0 0 auto;
  height:4px;
  background:linear-gradient(90deg,var(--fh-brand-deep),var(--fh-brand),var(--fh-brand-strong));
}
.auth-card h2{
  font-size:25px;
  font-weight:760;
  letter-spacing:-.03em;
}
.auth-card .auth-sub{
  color:var(--fh-muted);
  line-height:1.5;
}
.auth-card input{
  min-height:48px;
  border-color:var(--fh-line-strong);
  background:var(--fh-recess);
  color:var(--fh-text);
  font-size:16px;
  box-shadow:0 1px 0 var(--fh-edge) inset;
}
.auth-card input:focus{
  border-color:var(--fh-brand);
  box-shadow:0 0 0 4px var(--fh-focus);
}
#authGo{
  min-height:48px;
}
/* DESIGN-FIX-1, September 28, 2026 (design review finding 01): the sign-in labels. Left-aligned
   above their box, in the body text color and not the dimmed hint color, because a label is the
   thing that says what goes in the box. */
.auth-card .auth-lbl{
  display:block;
  text-align:left;
  font-size:13px;
  font-weight:650;
  color:var(--fh-text);
  margin:0 0 6px 2px;
}

/* Bottom bars and safe areas */
.actionbar,
.as-dr-foot{
  border-color:var(--fh-line);
  background:color-mix(in srgb,var(--fh-panel) 92%,transparent);
  box-shadow:0 -18px 40px -34px var(--fh-shadow-color);
  backdrop-filter:blur(18px) saturate(130%);
}
.actionbar{
  padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));
}
.scroll{
  padding-bottom:calc(126px + env(safe-area-inset-bottom,0px));
}
/* THE ACTION BAR STAYS UP ON AN EMPTY MODULE, AND THAT IS A REVERSAL.
   This block used to read:

     body:has(#scroll .empty) #actionbar { display:none; }
     body:has(#scroll .empty) .scroll    { padding-bottom:calc(32px + safe-area); }

   which was aimed at a real problem the brief named: on module-18 the fixed bar can cover the
   empty state. Hiding the bar does clear the overlap. It also removes the only control that
   creates the first record, on a screen whose own words are "No permits yet. Tap below to raise
   one." Rendered on 2026-08-19 across the 18 module pages, that is exactly what happened: the
   empty state told the user to tap below and there was nothing below to tap.

   It is the first thing a new client sees in a module, so it is the worst possible screen to take
   the button off.

   The overlap is fixed the other way instead: leave the bar where it is and keep the full bottom
   padding on the scroller, which is what .scroll already declares above, so the empty card clears
   the bar rather than hiding behind it. Nothing is covered and nothing is removed. */

/* Known phone corrections that can be made safely with presentation alone. */
.board-row .bmain,
.board-row .bsub{
  display:block;
}
.board-row .bsub{
  margin-top:3px;
  line-height:1.4;
}

@media(max-width:820px){
  .wrap{
    padding-left:16px;
    padding-right:16px;
  }
  header.bar{
    border-radius:var(--fh-radius-md);
  }
  .statgrid{
    grid-template-columns:repeat(2,minmax(0,1fr));
  }
  .featured{
    grid-template-columns:1fr;
  }
  .tile{
    min-height:142px;
  }
  .home-head{
    padding:22px 20px 30px !important;
  }
}

@media(max-width:430px){
  .stat-row{
    display:grid;
    grid-template-columns:repeat(2,minmax(0,1fr));
  }
  .stat-row .stat{
    min-width:0;
  }
  .auth-card{
    padding:30px 22px 24px;
  }
}

@media(max-width:820px) and (pointer:coarse){
  input,
  select,
  textarea,
  .ctl,
  .fin,
  .fta,
  .srcwrap textarea{
    font-size:16px !important;
  }
  button,
  .btn,
  .back,
  .bar .back,
  .navlink,
  .iconbtn,
  .exbtn,
  .chip,
  .pill{
    min-height:44px;
  }
  /* PW-4 (phone finding, September 14, 2026): the 44px floor just above makes a bare .chip/.pill
     taller than its label, and an inline span does not vertically center its text, so the label sat
     at the top of the pill with the gap beneath it (measured 26px above and 77px below on the
     library card's NOT READY pill). inline-flex centering puts the label in the middle on both axes,
     for every status and both themes, and leaves the pill hugging its content horizontally. */
  .chip,
  .pill{
    display:inline-flex;
    align-items:center;
    justify-content:center;
  }
}

@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    scroll-behavior:auto !important;
    transition-duration:.001ms !important;
  }
}

@media print{
  .sheet,
  .sheet *{
    box-shadow:none !important;
    text-shadow:none !important;
  }
}

/* ============================================================================
   THE MODULE TOP BAR CARRIES THE HERO TREATMENT, August 19, 2026.
   The bar reading the company name sits at the top of all 18 modules, and the theme had left it
   as a pale sticky strip. The hero block on the 11 modules that have one already had the look
   Bryant wants, so this is that same recipe moved onto the bar, which every module has.

   Scoped with body:has(#scroll) because only the module pages carry #scroll. index.html and the
   Executive Dashboard use the same header.bar class and are deliberately left alone.

   Every color here is a brand token, so a client with an orange palette gets an orange bar. The
   three background layers are the original recipe, verbatim: a fine diagonal weave, a corner
   highlight, and the brand ramp underneath. */
/* Rebuilt September 4, 2026 (TD-19), to the same polish vocabulary as the client
   home's hero. The white diagonal weave is GONE, on Bryant's instruction of that
   date; the visual direction says it comes off every hero rather than being
   refined. In its place: the counter-shade band that carries the ink over the
   contrast floor for this client (brand-apply.js works out its color and its
   strength), the lit corner this bar always had, a dark counter-shade in the
   opposite one, and the brand ramp underneath, unchanged. The ink reads
   --fh-on-brand rather than a fixed white, so a pale client primary gets dark
   letters instead of white on white. */
body:has(#scroll) header.bar{
  position:sticky;
  overflow:hidden;
  color:var(--fh-on-brand);
  border-bottom-color:transparent;
  background:
    linear-gradient(180deg,var(--brand-scrim,rgba(0,0,0,.26)) 0%,transparent 62%),
    radial-gradient(130% 150% at 88% -25%,rgba(255,255,255,.18),transparent 55%),
    radial-gradient(95% 125% at -12% 122%,rgba(0,0,0,.26),transparent 58%),
    linear-gradient(135deg,var(--fh-brand-bright) 0%,var(--fh-brand) 48%,var(--fh-brand-strong) 100%);
  box-shadow:
    0 18px 40px -24px color-mix(in srgb,var(--fh-brand) 55%,transparent),
    inset 0 1px 0 rgba(255,255,255,.28),
    inset 0 0 0 1px rgba(255,255,255,.08);
}
body:has(#scroll) header.bar .ttl,
body:has(#scroll) header.bar .ttl.co{
  position:relative;
  z-index:1;
  color:var(--fh-on-brand);
}
/* The back control and the nav that module-nav.js injects both have to stay legible on the brand
   fill, and both are outside this stylesheet's markup, so they are restyled rather than moved. */
body:has(#scroll) header.bar .back,
body:has(#scroll) header.bar .fhnav,
body:has(#scroll) header.bar .fhnav-back,
body:has(#scroll) header.bar .fhnav-fwd,
body:has(#scroll) header.bar .fhnav-close{
  position:relative;
  z-index:1;
  color:#fff;
  background:rgba(255,255,255,.14);
  border-color:rgba(255,255,255,.24);
}
body:has(#scroll) header.bar .fhnav-lbl{
  color:#fff;
}

/* THE COMPANY NAME MUST SURVIVE A PHONE. Added 27 Aug 2026.

   Measured on the deployed site and reproduced here: at 390px #barTitle rendered ELEVEN pixels
   wide, and at 360px it rendered ZERO. The client's own company name, on the client's own module
   page, was not on screen. It is `flex:1;min-width:0`, so it absorbs the entire shortfall while
   .back (`flex:none`, 179px with its full text label) and the two controls module-nav.js injects
   refuse to give any of it back. The bar's content ran 52px past its box and `overflow:hidden`
   swallowed the difference, so the page never scrolled sideways and nothing looked wrong.

   The fix takes the space back from the two controls that can spare it rather than from the name.
   Both .back and #fhCloseModule carry their label in a <span> and their accessible name in an
   aria-label, so hiding the span costs a screen reader nothing: the chevron and the cross keep
   their meaning, and every assistive technology still reads "Back to Main Dashboard" and
   "Close module".

   Scoped body:has(#scroll) like the rest of this block, so index.html and the Executive Dashboard
   are untouched. 560px matches the breakpoint the client brand bar uses in dashboard.css.
   verify_module_header_onscreen.js pins it. */
@media(max-width:560px){
  body:has(#scroll) header.bar .back span,
  body:has(#scroll) header.bar #fhCloseModule span{
    display:none;
  }
  body:has(#scroll) header.bar .back,
  body:has(#scroll) header.bar #fhCloseModule{
    gap:0;
    padding-left:10px !important;
    padding-right:10px !important;
    /* an icon-only control still has to be hittable with a glove on, so it keeps a 44px box
       rather than shrinking to its 15px glyph */
    min-width:44px;
    justify-content:center;
  }
}
/* THE WATERMARK. #brandLogo already holds the client logo when one is published and the F&H seal
   otherwise, so this needs no new markup and no new asset: it takes the image that is already in
   the bar and enlarges it into the corner mark, the way the hero block does. soft-light keeps it
   as a texture rather than a second logo competing with the name. */
body:has(#scroll) header.bar .brand-logo{
  /* !important because the page hides this image from JAVASCRIPT, as an inline style, and an
     inline style beats any selector. It is hidden because the bar shows the module name instead
     of the logo, which was the right call when the bar was a pale strip. On the brand fill there
     is room for both, and the image is already the client's when one is published. */
  display:block !important;
  position:absolute;
  /* Far enough right that the seal is a corner texture and not a disc sitting under the close
     control. The hero block does the same thing with right:-30px bottom:-40px; the bar is shorter,
     so it needs more horizontal offset to read the same way. z-index keeps it behind the title
     and the nav, which are both set position:relative above for exactly that reason. */
  /* BROUGHT FULLY INSIDE THE BAR, 30 Aug 2026. It used to hang 52px off the right at 150px
     across, so the client's own mark was sliced by the edge of their own header. Bryant's
     words: it looks funny when it is cut off. A watermark that is cut off does not read as a
     watermark, it reads as an image that failed to load properly, and it was the client logo
     it was happening to.

     Smaller and fainter rather than bigger and bled. The bar already carries the company name
     in 48px serif, so this does not need to assert anything: at 84px and .20 it is a texture
     behind the controls, which is what the note above always said it was for.

     ROUNDED SQUARE, NOT A CIRCLE. The 50% here contradicted the standing rule for this estate,
     which is that a client logo is never masked into a circle because a circle cuts a wordmark.
     object-fit:contain finishes the job: the whole mark is shown, never a crop of it. */
  right:14px;
  left:auto;
  top:50%;
  transform:translateY(-50%);
  z-index:0;
  width:84px;
  height:84px;
  max-width:none;
  border-radius:16%;
  object-fit:contain;
  opacity:.20;
  mix-blend-mode:soft-light;
  box-shadow:none;
  pointer-events:none;
}

/* THE COMPANY NAME NEEDS ROOM FOR ITS DESCENDERS. Added 30 Aug 2026.

   Every module page sets `.bar .ttl.co{font-size:clamp(30px,7.5vw,48px);line-height:1.12}`, and
   the title carries overflow:hidden. Fraunces at 48px declares a font box of 71px: 56 up, 15
   down. A line box of 53.76px cannot hold that, so the half-leading goes NEGATIVE and the
   overflow rule quietly removes the difference. Measured on the deployed page: the title element
   was 53.75px tall around content that needed 62px, and the tails of the g and the y in
   "Basin Ridge Energy" were cut off. Any client whose name carries a descender had the same
   problem, which is most of them.

   1.5 is the font box, not a guess: 48 x 1.5 = 72 against the 71 the font asks for. It is set
   here rather than in fifteen module files because this stylesheet loads after their inline
   style and `header.bar` outweighs `.bar`, so one rule reaches all twenty-two of them. The bar
   grows about eighteen pixels at full size and proportionally less on a phone, where the name
   is already clamped smaller. A taller header is the price of the name being on screen. */
header.bar .ttl.co{
  line-height:1.5;
}

/* ============================================================================
   THE KPI TILES, Executive Dashboard.
   DESIGN-FIX-1, September 28, 2026: the 3px bar across the top of each tile is retired, as the
   left rails were by UI-ACCENT-1. The tile's state (good, watch, act) now leads its label as a
   status dot, the recipe the module stat tiles already use. The Compliance health tile is .b,
   the brand, not a state, so it gets no dot; its band word is on the tile.

   The dots read Bryant's STATUS-1b pill rings. This page does not set data-theme="light", so the
   ring tokens, which live in the light block above, are not defined here and the value after
   each comma is what paints: the same three approved values, written a second time.
   verify_accent_lines.js holds those three literals to the approved tokens, so the second copy
   cannot drift from the first. On white each ring clears 3:1 as a graphic: 3.50, 4.68, 7.05.

   The tiles keep the 14px gap below the hero. It was opened so a 3px bar could read against the
   page; with no bar it still separates the tiles from the hero, and removing it is not this
   change's to make. */
.tiles{
  margin-top:14px;
}
.tile:is(.s,.c,.d) .k::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;
}
.tile.s .k::before{ background:var(--fh-pill-safe-ring,#009E4C); }
.tile.c .k::before{ background:var(--fh-pill-caution-ring,#A86400); }
.tile.d .k::before{ background:var(--fh-pill-stop-ring,#B5001B); }

/* ============================================================================
   SECTION HEADINGS ON THE EXECUTIVE DASHBOARD, August 19, 2026.
   The five panel headings ("Modules", "Needs attention", "Compliance by domain", "Incident
   trend", "Forms built for this client") were 15px and sat at almost the same weight as the body
   text under them, so nothing on the page announced where one section stopped and the next began.

   All five are raised together rather than just the one that prompted it. Making a single heading
   larger would be the same inconsistency in the other direction, and the point of the exercise is
   that the page reads as one thing.

   The rule sits in the theme rather than in the page so it comes off with the theme. */
.panel h2{
  font-size:19px;
  font-weight:750;
  letter-spacing:-.02em;
  padding-bottom:8px;
  margin-bottom:4px;
  /* A border rather than text-decoration: an underline drawn by the font sits tight under the
     letters and cuts through descenders. This one spans the panel and reads as a rule under a
     heading, which is what it is. */
  border-bottom:1px solid var(--fh-line);
}

/* ============================================================================
   TEXT ON A BRAND-FILLED CARD, August 19, 2026. Found by forcing dark mode and measuring.

   This stylesheet fills .action, .op-action and .cd-exec with the brand color. dashboard.css sets
   the text INSIDE those cards, and it was written for a card on a normal surface, so it uses the
   ordinary page colors. Measured on index.html with data-theme="dark":

     .op-action-txt h3   --text        rgb(223,227,238) on rgb(144,90,207)   3.60
     .op-action-txt p    --text-dim    rgb(139,147,173) on rgb(144,90,207)   1.51
     .op-action-go       --purple-bright rgb(144,90,207) on rgb(144,90,207)  1.00

   The last one is the arrow, drawn in exactly the color of the card behind it. It is not low
   contrast, it is invisible, and it is the control that tells you the card is a link.

   The card fill is this stylesheet's doing, so the text on it is this stylesheet's problem.
   --fh-on-brand is the color brand-apply.js computes to be readable on that exact fill, and it
   follows the client's palette. The existing rules named .op-action itself; dashboard.css targets
   the children, which is more specific, so the children have to be named here too. */
.action h3,
.action p,
.op-act-t,
.op-act-go,
.cd-exec-h,
.cd-exec-s{
  color:var(--fh-card-ink);
}
/* The icon chip, which the August 19 pass missed. dashboard.css gives it
   background:var(--ctl-bg) and color:var(--purple-bright), both of which are page colors
   on a card that is not a page surface. This sheet made the card, so this sheet says what
   is legible on it. Equal specificity to dashboard.css's rule, and this stylesheet loads
   last, which is the whole reason for that ordering rule. */
.op-act-ico,
.op-act .op-act-ico{
  background:var(--fh-chip-bg);
  border-color:color-mix(in srgb,var(--fh-card-ink) 26%,transparent);
  color:var(--fh-chip-ink);
}
/* SUITE-FIX-4, October 6, 2026: THE CHIP CAME OFF A SECOND TIME, BY SPECIFICITY. When the
   painted function art went in, dashboard.css gained ".op-act .op-act-ico{...background:none;
   box-shadow:none}" (its "Master Dashboard refinement" block). Two classes beat the one-class
   rule above, so the chip lost its fill on every card and every brand, and
   verify_card_contrast_dom measured it at 1.00 to 1.07 against its own card from September 16
   on. The second selector above puts the chip back at the same weight, and this sheet loads
   last, so it wins. Same tokens, so it still follows the client's brand: a scrim of the card's
   ink in dark mode, the brand at full strength in light mode.
   The line-drawn fallback (what shows offline, before the painted art arrives) was set to
   --text in the same block, which is dark ink on a full-strength brand chip in light mode. It
   takes the chip's own ink now, which is the color brand-apply.js computes to read on that
   fill. And the light chip gets back the soft lift it had before the refinement, in the brand's
   own color, so it reads as a raised tile and not a flat swatch. Dark mode keeps the edge
   dashboard.css already gives it. dashboard.css is in the service worker's CORE budget, so the
   fix lives here. */
.op-act .fh-function-art>svg{
  color:inherit;
}
:root[data-theme="light"] .op-act .op-act-ico{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 6px 14px -6px color-mix(in srgb,var(--fh-brand) 60%,transparent);
}
/* The sub-line and the arrow sit back a little, but by weight and size rather than by fading the
   color, because fading is what put them at 1.51 and 1.00 in the first place. */
.action p,
.cd-exec-s{
  font-weight:450;
}
.op-act-go{
  opacity:.9;
}

/* UI-ACCENT-1, September 23, 2026: THE MASTER DASHBOARD'S OWN SURFACES. These classes belong to
   dashboard.css, and their stripes were removed there. What replaced them lives here, beside the
   tokens, because dashboard.css is in the service worker's first download and that download has
   a size budget (verify_sw.js) with almost no room left in it. This sheet loads after it. */

/* The selected suite in the spine wore a 3px rail in its own accent (an underline below 820px).
   It is the selected recipe at every width: a flat tint of the suite's OWN accent over
   --select-base and a hairline in that accent. dashboard.js sets --op-accent on the row, because
   a color in a stylesheet does not change when the client changes. */
.op-row.on{
  background:linear-gradient(color-mix(in srgb,var(--op-accent,var(--fh-brand)) var(--select-pct),transparent),color-mix(in srgb,var(--op-accent,var(--fh-brand)) var(--select-pct),transparent)),var(--select-base);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--op-accent,var(--fh-brand)) var(--select-line-pct),transparent);
}
/* Configure suite, a module that is in the suite. The row already has a 1px border, so the
   hairline is that border in --select-line: the line --select-edge draws, without doubling it. */
.sb-row.on{background:var(--select-wash),var(--select-base);border-color:var(--select-line)}
/* An action card is emphasis inside a card: no rule and no fill of its own. */
.acard{border:0;box-shadow:var(--emph-edge),var(--emph-shadow-nested)}
/* A finding is status: a dot and a hairline in its severity color. dashboard.js sets --sev per
   row, in the same colors the rule used; the severity pill in the title is unchanged. */
.finding{border:0;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--sev,var(--purple)) 38%,transparent),var(--emph-shadow-nested)}
.finding .ft::before{content:"";display:inline-block;width:var(--status-dot-size);height:var(--status-dot-size);border-radius:50%;margin-right:8px;vertical-align:.08em;background:var(--sev,var(--purple))}
/* Suite Assembly ties the open cards to their heading. That was a 2px rule down their left; it
   is a tray now, the accent hairline all the way round and the nested shadow. */
.as-area.open .as-grid{padding:14px;border-radius:var(--fh-radius-md);box-shadow:var(--emph-edge),var(--emph-shadow-nested)}

/* SUITE-FIX-3, October 5, 2026: THE SPINE'S SECTION LABELS IN LIGHT. "Suites" and "F&H" are
   9.5px bold capitals in --text-dim, and verify_td19_contrast_dom.js measured "Suites" at 4.44:1
   on the light spine, under the 4.5 floor for text. --metal-3 is the next gray down in the light
   palette (#565C6E, about 5.9:1 on that ground), so the labels keep their look and clear the
   floor. Dark is left alone: --text-dim already clears it there. */
:root[data-theme="light"] .op-spine-sec{color:var(--metal-3,#565C6E)}
