﻿/* ============================================================
   OBRS — National Investment Board of Sierra Leone
   Design System  |  2025 refresh
   ============================================================ */

/* ── Inter, self-hosted (not Google Fonts — the live app never fetches a package/library over
   the internet; see wwwroot/lib/inter/inter.css for provenance) ─────────────────────────────── */
@import url('../lib/inter/inter.css');

/* ── Design tokens ─────────────────────────────────────────── */
:root {
    /* Brand colours — Sierra Leone's flag: blue is primary, green secondary, white the surfaces;
       orange is the accent (the dot in the OBRS wordmark).

       Measured, not picked by eye.  White text on --obrs-primary is 4.97:1 and on
       --obrs-primary-dark 7.10:1, both past the 4.5:1 AA floor.  A LIGHTER blue is not: #1A82D0
       is 4.08:1, so anything carrying white text runs primary -> dark and never towards a lighter
       tint (there is deliberately no "-mid").  The flag green is 2.72:1 against white either way
       round — fills, stripes and icons beside text only, never text; where green must be READ use
       --obrs-secondary-text (5.02:1).  Keep -rgb in step with its hex: the tints below use it. */
    --obrs-primary:         #0072c6;
    --obrs-primary-rgb:     0, 114, 198;
    --obrs-primary-dark:    #005a9e;
    --obrs-primary-light:   #e6f1fa;
    --obrs-primary-glow:    rgba(var(--obrs-primary-rgb), .18);

    --obrs-secondary:       #1eb53a;
    --obrs-secondary-rgb:   30, 181, 58;
    --obrs-secondary-text:  #15803d;
    --obrs-secondary-light: #e8f7ec;

    --obrs-accent:          #f58220;
    --obrs-accent-light:    #fff1e6;
    /* The orange darkened for a fill carrying white (4.54:1) — the bright accent is 2.59:1. */
    --obrs-accent-dark:     #c2560b;

    /* The one brand gradient panel.  Every full-bleed brand surface uses this token — the portal
       welcome banner, the landing page's NIB strip and its closing CTA — so they cannot drift
       apart into three slightly different blues.  primary -> DARK for the contrast reason above:
       white holds 4.97:1 at its lightest point and improves across the panel. */
    --obrs-brand-gradient:  linear-gradient(135deg, var(--obrs-primary) 0%, var(--obrs-primary-dark) 100%);

    /* Bootstrap's own "primary" follows the brand, so text-primary / bg-primary / border-primary
       and links are the same blue as .btn-primary.  Until 2026-09-25 they were Bootstrap's default
       blue beside a green brand — two primaries on one page.  Bootstrap's SECONDARY is deliberately
       left grey: text-secondary is the muted-text colour on well over a hundred elements. */
    --bs-primary:               var(--obrs-primary);
    --bs-primary-rgb:           var(--obrs-primary-rgb);
    --bs-primary-text-emphasis: #003a66;
    --bs-primary-bg-subtle:     var(--obrs-primary-light);
    --bs-primary-border-subtle: #99c7e8;
    --bs-link-color:            var(--obrs-primary);
    --bs-link-color-rgb:        var(--obrs-primary-rgb);
    --bs-link-hover-color:      var(--obrs-primary-dark);
    --bs-link-hover-color-rgb:  0, 90, 158;

    /* Admin sidebar */
    --obrs-sidebar-bg:      #0f1e2e;
    --obrs-sidebar-border:  rgba(255,255,255,.06);
    --obrs-sidebar-hover:   rgba(255,255,255,.06);
    --obrs-sidebar-active:  rgba(var(--obrs-primary-rgb), .25);
    --obrs-sidebar-text:    #8fa3b8;
    --obrs-sidebar-width:   268px;

    /* Monospace for identifiers, figures and "technical" labels.  System fonts only — nothing is
       downloaded, so this holds on workstations with no internet route (CLAUDE.md, self-hosted
       libraries rule). */
    --obrs-font-mono: ui-monospace, "Cascadia Code", "SF Mono", SFMono-Regular, Consolas,
                      "Liberation Mono", Menlo, monospace;

    /* Spacing */
    --obrs-topbar-height:   60px;
    --obrs-bottom-nav-h:    64px;

    /* Shadows */
    --shadow-xs:   0 1px 2px rgba(0,0,0,.05);
    --shadow-sm:   0 1px 4px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
    --shadow-md:   0 4px 12px rgba(0,0,0,.08), 0 8px 24px rgba(0,0,0,.04);
    --shadow-lg:   0 8px 24px rgba(0,0,0,.10), 0 20px 48px rgba(0,0,0,.06);
    --shadow-primary: 0 4px 16px rgba(var(--obrs-primary-rgb), .28);

    /* Radius */
    --radius-sm:   8px;
    --radius-md:   12px;
    --radius-lg:   16px;
    --radius-xl:   20px;
    --radius-pill: 50rem;

    /* Transitions */
    --ease-out: cubic-bezier(0.4, 0, 0.2, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --transition: 0.2s var(--ease-out);
}

/* ── Base ───────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

/* Full-height layout — header · content · footer, no orphan gap.
   html must have height:100% so that body's min-height:100% resolves
   to the viewport, giving <main> a definite height for its flex
   children (e.g. .obrs-auth-bg) to stretch against.               */
html {
    height:          100%;
    scroll-behavior: smooth;
}

body {
    min-height:  100%;          /* resolves to 100vh via html's height   */
    display:     flex;
    flex-direction: column;
    font-family: 'Inter', system-ui, -apple-system, sans-serif;
    font-size:   0.9375rem;
    line-height: 1.6;
    color:       #1a1f2e;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* <main> fills all remaining space between navbar and footer */
main { flex: 1 0 auto; display: flex; flex-direction: column; }

/* Footer never shrinks below its natural height */
footer { flex-shrink: 0; }

a { color: var(--obrs-primary); transition: color var(--transition); }
a:hover { color: var(--obrs-primary-dark); }

h1,h2,h3,h4,h5,h6 {
    font-weight: 700;
    letter-spacing: 0;
    line-height: 1.25;
}
h1, h2, h3 { letter-spacing: -0.02em; }

/* Smooth page-in fade.
 * IMPORTANT: do NOT animate `transform` on <main>. Any non-identity transform
 * (including translateY(0)) creates a stacking context, which traps Bootstrap
 * modals inside <main> beneath the body-level .modal-backdrop (z-index 1050).
 * Stick to opacity-only animation so <main> never owns a stacking context. */
main { animation: fadeIn 0.3s var(--ease-out); }
@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Belt-and-braces: ensure overlays always sit above any layout element.
 * Bootstrap defaults are dropdown 1000 / backdrop 1050 / modal 1055; our
 * sticky navbars sit at 900–1030, so we bump the overlays comfortably above
 * the highest navbar to prevent accidental clipping when a future change
 * adds another sticky/positioned bar. */
.dropdown-menu  { z-index: 1075 !important; }
.modal-backdrop { z-index: 1080 !important; }
.modal          { z-index: 1090 !important; }

/* ── Emoji-capable font for dropdowns ─────────────────────────────────────
 * Some labels carry Unicode regional-indicator pairs that compose into
 * country flags (e.g. "🇸🇱 Sierra Leone" in the nationality picker).  We
 * prefix the option's font-family with the major OS emoji families so the
 * glyph renders as a flag rather than the fallback "SL" letter pair.
 *
 *   Windows 10+ :  Segoe UI Emoji
 *   macOS       :  Apple Color Emoji
 *   Linux       :  Noto Color Emoji   (if installed)
 *
 * If none of these are present the system falls back to the regional-
 * indicator letters — semantically correct, just less pretty.  Applied to
 * both the bootstrap-select menu and the underlying native <select>.
 *
 * The text font comes FIRST and the emoji families after it (user, 2026-11-03).  The emoji fonts carry their own
 * digits, "+", "#" and "*" (the keycap bases), so listed first they drew every number in a dropdown in the emoji
 * font: on an iPhone "+232" read "+ 2 3 2", spread out and off the line.  The text font has no flag glyphs, so a
 * flag still falls through to the emoji font.                                                                    */
.bootstrap-select .dropdown-menu li a span.text,
.bootstrap-select > .dropdown-toggle .filter-option-inner-inner,
select option {
    font-family: 'Inter', system-ui, -apple-system, sans-serif,
                 "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji";
}

/* ── Scrollbar (webkit) ─────────────────────────────────────── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(0,0,0,.15); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,.25); }

/* ── Buttons ──────────────────────────────────────────────────
 * Default = modern rounded rectangle (radius-md = 12px).  Use .btn-pill
 * explicitly when you want a true pill-shape (e.g. hero CTAs).  The old
 * default was --radius-pill which made every icon-only button render as a
 * circle — looked broken in btn-group rows of action icons. */
.btn {
    font-weight:    600;
    font-size:      0.875rem;
    border-radius:  var(--radius-md);
    transition:     all var(--transition);
    letter-spacing: 0;
}
.btn-pill { border-radius: var(--radius-pill); }   /* opt-in pill shape */
.btn:active { transform: scale(0.97); }

.btn-primary {
    background:   linear-gradient(135deg, var(--obrs-primary) 0%, var(--obrs-primary-dark) 100%);
    border:       none;
    box-shadow:   var(--shadow-primary);
    color:        #fff;
}
.btn-primary:hover, .btn-primary:focus {
    background:   linear-gradient(135deg, var(--obrs-primary) 0%, var(--obrs-primary-dark) 100%);
    box-shadow:   0 6px 20px rgba(var(--obrs-primary-rgb), .38);
    transform:    translateY(-1px);
    color:        #fff;
}

/* Bootstrap hard-codes its default blue per component rather than reading --bs-primary, so these
   would otherwise stay #0d6efd beside the brand blue: the pressed/disabled button, active
   pagination, pills, dropdown and list-group items, progress bars. */
.btn-primary {
    --bs-btn-active-bg:          var(--obrs-primary-dark);
    --bs-btn-active-border-color: var(--obrs-primary-dark);
    --bs-btn-disabled-bg:        var(--obrs-primary);
    --bs-btn-disabled-border-color: var(--obrs-primary);
    --bs-btn-focus-shadow-rgb:   var(--obrs-primary-rgb);
}
.btn-outline-primary {
    --bs-btn-color:              var(--obrs-primary);
    --bs-btn-border-color:       var(--obrs-primary);
    --bs-btn-hover-bg:           var(--obrs-primary);
    --bs-btn-hover-border-color: var(--obrs-primary);
    --bs-btn-active-bg:          var(--obrs-primary-dark);
    --bs-btn-active-border-color: var(--obrs-primary-dark);
    --bs-btn-focus-shadow-rgb:   var(--obrs-primary-rgb);
}
.nav-pills   { --bs-nav-pills-link-active-bg: var(--obrs-primary); }
/* A page bar wraps rather than running off the page (ScanBatch once drew one button per page).
 * Lists use the bounded _Pager partial; this is the safety net for any bar drawn another way. */
.pagination  {
    flex-wrap: wrap;
    row-gap: .25rem;
    --bs-pagination-color:            var(--obrs-primary);
    --bs-pagination-hover-color:      var(--obrs-primary-dark);
    --bs-pagination-focus-color:      var(--obrs-primary-dark);
    --bs-pagination-focus-box-shadow: 0 0 0 .25rem var(--obrs-primary-glow);
    --bs-pagination-active-bg:        var(--obrs-primary);
    --bs-pagination-active-border-color: var(--obrs-primary);
}
.dropdown-menu { --bs-dropdown-link-active-bg: var(--obrs-primary); }
.list-group    { --bs-list-group-active-bg: var(--obrs-primary); --bs-list-group-active-border-color: var(--obrs-primary); }
.progress, .progress-stacked { --bs-progress-bar-bg: var(--obrs-primary); }

/* btn-sm sits inline with form-control-sm / form-select-sm — match their
 * border-radius (--radius-sm = 8px) so the row looks uniform.
 * Large buttons keep --radius-md; only sm-tier needs the tighter radius. */
.btn-sm  { padding: 0.25rem 0.9rem;  font-size: 0.8125rem; border-radius: var(--radius-sm); }
.btn-lg  { padding: 0.7rem 1.75rem; font-size: 1rem; }
/* A button in a row of full-size fields (e.g. "Send invitation" after Role and Message): as tall as the
   .form-control beside it — 0.9rem text at line-height 1.5, 0.55rem padding top and bottom, 1px borders. */
.btn-field-height { min-height: calc(0.9rem * 1.5 + 1.1rem + 2px); }

/* ── Control height normalisation ─────────────────────────────────────────
 * form-control-sm / form-select-sm / btn-sm / bootstrap-select trigger must
 * all be the SAME height so filter-panel rows are flush.
 *
 * Root cause: our btn-sm originally used padding-y 0.3rem (larger than the
 * Bootstrap default 0.25rem), and bootstrap-select wraps the trigger text in
 * nested <div class="filter-option…"> elements that can inflate or deflate
 * the button height by 1-3 px across browsers.  <input type="date"> adds
 * native calendar-icon chrome that varies similarly.
 *
 * Fix: pin every sm-tier control to --ctrl-sm-h = Bootstrap's own
 * $input-height-sm formula so there is exactly one authoritative height.   */
:root { --ctrl-sm-h: calc(1.8125rem + 2px); }   /* 1.5×0.875rem + 2×0.25rem + 2×1px = 31px */

/* Pin sm-tier inputs to the token height.
 * File inputs are excluded — their native browser chrome sets its own
 * internal height and cannot be overridden safely via height alone.
 *
 * form-select-sm uses min-height (not height) so native <select> elements
 * can render their selected text without being clipped on high-DPI or
 * larger-font-size OS settings.  form-control-sm (text inputs) keeps the
 * exact height because single-line inputs don't need vertical flex room.  */
.form-control-sm:not([type="file"]) {
    height:     var(--ctrl-sm-h);
    min-height: unset;   /* Bootstrap uses min-height; pin to exact height instead */
}
.form-select-sm {
    min-height: var(--ctrl-sm-h);
    height:     auto;    /* allow native select to grow if text would be clipped */
}

/* Textareas grow naturally but start at a usable minimum height.
 * The height pin must not apply to <textarea> — they are multi-line.  */
textarea.form-control    { height: auto !important; min-height: 80px; }
textarea.form-control-sm { height: auto !important; min-height: 64px; }

/* File inputs: reset to comfortable padding so "Choose file" text isn't clipped. */
input[type="file"].form-control    { padding: 0.375rem 0.75rem; line-height: 1.4; }
input[type="file"].form-control-sm { padding: 0.25rem  0.5rem;  line-height: 1.4; height: auto; }

/* All .btn-sm — use flex so icons and text are always vertically centred.
 * Bootstrap's default display:inline-block can leave small vertical offsets
 * when a fixed height is set and the font-size changes.                   */
.btn-sm {
    height:      var(--ctrl-sm-h);
    display:     inline-flex;
    align-items: center;
}

/* bootstrap-select trigger: nested .filter-option divs inflate height;
 * inline-flex + center alignment keeps text vertically centred.       */
.bootstrap-select > .dropdown-toggle.btn-sm {
    height:      var(--ctrl-sm-h) !important;
    display:     inline-flex !important;
    align-items: center      !important;
}
.bootstrap-select > .dropdown-toggle.btn-sm .filter-option {
    /* block is fine — the parent's flex centering handles vertical position */
    min-height: unset;
}

/* ── btn-group fixes ──────────────────────────────────────────
 * Bootstrap's stock .btn-group selectors only target direct .btn children.
 * Our admin tables wrap the toggle button in <form class="d-inline"> for CSRF;
 * those rules below extend the same first/middle/last radius logic to forms
 * nested inside a btn-group, so the row no longer looks like ▢ ◯ ▢. */
.btn-group > form {
    display: inline-flex;     /* keeps form button flush with siblings */
    margin: 0;
}
.btn-group > .btn,
.btn-group > form > .btn {
    border-radius: 0;
}
.btn-group > :first-child .btn,
.btn-group > .btn:first-child {
    border-top-left-radius:    var(--radius-md);
    border-bottom-left-radius: var(--radius-md);
}
.btn-group > :last-child .btn,
.btn-group > .btn:last-child {
    border-top-right-radius:    var(--radius-md);
    border-bottom-right-radius: var(--radius-md);
}

/* ── Soft action buttons ──────────────────────────────────────
 * Replacement for Bootstrap outline buttons.  Subtle tinted background +
 * coloured icon/text that flips to a filled, accessible state on hover.
 * Designed for dense admin tables (Edit / Toggle / Delete row actions) where
 * full solid buttons would be visually heavy. */
.btn-soft-secondary {
    background:   #f3f4f6;
    border:       1px solid transparent;
    color:        #4b5563;
}
/* btn-soft-secondary hosts neutral row-action icons that frequently carry their
 * OWN state colour (e.g. <i class="bi-toggle-on text-success">).  Filling the
 * background with dark grey on hover would clash with that green/red/etc.
 * icon — so the hover is a SUBTLE bg darken, not a dramatic fill. */
.btn-soft-secondary:hover, .btn-soft-secondary:focus {
    background:   #e5e7eb;
    border-color: #d1d5db;
    color:        #1f2937;
    transform:    translateY(-1px);
}

.btn-soft-primary {
    background:   var(--obrs-primary-light);
    border:       1px solid transparent;
    color:        var(--obrs-primary-dark);
}
/* Soft accent buttons (primary, success, danger, warning) are deliberately bold
 * on hover — they signal a meaningful action where strong feedback is wanted. */
.btn-soft-primary:hover, .btn-soft-primary:focus {
    background:   var(--obrs-primary);
    border-color: var(--obrs-primary);
    color:        #fff;
    transform:    translateY(-1px);
}

.btn-soft-success {
    background:   #ecfdf5;
    border:       1px solid transparent;
    color:        #047857;
}
.btn-soft-success:hover, .btn-soft-success:focus {
    background:   #047857;
    border-color: #047857;
    color:        #fff;
    transform:    translateY(-1px);
}

.btn-soft-danger {
    background:   #fef2f2;
    border:       1px solid transparent;
    color:        #b91c1c;
}
.btn-soft-danger:hover, .btn-soft-danger:focus {
    background:   #b91c1c;
    border-color: #b91c1c;
    color:        #fff;
    transform:    translateY(-1px);
}

.btn-soft-warning {
    background:   #fffbeb;
    border:       1px solid transparent;
    color:        #b45309;
}
.btn-soft-warning:hover, .btn-soft-warning:focus {
    background:   #b45309;
    border-color: #b45309;
    color:        #fff;
    transform:    translateY(-1px);
}

/* Force any Bootstrap `text-*` utility on a descendant icon to inherit the
 * parent's hover colour.  Without this, an `<i class="text-success">` inside
 * a soft button keeps its green colour on hover and clashes with the (now
 * filled) hover background of btn-soft-danger / -primary / -success / -warning.
 * Bootstrap applies text-* with !important, so this rule must too. */
[class*="btn-soft-"]:hover [class*="text-"],
[class*="btn-soft-"]:focus [class*="text-"] {
    color: inherit !important;
}

/* Belt-and-braces: explicit hover contrast for any remaining outline buttons.
 * If a view still uses btn-outline-secondary, ensure the icon flips to white
 * on hover — never let an icon vanish into a dark hover background. */
.btn-outline-secondary:hover,
.btn-outline-secondary:focus,
.btn-outline-danger:hover,
.btn-outline-danger:focus {
    color: #fff;
}
.btn-outline-secondary:hover [class*="text-"],
.btn-outline-secondary:focus [class*="text-"],
.btn-outline-danger:hover [class*="text-"],
.btn-outline-danger:focus [class*="text-"] {
    color: inherit !important;
}

/* ── Cards ──────────────────────────────────────────────────── */
.card {
    border:        1px solid rgba(0,0,0,.07);
    border-radius: var(--radius-lg);
    box-shadow:    var(--shadow-sm);
    transition:    box-shadow var(--transition), transform var(--transition);
    overflow:      hidden;
    background:    #fff;
}

.card-header {
    background:    #fafbfc;
    border-bottom: 1px solid rgba(0,0,0,.06);
    font-weight:   600;
    font-size:     0.875rem;
    padding:       0.875rem 1.25rem;
}

.card-body { padding: 1.25rem; }

/* Lift-on-hover variant */
.card-lift {
    cursor: pointer;
}
.card-lift:hover {
    transform:  translateY(-3px);
    box-shadow: var(--shadow-lg);
}

/* ── Tables ─────────────────────────────────────────────────── */
.table {
    font-size:    0.875rem;
    border-color: rgba(0,0,0,.06);
}
.table thead th {
    font-size:      0.75rem;
    font-weight:    600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color:          #6b7280;
    background:     #fafbfc;
    border-bottom:  1px solid rgba(0,0,0,.08) !important;
    white-space:    nowrap;
    padding:        0.65rem 1rem;
}
.table tbody td { padding: 0.75rem 1rem; vertical-align: middle; }
.table-hover tbody tr { transition: background var(--transition); }
.table-hover tbody tr:hover { background: rgba(var(--obrs-primary-rgb), .04) !important; }

/* ── Badges ─────────────────────────────────────────────────── */
.badge { font-weight: 600; letter-spacing: 0.02em; border-radius: var(--radius-sm); }
.badge-active    { background-color: #dcfce7; color: #166534; }
.badge-dormant   { background-color: #fef9c3; color: #854d0e; }
.badge-woundup   { background-color: #fee2e2; color: #991b1b; }
.badge-struckoff { background-color: #f3f4f6; color: #374151; }
.badge-pending   { background-color: #dbeafe; color: #1e40af; }

/* ── Forms ──────────────────────────────────────────────────── */
.form-control, .form-select {
    border-radius: var(--radius-sm);
    border-color:  #d1d5db;
    font-size:     0.9rem;
    padding:       0.55rem 0.875rem;
    /* background-color, never the `background` shorthand: the shorthand also clears Bootstrap's arrow image,
       and a plain dropdown then looks like a text box (the invitation's NIN / Passport choice did). */
    background-color: #fff;
    transition:    border-color var(--transition), box-shadow var(--transition);
}
/* Room for the arrow on the right, as Bootstrap lays it out. */
.form-select { padding-right: 2.25rem; }
.form-control:focus, .form-select:focus {
    border-color: var(--obrs-primary);
    box-shadow:   0 0 0 3px var(--obrs-primary-glow);
    background-color: #fff;
}
.form-control::placeholder { color: #b0b7c3; }
.form-label {
    font-weight:  600;
    font-size:    0.855rem;
    color:        #374151;
    margin-bottom: .4rem;
    letter-spacing: 0;
}
.form-text  { font-size: 0.8rem; color: #9ca3af; margin-top: .35rem; line-height: 1.5; }
.form-check-label { font-size: 0.875rem; }

/* Check / radio inputs */
.form-check-input:checked {
    background-color: var(--obrs-primary);
    border-color:     var(--obrs-primary);
}
.form-check-input:focus {
    border-color: var(--obrs-primary);
    box-shadow:   0 0 0 3px var(--obrs-primary-glow);
}

/* ── NIB brand ──────────────────────────────────────────────── */
.obrs-brand {
    display:     flex;
    align-items: center;
    gap:         0.5rem;
    font-weight: 800;
    font-size:   1.15rem;
    /* Near-black wordmark with the orange accent dot.  On a dark surface (the mobile menu) the
       wordmark turns white below — black on navy would vanish — and the dot stays orange. */
    color:       #111827 !important;
    text-decoration: none;
    letter-spacing: -0.03em;
}
/* The NIB logo (white background, so it sits straight on the white headers; on a dark surface —
   the admin sidebar, the mobile menu — it gets a white tile so its blue lettering stays readable). */
.obrs-brand-logo {
    height:      40px;
    width:       auto;
    flex-shrink: 0;
}
.obrs-brand-logo-tile {
    height:        36px;
    padding:       3px 4px;
    background:    #fff;
    border-radius: 8px;
}
.obrs-brand-name {
    font-size:      0.85rem;
    font-weight:    700;
    letter-spacing: 0;
    line-height:    1.15;
}
.obrs-brand-name .obrs-brand-gov { margin-top: 2px; }
/* On the narrowest phones the logo, the name and the menu button do not fit one row; the logo
   carries the brand there and the menu button stays beside it (CD-616). */
@media (max-width: 359.98px) {
    .navbar-public .obrs-brand-name { display: none; }
}
.obrs-brand-gov {
    font-size:   0.6rem;
    font-weight: 400;
    color:       #6b7280;   /* 4.83:1 on white; the lighter grey was 2.54:1 (CD-616) */
    line-height: 1;
    letter-spacing: 0.03em;
    display:     block;
}

/* ── Gov stripe ─────────────────────────────────────────────── */
/* Sierra Leone's tricolour, in flag order.  Hard stops, not a blend — blended green and blue make
   teal, which is no flag.  The white third sits on a white header on purpose: it reads as the
   flag's white band. */
.obrs-gov-stripe {
    height:     4px;
    background: linear-gradient(90deg,
                    var(--obrs-secondary) 0 33.34%,
                    #fff 33.34% 66.67%,
                    var(--obrs-primary) 66.67% 100%);
}

/* ── Avatar ─────────────────────────────────────────────────── */
.obrs-avatar {
    width:         36px;
    height:        36px;
    border-radius: 50%;
    background:    linear-gradient(135deg, var(--obrs-primary), var(--obrs-primary-dark));
    color:         #fff;
    display:       inline-flex;
    align-items:   center;
    justify-content: center;
    font-size:     0.8rem;
    font-weight:   700;
    flex-shrink:   0;
    letter-spacing: 0;
}

/* ── Portal notices ─────────────────────────────────────────── */
/* The count beside a nav link and on the bell.  Semantic red, white text (4.5:1); no tracking on small text. */
.obrs-count {
    display:        inline-block;
    min-width:      1.25rem;
    height:         1.25rem;
    padding:        0 .35rem;
    margin-left:    .35rem;
    border-radius:  999px;
    background:     var(--bs-danger);
    color:          #fff;
    font-size:      .6875rem;
    font-weight:    700;
    line-height:    1.25rem;
    text-align:     center;
    letter-spacing: 0;
    vertical-align: middle;
}
.obrs-count[hidden] { display: none; }

.obrs-bell { position: relative; color: var(--bs-body-color); }
.obrs-bell .obrs-count {
    position:    absolute;
    top:         0;
    right:       -.1rem;
    margin-left: 0;
}
.obrs-bell-menu { width: min(24rem, 92vw); max-height: 70vh; overflow-y: auto; }

/* ── Page header ────────────────────────────────────────────── */
.obrs-page-header {
    display:         flex;
    justify-content: space-between;
    align-items:     flex-start;
    margin-bottom:   1.75rem;
    gap:             1rem;
    flex-wrap:       wrap;
}
/* A page's title row on a phone (user, 2026-11-03): buttons beside a title go under it when they do not fit, and a row
   of buttons wraps, instead of running off the right edge (Counter service ran 169px past it in every browser).  A row
   that fits is unchanged. */
@media (max-width: 767.98px) {
    main .d-flex.justify-content-between.align-items-start,
    main .d-flex.justify-content-between.align-items-center { flex-wrap: wrap; row-gap: .5rem; }
    main .d-flex.justify-content-between > .d-flex.gap-2 { flex-wrap: wrap; }
}
.obrs-page-header h1,
.obrs-page-header h4 { margin: 0; }
.obrs-page-header p  { color: #6b7280; margin: .25rem 0 0; font-size: .875rem; }

/* ── Stat card ──────────────────────────────────────────────── */
.obrs-stat-card {
    padding:       1.25rem;
    border-radius: var(--radius-lg);
    border:        1px solid rgba(0,0,0,.07);
    box-shadow:    var(--shadow-sm);
    background:    #fff;
    transition:    all var(--transition);
}
.obrs-stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.obrs-stat-card .stat-icon {
    width:         44px;
    height:        44px;
    border-radius: var(--radius-sm);
    background:    var(--obrs-primary-light);
    color:         var(--obrs-primary);
    display:       flex;
    align-items:   center;
    justify-content: center;
    font-size:     1.25rem;
}
.obrs-stat-card .stat-value { font-size: 1.75rem; font-weight: 800; letter-spacing: -0.03em; }
.obrs-stat-card .stat-label { font-size: 0.8rem; font-weight: 500; color: #6b7280; }

/* ── Form helpers ───────────────────────────────────────────── */
.form-section-title {
    font-size:      0.7rem;
    font-weight:    700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color:          #9ca3af;
    margin-bottom:  0.75rem;
    padding-bottom: 0.5rem;
    border-bottom:  1px solid #f3f4f6;
}

/* ── Alerts ─────────────────────────────────────────────────── */
.alert {
    border:        none;
    border-radius: var(--radius-md);
    font-size:     0.875rem;
    font-weight:   500;
}
.alert-success { background: #dcfce7; color: #166534; }
.alert-danger  { background: #fee2e2; color: #991b1b; }
.alert-warning { background: #fef9c3; color: #854d0e; }
.alert-info    { background: #dbeafe; color: #1e40af; }

/* ── Dropdown ───────────────────────────────────────────────── */
.dropdown-menu {
    border:        1px solid rgba(0,0,0,.08);
    border-radius: var(--radius-md);
    box-shadow:    var(--shadow-lg);
    padding:       0.5rem;
    font-size:     0.875rem;
    /* No transform-based animation — `transform: translateY(0)` in the final
     * keyframe creates a stacking context AND makes tall menus appear to
     * "slide" (the user reads this as a glitch on opening, especially with 4+
     * items where the slide is more visually obvious).  Plain opacity-only
     * fade or no animation at all is safer. */
    animation:     dropFade 0.12s var(--ease-out);
}
@keyframes dropFade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.dropdown-item {
    border-radius: var(--radius-sm);
    padding:       0.45rem 0.75rem;
    font-weight:   500;
    transition:    background var(--transition);
}
.dropdown-item:hover { background: var(--obrs-primary-light); color: var(--obrs-primary); }
.dropdown-item.text-danger:hover { background: #fee2e2; }
.dropdown-divider { border-color: rgba(0,0,0,.06); margin: 0.35rem 0; }

/* ═══════════════════════════════════════════════════════════════
   PUBLIC LAYOUT
═══════════════════════════════════════════════════════════════ */

.navbar-public {
    /* No backdrop-filter — it creates a stacking context that traps Bootstrap
     * dropdown menus (Popper.js positions absolute, which is constrained to the
     * stacking context and produces a flicker on open).  Use a near-opaque
     * background instead so the bar still reads as distinct from the page. */
    background:        rgba(255,255,255,.98);
    border-bottom:     1px solid rgba(0,0,0,.07);
    box-shadow:        var(--shadow-xs);
    position:          sticky;
    top:               0;
    z-index:           1030;
    min-height:        var(--obrs-topbar-height);
    transition:        background var(--transition), box-shadow var(--transition);
}
.navbar-public.scrolled {
    background:  rgba(255,255,255,.98);
    box-shadow:  var(--shadow-sm);
}
.navbar-public .nav-link {
    color:         #374151;
    font-weight:   500;
    font-size:     0.875rem;
    padding:       0.4rem 0.75rem;
    border-radius: var(--radius-pill);
    transition:    all var(--transition);
}
.navbar-public .nav-link:hover { background: var(--obrs-primary-light); color: var(--obrs-primary); }
.navbar-public .nav-link.active { color: var(--obrs-primary); font-weight: 600; }

/* Mobile menu toggle (public) */
.obrs-nav-toggle {
    width:         36px;
    height:        36px;
    border:        none;
    background:    transparent;
    cursor:        pointer;
    border-radius: var(--radius-sm);
    display:       flex;
    flex-direction: column;
    align-items:   center;
    justify-content: center;
    gap:           5px;
    padding:       0;
    transition:    background var(--transition);
}
.obrs-nav-toggle:hover { background: var(--obrs-primary-light); }
.obrs-nav-toggle span {
    display:       block;
    height:        2px;
    background:    #374151;
    border-radius: 2px;
    transition:    all 0.3s var(--ease-out);
}
.obrs-nav-toggle span:nth-child(1) { width: 22px; }
.obrs-nav-toggle span:nth-child(2) { width: 16px; }
.obrs-nav-toggle span:nth-child(3) { width: 22px; }
.obrs-nav-toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); width: 22px; }
.obrs-nav-toggle.open span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.obrs-nav-toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); width: 22px; }

/* Full-screen mobile menu (public) */
.obrs-mobile-menu {
    position:   fixed;
    inset:      0;
    background: linear-gradient(160deg, var(--obrs-primary-dark) 0%, #00304f 100%);
    z-index:    1040;
    display:    flex;
    flex-direction: column;
    padding:    1.5rem 1.75rem;
    transform:  translateY(-100%);
    transition: transform 0.42s cubic-bezier(0.4, 0, 0.2, 1);
    overflow:   hidden;
}
.obrs-mobile-menu.open { transform: translateY(0); }
.obrs-mobile-menu::before {
    content:       '';
    position:      absolute;
    top:           -80px;
    right:         -80px;
    width:         300px;
    height:        300px;
    border-radius: 50%;
    background:    rgba(200,149,42,.12);
    pointer-events: none;
}
.obrs-mobile-menu-header {
    display:         flex;
    justify-content: space-between;
    align-items:     center;
    margin-bottom:   2.5rem;
}
.obrs-mobile-menu-close {
    width:         40px;
    height:        40px;
    border-radius: 50%;
    border:        1.5px solid rgba(255,255,255,.2);
    background:    rgba(255,255,255,.08);
    color:         #fff;
    cursor:        pointer;
    display:       flex;
    align-items:   center;
    justify-content: center;
    font-size:     1.25rem;
    line-height:   1;
    transition:    background var(--transition);
}
.obrs-mobile-menu-close:hover { background: rgba(255,255,255,.18); }
.obrs-mobile-menu nav a {
    display:        block;
    color:          rgba(255,255,255,.85);
    text-decoration: none;
    font-size:      1.6rem;
    font-weight:    700;
    letter-spacing: -0.02em;
    padding:        0.6rem 0;
    border-bottom:  1px solid rgba(255,255,255,.08);
    transition:     color var(--transition), padding-left var(--transition);
}
.obrs-mobile-menu nav a:last-child { border-bottom: none; }
.obrs-mobile-menu nav a:hover {
    color:        #fff;
    padding-left: 0.5rem;
}
.obrs-mobile-menu nav a.active { color: #b8ddf9; }
.obrs-mobile-menu-foot {
    margin-top:  auto;
    display:     flex;
    gap:         0.75rem;
    padding-top: 2rem;
}
.obrs-mobile-menu-foot .btn {
    flex:          1;
    font-size:     0.9rem;
    padding:       0.65rem;
    border-radius: var(--radius-md);
}

/* Brand inside the mobile overlay — override the green !important */
.obrs-mobile-menu .obrs-brand         { color: #fff !important; }
.obrs-mobile-menu .obrs-brand-gov     { color: rgba(255,255,255,.5) !important; }

/* Public hero */
.obrs-hero {
    background: linear-gradient(145deg, var(--obrs-primary) 0%, var(--obrs-primary-dark) 100%);
    color:      #fff;
    padding:    5rem 0;
    position:   relative;
    overflow:   hidden;
}
.obrs-hero::after {
    content:       '';
    position:      absolute;
    bottom:        -60px;
    right:         -60px;
    width:         400px;
    height:        400px;
    border-radius: 50%;
    background:    rgba(200,149,42,.1);
    pointer-events: none;
}
.obrs-hero h1 { font-weight: 800; letter-spacing: -0.03em; }
.obrs-hero .lead { opacity: 0.88; font-weight: 400; }

/* Public footer */
/* Slim, unobtrusive footer.  Same class on all three layouts; the public layout
 * provides extra inline links via the markup, the others just show copyright. */
.obrs-footer {
    background:  #f9fafb;
    border-top:  1px solid #e5e7eb;
    padding:     0.75rem 0;
    font-size:   0.75rem;
    color:       #6b7280;
    margin-top:  auto;
    line-height: 1.5;
}
.obrs-footer a {
    color:           #6b7280;
    text-decoration: none;
    transition:      color .15s;
}
.obrs-footer a:hover { color: var(--obrs-primary-dark); }
.obrs-footer .obrs-footer-sep {
    margin: 0 .5rem;
    color:  #d1d5db;
}

/* ═══════════════════════════════════════════════════════════════
   APPLICANT LAYOUT
═══════════════════════════════════════════════════════════════ */

.obrs-appbar {
    /* No backdrop-filter — see .navbar-public note. backdrop-filter creates a
     * stacking context that traps Bootstrap dropdown menus (Popper miscomputes
     * coordinates → visible flicker when opening the user menu). */
    background:     rgba(255,255,255,.98);
    border-bottom:  1px solid rgba(0,0,0,.07);
    height:         var(--obrs-topbar-height);
    position:       sticky;
    top:            0;
    z-index:        1020;
    box-shadow:     var(--shadow-xs);
    display:        flex;
    align-items:    center;
    padding:        0 1.25rem;
    gap:            1rem;
}
/* min-width:0 is required for a flex child to shrink below its content's natural width — without
   it, .obrs-appbar-links below cannot scroll internally; the flex row just keeps growing and pushes
   the whole page into horizontal scroll instead. */
.obrs-appbar-nav-wrap { min-width: 0; }
/* The citizen nav (Portal, Start a service, My Businesses, Help & support, Name Search, Name
   Reservation, My Requests, My Invoices, My Payments, My Certified Copies, ...) has grown past what
   fits in one row on common laptop widths.  Scroll it internally rather than letting it force the
   whole page wider than the viewport — the account menu on the right must stay reachable. */
.obrs-appbar-links {
    overflow-x:  auto;
    flex-wrap:   nowrap;
    flex-shrink: 1;
    scrollbar-width: thin;
}
/* Both of these are load-bearing for the scroll above to ever happen.  Without them a nav
   item is still free to shrink to its min-content width — which for a text run is its
   LONGEST WORD — so the labels silently break onto a second line ("My / Businesses") inside
   a 60px bar, the row always "fits", scrollWidth never exceeds clientWidth, and the
   overflow-x:auto is dead code.  Keep the items at their natural width and let the
   container scroll, which was the point. */
.obrs-appbar-links > .nav-item { flex: 0 0 auto; }
.obrs-appbar .nav-link         { white-space: nowrap; }

/* No visible scrollbar under the nav — it read as a defect on every page.  Scrolling is now
   only the no-script fallback anyway: appbar-overflow.js moves whichever links do not fit into
   the "More" dropdown at the end of the row, so with script enabled the row never overflows and
   there is nothing to scroll.

   An earlier version faded the right edge with a mask-image to hint at the hidden items.  That
   is gone: a fade is not a control — a mouse user still had no way to reach what it hid — and
   with the overflow menu in place the only thing near that edge is the "More" toggle itself,
   which must not look half-transparent.  (A mask also creates a stacking context, which is
   exactly what CLAUDE.md warns traps Bootstrap dropdowns — and the More menu is one.)

   The scrollbar is suppressed only once appbar-overflow.js has announced itself by adding
   .js-overflow-managed.  That way the no-script path keeps a real, usable scrollbar instead of
   inheriting a hidden one — hiding it is only safe because the script guarantees there is
   nothing left to scroll to. */
.obrs-appbar-links.js-overflow-managed::-webkit-scrollbar { height: 0; }
.obrs-appbar-links.js-overflow-managed {
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.obrs-appbar .nav-link {
    color:         #374151;
    font-weight:   500;
    font-size:     0.875rem;
    padding:       .4rem .8rem;
    border-radius: var(--radius-pill);
    transition:    all var(--transition);
    display:       flex;
    align-items:   center;
    gap:           0.35rem;
}
.obrs-appbar .nav-link:hover { background: var(--obrs-primary-light); color: var(--obrs-primary); }
.obrs-appbar .nav-link.active {
    background: var(--obrs-primary-light);
    color:      var(--obrs-primary);
    font-weight: 600;
}

/* ── Applicant bottom nav (mobile only) ─────────────────────── */
.obrs-bottom-nav {
    position:      fixed;
    bottom:        0;
    left:          0;
    right:         0;
    height:        var(--obrs-bottom-nav-h);
    /* Solid white background only — the previous `backdrop-filter: blur(20px)`
       created a stacking context on this fixed mobile chrome.  Any Bootstrap
       overlay opened on mobile (modal, dropdown, popover) ended up trapped
       under the `.modal-backdrop`, repeating the bug already documented in
       CLAUDE.md's "Front-end / CSS — stacking context discipline".  Solid
       background looks fine and stays out of the way of overlays. */
    background:    rgba(255,255,255,.97);
    border-top:    1px solid rgba(0,0,0,.08);
    display:       none;
    z-index:       1030;
    box-shadow:    0 -4px 24px rgba(0,0,0,.07);
    padding:       0 0.25rem;
    padding-bottom: env(safe-area-inset-bottom); /* iPhone notch support */
}

@media (max-width: 767.98px) {
    .obrs-bottom-nav { display: flex; }

    /* Push content above the bottom nav */
    .obrs-appbar-body {
        padding-bottom: calc(var(--obrs-bottom-nav-h) + env(safe-area-inset-bottom));
    }
}

.obrs-bottom-nav-item {
    flex:              1;
    display:           flex;
    flex-direction:    column;
    align-items:       center;
    justify-content:   center;
    gap:               3px;
    font-size:         0.65rem;
    font-weight:       600;
    color:             #9ca3af;
    text-decoration:   none;
    position:          relative;
    padding:           0.4rem 0.25rem;
    transition:        color 0.2s var(--ease-out);
    letter-spacing:    0.01em;
    -webkit-tap-highlight-color: transparent;
}
.obrs-bottom-nav-item i {
    font-size:  1.3rem;
    transition: transform 0.25s var(--ease-spring);
    line-height: 1;
}
.obrs-bottom-nav-item.active {
    color: var(--obrs-primary);
}
.obrs-bottom-nav-item.active i {
    transform: translateY(-2px);
}
/* Indicator pill at top of active item */
.obrs-bottom-nav-item::after {
    content:       '';
    position:      absolute;
    top:           4px;
    left:          50%;
    transform:     translateX(-50%) scaleX(0);
    width:         28px;
    height:        3px;
    background:    var(--obrs-primary);
    border-radius: 0 0 4px 4px;
    transition:    transform 0.25s var(--ease-spring);
}
.obrs-bottom-nav-item.active::after { transform: translateX(-50%) scaleX(1); }

/* Touch ripple on bottom nav items */
.obrs-bottom-nav-item:active i { transform: scale(0.88); }

/* ═══════════════════════════════════════════════════════════════
   ADMIN LAYOUT
═══════════════════════════════════════════════════════════════ */

.obrs-admin-wrapper { display: flex; min-height: 100vh; background: #f4f6f9; }

/* Sidebar */
.obrs-sidebar {
    width:           var(--obrs-sidebar-width);
    min-width:       var(--obrs-sidebar-width);
    background:      var(--obrs-sidebar-bg);
    display:         flex;
    flex-direction:  column;
    position:        sticky;
    top:             0;
    height:          100vh;
    height:          100dvh;     /* the visible screen on phones and tablets; older browsers keep 100vh */
    overflow-y:      auto;
    overflow-x:      hidden;
    flex-shrink:     0;
    z-index:         1000;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,.1) transparent;
}

.obrs-sidebar-brand {
    padding:         1.1rem 1.25rem;
    border-bottom:   1px solid var(--obrs-sidebar-border);
    display:         flex;
    align-items:     center;
    gap:             0.6rem;
    color:           #fff !important;
    text-decoration: none;
    font-weight:     800;
    font-size:       1.05rem;
    letter-spacing:  -0.03em;
    flex-shrink:     0;
}
.obrs-sidebar-brand:hover { color: #fff !important; }
.obrs-sidebar-brand-sub {
    font-size:   0.6rem;
    font-weight: 400;
    color:       var(--obrs-sidebar-text);
    display:     block;
    line-height: 1.2;
    letter-spacing: 0.01em;
}

/* Sidebar group section header.  In its current form this is a clickable
 * <button> that collapses / expands the nav-links underneath.  Wrapped div
 * is .obrs-sidebar-group; the toggled body is .obrs-sidebar-group-body.
 * Plain styling matches the historical <div> appearance so admins coming
 * back to the redesigned sidebar see something familiar — the only visual
 * addition is the small chevron at the start of the label. */
.obrs-sidebar-section {
    display:        flex;
    align-items:    center;
    gap:            0.45rem;
    padding:        1.4rem 1.25rem 0.3rem;
    font-size:      0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color:          rgba(143,163,184,.5);
    font-weight:    700;
    background:     transparent;
    border:         0;
    width:          100%;
    text-align:     left;
    font-family:    inherit;
    cursor:         pointer;
    transition:     color 0.15s var(--ease-out);
}
.obrs-sidebar-section:hover { color: rgba(214,225,237,.85); }
.obrs-sidebar-section:focus-visible {
    outline: 2px solid rgba(96,165,250,.7);
    outline-offset: -2px;
    border-radius: var(--radius-sm);
}
.obrs-sidebar-section .chev {
    font-size: 0.7rem;
    transition: transform 0.18s var(--ease-out);
    opacity:    0.7;
}
.obrs-sidebar-group.is-collapsed > .obrs-sidebar-section .chev {
    transform: rotate(-90deg);
}
.obrs-sidebar-group-body {
    overflow: hidden;
    transition: max-height 0.22s var(--ease-out);
    max-height: 1500px;          /* generous upper bound for animated collapse */
}
.obrs-sidebar-group.is-collapsed > .obrs-sidebar-group-body {
    max-height: 0;
}

.obrs-sidebar .nav-link {
    color:         var(--obrs-sidebar-text);
    padding:       0.48rem 1.25rem;
    display:       flex;
    align-items:   center;
    gap:           0.7rem;
    border-radius: 0;
    font-size:     0.855rem;
    font-weight:   500;
    transition:    all 0.18s var(--ease-out);
    border-left:   2px solid transparent;
    margin:        0.05rem 0.5rem 0.05rem 0;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    letter-spacing: 0;
}
.obrs-sidebar .nav-link:hover {
    background: var(--obrs-sidebar-hover);
    color:      rgba(255,255,255,.9);
    padding-left: 1.45rem;
}
.obrs-sidebar .nav-link.active {
    background:  var(--obrs-sidebar-active);
    color:       #7cc4f5;
    border-left: 2px solid #7cc4f5;
    font-weight: 600;
}
.obrs-sidebar .nav-link i {
    width:     18px;
    font-size: 1rem;
    flex-shrink: 0;
    opacity:   0.85;
}
.obrs-sidebar .nav-link.active i { opacity: 1; }

.obrs-sidebar-footer {
    margin-top:  auto;
    padding:     1rem 1.25rem;
    border-top:  1px solid var(--obrs-sidebar-border);
    font-size:   0.8rem;
    color:       var(--obrs-sidebar-text);
    flex-shrink: 0;
}

/* Sidebar toggle (mobile) — slide from left, blur backdrop */
.obrs-sidebar-overlay {
    display:    none;
    position:   fixed;
    inset:      0;
    background: rgba(10,20,30,.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index:    1100;
    opacity:    0;
    transition: opacity 0.3s var(--ease-out);
}
.obrs-sidebar-overlay.show {
    display: block;
    opacity: 1;
}

.obrs-sidebar-close {
    display:         none;
    position:        absolute;
    top:             1rem;
    right:           -52px;
    width:           40px;
    height:          40px;
    border-radius:   50%;
    border:          1.5px solid rgba(255,255,255,.2);
    background:      rgba(255,255,255,.1);
    color:           #fff;
    cursor:          pointer;
    align-items:     center;
    justify-content: center;
    font-size:       1.1rem;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    transition:      background var(--transition);
}
.obrs-sidebar-close:hover { background: rgba(255,255,255,.22); }

@media (max-width: 991.98px) {
    .obrs-sidebar {
        position:   fixed;
        left:       -100vw;   /* slide off-screen; left avoids transform stacking context */
        top:        0;
        height:     100%;
        transition: left 0.35s cubic-bezier(0.4, 0, 0.2, 1);
        z-index:    1110;
        box-shadow: none;
    }
    .obrs-sidebar.show {
        left:       0;
        box-shadow: var(--shadow-lg);
    }
    .obrs-sidebar-close { display: flex; }
    .obrs-admin-main    { width: 100%; }
}

/* Admin main area */
.obrs-admin-main {
    flex:           1;
    min-width:      0;
    display:        flex;
    flex-direction: column;
}

.obrs-admin-topbar {
    height:        var(--obrs-topbar-height);
    /* No backdrop-filter — same reason as .navbar-public / .obrs-appbar.
     * The user dropdown lives in this bar; backdrop-filter would trap it. */
    background:    rgba(255,255,255,.98);
    border-bottom: 1px solid rgba(0,0,0,.07);
    display:       flex;
    align-items:   center;
    padding:       0 1.5rem;
    gap:           1rem;
    position:      sticky;
    top:           0;
    z-index:       900;
    box-shadow:    var(--shadow-xs);
}

.obrs-admin-content {
    padding: 1.75rem;
    flex:    1;
}

/* Breadcrumb */
.obrs-breadcrumb           { font-size: 0.8rem; }
.obrs-breadcrumb .breadcrumb { margin: 0; }
.obrs-breadcrumb .breadcrumb-item + .breadcrumb-item::before { color: #d1d5db; }
.obrs-breadcrumb .breadcrumb-item a { color: #6b7280; text-decoration: none; }
.obrs-breadcrumb .breadcrumb-item a:hover { color: var(--obrs-primary); }
.obrs-breadcrumb .breadcrumb-item.active { color: #374151; font-weight: 600; }

/* Hamburger button (admin) */
.obrs-admin-menu-btn {
    width:         38px;
    height:        38px;
    border:        1px solid rgba(0,0,0,.1);
    border-radius: var(--radius-sm);
    background:    transparent;
    cursor:        pointer;
    display:       flex;
    flex-direction: column;
    align-items:   center;
    justify-content: center;
    gap:           4px;
    transition:    background var(--transition);
    flex-shrink:   0;
}
.obrs-admin-menu-btn:hover { background: #f3f4f6; }
.obrs-admin-menu-btn span {
    display:       block;
    width:         18px;
    height:        1.5px;
    background:    #374151;
    border-radius: 2px;
    transition:    all 0.25s var(--ease-out);
}
.obrs-admin-menu-btn span:nth-child(2) { width: 13px; align-self: flex-start; margin-left: 2.5px; }

/* ═══════════════════════════════════════════════════════════════
   AUTH PAGES (login, forgot, reset, lockout…)
═══════════════════════════════════════════════════════════════ */

/* Subtle gradient background on auth/register screens */
.obrs-auth-bg {
    background: linear-gradient(150deg, #eef5fc 0%, #f8fafc 55%, #fff6ee 100%);
    flex: 1;                /* stretches to fill <main> — no bare white below */
    padding: 3rem 0 4rem;
}
@media (max-width: 575.98px) { .obrs-auth-bg { padding: 1.5rem 0 3rem; } }

/* Card wrapper used on login, forgot-password etc. */
.obrs-auth-card {
    background:    #fff;
    border:        1px solid rgba(0,0,0,.07);
    border-radius: var(--radius-xl);
    box-shadow:    var(--shadow-md);
    padding:       2.5rem 2.25rem;
}
@media (max-width: 575.98px) {
    .obrs-auth-card { padding: 1.75rem 1.25rem; border-radius: var(--radius-lg); }
}

/* Auth icon badge — standalone (top of card) or inline beside a title.
   No margin is set here; callers add mb-* when used standalone,
   and rely on the parent flex's align-items:center when used inline.  */
.obrs-auth-icon {
    width:           52px;
    height:          52px;
    border-radius:   var(--radius-lg);
    display:         inline-flex;
    align-items:     center;
    justify-content: center;
    font-size:       1.5rem;
    flex-shrink:     0;
    align-self:      center;
}
.obrs-auth-icon-primary { background: var(--obrs-primary-light); color: var(--obrs-primary); }
.obrs-auth-icon-accent  { background: var(--obrs-accent-light);  color: #b45309; }   /* 4.54:1 — the bright accent is 2.4:1 here */
.obrs-auth-icon-muted { background: #f3f4f6; color: #9ca3af; }

/* Divider with text */
.obrs-auth-divider {
    display:     flex;
    align-items: center;
    gap:         0.75rem;
    color:       #9ca3af;
    font-size:   0.78rem;
    margin:      1.25rem 0;
}
.obrs-auth-divider::before,
.obrs-auth-divider::after {
    content:    '';
    flex:       1;
    height:     1px;
    background: rgba(0,0,0,.07);
}

/* ═══════════════════════════════════════════════════════════════
   REGISTRATION STEP WIZARD
═══════════════════════════════════════════════════════════════ */

.reg-tracker {
    display:     flex;
    align-items: flex-start;
    margin:      0 auto 2rem;
    max-width:   360px;
}
.reg-track-step {
    display:        flex;
    flex-direction: column;
    align-items:    center;
    gap:            0.4rem;
    flex-shrink:    0;
}
.reg-track-dot {
    width:           36px;
    height:          36px;
    border-radius:   50%;
    border:          2px solid #e5e7eb;
    background:      #fff;
    display:         flex;
    align-items:     center;
    justify-content: center;
    font-size:       0.82rem;
    font-weight:     700;
    color:           #9ca3af;
    transition:      all 0.3s var(--ease-out);
    position:        relative;
    z-index:         1;
}
.reg-track-step.active .reg-track-dot {
    background:   var(--obrs-primary);
    border-color: var(--obrs-primary);
    color:        #fff;
    box-shadow:   0 0 0 5px var(--obrs-primary-glow);
}
.reg-track-step.done .reg-track-dot {
    background:   var(--obrs-primary);
    border-color: var(--obrs-primary);
    color:        #fff;
}
.reg-track-label {
    font-size:      0.65rem;
    font-weight:    700;
    color:          #9ca3af;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space:    nowrap;
    transition:     color 0.3s;
}
.reg-track-step.active .reg-track-label,
.reg-track-step.done   .reg-track-label { color: var(--obrs-primary); }

.reg-track-connector {
    flex:       1;
    height:     2px;
    background: #e5e7eb;
    margin-top: 17px; /* vertically centre with dot */
    transition: background 0.4s var(--ease-out);
}
.reg-track-connector.done { background: var(--obrs-primary); }

/* Step panels */
.reg-step { display: none; }
.reg-step.active { display: block; animation: fadeIn 0.22s var(--ease-out) both; }

/* Step nav row */
.reg-nav {
    display:    flex;
    gap:        0.75rem;
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid rgba(0,0,0,.06);
}
.reg-nav .reg-btn-back { min-width: 110px; }
.reg-nav .reg-btn-next,
.reg-nav .reg-btn-submit { flex: 1; }

/* Summary card in step 3 */
.reg-summary-card {
    background:    #fafbfc;
    border:        1px solid rgba(0,0,0,.07);
    border-radius: var(--radius-md);
    padding:       1rem 1.1rem;
    margin-bottom: 1.25rem;
    font-size:     0.85rem;
}
.reg-summary-card .sum-label {
    font-size:      0.68rem;
    font-weight:    700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color:          #9ca3af;
    display:        block;
    margin-bottom:  0.15rem;
}

/* ═══════════════════════════════════════════════════════════════
   ACCOUNT MANAGE PAGES
═══════════════════════════════════════════════════════════════ */

.obrs-manage-nav {
    display:       flex;
    flex-wrap:     wrap;
    gap:           0.15rem;
    border-bottom: 2px solid rgba(0,0,0,.06);
    margin-bottom: 2rem;
}
.obrs-manage-nav-link {
    display:        flex;
    align-items:    center;
    gap:            0.4rem;
    padding:        0.6rem 1rem;
    font-size:      0.875rem;
    font-weight:    600;
    color:          #6b7280;
    text-decoration: none;
    border-bottom:  2px solid transparent;
    margin-bottom:  -2px;
    border-radius:  var(--radius-sm) var(--radius-sm) 0 0;
    transition:     all var(--transition);
    letter-spacing: 0;
}
.obrs-manage-nav-link:hover {
    color:      var(--obrs-primary);
    background: var(--obrs-primary-light);
}
.obrs-manage-nav-link.active {
    color:              var(--obrs-primary);
    border-bottom-color: var(--obrs-primary);
}
@media (max-width: 575.98px) {
    .obrs-manage-nav-link { font-size: .8rem; padding: .5rem .75rem; }
}

/* ═══════════════════════════════════════════════════════════════
   REGISTRATION FORM
═══════════════════════════════════════════════════════════════ */

/* Numbered step badge in card headers */
.obrs-step-badge {
    width:           28px;
    height:          28px;
    border-radius:   50%;
    background:      var(--obrs-primary);
    color:           #fff;
    font-size:       0.72rem;
    font-weight:     700;
    display:         inline-flex;
    align-items:     center;
    justify-content: center;
    flex-shrink:     0;
    letter-spacing:  0;
}

/* Big icon above the register form heading */
.obrs-step-icon-wrap {
    width:           56px;
    height:          56px;
    border-radius:   var(--radius-lg);
    background:      var(--obrs-primary-light);
    color:           var(--obrs-primary);
    font-size:       1.5rem;
    display:         inline-flex;
    align-items:     center;
    justify-content: center;
}

/* Registration section cards — slightly tighter than default */
.obrs-reg-card .card-header {
    background:    #fff;
    border-bottom: 2px solid rgba(0,0,0,.06);
    font-weight:   700;
    font-size:     0.875rem;
    padding:       0.875rem 1.25rem;
    letter-spacing: 0;
}
.obrs-reg-card .card-body {
    padding: 1.375rem 1.25rem;
}

/* Form field spacing — a bit more breathing room */
.obrs-reg-card .mb-3 { margin-bottom: 1.1rem !important; }
.obrs-reg-card .mb-4 { margin-bottom: 1.4rem !important; }

/* ═══════════════════════════════════════════════════════════════
   ID CAPTURE WIDGET
═══════════════════════════════════════════════════════════════ */

.id-capture-widget {
    border:        1px solid rgba(0,0,0,.08);
    border-radius: var(--radius-lg);
    overflow:      hidden;
    background:    #fff;
    box-shadow:    var(--shadow-xs);
}
.id-capture-camera-wrap {
    position:   relative;
    background: #0f1e2e;
    width:      100%;
    line-height: 0;
}
#id-capture-video {
    width:      100%;
    display:    block;
    max-height: 340px;
    object-fit: cover;
}
.id-capture-svg-overlay {
    position:       absolute;
    inset:          0;
    width:          100%;
    height:         100%;
    pointer-events: none;
}
.id-capture-controls {
    background: rgba(10,20,30,.8);
    padding:    0.875rem 1rem;
    display:    flex;
    align-items: center;
    justify-content: center;
    gap:        1rem;
}
#id-capture-btn {
    width:         60px;
    height:        60px;
    border-radius: 50%;
    border:        3px solid rgba(255,255,255,.8);
    background:    rgba(255,255,255,.15);
    cursor:        pointer;
    transition:    all 0.15s var(--ease-out);
    padding:       4px;
    display:       flex;
    align-items:   center;
    justify-content: center;
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}
#id-capture-btn:active { transform: scale(0.9); }
#id-capture-btn-inner {
    width:         46px;
    height:        46px;
    border-radius: 50%;
    background:    linear-gradient(135deg, var(--obrs-primary), var(--obrs-primary-dark));
    box-shadow:    var(--shadow-primary);
}

.id-capture-dropzone {
    border:        2px dashed #d1d5db;
    border-radius: var(--radius-lg);
    padding:       2rem 1.5rem 1.75rem;
    text-align:    center;
    cursor:        pointer;
    transition:    all var(--transition);
    background:    #fafbfc;
    display:       flex;
    flex-direction: column;
    align-items:   center;
    gap:           0.35rem;
    min-height:    168px;
    justify-content: center;
}
.id-capture-dropzone:hover,
.id-capture-dropzone.dragover {
    border-color: var(--obrs-primary);
    background:   var(--obrs-primary-light);
}
.id-capture-dropzone:hover .id-capture-dropzone-icon,
.id-capture-dropzone.dragover .id-capture-dropzone-icon {
    background: var(--obrs-primary);
    color:      #fff;
}
.id-capture-dropzone-icon {
    width:         52px;
    height:        52px;
    border-radius: var(--radius-md);
    background:    #f0f2f5;
    color:         #9ca3af;
    font-size:     1.5rem;
    display:       flex;
    align-items:   center;
    justify-content: center;
    margin-bottom: 0.25rem;
    transition:    all var(--transition);
}
.id-capture-dropzone-title {
    font-weight: 600;
    font-size:   0.9rem;
    color:       #374151;
}
.id-capture-dropzone-sub {
    font-size: 0.82rem;
    color:     #6b7280;
}
.id-capture-dropzone-hint {
    font-size:   0.75rem;
    color:       #9ca3af;
    margin-top:  0.25rem;
    letter-spacing: 0.02em;
}

.id-capture-preview {
    display:       none;
    border-radius: var(--radius-lg);
    overflow:      hidden;
    border:        1px solid rgba(0,0,0,.08);
}
.id-capture-preview img {
    width:      100%;
    display:    block;
    max-height: 260px;
    object-fit: contain;
    background: #f9fafb;
}
.id-capture-preview-bar {
    background: rgba(10,20,30,.72);
    padding:    0.6rem 0.75rem;
    display:    flex;
    justify-content: center;
    gap:        0.5rem;
}

/* PDF info block (shown instead of img for PDF uploads) */
.id-capture-pdf-info {
    padding:     1.5rem;
    display:     flex;
    align-items: center;
    gap:         1rem;
    background:  #fafbfc;
}

/* Photo accepted / captured success state */
.id-capture-accepted {
    display:       flex;
    align-items:   center;
    gap:           0.875rem;
    padding:       0.875rem 1rem;
    border:        1px solid #bbf7d0;
    border-radius: var(--radius-md);
    background:    #f0fdf4;
}
.id-capture-accepted-icon {
    width:           36px;
    height:          36px;
    border-radius:   50%;
    background:      #22c55e;
    color:           #fff;
    display:         flex;
    align-items:     center;
    justify-content: center;
    font-size:       1.1rem;
    flex-shrink:     0;
}

/* ═══════════════════════════════════════════════════════════════
   UTILITIES
═══════════════════════════════════════════════════════════════ */


.obrs-access-denied {
    text-align: center;
    padding:    4rem 1rem;
    color:      #9ca3af;
}
.obrs-access-denied i { font-size: 3.5rem; color: #e5e7eb; }

/* ═══════════════════════════════════════════════════════════════
   ADMIN DASHBOARD TILES
   Extracted from Views/AdminDashboard/Index.cshtml inline <style>
   so the rules can be reused by future role-specific dashboards
   (officer, applicant) without copy-paste.
═══════════════════════════════════════════════════════════════ */

.admin-tile          { transition: transform .12s ease, box-shadow .12s ease; }
.admin-tile:hover    { transform: translateY(-2px); box-shadow: 0 .5rem 1rem rgba(0,0,0,.08) !important; }
.admin-tile-disabled { opacity: .55; cursor: not-allowed; }
.admin-tile-disabled:hover { transform: none; box-shadow: 0 .125rem .25rem rgba(0,0,0,.075) !important; }
/* Defence in depth: an [aria-disabled] tile must also be visually disabled
   even if the .admin-tile-disabled class is forgotten on a future copy. */
[aria-disabled="true"].admin-tile { opacity: .55; cursor: not-allowed; }
[aria-disabled="true"].admin-tile:hover { transform: none; box-shadow: 0 .125rem .25rem rgba(0,0,0,.075) !important; }

.admin-tile-icon {
    width: 44px; height: 44px; border-radius: 12px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.25rem; flex-shrink: 0;
}

.bg-purple-subtle { background-color: rgba(102, 16, 242, 0.12); }
.text-purple      { color: #6610f2; }

/* Responsive admin padding */
@media (max-width: 767.98px) {
    .obrs-admin-content { padding: 1.25rem 1rem; }
    .obrs-admin-topbar  { padding: 0 1rem; }
}


/* Tooltip skin removed — we use the browser's native tooltip via the
   `title` attribute on hover.  No CSS to maintain, no JS bindings to
   keep alive, no first-hover edge cases.  Tradeoff: appearance is
   browser-controlled (slight inconsistency across Chrome/Firefox/Safari)
   but reliability is total. */

/* ═══════════════════════════════════════════════════════════════
   BOOTSTRAP-SELECT — brand integration
   bootstrap-select renders the trigger as a real <button> + a
   .dropdown-menu, both anchored to the original <select>'s position
   within its parent .bootstrap-select wrapper.  Tweak the toggle so
   it visually matches our other form controls, override focus state
   to OBRS green, and tighten dropdown row spacing.
═══════════════════════════════════════════════════════════════ */

/* The wrapper sits where the original <select> was — keep it block
   and full-width so the inline form layouts (col-md-* grids) work
   without explicit width settings on the toggle. */
.bootstrap-select { width: 100% !important; }

/* The toggle button — make it look like .form-select rather than a
   primary button.  Border + bg + colour come from Bootstrap's btn-light
   already; we override radius + size + focus only.
   Use --radius-sm (8px) so the trigger matches adjacent form-control-sm /
   form-select-sm / btn-sm in filter-panel rows.                          */
.bootstrap-select > .btn.dropdown-toggle {
    background: #fff;
    border: 1px solid #d1d5db;
    color: var(--obrs-text, #1a1f2e);
    font-weight: 400;
    text-align: left;
    border-radius: var(--radius-sm);
    box-shadow: none;
}
/* Full-size trigger: the same padding, font and line height as .form-control / .form-select, so a searchable
   dropdown is exactly as tall as the text box beside it (it was 6px shorter).  The sm-context rules below win. */
.bootstrap-select > .btn.dropdown-toggle:not(.btn-sm) {
    padding: 0.55rem 0.875rem;
    font-size: 0.9rem;
    line-height: 1.5;
}
/* The chosen text stays inside its box in every browser (user, 2026-11-03): Chrome and Firefox clipped it, but Safari
   let the hidden part of a long choice widen the whole page, so an iPhone could scroll the form sideways.  On a phone
   the choice wraps instead of being cut ("Kono Community Development Foundation: NR-2026-990016, reserved until …"). */
.bootstrap-select > .dropdown-toggle .filter-option,
.bootstrap-select > .dropdown-toggle .filter-option-inner { overflow: hidden; min-width: 0; }
.bootstrap-select > .dropdown-toggle .filter-option-inner-inner { overflow: hidden; text-overflow: ellipsis; }
/* The native list behind the searchable one is 1px wide and invisible, but Safari still lays its chosen option's text out
   from there and widens the page by it (866px on a 390px iPhone).  Containing its painting keeps the text in.  It holds no
   menu or dialog, so the stacking context this makes traps nothing. */
.bootstrap-select > select { contain: paint; }
@media (max-width: 767.98px) {
    .bootstrap-select > .dropdown-toggle .filter-option-inner-inner {
        white-space: normal;
        overflow-wrap: anywhere;
        text-overflow: clip;
    }
}
.bootstrap-select > .btn.dropdown-toggle:hover {
    background: #fafbfc;
    border-color: #b6bcc4;
}
.bootstrap-select.show > .btn.dropdown-toggle,
.bootstrap-select > .btn.dropdown-toggle:focus {
    border-color: var(--obrs-primary) !important;
    box-shadow: 0 0 0 .15rem var(--obrs-primary-glow) !important;
    outline: 0 !important;
}
/* Sm-context */
.form-select-sm + .bootstrap-select > .btn.dropdown-toggle,
.bootstrap-select.form-control-sm > .btn.dropdown-toggle,
.bootstrap-select.form-select-sm > .btn.dropdown-toggle,
.bootstrap-select > .btn.dropdown-toggle.form-control-sm {
    min-height: calc(1.5em + .5rem + 2px);
    padding: .25rem .5rem;
    font-size: .875rem;
}

/* Dropdown — rounded card with soft shadow, scoped above sticky bars and
   below modals (z-index discipline per CLAUDE.md).
   The outer menu uses `overflow: hidden` only for the rounded corners; the
   inner scroll region sets its own overflow-y so the mouse-wheel scrolls
   inside the dropdown rather than bubbling to the page. */
.bootstrap-select .dropdown-menu {
    z-index: 1075;
    border: 1px solid rgba(15, 30, 46, 0.08);
    border-radius: 10px;
    box-shadow:
        0 1px 2px rgba(15, 30, 46, 0.06),
        0 8px 24px rgba(15, 30, 46, 0.10);
    padding: 0;
    overflow: hidden;             /* clip child rounded corners only */
    animation: bsSelectFade 120ms ease-out;
}
/* Inner scroll region — selectpicker uses `.inner` for the option list when
   there are many options.  Make it the ONLY scrolling element so wheel
   events stay captured here, not bubbling to the page. */
.bootstrap-select .dropdown-menu .inner {
    max-height: 280px !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
    overscroll-behavior: contain;  /* don't pass scroll to the page when we hit the bounds */
}
/* Custom scrollbar so the menu doesn't lose visual hierarchy. */
.bootstrap-select .dropdown-menu .inner::-webkit-scrollbar {
    width: 8px;
}
.bootstrap-select .dropdown-menu .inner::-webkit-scrollbar-thumb {
    background: rgba(15, 30, 46, 0.18);
    border-radius: 4px;
}
.bootstrap-select .dropdown-menu .inner::-webkit-scrollbar-thumb:hover {
    background: rgba(15, 30, 46, 0.28);
}

@keyframes bsSelectFade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Row spacing + brand-tinted hover/active states. */
.bootstrap-select .dropdown-menu li > a,
.bootstrap-select .dropdown-menu li > button {
    padding: .45rem .75rem;
    line-height: 1.35;
    color: var(--obrs-text, #1a1f2e);
    font-size: .875rem;
}
.bootstrap-select .dropdown-menu li > a:hover,
.bootstrap-select .dropdown-menu li > button:hover,
.bootstrap-select .dropdown-menu li > a:focus,
.bootstrap-select .dropdown-menu li > button:focus,
.bootstrap-select .dropdown-menu li.active > a,
.bootstrap-select .dropdown-menu li.active > button,
.bootstrap-select .dropdown-menu li > a.active {
    background-color: var(--obrs-primary-light) !important;
    color: var(--obrs-primary-dark) !important;
}

/* "Selected" state — leave a trace of the active option. */
.bootstrap-select .dropdown-menu li.selected > a,
.bootstrap-select .dropdown-menu li.selected > button {
    background-color: rgba(var(--obrs-primary-rgb), .06);
    color: var(--obrs-primary-dark);
    font-weight: 500;
}

/* Live-search input — sticky to top of the menu, sized like form-control-sm. */
.bootstrap-select .bs-searchbox {
    padding: .5rem .5rem .25rem;
    position: sticky;
    top: 0;
    background: #fff;
    z-index: 1;
    border-bottom: 1px solid rgba(15, 30, 46, 0.06);
}
.bootstrap-select .bs-searchbox input.form-control {
    border-radius: 8px;
    border-color: rgba(15, 30, 46, 0.12);
    font-size: .875rem;
    padding: .35rem .6rem;
    height: auto;
}
.bootstrap-select .bs-searchbox input.form-control:focus {
    border-color: var(--obrs-primary) !important;
    box-shadow: 0 0 0 .15rem var(--obrs-primary-glow) !important;
}

/* Multi-select tokens — pill chips on the toggle. */
.bootstrap-select .filter-option-inner-inner {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* "No results" message — clear styling. */
.bootstrap-select .no-results {
    padding: 10px 12px;
    color: var(--obrs-primary-dark);
    font-style: italic;
}

/* Bootstrap-select wrapper: stacking context owner so its dropdown stays
   above sibling form controls without bleeding into modals.  Width: 100%
   so the wrapper matches the column it sits in (mirrors the `width: '100%'`
   we pass to selectpicker JS). */
.bootstrap-select { width: 100% !important; position: relative; }

/* ──────────────────────────────────────────────────────────────────────────
   Card overflow override
   `.card { overflow: hidden }` is set globally above (line ~275) so card
   headers / images don't escape rounded corners.  But that same overflow
   clips a bootstrap-select dropdown that opens inside the card.  Use :has()
   to flip it back to visible only when a selectpicker is inside.
   Browser support: Chrome 105+, Safari 15.4+, Firefox 121+ — all green for
   government IT today.
   ────────────────────────────────────────────────────────────────────────── */
.card:has(.bootstrap-select),
.card:has(.bootstrap-select) > .card-body,
.card-body:has(.bootstrap-select) {
    overflow: visible;
}

/* ──────────────────────────────────────────────────────────────────────────
   Horizontal scroll for wide tables inside flex cards
   `.card` is `display:flex; flex-direction:column` with `overflow:hidden`.
   A `.table-responsive` placed inside is a flex item whose default
   `min-width:auto` refuses to shrink below its content width — so a table
   wider than the card (e.g. the PII access-policy matrix, one column per
   position) stretches the item to full width and the card clips the excess
   with NO scrollbar.  `min-width:0` lets the item shrink to the card and its
   own `overflow-x:auto` then provides the horizontal scrollbar.
   ────────────────────────────────────────────────────────────────────────── */
.card .table-responsive { min-width: 0; }

/* ── Legacy Data Entry sub-tabs ────────────────────────────────────────────
 * Used on Legacy Data Entry → Index, Scan Batches → Index, Search Digitised.
 * Keeps the three views feeling like one module instead of three sidebar entries.
 */
.legacy-subnav {
    gap: .25rem;
    border-bottom: 1px solid var(--bs-border-color);
    padding-bottom: .5rem;
}
.legacy-subnav .nav-link {
    color: var(--bs-secondary-color, #6c757d);
    background: transparent;
    border-radius: 999px;
    padding: .35rem .85rem;
    font-size: .85rem;
    font-weight: 500;
}
.legacy-subnav .nav-link:hover { color: var(--bs-primary); background: rgba(13,110,253,.05); }
.legacy-subnav .nav-link.active {
    background: rgba(13,110,253,.10);
    color: var(--bs-primary);
}

/* ── Scan-page thumbnails ────────────────────────────────────────────────
 * Tiny page tiles shown in the digitisation workspace + admin batch pages.
 */
.scan-pages-grid {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}
.scan-pages-grid:empty { display: none; }
.scan-page-thumb {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    width: 64px; height: 80px;
    border: 1px solid var(--bs-border-color);
    border-radius: 8px;
    background: #fff;
    color: var(--bs-secondary-color, #6c757d);
    text-decoration: none;
    transition: transform .08s ease, border-color .12s ease, color .12s ease;
    gap: .15rem;
    flex-shrink: 0;
}
.scan-page-thumb i { font-size: 1.3rem; }
.scan-page-thumb span { font-size: .68rem; }
.scan-page-thumb:hover {
    border-color: var(--bs-primary);
    color: var(--bs-primary);
    transform: translateY(-1px);
}

/* ── Toast (modern bottom-right notification used across the app) ────────
 *
 * Pinned bottom-right, slides up from below on appearance, fades + slides
 * down on dismissal.  Multiple toasts stack vertically with newest at the
 * bottom.  Each toast carries a coloured accent strip on the left so
 * success / error / info reads at a glance.
 *
 * Design language: 12px radius, soft drop-shadow, dark slate background
 * (kept constant — coloured fills make icons + text hard to read).  The
 * accent stripe + icon do the semantic signalling.
 */
.scan-toast-container {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 1080;
    display: flex;
    flex-direction: column-reverse;     /* newest at the bottom */
    gap: .55rem;
    pointer-events: none;
    max-width: min(380px, calc(100vw - 2.5rem));
}
.scan-toast {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: .65rem;
    background: rgba(15, 23, 42, .96);
    color: #f8fafc;
    padding: .75rem 1rem .75rem 1.1rem;
    border-radius: 12px;
    font-size: .85rem;
    line-height: 1.35;
    box-shadow:
        0 1px  2px rgba(15, 23, 42, .18),
        0 8px 28px rgba(15, 23, 42, .25);
    opacity: 0;
    transform: translateY(14px);
    transition:
        opacity .22s ease,
        transform .26s cubic-bezier(.16, 1, .3, 1);
    pointer-events: auto;
    overflow: hidden;
}
.scan-toast::before {
    /* Coloured accent strip on the left edge — 4px wide. */
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 4px;
    background: #64748b;                /* neutral default; overridden per variant */
}
.scan-toast.is-visible { opacity: 1; transform: translateY(0); }
.scan-toast.is-leaving { opacity: 0; transform: translateY(14px); }

.scan-toast-icon {
    flex-shrink: 0;
    width: 22px; height: 22px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: rgba(255, 255, 255, .10);
    color: inherit;
    font-size: .85rem;
    line-height: 1;
}
.scan-toast-text { flex: 1 1 auto; min-width: 0; word-wrap: break-word; }

/* Variants — accent strip + icon tint change; body stays dark slate. */
.scan-toast--ok::before  { background: #22c55e; }
.scan-toast--ok  .scan-toast-icon { color: #4ade80; background: rgba(34, 197, 94, .18); }
.scan-toast--err::before { background: #ef4444; }
.scan-toast--err .scan-toast-icon { color: #fca5a5; background: rgba(239, 68, 68, .18); }
.scan-toast--info::before { background: #38bdf8; }
.scan-toast--info .scan-toast-icon { color: #7dd3fc; background: rgba(56, 189, 248, .18); }

/* ── Card + table corner-matching ──────────────────────────────────────────
 * Bootstrap's .card has rounded corners but .table-responsive (a child wrapping
 * the table) doesn't clip to that radius — so any .card-header + .table combo
 * renders the card with rounded top + square bottom, creating a visual mismatch
 * (most visible on the Billing category in /SettingsAdmin and any other admin
 * list page).  Force the bottom corners to match by clipping the wrapper.
 */
.card > .table-responsive:last-child {
    border-bottom-left-radius:  var(--bs-card-border-radius);
    border-bottom-right-radius: var(--bs-card-border-radius);
    /* overflow-x:auto (NOT overflow:hidden) — `hidden` clipped wide tables with
       no scrollbar (e.g. the PII access-policy matrix, one column per position).
       overflow-x:auto still clips to the border-radius for the corner-rounding
       this rule exists for, while restoring horizontal scrolling. */
    overflow-x: auto;
}
.card > .table-responsive:last-child > table {
    margin-bottom: 0;          /* otherwise Bootstrap leaves a 1rem gap below */
}
.card > .table-responsive > table.table-bordered { border: 0; }

/* ── Pill-tab strip pattern ────────────────────────────────────────────────
 * Used on admin landing pages where many tile-cards or category-cards would
 * otherwise cluster into one long scroll (Settings, Admin Dashboard, etc.).
 * Reuses .legacy-subnav's visual language so we stay consistent.
 */
.admin-tabnav {
    gap: .25rem;
    border-bottom: 1px solid var(--bs-border-color);
    padding-bottom: .5rem;
    margin-bottom: 1.25rem;
    flex-wrap: wrap;
}
.admin-tabnav .nav-link {
    color: var(--bs-secondary-color, #6c757d);
    background: transparent;
    border-radius: 999px;
    padding: .4rem 1rem;
    font-size: .88rem;
    font-weight: 500;
    border: 0;
}
.admin-tabnav .nav-link i { margin-right: .35rem; }
.admin-tabnav .nav-link:hover { color: var(--bs-primary); background: rgba(13,110,253,.05); }
.admin-tabnav .nav-link.active {
    background: rgba(13,110,253,.10);
    color: var(--bs-primary);
}
.admin-tabnav .nav-link .badge {
    font-size: .68rem;
    font-weight: 500;
    margin-left: .3rem;
    background: rgba(0,0,0,.08);
    color: inherit;
}
.admin-tabnav .nav-link.active .badge {
    background: rgba(13,110,253,.18);
}

/* ── Document cards (business record + legacy data entry) ─────────────────
 * Compact tile-style alternative to the old four-column "File / Type /
 * Uploaded / Size" table.  Each card surfaces the document type prominently
 * (it's what the operator searches for), with the raw filename + size
 * relegated to a quieter secondary line.  Auto-fitting grid so the layout
 * gracefully reflows on narrower viewports.
 */
.docs-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
    gap: .75rem;
}
.docs-card {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    padding: .75rem .85rem;
    background: #fff;
    border: 1px solid var(--bs-border-color);
    border-radius: 10px;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.docs-card:hover {
    border-color: var(--bs-primary);
    box-shadow: 0 2px 12px rgba(13, 110, 253, .07);
}
.docs-card-icon {
    flex-shrink: 0;
    width: 38px; height: 38px;
    display: flex; align-items: center; justify-content: center;
    background: rgba(13, 110, 253, .08);
    color: var(--bs-primary);
    border-radius: 8px;
    font-size: 1.25rem;
}
.docs-card-body { flex: 1 1 auto; min-width: 0; }
.docs-card-type {
    font-size: .9rem;
    font-weight: 600;
    color: var(--bs-body-color);
    line-height: 1.25;
}
.docs-card-name {
    font-size: .76rem;
    color: var(--bs-secondary-color, #6c757d);
    margin-top: .15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.docs-card-meta {
    font-size: .72rem;
    color: var(--bs-secondary-color, #6c757d);
    margin-top: .2rem;
}
.docs-card-note {
    font-size: .76rem;
    color: var(--bs-body-color);
    margin-top: .25rem;
    line-height: 1.3;
}
.docs-card-actions {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    flex-shrink: 0;
}

/* ── Digitise 2-step indicator ──────────────────────────────────────────
   Used at the top of LegacyDataEntry Create + Digitise to make the
   "fill record details, then scan documents" flow obvious.  Bubble +
   label + connector pattern; current step is filled, prior steps show
   a check, future steps are muted. */
.digitise-steps {
    display: flex;
    align-items: center;
    gap: 0;
    flex-wrap: nowrap;
    overflow-x: auto;
    padding: 0.75rem 0;
}
.digitise-step {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
}
.digitise-step-bubble {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.85rem;
    transition: background 0.18s var(--ease-out), color 0.18s var(--ease-out);
    flex-shrink: 0;
}
.digitise-step-label {
    display: inline-flex;
    flex-direction: column;
    line-height: 1.15;
}
.digitise-step-eyebrow {
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--obrs-muted, #6c757d);
    font-weight: 600;
}
.digitise-step-text {
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--obrs-text, #1a2332);
}
.digitise-step-connector {
    flex: 1 1 auto;
    min-width: 24px;
    max-width: 64px;
    height: 2px;
    margin: 0 0.75rem;
    background: #e0e6ed;
    border-radius: 2px;
}

/* Current step — primary fill, bold label */
.digitise-step.is-current .digitise-step-bubble {
    background: var(--bs-primary, #0d6efd);
    color: #fff;
    box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.12);
}
.digitise-step.is-current .digitise-step-text {
    color: var(--bs-primary, #0d6efd);
}

/* Done step — success tint, check icon */
.digitise-step.is-done .digitise-step-bubble {
    background: #dff5e7;
    color: #1a8745;
}
.digitise-step.is-done .digitise-step-text {
    color: #1a8745;
}
.digitise-step.is-done ~ .digitise-step-connector,
.digitise-step.is-done + .digitise-step-connector {
    background: #b8e2c8;
}

/* Future step — muted */
.digitise-step.is-future .digitise-step-bubble {
    background: #f1f3f7;
    color: var(--obrs-muted, #6c757d);
    border: 1px solid #e0e6ed;
}
.digitise-step.is-future .digitise-step-text {
    color: var(--obrs-muted, #6c757d);
}

@media (max-width: 600px) {
    .digitise-step-eyebrow { display: none; }
    .digitise-step-text { font-size: 0.8rem; }
    .digitise-step-connector { max-width: 24px; margin: 0 0.4rem; }
}

/* ── Shared embedded PDF viewer (pdf-viewer.js) ────────────────────────
   Renders inside any host container — modal, EDMS preview pane, etc.
   The JS injects .obrs-pdfv-shell as the only direct child of the host. */
/* The document on the public verify page: every page one under another, each a sheet on a grey ground. */
.obrs-verify-document { background: #e9ecef; }
.obrs-verify-document [data-verify-pages] canvas {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 0 auto .5rem;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .18);
}
.obrs-verify-document [data-verify-pages] canvas:last-child { margin-bottom: 0; }
.obrs-pdfv-shell {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
    background: #2a2d31;
    color: #e8eaed;
    overflow: hidden;
}
.obrs-pdfv-toolbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .85rem;
    background: #1f2226;
    color: #e8eaed;
    font-size: .82rem;
    border-bottom: 1px solid #15171a;
}
.obrs-pdfv-title {
    font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    max-width: 50%;
}
.obrs-pdfv-warn {
    font-size: .7rem; color: #ffa726;
    white-space: nowrap;
}
.obrs-pdfv-spacer { flex: 1 1 auto; }
.obrs-pdfv-canvas {
    flex: 1 1 auto;
    overflow: auto;
    text-align: center;
    padding: 1rem;
    background: #2a2d31;
}
.obrs-pdfv-canvas canvas {
    box-shadow: 0 4px 14px rgba(0,0,0,.4);
    margin-bottom: .75rem;
    max-width: 100%;
    height: auto;
}
.obrs-pdfv-status {
    text-align: center;
    color: #9aa0a6;
    padding: 2rem 1rem;
    font-size: .85rem;
}
.obrs-pdfv-shell .btn-outline-light { border-color: rgba(255,255,255,.4); color: #e8eaed; }
.obrs-pdfv-shell .btn-outline-light:hover { background: rgba(255,255,255,.1); color: #fff; }

/* Modal host fills the full body — the dialog is already modal-fullscreen */
#pdfViewerModal .obrs-pdfv-host { height: 100%; }

/* ============================================================
   Citizen portal — dashboard + service catalogue
   Compact, categorised, mobile-first.  Cross-browser: flexbox +
   CSS grid with auto-fill (no container queries, no :has()).
   ============================================================ */

.portal-wrap { max-width: 1080px; }

/* ── Dashboard working area ──────────────────────────────────────
   The portal home is the one portal page that is SCANNED rather than read, and it was a single
   1080px column of full-width blocks: attention, requests, payments, activity, services, shortcuts,
   one under another.  On a desktop that left ~800px empty either side while the citizen scrolled
   past content that had room to sit beside itself.  The other three .portal-wrap pages (Service,
   Catalogue, RegisterBusiness) are reading and form pages where a narrow measure is correct, so the
   wider wrap is a modifier here rather than a change to .portal-wrap itself.

   Two working columns above 1200px: what the citizen must act on and what they came to check on the
   left, ambient detail on the right.  Below that the rail flows underneath in source order, which
   is why the markup puts the main column first. */
.portal-wrap-dash { max-width: 1320px; }

.portal-dash {
    display: grid;
    gap: 1rem;
    /* minmax(0, 1fr), never a bare 1fr — `1fr` is minmax(auto, 1fr) and `auto` resolves to
       min-content, which a long reference number or service name can push past the track.  That is
       the same rule that caused the 99px page-scroll in .portal-grid (CD-264). */
    grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 1200px) {
    .portal-dash { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); align-items: start; }
    /* One side empty would otherwise leave a 1.55fr column of white space next to a lone card. */
    .portal-dash-single { grid-template-columns: minmax(0, 1fr); }
}

/* ── Hero band (primary CTA) ─────────────────────────────────── */
.portal-hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--obrs-brand-gradient);
    color: #fff;
    box-shadow: var(--shadow-primary);
}
/* The welcome line is 14px, so it needs the full 4.5:1 and cannot afford dimming: at the
   `opacity:.9` it used to carry it measured 3.52:1 on the old lighter gradient, and 3.07:1 under
   the glow circle.  Full white on the current gradient is 5.38:1. */
.portal-hero p { opacity: 1 !important; }
.portal-hero > * { position: relative; z-index: 1; }
.portal-hero::after {                    /* decorative glow, pointer-safe */
    content: "";
    position: absolute;
    inset: auto -60px -80px auto;
    width: 240px; height: 240px;
    background: rgba(255,255,255,.10);
    border-radius: 50%;
    pointer-events: none;
    z-index: 0;
}
.portal-hero .btn-light { color: var(--obrs-primary-dark); font-weight: 600; }

/* ── Section label ───────────────────────────────────────────── */
.portal-eyebrow {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #6b7684;
}

/* The search box over a list of services (_ServiceSearchBox). */
.svc-search { max-width: 28rem; }

/* ── Category filter chips (horizontal scroll on mobile) ─────── */
.portal-chips {
    display: flex;
    flex-wrap: nowrap;
    gap: .5rem;
    overflow-x: auto;
    padding: .25rem 0 .5rem;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;                /* Firefox */
    scroll-snap-type: x proximity;
}
.portal-chips::-webkit-scrollbar { display: none; }  /* WebKit/Blink */

/* The rail above hides its scrollbar, which is only an honest trade where a swipe replaces it.
   On a pointer device there is nothing to drag and most mice have no horizontal wheel, so the
   category filters past the right edge would be unreachable — at 375px this rail is 312px wide
   holding 954px of chips, i.e. most of them.  Wrap instead of scrolling when the input is fine:
   a filter bar on two or three lines is conventional and hides nothing.  Touch keeps the swipe
   rail, where the cut-off chip at the edge is its own affordance.  Same rule as the appbar nav —
   see the "hiding a scrollbar is not the same as removing the need to scroll" note in CLAUDE.md. */
@media (pointer: fine) {
    .portal-chips {
        flex-wrap:       wrap;
        overflow-x:      visible;
        scroll-snap-type: none;
    }
}
.portal-chip {
    flex: 0 0 auto;
    scroll-snap-align: start;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .4rem .85rem;
    border: 1px solid #e2e7ee;
    border-radius: var(--radius-pill);
    background: #fff;
    color: #3a4453;
    font-size: .82rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.portal-chip:hover { border-color: var(--obrs-primary); color: var(--obrs-primary-dark); }
.portal-chip.is-active {
    background: var(--obrs-primary);
    border-color: var(--obrs-primary);
    color: #fff;
}
.portal-chip .portal-chip-count {
    font-size: .72rem;
    background: rgba(0,0,0,.06);
    border-radius: var(--radius-pill);
    padding: 0 .45rem;
    line-height: 1.4;
}
.portal-chip.is-active .portal-chip-count { background: rgba(255,255,255,.22); }

/* ── Service grid (compact horizontal cards) ─────────────────── */
.portal-grid {
    display: grid;
    /* min(280px, 100%) — NOT a bare 280px.  A fixed track minimum wider than the container
       cannot shrink, so the grid overflows the page on a narrow phone.  Wrapping it in min()
       lets the track collapse to the container when that is the smaller of the two. */
    grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
    gap: .75rem;
}
.portal-card {
    display: flex;
    /* Grid and flex items default to min-width:auto, which resolves to their MIN-CONTENT.
       Without this a long word in the card would set its smallest width and push the whole
       page sideways.  Setting 0 lets it fit its track; the text rules below wrap the rest. */
    min-width: 0;
    align-items: center;
    gap: .85rem;
    padding: .8rem .9rem;
    border: 1px solid #e9edf2;
    border-radius: var(--radius-md);
    background: #fff;
    text-decoration: none;
    color: inherit;
    transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.portal-card:hover {
    border-color: var(--obrs-primary);
    box-shadow: var(--shadow-sm);
    transform: translateY(-1px);
    color: inherit;
}
.portal-card:focus-visible {
    outline: 2px solid var(--obrs-primary);
    outline-offset: 2px;
}
.portal-card-icon {
    flex: 0 0 auto;
    width: 44px; height: 44px;
    border-radius: var(--radius-sm);
    background: var(--obrs-primary-light);
    color: var(--obrs-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem;
}
.portal-card-body { min-width: 0; flex: 1 1 auto; }
/* A service's name and description are always shown whole (user, 2026-10-28): they wrap onto as many
   lines as they need, never cut with "…".  A word too long for the card breaks rather than pushing
   the page sideways. */
.portal-card-title {
    font-weight: 600;
    font-size: .9rem;
    line-height: 1.3;
    margin: 0;
    overflow-wrap: anywhere;
}
.portal-card-desc {
    font-size: .8rem;
    color: #6b7684;
    line-height: 1.35;
    margin: .1rem 0 0;
    overflow-wrap: anywhere;
}
.portal-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
    margin-top: .35rem;
}
.portal-tag {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    font-size: .68rem;
    font-weight: 600;
    color: #5a6472;
    background: #f2f5f8;
    border-radius: var(--radius-pill);
    padding: .1rem .5rem;
    white-space: nowrap;
}
.portal-card-go {
    flex: 0 0 auto;
    color: #9aa4b1;
    font-size: 1.05rem;
    transition: transform var(--transition), color var(--transition);
}
.portal-card:hover .portal-card-go { color: var(--obrs-primary); transform: translateX(2px); }

/* Category section hidden by the chip filter */
.portal-cat.is-hidden { display: none; }

/* ── Small screens ───────────────────────────────────────────── */
@media (max-width: 575.98px) {
    /* minmax(0, 1fr), never a bare 1fr: `1fr` is shorthand for minmax(AUTO, 1fr), and that
       auto minimum is exactly the min-content leak this whole block exists to stop. */
    .portal-grid { grid-template-columns: minmax(0, 1fr); }
    /* Let the description wrap on a phone rather than truncate — there is vertical room, and
       a card subtitle cut mid-word reads worse than two short lines. */
    .portal-card-icon { width: 40px; height: 40px; font-size: 1.05rem; }
}

/* Service-detail "what happens next" — compact numbered stages */
.portal-stages { list-style: none; counter-reset: stage; padding: 0; margin: 0; }
.portal-stages li { position: relative; padding: .35rem 0 .35rem 2.1rem; }
.portal-stages li::before {
    counter-increment: stage;
    content: counter(stage);
    position: absolute; left: 0; top: .3rem;
    width: 1.5rem; height: 1.5rem;
    display: flex; align-items: center; justify-content: center;
    font-size: .72rem; font-weight: 700;
    color: var(--obrs-primary); background: var(--obrs-primary-light);
    border-radius: 50%;
}

/* ── Step configuration panel (RequestTypeAdmin/Detail expand rows) ───────────
   Each expanded row previously rendered as a bare full-width grey band with no
   visual tie to its step — with several rows expanded the panels were
   indistinguishable.  The panel now carries a green accent, an inset tint, and
   a caption naming its step. */
.step-config {
    background:  #f7faf8;
    border-left: 3px solid var(--obrs-primary);
    padding:     1rem 1.25rem 1.1rem;
}
.step-config-caption {
    font-size:      .72rem;
    font-weight:    700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color:          var(--obrs-primary-dark);
    margin-bottom:  .75rem;
}
.step-config .list-group-item { background: transparent; }

/* Flex-child truncation helper (Bootstrap has no .min-w-0) — lets a
   flex-grow select shrink instead of pushing its sibling button out. */
.min-w-0 { min-width: 0; }

/* Equal-height columns inside the step-config panel.  The three sections carry
   different content heights ("None." vs a populated list), which left the three
   add-forms at ragged vertical positions.  Each column is a flex column and the
   add-form (always the last child) is pinned to the bottom, so the three
   selects + buttons align on one baseline regardless of list length. */
.step-config-col { display: flex; flex-direction: column; }
.step-config-col > form:last-child { margin-top: auto; }

/* Subtle divider between the three sections on md+ (columns stack on mobile). */
@media (min-width: 768px) {
    .step-config-col + .step-config-col {
        border-left: 1px solid rgba(15, 30, 46, 0.07);
    }
}

/* ── Phone number with country code (user, 2026-09-30) ─────────────────────
   One joined control: the country picker and the number share a height and a border, like any
   input-group.  The picker is upgraded to a searchable dropdown, which wraps the <select> in its
   own box — Bootstrap's input-group joining only sees that box, and the site-wide rule makes it
   full width — so the joins, the width and the height are set here.  The picker's labels are long
   ("Sierra Leone (+232)"); it is capped so the number keeps the room. */
.obrs-phone-input { flex-wrap: nowrap; align-items: stretch; }
.obrs-phone-input > .obrs-phone-country,
.obrs-phone-input > .bootstrap-select {
    flex: 0 0 auto;
    width: auto !important;
    max-width: 48%;
    margin: 0;
}
.obrs-phone-input > .bootstrap-select > .btn.dropdown-toggle {
    height: 100%;
    display: flex;
    align-items: center;
    font-size: 0.9rem;
    border-color: #d1d5db;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.obrs-phone-input > .bootstrap-select .filter-option { display: flex; align-items: center; }
/* The chosen code ("+232") on one line, never spaced out or broken between its digits on a phone. */
.obrs-phone-input > .bootstrap-select > .dropdown-toggle .filter-option-inner-inner { white-space: nowrap; letter-spacing: 0; }
.obrs-phone-input > .bootstrap-select > .dropdown-toggle::after { margin-left: .6rem; }
/* The menu is wider than the button, so long country names read on one line. */
.obrs-phone-input > .bootstrap-select > .dropdown-menu { min-width: min(18rem, 85vw) !important; }
.obrs-phone-input > .form-control {
    min-width: 0;
    margin-left: -1px;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}
/* The focused half sits on top so its whole outline shows over the shared edge. */
/* The focus ring is raised over the neighbouring field on the BUTTON, never on the wrapper: a
   z-index on the wrapper would make it a stacking context and trap the open country list inside it
   (the overlay rule in CLAUDE.md; CD-616). */
.obrs-phone-input > .bootstrap-select.show > .dropdown-toggle,
.obrs-phone-input > .bootstrap-select:focus-within > .dropdown-toggle,
.obrs-phone-input > .form-control:focus { position: relative; z-index: 3; }

/* ── Sign in with email or phone: a no-script switch ──────────────────────
   Two radios drive which panel shows, by CSS alone — the page works without JavaScript. */
.obrs-signin-panel { display: none; }
#signin-by-email:checked ~ .obrs-signin-panels .obrs-signin-panel-email,
#signin-by-phone:checked ~ .obrs-signin-panels .obrs-signin-panel-phone { display: block; }
.obrs-signin-switch > .btn { width: calc(50% - .25rem); margin-bottom: 1rem; }

/* ── Sortable tables (table[data-sortable], wwwroot/js/table-sort.js) ──
   The heading's text becomes a button that keeps the heading's look; a heading that holds a link
   gets a small icon button beside it.  Right-aligned number columns keep their alignment. */
.obrs-sort-btn { all: unset; cursor: pointer; display: inline-flex; align-items: center; gap: .3rem;
                 font: inherit; color: inherit; border-radius: 4px; }
th.text-end .obrs-sort-btn { flex-direction: row-reverse; }
.obrs-sort-btn:focus-visible { outline: 2px solid var(--obrs-primary); outline-offset: 2px; }
.obrs-sort-btn-icon { margin-left: .25rem; }
.obrs-sort-icon { font-size: .7em; color: var(--bs-secondary-color); }
th[aria-sort="ascending"] .obrs-sort-icon,
th[aria-sort="descending"] .obrs-sort-icon { color: var(--obrs-primary); }

/* Staging server marker (_TestEnvironmentBanner) — a small fixed tab on every layout when
   App:Environment is "Staging".  A leaf element, never an ancestor of a modal or dropdown, so
   its fixed position and z-index cannot trap an overlay; it sits below the modal backdrop (1080).
   White on --obrs-accent-dark is 4.54:1.  No transforms or animation (stacking rules). */
.obrs-test-env-tab {
    position: fixed; left: 12px; bottom: 12px; z-index: 1030;
    max-width: calc(100vw - 24px);
    background: var(--obrs-accent-dark); color: #fff;
    border-radius: var(--radius-pill); padding: 6px 14px;
    font-size: .8rem; line-height: 1.35;
    box-shadow: var(--shadow-md);
}
.obrs-test-env-tab a { color: #fff; font-weight: 600; text-decoration: underline; }
.obrs-test-env-tab a:hover, .obrs-test-env-tab a:focus-visible { color: #fff; text-decoration-thickness: 2px; }
@media (max-width: 767.98px) {
    /* Above the applicant's bottom navigation on a phone. */
    .obrs-test-env-tab { bottom: calc(var(--obrs-bottom-nav-h) + 8px); }
}
@media print { .obrs-test-env-tab { display: none; } }

/* ── iPhone and iPad: no zoom into a field (2026-10-24) ──────────────────────
   Safari on iOS zooms the page into any field whose text is smaller than 16px and leaves it zoomed, so filling in a
   table of shareholders meant pinching back out after every box.  On iOS only (-webkit-touch-callout exists only
   there) fields keep 16px text; every other browser keeps the compact sizes.  Zoom itself is never switched off
   (no maximum-scale): people who need to enlarge the page must still be able to. */
@media (max-width: 767.98px) and (pointer: coarse) {
    /* Phones of every kind: 16px is also the smallest text comfortable to fill in on a small screen. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
    select,
    textarea,
    .form-control,
    .form-select,
    .bootstrap-select > .dropdown-toggle,
    .bootstrap-select .bs-searchbox input {
        font-size: 16px !important;
    }
    /* The larger text needs the room: a compact box kept its fixed height and cut the bottom off "Passport".
       44px is the smallest comfortable target for a finger. */
    .form-control:not(textarea),
    .form-select,
    .bootstrap-select > .dropdown-toggle {
        height: auto !important;
        min-height: 44px;
    }
}
@supports (-webkit-touch-callout: none) {
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
    select,
    textarea,
    .form-control,
    .form-select,
    .bootstrap-select > .dropdown-toggle,
    .bootstrap-select .bs-searchbox input {
        font-size: 16px !important;
    }
    .form-control:not(textarea),
    .form-select,
    .bootstrap-select > .dropdown-toggle {
        height: auto !important;
    }
}

/* ── A panel being drawn again after a save or an upload (CD-922) ───────────
   Said in words beside its title, never by fading the panel: opacity below 1 would trap the panel's dropdowns. */
[data-refresh-after-save][aria-busy="true"] .card-header::after {
    content: "Updating…";
    font-weight: 400;
    color: var(--bs-secondary-color, #6c757d);
    margin-left: .5rem;
}

/* ── Phones: buttons and long dropdown text (user, 2026-11-06, from Safari on an iPhone) ───────────────────────
   A small button is a flex box, and a flex box wider than its text starts the text on the left: "Submit" stretched
   across a form's foot read that way.  Centred here, except a dropdown's trigger, which starts its text on the left. */
.btn:not(.dropdown-toggle) { justify-content: center; }

/* A button in a table's action cell never breaks its label ("Pay" over "now").  Two of them sit side by side where there is
   room and one over the other on a phone, with a gap, so they do not push the table past the screen or touch. */
.table td .btn { white-space: nowrap; }
.table td > .btn { margin-top: .125rem; margin-bottom: .125rem; }
@media (min-width: 768px) { .table td:has(> .btn) { white-space: nowrap; } }

/* A dropdown's long option breaks onto a second line instead of widening the menu past the screen. */
.bootstrap-select .dropdown-menu { max-width: calc(100vw - 2rem); }
.bootstrap-select .dropdown-menu .inner { overflow-x: hidden; }
.bootstrap-select .dropdown-menu li > a,
.bootstrap-select .dropdown-menu li > button { white-space: normal; overflow-wrap: anywhere; }
.bootstrap-select .dropdown-menu li a span.text { white-space: normal; }

@media (max-width: 767.98px) {
    /* The menu is as wide as its field, wherever the field starts: 100vw would still run past a field set inside a card. */
    .bootstrap-select .dropdown-menu { max-width: 100%; min-width: 100% !important; }
    /* A small button's fixed height is why a label that wrapped touched its border: it grows with its label instead. */
    .btn-sm { height: auto; min-height: var(--ctrl-sm-h); line-height: 1.3; padding-top: .3rem; padding-bottom: .3rem; }
}

/* Portal notifications as they arrive (portal-notices.js, task #389).  A child of <body>, so its transform and z-index
   cannot trap an overlay: above the top bar, below an open menu and any modal.  Clicks pass through the empty stack. */
.obrs-toasts {
    position: fixed;
    top: calc(var(--obrs-topbar-height) + .75rem);
    right: 1rem;
    z-index: 1070;
    width: 23.5rem;
    max-width: calc(100vw - 2rem);
    display: flex;
    flex-direction: column;
    gap: .625rem;
    pointer-events: none;
}
.obrs-toast {
    --obrs-toast-tint: rgba(var(--obrs-primary-rgb), .1);
    --obrs-toast-ink:  var(--obrs-primary);
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    padding: .75rem .625rem .75rem .75rem;
    background-color: #fff;
    border: 1px solid rgba(0,0,0,.08);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    color: var(--bs-body-color);
    letter-spacing: 0;
    animation: obrs-toast-in .22s ease-out both;
}
.obrs-toast-success { --obrs-toast-tint: var(--bs-success-bg-subtle); --obrs-toast-ink: var(--bs-success-text-emphasis); }
.obrs-toast-warning { --obrs-toast-tint: var(--bs-warning-bg-subtle); --obrs-toast-ink: var(--bs-warning-text-emphasis); }
.obrs-toast-danger  { --obrs-toast-tint: var(--bs-danger-bg-subtle);  --obrs-toast-ink: var(--bs-danger-text-emphasis); }
.obrs-toast-leaving { animation: obrs-toast-out .18s ease-in both; pointer-events: none; }
.obrs-toast-icon {
    flex: none;
    width: 2.375rem;
    height: 2.375rem;
    border-radius: .625rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--obrs-toast-tint);
    color: var(--obrs-toast-ink);
    font-size: 1.15rem;
}
.obrs-toast-body { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.obrs-toast-title { font-size: .875rem; font-weight: 600; line-height: 1.35; }
.obrs-toast-text { font-size: .8125rem; line-height: 1.45; color: var(--bs-secondary-color); margin-top: .125rem; }
.obrs-toast-action {
    display: inline-block;
    margin-top: .375rem;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--obrs-primary);
    text-decoration: none;
}
.obrs-toast-action:hover, .obrs-toast-action:focus-visible { color: var(--obrs-primary-dark); text-decoration: underline; }
.obrs-toast-close {
    flex: none;
    width: 1.75rem;
    height: 1.75rem;
    margin: -.25rem -.125rem 0 0;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: transparent;
    border: 0;
    border-radius: .5rem;
    color: var(--bs-secondary-color);
    font-size: .75rem;
}
.obrs-toast-close:hover, .obrs-toast-close:focus-visible { background-color: rgba(0,0,0,.05); color: var(--bs-body-color); }
.obrs-toast-more {
    pointer-events: auto;
    align-self: flex-end;
    padding: .25rem .75rem;
    background-color: #fff;
    border: 1px solid rgba(0,0,0,.08);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    font-size: .8125rem;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--obrs-primary);
}
.obrs-toast-more[hidden] { display: none; }
@keyframes obrs-toast-in  { from { opacity: 0; transform: translateY(-.5rem); } to { opacity: 1; transform: none; } }
@keyframes obrs-toast-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes obrs-toast-fade-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
    .obrs-toast { animation-name: obrs-toast-fade-in; }
    .obrs-toast-leaving { animation-name: obrs-toast-out; }
}
@media (max-width: 575.98px) {
    .obrs-toasts { left: .75rem; right: .75rem; width: auto; max-width: none; }
}

/* A long email address breaks inside the reminder instead of widening a phone's page. */
.obrs-email-reminder-text { min-width: 0; flex-basis: 16rem; overflow-wrap: anywhere; }
