/* ═══════════════════════════════════════════════════
   admin.css — Cornerstone Admin Design System
   ═══════════════════════════════════════════════════ */

@import url('https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700;800&family=Source+Serif+4:opsz,ital,wght@8..60,0,500;8..60,0,600;8..60,1,500;8..60,1,600&display=swap');

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { overflow-x: hidden; max-width: 100%; }

:root {
  --navy:          #0e2f47;
  --navy-topbar:   #0a2536;
  --navy-light:    #123a55;
  --navy-muted:    #5c778c;
  --gold:          #c9a227;
  --gold-light:    #d8b53f;
  --gold-dark:     #9a7716;
  --cream:         #efe9df;
  --white:         #ffffff;
  --charcoal:      #1c2b36;
  --gray:          #6b7680;
  --gray-light:    #ece9e2;
  --border:        #ddd6c8;
  --border-soft:   rgba(14,47,71,0.07);
  --gray-muted:    color-mix(in srgb, var(--gray) 60%, var(--white));
  --gray-pale:     #eef2f5;
  --danger:        #b23b2e;
  --success:       #2f8f57;
  --success-light: #e6f4ea;
  --success-dark:  #25774a;
  --warning:       #b8862c;
  --warning-light: #fff8e6;
  --warning-dark:  #805d1f;
  --danger-light:  #fdeaea;
  --danger-dark:   #8b2d23;
  --font-ui:       'Albert Sans', system-ui, sans-serif;
  --font-serif:    'Source Serif 4', serif;
  --radius-sm:     8px;
  --radius-md:     12px;
  --radius-lg:     16px;
  --sidebar-width: 240px;
  --topbar-height: 60px;

  /* Spacing scale (2026-08-20, Diego/Kai UI audit) — new/touched CSS
     should pick from here instead of an ad-hoc rem value. Existing
     working CSS is not being retrofitted wholesale, just no new
     one-off numbers going forward. */
  --space-1: 0.25rem;  /* 4px  */
  --space-2: 0.5rem;   /* 8px  */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-6: 1.5rem;   /* 24px */
  --space-8: 2rem;     /* 32px */
  --space-12: 3rem;    /* 48px */

  /* Modal/overlay layering (2026-08-20) — promoted from the pattern
     first used for admin-jobs.html's Move Hours / PO Editor modals
     (a secondary modal opening from within an already-open one). Every
     page's modal stack should reference these instead of inventing its
     own number — see [[feedback_...]] pattern: never hardcode z-index
     for a new modal/overlay/dropdown.
       base       — the app's own single-level modals (.modal-overlay)
       secondary  — a modal that opens from within an already-open modal
       dropdown   — menus/popovers anchored to a trigger (kebab menus,
                    the Cell Editor popover, filter dropdowns)
       toast      — feedback banners, always above everything else
     .modal-overlay's own z-index (500) predates this token and is left
     as a literal — --z-modal-base sits one layer above it (600) so a
     page can go plain-modal (500) -> secondary (600/700) without ever
     colliding with the also-pre-existing --z-modal-base users
     (admin-jobs.html's Job Dashboard, at 600). */
  --z-dropdown: 400;
  --z-modal-base: 600;
  --z-modal-secondary: 700;
  /* tertiary (2026-08-21): a modal that can itself be opened from inside
     a secondary modal — e.g. Import PO Items, launched from within the
     already-secondary PO Editor. Always used regardless of which context
     a shared modal opens from (Job Dashboard vs. PO Editor); one layer
     too high never causes a bug, one layer too low does. */
  --z-modal-tertiary: 800;
  --z-toast: 1000;

  /* Modal surface tokens (2026-09-04) — one scrim + blur for EVERY modal
     backdrop (.modal-overlay and admin-jobs' Job Dashboard, which now
     rides the same shared base). Was two near-identical navy scrims
     hardcoded in two places (rgba(10,37,54,0.6) here, rgba(13,46,63,0.65)
     inline in admin-jobs); unified so a scrim/blur change is one edit,
     not a hunt across page-local styles. Change the backdrop feel here. */
  --modal-scrim: rgba(10,37,54,0.6);
  --modal-blur: 3px;

  /* Breakpoints (2026-08-20) — CSS custom properties can't be used
     inside @media conditions, so these are the canonical NUMBERS to
     copy into any new media query; existing queries at other values
     (600/640/680/700/860/900/980/1100...) migrate opportunistically
     when that page is next touched, not in one risky sweep.
       480  — small phone
       768  — phone/tablet boundary
       1024 — tablet/desktop boundary */

  /* Page gutters (2026-09-05, parametric-foundation pass) — THE outer
     padding for page content. .admin-content consumes these; a page must
     never re-decide its own outer padding. Changing one value here
     restyles every normal admin page at that breakpoint. */
  --page-gutter-mobile:  1rem;
  --page-gutter-tablet:  1.5rem;
  --page-gutter-desktop: 2rem;

  /* Elevation (2026-09-05) — the three shadow levels actually used
     across the app (cards / raised cards+dropdowns / modals). Pick from
     these; don't invent new box-shadow recipes for standard surfaces. */
  --shadow-1: 0 1px 3px rgba(13,46,63,0.06);
  --shadow-2: 0 4px 16px rgba(13,46,63,0.08);
  --shadow-3: 0 12px 40px rgba(13,46,63,0.18);
}

html, body { height: 100%; font-family: var(--font-ui); background: var(--gray-light); color: var(--charcoal); -webkit-font-smoothing: antialiased; }

/* ════════════════════════════════════════
   LOCK SCREEN
   ════════════════════════════════════════ */
#lock-screen {
  position: fixed;
  inset: 0;
  background: var(--navy);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
#lock-screen.hidden { display: none; }

.lock-box {
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: var(--radius-lg, 16px);
  padding: 3rem 2.5rem;
  width: 100%;
  max-width: 360px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 1.6rem;
  backdrop-filter: blur(8px);
}
.lock-wordmark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}
.lock-wordmark-top {
  font-family: var(--font-serif);
  font-size: 2.2rem;
  font-weight: 600;
  color: var(--white);
  letter-spacing: 0.02em;
  line-height: 1;
}
.lock-wordmark-sub {
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.38em;
  text-transform: uppercase;
  color: var(--gold-light);
}
.lock-wordmark-rule {
  width: 32px;
  height: 1px;
  background: rgba(201,162,39,0.5);
  margin-top: 0.4rem;
}
.lock-title {
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.35);
}
.lock-input {
  border: 1px solid rgba(255,255,255,0.15);
  background: rgba(255,255,255,0.07);
  border-radius: var(--radius-md, 12px);
  padding: 0.9rem 1rem;
  font-family: var(--font-ui);
  font-size: 16px;
  color: var(--white);
  outline: none;
  width: 100%;
  text-align: center;
  letter-spacing: 0.12em;
  transition: border-color 0.2s, background 0.2s;
  touch-action: manipulation;
}
.lock-input:focus { border-color: var(--gold); background: rgba(255,255,255,0.1); }
.lock-input.error { border-color: #e74c3c; animation: shake 0.3s; }
.lock-btn {
  background: var(--gold);
  color: var(--navy);
  border: none;
  border-radius: var(--radius-md, 12px);
  padding: 0.9rem;
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  width: 100%;
  transition: background 0.2s;
}
.lock-btn:hover { background: var(--gold-light); }
.lock-error {
  color: #f87171;
  font-size: 0.78rem;
  min-height: 1rem;
}
#lock-view-login, #lock-view-forgot {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.lock-forgot-btn {
  background: none;
  border: none;
  color: rgba(255,255,255,0.45);
  font-size: 0.72rem;
  cursor: pointer;
  font-family: inherit;
  letter-spacing: 0.06em;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.2s;
  padding: 0;
}
.lock-forgot-btn:hover { color: rgba(255,255,255,0.8); }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25%       { transform: translateX(-6px); }
  75%       { transform: translateX(6px); }
}

/* ════════════════════════════════════════
   ADMIN BODY + TOPBAR
   ════════════════════════════════════════ */
body.admin-body { margin: 0; }

.admin-topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--topbar-height);
  background: var(--navy-topbar);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.5rem;
  z-index: 200;
  box-shadow: 0 2px 12px rgba(0,0,0,0.25);
  border-bottom: 1px solid rgba(255,255,255,0.06);
}
.topbar-left { display: flex; align-items: center; gap: 1rem; }
.topbar-logo img { height: 36px; width: auto; }
.topbar-divider { width: 1px; height: 22px; background: rgba(255,255,255,0.2); }
.topbar-title {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold-light);
  background: transparent;
  padding: 0.3rem 0.7rem;
  border: 1px solid rgba(201,162,39,0.5);
  border-radius: var(--radius-sm, 8px);
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}
.dev-badge {
  display: inline-flex;
  align-items: center;
  background: #c026d3;
  color: var(--white);
  font-size: 0.58rem;
  padding: 2px 7px;
  border-radius: 99px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  vertical-align: middle;
  border: none;
}
.topbar-right { display: flex; align-items: center; gap: 0.75rem; }
.topbar-link {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--gray-muted);
  text-decoration: none;
  transition: color 0.2s;
}
.topbar-link:hover { color: var(--white); }
.topbar-user {
  font-size: 0.72rem;
  color: rgba(255,255,255,0.4);
  padding-left: 0.75rem;
  border-left: 1px solid rgba(255,255,255,0.15);
}
/* Truly centered on the topbar, not just centered in whatever gap is left
   between .topbar-left and .topbar-right — those two are different widths,
   so leaving this in normal flex flow (space-between) pulled the search
   box off-center toward whichever side was narrower. */
.topbar-search {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 260px;
}
.topbar-search input {
  width: 100%;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: var(--radius-sm, 8px);
  color: var(--white);
  font-family: var(--font-ui);
  font-size: 0.78rem;
  padding: 0.45rem 0.7rem 0.45rem 2rem;
  outline: none;
  transition: background 0.15s, border-color 0.15s;
}
.topbar-search input::placeholder { color: rgba(255,255,255,0.4); }
.topbar-search input:focus { background: rgba(255,255,255,0.14); border-color: rgba(201,162,39,0.5); }
.topbar-search-icon {
  position: absolute;
  left: 0.65rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.78rem;
  color: rgba(255,255,255,0.4);
  pointer-events: none;
}
.topbar-search-results {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--white);
  border: 1px solid rgba(14,47,71,0.12);
  border-radius: var(--radius-md, 12px);
  box-shadow: 0 10px 30px rgba(14,47,71,0.22);
  padding: 0.4rem;
  z-index: 900;
  max-height: 360px;
  overflow-y: auto;
  display: none;
}
.topbar-search-results.open { display: block; }
.tsr-group-label {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gray);
  padding: 0.4rem 0.6rem 0.2rem;
}
.tsr-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-sm, 8px);
  cursor: pointer;
  color: var(--charcoal);
  font-size: 0.82rem;
}
.tsr-item:hover, .tsr-item.active { background: var(--gray-light); }
.tsr-item-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tsr-item-meta { font-size: 0.68rem; color: var(--gray); flex-shrink: 0; }
.tsr-empty { padding: 0.7rem 0.6rem; font-size: 0.78rem; color: var(--gray); }
@media (max-width: 900px) {
  .topbar-search { display: none; }
}

/* mobile hamburger */
.topbar-hamburger {
  display: none;
  background: none;
  border: none;
  color: var(--white);
  font-size: 1.3rem;
  cursor: pointer;
  padding: 0.3rem;
}

/* ════════════════════════════════════════
   SIDEBAR
   ════════════════════════════════════════ */
.admin-sidebar {
  position: fixed;
  left: 0;
  top: var(--topbar-height);
  width: var(--sidebar-width);
  height: calc(100vh - var(--topbar-height));
  background: var(--navy);
  overflow-y: auto;
  z-index: 100;
  display: flex;
  flex-direction: column;
  padding: 1.5rem 0 2rem;
  transition: transform 0.3s ease;
}
.admin-sidebar::-webkit-scrollbar { width: 4px; }
.admin-sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); }

.sidebar-section {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--navy-muted);
  padding: 1.2rem 1.3rem 0.5rem;
}
.sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.65rem 1.3rem;
  margin-right: 0.6rem;
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--gray-muted);
  text-decoration: none;
  transition: color 0.2s, background 0.2s, border-color 0.2s;
  border-left: 3px solid transparent;
  border-radius: 0 8px 8px 0;
  letter-spacing: 0.02em;
}
.sidebar-link:hover { color: var(--white); background: rgba(255,255,255,0.05); }
.sidebar-link.active {
  color: var(--gold-light);
  border-left-color: var(--gold);
  background: rgba(201,162,39,0.14);
  font-weight: 600;
}
.sidebar-link .icon { font-size: 0.95rem; flex-shrink: 0; display: flex; align-items: center; }
.sidebar-link .icon svg { display: block; opacity: 0.7; transition: opacity 0.2s; }
.sidebar-link:hover .icon svg, .sidebar-link.active .icon svg { opacity: 1; }

.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 99;
}

/* ════════════════════════════════════════
   CONTENT AREA
   ════════════════════════════════════════ */
.admin-content {
  margin-left: var(--sidebar-width);
  margin-top: var(--topbar-height);
  /* Page gutter is a token (2026-09-05) — change --page-gutter-* in :root,
     never re-decide outer padding per page. */
  padding: var(--page-gutter-desktop);
  min-height: calc(100vh - var(--topbar-height));
  background: var(--gray-light);
  width: calc(100% - var(--sidebar-width));
  max-width: 100%;
}

/* ════════════════════════════════════════
   PAGE HEADER
   ════════════════════════════════════════ */
.admin-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.75rem;
  flex-wrap: wrap;
}
.admin-page-header h1 {
  font-family: var(--font-serif);
  font-size: 2.3rem;
  font-weight: 600;
  color: var(--navy);
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.admin-page-header .page-subtitle {
  font-size: 0.86rem;
  color: var(--gray);
  margin-top: 0.25rem;
}
.admin-page-header .page-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }

/* ════════════════════════════════════════
   CARDS
   ════════════════════════════════════════ */
.admin-card {
  background: var(--white);
  border: 1px solid rgba(14,47,71,0.05);
  border-radius: var(--radius-lg, 16px);
  box-shadow: 0 1px 2px rgba(14,47,71,0.04), 0 10px 28px rgba(14,47,71,0.09);
  padding: 1.75rem;
}
.admin-card-title {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin-bottom: 1.1rem;
}

/* Stat Cards */
.stat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
  margin-bottom: 1.75rem;
}
.stat-card {
  background: var(--white);
  border: 1px solid rgba(14,47,71,0.05);
  border-radius: var(--radius-lg, 16px);
  box-shadow: 0 1px 2px rgba(14,47,71,0.04), 0 10px 28px rgba(14,47,71,0.09);
  padding: 1.35rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.stat-card-value {
  font-family: var(--font-serif);
  font-size: 2.6rem;
  font-weight: 600;
  color: var(--navy);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat-card-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gray-muted);
}
.stat-card.accent { background: var(--navy); border-color: var(--navy); }
.stat-card.accent .stat-card-value { color: var(--gold-light); }
.stat-card.accent .stat-card-label { color: var(--gold-light); }

/* Quick action cards */
.quick-actions {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1rem;
}
.quick-card {
  background: var(--white);
  border: 1px solid rgba(14,47,71,0.05);
  border-radius: var(--radius-md, 12px);
  padding: 1.35rem;
  text-decoration: none;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  box-shadow: 0 1px 2px rgba(14,47,71,0.03), 0 6px 18px rgba(14,47,71,0.07);
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}
.quick-card:hover {
  box-shadow: 0 10px 26px rgba(14,47,71,0.14);
  transform: translateY(-3px);
}
.quick-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(201,162,39,0.13);
  color: var(--gold-dark);
  flex-shrink: 0;
  transition: background 0.2s, color 0.2s, transform 0.2s;
}
.quick-card:hover .quick-card-icon { background: var(--gold); color: var(--navy); transform: scale(1.06); }
.quick-card-label {
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--navy);
}
.quick-card-desc { font-size: 0.74rem; color: var(--gray); line-height: 1.45; }

/* ════════════════════════════════════════
   TABLES
   ════════════════════════════════════════ */
.table-wrap { overflow-x: auto; }
.admin-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 0.88rem;
}
.admin-table thead th {
  text-align: left;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gray-muted);
  padding: 0.75rem;
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
  background: var(--white);
}
.admin-table tbody td {
  padding: 0.95rem 0.75rem;
  border-bottom: 1px solid var(--border-soft);
  color: var(--charcoal);
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table tbody tr:hover td { background: var(--gray-light); }
.admin-table .td-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* ── Minimalist row: avatar + stacked name/username ── */
.row-person { display: flex; align-items: center; gap: 0.7rem; min-width: 0; }
.row-avatar {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(14,47,71,0.08);
  color: var(--navy);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  flex-shrink: 0;
}
.row-person-text { min-width: 0; display: flex; flex-direction: column; }
.row-person-name { font-weight: 600; color: var(--navy); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-person-sub { font-size: 0.78rem; color: var(--gray); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Merged summary cell (e.g. rate • hours) ──
   flex-wrap so a trailing badge (e.g. "No OT") wraps to its own line
   instead of overflowing past the card edge — only bites on the
   narrow #emp-table mobile card layout, where this cell's column is a
   fixed 1fr share of the card rather than a table cell free to grow;
   a no-op on desktop, where there's always room for it on one line. */
.row-summary { display: flex; align-items: center; gap: 0.5rem; color: var(--charcoal); flex-wrap: wrap; }
.row-summary-icon {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(14,47,71,0.06);
  color: var(--gray);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.68rem;
  flex-shrink: 0;
}
.row-summary-dot { color: var(--gray-muted); margin: 0 0.15rem; }

/* ── View + kebab overflow actions ── */
.td-actions-min { display: flex; align-items: center; gap: 0.4rem; }
.kebab-btn {
  width: 30px; height: 30px;
  border-radius: var(--radius-sm, 8px);
  border: 1px solid rgba(14,47,71,0.14);
  background: var(--white);
  color: var(--gray);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.kebab-btn:hover, .kebab-btn.active { border-color: var(--navy); color: var(--navy); background: var(--gray-light); }
.row-menu {
  position: fixed;
  min-width: 160px;
  background: var(--white);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md, 12px);
  box-shadow: 0 12px 28px rgba(14,47,71,0.14);
  padding: 0.4rem;
  z-index: 800;
  display: none;
}
.row-menu.open { display: block; }
.row-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 0.55rem 0.7rem;
  border-radius: var(--radius-sm, 8px);
  font-family: var(--font-ui);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--charcoal);
  cursor: pointer;
}
.row-menu-item:hover { background: var(--gray-light); }
.row-menu-item.danger { color: var(--danger); }
.row-menu-item.danger:hover { background: rgba(178,59,46,0.08); }
.row-menu-divider { height: 1px; background: rgba(14,47,71,0.08); margin: 0.3rem 0.4rem; }

/* Empty state */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 3.5rem 2rem;
  color: var(--gray);
  text-align: center;
}
.empty-icon { display: flex; align-items: center; justify-content: center; font-size: 2.5rem; opacity: 0.35; }
.empty-text { font-size: 0.88rem; line-height: 1.6; }

/* ════════════════════════════════════════
   BADGES
   ════════════════════════════════════════ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.28rem 0.72rem;
  border-radius: 999px;
  font-size: 0.63rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.badge-success { background: rgba(47,143,87,0.13); color: var(--success); }
.badge-warning { background: rgba(201,162,39,0.18); color: var(--gold-dark); }
.badge-danger  { background: rgba(178,59,46,0.12); color: var(--danger); }
.badge-gray    { background: rgba(14,47,71,0.07); color: var(--gray); }
.badge-navy    { background: rgba(14,47,71,0.08); color: var(--navy); }
.badge-active  { background: rgba(47,143,87,0.13); color: var(--success); }
.badge-inactive { background: rgba(14,47,71,0.07); color: var(--gray); }

/* Canonical Status Badge (2026-08-20 UI audit) — .badge/.badge-* above
   already covers every status color a page needs (success/warning/
   danger/gray/navy/active/inactive). Several pages instead grew their
   own one-off badge class (.jd-badge, .ot-badge, .cat-badge, .open-badge,
   .day-hrs-badge, .session-open-badge, .cat-count-badge — one file even
   defines .cat-badge twice) that all render the same concept slightly
   differently. New status indicators anywhere in the app should use
   .badge + a color modifier above, not a new page-local class; existing
   duplicates migrate opportunistically as their page is touched. */

/* ════════════════════════════════════════
   EMPTY STATE
   ════════════════════════════════════════ */
/* Canonical "nothing here yet" pattern (2026-08-20 UI audit) — a dozen
   page-local equivalents existed before this (.jd-empty, .sis-empty,
   .row-empty, .pt-cell-empty, .pl-detail-empty, .emp-detail-empty,
   .job-emp-assign-empty, .job-cat-empty, .empty-gallery, .empty...),
   all the same idea reimplemented. This is the one new sections should
   reach for; a bare message ("No materials added yet.") still works
   with just .empty-state — the icon/action slots are optional. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-2, 0.5rem);
  padding: var(--space-8, 2rem) var(--space-4, 1rem);
  color: var(--gray);
}
.empty-state.empty-state-inline {
  padding: var(--space-4, 1rem) var(--space-2, 0.5rem);
  gap: var(--space-1, 0.25rem);
}
.empty-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--gray-light);
  color: var(--gray-muted);
  margin-bottom: var(--space-1, 0.25rem);
}
.empty-icon svg { width: 18px; height: 18px; }
.empty-text {
  font-size: 0.85rem;
  color: var(--gray);
}
.empty-state .btn { margin-top: var(--space-1, 0.25rem); }

/* Loading & Error share the .empty-state centered layout (2026-09-04) —
   the same three-part block (icon/spinner · text · optional action) for
   all three async states, so a section swapping loading → empty → error
   never shifts. Rendered by ShellState in js/shell.js; a page can also
   hand-write <div class="empty-state state-error">…</div> directly. Before
   this, pages reinvented these inline (e.g. Materials' hardcoded
   "No materials recorded in this period." div). */
.empty-state.state-error .empty-icon { background: rgba(178,59,46,0.1); color: var(--danger); }
.empty-state.state-error .empty-text { color: var(--danger); }
.state-spinner {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2.5px solid var(--gray-light);
  border-top-color: var(--gold);
  animation: state-spin 0.7s linear infinite;
}
@keyframes state-spin { to { transform: rotate(360deg); } }
/* Honor reduced-motion: keep a slow turn (so "still working" still reads)
   rather than freezing the only liveness cue. */
@media (prefers-reduced-motion: reduce) { .state-spinner { animation-duration: 2.4s; } }

/* ════════════════════════════════════════
   FORM GROUPS
   ════════════════════════════════════════ */
.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.form-group label {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gray);
}
/* 2026-09-05 audit: 8 pages put class="form-input" on fields OUTSIDE any
   .form-group (Intelligence's Ask box, Punch Logs' import select, Settings'
   site-content fields…) expecting this styling — but only the wrapped
   selector existed, so those fields rendered as bare unstyled native
   inputs. The classes now carry the exact same rule on their own. */
.form-group input,
.form-group select,
.form-group textarea,
.form-input,
.form-select {
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 12px);
  padding: 0.7rem 0.9rem;
  font-family: var(--font-ui);
  font-size: 16px; /* 16px minimum prevents iOS auto-zoom on any screen size */
  color: var(--charcoal);
  outline: none;
  background: var(--white);
  transition: border-color 0.2s;
  width: 100%;
}
/* Focus reads as ink, not as gold (Lucas, 2026-09-07: "minimalista, sem
   muitas dessas cores fortes, com tom de preto"). A quiet navy edge with a
   soft ring says "you are here" without shouting; gold stays for actions
   that genuinely are the brand's accent. */
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.form-input:focus,
.form-select:focus,
.admin-content select:focus {
  border-color: rgba(14,47,71,0.55);
  box-shadow: 0 0 0 3px rgba(14,47,71,0.08);
}

/* ── Select ──
   EVERY select inside admin content, not only the ones that remembered a
   class: on the Intelligence page a bare <select> fell through to the
   browser's native control, which looked nothing like the rest of the app
   (Lucas, 2026-09-07). Styling the element itself means a select added
   tomorrow is already correct. */
.form-group select,
select.form-input,
.form-select,
.admin-content select {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md, 12px);
  padding: 0.62rem 2.4rem 0.62rem 0.85rem;
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 500;
  color: var(--charcoal);
  background-color: var(--white);
  /* Thin chevron in navy at low opacity — a hint, not a fixture. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' stroke='%230e2f47' stroke-opacity='0.55' stroke-width='1.4' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  outline: none;
  cursor: pointer;
  transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease;
}
.form-group select:hover,
select.form-input:hover,
.form-select:hover,
.admin-content select:hover {
  border-color: rgba(14,47,71,0.22);
  background-color: #fcfbf9;
}
/* The native option list can't be styled, but its text can inherit ours. */
.admin-content select option { color: var(--charcoal); font-weight: 500; }
.form-group textarea { resize: vertical; min-height: 80px; }

/* Touch sizing for EVERY select, including the class-less ones the rule
   above now covers (2026-09-07). */
@media (max-width: 640px) {
  .admin-content select { min-height: 44px; font-size: 16px; }
}
.form-hint { font-size: 0.72rem; color: var(--gray); }
.form-checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-ui);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--charcoal);
  cursor: pointer;
}
.form-checkbox-label input[type="checkbox"] {
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--gold);
  cursor: pointer;
}

.history-toggle-btn {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-ui);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gray);
  cursor: pointer;
}
.history-toggle-btn:hover { color: var(--navy); }
.history-toggle-btn .history-toggle-chevron { transition: transform 0.15s; flex-shrink: 0; }
.history-toggle-btn.open .history-toggle-chevron { transform: rotate(90deg); }
.emp-history-header { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.emp-history-add-link {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-ui);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--gold-dark, var(--gold));
  cursor: pointer;
}
.emp-history-add-link:hover { color: var(--navy); }
.emp-history-panel {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.6rem;
  /* Was a flat 240px — about three rows, inside a modal that scrolls,
     inside an overlay that also scrolls. Small AND the third layer of
     nesting, which is most of why the scroll felt wrong (Lucas,
     2026-09-09). It now takes the room the screen actually has. */
  max-height: min(52vh, 30rem);
  overflow-y: auto;
  overscroll-behavior: contain;   /* stop the gesture chaining outward */
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
  padding: 0.15rem;
}
@media (max-width: 640px) {
  /* On a phone the modal is already a bottom sheet, so the panel can take
     more of it without pushing the footer out of reach. */
  .emp-history-panel { max-height: min(58dvh, 34rem); }
}
.emp-history-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0.75rem;
  background: var(--gray-light);
  border-radius: var(--radius-sm, 8px);
  font-size: 0.82rem;
  color: var(--charcoal);
}
.emp-history-info { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.emp-history-change { line-height: 1.4; }
.emp-history-change strong { color: var(--navy); }
.emp-history-date {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--navy-muted);
}
.emp-history-actions { display: flex; gap: 0.35rem; flex-shrink: 0; margin-left: auto; }
.emp-history-edit-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 8px);
  color: var(--navy-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.emp-history-edit-btn:hover { color: var(--navy); border-color: var(--gold); }
.emp-history-delete-btn:hover { color: var(--danger); border-color: var(--danger); }
.emp-history-edit-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  background: var(--gray-light);
  border-radius: var(--radius-sm, 8px);
}
.emp-history-edit-row input {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.4rem 0.5rem;
  font-size: 0.82rem;
  font-family: var(--font-ui);
  width: auto;
  min-height: 32px;
}
.emp-history-edit-row select {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.4rem 0.5rem;
  font-size: 0.82rem;
  font-family: var(--font-ui);
  min-height: 32px;
}
.emp-history-edit-row .btn-sm { min-height: 32px; }

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
.form-row-3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 1rem;
}
/* Grid items default to min-width:auto, so a native <input type="date">/
   <input type="time"> — whose intrinsic minimum rendering width on iOS
   Safari is wider than a narrow phone's half-screen 1fr track — refuses
   to shrink and pushes the row (and whatever wraps it) past the modal's
   edge instead of just filling its own column. Rides along on every
   .form-row/.form-row-3 everywhere, including pages that force a 2-up
   layout back on mobile (e.g. admin-punch-logs.html's Punch In/Out).
   2026-08-31, Lucas: "o text box está saindo da janela". */
.form-row > *, .form-row-3 > * { min-width: 0; }

/* Toggle switch */
.toggle-wrap {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.toggle-switch {
  position: relative;
  width: 44px;
  height: 24px;
  flex-shrink: 0;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
  position: absolute;
  inset: 0;
  background: #d1d5db;
  border-radius: 24px;
  cursor: pointer;
  transition: background 0.2s;
}
.toggle-slider::before {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  background: white;
  border-radius: 50%;
  top: 3px;
  left: 3px;
  transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.toggle-switch input:checked + .toggle-slider { background: var(--gold); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(20px); }
.toggle-label { font-size: 0.88rem; color: var(--charcoal); }

/* ════════════════════════════════════════
   BUTTONS
   ════════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.7rem 1.4rem;
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: none;
  border-radius: var(--radius-sm, 8px);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.2s, color 0.2s, border-color 0.2s, transform 0.15s;
  white-space: nowrap;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { transform: translateY(-1px); }
/* Color-emoji glyphs (📊🖨📷🚫👁🗑…) render via the system color-emoji
   font, which — unlike plain text symbols (⬇✎✕) — doesn't reliably
   leave room for a following literal space character, so "🖨 Print"
   renders with the icon crowding straight into the label. Wrap the
   icon in <span class="icon"> and this margin guarantees the gap
   regardless of glyph/font quirks. */
.btn .icon { margin-right: 0.4rem; }
.btn-navy    { background: var(--navy); color: var(--white); }
.btn-navy:hover { background: var(--navy-light); }
.btn-gold    { background: var(--gold); color: var(--navy); }
.btn-gold:hover { background: var(--gold-light); }
.btn-danger  { background: var(--danger); color: var(--white); }
.btn-danger:hover { background: #963025; }
.btn-outline {
  background: transparent;
  border: 1px solid rgba(14,47,71,0.2);
  color: var(--navy);
  font-weight: 700;
}
.btn-outline:hover { border-color: var(--navy); background: rgba(14,47,71,0.04); }
.btn-outline-navy {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.3);
  color: rgba(255,255,255,0.8);
}
.btn-outline-navy:hover { border-color: rgba(255,255,255,0.7); color: var(--white); }
.btn-outline-danger {
  background: transparent;
  border: 1px solid rgba(178,59,46,0.35);
  color: var(--danger);
  font-weight: 700;
}
.btn-outline-danger:hover { border-color: var(--danger); background: rgba(178,59,46,0.06); }
.btn-sm {
  padding: 0.45rem 0.9rem;
  font-size: 0.7rem;
  border-radius: var(--radius-sm, 8px);
}
.btn-xs {
  padding: 0.32rem 0.7rem;
  font-size: 0.64rem;
  letter-spacing: 0.04em;
  border-radius: var(--radius-sm, 8px);
}
.btn-danger.btn-xs { background: var(--danger); color: var(--white); }

/* ════════════════════════════════════════
   FILTER BAR
   ════════════════════════════════════════ */
.filter-bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.filter-bar select,
.filter-bar input[type="date"],
.filter-bar input[type="text"] {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 8px);
  padding: 0.55rem 0.8rem;
  font-family: var(--font-ui);
  font-size: 15px;
  color: var(--charcoal);
  background: var(--white);
  outline: none;
  transition: border-color 0.2s;
}
/* Same ink-not-gold focus as every other control (2026-09-07). */
.filter-bar select:focus,
.filter-bar input:focus {
  border-color: rgba(14,47,71,0.55);
  box-shadow: 0 0 0 3px rgba(14,47,71,0.08);
}
.filter-bar select:hover,
.filter-bar input:hover { border-color: rgba(14,47,71,0.22); }
.filter-bar select {
  appearance: none;
  -webkit-appearance: none;
  border-color: var(--border-soft);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' stroke='%230e2f47' stroke-opacity='0.55' stroke-width='1.4' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.8rem center;
  padding-right: 2.2rem;
  cursor: pointer;
}
.filter-bar input[type="date"],
.filter-bar input[type="text"] { border-color: var(--border-soft); }
.filter-label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gray);
  white-space: nowrap;
}
.filter-spacer { flex: 1; }

/* ════════════════════════════════════════
   MODAL
   ════════════════════════════════════════ */
/* No inner scrollbar on this box (Lucas — same "não gosto de scroll em
   lugar nenhum, fora de um pop-up" rule already applied to .jd-box):
   .modal grows to its real content height instead of capping at 90vh.
   The OVERLAY scrolls the whole card into view (align-items:flex-start
   + overflow-y:auto here) when content is taller than the viewport,
   which reads as ordinary page scroll rather than a scrollbar boxed
   inside a popup. */
/* ── Scrolling, application-wide (Lucas, 2026-09-09: "o scroll está meio
   estranho... tem como suavizar o scroll de tudo") ──────────────────────
   Three things make scrolling feel wrong here: a jump instead of a glide
   when something scrolls programmatically, no momentum on iOS inside a
   scrollable box, and a gesture that reaches the end of one container and
   silently starts moving another. This fixes all three in the shared
   stylesheet rather than per page. The reduce-motion block further down
   already turns the smoothness back off for anyone who asks for that. */
html { scroll-behavior: smooth; }
body { overscroll-behavior-y: none; }   /* no rubber-band on the page itself */
.admin-content,
.table-wrap,
.x-scroll,
[data-scroll] {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--modal-scrim);
  z-index: 500;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 1rem;
  backdrop-filter: blur(var(--modal-blur));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
  overflow-y: auto;
  /* Without this, iOS Safari lets a touch-scroll gesture that reaches
     the end of THIS element's own scroll range "chain" onto the page
     underneath — the overlay is only visually on top, the body behind
     is still a live scrollable context. overscroll-behavior:contain
     tells the browser to stop the scroll right here instead of handing
     it off, on all 31+ modals that share this one overlay class.
     2026-08-31, Lucas: scrolling to the end of a modal on his iPhone
     started scrolling the page behind it. */
  overscroll-behavior: contain;
}
.modal-overlay.active { opacity: 1; pointer-events: all; }
.modal {
  background: var(--white);
  width: 100%;
  /* 520px was a flat cap regardless of screen size — fine on a phone,
     but on a wide desktop window it left most of the horizontal space
     empty while tall forms (Employee edit + Fixed Hours, Job dashboard,
     etc.) forced extra vertical scrolling for no reason. Scales up to
     900px as the viewport widens; the max-width:100% override inside
     the @media(max-width:768px) block below still wins on mobile, so
     the bottom-sheet phone layout Lucas likes is unaffected.
     2026-08-31, Lucas: "na largura, assim fica mais fácil de editar...
     mas eu gosto de como está no celular". */
  max-width: clamp(520px, 60vw, 900px);
  margin: 1rem 0;
  /* A tall modal used to grow past the screen and let the OVERLAY scroll,
     so the header and footer slid away and the gesture handed off between
     containers. Capping it here keeps the frame still and moves the
     scrolling into .modal-body, where it belongs — one scroll, with the
     title and the buttons always reachable. */
  /* 4rem, not 2: the overlay adds 1rem of padding top and bottom and this
     element adds 1rem of margin top and bottom. Subtracting only the padding
     left the footer about 17px below the fold and kept the overlay
     scrolling — the exact thing this cap exists to stop. */
  max-height: calc(100dvh - 4rem);
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg, 16px);
  border-top: 3px solid var(--gold);
  box-shadow: 0 24px 60px rgba(10,37,54,0.25);
  transform: translateY(16px);
  transition: transform 0.2s;
}
.modal-overlay.active .modal { transform: translateY(0); }
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem 1.5rem;
  border-bottom: 1px solid var(--border);
}
.modal-title {
  font-family: var(--font-serif);
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--navy);
}
.modal-close {
  background: none;
  border: none;
  font-size: 1.3rem;
  color: var(--gray);
  cursor: pointer;
  padding: 0.25rem;
  line-height: 1;
  transition: color 0.2s;
}
.modal-close:hover { color: var(--charcoal); }
.modal-body {
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  /* min-height:0 is what actually lets a flex child scroll — without it the
     body refuses to shrink and the cap above does nothing. */
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.modal-footer {
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* Secondary/nested modal (2026-08-20 UI audit) — add this class
   alongside .modal-overlay on any modal that can open FROM WITHIN an
   already-open modal (e.g. admin-jobs.html: Move Hours / the PO Editor,
   both opened from inside the Job Dashboard). Pairs with marking the
   parent's box `inert` while the nested one is open — see either of
   those two for the reference implementation. Never hand-write a
   one-off z-index for this case. */
.modal-overlay-secondary { z-index: var(--z-modal-secondary, 700); }

/* Tertiary/double-nested modal (2026-08-21, Bulk Import) — the shared
   Import wizard can open from inside the PO Editor, which is itself
   already .modal-overlay-secondary. Same inert-the-parent pattern. */
.modal-overlay-tertiary { z-index: var(--z-modal-tertiary, 800); }

/* Generic dim-the-parent companion to `inert` for ANY box a nested modal
   opens above — same visual as admin-jobs.html's older .jd-box-inactive,
   but not scoped to .jd-box, so a single nested modal (like Bulk Import)
   can dim whichever parent box it happens to be opening from. */
.modal-inactive { opacity: 0.45; filter: grayscale(0.2); transition: opacity 0.15s; }

/* ── BULK IMPORT WIZARD (2026-08-21) ──
   Shared by Import Materials and Import PO Items — one modal, driven by
   a config object, reusing .btn/.modal/.form-hint throughout rather than
   inventing new button/input styles. */
.bi-steps { display: flex; gap: var(--space-2, 0.5rem); margin-bottom: var(--space-4, 1rem); font-family: var(--font-ui); font-size: 0.72rem; color: var(--gray); flex-wrap: wrap; }
.bi-steps span { padding: 0.2rem 0.6rem; border-radius: 999px; background: var(--bg-soft, #f4f4f4); }
.bi-steps span.active { background: var(--navy); color: var(--white); font-weight: 600; }
.bi-steps span.done { background: var(--success-light); color: var(--success-dark); }
.bi-dropzone { border: 2px dashed var(--border); border-radius: var(--radius, 8px); padding: var(--space-8, 2rem) var(--space-4, 1rem); text-align: center; cursor: pointer; transition: border-color 0.15s, background 0.15s; }
.bi-dropzone:hover, .bi-dropzone.dragover { border-color: var(--navy); background: var(--bg-soft, #f7f8fa); }
.bi-dropzone input[type=file] { display: none; }
.bi-table-wrap { overflow-x: auto; max-height: 46vh; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius, 8px); }
.bi-table { width: 100%; border-collapse: collapse; font-family: var(--font-ui); font-size: 0.78rem; }
.bi-table th, .bi-table td { padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border); text-align: left; white-space: nowrap; }
.bi-table thead th { background: var(--bg-soft, #f7f8fa); position: sticky; top: 0; }
.bi-table tr.bi-row-invalid { background: var(--danger-light); }
.bi-table tr.bi-row-dup td:first-child { border-left: 3px solid var(--warning-dark); }
.bi-map-row { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr) minmax(0,1fr); gap: var(--space-3, 0.75rem); align-items: center; padding: 0.5rem 0; border-bottom: 1px solid var(--border); }
.bi-map-row label { font-family: var(--font-ui); font-size: 0.8rem; font-weight: 600; color: var(--charcoal); }
.bi-map-row select { width: 100%; }
.bi-map-sample { font-size: 0.72rem; color: var(--gray); overflow: hidden; text-overflow: ellipsis; }
.bi-summary { display: flex; gap: var(--space-4, 1rem); flex-wrap: wrap; font-family: var(--font-ui); font-size: 0.82rem; margin-bottom: var(--space-3, 0.75rem); }
.bi-summary strong { color: var(--navy); }
.bi-warning-banner { background: var(--warning-light); border: 1px solid var(--warning); border-radius: var(--radius, 8px); padding: var(--space-3, 0.75rem); margin-bottom: var(--space-3, 0.75rem); font-size: 0.82rem; }
.bi-warning-banner label { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.5rem; font-weight: 600; }
.bi-error-text { color: var(--danger); font-size: 0.72rem; }
@media (max-width: 700px) {
  .bi-map-row { grid-template-columns: 1fr; gap: 0.25rem; }
}

/* ════════════════════════════════════════
   TOAST
   ════════════════════════════════════════ */
.toast {
  position: fixed;
  bottom: 1.75rem;
  left: 50%;
  transform: translateX(-50%) translateY(80px);
  background: var(--navy);
  color: var(--white);
  padding: 0.75rem 1.75rem;
  font-size: 0.83rem;
  letter-spacing: 0.02em;
  border-radius: var(--radius-md, 12px);
  z-index: 9000;
  white-space: nowrap;
  box-shadow: 0 4px 20px rgba(0,0,0,0.3);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
}
.toast.show { transform: translateX(-50%) translateY(0); }
.toast.toast-success { background: var(--success); }
.toast.toast-danger  { background: var(--danger); }
.toast.toast-warning { background: var(--warning); }

/* ════════════════════════════════════════
   SECTION DIVIDER
   ════════════════════════════════════════ */
.section-title {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin-bottom: 0.8rem;
}

/* ════════════════════════════════════════
   INFO NOTE
   ════════════════════════════════════════ */
.info-note {
  font-size: 0.82rem;
  color: var(--charcoal);
  line-height: 1.6;
  padding: 0.85rem 1rem 0.85rem 1rem;
  background: rgba(201,162,39,0.1);
  border-left: 3px solid var(--gold);
  border-radius: 0 8px 8px 0;
}
.info-note.info-danger  { background: rgba(178,59,46,0.08); border-left-color: var(--danger); color: var(--danger); }
.info-note.info-success { background: rgba(47,143,87,0.08); border-left-color: var(--success); color: var(--success); }

/* ════════════════════════════════════════
   GRID LAYOUT HELPERS
   ════════════════════════════════════════ */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1.5rem; }
.flex-row { display: flex; align-items: center; gap: 0.75rem; }
.flex-col { display: flex; flex-direction: column; gap: 0.75rem; }
.gap-1 { gap: 1rem; }

/* Location test result */
.location-result {
  margin-top: 1rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 12px);
  background: var(--gray-light);
  font-size: 0.88rem;
  line-height: 1.7;
}
.location-result .result-ok  { color: var(--success); font-weight: 600; }
.location-result .result-far { color: var(--danger);  font-weight: 600; }

/* ════════════════════════════════════════
   SUMMARY CARD (payroll)
   ════════════════════════════════════════ */
.summary-card {
  background: var(--navy);
  color: var(--white);
  border-radius: var(--radius-lg, 16px);
  padding: 1.75rem 2rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}
.summary-item-label {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.45);
  margin-bottom: 0.4rem;
}
.summary-item-value {
  font-family: var(--font-serif);
  font-size: 1.9rem;
  font-weight: 600;
  color: var(--white);
  line-height: 1;
}
.summary-item-value.gold { color: var(--gold-light); }
/* Text values (name, date) — readable sans-serif instead of display serif */
.summary-item-value.text-val {
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.35;
  opacity: 0.92;
}

/* ════════════════════════════════════════
   COLLAPSIBLE SECTION
   ════════════════════════════════════════ */
.collapsible-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  padding: 0.85rem 1rem;
  background: var(--gray-light);
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 12px);
  user-select: none;
}
.collapsible-header:hover { background: #e2ddd0; }
.collapsible-title { font-size: 0.78rem; font-weight: 600; color: var(--navy); letter-spacing: 0.1em; text-transform: uppercase; }
.collapsible-arrow { font-size: 0.7rem; color: var(--gray); transition: transform 0.2s; }
.collapsible-header.open .collapsible-arrow { transform: rotate(180deg); }
.collapsible-header.open { border-radius: 10px 10px 0 0; }
.collapsible-body { display: none; border: 1px solid var(--border); border-top: none; border-radius: 0 0 10px 10px; }
.collapsible-body.open { display: block; }

/* ════════════════════════════════════════
   TABLET (iPad-width gap between the 768px mobile breakpoint and
   desktop) — the sidebar/topbar/page grids all already flex down fine
   at this width (verified against the dashboard, quick-action cards,
   etc.), so this block deliberately does NOT switch to the hamburger
   layout below. It rescues exactly one thing: .admin-table forces
   table-layout:fixed + width:100%, which divides column width evenly
   across whatever room is left — comfortable on a desktop-wide
   content area, but at ~500-550px of actual content width (iPad minus
   the still-visible sidebar) that squeezes every column, clipping
   names/labels to unreadable ellipsis instead of ever letting
   .table-wrap's overflow-x:auto engage. Below 768px this same problem
   is already solved with a min-width floor (and #emp-table gets its
   own full card layout instead) — same fix here, just table-layout
   switched to auto too so columns size to their own content instead
   of splitting evenly, which is what actually stops the clipping.
   ════════════════════════════════════════ */
@media (min-width: 769px) and (max-width: 1024px) {
  .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .admin-table { table-layout: auto; width: auto; min-width: 640px; }
  /* Tablet gutter (2026-09-05) — tablet is a real mode, not the gap
     between mobile and desktop rules. */
  .admin-content { padding: var(--page-gutter-tablet); }
}

/* ════════════════════════════════════════
   MOBILE RESPONSIVE
   ════════════════════════════════════════ */
@media (max-width: 768px) {
  .topbar-hamburger { display: block; }

  .admin-sidebar {
    transform: translateX(-100%);
  }
  .admin-sidebar.mobile-open {
    transform: translateX(0);
  }
  .sidebar-overlay.active { display: block; }

  .admin-content {
    margin-left: 0;
    padding: var(--page-gutter-mobile);
  }

  .admin-topbar { padding: 0 0.75rem; }
  .topbar-right { display: none; }     /* hide all topbar links on mobile — use sidebar nav */
  .topbar-user { display: none; }
  .topbar-title { display: none; }
  .topbar-divider { display: none; }
  .admin-content { width: 100%; }

  .admin-page-header { flex-direction: column; align-items: stretch; gap: 0.85rem; }
  /* Stacked, full-width, evenly-spaced — predictable at 1, 2, or 3 buttons,
     instead of an uneven wrap that leaves an odd button stranded alone. */
  .page-actions { display: flex; flex-direction: column; gap: 0.55rem; width: 100%; }
  .page-actions .btn { width: 100%; min-height: 46px; justify-content: center; font-size: 0.78rem; }

  .filter-bar { flex-direction: column; align-items: stretch; gap: 0.6rem; }
  .filter-bar select,
  .filter-bar input[type="date"],
  .filter-bar input[type="text"] { width: 100%; min-height: 44px; font-size: 16px; }
  .filter-label { font-size: 0.72rem; font-weight: 700; margin-bottom: -0.2rem; }
  .filter-spacer { display: none; }

  .stat-cards { grid-template-columns: 1fr 1fr; }
  .quick-actions { grid-template-columns: 1fr 1fr; }
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
  .form-row, .form-row-3 { grid-template-columns: 1fr; }
  .summary-card { grid-template-columns: 1fr 1fr; padding: 1.25rem; }

  /* Form inputs — 16px font prevents iOS zoom */
  .form-group input,
  .form-group select,
  .form-group textarea { font-size: 16px; min-height: 44px; }
  .form-group textarea { min-height: 80px; }

  /* Horizontally scrollable tables */
  .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .admin-table { min-width: 560px; }
  .admin-table th, .admin-table td { white-space: nowrap; padding: 0.6rem 0.75rem; }

  /* Employees table — a scrollable 560px-wide table just truncates names
     ("Luc...", "Der...") and pushes Status off-screen, so this one gets
     a proper card layout instead of the generic scroll-table above. */
  #emp-table.admin-table { min-width: 0; width: 100%; }
  #emp-table thead { display: none; }
  #emp-table, #emp-table tbody { display: block; width: 100%; }
  /* Grid, not flex-wrap: every card gets the exact same 3-row shape
     (name+actions / # / summary+status) via explicit grid-row placement,
     regardless of how short or long the name/summary text is. Flex-wrap
     with a forced 100%-basis line-break was tried first, but forcing an
     item to start a new line also forces it to fill that whole line,
     which pushed Status to its own 3rd line only on short-name rows. */
  #emp-table tr {
    display: grid;
    grid-template-columns: 1fr auto;
    column-gap: 0.75rem;
    row-gap: 0.4rem;
    align-items: center;
    background: var(--white);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 0.9rem;
    margin-bottom: 0.75rem;
    white-space: normal;
  }
  #emp-table tr:last-child { margin-bottom: 0; }
  #emp-table td {
    display: block;
    border-bottom: none;
    padding: 0;
    white-space: normal;
  }
  #emp-table td:nth-child(1) { grid-column: 1; grid-row: 1; min-width: 0; }                          /* name */
  #emp-table td:nth-child(5) { grid-column: 2; grid-row: 1; justify-self: end; }                     /* view/kebab */
  #emp-table td:nth-child(2) { grid-column: 1 / -1; grid-row: 2; font-size: 0.78rem; color: var(--gray); }  /* # */
  #emp-table td:nth-child(2)::before { content: "#"; }
  #emp-table td:nth-child(3) { grid-column: 1; grid-row: 3; }                                        /* summary */
  #emp-table td:nth-child(4) { grid-column: 2; grid-row: 3; justify-self: end; }                     /* status */
  #emp-table .row-person-name,
  #emp-table .row-person-sub { white-space: normal; overflow: visible; text-overflow: clip; }

  /* Modal — slide up from bottom on mobile. "safe flex-end" keeps the
     bottom-sheet look for short content but falls back to top-aligned
     the moment content is taller than the screen, so the overlay's own
     scroll (not a clipped/trapped scrollbar) is what reaches the rest —
     plain "flex-end" would pin the sheet's bottom in place and let its
     top run off-screen with no way to reach it. */
  .modal-overlay { align-items: safe flex-end; padding: 0; }
  .modal {
    width: 100%;
    max-width: 100%;
    margin: 0;
    /* The overlay has no padding on a phone, so the sheet may use the whole
       height — the 2rem the desktop cap leaves for the scrim is not there. */
    max-height: 100dvh;
    border-radius: 16px 16px 0 0;
    border-top: 3px solid var(--gold);
    transform: translateY(40px);
  }
  .modal-overlay.active .modal { transform: translateY(0); }
  .modal-body { padding: 1rem; }
  .modal-footer { flex-direction: column-reverse; }
  .modal-footer .btn { width: 100%; justify-content: center; }

  /* Touch targets */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 40px; }
  .btn-xs { min-height: 36px; }

  .admin-card-title { font-size: 0.85rem; }

  /* Lock screen */
  .lock-box { padding: 2rem 1.5rem; max-width: 100%; margin: 0 1rem; }
  .lock-input { font-size: 16px; min-height: 48px; }

  /* Toast wraps on mobile */
  .toast { white-space: normal; text-align: center; max-width: 90vw; }

  /* Collapsible header text wraps on mobile */
  .collapsible-title { font-size: 0.72rem; }
}

@media (max-width: 480px) {
  .stat-cards { grid-template-columns: 1fr 1fr; gap: 0.6rem; }
  .quick-actions { grid-template-columns: 1fr 1fr; gap: 0.6rem; }
  .summary-card { grid-template-columns: 1fr; }
  .admin-content { padding: var(--page-gutter-mobile); }
  .topbar-logo img { height: 28px; }
}

/* ── Sign Out button (topbar) ── */
.topbar-signout {
  background: none;
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: var(--radius-sm, 8px);
  color: var(--gray-pale);
  padding: 0.35rem 0.8rem;
  font-family: var(--font-ui);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s, background 0.2s;
  white-space: nowrap;
  min-height: 34px;
}
.topbar-signout:hover { border-color: rgba(255,255,255,0.6); color: var(--white); }

/* Manual refresh (2026-08-25) — added for the same reason as the one on
   employee-dashboard.html: a page added to a phone's home screen opens
   with no browser chrome at all, so there's otherwise no pull-to-refresh
   or reload button to force a fresh pull of data. Icon-only, sized to
   match .topbar-signout so the two sit evenly together. Deliberately its
   own class, not .topbar-link — that class is hidden on mobile below,
   and this button needs to stay reachable there too. */
.topbar-refresh {
  background: none;
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: var(--radius-sm, 8px);
  color: var(--gray-pale);
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s, background 0.2s;
}
.topbar-refresh:hover { border-color: rgba(255,255,255,0.6); color: var(--white); }
.topbar-refresh:disabled { opacity: 0.5; cursor: not-allowed; }
.topbar-refresh.spinning svg { animation: topbar-refresh-spin 0.7s linear infinite; }
@keyframes topbar-refresh-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .topbar-refresh.spinning svg { animation: none; }
}

/* On mobile the topbar links are hidden, but Sign Out must stay reachable */
@media (max-width: 768px) {
  .topbar-right { display: flex; }
  .topbar-right .topbar-link { display: none; }
}

/* ════════════════════════════════════════
   FOUNDATION HARDENING (2026-09-05 full-app audit)
   Accessibility, touch ergonomics, motion discipline — shared root, so
   every admin page inherits at once.
   ════════════════════════════════════════ */

/* iOS Safari inflates text on rotation to landscape without this. */
html { -webkit-text-size-adjust: 100%; }

/* Keyboard-visible focus — none of the app had a visible focus state
   (mouse users never noticed; keyboard/switch users were navigating
   blind). :focus-visible only fires for keyboard/AT focus, so mouse
   clicks stay clean. */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 2px;
}
.form-group input:focus-visible,
.form-group select:focus-visible,
.form-group textarea:focus-visible,
.form-input:focus-visible,
.form-select:focus-visible {
  outline: none; /* these already signal focus via gold border-color */
}

::selection { background: rgba(201,162,39,0.28); }

/* Kill the 300ms double-tap-zoom delay + gray tap flash on every
   interactive element, not just the ones that remembered to opt in. */
a, button, [role="button"], input, select, textarea, label {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Digits align in every table (dates, hours, money) — tabular-nums only
   affects numerals, so text cells are untouched. */
td, th { font-variant-numeric: tabular-nums; }

/* Consistent disabled affordance app-wide. */
.btn:disabled { opacity: 0.55; cursor: not-allowed; transform: none; }

/* Comfortable touch targets on phones: primary controls only — dense
   table icon-buttons stay compact by design. */
@media (max-width: 768px) {
  .btn { min-height: 44px; }
  .form-group input,
  .form-group select,
  .form-group textarea,
  .form-input,
  .form-select { min-height: 44px; }
  .topbar-signout, .topbar-refresh { min-height: 40px; }
  .topbar-refresh { width: 40px; }
}

/* Desktop-only slim scrollbars for inner scroll containers, so wide
   tables read as intentional panels instead of OS-chrome boxes. */
@media (pointer: fine) {
  .admin-content ::-webkit-scrollbar { height: 8px; width: 8px; }
  .admin-content ::-webkit-scrollbar-thumb { background: rgba(14,47,71,0.22); border-radius: 4px; }
  .admin-content ::-webkit-scrollbar-thumb:hover { background: rgba(14,47,71,0.4); }
  .admin-content ::-webkit-scrollbar-track { background: transparent; }
}

/* ════════════════════════════════════════
   PAY PRIVACY (2026-09-06, per Lucas — mirrors the employee dashboard's
   toggle). The eye button js/shell.js injects into the topbar toggles
   body.values-hidden; every element carrying .money blurs. Payroll's
   .tc-pay-amount spans are included centrally so its fifteen breakdown
   templates don't each need editing. Blur (not display) keeps layout
   stable; the preference is per-device localStorage, display-only.
   Mark any NEW money readout with class="money".
   ════════════════════════════════════════ */
.money, .tc-pay-amount { transition: filter 0.18s, opacity 0.18s; }
body.values-hidden .money,
body.values-hidden .tc-pay-amount {
  filter: blur(7px);
  opacity: 0.65;
  user-select: none;
}
body.values-hidden .money:focus,
body.values-hidden .money:focus-within { filter: none; opacity: 1; }

/* Motion discipline: honor reduced-motion globally. The one exception is
   the loading spinner — it is the only "still working" cue, so it keeps
   a slow, calm turn instead of freezing (matches the earlier per-rule
   decision above, restated here because this dampener would out-cascade
   it otherwise). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .state-spinner {
    animation: state-spin 2.4s linear infinite !important;
  }
}
