/* Getfive ERP — minimal app-level overrides on top of Bootstrap 5 defaults. */

/* Explicitly light: prevents a browser/OS "force dark mode" feature from
   recoloring the app (which produces a muddy dark-navy-with-orange-tint
   look on cards/text that were never designed for it -- Bootstrap's
   `bg-body-tertiary`-style CSS variables are exactly what those browser
   features target first). data-bs-theme="light" on <html> (see layouts)
   plus this hardcode both attack the same problem from two angles. */
:root {
    color-scheme: light only;
}

body {
    font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    /* The top navbar is `fixed-top` (see navbar.php) so it stays visible at
       all times instead of scrolling away with the page. Being fixed takes
       it out of normal document flow, so without this padding the page's
       actual content would render underneath it, hidden behind the bar. */
    padding-top: 56px;
}

.app-body {
    background-color: #eef1f6;
    color: #1f2733;
}

/* .app-shell is the flex row holding the sidebar + <main>. min-height keeps
   the navy panel at least one viewport tall on short pages; align-items:
   stretch (flex default, set explicitly for clarity) makes the sidebar
   grow to match <main> on pages taller than the viewport (long DataTables,
   forms, etc.) -- without this the navy background stopped at one
   viewport's height while the page content kept scrolling underneath it. */
.app-shell {
    align-items: stretch;
    min-height: calc(100vh - 56px);
}

/* Flex items default to min-width: auto, which means <main> refuses to
   shrink below the intrinsic width of its widest content (a wide
   DataTable, for example) even though that content already has its own
   .table-responsive scrollbar. Without this, a wide table doesn't just
   scroll internally -- it stretches <main>, which stretches .app-shell
   and <body> past the viewport, so scrolling right drags the fixed
   sidebar and navbar along with it instead of staying in place. Setting
   min-width: 0 lets <main> shrink to the available flex space and defers
   overflow to whichever inner container (.table-responsive, etc.)
   actually declares overflow-x: auto. */
.app-shell > main {
    min-width: 0;
    /* Below 992px the fixed bottom tab bar (partials/bottom-nav.php) sits
       over the bottom of the viewport instead of the copyright footer (which
       is d-none below that breakpoint, see footer.php) -- this reserves
       space for the tab bar's own height plus any device safe-area inset
       (notch/home-indicator), so page content never renders underneath it.
       The >=992px override further down restores the old footer-height
       reservation once the tab bar itself is hidden (d-lg-none) and the
       copyright footer is what's actually showing again. */
    /* !important: <main> also carries Bootstrap's p-3/p-md-4 utility
       classes, which set padding (including padding-bottom) with their own
       !important -- without matching it here, this rule silently loses and
       the fixed bar overlaps the last bit of page content. */
    padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px) + 0.75rem) !important;
}

/* No view in this app wraps its Bootstrap `.row` grids in a `.container` /
   `.container-fluid` -- <main>'s own p-3/p-md-4 utility padding is the only
   thing standing in for one. But a bare `.row` still carries Bootstrap's
   default negative left/right margins (meant to be canceled out by a
   container's matching positive padding), so every grid in the app --
   dashboard KPI cards, list-page filter rows, form rows, etc. -- bleeds a
   few pixels past <main>'s padding box on both sides. That's invisible on
   wide desktop viewports (plenty of surrounding whitespace to absorb it)
   but on a narrow phone screen those few pixels are exactly enough to clip
   the last column in each row against the edge of the viewport. Zeroing it
   here fixes every row on every page in one place, instead of hunting down
   and patching each view's markup individually. */
.app-shell > main > .row {
    margin-left: 0;
    margin-right: 0;
}

.sidebar-nav {
    width: 240px;
    height: 100%;
}

/* #sidebarOffcanvas (Bootstrap's .offcanvas) is itself a flex column, and
   .offcanvas-body already gets flex-grow: 1 + overflow-y: auto from
   Bootstrap -- so the nav links inside it scroll independently, while
   .sidebar-user-footer (avatar/name/role + Logout), placed as a sibling
   AFTER .offcanvas-body rather than inside it, is never part of that
   scrolling region and stays pinned to the bottom of the panel instead. */
.sidebar-user-footer {
    flex-shrink: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    padding: 0.75rem;
}

@media (max-width: 991.98px) {
    .sidebar-nav { width: 100%; }

    /* Bootstrap's offcanvas defaults to a fixed 400px width with no
       max-width cap -- on phones narrower than that (most of them) it would
       render wider than the viewport itself. Capping to 85vw keeps a strip
       of the dimmed backdrop visible even on small screens, which is the
       cue that there's a page behind the drawer and it's dismissible. */
    #sidebarOffcanvas.offcanvas-lg {
        width: min(320px, 85vw);
    }
}

/* Close button + "Menu" label shown only below the 992px breakpoint (see
   sidebar.php's d-lg-none on this header) -- matches the dark drawer instead
   of Bootstrap's default light offcanvas-header styling. */
#sidebarOffcanvas .offcanvas-header {
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    color: #fff;
}

/* Separate scroll regions for the menu vs. the page content, at the same
   >=992px breakpoint where Bootstrap's own `.offcanvas-lg` reset already
   turns the sidebar from a slide-out drawer into an always-visible static
   panel. Below this breakpoint the sidebar stays exactly as Bootstrap's
   offcanvas drawer behaves (its own overlay, own scroll) -- untouched.
   Above it: the sidebar is pinned in place (its own independent scrollbar
   if its links ever exceed the screen height) while <main> scrolls on its
   own, instead of the whole page -- including the navy menu panel --
   scrolling together as one long unit. */
@media (min-width: 992px) {
    #sidebarOffcanvas.offcanvas-lg {
        position: fixed;
        top: 56px;
        bottom: 38px;
        left: 0;
        width: 240px;
        z-index: 1020;
    }

    /* h-100 (Bootstrap utility, !important) would otherwise resolve
       against the viewport once this panel is position: fixed, fighting
       the top/bottom offsets above -- auto lets top+bottom define the box
       instead, so the panel's own overflow-y: auto (set earlier) scrolls
       correctly within the space between the navbar and the footer. */
    #sidebarOffcanvas .sidebar-nav {
        height: auto !important;
    }

    .app-shell > main {
        margin-left: 240px;
        height: calc(100vh - 56px - 38px);
        overflow-y: auto;
        /* Restores the original footer-height reservation now that the
           bottom tab bar is hidden (d-lg-none) and the copyright footer
           (d-lg-block) is what's actually fixed to the bottom again. */
        padding-bottom: 3.25rem !important;
    }
}

.sidebar-nav .nav-link {
    border-radius: 0.4rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.92rem;
    transition: background-color 0.15s ease, padding-left 0.15s ease;
}

.sidebar-nav .nav-link.sub-link {
    font-size: 0.83rem;
    font-weight: 400;
    opacity: 0.78;
    margin-left: 0.65rem;
    padding-left: 0.65rem;
    border-left: 2px solid rgba(255, 255, 255, 0.12);
    border-radius: 0;
}

.sidebar-nav .nav-link.sub-link:hover,
.sidebar-nav .nav-link.sub-link.active {
    opacity: 1;
    border-left-color: var(--gf-orange);
}

.sidebar-nav .dropdown-toggle-custom {
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
}

.sidebar-nav .dropdown-toggle-custom::after {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid rgba(255, 255, 255, 0.55);
    border-bottom: 2px solid rgba(255, 255, 255, 0.55);
    transform: rotate(45deg);
    transition: transform 0.2s ease;
    margin-left: 0.5rem;
    flex-shrink: 0;
}

.sidebar-nav .dropdown-toggle-custom.collapsed::after {
    transform: rotate(-45deg);
}

.sidebar-nav ul.collapse,
.sidebar-nav ul.collapsing {
    padding: 0;
    margin: 0;
}

.sidebar-section-title {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.4);
    padding: 0.9rem 0.75rem 0.3rem;
    margin-top: 0.15rem;
}

.sidebar-nav .nav-item:first-child .sidebar-section-title {
    padding-top: 0.3rem;
}

.nav-pills .nav-link.active {
    background-color: rgba(255, 255, 255, 0.15);
    font-weight: 600;
}

.nav-pills .nav-link:hover {
    background-color: rgba(255, 255, 255, 0.08);
}

.auth-body { background: linear-gradient(135deg, var(--gf-navy) 0%, var(--gf-navy-light) 100%); }

/* ---- Top navbar ---- */

.navbar {
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
}

/* Navbar is white app-wide (per explicit request) -- a thin bottom border
   stands in for the contrast the old navy background used to provide
   against the page's light-gray body background. */
.navbar.bg-white {
    border-bottom: 1px solid rgba(22, 41, 74, 0.08);
}

.navbar.bg-white .navbar-brand {
    color: var(--gf-navy);
}

/* The brand/logo corner stays navy (matching the sidebar) even though the
   rest of the navbar is white -- bled out to the true top/left/bottom edges
   by canceling the navbar's own padding (px-3 = 1rem horizontal, 0.6rem
   vertical from the rule above) with equal negative margins, then pinned to
   height: 56px (the app's one fixed navbar-height constant, see .app-body's
   padding-top and .app-shell's min-height) so it fills the bar exactly.
   Fixed to the sidebar's own 240px width once the sidebar is permanently
   visible (>=992px, see #sidebarOffcanvas.offcanvas-lg's width below) so the
   two read as one continuous navy panel; below that breakpoint there's no
   persistent sidebar to align with, so it just hugs its own content
   (hamburger toggle + logo). */
.navbar-brand-panel {
    display: flex;
    align-items: center;
    height: 56px;
    background-color: var(--gf-navy);
    padding: 0 1.25rem;
    margin: -0.6rem 0 -0.6rem -1rem;
}

@media (min-width: 992px) {
    .navbar-brand-panel {
        width: 240px;
    }
}

@media (max-width: 991.98px) {
    .navbar-brand-panel {
        display: none;
    }
}

.navbar-brand-panel .navbar-toggler {
    border: 0;
    padding: 0.25rem 0.4rem;
}
.navbar-brand-panel .navbar-toggler:focus {
    box-shadow: none;
}
.navbar-brand-panel .navbar-toggler-icon {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255,255,255,0.9)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
.navbar-brand-panel .navbar-brand {
    color: #fff;
}

.navbar-brand {
    font-size: 1.15rem;
    letter-spacing: 0.2px;
}

.navbar-role-badge {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 700;
    color: #fff;
    background: rgba(255, 255, 255, 0.16);
    padding: 0.2rem 0.55rem;
    border-radius: 50rem;
}
/* Today's date in the banner (Dashboard only) -- a small rounded pill with
   an orange-tinted icon, matching the same pill-with-icon pattern the
   Expenses period filter uses elsewhere on this page, rather than plain
   muted text that looked like an afterthought next to the logo. */
.navbar-date-pill {
    background: rgba(241, 132, 22, 0.08);
    border: 1px solid rgba(241, 132, 22, 0.18);
    border-radius: 50rem;
    padding: 0.3rem 0.9rem;
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--gf-navy);
    white-space: nowrap;
}

.navbar-date-pill i {
    color: var(--gf-orange);
    font-size: 0.85rem;
}

/* Below 992px the brand-panel corner is hidden entirely (see
   .navbar-brand-panel's own max-width: 991.98px rule) and the date pill was
   ALSO hidden (d-lg-flex) -- together that left mobile users with a
   completely bare white banner and nothing but the avatar floating on the
   far right. The pill is shown here too (markup switched from d-none
   d-lg-flex to d-flex, with a shorter "21 Aug 2026" date swapped in for the
   full weekday version so it doesn't crowd a narrow screen) and sized down
   to read as a compact banner element rather than the same desktop-size
   pill just relocated. */
@media (max-width: 991.98px) {
    .navbar-date-pill {
        font-size: 0.72rem;
        padding: 0.28rem 0.7rem;
        gap: 0.4rem;
    }

    .navbar-date-pill i {
        font-size: 0.78rem;
    }
}

.navbar-user-toggle {
    padding: 0.25rem 0.5rem 0.25rem 0.25rem;
    margin-right: -0.25rem;
    border-radius: 50rem;
    transition: background-color 0.15s ease;
}
.navbar-user-toggle:hover,
.navbar-user-toggle:focus {
    background-color: rgba(22, 41, 74, 0.06);
}
.navbar-user-avatar {
    box-shadow: 0 0 0 2px rgba(22, 41, 74, 0.08);
}
.navbar-user-toggle.dropdown-toggle::after {
    margin-left: 0.35rem;
    vertical-align: 0.15em;
    color: #8a94a6;
}

#companySwitcher {
    min-width: 180px;
}

/*
 * Notification bell (navbar) -- same circular hover treatment as
 * .navbar-user-toggle just above, sized down since it's an icon-only
 * button rather than an avatar + name block. The unread badge is a small
 * absolutely-positioned dot-with-count pinned to the bell's top-right
 * corner rather than inline text, so it reads as a status indicator at a
 * glance the way every other app's notification badge does.
 */
.nav-bell-toggle {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    color: var(--gf-navy);
    font-size: 1.05rem;
    transition: background-color 0.15s ease;
}

.nav-bell-toggle:hover,
.nav-bell-toggle:focus {
    background-color: rgba(22, 41, 74, 0.06);
    color: var(--gf-navy);
}

.nav-bell-toggle.dropdown-toggle::after {
    display: none;
}

.notif-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 3px;
    border-radius: 50rem;
    background: #dc3545;
    color: #fff;
    font-size: 0.6rem;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    box-shadow: 0 0 0 2px #fff;
}

.notif-dropdown-menu {
    width: 340px;
    max-width: calc(100vw - 2rem);
    padding: 0;
    overflow: hidden;
}

.notif-list {
    max-height: 360px;
    overflow-y: auto;
}

.notif-item {
    display: block;
    padding: 0.6rem 1rem;
    border-bottom: 1px solid rgba(22, 41, 74, 0.06);
    white-space: normal;
    color: #1f2733;
}

.notif-item:last-child {
    border-bottom: 0;
}

.notif-item:hover,
.notif-item:focus {
    background-color: rgba(22, 41, 74, 0.04);
    color: #1f2733;
}

/* Unread rows get a small navy dot + a very light tinted background --
   subtle enough not to compete with the badge count, but enough to scan the
   list and tell read from unread without reading every timestamp. */
.notif-item.is-unread {
    background-color: rgba(22, 41, 74, 0.035);
}

.notif-item-title {
    font-size: 0.82rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.notif-item.is-unread .notif-item-title::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--gf-orange);
    flex-shrink: 0;
}

.notif-item-message {
    font-size: 0.78rem;
    color: #5b6577;
    margin-top: 0.1rem;
}

.notif-item-time {
    font-size: 0.7rem;
    color: #9aa3b2;
    margin-top: 0.25rem;
}

@media (max-width: 575.98px) {
    .nav-bell-toggle {
        width: 32px;
        height: 32px;
        font-size: 0.95rem;
    }

    .notif-dropdown-menu {
        width: 300px;
    }
}

.card {
    border: none;
    background-color: #fff;
    color: #1f2733;
    border-radius: 0.75rem;
}

.card.shadow-sm {
    box-shadow: 0 1px 3px rgba(22, 41, 74, 0.07), 0 4px 14px rgba(22, 41, 74, 0.09) !important;
}

.card-header {
    border-bottom: 1px solid rgba(22, 41, 74, 0.08);
    border-top-left-radius: 0.75rem !important;
    border-top-right-radius: 0.75rem !important;
    padding: 0.9rem 1.25rem;
}

.card-body {
    padding: 1.25rem;
}

/* ---- Typography ---- */

h1, h2, h3, h4, h5, h6 {
    color: var(--gf-navy);
    font-weight: 700;
    letter-spacing: -0.015em;
}

.card-header.fw-semibold {
    font-size: 0.95rem;
    letter-spacing: -0.005em;
}

.text-muted { color: #7a8496 !important; }

/* ---- Buttons ---- */

.btn {
    border-radius: 0.5rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.btn-primary {
    box-shadow: 0 2px 6px rgba(241, 132, 22, 0.28);
}

.btn-primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 10px rgba(241, 132, 22, 0.32);
}

.btn-sm {
    border-radius: 0.4rem;
}

.btn-outline-primary {
    color: var(--gf-orange);
    border-color: var(--gf-orange);
}

.btn-outline-primary:hover {
    background-color: var(--gf-orange);
    border-color: var(--gf-orange);
}

/* ---- Badges ---- */

.badge {
    font-weight: 600;
    letter-spacing: 0.01em;
    padding: 0.4em 0.7em;
    border-radius: 50rem;
}

/* ---- Forms ---- */

.form-control,
.form-select {
    border-radius: 0.5rem;
    border-color: #d8dee6;
}

.form-label {
    font-weight: 600;
    color: #3a4252;
}

/* ---- List groups & alerts ---- */

.list-group-item {
    border-color: rgba(22, 41, 74, 0.08);
}

.list-group-flush .list-group-item {
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
}

.alert {
    border-radius: 0.6rem;
    border: none;
}

/* ---- DataTables (jQuery DataTables + Bootstrap 5 integration) ---- */

/* Solid navy header -- the same --gf-navy as the sidebar logo panel --
   applied globally so every list page in the app (Expenses, Invoices,
   Vendors, Tasks, Users, Compliance, Payroll, etc.) shares one consistent,
   branded table header instead of the plain gray-on-white it started with.
   Sort-direction arrows are DataTables' own ::before/::after pseudo-
   elements with no color of their own, so they automatically inherit this
   white text color and stay visible. Only a single header row per table,
   so unlike a repeated per-row divider this doesn't add print-ink weight. */
table.dataTable thead th {
    background-color: var(--gf-navy);
    color: #fff;
    white-space: nowrap;
    text-transform: uppercase;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    font-weight: 700;
    border-bottom-width: 2px !important;
    border-bottom-color: var(--gf-navy) !important;
}

table.dataTable tbody tr:hover {
    background-color: rgba(241, 132, 22, 0.04);
}

table.dataTable tbody td {
    vertical-align: middle;
    padding-top: 0.85rem;
    padding-bottom: 0.85rem;
}

/* Row-title links (entity/lead/user names etc.) use brand navy instead of
   the default Bootstrap blue, which clashed with the navy/orange palette. */
table.dataTable tbody td a {
    color: var(--gf-navy);
    text-decoration: none;
    font-weight: 600;
}

table.dataTable tbody td a:hover {
    color: var(--gf-orange);
    text-decoration: underline;
}

/* Empty-value placeholders ("-") rendered by DataTables defaultContent */
table.dataTable tbody td.text-muted,
table.dataTable tbody td .cell-empty {
    color: #b7bec9;
}

/* Multi-value cells (e.g. two GSTINs on one entity) -- each value gets its
   own line + a light pill instead of one long comma-separated string. */
.cell-multi-value {
    display: inline-block;
    background: rgba(22, 41, 74, 0.05);
    color: var(--gf-navy);
    border-radius: 0.3rem;
    padding: 0.1rem 0.45rem;
    font-size: 0.8rem;
    line-height: 1.6;
    margin: 0.05rem 0;
}

/* The length <select> already carries Bootstrap's own .form-select
   .form-select-sm classes (added by the DataTables Bootstrap 5 skin) --
   it must NOT get a padding/border override here. An earlier version of
   this rule set equal padding on all four sides, which ate the extra
   right-padding Bootstrap reserves for the select's dropdown-chevron
   background-image, making the control look squashed and the arrow
   overlap the number. It only needs the same focus treatment as the
   search box below, which composes fine with .form-select. */
.dataTables_wrapper .dataTables_filter input {
    border-radius: 0.4rem;
    border: 1px solid #d8dee6;
    padding: 0.3rem 0.6rem;
}

.dataTables_wrapper .dataTables_length select {
    min-width: 4.5rem;
}

.dataTables_wrapper .dataTables_filter input:focus,
.dataTables_wrapper .dataTables_length select:focus {
    outline: none;
    border-color: var(--gf-orange);
    box-shadow: 0 0 0 0.15rem rgba(241, 132, 22, 0.18);
}

.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
    font-size: 0.85rem;
    color: #7a8496;
}

.dataTables_wrapper .page-link {
    border-radius: 0.4rem;
    margin: 0 0.1rem;
    color: var(--gf-navy);
    border-color: transparent;
}

.dataTables_wrapper .page-item.active .page-link {
    background-color: var(--gf-orange);
    border-color: var(--gf-orange);
}

@media (max-width: 575.98px) {
    .dataTables_wrapper .page-link {
        min-width: 2.25rem;
        min-height: 2.25rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        margin: 0 0.2rem;
        font-size: 0.9rem;
    }
}
.dataTables_wrapper .page-item.disabled .page-link {
    color: #c2c9d4;
}

/* ---- Merger & Acquisition: Partners Kanban board ---- */

.kanban-column {
    max-height: 65vh;
    overflow-y: auto;
}

.kanban-card {
    border: 1px solid rgba(22, 41, 74, 0.08);
}

/* ---- Scrollbar (WebKit/Blink; degrades gracefully elsewhere) ---- */

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: #c2c9d4;
    border-radius: 6px;
}

::-webkit-scrollbar-thumb:hover {
    background: #a3adbb;
}

/* ---- Login screen (app/Views/auth/login.php) ---- */
/* Single centered card: navy header (logo + subtitle) over a white form
   body -- logo image comes from Settings > Branding (SettingsController)
   when an admin has uploaded one, else falls back to a text wordmark. */

.auth-card {
    max-width: 420px;
    width: 100%;
    background: #fff;
}

.auth-card-header {
    background: linear-gradient(160deg, var(--gf-navy-dark) 0%, var(--gf-navy) 55%, var(--gf-navy-light) 100%);
    color: #fff;
    padding: 2.5rem 2rem 2rem;
    text-align: center;
}

.auth-logo-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    background: var(--gf-navy-dark);
    color: #fff;
    padding: 0.55rem 1.1rem;
    border-radius: 0.5rem;
    font-weight: 700;
    font-size: 1.15rem;
    letter-spacing: 0.2px;
    margin-bottom: 1rem;
}

.auth-logo-chip .auth-logo-accent {
    color: var(--gf-orange);
    font-size: 1.4rem;
    line-height: 1;
    margin-left: 1px;
}

.auth-logo-img {
    max-height: 48px;
    max-width: 220px;
    margin-bottom: 1rem;
}

.auth-card-subtitle {
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.9rem;
    margin: 0;
}

.auth-card-body {
    padding: 2rem;
}

.auth-forgot-link {
    font-size: 0.85rem;
    color: #495364;
}

.auth-footer-note {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    color: #8a94a6;
    margin-top: 1.25rem;
}

.input-icon-group {
    position: relative;
}

.input-icon-group .form-control {
    padding-left: 2.75rem;
}

.input-icon-group .input-icon {
    position: absolute;
    left: 0.9rem;
    top: 50%;
    transform: translateY(-50%);
    color: #8a94a6;
    pointer-events: none;
    display: flex;
}

.input-icon-group input[type="password"] {
    padding-right: 2.75rem;
}

.password-toggle-btn {
    position: absolute;
    right: 0.6rem;
    top: 50%;
    transform: translateY(-50%);
    border: none;
    background: none;
    color: #8a94a6;
    padding: 0.25rem;
    line-height: 0;
    cursor: pointer;
}

.password-toggle-btn:hover {
    color: #495364;
}

/* ---- Getfive brand palette: navy + orange (applied app-wide) ---- */

:root {
    /* Brand palette (exact values supplied): Primary Navy, Secondary Navy,
       Accent Orange, White. --gf-navy-dark intentionally equals --gf-navy
       (no separate "darkest" shade was supplied) so every navy surface in
       the app only ever renders one of these two exact navy hex values.
       --gf-orange-dark is the only derived (non-supplied) shade -- a
       ~15% darken of the accent orange used solely for :hover/:active
       button states, which is standard interaction-state engineering
       rather than a new brand color. */
    --gf-navy: #16294A;
    --gf-navy-dark: #16294A;
    --gf-navy-light: #1A2E52;
    --gf-orange: #F18416;
    --gf-orange-dark: #CD7013;
    --bs-primary: var(--gf-orange);
    --bs-primary-rgb: 241, 132, 22;
}

.sidebar-nav.bg-dark,
.offcanvas-lg.bg-dark {
    background-color: var(--gf-navy) !important;
}

.nav-pills .nav-link.active {
    background-color: rgba(241, 132, 22, 0.18);
    border-left: 3px solid var(--gf-orange);
    padding-left: calc(0.75rem - 3px);
    font-weight: 600;
}

.nav-pills .nav-link:hover {
    background-color: rgba(241, 132, 22, 0.1);
}

.btn-primary {
    background-color: var(--gf-orange);
    border-color: var(--gf-orange);
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
    background-color: var(--gf-orange-dark) !important;
    border-color: var(--gf-orange-dark) !important;
}

.text-primary {
    color: var(--gf-orange) !important;
}

.border-primary {
    border-color: var(--gf-orange) !important;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--gf-orange);
    box-shadow: 0 0 0 0.2rem rgba(241, 132, 22, 0.18);
}

.btn-auth-submit {
    background-color: var(--gf-orange);
    border-color: var(--gf-orange);
    padding: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.2px;
    border-radius: 0.5rem;
}

.btn-auth-submit:hover,
.btn-auth-submit:focus {
    background-color: var(--gf-orange-dark);
    border-color: var(--gf-orange-dark);
}

/* ---- Dashboard KPI cards (app/Views/dashboard/index.php) ---- */

/* A fixed grid, not Bootstrap's auto-width flex columns (.col-xl etc). The
   flex version wraps a card to the next line based on that CARD's own
   min-content width, which shifts with what's actually inside it -- e.g.
   the red "Overdue Tasks" urgency state is a few px wider than the neutral
   one -- so whether five cards fit on one line or four do depended on the
   day's numbers, not the screen. Same screen width, same five cards,
   sometimes wraps and sometimes doesn't: that inconsistency was the
   reported misalignment. An explicit column COUNT per breakpoint can't
   drift like that -- it always seats exactly five above the xl breakpoint,
   three from lg, two below that, regardless of label length or which
   card's value picked up urgency styling. */
.dashboard-kpi-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem; /* matches Bootstrap's .g-3 gutter this replaced */
    margin-bottom: 1rem;
}

@media (min-width: 992px) {
    .dashboard-kpi-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 1200px) {
    .dashboard-kpi-grid {
        grid-template-columns: repeat(5, minmax(0, 1fr));
    }
}

.dashboard-kpi {
    position: relative; /* ripple anchor, see .gf-ripple in the mobile UX pass below */
    transition: transform 0.15s ease, box-shadow 0.15s ease;
    /* Hard containment guarantee: on a narrow 2-column mobile grid, a
       fixed-size icon badge + gap + an all-caps label with letter-spacing
       can add up to more width than the card actually has, and a plain
       .card has no clipping by default -- the overflowing content just
       paints past the card's rounded border instead of wrapping or
       ellipsis-truncating the way it's supposed to. This guarantees
       nothing ever visually bleeds past the card again, regardless of
       what's inside it. */
    overflow: hidden;
}

/* Only cards with a real destination (rendered as <a>, via the $kpi()
   helper's $href argument) get the hover lift + pointer cursor -- a plain
   <div> card (e.g. "Your Role", which has nowhere to go) stays static so
   its appearance doesn't falsely promise it's clickable. */
.dashboard-kpi-link {
    display: block;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.dashboard-kpi-link:hover,
.dashboard-kpi-link:focus-visible {
    color: inherit;
    text-decoration: none;
    transform: translateY(-2px);
    box-shadow: 0 0.5rem 1.25rem rgba(22, 41, 74, 0.1) !important;
}

.dashboard-kpi-link:focus-visible {
    outline: 2px solid var(--gf-orange, #F18416);
    outline-offset: 2px;
}

/* Compact by request: the KPI strip is a glance-level summary sitting above
   the real content, so it earns height sparingly. Padding, icon, label and
   value are all a notch down from the original sizing -- the row now reads as
   a thin band of figures rather than five tall panels, and a long amount like
   "Rs 23,76,000.00" fits on one line at the xl 5-column width instead of
   wrapping and forcing every card in the row taller. */
.kpi-card {
    padding: 0.7rem 0.85rem;
    position: relative;
}

/* The markup uses Bootstrap's .gap-3 on .kpi-body; overridden here (as the
   mobile rules below already do) so the icon sits closer to the text. */
.kpi-body {
    gap: 0.6rem !important;
}

.kpi-card .settings-icon-badge {
    width: 30px;
    height: 30px;
    font-size: 0.8rem;
    border-radius: 0.5rem;
}

/* ---- Dashboard chart sizing ----
   Heights live here rather than as inline styles so the two panels can be
   tuned together: the doughnut card's list used to grow the card taller than
   the bar chart beside it, leaving the row visibly uneven. The list now
   scrolls within a fixed area, so both cards stay the same height however
   many heads exist. */
/* Solid navy header -- same --gf-navy as the sidebar/logo panel and the
   DataTables header -- for the two dashboard chart cards (Monthly Expense,
   Expense Breakdown), replacing the plain bg-white strip so the dashboard's
   top-level cards read as branded rather than generic. Icon, title, muted
   summary text, and the "All heads" link all need explicit light colors
   since none of them inherit a usable color from a dark background. */
.dash-chart-header {
    background-color: var(--gf-navy);
    color: #fff;
    border-bottom-color: var(--gf-navy);
}

.dash-chart-header .text-muted {
    color: rgba(255, 255, 255, 0.72) !important;
}

.dash-chart-header .text-body {
    color: #fff !important;
}

.dash-chart-header a {
    color: rgba(255, 255, 255, 0.92);
}

.dash-chart-header a:hover {
    color: #fff;
}

/* Takes whatever height the card has left after the header and summary
   strip, so the bars fill the panel rather than sitting above a dead band.
   min-height keeps it readable if the card is ever short; the explicit
   `position: relative` + absolutely-positioned canvas is what lets
   Chart.js shrink as well as grow inside a flex parent. */
.dash-chart-fill {
    flex: 1 1 auto;
    position: relative;
    min-height: 260px;
}

.dash-chart-fill > canvas {
    position: absolute;
    inset: 0;
}

.dash-chart-donut {
    height: 200px;
}

.dash-breakdown-scroll {
    max-height: 232px;
    overflow-y: auto;
}

/* Tighter rows than the table default -- eight heads should read as a
   compact legend beside the chart, not as a full data table. */
.dash-breakdown-table td {
    padding-top: 0.32rem;
    padding-bottom: 0.32rem;
}

.dash-breakdown-table tr:last-child td {
    border-bottom: 0;
}

@media (max-width: 1199.98px) {
    /* Stacked below xl: the cards no longer sit side by side, so there's no
       height to match and both can size to their own content. */
    .dash-chart-fill { min-height: 300px; }
    .dash-chart-donut { height: 230px; }
    .dash-breakdown-scroll { max-height: none; }
}

@media (min-width: 1200px) {
    /* Per explicit request ("use bottom and right side space, align
       proper"): the base 260px/200px/232px figures were sized for a
       cramped laptop viewport and left a large dead band of white space
       below both cards on anything wider/taller -- a wasted two-thirds of
       the row on a normal desktop monitor. Taller here means the bar
       chart, the doughnut and its scrollable legend all stretch to use
       the vertical room the row already has (both cards still match via
       .card.h-100), instead of floating in the top third of it. */
    .dash-chart-fill { min-height: clamp(240px, calc(100vh - 340px), 520px); }
    .dash-chart-donut { height: clamp(180px, calc(100vh - 390px), 420px); }
    .dash-breakdown-scroll { max-height: clamp(160px, calc(100vh - 470px), 440px); }
}

/* Dashboard "Recent Activity" card -- a navy header (same brand navy as the
   sidebar/brand panel, so it reads as a deliberate section break rather
   than just another white card) with a "LIVE" pill on the right, then a
   flush list of Task status-change events (see AuditLogModel::
   recentTaskStatusChanges()). "LIVE" is a static label, not an actual
   auto-refresh feed -- it communicates "this is current as of page load",
   matching how the rest of the Dashboard already works (every figure here
   reloads with the page, nothing polls). */
.recent-activity-card {
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.recent-activity-header {
    background: var(--gf-navy);
    color: #fff;
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    padding: 0.85rem 1.15rem;
}

.recent-activity-live-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    padding: 0.25rem 0.7rem;
    border-radius: 50rem;
}

.recent-activity-live-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #34d399;
    box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.25);
}

.recent-activity-list {
    /* Card is now a flex column sharing a row with the two expense charts,
       so it should fill whatever height the row's tallest column sets
       (matching .card.h-100) and scroll WITHIN that height, rather than
       always claiming a fixed 420px regardless of the neighbouring cards --
       that fixed height was what made a row cut a list item off mid-way. */
    flex: 1 1 auto;
    min-height: 0;
    max-height: 420px;
    overflow-y: auto;
}

@media (min-width: 1200px) {
    .recent-activity-list {
        /* Above xl, the card sits in a three-column row next to the taller
           chart card -- let it stretch to match instead of capping at
           420px, so both columns end at the same edge. */
        max-height: none;
    }
}

.recent-activity-item {
    padding: 0.9rem 1.15rem;
}

.recent-activity-icon {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
}

.recent-activity-icon-pending {
    background: rgba(241, 132, 22, 0.12);
    color: var(--gf-orange);
}

.recent-activity-icon-done {
    background: rgba(25, 135, 84, 0.12);
    color: #198754;
}

.recent-activity-icon-cancelled {
    background: rgba(108, 117, 125, 0.14);
    color: #6c757d;
}

/* Dashboard greeting in the top banner. Sized to sit level with the brand
   and the user block without competing with either. */
.navbar-greeting {
    font-size: 0.95rem;
    color: var(--gf-navy);
    white-space: nowrap;
}

/* Below 992px the brand-panel corner is hidden and the greeting is the
   first thing in the banner, sharing a single row with the date pill and
   the avatar -- sized down to "Hi, Name" scale so all three fit without
   crowding a narrow screen (see the shortened "Hi, X" markup variant in
   navbar.php, shown only below the sm breakpoint). */
@media (max-width: 991.98px) {
    .navbar-greeting {
        font-size: 0.78rem;
    }
}

/* Page-level period filter (Dashboard header). Given a light surround so it
   reads as one control rather than loose buttons floating beside the title,
   and so it's visibly a filter rather than a pair of actions. */
.dash-filter {
    background: #fff;
    border: 1px solid rgba(22, 41, 74, 0.08);
    border-radius: 999px;
    padding: 0.3rem 0.55rem 0.3rem 0.75rem;
    box-shadow: 0 1px 3px rgba(22, 41, 74, 0.05);
}

.dash-filter-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8a94a6;
    white-space: nowrap;
}

.dash-filter .btn-group .btn {
    border-radius: 999px;
    padding-inline: 0.85rem;
}

/* Period filter as a docked vertical rail instead of a horizontal pill in
   the header row (per explicit request) -- fixed to the right edge of the
   viewport, docked near the TOP rather than vertically centered (also per
   explicit request, after the centered version read as floating in the
   middle of the page rather than attached to it), and sized down
   throughout so a two-button filter doesn't take up more visual weight
   than the figures it's scoping. Only ever holds the Year/Month toggle
   (never wraps to more than this), so a fixed width is safe here in a way
   it wouldn't be for .dash-filter's variable-width pill. */
.dash-filter-rail {
    position: fixed;
    top: 72px;
    /* Per explicit request: was flush against the true browser edge
       (right: 0), which read as clipped/cramped rather than a deliberately
       placed panel. A small margin plus rounding all four corners (was
       square on the right side, built for the old flush-edge look) makes
       it read as a floating card with its own breathing room instead. */
    right: 0.85rem;
    z-index: 25;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    background: #fff;
    border: 1px solid rgba(22, 41, 74, 0.08);
    border-radius: 0.6rem;
    box-shadow: 0 3px 10px rgba(22, 41, 74, 0.12);
    padding: 0.6rem 0.55rem;
}

/* Grip handle -- the ONLY part of the rail that starts a drag (see the
   inline script right after this markup in dashboard/index.php), so
   clicking Year/Month or the month dropdown never accidentally moves the
   panel instead of using it. A hover background (rather than just a color
   change on the bare icon) makes it read as its own small tappable control
   instead of a stray glyph floating above the rest of the panel. */
.dash-filter-rail-handle {
    cursor: grab;
    color: #c2c9d6;
    font-size: 0.75rem;
    line-height: 1;
    padding: 0.2rem 0.6rem;
    margin: -0.25rem 0 0;
    border-radius: 0.3rem;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.dash-filter-rail-handle:hover {
    color: #6c7688;
    background-color: rgba(22, 41, 74, 0.05);
}

.dash-filter-rail-dragging {
    cursor: grabbing;
    box-shadow: 0 3px 14px rgba(22, 41, 74, 0.2);
}

/* The rail is position: fixed, docked to the true right edge of the
   VIEWPORT (right: 0) -- not the content area's own right edge, which on
   a wide window sits well inside the viewport with only a few pixels of
   margin. Per explicit follow-up request ("do this also in right side"),
   the reserved gutter is removed entirely now -- first from the chart row
   below, then from the KPI row here too -- so neither row loses width to
   a gutter sized for a floating panel that usually isn't even over that
   row (the rail defaults to top:72px, but is user-draggable to anywhere
   down the page). The rail is z-index: 25 and floats ON TOP of whichever
   card happens to be under it at its current position, rather than a row
   being guaranteed a permanent gutter it mostly doesn't need -- the
   trade-off accepted for the chart row now applies here as well. */

.dash-filter-rail-dragging .dash-filter-rail-handle {
    cursor: grabbing;
}

/* A small navy badge (same visual language as .settings-icon-badge
   elsewhere in the app) instead of a bare gray glyph -- gives "what this
   panel is for" an actual focal point rather than leaving the eye to land
   straight on the Year/Month buttons with no label at all. */
.dash-filter-rail-icon {
    font-size: 0.68rem;
    color: #fff;
    background: var(--gf-navy);
    width: 22px;
    height: 22px;
    border: 0;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
    transition: opacity 0.15s ease, transform 0.15s ease;
}

.dash-filter-rail-icon:hover {
    opacity: 0.85;
}

/* Pressed/collapsed state gets a subtle press-down so the icon itself hints
   that it's a toggle, not just a static label for the buttons below it. */
.dash-filter-rail.is-collapsed .dash-filter-rail-icon {
    transform: scale(0.94);
}

/* Wraps the Year/Month controls so the hide/show toggle only has to flip
   one display value instead of two separate selectors. */
.dash-filter-rail-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
}

/* Collapsed = just the drag handle + funnel icon, per explicit request
   ("give option to hide a filter"). Padding/gap shrink along with it so it
   reads as a small pill rather than a card with empty space inside it. */
.dash-filter-rail.is-collapsed {
    padding: 0.35rem 0.4rem;
    gap: 0.2rem;
}

.dash-filter-rail.is-collapsed .dash-filter-rail-body {
    display: none;
}

.dash-filter-rail .btn-group-vertical {
    width: 100%;
}

.dash-filter-rail .btn-group-vertical .btn {
    font-size: 0.72rem;
    font-weight: 600;
    padding: 0.32rem 0.6rem;
    white-space: nowrap;
}

.dash-filter-rail #scopeMonthSelect {
    width: 100% !important;
    margin-top: 0.25rem;
    font-size: 0.72rem;
    padding: 0.25rem 0.4rem;
}

/* Below lg, per explicit follow-up request ("smart panel in mobile, easily
   drag out right and left and up and down"), the rail STAYS the same
   floating, position:fixed card it is on desktop instead of collapsing
   into a static full-width horizontal bar (the earlier behavior, reverted
   here) -- just sized down for a narrow screen. The grip handle stays
   visible for exactly that reason. Desktop only ever needed vertical
   dragging (it's pinned to the right edge, and a wide viewport has nowhere
   useful to go horizontally); a phone is narrow enough that left/right
   matters too -- e.g. dragging it clear of the KPI cards or the chart
   underneath it -- so the drag script below tracks both axes once this
   media query is active. See dashboard/index.php's inline script. */
@media (max-width: 991.98px) {
    .dash-filter-rail {
        top: 64px;
        right: 0.5rem;
        padding: 0.5rem 0.45rem;
        gap: 0.3rem;
    }

    .dash-filter-rail-icon {
        width: 20px;
        height: 20px;
        font-size: 0.62rem;
    }

    .dash-filter-rail .btn-group-vertical .btn {
        font-size: 0.68rem;
        padding: 0.28rem 0.55rem;
    }

    .dash-filter-rail #scopeMonthSelect {
        font-size: 0.68rem;
        padding: 0.22rem 0.35rem;
    }
}

/* Phones proper (same tier as the KPI-card breakpoint above): shrink the
   rail again on top of the 991.98px sizing, and give the collapsed pill its
   own even-tighter padding -- per explicit request ("make a small size
   filter in mobile"). The rail also STARTS collapsed on a first visit at
   this width (see dashboard/index.php's inline script), so this is really
   sizing the common case, not just the edge case of someone shrinking a
   wider window. */
@media (max-width: 575.98px) {
    .dash-filter-rail {
        top: 60px;
        right: 0.4rem;
        padding: 0.4rem 0.35rem;
        gap: 0.25rem;
    }

    .dash-filter-rail.is-collapsed {
        padding: 0.3rem 0.35rem;
        gap: 0.15rem;
    }

    .dash-filter-rail-handle {
        font-size: 0.68rem;
        padding: 0.15rem 0.5rem;
        margin-top: -0.15rem;
    }

    .dash-filter-rail-icon {
        width: 18px;
        height: 18px;
        font-size: 0.56rem;
    }

    .dash-filter-rail .btn-group-vertical .btn {
        font-size: 0.62rem;
        padding: 0.24rem 0.5rem;
    }

    .dash-filter-rail #scopeMonthSelect {
        font-size: 0.62rem;
        padding: 0.18rem 0.3rem;
    }
}

/* A card whose number is currently colored text-danger/text-warning (an
   actual overdue/pending count > 0, decided by the caller in
   dashboard/index.php) gets a slightly heavier left accent and a small
   corner dot -- enough to draw the eye first in a grid of otherwise
   identically-weighted cards, without adding a new color to the palette:
   both reuse Bootstrap's existing danger/warning red and orange. */
.dashboard-kpi-urgent-danger {
    border-left-width: 4px !important;
    box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.12), 0 .125rem .25rem rgba(0, 0, 0, .075) !important;
}

.dashboard-kpi-urgent-warning {
    border-left-width: 4px !important;
    box-shadow: 0 0 0 1px rgba(166, 113, 10, 0.14), 0 .125rem .25rem rgba(0, 0, 0, .075) !important;
}

.kpi-urgency-dot {
    position: absolute;
    top: 0.5rem;
    right: 0.55rem;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
}

.dashboard-kpi-urgent-danger .kpi-urgency-dot { color: #dc3545; }
.dashboard-kpi-urgent-warning .kpi-urgency-dot { color: #a6710a; }

/* The number itself gets a size bump too -- danger (an actual overdue
   count > 0) more than warning (due-soon/pending), so the single most
   action-worthy figure on the page reads as more important at a glance,
   not just differently colored, than the routine counters around it. */
.dashboard-kpi-urgent-danger .kpi-value {
    font-size: 1.4rem;
}

.dashboard-kpi-urgent-warning .kpi-value {
    font-size: 1.3rem;
}

.kpi-body {
    min-width: 0;
}

.kpi-label {
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #8a94a6;
    font-weight: 700;
    margin-bottom: 0.15rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.kpi-value {
    font-size: 1.2rem;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: #1f2733;
    /* A rupee amount like "₹2,827,750.00" is one unbroken token -- no
       spaces for the browser to wrap on -- so on a narrow card it would
       otherwise overflow and get silently clipped by .dashboard-kpi's
       overflow:hidden (which exists so OTHER content never bleeds past the
       card, see that rule's own comment) instead of wrapping to a second
       line the way a real app would. */
    overflow-wrap: anywhere;
}

.kpi-value-sm {
    font-size: 1rem;
}

/* Below 992px, a fixed-size icon badge + gap + an all-caps letter-spaced
   label routinely adds up to more width than a half-width (col-6) card has
   to give -- rather than lean on text-overflow:ellipsis (which reads as
   "something's cut off and broken" on a label as short as "OVERDUE
   TASKS"), let it wrap to two lines like a real app would, and shrink the
   icon/gap/padding slightly to give the label more room to begin with. */
@media (max-width: 991.98px) {
    .kpi-label {
        white-space: normal;
        overflow: visible;
        text-overflow: unset;
        line-height: 1.25;
    }

    .kpi-value {
        font-size: 1.5rem;
    }

    /* Tighter gutter than the 1rem desktop gap -- on a 2-column phone grid
       a full rem between cards reads as loose, uneven whitespace once the
       cards themselves have shrunk (see .kpi-card padding above); the grid
       stays proportional to the cards it holds instead of the spacing
       overpowering them. */
    .dashboard-kpi-grid {
        gap: 0.65rem;
    }

    /* This grid only ever gets a fixed 2-column track on mobile (see the
       base .dashboard-kpi-grid rule) -- when the number of visible cards is
       odd (5 KPIs is the common case, fewer if a role can't see one), the
       last card lands alone in a final row, flush left with a dangling
       empty half beside it. :last-child combined with :nth-child(odd)
       matches exactly that situation (an item both last AND in an odd
       position only happens when it's the sole occupant of its row on a
       2-column track), so it's promoted to spanning both columns and the
       row reads as a deliberate closing card instead of a leftover. */
    .dashboard-kpi-grid > *:last-child:nth-child(odd) {
        grid-column: 1 / -1;
    }

    .dashboard-kpi-grid > *:last-child:nth-child(odd) .kpi-body {
        align-items: center;
    }
}

/* Phone-width tightening, per explicit request ("make card small for
   mobile user"). The 991.98px breakpoint above already shrinks things once
   for tablets, but on an actual phone (≤575.98px, Bootstrap's own `sm`
   cutoff) the same cards still read as oversized: this trims padding, icon
   badge, gaps and type sizes a second notch so five KPI cards fit two-up
   without each one sprawling most of the screen height. Values keep enough
   size to stay legible as the headline figure -- only the surrounding
   chrome shrinks. */
@media (max-width: 575.98px) {
    .kpi-card {
        padding: 0.7rem 0.65rem;
    }

    .kpi-body {
        gap: 0.5rem !important;
    }

    .kpi-card .settings-icon-badge {
        width: 26px;
        height: 26px;
        font-size: 0.7rem;
        border-radius: 0.5rem;
    }

    .kpi-label {
        font-size: 0.62rem;
        margin-bottom: 0.15rem;
        line-height: 1.15;
    }

    .kpi-value {
        font-size: 1.15rem;
    }

    .dashboard-kpi-urgent-danger .kpi-value {
        font-size: 1.3rem;
    }

    .dashboard-kpi-urgent-warning .kpi-value {
        font-size: 1.25rem;
    }

    .dashboard-kpi-grid {
        gap: 0.5rem;
        margin-bottom: 0.75rem;
    }

    .kpi-urgency-dot {
        top: 0.5rem;
        right: 0.55rem;
        width: 7px;
        height: 7px;
    }
}

.kpi-value.text-danger { color: #dc3545 !important; }
.kpi-value.text-warning { color: #a6710a !important; }

/* Category accent -- a 3px left border on the card that identifies which
   module a KPI belongs to (company/compliance/tasks/invoices/expenses/
   payroll), independent of the text-danger/text-warning urgency coloring
   on the value itself. Reuses the same supplementary chart-neutral colors
   already established for the expense pie chart, not new brand hues. */
.dashboard-kpi.kpi-accent-navy { border-left: 3px solid var(--gf-navy); }
.dashboard-kpi.kpi-accent-orange { border-left: 3px solid var(--gf-orange); }
.dashboard-kpi.kpi-accent-blue { border-left: 3px solid #4a6fa5; }
.dashboard-kpi.kpi-accent-teal { border-left: 3px solid #5b7c99; }
.dashboard-kpi.kpi-accent-slate { border-left: 3px solid #8a94a6; }
.dashboard-kpi.kpi-accent-navylight { border-left: 3px solid var(--gf-navy-light); }

/* Dashboard "Quick Access" navigation cards -- same kpi-accent-* palette as
   the KPI cards above, just applied as a top border instead of a left one
   (matches the "pick a workspace" card style requested for the Dashboard's
   quick-nav row: colored top bar + icon + title + short blurb + CTA). */
.qa-card { border-top: 3px solid transparent; border-left: none !important; transition: transform 0.12s ease, box-shadow 0.12s ease; }
.qa-card:hover { transform: translateY(-2px); box-shadow: 0 0.5rem 1rem rgba(22, 41, 74, 0.1) !important; }
.qa-card.kpi-accent-navy { border-top-color: var(--gf-navy); }
.qa-card.kpi-accent-orange { border-top-color: var(--gf-orange); }
.qa-card.kpi-accent-blue { border-top-color: #4a6fa5; }
.qa-card.kpi-accent-teal { border-top-color: #5b7c99; }
.qa-card.kpi-accent-slate { border-top-color: #8a94a6; }
.qa-card.kpi-accent-navylight { border-top-color: var(--gf-navy-light); }

.chart-empty-state {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 160px;
    text-align: center;
}

/* Dashboard "nothing here" panels (see $emptyState() in dashboard/index.php)
   -- replaces a single muted <p> with an icon + title + subtitle so an
   empty widget reads as a deliberate, friendly state rather than looking
   broken or unfinished. */
.dash-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2rem 1rem;
    min-height: 160px;
}

.dash-empty-icon {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0.85rem;
}

.dash-empty-icon-success {
    background: #d1e7dd;
    color: #0f5132;
}

.dash-empty-icon-neutral {
    background: #eef1f6;
    color: #8a94a6;
}

.dash-empty-title {
    font-weight: 600;
    color: #1f2733;
    font-size: 0.92rem;
    margin-bottom: 0.15rem;
}

.dash-empty-sub {
    font-size: 0.8rem;
    color: #8a94a6;
    max-width: 320px;
}

/* ---- Settings page (app/Views/settings/index.php) ---- */

/* Profile hero banner -- same navy gradient used on the login page
   (.auth-body), so "who am I signed in as" reads as a premium, branded
   moment rather than another plain white card in the stack below it. */
.settings-hero {
    background: linear-gradient(160deg, var(--gf-navy-dark) 0%, var(--gf-navy) 55%, var(--gf-navy-light) 100%);
    border-radius: 0.75rem;
    color: #fff;
}

.settings-hero-avatar {
    width: 64px;
    height: 64px;
    border: 3px solid rgba(255, 255, 255, 0.25);
}

.settings-hero .btn-outline-light:hover {
    color: var(--gf-navy);
}

.settings-section-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #8a94a6;
    font-weight: 700;
}

.settings-section-sub {
    font-size: 0.82rem;
    color: #7a8496;
}

/* Same hover-lift language as .dashboard-kpi-link, applied to Settings
   cards so the whole app treats "hover = this card is interactive" the
   same way. */
.settings-card {
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.settings-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 0.75rem 1.5rem rgba(22, 41, 74, 0.12) !important;
}

/* Expense Heads cards: the footer line (year total / money awaiting
   approval) is a different KIND of fact from the headline figure above it,
   so it gets a hairline rule rather than just more whitespace. Without the
   rule the two numbers read as one block and the eye has to work out which
   is the headline. The rule is deliberately faint -- it separates, it
   doesn't box. */
.expense-head-foot {
    border-top: 1px solid var(--bs-border-color, #e6e9ee);
    margin-top: auto;
}

/* The chevron only tells you the card is a link, so it should recede until
   you are actually looking at that card -- then it moves the way your eye
   is about to. */
.settings-card .fa-chevron-right {
    opacity: 0.45;
    transition: opacity 0.15s ease, transform 0.15s ease;
}

.settings-card:hover .fa-chevron-right {
    opacity: 1;
    transform: translateX(2px);
}

/* Circular colored icon badges replace the old bare Font Awesome icon next
   to the card title -- a small change that makes each card's header scan
   as a distinct, deliberately-designed module rather than a plain list
   item. Colors reuse the same accent palette as the dashboard KPI cards
   (kpi-accent-*), not new brand hues. */
.settings-icon-badge {
    width: 38px;
    height: 38px;
    border-radius: 0.6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    flex-shrink: 0;
}

.settings-icon-badge.bg-orange-subtle { background: rgba(241, 132, 22, 0.12); color: var(--gf-orange); }
.settings-icon-badge.bg-navy-subtle { background: rgba(22, 41, 74, 0.08); color: var(--gf-navy); }
.settings-icon-badge.bg-blue-subtle { background: rgba(74, 111, 165, 0.12); color: #4a6fa5; }
.settings-icon-badge.bg-teal-subtle { background: rgba(91, 124, 153, 0.12); color: #5b7c99; }
.settings-icon-badge.bg-slate-subtle { background: rgba(138, 148, 166, 0.16); color: #5c6576; }
.settings-icon-badge.bg-navylight-subtle { background: rgba(26, 46, 82, 0.10); color: var(--gf-navy-light); }

/* Larger icon-badge variant for the Dashboard's Quick Access cards, where
   the icon is the visual anchor of the card rather than a small inline mark. */
.settings-icon-badge.settings-icon-badge-lg { width: 56px; height: 56px; border-radius: 0.75rem; font-size: 1.4rem; }

/* ---- Tasks list (app/Views/tasks/index.php) ---- */

/* Per-row priority accent, applied via DataTables' rowCallback alongside
   Bootstrap's border-start/border-3 utilities (which set width/style but
   not color) -- same palette as the Dashboard KPI cards' kpi-accent-*
   borders, just not coupled to that selector's .dashboard-kpi requirement
   so it can apply to a plain <tr>. */
.task-row-accent-orange { border-left-color: var(--gf-orange) !important; }
.task-row-accent-navylight { border-left-color: var(--gf-navy-light) !important; }
.task-row-accent-slate { border-left-color: #8a94a6 !important; }

/* ---- Budget & P&L show page (app/Views/budgets/show.php) ---- */

/* Thin progress bars used on the Revenue/Expenses summary cards and inside
   the Expense Category Variance table's "% of Budget Used" column -- the
   default Bootstrap .progress is too tall (1rem) for a dense table row, so
   this variant is deliberately slimmer and pill-shaped. */
.variance-progress-thin {
    height: 6px;
    border-radius: 999px;
    background-color: rgba(138, 148, 166, 0.18);
}

.variance-progress-thin .progress-bar {
    border-radius: 999px;
}

/* Bootstrap has no built-in "teal" utility; reuses the same teal already in
   the KPI/settings accent palette (#5b7c99) for the Revenue progress bar
   while it is still below 100% of target. */
.bg-teal { background-color: #5b7c99 !important; }

/* ---- Management Report (app/Views/budgets/show.php Sections 1-4) ---- */

/* A light TINT here, not the solid navy used on the list pages' DataTable
   headers (see the global table.dataTable rule above) -- those render once
   per page, while this repeats once per category/entity group, often
   7-10+ times on a Full Year report, so a solid dark fill would reintroduce
   the heavy, ink-hungry, repetitive-banner problem the group-header divider
   below was fixed for. The opacity is deliberately higher than a "barely
   there" tint (0.07 read as a flat, undifferentiated gray, not a color) --
   bumped so it unmistakably reads as light navy, with a solid navy
   underline and uppercase/letter-spaced label text (matching the same
   treatment already used on every DataTables list header) to reinforce
   that it's a deliberate column header, not a stray background. */
.mgmt-report-table thead th {
    background-color: rgba(22, 41, 74, 0.16);
    color: var(--gf-navy);
    border-bottom: 2px solid rgba(22, 41, 74, 0.35) !important;
    text-transform: uppercase;
    font-size: 0.78rem;
    letter-spacing: 0.03em;
    white-space: nowrap;
}

.mgmt-report-table tbody tr:hover {
    background-color: rgba(241, 132, 22, 0.04);
}

/* Section number badge in each card-header (1-4), reusing the same
   kpi-accent/settings-icon-badge palette so the report reads as four steps
   of one flow rather than four unrelated widgets. */
.mgmt-section-badge {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    font-weight: 700;
    flex-shrink: 0;
}

.mgmt-section-badge.bg-orange-subtle { background: rgba(241, 132, 22, 0.14); color: var(--gf-orange); }
.mgmt-section-badge.bg-navy-subtle { background: rgba(22, 41, 74, 0.1); color: var(--gf-navy); }
.mgmt-section-badge.bg-teal-subtle { background: rgba(91, 124, 153, 0.14); color: #5b7c99; }
.mgmt-section-badge.bg-blue-subtle { background: rgba(74, 111, 165, 0.14); color: #4a6fa5; }

/* Section header bar -- same navy as the sidebar logo panel / Expense Budget
   list header, so every "banner" in the module reads as one consistent
   brand color instead of some being plain white. The number badge switches
   to a translucent white circle here (the tinted color variants above are
   tuned for a white card-header and would barely show on navy). */
.mgmt-section-header {
    background-color: var(--gf-navy) !important;
    color: #fff;
}

.mgmt-section-header .mgmt-section-badge {
    background: rgba(255, 255, 255, 0.18) !important;
    color: #fff !important;
}

/* A divider bar above each category/entity group inside Sections 2-4 --
   replaces a plain uppercase text line with a filled strip so dense stacked
   tables visually separate into scannable groups. Two solid navy fills
   stacked directly on top of each other (the section banner, then a second
   dark bar per group) read as heavy/repetitive and burn through toner on a
   printed report -- this page has a Print button, so that's a real cost,
   not just a look. Reuses the exact light-tint + colored-left-border idiom
   already established on the Dashboard KPI cards (.dashboard-kpi-urgent-*,
   .kpi-accent-*) instead of inventing a new one, so the same class can be
   dropped onto any other list/report page in the app and match. */
.mgmt-group-header {
    background-color: rgba(22, 41, 74, 0.05);
    border-left: 4px solid var(--gf-orange);
    color: var(--gf-navy);
    border-radius: 0.35rem;
    padding: 0.55rem 0.9rem;
}

.mgmt-group-header .text-muted {
    color: #7a8496 !important;
}

.mgmt-group-header .text-body {
    color: var(--gf-navy) !important;
}

/* Section/table grand-total emphasis -- a soft brand-orange tint (not a
   solid fill, which would compete with the primary button) so the number
   finance actually reports upward is the last thing the eye lands on in
   each card. */
.mgmt-total-bar {
    background-color: rgba(241, 132, 22, 0.07);
    border-radius: 0.5rem;
    padding: 0.65rem 0.9rem;
}

.mgmt-report-table tfoot tr.mgmt-total-row td {
    background-color: rgba(241, 132, 22, 0.07) !important;
    font-weight: 700;
    border-top: 2px solid rgba(241, 132, 22, 0.18);
}

/* Dashed container around every inline "Add ___" form on the report --
   marks these controls as a distinct "create new" affordance rather than
   floating form fields with no visual boundary. Orange tint (matching the
   solid-orange "Add" button inside it) rather than a flat gray. */
.mgmt-add-panel {
    background-color: rgba(241, 132, 22, 0.05);
    border: 1px dashed rgba(241, 132, 22, 0.3);
    border-radius: 0.6rem;
    padding: 1rem 1rem 0.85rem;
}

/* =========================================================================
   Mobile app-grade UX pass: bottom tab bar, touch-friendly tap targets,
   tap feedback (hover doesn't fire on touch, so :active fills that role),
   and a slightly more modern card treatment. Everything here is scoped to
   either the .bottom-nav component itself or a (max-width: 991.98px) media
   query, matching the same breakpoint used everywhere else in this file for
   "mobile/tablet, before the sidebar becomes a permanent panel" -- desktop's
   existing :hover-based interactions (already present above, e.g.
   .dashboard-kpi-link:hover) are untouched.
   ========================================================================= */

/* ---- Bottom tab bar (partials/bottom-nav.php) ---- */

.bottom-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1030; /* below the offcanvas (1045) and its backdrop (1040), so
                       the menu drawer correctly dims/covers this like the
                       rest of the page while it's open */
    display: flex;
    background-color: #fff;
    border-top: 1px solid rgba(22, 41, 74, 0.08);
    box-shadow: 0 -2px 10px rgba(22, 41, 74, 0.06);
    padding-bottom: env(safe-area-inset-bottom, 0px); /* clears the home-
        indicator/gesture-bar area on notched phones instead of sitting
        underneath it */
}

.bottom-nav-item {
    position: relative; /* ripple anchor, see .gf-ripple below */
    overflow: hidden;
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    padding: 0.45rem 0.25rem;
    min-height: 56px;
    color: #8a94a6;
    text-decoration: none;
    font-size: 0.68rem;
    font-weight: 600;
    border: none;
    background: none;
    -webkit-tap-highlight-color: transparent;
    transition: color 0.15s ease;
}

.bottom-nav-item i {
    font-size: 1.05rem;
    line-height: 1;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    transition: background-color 0.15s ease, color 0.15s ease;
}

/* Active tab: orange text + a soft pill behind the icon, the same visual
   language modern nav bars (Android/YouTube/Google apps) use to show the
   current destination without relying on color alone. */
.bottom-nav-item.active {
    color: var(--gf-orange);
}

.bottom-nav-item.active i {
    background-color: rgba(241, 132, 22, 0.14);
}

.bottom-nav-item:active {
    color: var(--gf-orange-dark);
}

/* ---- Touch targets + tap feedback (mobile/tablet only) ---- */

@media (max-width: 991.98px) {
    /* Removes the default gray flash Android/Chrome draws on every tap --
       the custom :active states below replace it with something that
       matches the app instead of a generic system highlight. */
    a, button, .nav-link, .card {
        -webkit-tap-highlight-color: transparent;
    }

    /* Nav links and buttons sized to Apple/Google's ~44px minimum
       touch-target guidance -- comfortable to hit with a thumb, where the
       desktop sizing (tuned for a mouse pointer) runs noticeably smaller. */
    .sidebar-nav .nav-link,
    .navbar .btn,
    .btn {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    .sidebar-nav .nav-link {
        justify-content: flex-start;
    }

    /* Tap feedback for anything that's meant to be tappable but only had a
       :hover style before (which never fires on a touchscreen) -- a quick
       scale-down reads as "this responded to your tap" the way a native
       app's button press does. */
    .dashboard-kpi-link:active,
    .qa-card:active,
    .settings-card:active,
    .btn:active {
        transform: scale(0.97);
        transition: transform 0.1s ease;
    }

    /* Slightly larger corner radius + softer shadow than the desktop
       default -- reads less like a dense spreadsheet, more like a modern
       app's card list, without changing any layout/spacing underneath. */
    .card {
        border-radius: 0.85rem;
    }

    .card.shadow-sm {
        box-shadow: 0 2px 10px rgba(22, 41, 74, 0.07) !important;
    }
}

/* ---- Content fade-in ---- */

/* Every navigation in this app is a full page load (server-rendered PHP,
   no client-side router) -- there's no "page transition" to animate in the
   SPA sense. This is the low-cost equivalent: the main content area fades
   and lifts in slightly on load instead of just snapping into place, which
   softens the visual "flash" of a full page reload without touching how
   navigation actually works. prefers-reduced-motion respects users who've
   asked their OS to minimize motion. */
@media (prefers-reduced-motion: no-preference) {
    .app-shell > main {
        animation: gf-content-in 0.25s ease-out;
    }
}

@keyframes gf-content-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* =========================================================================
   Big-app UX pass 2: list pages that read like a native app's list (Gmail
   inbox, WhatsApp chat list) instead of a spreadsheet, a floating action
   button for each list's primary create action, and small "feels alive"
   touches (tap ripple, a nicer loading state) that YouTube/Google-style
   apps use throughout. Everything below is additive and scoped either to
   its own new classes (.dt-mobile-cards, .fab-button, .gf-ripple) or to the
   existing <=991.98px mobile/tablet breakpoint -- desktop's table/DataTables
   layout is untouched.
   ========================================================================= */

/* ---- List pages as app-style cards on phones ----
   .dt-mobile-cards is added to every DataTables-backed list table (see each
   module's index.php). app.js gives each <td> a data-label attribute (read
   from that column's <th>) on every draw, so this transform works for any
   table that carries the class -- one shared pattern instead of a bespoke
   mobile layout per module. Scoped to phones (<768px); tablets (768-991px)
   keep the real table, scrolling horizontally inside .table-responsive,
   since a 6-8 column table still mostly fits there. */
@media (max-width: 767.98px) {
    .table-responsive:has(.dt-mobile-cards) {
        overflow-x: visible; /* the card layout below never needs to scroll */
    }

    .dt-mobile-cards thead {
        display: none;
    }

    .dt-mobile-cards,
    .dt-mobile-cards tbody,
    .dt-mobile-cards tr,
    .dt-mobile-cards td {
        display: block;
        width: 100%;
    }

    .dt-mobile-cards tr {
        background-color: #fff;
        border: 1px solid rgba(22, 41, 74, 0.08);
        border-radius: 14px;
        box-shadow: 0 2px 8px rgba(22, 41, 74, 0.06);
        padding: 0.9rem 1rem;
        margin-bottom: 0.65rem;
        position: relative; /* ripple anchor, see .gf-ripple */
        overflow: hidden;
    }

    .dt-mobile-cards tr:last-child {
        margin-bottom: 0;
    }

    /* DataTables' own "no matching records" row shouldn't look like a card. */
    .dt-mobile-cards tr.odd.dt-empty-row,
    .dt-mobile-cards tr:has(td.dataTables_empty) {
        background: none;
        border: none;
        box-shadow: none;
        text-align: center;
        color: #8a94a6;
        padding: 1.5rem 0;
    }

    .dt-mobile-cards td {
        border: none !important;
        padding: 0.2rem 0 !important;
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 0.75rem;
        font-size: 0.85rem;
        white-space: normal !important;
    }

    /* The field name ("Due Date", "Status"...) rendered from the column's
       own <th> text -- same "label above/before value" convention as the
       Dashboard's KPI cards, so a list row and a KPI card read as the same
       design language. */
    .dt-mobile-cards td::before {
        content: attr(data-label);
        flex: 0 0 auto;
        font-size: 0.68rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.03em;
        color: #8a94a6;
    }

    /* First column is the row's identity (task title, invoice #, client
       name...) -- rendered big and bold with no label, exactly like an
       email subject line or a chat contact name heads a Gmail/WhatsApp
       row, with everything else underneath it as secondary detail. */
    .dt-mobile-cards td:first-child {
        display: block;
        font-size: 1rem;
        font-weight: 600;
        color: #1f2733;
        margin-bottom: 0.5rem;
        padding-bottom: 0.5rem !important;
        border-bottom: 1px solid rgba(22, 41, 74, 0.06);
    }

    .dt-mobile-cards td:first-child::before {
        content: none;
    }

    /* Last column is conventionally the row's action (a "View" button) in
       every list page in this app -- full-width and label-free, like a
       card's own call-to-action rather than a cramped inline link. */
    .dt-mobile-cards td:last-child {
        display: block;
        margin-top: 0.6rem;
    }

    .dt-mobile-cards td:last-child::before {
        content: none;
    }

    .dt-mobile-cards td:last-child a.btn,
    .dt-mobile-cards td:last-child button.btn {
        display: block;
        width: 100%;
        text-align: center;
    }

    /* A column with nothing but an empty header (already true for every
       list page's trailing action column, see td:last-child above) has an
       empty data-label -- collapse the reserved label space so it doesn't
       leave a blank gap. */
    .dt-mobile-cards td[data-label=""]::before {
        display: none;
    }

    /* Compliance list: users with delete rights get an extra bulk-select
       checkbox column that's hidden on phones (d-none d-lg-table-cell, see
       compliance/index.php) but is still the DOM's actual first <td> --
       :first-child above would otherwise style the (invisible) checkbox
       cell as the bold "title" line and leave Title itself looking like an
       ordinary labeled field. Re-point the title treatment at the real
       second column for that one case. */
    #complianceTable td:nth-child(2) {
        display: block;
        font-size: 1rem;
        font-weight: 600;
        color: #1f2733;
        margin-bottom: 0.5rem;
        padding-bottom: 0.5rem !important;
        border-bottom: 1px solid rgba(22, 41, 74, 0.06);
    }

    #complianceTable td:nth-child(2)::before {
        content: none;
    }
}

/* ---- Floating action button (Gmail compose / WhatsApp new-chat pattern) ---- */

.fab-button {
    position: fixed;
    right: 1.1rem;
    bottom: calc(70px + env(safe-area-inset-bottom, 0px) + 1rem);
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--gf-orange);
    color: #fff;
    font-size: 1.35rem;
    text-decoration: none;
    box-shadow: 0 4px 14px rgba(241, 132, 22, 0.45), 0 2px 4px rgba(22, 41, 74, 0.18);
    z-index: 1035; /* above the bottom tab bar (1030) so it never sits behind
                       it, below the offcanvas backdrop (1040)/panel (1045)
                       so the menu drawer still correctly covers it */
    transition: transform 0.12s ease, box-shadow 0.12s ease;
    overflow: hidden; /* ripple anchor */
}

.fab-button:active {
    transform: scale(0.93);
    box-shadow: 0 2px 8px rgba(241, 132, 22, 0.45);
    color: #fff;
}

/* ---- Tap ripple ----
   A touch-first affordance (see app.js's pointerdown handler, which skips
   fine-pointer/mouse devices -- their existing :hover states already cover
   this) applied to anything that's meant to feel tappable the way a native
   app's list rows and buttons do: a quick circular fade from the contact
   point rather than nothing happening until the whole page navigates. */
.btn {
    position: relative; /* ripple anchor -- Bootstrap doesn't set this on
                            .btn itself, only on a couple of its variants */
    overflow: hidden;
}

.gf-ripple {
    position: absolute;
    border-radius: 50%;
    background-color: rgba(22, 41, 74, 0.09);
    transform: scale(0);
    opacity: 1;
    pointer-events: none;
    animation: gf-ripple-out 0.5s ease-out;
}

.fab-button .gf-ripple,
.btn-primary .gf-ripple,
.bottom-nav-more .gf-ripple {
    background-color: rgba(255, 255, 255, 0.45);
}

@keyframes gf-ripple-out {
    to {
        transform: scale(1);
        opacity: 0;
    }
}

/* ---- DataTables loading state ----
   DataTables' own default centers the "Processing" indicator on the whole
   wrapper (filter row + table + pagination combined), not on the table
   body. With few rows that midpoint lands ON the header row -- exactly the
   stray orange circle sitting over "Entity" that was reported. Rather than
   centering on content whose height changes every reload, this pins a
   slim top-of-table loading bar instead: a fixed position (top edge of the
   wrapper, full width) that never depends on how many rows are showing, so
   it can't land somewhere unexpected. No spinner, no overlay text -- just
   the kind of indeterminate progress line a native app uses while a list
   refreshes. */
.dataTables_wrapper {
    position: relative;
}

.dataTables_wrapper .dataTables_processing {
    position: absolute !important;
    inset: 0 0 auto 0 !important;
    top: 0 !important;
    left: 0 !important;
    right: 0 !important;
    bottom: auto !important;
    margin: 0 !important;
    width: 100% !important;
    height: 3px !important;
    padding: 0 !important;
    border: 0;
    border-radius: 2px;
    background-color: rgba(22, 41, 74, 0.1);
    overflow: hidden;
    font-size: 0; /* the default "Processing..." text node -- the bar alone says it */
    z-index: 5;
}

.dataTables_wrapper .dataTables_processing::before {
    content: '';
    display: block;
    height: 100%;
    width: 40%;
    background-color: var(--gf-orange);
    border-radius: 2px;
    animation: gf-loading-sweep 1.1s ease-in-out infinite;
}

@keyframes gf-loading-sweep {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

@media (prefers-reduced-motion: reduce) {
    .gf-ripple {
        animation: none;
        display: none;
    }

    .dataTables_wrapper .dataTables_processing {
        background-color: rgba(22, 41, 74, 0.35);
    }

    .dataTables_wrapper .dataTables_processing::before {
        animation: none;
        width: 100%;
    }
}
