/*
 * Project overrides on top of the Thunder AI theme.
 * Mainly dark-mode polish: our Blade pages use hard-coded light Bootstrap
 * utilities (bg-white headers, table-light heads) that must follow the theme
 * surface colour when dark mode is active.
 */
[data-bs-theme="dark"] .card-header.bg-white,
[data-bs-theme="dark"] .card-footer.bg-white,
[data-bs-theme="dark"] .modal-content .bg-white {
    background-color: var(--bs-card-bg) !important;
    color: var(--bs-body-color);
}

[data-bs-theme="dark"] .table-light,
[data-bs-theme="dark"] .table-light > th,
[data-bs-theme="dark"] thead.table-light th {
    --bs-table-bg: var(--bs-card-bg);
    --bs-table-color: var(--bs-body-color);
    color: var(--bs-body-color);
}

[data-bs-theme="dark"] .text-bg-light {
    background-color: var(--bs-card-bg) !important;
    color: var(--bs-body-color) !important;
}

/* Initials avatar in the employee table keeps its primary fill in both modes. */
.avatar.bg-primary { color: #fff !important; }

/* Auth separator: "──── text ────" with a line on each side. */
.text-divider {
    display: flex;
    align-items: center;
    text-align: center;
    gap: 1rem;
}
.text-divider::before,
.text-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background-color: rgba(128, 128, 128, .4);
}

/* Plan-limit upgrade notice (Employees page) — soft branded tint rather
   than a stock warning-yellow alert, since this is a "nudge to upgrade"
   notice, not an error. */
.upgrade-notice-card {
    background: linear-gradient(135deg, color-mix(in srgb, var(--bs-primary) 8%, transparent), color-mix(in srgb, var(--bs-primary) 3%, transparent));
    border: 1px solid color-mix(in srgb, var(--bs-primary) 18%, transparent) !important;
}
.upgrade-notice-icon {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--bs-primary) 15%, transparent);
    color: var(--bs-primary);
    font-size: 1.15rem;
}
@media (max-width: 575.98px) {
    .upgrade-notice-card .card-body { text-align: center; justify-content: center; }
    .upgrade-notice-card .btn { width: 100%; }
}

/* ==========================================================================
   Premium design-system layer
   --------------------------------------------------------------------------
   Everything below is a pure visual pass on top of the existing Thunder AI
   theme + Bootstrap. Nothing here changes markup structure, routes, or
   behaviour — every admin page extends admin.layouts.app and pulls in
   Bootstrap utility classes, so token + component refinements made once
   here reach all ~40 screens automatically. Kept in `!important`-light,
   additive rules that respect whichever `--bs-primary` skin is active
   (see common.js theme-color switcher) rather than hardcoding a new brand
   colour.
   ========================================================================== */

:root {
    /* Neutral scale — calmer, less saturated than Bootstrap's stock greys,
       closer to the Linear/Vercel/Stripe "cool slate" family. */
    --surface-0: #ffffff;
    --surface-1: #f8f9fb;
    --surface-2: #f1f3f6;
    --border-subtle: #e7e9ee;
    --border-default: #dde1e8;
    --text-strong: #14161a;
    --text-default: #3c4149;
    --text-muted: #6b7280;

    /* Elevation — soft, layered shadows instead of Bootstrap's harsh default. */
    --shadow-xs: 0 1px 2px rgba(16, 24, 40, .04);
    --shadow-sm: 0 1px 3px rgba(16, 24, 40, .06), 0 1px 2px rgba(16, 24, 40, .04);
    --shadow-md: 0 4px 12px rgba(16, 24, 40, .08), 0 2px 4px rgba(16, 24, 40, .04);
    --shadow-lg: 0 12px 32px rgba(16, 24, 40, .12), 0 4px 8px rgba(16, 24, 40, .04);

    /* Motion — fast and consistent, never sluggish. */
    --ease-out: cubic-bezier(.16, 1, .3, 1);
    --dur-fast: 120ms;
    --dur-base: 180ms;
}

[data-bs-theme="dark"] {
    --surface-0: var(--bs-body-bg);
    --surface-1: color-mix(in srgb, var(--bs-body-bg) 92%, white 8%);
    --surface-2: color-mix(in srgb, var(--bs-body-bg) 85%, white 15%);
    --border-subtle: color-mix(in srgb, var(--bs-body-color) 12%, transparent);
    --border-default: color-mix(in srgb, var(--bs-body-color) 18%, transparent);
}

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

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

/* No font-family here on purpose. The theme sets it from the `font-*` class on
   <body> (styles.css: body.font-opensans etc.), and its Open Sans webfont is
   already pulled in by styles.css's own @import chain. An 'Inter' declaration
   used to sit here and quietly won, so every page rendered in Inter while the
   markup claimed Open Sans. */
body {
    color: var(--text-default);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text-strong);
}

.page-name { letter-spacing: -0.01em; }

small, .small { letter-spacing: 0; }

.text-muted { color: var(--text-muted) !important; }

/* -- Focus & accessibility -------------------------------------------- */

a, button, .btn, .form-control, .form-select, .form-check-input,
.dropdown-toggle, .nav-link, [tabindex] {
    outline-color: var(--bs-primary);
}
a:focus-visible, button:focus-visible, .btn:focus-visible,
.form-control:focus-visible, .form-select:focus-visible,
.nav-link:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

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

.btn {
    font-weight: 600;
    letter-spacing: 0;
    transition: transform var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                filter var(--dur-fast) var(--ease-out);
}
.btn:hover { filter: brightness(1.04); }
.btn:active { transform: scale(0.97); }
.btn-primary, .btn-success, .btn-danger {
    box-shadow: var(--shadow-xs);
}
.btn-primary:hover, .btn-success:hover, .btn-danger:hover {
    box-shadow: var(--shadow-sm);
}
.btn.disabled, .btn:disabled { transform: none; filter: none; }

/* -- Cards ---------------------------------------------------------------
   Existing pages already use `border-0 shadow-sm` liberally (Plan Limits,
   Billing, Organization Profile) — refine what that resolves to instead of
   changing every blade file's classes. */

.card {
    border-color: var(--border-subtle);
    box-shadow: var(--shadow-sm) !important;
    transition: box-shadow var(--dur-base) var(--ease-out);
}
.card.shadow-sm { box-shadow: var(--shadow-sm) !important; }
a.card:hover, .card[role="button"]:hover, .card-hover:hover {
    box-shadow: var(--shadow-md) !important;
    transform: translateY(-1px);
}

/* -- Badges & alerts — soft "tinted" style instead of solid saturated
   fills, the pattern used by Linear/Stripe/GitHub for status pills. ---- */

.badge {
    font-weight: 600;
    letter-spacing: .01em;
    padding: .4em .7em;
}
.badge.text-bg-primary { background-color: color-mix(in srgb, var(--bs-primary) 14%, transparent) !important; color: var(--bs-primary) !important; }
.badge.text-bg-success { background-color: color-mix(in srgb, var(--bs-success) 14%, transparent) !important; color: var(--bs-success) !important; }
.badge.text-bg-warning { background-color: color-mix(in srgb, var(--bs-warning) 22%, transparent) !important; color: #8a5a00 !important; }
.badge.text-bg-danger  { background-color: color-mix(in srgb, var(--bs-danger) 14%, transparent) !important; color: var(--bs-danger) !important; }
.badge.text-bg-secondary { background-color: var(--surface-2) !important; color: var(--text-muted) !important; }

.alert {
    border: 1px solid transparent;
    box-shadow: var(--shadow-xs);
}

/* -- Tables --------------------------------------------------------------
   Applies to every admin list page (Employees, Departments, Attendance,
   Plan Limits, etc.) since they all reuse `.table`. */

.table > thead {
    background: var(--surface-1);
}
.table > thead th {
    font-size: .72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-default);
    padding-top: .85rem;
    padding-bottom: .85rem;
}
.table > tbody > tr {
    transition: background-color var(--dur-fast) var(--ease-out);
}
.table-hover > tbody > tr:hover > * {
    background-color: var(--surface-1);
}
.table > :not(caption) > * > * { padding-top: .75rem; padding-bottom: .75rem; }

/* Clickable table rows (e.g. Organizations list) — `.cursor-pointer` was
   referenced in markup but never actually defined, so rows never showed a
   pointer cursor despite being clickable via onclick navigation. */
.cursor-pointer { cursor: pointer; }
.cursor-pointer:hover .fw-semibold { color: var(--bs-primary); }

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

.form-control, .form-select {
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}
.form-control:focus, .form-select:focus {
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--bs-primary) 18%, transparent);
}
.form-control.is-invalid:focus, .form-select.is-invalid:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--bs-danger) 18%, transparent);
}
.form-label { font-weight: 600; font-size: .875rem; color: var(--text-default); }
.form-text { color: var(--text-muted); }

/* -- Dropdown menus ------------------------------------------------------ */

.dropdown-menu {
    border-color: var(--border-subtle);
    box-shadow: var(--shadow-lg) !important;
    animation: dropdown-in var(--dur-base) var(--ease-out);
}
@keyframes dropdown-in {
    /* Opacity-only: Popper already controls this element's `transform` for
       positioning, so animating transform here fights it and causes a
       visible jerk/snap right as the animation finishes. */
    from { opacity: 0; }
    to   { opacity: 1; }
}
.dropdown-item {
    transition: background-color var(--dur-fast) var(--ease-out);
    border-radius: .5rem;
}

/* -- Modals ------------------------------------------------------------ */

.modal-content { box-shadow: var(--shadow-lg); border: none; }
.modal.fade .modal-dialog { transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out); }

/* -- Sidebar -------------------------------------------------------------
   The sidebar is the theme's, verbatim. Its appearance — 245px wide, 24px/20px
   padding, `nav-title` groups with the rotating +/× toggle, plain-text links on
   tree connectors, primary-coloured hover/active — comes entirely from
   styles.css and responsive.css. Nothing here restyles it.

   Two rounds of drift had to be undone to get back to that: an override block
   in this file (270px, connectors off, gradient pill links) and, less visibly,
   direct edits to the vendored `styles.css` itself, which had rewritten
   `.nav-sidebar .nav .nav-link` into a padded pill with a filled active state.
   If the sidebar ever stops matching the reference again, diff
   `public/theme/css/styles.css` against the template before looking here. */

/* The theme's stock width, named so the header rule below can derive from it. */
:root {
    --sidebar-width: 245px;
}

/* The header's logo column ends exactly on the sidebar's outer edge, so the
   divider under it continues the sidebar/content boundary — the same alignment
   the template gets from its hardcoded 221px logo column. The 24px is the
   header container's own `px-md-4` padding, which the sidebar doesn't have.
   (Layout only: this sets no colour, type or spacing inside the header.) */
.header .logo .brand-link {
    width: calc(var(--sidebar-width) - 24px);
    box-sizing: border-box;
}

/* Keyboard focus ring — an accessibility addition, not a restyle. */
.left-sidebar .nav-link:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
}

/* Off-canvas anchoring, tablet/iPad and below.
   ---------------------------------------------------------------------------
   The theme pins the slide-in panel to the viewport at a hardcoded
   `top: 136px` (its own header + breadcrumbs), and 104px under 768px. Punch
   Axis's header is not that height — and it grows by another row whenever the
   Super Admin impersonation banner is showing — so those constants would leave
   the panel overlapping the breadcrumbs on some screens and floating below them
   on others.

   Anchoring to .content-wrapper instead lands it flush under whatever is above
   it, on every device and with the banner up or down. Everything else about the
   panel — the -250px resting position, 245px/280px widths, 24px padding, the
   overlay and its fade — is still the theme's. */
@media (max-width: 991.98px) {
    .content-wrapper {
        position: relative;
    }

    .left-sidebar {
        position: absolute;
        top: 0;
    }
}

/* -- Header / profile dropdown ------------------------------------------ */

.header { box-shadow: var(--shadow-xs); }

/* Organization section in header */
.header .logo .border-start {
    border-left: 2px solid var(--border-subtle) !important;
    padding-left: 1.2rem !important;
    margin-left: 0.75rem !important;
    transition: all var(--dur-base) var(--ease-out);
}

.header .logo .border-start:hover {
    border-left-color: var(--bs-primary) !important;
}

.header .logo .border-start span {
    font-weight: 500;
    color: var(--text-default);
    transition: color var(--dur-base) var(--ease-out);
}

.header .logo .border-start:hover span {
    color: var(--bs-primary);
}

.header .logo .border-start img {
    transition: box-shadow var(--dur-base) var(--ease-out);
}

.header .logo .border-start:hover img {
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--bs-primary) 15%, transparent);
}

.profile-dropdown img { transition: box-shadow var(--dur-fast) var(--ease-out); }
.profile-dropdown:hover img { box-shadow: 0 0 0 3px color-mix(in srgb, var(--bs-primary) 20%, transparent); }

/* -- Billing: "Available upgrade" cards --------------------------------- */

.upgrade-option-card {
    border-color: var(--border-subtle);
    transition: box-shadow var(--dur-base) var(--ease-out),
                border-color var(--dur-base) var(--ease-out),
                transform var(--dur-base) var(--ease-out);
}
.upgrade-option-card:hover {
    border-color: color-mix(in srgb, var(--bs-primary) 35%, var(--border-subtle));
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}
.upgrade-option-icon {
    width: 44px;
    height: 44px;
    font-size: 1.1rem;
    color: var(--bs-primary);
    background: color-mix(in srgb, var(--bs-primary) 12%, transparent);
}

/* -- Layout: fixed header/sidebar, content-area scrolls independently ----
   Header, the impersonation banner, and the breadcrumbs bar stay put;
   only .content-area scrolls. The sidebar gets its own independent
   scroll too (sticky within the remaining height) so a long nav list
   doesn't push the page around.

   Desktop only (>=992px) — mobile's sidebar is a separate off-canvas
   overlay (see responsive.css, position:fixed there already), which is
   already independent of page scroll and is left untouched here. */

html, body {
    height: 100%;
}

body {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

#header,
.impersonation-banner,
.page-breadcrumbs {
    flex-shrink: 0;
}

.content-wrapper {
    flex: 1 1 auto;
    min-height: 0; /* let this flex item shrink below its content size so overflow can actually kick in */
    overflow: hidden;
}

.content-area {
    height: 100%;
    overflow-y: auto;
}

/* No desktop rule for .left-sidebar here on purpose. It used to be pinned with
   `position: sticky; height: 100%`, which is the theme's job to decide — and the
   theme's own `position: relative; overflow-y: auto` already gives the same
   result inside this shell, because the sidebar is a stretched flex child of
   .content-wrapper and so fills it and scrolls its own overflow. */

/* -- Skeleton loader primitive --------------------------------------------
   Reusable shimmer block: <div class="skeleton" style="width:120px;height:1rem"></div>
   Not yet wired into every async fetch — dropped in here so it's available
   to adopt page-by-page without another CSS pass. */

.skeleton {
    display: block;
    border-radius: .4rem;
    background: linear-gradient(90deg, var(--surface-1) 25%, var(--surface-2) 37%, var(--surface-1) 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
    0%   { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}

/* -- Empty states ----------------------------------------------------------
   Utility classes for a consistent "nothing here yet" layout: icon circle +
   message + CTA. Opt-in per page (see admin.plan-limits.index for a usage
   note) — not auto-applied so existing empty-state markup isn't touched
   sight-unseen. */

.empty-state { text-align: center; padding: 3rem 1.5rem; }
.empty-state-icon {
    width: 56px; height: 56px; margin: 0 auto 1rem;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-1);
    color: var(--text-muted);
    font-size: 1.5rem;
}
.empty-state-title { font-weight: 700; color: var(--text-strong); margin-bottom: .25rem; }
.empty-state-body { color: var(--text-muted); max-width: 32rem; margin: 0 auto 1.25rem; }

/* -- Monthly Attendance Matrix -------------------------------------------
   Every employee × every day of the month. Sticky employee column so it
   stays visible while scrolling horizontally through a full month. Kept
   to plain table markup (no per-cell JS) since a large roster × 31 days
   is already hundreds of cells — anything heavier per cell would hurt
   render performance. */

.attendance-matrix-wrapper {
    overflow-x: auto;
    border-top: 1px solid var(--border-subtle);
}
/* max-content so a full month is never squeezed — the wrapper scrolls
   instead — but floored at the wrapper's own width, so a month that *does*
   fit stretches to fill the card rather than stopping short and leaving a
   band of empty space down the right-hand side.
 *
 * The surplus lands on the day columns, which is the point: the employee
 * column and the five summary columns all pin themselves with matching
 * width/min-width/max-width, so they hold their exact size while the day
 * grid spreads out. That matters for more than looks — the summary block's
 * sticky offsets below are multiples of --att-sum-w, so those columns must
 * not grow. */
.attendance-matrix {
    border-collapse: separate;
    border-spacing: 0;
    font-size: .8rem;
    width: max-content;
    min-width: 100%;
}
.attendance-matrix th,
.attendance-matrix td {
    padding: .45rem .4rem;
    text-align: center;
    white-space: nowrap;
    border-bottom: 1px solid var(--border-subtle);
}
.attendance-matrix thead th {
    background: var(--surface-1);
    font-weight: 600;
    color: var(--text-muted);
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .03em;
    position: sticky;
    top: 0;
    z-index: 1;
}
.attendance-matrix .att-emp-col {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--surface-0);
    text-align: left;
    min-width: 180px;
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    box-shadow: 1px 0 0 var(--border-subtle);
}
.attendance-matrix thead .att-emp-col {
    z-index: 3;
    background: var(--surface-1);
}
/* The employee name links to that employee's profile, but the column is a
   roster first: it keeps the text colour it always had and only announces
   itself on hover. A column of link-coloured names would read as a list of
   buttons. */
.attendance-matrix .att-emp-link {
    color: inherit;
    text-decoration: none;
}
.attendance-matrix .att-emp-link:hover,
.attendance-matrix .att-emp-link:focus-visible {
    color: var(--bs-primary);
    text-decoration: underline;
}
.attendance-matrix .att-day-col { min-width: 38px; }
.attendance-matrix .att-day-num { font-weight: 600; }
.attendance-matrix .att-day-name { font-size: .6rem; font-weight: 500; }

/* Weekly-off columns are half the width of a working day's. Nobody was
   expected in, so the only thing those columns carry is the merged block's
   vertical "Weekly Off" label — which needs a line-height of room, not a
   day's worth — and the space that frees goes to the summary block on the
   right. The date number stays; the weekday name goes, because the block
   spanning those columns already says what they are, and "Sat" is what was
   holding the column open (min-width can't shrink a nowrap cell below its
   own content). */
.attendance-matrix .att-day-col.att-weekend {
    min-width: 19px;
    padding-left: .15rem;
    padding-right: .15rem;
}
.attendance-matrix .att-day-col.att-weekend .att-day-name { display: none; }
.attendance-matrix .att-day-col.att-weekend .att-day-num { font-size: .62rem; }

/* ---- Summary block -------------------------------------------------------
   Total / Present / Leave / Early Leave / Absent. One width for all five, so
   the figures line up under their headers, and pinned to the right edge from
   lg up: the counts are the answer the card is read for, and a full month of
   day-columns would otherwise scroll them out of sight. --att-sum-w has to
   be an exact width rather than a minimum, because the sticky offsets below
   are multiples of it; the headers wrap instead of widening their column
   ("Early Leave" is two lines, the same height the day headers already
   are). */
.attendance-matrix { --att-sum-w: 72px; }
/* Three short lines of heading ("Early Leave Taken") in the height the day
   headers already take, so naming the column properly costs the card no
   vertical space. */
.attendance-matrix thead .att-summary-col { line-height: 1.15; }
.attendance-matrix .att-summary-col {
    width: var(--att-sum-w);
    min-width: var(--att-sum-w);
    max-width: var(--att-sum-w);
    white-space: normal;
    font-variant-numeric: tabular-nums;
}

@media (min-width: 992px) {
    .attendance-matrix .att-summary-col {
        position: sticky;
        z-index: 2;
        background: var(--surface-0);
    }
    .attendance-matrix thead .att-summary-col {
        z-index: 3;
        background: var(--surface-1);
    }
    /* Offsets counted from the END of the row, so they stay correct if one of
       the commented-out summary columns (%, Holiday) is put back — the block
       simply grows leftwards. */
    .attendance-matrix .att-summary-col:nth-last-child(1) { right: 0; }
    .attendance-matrix .att-summary-col:nth-last-child(2) { right: var(--att-sum-w); }
    .attendance-matrix .att-summary-col:nth-last-child(3) { right: calc(var(--att-sum-w) * 2); }
    .attendance-matrix .att-summary-col:nth-last-child(4) { right: calc(var(--att-sum-w) * 3); }
    .attendance-matrix .att-summary-col:nth-last-child(5) { right: calc(var(--att-sum-w) * 4); }
    .attendance-matrix .att-summary-col:nth-last-child(6) { right: calc(var(--att-sum-w) * 5); }
    .attendance-matrix .att-summary-col:nth-last-child(7) { right: calc(var(--att-sum-w) * 6); }
}

/* Each count gets its own column rule, so Total / Present / Early Leave Taken
   / Absent read as separate figures rather than one run of numbers. Drawn as
   a box-shadow, not a border: the sticky offsets are exact multiples of
   --att-sum-w, and a border would add width to every column and walk the
   block off the right edge. Drawn at every width, not just where the block is
   pinned, and on thead too so the heading sits over its own column. */
.attendance-matrix .att-summary-col { box-shadow: inset 1px 0 0 var(--border-subtle); }

/* Where the day grid ends and the counts begin — the mirror of the employee
   column's right edge. Outset, so the line lands between the last day and the
   first count rather than inside the count's own cell. */
.attendance-matrix .att-summary-col--divider { box-shadow: -1px 0 0 var(--border-subtle); }

/* Below lg the pinned block would eat the viewport — 5 columns plus the
   sticky employee column is most of a phone's width — so it scrolls with the
   table there, exactly as it did before. The narrower weekly-off columns
   still apply, which is the part that helps a small screen most. */
.attendance-matrix .att-today { background: color-mix(in srgb, var(--bs-primary) 8%, transparent); }
.attendance-matrix .att-weekend:not(.att-today) { background: var(--surface-1); }
.attendance-matrix tbody tr:hover td:not(.att-emp-col) { background: var(--surface-1); }
.attendance-matrix tbody tr:hover td.att-emp-col { background: var(--surface-1); }

/* Collapsed weekend block: consecutive Sat/Sun day-columns merge into one
   cell (colspan across the run's dates, rowspan down every employee row)
   with a centered vertical "Weekend" label, instead of repeating the word
   in every row × every weekend column. */
.attendance-matrix .att-weekend-block {
    background: var(--surface-1);
    border-left: 1px dashed var(--border-subtle);
    border-right: 1px dashed var(--border-subtle);
    vertical-align: middle;
}
.attendance-matrix tbody tr:hover td.att-weekend-block { background: var(--surface-1); }
.att-weekend-label {
    writing-mode: sideways-lr;
    text-orientation: mixed;
    display: inline-block;
    color: var(--text-muted);
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    white-space: nowrap;
}

.att-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 26px;
    height: 22px;
    padding: 0 .25rem;
    border-radius: .4rem;
    font-size: .66rem;
    font-weight: 700;
}
/* A `WK` cell carries `h:mm` instead of a two-letter code (the hours the day
   has completed so far), and four or five characters don't fit a badge sized
   for two. Tighter tracking and a slightly smaller face keep it inside the
   38px day column; min-width still holds the badge's shape. */
.att-badge--hm {
    font-size: .6rem;
    letter-spacing: -.01em;
    font-variant-numeric: tabular-nums;
}

/* Early Leave marker on a Monthly Attendance cell.
 *
 * Leaving early is recorded on `check_out_status`, independently of the
 * verdict the day was graded with, so it can't be a badge of its own without
 * displacing the code the cell exists to show. It rides on the badge's corner
 * instead: absolutely positioned, so it adds nothing to the flow and the
 * badge's own centring, sizing and tint are untouched — a cell without one
 * renders exactly as it did before.
 *
 * The ring is the cell's own background, not white: it separates the dot from
 * whichever tint sits underneath it in either theme. It sits inside the day
 * column's padding (26px badge in a 38px column), so nothing is clipped and no
 * column widens. Yellow is the same warning colour the Early Leave summary
 * column is printed in. */
.att-badge { position: relative; }
.att-early-dot {
    position: absolute;
    top: -2px;
    right: -3px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--bs-warning, #ffc107);
    box-shadow: 0 0 0 1.5px var(--surface-0, #fff);
    pointer-events: auto;
}
/* The legend's specimen is a badge nobody hovers; it only has to show the
   mark's position. */
.att-legend-early { overflow: visible; }

/* Hover tooltip on a matrix cell — the day's verdict, then its first punch in
   and last punch out on their own lines.
 *
 * The whole string is the badge's `title` attribute (so the tooltip still says
 * everything with JavaScript off, exactly as it did before); Bootstrap renders
 * that string verbatim, which means the line breaks only survive with
 * `pre-line`. Left-aligned for the same reason — two labelled times centred
 * under a heading read as a poem.
 *
 * Applied via `data-bs-custom-class`, so it changes nothing about any other
 * tooltip in the app. */
.att-cell-tip .tooltip-inner {
    white-space: pre-line;
    text-align: left;
    max-width: 220px;
}

/* Status badge tints. These pairs (base colour + mix percentage) are mirrored
   in AttendanceMatrixService::STATUS_META, which flattens them to concrete hex
   for the PDF and Excel exports — neither can run color-mix(). Change one and
   change the other, or a download stops matching the screen. */
.att-present { background: color-mix(in srgb, var(--bs-success) 18%, transparent); color: var(--bs-success); }
.att-late { background: color-mix(in srgb, var(--bs-warning) 26%, transparent); color: #8a5a00; }
.att-halfday { background: color-mix(in srgb, #0dcaf0 22%, transparent); color: #087990; }
.att-working { background: color-mix(in srgb, var(--bs-primary) 18%, transparent); color: var(--bs-primary); }
.att-pending-review { background: color-mix(in srgb, var(--bs-danger) 26%, transparent); color: var(--bs-danger); }
.att-leave { background: color-mix(in srgb, #6f42c1 18%, transparent); color: #6f42c1; }
.att-holiday { background: color-mix(in srgb, var(--bs-primary) 16%, transparent); color: var(--bs-primary); }
.att-absent { background: color-mix(in srgb, var(--bs-danger) 16%, transparent); color: var(--bs-danger); }
.att-shortfall { background: color-mix(in srgb, var(--bs-warning) 18%, transparent); color: #8a5a00; }
/* ─────────────────────────────────────────────────────────────────────────────
 * Template component styles
 *
 * The theme styles list tables through per-<th> utility classes
 * (`py-2 fw-medium small text-uppercase`, repeated on every heading in the HTML
 * reference). Encoding it once here means a column added later cannot come out
 * in the wrong style, and our Blade tables stay free of presentational noise.
 * ───────────────────────────────────────────────────────────────────────────── */
.content-area .table > thead > tr > th,
.modal .table > thead > tr > th {
    padding-top: .5rem;
    padding-bottom: .5rem;
    font-size: .8125rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .02em;
    color: var(--text-muted, var(--bs-secondary-color));
    white-space: nowrap;
}

/* Row actions: borderless icon buttons that sit quietly until hovered, so the
 * page's primary action stays the only thing competing for attention. */
.table .btn-link {
    color: var(--bs-secondary-color);
    text-decoration: none;
    line-height: 1;
}
.table .btn-link:hover { color: var(--bs-primary); }
.table .btn-link.text-danger:hover { color: var(--bs-danger) !important; }
.table .btn-link:disabled { opacity: .4; }
.table .btn-link .bi { font-size: 1rem; vertical-align: middle; }

/* Filter panel above a list (the template's `border-bg` block). Its background
 * is the theme's border colour, which is the intended subtle tint. */
.border-bg { background: var(--bs-border-color); }

/* DataTables' own controls, aligned with the theme's form controls rather than
 * the plugin's stock look. */
.dataTables_wrapper .dataTables_filter input,
.dataTables_wrapper .dataTables_length select {
    border: 1px solid var(--bs-border-color);
    border-radius: .5rem;
    padding: .35rem .75rem;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
}
.dataTables_wrapper .dataTables_filter input:focus,
.dataTables_wrapper .dataTables_length select:focus {
    outline: 0;
    border-color: var(--bs-primary);
}
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter { color: var(--bs-secondary-color); font-size: .875rem; }

/* Cards holding a full-bleed table: the table's own borders provide the
 * separation, so the header sheds its bottom rule. */
.card > .card-header + .table-responsive > .table > thead > tr > th { border-top: 0; }

/* Grouped list rows (the attendance log's day headers and their column
 * headings). They live inside <tbody>, so they can't inherit the <thead> rule
 * above — and they were previously Bootstrap's `table-light`, which needed its
 * own dark-mode patch and never matched the theme's own tables. */
.table > tbody > tr.table-group-row > td {
    background: var(--bs-tertiary-bg);
    border-top: 1px solid var(--bs-border-color);
    /* The group row spans every column, so its content aligns to the table's
       own left edge — flush with the rows below it, never inheriting a
       centred alignment from a surrounding utility class. */
    text-align: left;
}
.table > tbody > tr.table-group-row:hover > td { background: var(--bs-secondary-bg); }

.table > tbody > tr.table-subhead-row > th {
    padding-top: .5rem;
    padding-bottom: .5rem;
    font-size: .8125rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .02em;
    color: var(--text-muted, var(--bs-secondary-color));
    white-space: nowrap;
    background: transparent;
}

/* ==========================================================================
   Work timer — the employee dashboard's live "remaining working time".
   --------------------------------------------------------------------------
   Deliberately quiet: one large tabular figure and a line of explanation. It
   owns a column of the dashboard's Today card — it is the only thing on that
   screen that moves, and the reason most people open it — with the punch
   times and status beside it in the same block, since app-ui.js refreshes all
   of them from the one poll that card owns.
   ========================================================================== */

/* The ring: an SVG circle whose stroke is dashed at exactly its own
   circumference, so shifting the dash offset sweeps the arc. The face is
   layered over it rather than placed inside the SVG — real text reflows and
   inherits theme colours, where <text> in an SVG does neither. */
.work-ring {
    position: relative;
    width: 6.25rem;
    height: 6.25rem;
    margin: 0 auto;
}
/* On the Today card the ring gets a column to itself, so it is drawn large
   enough to read across a room rather than at the size of an inline figure. */
.work-ring--lg {
    width: 8.5rem;
    height: 8.5rem;
}
.work-ring--lg .work-timer-value { font-size: 1.4rem; }
.work-ring--lg .work-timer-seconds { font-size: .78rem; }

.work-ring svg {
    width: 100%;
    height: 100%;
    /* Start the arc at 12 o'clock instead of 3. */
    transform: rotate(-90deg);
}
.work-ring circle {
    fill: none;
    stroke-width: 8;
    stroke-linecap: round;
}
.work-ring-track { stroke: var(--bs-secondary-bg); }
.work-ring-bar {
    /* Painted in whichever `text-*` tone the state carries, so the arc, the
       figure inside it and the state dot below always agree. */
    stroke: currentColor;
    transition: stroke-dashoffset var(--dur-base) var(--ease-out);
}

.work-ring-face {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    line-height: 1.1;
}

.work-timer-value {
    font-size: 1.05rem;
    font-weight: 700;
    /* Tabular figures so a ticking countdown doesn't shuffle its own width
       once a second — the single most distracting thing a live number can do. */
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

/* The seconds, kept small and muted: they prove the figure is live without
   pulling the eye away from the hours and minutes that actually matter. */
.work-timer-seconds {
    font-size: .7rem;
    font-weight: 600;
    color: var(--text-muted, var(--bs-secondary-color));
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    min-height: .85rem;
}

/* The status dot next to the heading. Only the running state animates, and
   only gently; a paused or finished timer has nothing to report. */
.work-timer-dot {
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--bs-secondary-color);
    flex: 0 0 auto;
}
.work-timer--running .work-timer-dot { background: var(--bs-primary); animation: work-timer-pulse 2s var(--ease-out) infinite; }
.work-timer--paused .work-timer-dot { background: var(--bs-warning); }
.work-timer--complete .work-timer-dot { background: var(--bs-success); }
.work-timer--closed .work-timer-dot,
.work-timer--pending_review .work-timer-dot { background: var(--bs-danger); }

@keyframes work-timer-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .35; }
}

/* Respect a reduced-motion preference: the dot still colours the state, it
   just stops moving. */
@media (prefers-reduced-motion: reduce) {
    .work-timer--running .work-timer-dot { animation: none; }
    .work-ring-bar { transition: none; }
}

/* ==========================================================================
   Employee profile images
   --------------------------------------------------------------------------
   Two pieces: `.app-avatar` (the <x-ui.avatar> component, used on every
   surface that shows a face) and `.profile-image-field` (the uploader in the
   Add/Edit Employee forms). Both size themselves from an inline width/height
   so one rule serves a 28px table avatar and a 96px profile header alike.
   ========================================================================== */

.app-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    overflow: hidden;
    border-radius: 50%;
    font-weight: 600;
    line-height: 1;
    /* The photo is a square JPEG, so a circular mask is all that's needed —
       but a name-initials fallback still has to be centred in the same disc. */
    background: var(--bs-primary);
    color: #fff;
    user-select: none;
}

.app-avatar img {
    width: 100%;
    height: 100%;
    /* `cover` rather than `contain`: the stored file is already centre-cropped
       square, and cover keeps a non-square legacy file from letterboxing. */
    object-fit: cover;
    display: block;
}

.app-avatar--light-blue { background: var(--bs-light-blue, #dbeafe); color: #14161a; }
.app-avatar--light-purple { background: var(--bs-light-purple, #ede9fe); color: #14161a; }

/* -- The uploader in the Add / Edit Employee forms --------------------- */

.profile-image-field {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

/* Sits behind the avatar preview so a photo that hasn't loaded (or an empty
   slot) still reads as a deliberate frame rather than a gap. */
.profile-image-preview {
    border: 2px solid var(--border-subtle, var(--bs-border-color));
    background: var(--surface-2, #f1f3f6);
}

.profile-image-actions {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    min-width: 12rem;
    flex: 1 1 14rem;
}

/* The real <input type="file"> stays in the layout (so validation styling and
   keyboard focus behave normally) but compact, next to the preview. */
.profile-image-field input[type="file"] {
    font-size: .85rem;
}

.profile-image-hint {
    font-size: .78rem;
    color: var(--text-muted, var(--bs-secondary-color));
}

/* "Remove photo" is only meaningful once there is one; JS toggles this. */
.profile-image-remove[hidden] { display: none !important; }

/* ==========================================================================
   Employee Profile page
   --------------------------------------------------------------------------
   Support for the profile components in resources/views/components/profile/,
   which reproduce the Thunder AI "Employee Profile" screen. The look itself
   comes from the vendored theme (`.border-bg`, `.image-input`, `.bg-light-*`,
   `.nav-pills`, timeline.css); only the few things the kit assumes but never
   actually ships live here.
   ========================================================================== */

/* A flex child will not shrink below its content by default, so a long email
   in a profile field or timeline entry pushes the row wider than its column
   instead of truncating. Both components rely on this. */
.profile-field,
.timeline-item .min-w-0 {
    min-width: 0;
}

.profile-field > p {
    /* Long values (an email, a designation) wrap rather than overflow the
       card they sit in — these cards are narrow at lg and below. */
    overflow-wrap: anywhere;
}

/* The framed avatar in the profile header. The theme's `.image-input` only
   handles background-image sizing, because the kit puts a CSS background
   there; ours holds a real <img> (or the initials disc), so it needs to be a
   box of its own. */
.image-input:has(> .app-avatar) {
    line-height: 0;
}

/* The tab strip's decorative connector hangs below the bar; without this the
   strip's own stacking context can clip it on some browsers. */
.nav-pills + .tab-content,
.border-bg > .nav-pills {
    position: relative;
}

/* Timeline entries carry a card of their own on wide screens; keep the gap
   from doubling up with the list's own bottom padding on the last one. */
.timeline-item:last-child {
    padding-bottom: 1em;
}

/* The tinted pill badges the template uses for roles and statuses read as
   washed-out on dark. Lift the text so it keeps its contrast. */
[data-bs-theme="dark"] .badge.bg-opacity-25 {
    --bs-bg-opacity: 0.28;
}

/* ==========================================================================
   Attendance Log
   --------------------------------------------------------------------------
   The month-at-a-time listing: a toolbar carrying the month picker, a row of
   stat tiles, and one collapsible row per calendar day.

   Naming note — everything here is `.att-toolbar` / `.att-month-*` /
   `.att-day-*` / `.att-stat*` / `.att-help*`. The Monthly Attendance Matrix
   further up already owns the bare `.att-present`, `.att-late`, `.att-absent`
   … tints and they are NOT scoped to `.attendance-matrix`, so nothing below
   may use those names. The two sets are also deliberately independent: the
   matrix's badges grade one employee on one day and must keep matching
   AttendanceMatrixService::STATUS_META for the PDF/Excel exports, whereas
   these grade a whole day across the roster and reach no export at all.
   ========================================================================== */

/* -- Toolbar ------------------------------------------------------------ */

.att-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid var(--border-subtle);
}

/* Three tracks so the month picker sits optically centred rather than
   wherever the title's length happens to leave it. */
@media (min-width: 1200px) {
    .att-toolbar {
        display: grid;
        grid-template-columns: 1fr auto 1fr;
    }
    .att-toolbar-actions { justify-self: end; }
}

.att-toolbar-title { margin-right: auto; }

.att-toolbar-actions {
    display: flex;
    align-items: center;
    gap: .6rem;
    margin-bottom: 0;
}

.att-status-select {
    min-width: 11.5rem;
    border-radius: .6rem;
}

.att-export-btn {
    white-space: nowrap;
    border-radius: .6rem;
}

/* -- Month navigation ----------------------------------------------------
   Shared: <x-ui.month-picker> renders this on the Attendance Log toolbar and
   in the dashboard's Monthly Attendance card header. The `--sm` modifier is
   for the latter, where it sits in a row of small buttons. */

.att-month-nav {
    display: flex;
    align-items: center;
    gap: .4rem;
}

.att-month-nav--sm .att-month-arrow {
    width: 1.95rem;
    height: 1.95rem;
    font-size: .8rem;
}
.att-month-nav--sm .att-month-btn {
    min-width: 10.5rem;
    padding: .3rem .65rem;
    font-size: .8125rem;
}

.att-month-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.35rem;
    height: 2.35rem;
    border-radius: .6rem;
    border: 1px solid var(--border-default);
    background: var(--surface-0);
    color: var(--text-default);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}
.att-month-arrow:hover {
    background: var(--surface-1);
    border-color: color-mix(in srgb, var(--bs-primary) 35%, var(--border-default));
    color: var(--bs-primary);
}
/* Kept in the layout rather than removed, so the picker doesn't shift
   sideways on the last month of history. */
.att-month-arrow.is-disabled {
    opacity: .35;
    pointer-events: none;
}

.att-month-btn {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    min-width: 13rem;
    padding: .5rem .85rem;
    border-radius: .6rem;
    border: 1px solid var(--border-default);
    background: var(--surface-0);
    color: var(--text-strong);
    font-weight: 600;
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}
.att-month-btn:hover,
.att-month-btn[aria-expanded="true"] {
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--bs-primary) 14%, transparent);
}
.att-month-btn-icon { color: var(--bs-primary); }
.att-month-btn-label { flex: 1 1 auto; text-align: left; }
.att-month-btn-caret {
    font-size: .75rem;
    color: var(--text-muted);
    transition: transform var(--dur-fast) var(--ease-out);
}
.att-month-btn[aria-expanded="true"] .att-month-btn-caret { transform: rotate(180deg); }

/* -- Month dropdown ----------------------------------------------------- */

.att-month-menu {
    width: 20rem;
    max-width: calc(100vw - 2rem);
    padding: .75rem;
    border-radius: .9rem;
}

.att-month-search {
    position: relative;
    margin-bottom: .6rem;
}
.att-month-search .form-control {
    padding-right: 2.2rem;
    border-radius: .6rem;
}
/* The magnifier is decorative — the input is already labelled — so it sits
   behind pointer events rather than stealing clicks meant for the field. */
.att-month-search .bi-search {
    position: absolute;
    top: 50%;
    right: .75rem;
    transform: translateY(-50%);
    color: var(--text-muted);
    font-size: .85rem;
    pointer-events: none;
}

.att-month-heading {
    margin: .25rem .25rem .35rem;
    font-size: .78rem;
    font-weight: 700;
    color: var(--text-strong);
}

.att-month-list {
    max-height: 17rem;
    overflow-y: auto;
}

.att-month-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .5rem .55rem;
    border-radius: .5rem;
    text-decoration: none;
    color: var(--text-default);
    font-size: .875rem;
    transition: background-color var(--dur-fast) var(--ease-out);
}
.att-month-option:hover { background: var(--surface-1); color: var(--text-strong); }
.att-month-option.is-selected {
    background: color-mix(in srgb, var(--bs-primary) 10%, transparent);
    color: var(--bs-primary);
    font-weight: 600;
}
/* The JS filter toggles `hidden`; these are flex items, so the attribute
   needs help to win. */
.att-month-option[hidden],
.att-month-all[hidden],
.att-month-empty[hidden] { display: none !important; }

.att-month-option-label { white-space: nowrap; }

.att-month-tag {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: .75rem;
    font-weight: 600;
    white-space: nowrap;
}
.att-month-tag .bi { font-size: .6rem; }
.att-month-tag--current { color: var(--bs-primary); }
.att-month-tag--done { color: var(--bs-success); }

.att-month-empty {
    margin: 0;
    padding: .75rem .55rem;
    font-size: .82rem;
    color: var(--text-muted);
}

.att-month-all {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    width: 100%;
    margin-top: .5rem;
    padding: .55rem;
    border: 0;
    border-top: 1px solid var(--border-subtle);
    border-radius: 0 0 .5rem .5rem;
    background: transparent;
    color: var(--bs-primary);
    font-size: .82rem;
    font-weight: 600;
}
.att-month-all:hover { background: var(--surface-1); }

/* Why the list stops where it does. Sits below "View all months" so it reads
   as the end of the range rather than a warning about it. */
.att-month-floor {
    display: flex;
    align-items: flex-start;
    gap: .4rem;
    margin: .5rem 0 0;
    padding: .5rem .55rem 0;
    border-top: 1px solid var(--border-subtle);
    font-size: .72rem;
    line-height: 1.45;
    color: var(--text-muted);
}
.att-month-floor .bi { color: var(--bs-primary); margin-top: .1rem; }

/* -- Stat tiles ---------------------------------------------------------- */

.att-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: .85rem;
    padding: 1.25rem 1.5rem;
}

.att-stat {
    display: flex;
    align-items: flex-start;
    gap: .8rem;
    padding: 1rem;
    border-radius: .85rem;
    border: 1px solid var(--border-subtle);
    background: var(--surface-0);
}

.att-stat-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: .7rem;
    font-size: 1.05rem;
}

.att-stat-value {
    margin: 0;
    font-size: 1.45rem;
    font-weight: 700;
    line-height: 1.15;
    color: var(--text-strong);
    /* A figure that changes between months shouldn't change the tile's
       width with it. */
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}
.att-stat-label {
    margin: .1rem 0 0;
    font-size: .82rem;
    font-weight: 600;
    color: var(--text-default);
}
.att-stat-caption {
    margin: 0;
    font-size: .72rem;
    color: var(--text-muted);
}

/* Fixed hues rather than `--bs-primary` derivatives: this row reads as a set
   of five distinct measures, and it must keep doing so whichever skin the
   theme-colour switcher is on. */
.att-stat--emerald .att-stat-icon { background: color-mix(in srgb, #10b981 15%, transparent); color: #047857; }
.att-stat--green   .att-stat-icon { background: color-mix(in srgb, #22c55e 15%, transparent); color: #15803d; }
.att-stat--amber   .att-stat-icon { background: color-mix(in srgb, #f59e0b 18%, transparent); color: #b45309; }
.att-stat--rose    .att-stat-icon { background: color-mix(in srgb, #f43f5e 15%, transparent); color: #be123c; }
.att-stat--violet  .att-stat-icon { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #6d28d9; }

/* -- Day rows ------------------------------------------------------------ */

.att-days { border-top: 1px solid var(--border-subtle); }

.att-day { border-bottom: 1px solid var(--border-subtle); }
/* "Load More" unfolds these; the whole month is always in the DOM. */
.att-day--folded { display: none; }

/* A <button> so the row is keyboard-operable, reset back to a plain block so
   nothing of the button look survives. */
.att-day-head {
    display: grid;
    /* date · weekday · status chips (elastic) · turnout · chevron */
    grid-template-columns: 10.5rem 8rem 1fr auto auto;
    align-items: center;
    gap: .75rem;
    width: 100%;
    padding: .85rem 1.5rem;
    border: 0;
    background: transparent;
    font: inherit;
    text-align: left;
    text-decoration: none;
    color: var(--text-default);
    transition: background-color var(--dur-fast) var(--ease-out);
}
.att-day-head:hover { background: var(--surface-1); color: var(--text-default); }
.att-day-head:focus-visible { outline-offset: -2px; }

.att-day--today > .att-day-head {
    background: color-mix(in srgb, var(--bs-primary) 5%, transparent);
    box-shadow: inset 3px 0 0 var(--bs-primary);
}

.att-day-date {
    font-weight: 700;
    color: var(--text-strong);
    white-space: nowrap;
}

.att-day-today {
    display: inline-block;
    margin-left: .4rem;
    padding: .1rem .4rem;
    border-radius: .35rem;
    background: color-mix(in srgb, var(--bs-primary) 14%, transparent);
    color: var(--bs-primary);
    font-size: .65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    vertical-align: middle;
}

.att-day-weekday {
    color: var(--text-muted);
    font-size: .875rem;
    white-space: nowrap;
}

/* Status tallies for the day — "4 Absent", "1 Half Day". Variable width by
   nature, so no min-width: the row is quiet on a day with nothing to flag. */
.att-day-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem;
}

.att-day-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: .3rem .65rem;
    border-radius: .5rem;
    font-size: .75rem;
    font-weight: 700;
    white-space: nowrap;
}

/* Day-level tints. Independent of the matrix's `.att-present` family on
   purpose (see the header note): these summarise a whole day across the
   roster and feed no export, so they follow the screen design rather than
   STATUS_META. */
.att-day-badge--present    { background: color-mix(in srgb, #22c55e 16%, transparent); color: #15803d; }
.att-day-badge--late       { background: color-mix(in srgb, #f59e0b 22%, transparent); color: #b45309; }
.att-day-badge--half-day   { background: color-mix(in srgb, #f59e0b 20%, transparent); color: #b45309; }
.att-day-badge--weekly-off { background: color-mix(in srgb, #fb7185 18%, transparent); color: #c2410c; }
.att-day-badge--holiday    { background: color-mix(in srgb, #8b5cf6 16%, transparent); color: #6d28d9; }
.att-day-badge--leave      { background: color-mix(in srgb, #6f42c1 15%, transparent); color: #6f42c1; }
.att-day-badge--working    { background: color-mix(in srgb, var(--bs-primary) 15%, transparent); color: var(--bs-primary); }
.att-day-badge--pending    { background: color-mix(in srgb, #ef4444 18%, transparent); color: #b91c1c; }
.att-day-badge--absent     { background: color-mix(in srgb, #ef4444 15%, transparent); color: #b91c1c; }

.att-day-count {
    justify-self: end;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-size: .85rem;
    font-weight: 600;
    color: var(--text-default);
    white-space: nowrap;
}
.att-day-count .bi { color: var(--text-muted); }

.att-day-chevron {
    color: var(--text-muted);
    font-size: .9rem;
    transition: transform var(--dur-fast) var(--ease-out);
}
.att-day-head:not(.collapsed) .att-day-chevron { transform: rotate(90deg); }

.att-day-body {
    padding: 0 1.5rem 1rem;
    background: var(--surface-1);
}
.att-day-body .att-day-table {
    background: var(--surface-0);
    border-radius: .6rem;
    overflow: hidden;
}

.att-day-empty {
    padding: .9rem 0 .25rem;
    font-size: .85rem;
    color: var(--text-muted);
}

/* Narrow screens: the five-track grid stops being readable well before it
   stops fitting, so it becomes a wrapping stack with the chevron pinned. */
@media (max-width: 991.98px) {
    .att-day-head {
        grid-template-columns: 1fr auto;
        row-gap: .35rem;
    }
    .att-day-date { grid-column: 1; }
    .att-day-chevron { grid-row: 1; grid-column: 2; justify-self: end; }
    .att-day-weekday,
    .att-day-chips,
    .att-day-count { grid-column: 1 / -1; justify-self: start; }
    .att-day-body { padding-inline: 1rem; }
    .att-toolbar,
    .att-stats { padding-inline: 1rem; }
}

/* -- Load more + footnote ------------------------------------------------ */

.att-load-more {
    display: flex;
    justify-content: center;
    padding: .35rem 0;
    border-bottom: 1px solid var(--border-subtle);
}
.att-load-more .btn-link {
    font-weight: 600;
    font-size: .875rem;
    text-decoration: none;
}

.att-footnote {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .9rem 1.5rem;
}
.att-footnote-text {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    font-size: .8rem;
    color: var(--text-muted);
}
.att-footnote-text .bi { color: var(--bs-primary); }

/* -- "How it works" panel ------------------------------------------------ */

.att-help {
    display: grid;
    grid-template-columns: auto repeat(3, 1fr);
    gap: 1.5rem;
    padding: 1.35rem 1.5rem;
    border: 1px solid var(--border-subtle);
    border-radius: 1rem;
    background: color-mix(in srgb, var(--bs-primary) 4%, var(--surface-0));
}
@media (max-width: 991.98px) {
    .att-help { grid-template-columns: 1fr; gap: 1rem; }
}

.att-help-lead {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-weight: 700;
    color: var(--bs-primary);
    line-height: 1.25;
}
.att-help-lead .bi { font-size: 1.1rem; }

.att-help-item {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
}

.att-help-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.35rem;
    height: 2.35rem;
    border-radius: .65rem;
    font-size: .95rem;
}
.att-help-icon--green  { background: color-mix(in srgb, #22c55e 15%, transparent); color: #15803d; }
.att-help-icon--violet { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #6d28d9; }
.att-help-icon--teal   { background: color-mix(in srgb, #14b8a6 15%, transparent); color: #0f766e; }

.att-help-title {
    margin: 0 0 .15rem;
    font-size: .875rem;
    font-weight: 700;
    color: var(--text-strong);
}
.att-help-body {
    margin: 0;
    font-size: .78rem;
    line-height: 1.5;
    color: var(--text-muted);
}

/* -- Dark mode -----------------------------------------------------------
   The tints above are already translucent so they sit correctly on a dark
   surface; only the opaque chrome needs re-pointing at the theme's own
   surface variables, and the badge text needs lifting off its own tint. */

[data-bs-theme="dark"] .att-month-arrow,
[data-bs-theme="dark"] .att-month-btn,
[data-bs-theme="dark"] .att-stat {
    background: var(--bs-card-bg);
    color: var(--bs-body-color);
    border-color: var(--border-default);
}
[data-bs-theme="dark"] .att-day-body { background: color-mix(in srgb, var(--bs-body-bg) 60%, var(--bs-card-bg)); }
[data-bs-theme="dark"] .att-day-body .att-day-table { background: var(--bs-card-bg); }
[data-bs-theme="dark"] .att-help { background: color-mix(in srgb, var(--bs-primary) 8%, var(--bs-card-bg)); }
[data-bs-theme="dark"] .att-stat-value,
[data-bs-theme="dark"] .att-day-date,
[data-bs-theme="dark"] .att-help-title,
[data-bs-theme="dark"] .att-month-heading { color: var(--bs-body-color); }

[data-bs-theme="dark"] .att-day-badge--present    { background: color-mix(in srgb, #22c55e 24%, transparent); color: #86efac; }
[data-bs-theme="dark"] .att-day-badge--late,
[data-bs-theme="dark"] .att-day-badge--half-day   { background: color-mix(in srgb, #f59e0b 26%, transparent); color: #fcd34d; }
[data-bs-theme="dark"] .att-day-badge--weekly-off { background: color-mix(in srgb, #fb7185 26%, transparent); color: #fda4af; }
[data-bs-theme="dark"] .att-day-badge--holiday,
[data-bs-theme="dark"] .att-day-badge--leave      { background: color-mix(in srgb, #8b5cf6 26%, transparent); color: #c4b5fd; }
[data-bs-theme="dark"] .att-day-badge--pending,
[data-bs-theme="dark"] .att-day-badge--absent     { background: color-mix(in srgb, #ef4444 26%, transparent); color: #fca5a5; }

[data-bs-theme="dark"] .att-stat--emerald .att-stat-icon { color: #6ee7b7; }
[data-bs-theme="dark"] .att-stat--green   .att-stat-icon { color: #86efac; }
[data-bs-theme="dark"] .att-stat--amber   .att-stat-icon { color: #fcd34d; }
[data-bs-theme="dark"] .att-stat--rose    .att-stat-icon { color: #fda4af; }
[data-bs-theme="dark"] .att-stat--violet  .att-stat-icon { color: #c4b5fd; }
[data-bs-theme="dark"] .att-help-icon--green  { color: #86efac; }
[data-bs-theme="dark"] .att-help-icon--violet { color: #c4b5fd; }
[data-bs-theme="dark"] .att-help-icon--teal   { color: #5eead4; }

/* ─────────────────────────────────────────────────────────────────────────────
 * Employee attendance calendar
 *
 * The Export dialog on the attendance detail screen: one employee's range as
 * month grids, a cell per day carrying its status badge, first in, last out
 * and worked hours. Built by EmployeeAttendanceCalendarService.
 *
 * The badges here are painted with INLINE colours the service hands the view
 * (`fill` + `ink`), not by class, so the modal and the PDF it downloads are
 * identical by construction rather than by two files agreeing. Only the cell
 * chrome below is themed, which is why it survives dark mode while the badges
 * stay light-tinted with dark ink — legible on either ground.
 *
 * The PDF renders the same data as a <table>: dompdf has no CSS grid.
 * ───────────────────────────────────────────────────────────────────────── */
.emp-cal-month + .emp-cal-month { margin-top: 1.5rem; }

.emp-cal-title {
    font-size: .95rem;
    font-weight: 700;
    margin-bottom: .5rem;
}

.emp-cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: .375rem;
}

.emp-cal-head {
    font-size: .68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    text-align: center;
    padding: .25rem 0;
    color: var(--bs-secondary-color, #6c757d);
}

.emp-cal-cell {
    min-height: 5.75rem;
    padding: .375rem .4rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .5rem;
    background: var(--bs-card-bg, #fff);
    font-size: .7rem;
    line-height: 1.35;
}
.emp-cal-cell--empty { border-style: dashed; opacity: .35; }
.emp-cal-cell--off { background: var(--bs-tertiary-bg, #f8f9fa); }
.emp-cal-cell--today { outline: 2px solid var(--bs-primary); outline-offset: -1px; }

.emp-cal-cell__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .25rem;
    margin-bottom: .25rem;
}
.emp-cal-cell__day { font-size: .8rem; font-weight: 700; }
.emp-cal-cell__weekday { display: none; font-size: .68rem; color: var(--bs-secondary-color, #6c757d); }

/* Status badge and flag chip share one shape; both are coloured inline. */
.emp-cal-badge,
.emp-cal-flag {
    display: inline-block;
    max-width: 100%;
    padding: .05rem .3rem;
    border-radius: .3rem;
    font-size: .62rem;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.emp-cal-flag { font-weight: 600; margin: .15rem .15rem 0 0; }

/* One row carrying both punches — "In: 09:44   Out: 19:19" — matching the
   PDF, which has no room for two (see EmployeeAttendanceCalendarService::
   TIME_FORMAT). It wraps to two lines rather than clipping if a cell is ever
   narrower than the pair, which on this grid only happens well below the
   phone breakpoint. */
.emp-cal-time {
    display: flex;
    flex-wrap: wrap;
    gap: .1rem .5rem;
    font-variant-numeric: tabular-nums;
}
.emp-cal-time__label { color: var(--bs-secondary-color, #6c757d); font-weight: 600; }
.emp-cal-worked { margin-top: .15rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.emp-cal-note { color: var(--bs-secondary-color, #6c757d); }

.emp-cal-legend {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem .75rem;
    align-items: center;
    font-size: .72rem;
}

/* Seven columns stop being readable well before the grid stops fitting, so
   phones get one day per row instead — with the weekday spelled out in the
   cell, since the column headings are what carried it. Slots outside the
   requested range have nothing to say in a list. */
@media (max-width: 767.98px) {
    .emp-cal-grid { grid-template-columns: 1fr; }
    .emp-cal-head { display: none; }
    .emp-cal-cell { min-height: 0; }
    .emp-cal-cell--empty { display: none; }
    .emp-cal-cell__weekday { display: inline; }
}

/* WO is this calendar's own code (EmployeeAttendanceCalendarService::EXTRA_META,
   which carries the same pair for the PDF) — the .att-* family above has no
   entry for it because the dashboard card leaves a day off blank. */
.att-weekly-off { background: color-mix(in srgb, #6c757d 14%, transparent); color: #6c757d; }

/* ==========================================================================
   Auth screens (layouts/auth.blade.php + the onboarding wizard)
   --------------------------------------------------------------------------
   These four screens (both sign-ins, forgot- and reset-password) plus the
   organization registration wizard are the only pages outside the panel
   layouts, so the panel's own rules never reached them.
   ========================================================================== */

/* Full-height at EVERY breakpoint, not just two of them. responsive.css pins
   `.login-container > .row { height: 100vh }` below 768px only, so the form
   was centred on a phone (by that rule) and on a desktop (by the artwork
   column's `height: 100vh` image) but top-aligned with a tall blank area
   under it on a tablet, where the artwork is hidden and nothing set a height.
   `min-height` also fixes the phone case properly: a fixed 100vh centred a
   form taller than the viewport — which is what a sign-in screen showing two
   validation messages is on a short phone — so its top scrolled out of reach
   above the fold. */
.login-container > .row { min-height: 100vh; }
@media (max-width: 767.98px) {
    /* Beats responsive.css's `height: 100vh` on the same selector. */
    .login-container > .row { height: auto; }
}

/* The artwork fills whatever the row ends up being rather than a hard 100vh:
   a form that outgrows the viewport used to leave white space below the image
   instead of extending it. */
.login-container .login-artwork {
    position: relative;
    min-height: 100vh;
}
.login-container .login-artwork img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* base.css zeroes `.form-check-input`'s margin and grows it to 1.3em, which
   drops Bootstrap's `margin-top: .25em`; the box then sits high against its
   label. "Remember me" is the only checkbox on these screens, so the fix is
   scoped here rather than restyling every checkbox in the panel. */
.login-container .form-check-input { margin-top: .1em; }

/* Password field with a reveal button. The button is layered inside the
   control rather than bolted on as an .input-group addon, so the input keeps
   the theme's own border, radius and focus ring instead of losing its right
   half to a second bordered element. */
/* The wrapper holds the input and the button only — never the
   .invalid-feedback. It is the button's positioning context and the button is
   its full height, so a feedback line inside it would grow the wrapper and
   drag the eye down off the field. */
.password-field { position: relative; }
.password-field > .form-control { padding-right: 2.75rem; }
.password-field > .password-toggle {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    width: 2.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text-muted);
    line-height: 1;
    transition: color var(--dur-fast) var(--ease-out);
}
.password-field > .password-toggle:hover,
.password-field > .password-toggle:focus-visible { color: var(--bs-primary); }
/* Bootstrap draws its own red/green validation glyph in the field's right
   padding, which is exactly where the toggle now sits. Move it inboard of the
   button rather than hiding it — .is-invalid is the state the field is most
   often in on a sign-in screen. */
.password-field > .form-control.is-invalid,
.password-field > .form-control.is-valid {
    padding-right: 5rem;
    background-position: right 3rem center;
}
/* Edge/IE and WebKit add reveal/clear controls of their own; two eyes is one
   too many. */
.password-field > .form-control::-ms-reveal,
.password-field > .form-control::-ms-clear { display: none; }

/* -- Onboarding (register) ------------------------------------------------
   The screen itself is now layouts/auth.blade.php, like the kit's
   sign-up.html — it used to be a standalone centred card that shared nothing
   with the sign-in it sits next to. All that is left of its own styling is
   the overlay covering the setup POST, which creates an organization, an
   admin, shifts and employees and is the one submit here worth blocking the
   page for. */
.onboard-overlay {
    position: fixed;
    inset: 0;
    z-index: 1080;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(var(--bs-body-bg-rgb), .85);
    backdrop-filter: blur(3px);
}
.onboard-overlay.show { display: flex; }
.onboard-overlay .spinner-border { width: 3rem; height: 3rem; border-width: .3rem; }
.onboard-overlay-msg { font-weight: 600; min-height: 1.5em; transition: opacity .2s; }

/* Status tabs (Offline Entries): icon + title/count + subtitle, no button
   chrome, a light rule underneath and a thin accent underline on the active
   tab. Real links; the page swaps its list in place. */
.status-tabs {
    display: flex;
    gap: 2.5rem;
    border-bottom: 1px solid var(--bs-border-color);
    overflow-x: auto;
    scrollbar-width: none;
}
.status-tabs::-webkit-scrollbar { display: none; }
.status-tab {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    padding: .5rem .125rem .875rem;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    color: var(--bs-secondary-color);
    text-decoration: none;
    white-space: nowrap;
    transition: color .15s ease, border-color .15s ease;
}
.status-tab:hover,
.status-tab:focus-visible {
    color: var(--bs-body-color);
    border-bottom-color: var(--bs-border-color);
}
.status-tab.active {
    color: var(--bs-primary);
    border-bottom-color: var(--bs-primary);
}
.status-tab-icon {
    font-size: 1.25rem;
    line-height: 1.2;
}
.status-tab-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-weight: 600;
}
.status-tab-count {
    min-width: 1.5rem;
    padding: .05rem .45rem;
    border-radius: 50rem;
    font-size: .75rem;
    font-weight: 600;
    text-align: center;
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
    transition: background-color .15s ease, color .15s ease;
}
.status-tab.active .status-tab-count {
    background: color-mix(in srgb, var(--bs-primary) 14%, transparent);
    color: var(--bs-primary);
}
.status-tab-sub {
    display: block;
    margin-top: .125rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}
@media (max-width: 575.98px) {
    .status-tabs { gap: 1.5rem; }
    .status-tab-sub { display: none; }
}
[data-oe-list] { transition: opacity .15s ease; }
[data-oe-list].is-loading { opacity: .5; pointer-events: none; }

/* Bulk Punch IN / OUT dialog (offline-entries/index). The dialog is
   `modal-dialog-scrollable`, so it never grows past the viewport, and its
   body never scrolls: it is a flex column in which the rows box is the one
   scroll area, taking whatever height the intro and notices leave. Title,
   notices, Cancel / Submit and the table head all stay put however many
   employees are listed. */
#oeBulkModal .modal-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}
#oeBulkModal .modal-body > * { flex: 0 0 auto; }
#oeBulkModal .modal-body > .oe-bulk-scroll {
    flex: 1 1 auto;
    min-height: 0;
}
.oe-bulk-scroll {
    overflow: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--bs-border-color);
    border-radius: .5rem;
}
.oe-bulk-scroll.is-loading { opacity: .5; pointer-events: none; }
.oe-bulk-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--bs-tertiary-bg);
    white-space: nowrap;
}
.oe-bulk-table tbody tr.oe-bulk-blocked > td { background: var(--bs-light-bg-subtle, transparent); }
.oe-bulk-table tbody tr.oe-bulk-blocked .app-avatar,
.oe-bulk-table tbody tr.oe-bulk-blocked .oe-bulk-photo { opacity: .55; }
/* The punch's captured face, large enough to judge the match from the row. */
.oe-bulk-photo {
    display: inline-block;
    width: 96px;
    height: 96px;
    border-radius: .5rem;
    overflow: hidden;
    border: 1px solid var(--bs-border-color);
    background: var(--bs-tertiary-bg);
}
.oe-bulk-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.oe-bulk-photo img { transition: transform .15s ease; }
.oe-bulk-photo:hover img { transform: scale(1.05); }
.oe-bulk-photo--none {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .25rem;
}
.oe-bulk-review { min-width: 220px; line-height: 1.6; }
@media (max-width: 575.98px) {
    .oe-bulk-photo { width: 72px; height: 72px; }
    .oe-bulk-photo--none .app-avatar { width: 40px !important; height: 40px !important; }
}
