/* =========================================================
   FSN APP-SHELL STYLESHEET — SHELL-SCOPED RULES ONLY
   =========================================================

   This file holds every FSN rule scoped to `body.fsn-app-shell` (Administrator,
   Client and Contractor) or `body.aside-enabled` (Administrator only). The public
   landing pages set `app-landing` on the body, so nothing here can reach them.

   IT IS A SEPARATE FILE ON PURPOSE. These rules used to sit at the end of
   `fsn-overrides.css`, which is also where anyone adding a rule appends. Two
   consecutive boundaries appended public rules and landed inside the region. The
   separation removes the failure mode rather than restating the rule.

   `base/app.blade.php` loads this file immediately after `fsn-overrides.css`, so the
   cascade order is exactly what it was when these rules were the tail of that file.

   WHAT MAY BE ADDED HERE. Only a rule whose selector starts with
   `body.fsn-app-shell` or `body.aside-enabled`. `StylesheetRegionSeparationTest`
   rejects anything else, and rejects a shell-scoped rule left in `fsn-overrides.css`.

   THE CONTENT BELOW IS UNCHANGED. The move was proved order-and-location only by
   comparing the sorted lines of the original file against the sorted lines of the two
   files it became. */

/* Row Actions menu (<x-ui.table.actions>): NO overflow exception is needed, and the
   two rules that used to live here were removed.

   HISTORY. Commit 0d805bd added `.table-responsive:has(.dropdown-menu){overflow:visible}`
   plus a matching `.card:has(...)` rule, because a Bootstrap `.dropdown-menu` is
   absolutely positioned and was clipped by the `.table-responsive` scroll container.
   The component later moved to the Metronic KTMenu pattern, and the selector was
   re-pointed at `[data-kt-menu-trigger]`, but the exception itself was never
   re-examined.

   WHY IT IS OBSOLETE. KTMenu positions an open menu with `position: fixed` and an
   inline `inset`/`transform`, so the menu is placed against the VIEWPORT. A fixed
   element is not clipped by an ancestor's `overflow`, so nothing needed un-clipping.
   Measured with the menu open: `position: fixed`, `z-index: 107`, and the menu
   rendered past the container's right edge while staying fully visible. No ancestor
   creates a containing block through `transform`, `filter`, `contain`, or
   `will-change`, which is the only condition that would reintroduce clipping.

   WHY IT HAD TO GO. `overflow: visible` also cancelled Bootstrap's horizontal
   scrolling on `.table-responsive`, so a wide admin table pushed the whole page
   sideways. Measured on Admin Categories at a 390px viewport: body scrollWidth 586
   against clientWidth 375. With the exception removed the body returns to 375, the
   table scrolls inside its own container again, and the menu stays visible.

   Removing an exception is preferred over narrowing it to a breakpoint: a breakpoint
   rule would keep a global exception alive for a problem that no longer exists. */

/* One consequence of restoring the scroll box had to be corrected. The row-action
   trigger is the last cell in the row, so it sits flush against the container's right
   edge: measured `triggerRight === containerRight` at 1920, 768, 390, and 320. The
   focus ring is a 2px outline drawn at a 2px offset, so its outer edge overshot the
   container by 3px to 4px and was clipped once the container stopped being
   `overflow: visible`. Nothing was visible before, because nothing clipped.

   The ring design is not changed — the outline and its offset stay exactly as the
   focus boundary defined them. The container simply gets that space back. This is
   padding, not an overflow exception, so it does not restore the defect. It is scoped
   to admin tables that actually contain a row-action trigger. */
body.aside-enabled .table-responsive:has([data-kt-menu-trigger]) {
    padding-inline-end: 4px;
}
/* =========================================================
   ADMIN ACCESSIBILITY — WCAG 2.2 AA (D-4 focus, D-5 contrast)
   =========================================================

   ROOT CAUSE (D-4). The Metronic bundle sets `outline: 0` on focus and relies on
   `box-shadow` for the ring. That box-shadow is broken in this build: 9 rules emit
   `box-shadow: false, ...`, and `false` is not a valid CSS value, so the WHOLE
   declaration is dropped. `.form-control:focus`, `.form-select:focus`, and the
   validated variants therefore render with no indicator at all. `.btn:focus-visible`
   resolves `var(--bs-btn-focus-box-shadow)`, which is empty for several variants, so
   buttons lose the ring too. Measured on a genuinely `:focus-visible` control:
   `outline: none 0px` and `box-shadow: none`.

   FIX. Restore a real `outline` instead of repairing the broken box-shadow chain.
   `outline` never affects layout, so there is no shift, resize, or clipping.

   TWO-COLOUR RING, and why it is necessary. No single colour clears 3:1 on every
   admin surface:
     dark  #071437 -> 18.04:1 on white, 17.13:1 on #F9F9F9, but only 1.12:1 on the
                     navy sidebar;
     white #FFFFFF -> 16.17:1 on the navy sidebar, but 1.00:1 on white.
   The dark outline sits outside a white halo that fills the 2px offset gap, so a
   light surface shows the dark ring and a dark surface shows the white ring.

   SCOPE. `body.fsn-app-shell` marks the authenticated FSN application shells. It is
   set by the default body class in resources/views/base/app.blade.php, which serves
   the Client and Contractor shells, and by resources/views/base/admin.blade.php.
   The public landing layout overrides the body class with `app-landing`, so marketing
   pages are NOT affected. The sign-in screens share the default body class and do
   receive the ring; that is intentional, because a visible focus indicator on a login
   form is a benefit, not a regression.

   The ring was admin-only until this boundary. `body.aside-enabled` remained the
   scope for the D-5 CONTRAST rules further down, which stay admin-only on purpose.
   This file loads after style.bundle.css, so no vendor file is modified. */

body.fsn-app-shell {
    --fsn-focus-ring: #071437;
    --fsn-focus-halo: #ffffff;
}

/* The focus ring. `:focus-visible` only, so a mouse click does not draw it. */
body.fsn-app-shell :focus-visible {
    outline: 2px solid var(--fsn-focus-ring);
    outline-offset: 2px;
    box-shadow: 0 0 0 2px var(--fsn-focus-halo);
}

/* Vendor rules set `outline: 0` directly on these selectors, so matching the same
   specificity is not enough to win. These are the narrowest selectors that restore the
   ring, and they are the only place !important is used in this block. */
body.fsn-app-shell .form-control:focus-visible,
body.fsn-app-shell .form-select:focus-visible,
body.fsn-app-shell .btn:focus-visible,
body.fsn-app-shell .page-link:focus-visible,
body.fsn-app-shell .menu-link:focus-visible,
body.fsn-app-shell .nav-link:focus-visible {
    outline: 2px solid var(--fsn-focus-ring) !important;
    outline-offset: 2px;
    box-shadow: 0 0 0 2px var(--fsn-focus-halo) !important;
}

/* On the navy sidebar the halo carries the contrast, so the order is reversed. Only
   the admin shell renders `.aside`, so this stays admin-only in practice. */
body.fsn-app-shell .aside :focus-visible {
    outline: 2px solid var(--fsn-focus-halo) !important;
    outline-offset: 2px;
    box-shadow: 0 0 0 4px var(--fsn-focus-ring) !important;
}

/* The blog tag field — PUBLIC-PAGES-AND-BLOG-01 round 3, D2 and D3. The field is ONE
   control: its ring surrounds the whole group — the box of tag pills and the Add
   button — while any part of it has focus. The text box inside draws no ring of its
   own, and the Add button marks itself with a fill rather than a second ring. */
body.fsn-app-shell .fsn-tag-box-input:focus-visible {
    outline: none !important;
    box-shadow: none !important;
}

body.fsn-app-shell .fsn-tag-control:focus-within {
    outline: 2px solid var(--fsn-focus-ring);
    outline-offset: 2px;
    box-shadow: 0 0 0 2px var(--fsn-focus-halo);
}

body.fsn-app-shell .fsn-tag-control .fsn-tag-add:focus-visible {
    outline: none !important;
    box-shadow: none !important;
    background-color: var(--bs-gray-300);
}

/* A natively disabled control must never look actionable.

   This rule used to read:

       body.aside-enabled :disabled:focus-visible,
       body.aside-enabled .disabled :focus-visible { ... }

   The second selector was over-broad. It removed the ring from ANY focusable
   descendant of a `.disabled` container, not just from a disabled control. That was
   harmless while an unavailable pagination control was a non-focusable <span>, but
   once it became a real button inside `li.page-item.disabled` it hid the indicator on
   a control that can still hold focus — a WCAG 2.4.7 failure. Only the native-disabled
   half remains. `aria-disabled` controls keep their ring, which is correct: they are
   still focusable and the user must see where focus is. */
body.fsn-app-shell :disabled:focus-visible {
    outline: none !important;
    box-shadow: none !important;
}

/* ---------------------------------------------------------
   D-5 CONTRAST
   ---------------------------------------------------------

   Scoped to `.wrapper`, the admin CONTENT column, NOT to `body`. The navy sidebar
   contains 5 elements using text-gray-500/600 that already pass on dark
   (12.12:1 and 6.26:1); darkening those would REDUCE their contrast. */

/* ── 🔴 THESE CORRECTIONS WERE ADMIN-ONLY, AND MOST OF THEIR USAGE IS NOT ──
   Widened from `body.aside-enabled .wrapper` to `body.fsn-app-shell .wrapper` by
   BL-SHELL-01, after measuring where the corrected classes actually render:

     class                  files OUTSIDE admin   files INSIDE admin
     text-muted                       46                   4
     badge-light-primary              21                  15
     badge-light-success              19                  16
     text-gray-400                    19                   9
     badge-light-warning              10                   9
     badge-light-danger                9                  29
     nav-link                          8                   6
     text-success                      8                  12

   `aside-enabled` is set by `base/admin.blade.php` and nowhere else, so every one
   of those outside-admin usages kept the uncorrected Metronic colour — including
   `badge-light-warning` at 1.58:1, the worst reading in the application. This is
   `§6D`: the fix was proved safe in one context and the context did not transfer
   by itself.

   🔴 WHY `body.fsn-app-shell .wrapper` IS THE SAFE WIDENING, measured not assumed:

     admin      body="fsn-app-shell aside-enabled …"   has .wrapper  -> still matches
     client     body="fsn-app-shell …"                 has .wrapper  -> NOW matches
     contractor body="fsn-app-shell …"                 has .wrapper  -> NOW matches
     landing    body="app-landing"                     .wrapper = 0  -> CANNOT match

   The landing exclusion is a DOUBLE guarantee, and it is load-bearing: the public
   menu links carry `menu-link nav-link`, so the `.nav-link` correction below would
   have turned the public navigation green if either guarantee failed. Neither can:
   the landing body is not `fsn-app-shell`, AND the landing page contains no
   `.wrapper` element at all.

   The three `body.aside-enabled` rules that are NOT `.wrapper` are genuine admin
   chrome — the aside menu and the admin table overflow — and are deliberately left
   alone. */

/* Muted content text. gray-600 #78829D measured 3.83:1 on white and 3.58:1 on the
   #F7F7F7 toolbar. gray-500 #99A1B7 measured 2.34:1 on white. All three are below the
   4.5:1 normal-text minimum. gray-700 #4B5675 is an existing Metronic token, so no new
   colour is invented.

   `!important` is required here, not optional. The vendor utilities are declared as
   `.text-gray-600 { color: var(--bs-text-gray-600) !important }`, so a normal
   declaration loses no matter how specific the selector is. The selector stays as
   narrow as the fix allows: admin shell + content column + the single utility class.

   gray-500 and gray-600 both map to gray-700, which removes the step between them.
   That step was 3.83:1 vs 2.34:1 — both failing, so no compliant hierarchy existed to
   preserve. On these pages the hierarchy comes from font size and weight. */
body.fsn-app-shell .wrapper .text-gray-600,
body.fsn-app-shell .wrapper .text-gray-500 {
    color: var(--bs-gray-700) !important;
}

/* Light badge text. The background stays unchanged, so the semantic status colour is
   preserved; only the text darkens.
   badge-light-primary: #E11D2E on #DFFFEA measured 4.44:1 -> #B30F1C gives 6.54:1.
   badge-light-success: #1B84FF on #E9F3FF measured 3.23:1 -> #0A58CA gives 5.74:1. */
body.fsn-app-shell .wrapper .badge-light-primary {
    color: #b30f1c;
}

body.fsn-app-shell .wrapper .badge-light-success {
    color: #0a58ca;
}

/* Active pagination page. White on #17C653 measured 2.27:1. #0F7A34 keeps the green
   identity and gives white text 5.45:1. */
body.fsn-app-shell .wrapper .page-item.active .page-link {
    background-color: #0f7a34;
    border-color: #0f7a34;
    color: #ffffff;
}

/* Form control boundaries (WCAG 1.4.11, 3:1 for meaningful non-text UI). The gray-300
   border #DBDFE9 measured 1.33:1 on white. gray-600 #78829D is an existing token and
   measures 3.83:1. */
body.fsn-app-shell .wrapper .form-control,
body.fsn-app-shell .wrapper .form-select {
    border-color: var(--bs-gray-600);
}

/* The blog tag field's Add button is the box's joined addon, so it takes the same
   boundary; otherwise the joined edge shows a second, lighter colour. Round 3, D3. */
body.fsn-app-shell .wrapper .fsn-tag-control .fsn-tag-add {
    border-color: var(--bs-gray-600);
}

/* Remaining light badge variants. Same principle as above: keep the background, so the
   status colour still reads, and darken only the text.
   badge-light-warning: measured 1.58:1 — the worst reading in the admin shell.
   badge-light-danger:  measured 3.45:1. */
body.fsn-app-shell .wrapper .badge-light-warning {
    color: #7a5200;
}

body.fsn-app-shell .wrapper .badge-light-danger {
    color: #b3082f;
}

/* Bootstrap's .text-muted and the checkbox labels resolve to gray-500 #99A1B7, which
   measured 2.58:1. `.text-muted` carries the "Showing X to Y of Z results" paginator
   summary on every admin list, so it is content, not decoration. */
body.fsn-app-shell .wrapper .text-muted,
body.fsn-app-shell .wrapper .form-check-label {
    color: var(--bs-gray-700) !important;
}

/* Active sidebar item. This is the only element in the aside that fails, because its
   active state swaps the navy background for a near-white #F9F9F9 pill while the label
   keeps the light green #17C653 — measured 2.16:1 on the one control that tells the
   user which page is open. Inactive items are unaffected and stay at 12.12:1.
   #0F7A34 is the same dark green already used for the active pagination page, so the
   green identity is kept and the pair measures 5.22:1. */
body.aside-enabled .aside .menu-link.active .menu-title,
body.aside-enabled .aside .menu-link.active .menu-icon i {
    color: #0f7a34 !important;
}

/* ---------------------------------------------------------
   ADMIN PALETTE CONTRAST CORRECTION — the five deferred findings
   ---------------------------------------------------------

   ROOT CAUSE. The Metronic `-light-*` variants and several gray tokens were never
   contrast-checked against the FSN palette, and `--bs-primary-light` stayed the
   Metronic green #DFFFEA when `--bs-primary` became the FSN red #E11D2E. The theme
   also defines `--bs-success` as the blue #1B84FF.

   NO CENTRAL TOKEN IS CHANGED. Editing `--bs-primary-light`, `--bs-success`, or
   `--bs-gray-400` would alter every surface in all three roles at once, including
   Client and Contractor pages that were never measured. Each finding is corrected
   with a narrow, admin-scoped rule instead.

   SCOPE. Every rule below is prefixed `body.aside-enabled .wrapper`. `aside-enabled`
   is set only by resources/views/base/admin.blade.php, and `.wrapper` is the admin
   content column, which also contains the admin header. Client and Contractor pages
   match neither, so they cannot regress. The focus ring is scoped separately to
   `body.fsn-app-shell` and is untouched here.

   NO NEW COLOUR IS INTRODUCED. Each replacement reuses a value already used
   elsewhere in this file:
     #7A5200  the badge-light-warning text colour
     #0F7A34  the active pagination page and the active sidebar item
     #0A58CA  the badge-light-success text colour
     --bs-gray-700  the muted-text colour

   Only the DEFAULT state of each control is corrected. Hover, active, checked, and
   disabled states stay vendor-controlled, so the existing interaction design is
   preserved. */

/* F1. `btn-light-warning` ("Deactivate" on Clients): #F6C000 on #FFF8DD = 1.58:1.

   This selector outranks the vendor `:hover` rule, so it governs BOTH states. On
   hover the vendor swaps the background to the solid #F6C000, which is far darker
   than the #FFF8DD rest state, so the text colour has to clear 4.5:1 on both. The
   badge value #7A5200 clears the rest state at 6.50:1 but only reaches 4.11:1 on
   the hover background, so this control needs the darker #664600. */
body.fsn-app-shell .wrapper .btn.btn-light-warning {
    color: #664600;
}

/* F1a. `btn-light-primary`: #E11D2E on #DFFFEA = 4.44:1, measured in a browser at
   390 px on the Contractor quotes listing. It is the SAME pair the badge rule
   above corrects, on the same tint, and it was missed there. 43 usages across 29
   views, so this is not one screen.

   🔴 ONE COLOUR CANNOT SERVE BOTH STATES, AND THAT IS MEASURED, NOT ASSUMED.
   As F1 records, the shell selector outranks the vendor's `:hover` rule, so a
   bare `color` here would govern the hover state too — and the vendor swaps the
   background to the SOLID #E11D2E on hover. Seven candidates were measured
   against both grounds and none clears 4.5:1 on both:

       value      rest #DFFFEA    hover #E11D2E
       #b30f1c       6.54 OK          1.47 FAIL
       #0f7a34       5.09 OK          1.15 FAIL
       #664600       8.04 OK          1.81 FAIL
       #1a1a1a      16.27 OK          3.66 FAIL
       #ffffff       1.07 FAIL        4.75 OK

   So the two states are given the two values that pass, and the vendor's own
   state selectors are mirrored so nothing is left to the cascade. #b30f1c is the
   value the badge rule already uses for this tint; #ffffff is the vendor's own
   `--bs-primary-inverse`, restated here only because the base rule above would
   otherwise outrank it. No new colour is introduced. */
body.fsn-app-shell .wrapper .btn.btn-light-primary {
    color: #b30f1c;
}

body.fsn-app-shell .wrapper .btn.btn-light-primary:focus:not(.btn-active),
body.fsn-app-shell .wrapper .btn.btn-light-primary:hover:not(.btn-active),
body.fsn-app-shell .wrapper .show > .btn.btn-light-primary {
    color: #ffffff;
}

/* F2. `text-gray-400` (the em-dash "no value" placeholder): #C4CADA on white =
   1.64:1. The dash carries meaning — it states that a cell has no value — so it is
   treated as text and takes the same muted colour as the rest of the admin. */
body.fsn-app-shell .wrapper .text-gray-400 {
    color: var(--bs-gray-700) !important;
}

/* F3. Avatar initial in the header: `.text-primary` resolves to the Metronic green
   #17C653, not the FSN red, and sits on `.bg-light-primary` #DFFFEA = 2.12:1. Only
   the letter darkens; the tinted circle is unchanged. */
body.fsn-app-shell .wrapper .symbol-label.text-primary {
    color: #0f7a34 !important;
}

/* F4. Status tab row (Work orders, Quotes, Invoices). Both states measured 2.27:1:
   the inactive tab is #17C653 on white, and the active tab is white on #17C653.
   The inactive text and the active background move to the same dark green, so the
   selected tab still reads as selected.

   🔴 SCOPED TO `.nav-pills` ON 2026-09-02. IT USED TO MATCH EVERY `.nav-link`.

   Reported by Haris: the Work Order tab colours had changed on both roles.
   Reproduced in the browser on `/client/projects/{meta}`. The selector was
   `body.fsn-app-shell .wrapper .nav-link`, which matched EVERY nav link under the
   application shell, not the pill row this rule was written for. Measured on the
   Work Order tab row:

       inactive tab   #0f7a34 green text          (this rule)
       ACTIVE tab     #e11d2e red text on a
                      #0f7a34 green background     (this rule's background,
                                                    the theme's red text)

   Red on green measures 1.15:1. The tab a user is looking at was the least
   readable thing on the page.

   FSN uses exactly two nav-link families under the shell, and they are already
   distinguishable in the markup:

       `.nav-pills`      8 rows - the six admin index filters, the Client quote
                         lifecycle row, the notifications filter. THIS rule.
       `.nav-line-tabs`  5 rows - both Work Order tab rows, contractor
                         invitations, the profile shell, the public profile.
                         Underline tabs; they take the theme's own styling and
                         the FSN red active state, and must not be repainted.

   `:not(.nav-pills)` is not used: the discriminator names what this rule IS for
   rather than what it is not, so a third family added later is unstyled by this
   rule rather than silently captured by it. */
body.fsn-app-shell .wrapper .nav-pills .nav-link {
    color: #0f7a34;
}

body.fsn-app-shell .wrapper .nav-pills .nav-link.active {
    background-color: #0f7a34;
    color: #ffffff;
}

/* F5. `text-success` ("Active" status text): the theme maps success to the blue
   #1B84FF, which measured 3.63:1 on white. The darker blue keeps the same hue. */
body.fsn-app-shell .wrapper .text-success {
    color: #0a58ca !important;
}

/* F7. `text-danger`: the theme's #F8285A measured 3.86:1 on white, below the 4.5
   floor that governs it. It is TEXT at 13px/400 and 12.35px/400 — not large text —
   so SC 1.4.3 applies at 4.5 and SC 1.4.11's 3:1 does not, because 1.4.11 excludes
   text whatever its size.

   THREE SHAPES WERE MEASURED SEPARATELY, and the third is why this value and not a
   lighter one:
     status text        18 sites  13.00px/400  on #FFFFFF  3.86 -> 5.63
     validation message 90 sites  12.35px/400  on #FFFFFF  3.86 -> 5.63
     dropdown Delete    16 sites  12.35px/400  on #F0F0F0  3.39 -> 4.94

   The open menu's #F0F0F0 ground is darker than the card, so it is the binding
   constraint. #D9214E clears white at 4.91 and FAILS the menu at 4.31; #C81E46
   clears all three. The menu link is also the element BL-ADMIN-03 dismissed as a
   closed dropdown — a closed dropdown opens, and the reading was real.

   The hue is preserved, measured rather than asserted: 345.6deg -> 345.9deg, a 0.3deg
   drift. Saturation falls 93.7% -> 73.9% and lightness 56.5% -> 45.1%. It is the same
   red, darker — it still reads as danger and has not drifted toward neutral. */
/* 🔴 THE SECOND SELECTOR IS THE ADMIN AUTH SCREENS, ADDED BY BL-CLOSE-02.

   The first selector requires a `.wrapper` ancestor, which `admin.layouts.app`
   provides. The three admin AUTH screens render through `auth.shared.layout`
   instead, which has NO `.wrapper` — so the correction did not reach them and their
   five validation messages were still painting #F8285A on white at 3.86:1.
   MEASURED in the browser on the sign-in and forgot-password screens, not inferred.

   Why not simply drop `.wrapper` from the first selector: `body.fsn-app-shell` is
   the DEFAULT body class in base/app.blade.php, so an unscoped rule would reach
   every non-landing page in the application, including the Client and Contractor
   areas. Those are a different palette with their own findings.

   Why not scope to the auth layout: it is SHARED — three admin components and three
   Client/Contractor components render through it. `fsn-admin-auth` is a class the
   three ADMIN auth views carry and the others do not.

   🔴 THE CLIENT AND CONTRACTOR AUTH SCREENS HAVE THE IDENTICAL DEFECT and are
   deliberately NOT corrected here. Recorded at boundary-register.md 8.85. */
body.fsn-app-shell .wrapper .text-danger,
body.fsn-app-shell .fsn-admin-auth .text-danger {
    color: #c81e46 !important;
}


