38686-vm/static/css/custom.css
Konrad du Plessis 4f15e4bd5f feat(adjustments): replace Choices.js chip-multiselect with popover-checkbox filters
Checkpoint-1 feedback from Konrad: the Choices.js chip pattern for
Type / Workers / Teams was visually intrusive once multiple options
were picked — the filter bar dominated the viewport.

Replacement: each filter is now a compact pill (like Feature 1's
inline-filter pills on the report page) that opens a popover with a
scrollable checkbox list, live-search, and Select All / Invert /
Clear action buttons. OK commits the pending state into hidden form
inputs; Cancel / Esc / click-outside revert. The existing Apply button
still submits the form normally.

Reuses Feature 1's .filter-pill / .filter-popover CSS vocabulary —
only new CSS is a scrollable checkbox-list rule and a pill-count
badge style. No new modals. Choices.js CDN stays loaded (other
tabs still use it).
2026-04-23 17:07:50 +02:00

2057 lines
58 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ===================================================================
FoxFitt LabourPay v5 — Premium Orange Theme
Dark-first design system with warm amber/orange accents.
Top bar navigation on desktop, bottom tab bar on mobile.
All colours are CSS variables — the theme toggle switches them.
=================================================================== */
/* === DARK MODE (default — premium dark-first design) === */
:root {
/* Brand — warm orange/amber accent */
--accent: #e8851a;
--accent-hover: #f59e0b;
--accent-subtle: rgba(232, 133, 26, 0.12);
--accent-text: #f59e0b;
--accent-glow: rgba(232, 133, 26, 0.25);
/* Surfaces — very dark, charcoal-navy */
--bg-body: #0c0e14;
--bg-card: #161921;
--bg-card-hover: #1c2029;
--bg-elevated: #1c2029;
--bg-inset: #111318;
--bg-input: #1a1d26;
--bg-nav: #0c0e14;
--bg-sidebar: #111318;
/* Borders — subtle with slight warm tint */
--border-default: rgba(255, 255, 255, 0.08);
--border-subtle: rgba(255, 255, 255, 0.04);
--border-strong: rgba(255, 255, 255, 0.15);
--border-accent: rgba(232, 133, 26, 0.3);
/* Text — softened white (~85% brightness) for easier reading on dark backgrounds */
--text-primary: #d8d8d8;
--text-secondary: #9ca3af;
--text-tertiary: #6b7280;
--text-on-accent: #ffffff;
--text-on-nav: #d8d8d8;
--text-on-nav-muted: #6b7280;
--text-link: #e8851a;
/* Override Bootstrap */
--bs-body-color: #d8d8d8;
--bs-body-bg: #0c0e14;
--bs-border-color: rgba(255, 255, 255, 0.08);
/* Shadows — deep with orange tint */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.6);
--shadow-glow: 0 0 20px rgba(232, 133, 26, 0.15);
/* Status colours */
--color-success: #22c55e;
--color-success-bg: rgba(34, 197, 94, 0.12);
--color-danger: #ef4444;
--color-danger-bg: rgba(239, 68, 68, 0.12);
--color-warning: #f59e0b;
--color-warning-bg: rgba(245, 158, 11, 0.12);
--color-info: #3b82f6;
--color-info-bg: rgba(59, 130, 246, 0.12);
/* Stat card accent colours */
--stat-1: #e8851a;
--stat-2: #22c55e;
--stat-3: #f59e0b;
--stat-4: #3b82f6;
/* Misc */
--radius-sm: 0.5rem;
--radius-md: 0.625rem;
--radius-lg: 0.875rem;
--radius-xl: 1.25rem;
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1);
/* Layout dimensions */
--bottom-nav-height: 64px;
/* === ADJUSTMENTS TAB — badge palette (dark theme) === */
/* Each adjustment type has its own colour family. Loan-Repayment and
Advance-Repayment are +15% saturation siblings of their parent colour
so "money coming back" reads as a hotter signal than "money going out". */
--badge-bonus-bg: #5b8260; --badge-bonus-fg: #e8f3ea;
--badge-overtime-bg: #a16881; --badge-overtime-fg: #fce4ec;
--badge-deduction-bg: #5b4f8c; --badge-deduction-fg: #e0daf3;
--badge-loan-bg: #9b7f39; --badge-loan-fg: #fef4d1;
--badge-loan-rep-bg: #b48a1a; --badge-loan-rep-fg: #fef4d1;
--badge-advance-bg: #3e5c7b; --badge-advance-fg: #d7e5f2;
--badge-advance-rep-bg: #2f679a; --badge-advance-rep-fg: #d7e5f2;
}
/* === LIGHT MODE === */
[data-theme="light"] {
--bs-body-color: #1a1a2e;
--bs-body-bg: #f4f4f8;
--bs-border-color: #e0e0e8;
/* Brand tweaks for light */
--accent: #d97706;
--accent-hover: #b45309;
--accent-subtle: rgba(217, 119, 6, 0.08);
--accent-text: #92400e;
--accent-glow: rgba(217, 119, 6, 0.15);
/* Surfaces */
--bg-body: #f4f4f8;
--bg-card: #ffffff;
--bg-card-hover: #fafafa;
--bg-elevated: #ffffff;
--bg-inset: #f0f0f5;
--bg-input: #ffffff;
--bg-nav: #1a1a2e;
--bg-sidebar: #1a1a2e;
/* Borders */
--border-default: #e0e0e8;
--border-subtle: #f0f0f5;
--border-strong: #c8c8d4;
--border-accent: rgba(217, 119, 6, 0.3);
/* Text */
--text-primary: #1a1a2e;
--text-secondary: #4a4a5a;
--text-tertiary: #8a8a9a;
--text-on-nav: #f0f0f0;
--text-on-nav-muted: #6b7280;
--text-link: #d97706;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.12);
--shadow-glow: 0 0 20px rgba(217, 119, 6, 0.08);
/* Status colours */
--color-success: #16a34a;
--color-success-bg: #ecfdf5;
--color-danger: #dc2626;
--color-danger-bg: #fef2f2;
--color-warning: #d97706;
--color-warning-bg: #fffbeb;
--color-info: #2563eb;
--color-info-bg: #eff6ff;
/* === ADJUSTMENTS TAB — badge palette (light theme) === */
--badge-bonus-bg: #d7e8d9; --badge-bonus-fg: #385640;
--badge-overtime-bg: #f3d1dd; --badge-overtime-fg: #703347;
--badge-deduction-bg: #d8d0ef; --badge-deduction-fg: #3b2f6d;
--badge-loan-bg: #f0dc9d; --badge-loan-fg: #6a5320;
--badge-loan-rep-bg: #f7d873; --badge-loan-rep-fg: #5a4418;
--badge-advance-bg: #bccee0; --badge-advance-fg: #243b56;
--badge-advance-rep-bg: #9ec1dd; --badge-advance-rep-fg: #1d3550;
}
/* ===================================================================
GLOBAL RESET & BASE STYLES
=================================================================== */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-body);
color: var(--text-primary);
--bs-body-color: var(--text-primary);
--bs-body-bg: var(--bg-body);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0;
padding: 0;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Poppins', sans-serif;
color: var(--text-primary);
font-weight: 600;
}
a {
color: var(--text-link);
transition: color var(--transition-fast);
}
a:hover {
color: var(--accent-hover);
}
/* ===================================================================
APP LAYOUT — top bar + main content area
=================================================================== */
/* Wrapper for the whole app (topbar + content stacked vertically) */
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* === TOP BAR (always visible — horizontal nav on desktop, brand-only on mobile) === */
.app-topbar {
background: var(--bg-sidebar);
border-bottom: 1px solid var(--border-default);
position: sticky;
top: 0;
z-index: 1030;
padding: 0 1rem;
}
/* Inner flexbox container for topbar items */
.topbar-inner {
display: flex;
align-items: center;
height: 52px;
gap: 0.75rem;
max-width: 1400px;
margin: 0 auto;
width: 100%;
}
/* Brand logo + text */
.topbar-brand {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
flex-shrink: 0;
}
.topbar-brand:hover {
text-decoration: none;
}
/* Bolt icon box (also reused on login page) */
.sidebar-brand__icon {
width: 30px;
height: 30px;
background: linear-gradient(135deg, #e8851a 0%, #f59e0b 100%);
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.85rem;
flex-shrink: 0;
}
.topbar-brand__text {
font-family: 'Poppins', sans-serif;
font-weight: 700;
font-size: 1.1rem;
color: var(--text-on-nav);
letter-spacing: -0.02em;
}
.topbar-brand__text span {
color: var(--accent);
}
/* Horizontal nav links in topbar — centred between brand and actions */
.topbar-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 0.15rem;
flex: 1;
}
.topbar-nav__link {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.4rem 0.7rem;
border-radius: var(--radius-sm);
color: var(--text-on-nav-muted);
font-size: 0.8rem;
font-weight: 500;
text-decoration: none;
transition: all var(--transition-fast);
white-space: nowrap;
}
.topbar-nav__link:hover {
color: var(--text-on-nav);
background: rgba(255, 255, 255, 0.06);
text-decoration: none;
}
.topbar-nav__link.active {
color: var(--accent);
background: rgba(232, 133, 26, 0.1);
font-weight: 600;
}
.topbar-nav__link i {
font-size: 0.8rem;
width: 1rem;
text-align: center;
}
/* Right side of topbar: theme toggle + user avatar + logout */
.topbar-actions {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
margin-left: auto;
}
/* User avatar + name in topbar */
.topbar-user {
display: flex;
align-items: center;
gap: 0.4rem;
}
.topbar-user__avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--accent);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 0.7rem;
flex-shrink: 0;
}
.topbar-user__name {
color: var(--text-on-nav);
font-size: 0.78rem;
font-weight: 500;
}
/* === MAIN CONTENT AREA === */
.app-main {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
/* NO position/z-index here — avoids trapping Bootstrap modals in a stacking context */
}
/* Decorative gradient glows — separate fixed element, not on .app-main */
.app-glow {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
overflow: hidden;
}
.app-glow::before {
content: '';
position: absolute;
top: -200px;
right: -100px;
width: 600px;
height: 600px;
background: radial-gradient(ellipse, var(--accent-glow) 0%, transparent 70%);
}
.app-glow::after {
content: '';
position: absolute;
bottom: -300px;
left: 0;
width: 500px;
height: 500px;
background: radial-gradient(ellipse, rgba(232, 133, 26, 0.06) 0%, transparent 70%);
}
/* === BOTTOM TAB BAR (mobile only) === */
.app-bottom-nav {
display: none; /* hidden on desktop */
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--bg-nav);
border-top: 1px solid var(--border-default);
z-index: 1030;
padding: 0.25rem 0;
padding-bottom: calc(0.25rem + env(safe-area-inset-bottom, 0px));
}
.bottom-nav-inner {
display: flex;
justify-content: space-around;
align-items: center;
}
.bottom-nav__link {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.15rem;
padding: 0.4rem 0.75rem;
color: var(--text-on-nav-muted);
text-decoration: none;
font-size: 0.65rem;
font-weight: 500;
transition: color var(--transition-fast);
border-radius: var(--radius-sm);
min-width: 56px;
}
.bottom-nav__link i {
font-size: 1.15rem;
}
.bottom-nav__link:hover,
.bottom-nav__link.active {
color: var(--accent);
text-decoration: none;
}
.bottom-nav__link.active {
font-weight: 600;
}
/* Main content inner */
.app-content {
flex-grow: 1;
/* NO position/z-index — lets Bootstrap modals escape to body-level stacking */
}
/* Footer inside main content */
.app-footer {
padding: 1.25rem 0;
margin-top: auto;
border-top: 1px solid var(--border-default);
font-size: 0.8rem;
color: var(--text-tertiary);
}
/* === HAMBURGER BUTTON (mobile only — hidden on desktop via d-lg-none) === */
.hamburger-btn {
background: none;
border: none;
color: var(--text-on-nav);
font-size: 1.2rem;
padding: 0.35rem 0.5rem;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition-fast);
}
.hamburger-btn:hover {
color: var(--accent);
background: rgba(255, 255, 255, 0.06);
}
/* === MOBILE MENU (slides down from topbar when hamburger is tapped) === */
/* Fixed below the topbar so it stays visible regardless of scroll position */
.mobile-menu {
position: fixed;
top: 52px; /* matches topbar-inner height */
left: 0;
right: 0;
z-index: 1029; /* just below topbar (1030) */
background: var(--bg-sidebar);
border-bottom: 1px solid var(--border-default);
max-height: 0;
overflow: hidden;
transition: max-height 300ms ease, opacity 200ms ease;
opacity: 0;
box-shadow: var(--shadow-lg);
}
/* Open state — toggled by JS */
.mobile-menu.open {
max-height: calc(100vh - 52px); /* never taller than remaining screen */
opacity: 1;
overflow-y: auto;
}
.mobile-menu__nav {
display: flex;
flex-direction: column;
padding: 0.5rem 1rem;
gap: 0.15rem;
}
.mobile-menu__link {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.65rem 0.75rem;
border-radius: var(--radius-sm);
color: var(--text-on-nav-muted);
font-size: 0.85rem;
font-weight: 500;
text-decoration: none;
transition: all var(--transition-fast);
}
.mobile-menu__link:hover {
color: var(--text-on-nav);
background: rgba(255, 255, 255, 0.06);
text-decoration: none;
}
.mobile-menu__link.active {
color: var(--accent);
background: rgba(232, 133, 26, 0.1);
font-weight: 600;
}
.mobile-menu__link i {
width: 1.25rem;
text-align: center;
font-size: 0.9rem;
}
.mobile-menu__footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-top: 1px solid var(--border-default);
margin-top: 0.25rem;
}
/* Semi-transparent backdrop behind the menu — tapping it closes the menu */
.mobile-menu-backdrop {
display: none;
position: fixed;
inset: 0;
top: 52px;
background: rgba(0, 0, 0, 0.5);
z-index: 1028; /* below menu (1029) and topbar (1030) */
}
.mobile-menu-backdrop.open {
display: block;
}
/* === RESPONSIVE: Mobile layout === */
@media (max-width: 991.98px) {
/* Hide desktop nav links — hamburger menu handles navigation on mobile */
.topbar-nav {
display: none;
}
/* Hide user name on mobile — just show avatar */
.topbar-user__name {
display: none;
}
/* Bottom tab bar hidden — replaced by hamburger menu */
.app-bottom-nav {
display: none;
}
}
/* ===================================================================
CARDS — glass-morphism with subtle borders
=================================================================== */
.card {
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
transition: background-color var(--transition-normal),
border-color var(--transition-normal),
box-shadow var(--transition-normal);
}
.card:hover {
box-shadow: var(--shadow-md);
}
.card-header {
background: transparent;
border-bottom: 1px solid var(--border-default);
}
/* === STAT CARDS — with orange accent line on left === */
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
transition: all var(--transition-normal);
position: relative;
overflow: hidden;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* Left accent bar instead of top */
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 3px;
background: var(--stat-accent, var(--accent));
}
.stat-card:hover {
box-shadow: var(--shadow-md);
border-color: var(--border-strong);
transform: translateY(-1px);
}
/* Per-card accent colours */
.stat-card--danger { --stat-accent: var(--color-danger); }
.stat-card--success { --stat-accent: var(--color-success); }
.stat-card--warning { --stat-accent: var(--color-warning); }
.stat-card--info { --stat-accent: var(--color-info); }
.stat-card--accent { --stat-accent: var(--accent); }
.stat-card--purple { --stat-accent: #8b5cf6; }
/* Stat card label */
.stat-label {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
margin-bottom: 0.25rem;
}
/* Stat card value */
.stat-value {
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary);
font-family: 'Poppins', sans-serif;
}
/* Stat card icon circle */
.stat-icon {
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
flex-shrink: 0;
}
.stat-icon--danger { background: var(--color-danger-bg); color: var(--color-danger); }
.stat-icon--success { background: var(--color-success-bg); color: var(--color-success); }
.stat-icon--warning { background: var(--color-warning-bg); color: var(--color-warning); }
.stat-icon--info { background: var(--color-info-bg); color: var(--color-info); }
.stat-icon--accent { background: var(--accent-subtle); color: var(--accent); }
.stat-icon--purple { background: rgba(139, 92, 246, 0.1); color: #8b5cf6; }
/* ===================================================================
BUTTONS
=================================================================== */
.btn {
font-weight: 500;
font-size: 0.875rem;
border-radius: var(--radius-sm);
transition: all var(--transition-fast);
letter-spacing: 0.01em;
}
/* Primary accent button — orange */
.btn-accent {
background: linear-gradient(135deg, #e8851a 0%, #f59e0b 100%);
color: var(--text-on-accent);
font-weight: 600;
border: none;
box-shadow: 0 2px 8px rgba(232, 133, 26, 0.3);
}
.btn-accent:hover {
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
color: var(--text-on-accent);
box-shadow: 0 4px 16px rgba(232, 133, 26, 0.4);
transform: translateY(-1px);
}
.btn-accent:active {
transform: translateY(0);
box-shadow: 0 1px 4px rgba(232, 133, 26, 0.3);
}
/* btn-primary — dark slate in dark mode, darker slate in light mode */
.btn-primary {
background-color: #2a2d3a;
border-color: #3a3d4a;
color: #d8d8d8;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #353849;
border-color: #4a4d5a;
color: #ffffff;
}
.btn-primary:active,
.btn-primary.active {
background-color: #1e2130;
border-color: #3a3d4a;
color: #ffffff;
}
/* Light mode btn-primary — dark navy for good contrast */
[data-theme="light"] .btn-primary {
background-color: #1e293b;
border-color: #1e293b;
color: #ffffff;
}
[data-theme="light"] .btn-primary:hover,
[data-theme="light"] .btn-primary:focus {
background-color: #334155;
border-color: #334155;
color: #ffffff;
}
/* Dark mode outline button fixes */
[data-theme="dark"] .btn-outline-secondary,
:root .btn-outline-secondary {
color: var(--text-secondary);
border-color: var(--border-strong);
}
[data-theme="dark"] .btn-outline-secondary:hover,
:root .btn-outline-secondary:hover {
background-color: var(--bg-elevated);
color: var(--text-primary);
border-color: var(--text-secondary);
}
.btn-outline-info {
color: var(--color-info);
border-color: var(--color-info);
}
.btn-outline-warning {
color: var(--color-warning);
border-color: var(--color-warning);
}
.btn-outline-danger {
color: var(--color-danger);
border-color: var(--color-danger);
}
[data-theme="light"] .btn-outline-secondary {
color: var(--text-secondary);
border-color: var(--border-default);
}
[data-theme="light"] .btn-outline-secondary:hover {
background-color: var(--bg-inset);
color: var(--text-primary);
}
/* ===================================================================
TABLES — compact text for data-dense views
=================================================================== */
.table {
color: var(--text-primary);
--bs-table-bg: transparent;
--bs-table-color: var(--text-primary);
font-size: 0.78rem;
}
.table > thead {
background-color: var(--bg-inset);
}
.table-light {
--bs-table-bg: var(--bg-inset);
--bs-table-color: var(--text-secondary);
}
.table > tbody > tr {
border-color: var(--border-subtle);
transition: background-color var(--transition-fast);
}
.table-hover > tbody > tr:hover {
background-color: var(--bg-card-hover);
--bs-table-hover-bg: var(--bg-card-hover);
--bs-table-hover-color: var(--text-primary);
color: var(--text-primary);
}
.table th {
font-weight: 600;
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
border-bottom-width: 1px;
}
/* ===================================================================
FORMS
=================================================================== */
.form-control,
.form-select {
background-color: var(--bg-input);
border-color: var(--border-default);
color: var(--text-primary);
border-radius: var(--radius-sm);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
/* Placeholder text — visible but subtle */
.form-control::placeholder,
.form-select::placeholder {
color: var(--text-tertiary);
opacity: 1;
}
.form-control:focus,
.form-select:focus {
background-color: var(--bg-input);
border-color: var(--accent);
color: var(--text-primary);
box-shadow: 0 0 0 3px var(--accent-subtle);
}
.form-label {
font-weight: 500;
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.375rem;
}
/* === NATIVE DATE/MONTH PICKER ICONS (Chromium) ===
The browser paints a small calendar icon on the right of
<input type="date"> and <input type="month"> via the pseudo-element
::-webkit-calendar-picker-indicator. On dark backgrounds the default
black icon is nearly invisible. CSS can't set its fill directly, so
we use a filter chain to tint it toward our amber accent (#e8851a).
Firefox doesn't render this indicator so the rule is a no-op there. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator {
cursor: pointer;
opacity: 0.9;
filter: invert(58%) sepia(89%) saturate(862%) hue-rotate(357deg) brightness(93%) contrast(92%);
}
input[type="date"]::-webkit-calendar-picker-indicator:hover,
input[type="month"]::-webkit-calendar-picker-indicator:hover {
opacity: 1;
}
.form-check-input:checked {
background-color: var(--accent);
border-color: var(--accent);
}
/* === FORMSET ROW — MARKED FOR DELETION ===
When a user clicks the trash button on a certification/warning row,
JS adds `.row-marked-delete` to that row. These styles fade the row
and strike through its inputs so it's visually obvious the row will
be removed on save. The "Undo" link restores everything. */
.formset-row.row-marked-delete {
opacity: 0.55;
background: rgba(239, 68, 68, 0.06);
border-color: rgba(239, 68, 68, 0.3) !important;
}
.formset-row.row-marked-delete .form-control,
.formset-row.row-marked-delete .form-select,
.formset-row.row-marked-delete textarea {
text-decoration: line-through;
pointer-events: none; /* can't edit a row you're removing */
background: var(--bg-inset, #f0f0f5);
}
.formset-row.row-marked-delete .form-label {
text-decoration: line-through;
}
.input-group-text {
background-color: var(--bg-inset);
border-color: var(--border-default);
color: var(--text-secondary);
}
/* === BOOTSTRAP TOOLTIPS — themed for dark/light modes ===
Bootstrap 5.3's default tooltip uses `--bs-body-color` as background
and `--bs-body-bg` as text colour. In dark mode that produces a light
tooltip with dark text, which clashes with the rest of the UI and can
be unreadable when the body/bg values are very close.
Override the tooltip CSS variables to use our elevated-surface colours
(same palette as cards on hover) — readable on both dark and light. */
.tooltip {
--bs-tooltip-bg: var(--bg-card-hover);
--bs-tooltip-color: var(--text-primary);
--bs-tooltip-opacity: 1;
}
.tooltip .tooltip-inner {
border: 1px solid var(--border-default);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
font-size: 0.8rem;
max-width: 280px;
padding: 6px 10px;
font-weight: 500;
}
/* The arrow inherits its color from --bs-tooltip-bg automatically, but
we give it a matching border so it stays connected visually. */
/* ===================================================================
MODALS
=================================================================== */
.modal-content {
background-color: var(--bg-elevated);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.modal-header {
border-bottom: 1px solid var(--border-default);
padding: 1.25rem 1.5rem;
}
.modal-title {
font-size: 1.1rem;
font-weight: 600;
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
border-top: 1px solid var(--border-default);
padding: 1rem 1.5rem;
}
/* ===================================================================
BADGES (status indicators)
=================================================================== */
.badge {
font-weight: 500;
font-size: 0.7rem;
letter-spacing: 0.02em;
padding: 0.35em 0.65em;
border-radius: var(--radius-sm);
}
/* ===================================================================
NAV TABS (Payroll dashboard tabs)
=================================================================== */
.nav-tabs {
border-bottom: 2px solid var(--border-default);
}
.nav-tabs .nav-link {
color: var(--text-secondary);
font-weight: 500;
font-size: 0.875rem;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
padding: 0.75rem 1.25rem;
transition: all var(--transition-fast);
}
.nav-tabs .nav-link:hover {
color: var(--text-primary);
border-bottom-color: var(--border-strong);
background: none;
}
.nav-tabs .nav-link.active {
color: var(--accent);
border-bottom-color: var(--accent);
background: none;
font-weight: 600;
}
/* ===================================================================
THEME TOGGLE BUTTON (in topbar)
=================================================================== */
.theme-toggle {
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--border-default);
color: var(--text-on-nav-muted);
cursor: pointer;
padding: 0.4rem;
border-radius: var(--radius-sm);
font-size: 0.85rem;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.theme-toggle:hover {
color: var(--accent);
background: rgba(232, 133, 26, 0.1);
border-color: var(--accent);
}
/* ===================================================================
DASHBOARD HEADER — gradient banner with orange accents
=================================================================== */
.dashboard-header {
background: linear-gradient(135deg, #111318 0%, #1a1d26 50%, #2a1a0a 100%);
color: white;
padding: 2rem 2.5rem;
margin-bottom: -3rem;
border-radius: var(--radius-xl);
position: relative;
overflow: hidden;
border: 1px solid var(--border-default);
}
/* Orange glow decoration on dashboard header */
.dashboard-header::before {
content: '';
position: absolute;
top: -60%;
right: -15%;
width: 350px;
height: 350px;
background: radial-gradient(circle, rgba(232, 133, 26, 0.2) 0%, transparent 65%);
pointer-events: none;
}
.dashboard-header::after {
content: '';
position: absolute;
bottom: -40%;
left: 10%;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(245, 158, 11, 0.1) 0%, transparent 70%);
pointer-events: none;
}
/* Light mode dashboard header */
[data-theme="light"] .dashboard-header {
background: linear-gradient(135deg, #1a1a2e 0%, #2d2d4a 50%, #3d2a10 100%);
}
/* ===================================================================
PAGE-LEVEL UTILITIES
=================================================================== */
.page-header {
margin-bottom: 1.5rem;
}
.page-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0;
}
/* === PAYROLL ACTION BUTTONS — 2x2 grid on mobile, row on desktop === */
@media (max-width: 767.98px) {
.payroll-actions {
display: grid !important;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
width: 100%;
}
.payroll-actions .btn {
font-size: 0.75rem;
padding: 0.45rem 0.6rem;
white-space: nowrap;
}
}
/* On desktop, restore normal button size (undo btn-sm) */
@media (min-width: 768px) {
.btn-md-normal {
font-size: 0.875rem !important;
padding: 0.375rem 0.75rem !important;
}
}
.text-muted {
color: var(--text-secondary) !important;
}
.text-dark {
color: var(--text-primary) !important;
}
.border-bottom {
border-color: var(--border-default) !important;
}
/* ===================================================================
ALERTS
=================================================================== */
.alert {
border-radius: var(--radius-md);
border: none;
font-size: 0.875rem;
}
/* ===================================================================
FOOTER (inside app-main)
=================================================================== */
.footer {
background-color: transparent;
color: var(--text-tertiary);
padding: 1.25rem 0;
margin-top: auto;
border-top: 1px solid var(--border-default);
font-size: 0.8rem;
}
/* ===================================================================
SCROLLBAR (premium dark)
=================================================================== */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border-strong);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
}
/* ===================================================================
QUICK ACTION CARD (dashboard)
=================================================================== */
.quick-action {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.25rem 1rem;
border-radius: var(--radius-lg);
border: 1px solid var(--border-default);
background: var(--bg-card);
color: var(--text-primary);
text-decoration: none;
transition: all var(--transition-fast);
flex: 1;
min-width: 120px;
}
.quick-action:hover {
background: var(--accent-subtle);
border-color: var(--accent);
color: var(--accent);
transform: translateY(-2px);
box-shadow: var(--shadow-glow);
text-decoration: none;
}
.quick-action i {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: var(--accent);
}
.quick-action span {
font-size: 0.8rem;
font-weight: 600;
}
/* ===================================================================
LIST GROUP (themed)
=================================================================== */
.list-group-item {
background-color: var(--bg-card);
border-color: var(--border-subtle);
color: var(--text-primary);
transition: background-color var(--transition-normal);
}
.list-group-item:hover {
background-color: var(--bg-card-hover);
}
/* ===================================================================
RESOURCE TOGGLE PANEL (dashboard)
=================================================================== */
.resource-hidden { display: none !important; }
.resource-row {
border-color: var(--border-subtle) !important;
transition: background-color var(--transition-fast);
}
.resource-row:hover {
background-color: var(--bg-card-hover);
}
/* ===================================================================
WORKER LOOKUP LINK (clickable names)
=================================================================== */
.worker-lookup-link {
color: var(--text-primary) !important;
text-decoration: none !important;
border-bottom: 1px dashed var(--border-strong);
transition: all var(--transition-fast);
}
.worker-lookup-link:hover {
color: var(--accent) !important;
border-bottom-color: var(--accent);
}
/* ===================================================================
CALENDAR VIEW (work history)
=================================================================== */
.cal-day {
min-height: 90px;
padding: 6px 8px;
border: 1px solid var(--border-default);
border-radius: var(--radius-sm);
background: var(--bg-card);
transition: all var(--transition-fast);
cursor: default;
}
.cal-day--other {
opacity: 0.4;
background: var(--bg-inset);
}
.cal-day--today {
border-color: var(--accent);
border-width: 2px;
}
.cal-day--today .cal-day__number {
color: var(--accent);
font-weight: 700;
}
.cal-day--has-logs {
cursor: pointer;
}
.cal-day--has-logs:hover {
background: var(--accent-subtle);
border-color: var(--accent);
box-shadow: var(--shadow-sm);
}
.cal-day--selected {
background: var(--accent-subtle);
border-color: var(--accent);
border-width: 2px;
box-shadow: 0 0 0 3px var(--accent-subtle);
}
.cal-day__number {
font-size: 0.85rem;
font-weight: 500;
color: var(--text-primary);
}
.cal-entry {
font-size: 0.72rem;
line-height: 1.3;
color: var(--text-secondary);
}
@media (max-width: 767.98px) {
.cal-day { min-height: 55px; padding: 4px; }
.cal-entry { display: none; }
}
/* ===================================================================
DARK MODE — Bootstrap utility overrides
Bootstrap's utility classes (.text-dark, .bg-warning, .table-light,
.form-control:disabled, etc.) use hardcoded colours that look wrong
on dark backgrounds. These overrides fix contrast issues.
=================================================================== */
/* .text-dark — Bootstrap uses #212529, unreadable on dark bg */
:root .text-dark,
:root .text-dark.ms-1 {
color: var(--text-primary) !important;
}
/* Loan badge: yellow bg + dark text for contrast */
.badge.bg-warning {
color: #000 !important;
}
/* table-light (used in thead) — fix for dark mode */
:root .table-light {
--bs-table-bg: var(--bg-inset);
--bs-table-color: var(--text-secondary);
--bs-table-border-color: var(--border-default);
}
[data-theme="light"] .table-light {
--bs-table-bg: #f0f0f5;
--bs-table-color: #4a4a5a;
}
/* Disabled form controls — visible text in dark mode */
.form-control:disabled,
.form-select:disabled {
background-color: var(--bg-inset);
color: var(--text-secondary);
opacity: 0.7;
border-color: var(--border-default);
}
/* form-select option dropdown — ensure readable in dark mode */
.form-select option {
background-color: var(--bg-card);
color: var(--text-primary);
}
/* btn-close — visible X button on dark backgrounds */
:root .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
[data-theme="light"] .btn-close {
filter: none;
}
/* Bootstrap text utilities that clash with dark mode */
:root .text-primary {
color: var(--color-info) !important;
}
:root .text-success {
color: var(--color-success) !important;
}
:root .text-danger {
color: var(--color-danger) !important;
}
:root .text-warning {
color: var(--color-warning) !important;
}
:root .text-info {
color: var(--color-info) !important;
}
:root .text-muted {
color: var(--text-secondary) !important;
}
/* btn-secondary in modals — visible in dark mode */
.btn-secondary {
background-color: var(--bg-elevated);
border-color: var(--border-strong);
color: var(--text-primary);
}
.btn-secondary:hover {
background-color: var(--border-default);
border-color: var(--border-strong);
color: var(--text-primary);
}
/* ===================================================================
PRINT STYLES — ensure payslips print as black text on white page
=================================================================== */
@media print {
/* Override ALL CSS variables to light/print-friendly values */
:root, [data-theme="dark"], [data-theme="light"] {
--bg-body: #ffffff !important;
--bg-card: #ffffff !important;
--bg-card-hover: #ffffff !important;
--bg-elevated: #ffffff !important;
--bg-inset: #f5f5f5 !important;
--bg-input: #ffffff !important;
--text-primary: #000000 !important;
--text-secondary: #333333 !important;
--text-tertiary: #666666 !important;
--text-on-accent: #000000 !important;
--text-link: #000000 !important;
--border-default: #cccccc !important;
--border-subtle: #dddddd !important;
--border-strong: #999999 !important;
--accent: #d97706 !important;
--color-success: #16a34a !important;
--color-danger: #dc2626 !important;
--color-warning: #d97706 !important;
--color-info: #2563eb !important;
--color-success-bg: #ecfdf5 !important;
--color-danger-bg: #fef2f2 !important;
--color-warning-bg: #fffbeb !important;
--color-info-bg: #eff6ff !important;
--bs-body-color: #000000 !important;
--bs-body-bg: #ffffff !important;
}
body {
background: white !important;
color: black !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Hide navigation and non-print elements */
.app-topbar, .app-bottom-nav,
.app-footer, .app-glow, .d-print-none { display: none !important; }
/* Cards: clean white with thin border, no blur */
.card {
background: white !important;
border: 1px solid #ddd !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
/* Stat cards: remove glass effect */
.stat-card {
background: white !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
box-shadow: none !important;
}
/* Tables: black text on white */
.table, .table th, .table td {
color: #000 !important;
background: white !important;
border-color: #ccc !important;
font-size: 11pt !important;
}
.table > thead, .table-light {
background-color: #f0f0f0 !important;
}
/* Headings and text: all black */
h1, h2, h3, h4, h5, h6, p, span, div, td, th, a {
color: #000 !important;
}
/* Badges: print-friendly */
.badge {
border: 1px solid #999 !important;
background: #f5f5f5 !important;
color: #000 !important;
}
/* Ensure the stat-label text prints as dark grey */
.stat-label {
color: #555 !important;
}
/* Links: no colour, no underline */
a { color: #000 !important; text-decoration: none !important; }
}
/* ===================================================================
SMOOTH TRANSITIONS for theme switch
=================================================================== */
body, .card, .modal-content, .form-control, .form-select,
.table, .btn, .alert, .badge,
.input-group-text, .stat-card, .cal-day,
.app-topbar, .app-bottom-nav {
transition: background-color var(--transition-normal),
color var(--transition-normal),
border-color var(--transition-normal),
box-shadow var(--transition-normal);
}
/* === Work log payroll: clickable row hover === */
/* Applied only by base.html / templates that add class="work-log-row" */
/* (admin-only; supervisors never get the class so hover doesn't apply). */
.work-log-row {
transition: background-color 120ms ease-in-out;
}
.work-log-row:hover td {
background: var(--bg-card-hover);
}
/* === Report filter pills === */
.filter-pill {
display: inline-flex;
align-items: center;
padding: 0.35rem 0.75rem;
font-size: 0.825rem;
background: var(--bg-inset);
color: var(--text-primary);
border: 1px solid var(--border-default);
border-radius: 999px;
line-height: 1.2;
}
.filter-pill i {
color: var(--accent);
font-size: 0.75rem;
}
.filter-pill__x {
margin-left: 0.5rem;
padding: 0 0.35rem;
color: var(--text-tertiary);
text-decoration: none;
font-weight: 600;
border-radius: 50%;
transition: color 120ms, background-color 120ms;
}
.filter-pill__x:hover {
color: var(--text-primary);
background: var(--bg-card-hover);
text-decoration: none;
}
/* === Choices.js theme overrides (dark + light, executive report modal) === */
/*
Choices.js ships with a white-bg, light-grey-text default that clashes with
the app's dark theme. These overrides replace those defaults with the app's
own design tokens so the multi-select picker matches every other card and
input on the page. All tokens auto-switch between dark (:root) and light
(:root.light) themes — no duplicate blocks needed.
Specificity note: the Choices.js CDN CSS loads AFTER custom.css (inside the
modal partial, near </body>). Every rule below chains the root `.choices`
class to beat the CDN's same-class selectors, and uses !important on the
two properties Choices.js hardcodes most aggressively (color + background)
so dark/light theme tokens always win.
*/
/* Container — the outer wrapper that replaces the native <select> */
.choices.choices {
margin-bottom: 0;
}
/* Closed-state input area (where chips and the placeholder/search sit) */
.choices .choices__inner {
background: var(--bg-inset) !important;
color: var(--text-primary) !important;
border: 1px solid var(--border-default) !important;
border-radius: 0.5rem;
padding: 0.4rem 0.55rem;
min-height: 2.55rem;
font-size: 0.925rem;
}
.choices.is-focused .choices__inner,
.choices.is-open .choices__inner {
border-color: var(--accent) !important;
box-shadow: 0 0 0 0.15rem rgba(232, 133, 26, 0.18);
}
/* The cloned search input typed into when the dropdown is open */
.choices .choices__input {
background: transparent !important;
color: var(--text-primary) !important;
font-size: 0.925rem;
}
.choices .choices__input::placeholder {
color: var(--text-tertiary) !important;
}
/* Dropdown popup — the list of choices */
.choices .choices__list--dropdown,
.choices .choices__list[aria-expanded] {
background: var(--bg-card) !important;
border: 1px solid var(--border-default) !important;
border-radius: 0.5rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
margin-top: 4px;
z-index: 2000;
color: var(--text-primary) !important;
}
/* Individual option rows in the dropdown — default state */
.choices .choices__list--dropdown .choices__item,
.choices .choices__list[aria-expanded] .choices__item {
color: var(--text-primary) !important;
background: transparent !important;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
}
/* Hovered / keyboard-highlighted option — matches the "Month button selected" look */
.choices .choices__list--dropdown .choices__item--selectable.is-highlighted,
.choices .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
background: var(--bg-card-hover) !important;
color: var(--text-primary) !important;
}
/* The trailing "Press to select" hint */
.choices .choices__list--dropdown .choices__item--selectable.is-highlighted::after,
.choices .choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after {
color: var(--accent);
opacity: 0.9;
}
/* Disabled / placeholder-style rows (e.g. "No matches found") */
.choices .choices__list--dropdown .choices__item--disabled,
.choices .choices__list[aria-expanded] .choices__item--disabled {
color: var(--text-tertiary) !important;
}
/* Placeholder text in the input area when nothing is selected */
.choices .choices__placeholder {
color: var(--text-tertiary) !important;
opacity: 1;
}
/* Selected chips in multi-select mode (visible when items are chosen) */
.choices .choices__list--multiple .choices__item {
background: var(--accent) !important;
border: 1px solid var(--accent) !important;
color: #fff !important;
font-size: 0.82rem;
font-weight: 500;
padding: 0.2rem 0.6rem;
margin: 0.15rem 0.25rem 0.15rem 0;
border-radius: 999px;
}
.choices .choices__list--multiple .choices__item.is-highlighted {
background: var(--accent-hover) !important;
border-color: var(--accent-hover) !important;
}
/* The × button on each selected chip */
.choices .choices__list--multiple .choices__button {
border-left: 1px solid rgba(255, 255, 255, 0.4);
margin: 0 0 0 0.5rem;
padding-left: 0.5rem;
opacity: 0.85;
}
.choices .choices__list--multiple .choices__button:hover {
opacity: 1;
}
/* No-results / no-choices message */
.choices .choices__list .choices__item--no-results,
.choices .choices__list .choices__item--no-choices {
color: var(--text-tertiary) !important;
font-style: italic;
background: transparent !important;
}
/* === Hero KPI card variant (executive report) === */
/*
A larger, more typographic version of the existing .stat-card,
used for the top-of-report KPI band. Keeps the same --accent-based
colour stripes (stat-card--danger, --warning, --info) but scales
the number, flattens the label to uppercase tracked caps, and adds
a subtle tertiary sub-line for context like "as of 15:42" or the
date range.
*/
.stat-card--hero {
padding: 1.25rem 1.4rem;
min-height: 130px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.stat-card--hero .stat-label {
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-tertiary);
margin-bottom: 0.4rem;
}
.stat-card--hero .stat-value {
font-family: 'Poppins', sans-serif;
font-weight: 600;
font-size: 1.85rem;
line-height: 1;
color: var(--text-primary);
font-variant-numeric: tabular-nums;
}
.stat-card--hero .stat-subline {
font-size: 0.78rem;
color: var(--text-tertiary);
margin-top: 0.6rem;
}
/* === Report chapter headings === */
/*
Numbered chapter markers (I, II, III, IV) on the executive report.
Each heading has an orange filled circle with the Roman numeral
followed by the chapter title. Used on Chapter I (Lifetime Context),
II (Selected Period), III (Worker Breakdown), IV (Team x Project).
*/
.chapter-heading {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--text-primary);
font-family: 'Poppins', sans-serif;
font-weight: 600;
}
.chapter-heading .chapter-num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.85rem;
height: 1.85rem;
border-radius: 50%;
background: var(--accent);
color: #fff;
font-size: 0.85rem;
font-weight: 700;
font-family: 'Inter', sans-serif;
}
/* tabular-nums for all numeric report tables */
.report-numeric td,
.report-numeric th {
font-variant-numeric: tabular-nums;
}
/* === Pivot-table footer totals (Chapter IV) === */
/*
Bold, slightly-lifted row at the bottom of the Team × Project pivot
that holds the column totals + grand total. The 2px top border
visually separates totals from the data rows; the inset background
is the same --bg-inset used by other "slightly raised" surfaces.
*/
.table-total-row td {
border-top: 2px solid var(--border-default) !important;
background: var(--bg-inset);
}
/* === Inline Filters (pill-as-dropdown) on the report page === */
/*
Layered on top of the existing .filter-pill rules (lines ~14961524).
Three components:
1. .filter-pill--editable: pointer cursor, hover tint, rotating chevron
2. .filter-popover: absolute-positioned dropdown anchored under the pill
3. .filter-popover__footer: sticky bottom bar so the OK button stays
visible even when Choices.js expands its dropdown list over the body
There is intentionally NO dirty-state indicator and NO global Apply button —
each popover's OK commits and reloads the page immediately. Simpler model,
less state to reason about. (Earlier revision had both; removed 2026-04-23
after UX feedback.)
*/
/* --- Wrapper keeps the popover anchored to its pill --- */
.filter-pill-wrap {
display: inline-flex;
align-items: center;
}
/* --- Editable pill: button, cursor, hover state, chevron --- */
.filter-pill--editable {
cursor: pointer;
border: 1px solid var(--border-default);
background: var(--bg-inset);
color: var(--text-primary);
transition: background-color 120ms, border-color 120ms, box-shadow 120ms;
}
.filter-pill--editable:hover {
background: var(--bg-card-hover);
border-color: var(--accent);
}
.filter-pill--editable[aria-expanded="true"] {
background: var(--bg-card-hover);
border-color: var(--accent);
}
.filter-pill__chevron {
opacity: 0.7;
transition: transform 120ms;
}
.filter-pill--editable[aria-expanded="true"] .filter-pill__chevron {
transform: rotate(180deg);
}
/* --- Popover positioned under the pill --- */
/* Border + shadow beefed up 2026-04-23 so the popover visually detaches
from the report body behind it — previous subtle shadow was getting
lost against the amber-accented report cards.
The popover uses a flex column so a sticky footer stays pinned at the
bottom even when the body scrolls. We DO NOT set overflow: hidden on
the popover itself — see the Choices.js override below for why. */
.filter-popover {
position: absolute;
top: calc(100% + 6px);
left: 0;
z-index: 1040; /* below Bootstrap modal (1055) but above everything else */
min-width: 300px;
max-width: 420px;
max-height: min(70vh, 520px);
display: flex;
flex-direction: column;
background: var(--bg-card);
/* Two-layer border for depth: outer accent-tinted halo + inner crisp edge */
border: 2px solid var(--accent);
border-radius: 0.5rem;
box-shadow:
0 0 0 4px rgba(232, 133, 26, 0.08), /* soft accent halo */
0 18px 44px rgba(0, 0, 0, 0.55), /* deep drop shadow */
0 6px 12px rgba(0, 0, 0, 0.35); /* near shadow for edge crispness */
padding: 0;
}
/* Light theme: shadow and halo need different opacity to read against white */
:root.light .filter-popover {
box-shadow:
0 0 0 4px rgba(217, 119, 6, 0.12),
0 18px 44px rgba(15, 23, 42, 0.22),
0 6px 12px rgba(15, 23, 42, 0.14);
}
.filter-popover[hidden] {
display: none;
}
.filter-popover__body {
padding: 1rem;
overflow-y: auto; /* body scrolls when content exceeds max-height */
flex: 1 1 auto;
}
/* Footer is sticky at the bottom of the popover so the OK button is always
reachable — fixes the "Choices.js dropdown hides the OK button" complaint. */
.filter-popover__footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 0.6rem 1rem;
border-top: 1px solid var(--border-default);
background: var(--bg-inset);
border-radius: 0 0 0.5rem 0.5rem;
flex: 0 0 auto;
position: sticky;
bottom: 0;
z-index: 2;
}
/* --- Choices.js dropdown override (scoped to filter popovers) ---
Choices.js renders its option list as position: absolute beneath the
input. Inside our popovers (a flex column with a max-height and a
sticky footer) that's a problem:
1. The absolute-positioned dropdown doesn't contribute to the body's
scrollHeight, so the body's overflow-y: auto never creates a
scrollbar — and the user's wheel scroll falls through to the page.
2. The dropdown's rendered position overlaps / sits behind the sticky
footer, so options aren't visible.
Forcing the dropdown to position: static lets it flow inline: the body
grows to contain it, the sticky footer pushes below, and for long
option lists the dropdown's own max-height + overflow-y gives a clean
internal scroll.
Specificity note: we mirror Choices.js's selector list
(`.choices__list--dropdown, .choices__list[aria-expanded]`) because
the second branch carries a class+attribute specificity (0,0,2,0)
that ties with a naive two-class override. Source order then decides
the winner and Choices.js's stylesheet loads AFTER ours. Mirroring
the selector lifts our specificity one step on the aria-expanded
branch and wins cleanly without needing !important.
Scoped to .filter-popover so other Choices.js usages in the app
(worker/team pickers on edit pages, etc.) keep their default behaviour. */
.filter-popover .choices__list--dropdown,
.filter-popover .choices__list[aria-expanded] {
position: static;
margin-top: 0.35rem;
max-height: 260px;
overflow-y: auto;
}
/* --- Mobile: popovers stretch full-width below the pill strip --- */
@media (max-width: 576px) {
.filter-popover {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
max-width: 100vw;
max-height: 80vh;
border-radius: 0.5rem 0.5rem 0 0;
z-index: 1050;
}
}
/* =============================================================================
* ADJUSTMENTS TAB
* Visual vocabulary for the Payroll → Adjustments tab.
* - 7 badge classes, one per adjustment type
* - Sticky filter bar that stays visible as the table scrolls
* - Group-by header style (collapsible section divider)
* - Floating bulk-action bar at the bottom of the viewport
* ============================================================================= */
/* --- Type badges (one class per PayrollAdjustment type) --- */
.badge-type-bonus,
.badge-type-overtime,
.badge-type-deduction,
.badge-type-new-loan,
.badge-type-loan-repayment,
.badge-type-advance-payment,
.badge-type-advance-repayment {
display: inline-block;
padding: 0.3rem 0.7rem;
border-radius: 999px;
font-family: 'Inter', sans-serif;
font-size: 0.7rem;
font-weight: 500;
line-height: 1;
white-space: nowrap;
}
.badge-type-bonus { background: var(--badge-bonus-bg); color: var(--badge-bonus-fg); }
.badge-type-overtime { background: var(--badge-overtime-bg); color: var(--badge-overtime-fg); }
.badge-type-deduction { background: var(--badge-deduction-bg); color: var(--badge-deduction-fg); }
.badge-type-new-loan { background: var(--badge-loan-bg); color: var(--badge-loan-fg); }
.badge-type-loan-repayment { background: var(--badge-loan-rep-bg); color: var(--badge-loan-rep-fg); }
.badge-type-advance-payment { background: var(--badge-advance-bg); color: var(--badge-advance-fg); }
.badge-type-advance-repayment { background: var(--badge-advance-rep-bg); color: var(--badge-advance-rep-fg); }
/* --- Sticky filter bar (keeps filters visible as the table scrolls) --- */
.adjustments-filter-bar {
position: sticky;
top: 0;
z-index: 10;
background: var(--bg-card);
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-default);
border-radius: 0.5rem 0.5rem 0 0;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: end;
}
/* --- Group header (collapsible section divider for group-by mode) --- */
.adj-group-header {
cursor: pointer;
padding: 0.75rem 1rem;
background: var(--bg-inset);
border-top: 1px solid var(--border-default);
border-bottom: 1px solid var(--border-default);
display: flex;
align-items: center;
gap: 0.75rem;
user-select: none;
transition: background-color 120ms;
}
.adj-group-header:hover { background: var(--bg-card-hover); }
.adj-group-header .fa-chevron-down,
.adj-group-header .fa-chevron-right { opacity: 0.7; width: 0.8rem; }
.adj-group-header .adj-group-label { font-weight: 600; }
.adj-group-header .adj-group-meta { color: var(--text-secondary); font-size: 0.875rem; margin-left: auto; }
/* --- Floating bulk action bar (appears when >=1 row selected) --- */
.adj-bulk-bar {
position: fixed;
left: 50%;
bottom: 1.5rem;
transform: translateX(-50%);
z-index: 1050;
background: var(--bg-card);
border: 2px solid var(--accent);
border-radius: 2rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
padding: 0.6rem 1.25rem;
display: flex;
align-items: center;
gap: 1rem;
animation: adj-bulk-bar-in 180ms ease-out;
}
.adj-bulk-bar[hidden] { display: none; }
@keyframes adj-bulk-bar-in {
from { opacity: 0; transform: translate(-50%, 10px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
/* --- Empty state card --- */
.adj-empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
}
.adj-empty-state .adj-empty-icon { font-size: 2.5rem; opacity: 0.35; margin-bottom: 1rem; }
/* --- Group-by toggle pill buttons (Flat / By Type / By Worker) --- */
.adj-groupby-toggle .btn { font-size: 0.8rem; padding: 0.3rem 0.75rem; }
/* --- Sort header arrows --- */
th.sortable { cursor: pointer; user-select: none; }
th.sortable .sort-arrow {
opacity: 0.4;
margin-left: 0.25rem;
font-size: 0.7rem;
transition: opacity 120ms;
}
th.sortable:hover .sort-arrow,
th.sortable.sorted .sort-arrow { opacity: 1; }
/* =============================================================================
* ADJUSTMENTS TAB — pill-popover checkbox list
* The Type / Workers / Teams filters each open a popover that reuses the
* shared .filter-popover styles (see "Inline Filters" block above). This
* section only adds the bits specific to the checkbox-list body — the rest
* of the visual vocabulary (pill button, popover chrome, sticky footer)
* is inherited.
* ============================================================================= */
/* --- Scrollable checkbox list inside each Adjustments popover --- */
.adj-checkbox-list {
max-height: 280px;
overflow-y: auto;
border: 1px solid var(--border-subtle);
border-radius: 0.375rem;
padding: 0.25rem 0.5rem;
}
/* Each row is a full-width <label> so clicking the text toggles the checkbox */
.adj-cb-row {
cursor: pointer;
margin: 0;
padding: 0.2rem 0.25rem;
border-radius: 0.25rem;
transition: background-color 120ms;
}
.adj-cb-row:hover { background: var(--bg-card-hover); }
.adj-cb-label { user-select: none; }
/* --- Count badge shown on the pill when 2+ options are selected --- */
/* (For 0 or 1 selected the label text carries the info; the badge stays hidden.) */
.filter-pill__count {
font-size: 0.75em;
opacity: 0.75;
font-weight: 600;
}