/* ==========================================================================
   CipherX — Design System
   --------------------------------------------------------------------------
   Single source of truth for the application's visual language. Loaded once,
   after Bootstrap, in templates/base.html. Rebrands Bootstrap via its CSS
   custom properties (so existing .btn-primary / .card / .table markup picks
   up the theme with no per-page edits) and adds a small set of reusable
   CipherX components (.cx-*).

   See design.md for the full rationale, palette and usage guidelines.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Brand tokens
   -------------------------------------------------------------------------- */
:root {
  /* Core ink / black — the auth screen's card background (#151515), which the
     white logo_black.svg variant is drawn for */
  --cx-black:        #151515;   /* sidebar, logo backdrop */
  --cx-ink:          #1A1D1F;   /* primary text & headings on light surfaces */
  --cx-ink-soft:     #3A4042;   /* secondary headings */

  /* Signal violet — a cool, saturated accent chosen to sit *with* the cool
     gray neutrals below rather than against them (the earlier warm orange
     was calibrated for a since-removed warm/cream base and read muddy once
     the neutrals went cool). Reads modern/precise; distinct in hue from
     both the neutrals and the teal-leaning steel secondary, so it stays
     the one saturated thing on the page. Primary accent. */
  --cx-accent:       #6E5BEA;   /* fills: buttons, active states, highlights */
  --cx-accent-600:   #5C48D6;   /* hover  */
  --cx-accent-700:   #4A38B8;   /* active / pressed */
  --cx-accent-text:  #4F3FB0;   /* accent used as text/links (AA on white) */
  --cx-accent-soft:  #EFEDFC;   /* tint background */
  --cx-accent-rgb:   110, 91, 234;

  /* Cool neutral grays — replaces the earlier warm/cream base */
  --cx-bg:           #F3F4F6;   /* app background */
  --cx-surface:      #FFFFFF;   /* cards, panels */
  --cx-sand:         #E9EBEF;   /* muted surfaces, hover rows */
  --cx-border:       #E2E5EA;   /* cool hairline borders */

  /* Steel grey — Pantone 7544 U. Secondary / muted. */
  --cx-steel:        #76858B;   /* muted icons & meta */
  --cx-steel-600:    #5C6A70;   /* secondary text (AA on white) */
  --cx-steel-100:    #EDF0F0;   /* subtle steel tint */
  --cx-steel-rgb:    118, 133, 139;

  /* Geometry & elevation */
  --cx-radius-sm:   8px;
  --cx-radius:      12px;
  --cx-radius-lg:   18px;
  --cx-radius-pill: 999px;
  --cx-shadow-sm: 0 1px 2px rgba(21, 21, 21, .06);
  --cx-shadow:    0 2px 10px rgba(21, 21, 21, .06), 0 1px 2px rgba(21, 21, 21, .04);
  --cx-shadow-lg: 0 12px 32px rgba(21, 21, 21, .12);
  --cx-sidebar-width: 250px;

  /* Calendar people colours (Shifts calendar) -- one edge colour and
     one soft fill per slot, muted so the violet accent stays reserved
     for actions and the "now" line. Slot names are listed in
     blueprints/shifts/config.py:CALENDAR_COLOURS. */
  --cx-cal-slate: #5C6A70;  --cx-cal-slate-soft: #EDF0F0;
  --cx-cal-teal:  #2B7F78;  --cx-cal-teal-soft:  #E2F2F0;
  --cx-cal-amber: #A86A12;  --cx-cal-amber-soft: #FBF0DC;
  --cx-cal-rose:  #B23A62;  --cx-cal-rose-soft:  #FBE6ED;
  --cx-cal-sky:   #2C72B0;  --cx-cal-sky-soft:   #E3EEF9;
  --cx-cal-olive: #5E7D1F;  --cx-cal-olive-soft: #EDF3DF;
  /* Events on the calendar: their tag under the day heading and the thin
     bar down the day column while they run. Blue, kept apart from the
     six people colours above so an event never reads as a person. */
  --cx-cal-event:      #1F5FD0;  --cx-cal-event-soft: #E4EDFC;
  /* Event statuses (blueprints/shifts/config.py:EVENT_DISPLAY_LABELS):
     planning = amber (tentative), upcoming = the event blue, running =
     green, finished = grey. Called-off events aren't drawn. */
  --cx-ev-planning: #B0620F;  --cx-ev-planning-soft: #FCEFDC;
  --cx-ev-upcoming: #1F5FD0;  --cx-ev-upcoming-soft: #E4EDFC;
  --cx-ev-running:  #17803D;  --cx-ev-running-soft:  #E1F3E7;
  --cx-ev-finished: #6B7785;  --cx-ev-finished-soft: #ECEFF2;
  --cx-ev-called-off: #B02A2A;  --cx-ev-called-off-soft: #FBE3E3;

  /* Typography */
  --cx-font-sans: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --cx-font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* --------------------------------------------------------------------------
   2. Bootstrap variable overrides (rebrands existing components)
   -------------------------------------------------------------------------- */
:root {
  --bs-primary:           var(--cx-accent);
  --bs-primary-rgb:       var(--cx-accent-rgb);
  --bs-link-color:        var(--cx-accent-text);
  --bs-link-color-rgb:    79, 63, 176;
  --bs-link-hover-color:  var(--cx-accent-700);

  --bs-secondary:         var(--cx-steel);
  --bs-secondary-rgb:     var(--cx-steel-rgb);

  --bs-dark:              var(--cx-black);
  --bs-dark-rgb:          21, 21, 21;

  --bs-body-bg:           var(--cx-bg);
  --bs-body-color:        var(--cx-ink);
  --bs-body-color-rgb:    26, 29, 31;
  --bs-border-color:      var(--cx-border);

  --bs-border-radius:     var(--cx-radius-sm);
  --bs-border-radius-sm:  6px;
  --bs-border-radius-lg:  var(--cx-radius);

  --bs-font-sans-serif: var(--cx-font-sans);
  --bs-font-monospace: var(--cx-font-mono);
}

/* --------------------------------------------------------------------------
   3. Base & typography
   -------------------------------------------------------------------------- */
body {
  font-family: var(--bs-font-sans-serif);
  background-color: var(--cx-bg);
  color: var(--cx-ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  color: var(--cx-ink);
  font-weight: 650;
  letter-spacing: -0.01em;
}

a { text-decoration: none; }
a:hover { text-decoration: none; }

code, kbd, pre, samp { font-family: var(--cx-font-mono); }

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

/* Selection + focus ring in brand accent */
::selection { background: rgba(var(--cx-accent-rgb), .25); }
:focus-visible {
  outline: 2px solid rgba(var(--cx-accent-rgb), .55);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   4. Layout — light sidebar (resizable + collapsible on desktop)
   -------------------------------------------------------------------------- */

/* Replaces Bootstrap's row/col grid so the sidebar can be an arbitrary,
   JS-resizable pixel width (--cx-sidebar-width) instead of a 12-column
   percentage. Stacks on mobile like the old col-12 layout did. */
.cx-shell {
  display: flex;
  flex-wrap: wrap;
  position: relative;
  margin: 0;
}
.cx-shell > .cx-main {
  flex: 1 1 0%;
  min-width: 0;
  width: 100%;
  padding: 1.5rem;
  background: var(--cx-bg);
}
@media (min-width: 768px) {
  .cx-shell { flex-wrap: nowrap; }

  /* Reserves room for the floating toggle button so a hidden sidebar
     doesn't leave it sitting on top of the page title. */
  .cx-shell > .cx-main {
    transition: padding-left .3s cubic-bezier(.4, 0, .2, 1);
  }
  html.cx-sidebar-collapsed .cx-shell > .cx-main {
    padding-left: 3.25rem;
  }
  html.cx-sidebar-resizing .cx-shell > .cx-main {
    transition: none;
  }
}

.sidebar {
  min-height: 100vh;
  background: var(--cx-surface);
  border-right: 1px solid var(--cx-border);
  color: var(--cx-steel-600);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  overflow-x: hidden;
  flex: 0 0 auto;
  width: 100%;
  scrollbar-width: thin;
  scrollbar-color: rgba(21,21,21,.15) transparent;
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(21,21,21,.12); border-radius: 8px; }

@media (min-width: 768px) {
  .sidebar {
    width: var(--cx-sidebar-width);
    transition: width .3s cubic-bezier(.4, 0, .2, 1), opacity .22s ease, border-color .22s ease;
  }
  /* Fixed to the last expanded width regardless of the outer .sidebar's
     current (possibly animating-to-zero) width, so collapsing clips the
     content like a drawer sliding shut instead of reflowing/wrapping it. */
  .cx-sidebar-inner {
    width: var(--cx-sidebar-width);
    min-width: var(--cx-sidebar-width);
  }
  html.cx-sidebar-collapsed .sidebar {
    width: 0;
    opacity: 0;
    pointer-events: none;
    border-right-color: transparent;
  }
  html.cx-sidebar-resizing .sidebar,
  html.cx-sidebar-resizing .cx-sidebar-toggle {
    transition: none;
  }
}

/* Drag handle on the sidebar's right edge */
.cx-sidebar-resizer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 5px;
  cursor: col-resize;
  background: transparent;
  transition: background-color .12s ease;
}
.cx-sidebar-resizer:hover,
.cx-sidebar-resizer:focus-visible {
  background: rgba(var(--cx-accent-rgb), .35);
  outline: none;
}
html.cx-sidebar-resizing .cx-sidebar-resizer { background: rgba(var(--cx-accent-rgb), .5); }

/* Collapse/expand toggle — a slim tab embedded in the sidebar/content
   border, vertically centered on the *viewport*, rather than a floating
   circular button. Straddles the border (it lives outside .sidebar,
   since that box itself is clipped to zero width when collapsed) so it
   stays reachable even while the sidebar is hidden.

   `position: fixed`, not `absolute` -- it's a sibling of .sidebar
   inside .cx-shell, a plain (non-sticky) flex container whose height
   is whatever that page's content happens to add up to, not the
   viewport. `top: 50%` against that varying total page height put the
   toggle at a different on-screen spot depending on which page/project
   was open (a short page vs. a page many viewports tall centers very
   differently) -- `fixed` centers against the viewport itself, which
   is constant, and as a side benefit keeps it reachable without
   scrolling back up on a long page. */
.cx-sidebar-toggle {
  position: fixed;
  top: 50%;
  left: calc(var(--cx-sidebar-width) - 11px);
  transform: translateY(-50%);
  z-index: 30;
  width: 22px;
  height: 46px;
  align-items: center;
  justify-content: center;
  border-radius: var(--cx-radius-sm);
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  color: var(--cx-steel-600);
  box-shadow: none;
  cursor: pointer;
  transition: left .3s cubic-bezier(.4, 0, .2, 1), background-color .12s ease, color .12s ease;
}
.cx-sidebar-toggle:hover { background: var(--cx-sand); color: var(--cx-ink); }
.cx-sidebar-toggle svg { transition: transform .3s cubic-bezier(.4, 0, .2, 1); }
html.cx-sidebar-collapsed .cx-sidebar-toggle {
  left: 11px;
  background: var(--cx-ink);
  border-color: var(--cx-ink);
  color: #fff;
}
html.cx-sidebar-collapsed .cx-sidebar-toggle:hover { background: var(--cx-black); border-color: var(--cx-black); }
html.cx-sidebar-collapsed .cx-sidebar-toggle svg { transform: rotate(180deg); }

.sidebar .logo,
.sidebar .cx-brand img { max-width: 150px; height: auto; display: block; }
.sidebar .cx-brand {
  display: flex;
  justify-content: center;
  padding: .75rem 0 1.5rem;
  margin-bottom: .5rem;
  border-bottom: 1px solid var(--cx-border);
}

/* Top-level nav sections (Employee Portal, Management Portal, Lab,
   General) -- each one divided from the next by a rule, same idiom as
   the one under the logo (.cx-brand), rather than relying on spacing
   alone to separate unrelated groups. No rule above the first section:
   the logo's own bottom border already serves that role there. */
.sidebar .cx-nav-section {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding-top: .75rem;
  margin-top: .5rem;
  border-top: 1px solid var(--cx-border);
}
.sidebar .cx-nav-section:first-child {
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
}

/* Section group labels (e.g. "General", "Admin") */
.sidebar .cx-nav-label {
  display: block;
  padding: .5rem .7rem .35rem;
  font-size: .68rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--cx-steel);
}

/* Nav links inside the light sidebar */
.sidebar .nav-link {
  color: var(--cx-steel-600);
  border-radius: var(--cx-radius-sm);
  padding: .5rem .7rem;
  font-size: .9rem;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: .65rem;
  transition: background-color .12s ease, color .12s ease;
}
.sidebar .nav-link svg { flex: none; opacity: .85; }
.sidebar .nav-link:hover,
.sidebar .nav-link:focus {
  color: var(--cx-ink);
  background: var(--cx-sand);
}
.sidebar .nav-link[aria-expanded="true"] { color: var(--cx-ink); }

/* Split-row toggle chevron (e.g. Experimental's own link + its submenu arrow) */
.sidebar .nav-link-toggle {
  border: none;
  background: transparent;
  color: var(--cx-steel-600);
  padding: .5rem .6rem;
  border-radius: var(--cx-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.sidebar .nav-link-toggle:hover { color: var(--cx-ink); background: var(--cx-sand); }

/* Section sub-links */
.sidebar .collapse .nav-link { font-weight: 450; font-size: .86rem; color: var(--cx-steel-600); }
.sidebar .collapse .nav-link:hover { color: var(--cx-ink); background: var(--cx-sand); }

/* Active page link — soft accent fill, flat like the rest of the group */
.sidebar .nav-link.active {
  color: var(--cx-accent-700);
  background: var(--cx-accent-soft);
}
.sidebar .nav-link.active svg { opacity: 1; }

@media (max-width: 767.98px) {
  .sidebar { min-height: auto; height: auto; position: static; border-bottom: 1px solid var(--cx-border); }
}

/* --------------------------------------------------------------------------
   5. Buttons — fully pill-shaped
   -------------------------------------------------------------------------- */
.btn { font-weight: 550; border-radius: var(--cx-radius-pill); white-space: nowrap; }
.btn-sm, .btn-group-sm > .btn { font-size: .8rem; }

/* Primary = accent fill with white text (violet is too saturated for the
   old orange-on-ink pairing to stay AA-readable) */
.btn-primary,
.btn-accent {
  --bs-btn-bg:            var(--cx-accent);
  --bs-btn-border-color:  var(--cx-accent);
  --bs-btn-color:         #fff;
  --bs-btn-hover-bg:      var(--cx-accent-600);
  --bs-btn-hover-border-color: var(--cx-accent-600);
  --bs-btn-hover-color:   #fff;
  --bs-btn-active-bg:     var(--cx-accent-700);
  --bs-btn-active-border-color: var(--cx-accent-700);
  --bs-btn-active-color:  #fff;
  --bs-btn-disabled-bg:   var(--cx-accent);
  --bs-btn-disabled-border-color: var(--cx-accent);
  --bs-btn-disabled-color: #fff;
}

.btn-outline-primary {
  --bs-btn-color:         var(--cx-accent-text);
  --bs-btn-border-color:  var(--cx-accent);
  --bs-btn-hover-bg:      var(--cx-accent);
  --bs-btn-hover-border-color: var(--cx-accent);
  --bs-btn-hover-color:   #fff;
  --bs-btn-active-bg:     var(--cx-accent-600);
  --bs-btn-active-border-color: var(--cx-accent-600);
  --bs-btn-active-color:  #fff;
}

/* Secondary = steel */
.btn-secondary {
  --bs-btn-bg:            var(--cx-steel);
  --bs-btn-border-color:  var(--cx-steel);
  --bs-btn-color:         #fff;
  --bs-btn-hover-bg:      var(--cx-steel-600);
  --bs-btn-hover-border-color: var(--cx-steel-600);
  --bs-btn-active-bg:     var(--cx-steel-600);
}
.btn-outline-secondary {
  --bs-btn-color:         var(--cx-steel-600);
  --bs-btn-border-color:  var(--cx-steel);
  --bs-btn-hover-bg:      var(--cx-ink);
  --bs-btn-hover-border-color: var(--cx-ink);
  --bs-btn-hover-color:   #fff;
  --bs-btn-active-bg:     var(--cx-ink);
  --bs-btn-active-border-color: var(--cx-ink);
}

/* Dark = ink */
.btn-dark {
  --bs-btn-bg:            var(--cx-black);
  --bs-btn-border-color:  var(--cx-black);
  --bs-btn-hover-bg:      #000;
  --bs-btn-hover-border-color: #000;
}

/* --------------------------------------------------------------------------
   6. Forms
   -------------------------------------------------------------------------- */
.form-control, .form-select {
  border-color: var(--cx-border);
  border-radius: var(--cx-radius-sm);
}
.form-control:focus, .form-select:focus {
  border-color: var(--cx-accent);
  box-shadow: 0 0 0 .2rem rgba(var(--cx-accent-rgb), .2);
}
.form-check-input:checked {
  background-color: var(--cx-accent);
  border-color: var(--cx-accent);
}
.form-check-input:focus { border-color: var(--cx-accent); box-shadow: 0 0 0 .2rem rgba(var(--cx-accent-rgb), .2); }
.form-label { font-weight: 550; color: var(--cx-ink-soft); font-size: .875rem; }

/* --------------------------------------------------------------------------
   7. Cards & panels
   -------------------------------------------------------------------------- */
.card {
  --bs-card-bg: var(--cx-surface);
  --bs-card-border-color: var(--cx-border);
  --bs-card-border-radius: var(--cx-radius);
  --bs-card-inner-border-radius: calc(var(--cx-radius) - 1px);
  box-shadow: var(--cx-shadow);
}
.card-header {
  background: var(--cx-surface);
  border-bottom: 1px solid var(--cx-border);
  font-weight: 600;
  color: var(--cx-ink);
}
.card-header.bg-light { background: var(--cx-bg) !important; }

/* --------------------------------------------------------------------------
   8. Tables
   -------------------------------------------------------------------------- */
.table {
  --bs-table-hover-bg: rgba(var(--cx-accent-rgb), .06);
  --bs-table-border-color: var(--cx-border);
  color: var(--cx-ink);
}
.table > thead {
  --bs-table-bg: var(--cx-bg);
}
.table > thead th {
  text-transform: uppercase;
  letter-spacing: .04em;
  font-size: .74rem;
  font-weight: 650;
  color: var(--cx-steel-600);
  border-bottom: 2px solid var(--cx-border);
  white-space: nowrap;
}
.table-light { --bs-table-bg: var(--cx-bg); }

/* --------------------------------------------------------------------------
   9. Badges, pills, chips
   -------------------------------------------------------------------------- */
.badge { font-weight: 600; letter-spacing: .01em; padding: .4em .6em; border-radius: var(--cx-radius-pill); }
.badge.bg-secondary { background-color: var(--cx-steel) !important; }
.badge.bg-light { background-color: var(--cx-sand) !important; color: var(--cx-ink) !important; }

/* Soft status chip — reusable component */
.cx-chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .25rem .6rem;
  font-size: .76rem;
  font-weight: 600;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-steel-100);
  color: var(--cx-steel-600);
  line-height: 1.2;
}
.cx-chip--accent  { background: var(--cx-accent-soft); color: var(--cx-accent-700); }
.cx-chip--success { background: #E4F4E9; color: #1E7A3D; }
.cx-chip--warning { background: #FBF0D6; color: #946200; }
.cx-chip--danger  { background: #FBE3E3; color: #B02A2A; }
.cx-chip--ink     { background: rgba(21,21,21,.08); color: var(--cx-ink); }

/* Receipt-review confirm checkboxes (Employee Portal reimbursements) --
   same green as .cx-chip--success above, so a "confirmed" checkmark
   reads as the same success colour used everywhere else in the app. */
.cx-verify-check { accent-color: #1E7A3D; width: 1.1em; height: 1.1em; }

/* Accounting Portal ledger: the per-row "N file(s)" button once an
   invoice is attached -- same light green as .cx-chip--success. */
.btn.cx-btn--invoice-attached { background: #E4F4E9; color: #1E7A3D; border-color: #E4F4E9; }
.btn.cx-btn--invoice-attached:hover,
.btn.cx-btn--invoice-attached:focus-visible { background: #D3ECDB; color: #1E7A3D; border-color: #1E7A3D; }

/* Accounting Portal: slide-in-from-right attachment preview (see
   accounting_portal/_attachment_menu.html) -- wide enough to read an
   A4 invoice, full width on a phone. */
.cx-preview-panel { --bs-offcanvas-width: min(760px, 100vw); }
/* Side by side, not an overlay: while the panel is open the page
   content gives up the same width on the right, so the table stays
   fully visible (and editable) next to the document. Narrow screens
   can't fit both, so there the panel simply slides over the page. */
@media (min-width: 992px) {
  .cx-preview-panel { --bs-offcanvas-width: min(46vw, 760px); }
  /* Deliberately not animated: an animated width change re-wraps the
     table rows over several frames and drags the row being previewed
     around; one instant shift lets the script pin that row in place. */
  body.cx-preview-open .cx-shell > .cx-main { margin-right: min(46vw, 760px); }
}
/* The table row whose attachment is open in the preview panel. */
.table > tbody > tr.cx-row--previewing > * {
  --bs-table-bg-state: var(--cx-accent-soft);
}
.table > tbody > tr.cx-row--previewing > :first-child {
  box-shadow: inset 3px 0 0 var(--cx-accent), inset 0 0 0 9999px var(--cx-accent-soft);
}
.cx-preview-panel .offcanvas-header { border-bottom: 1px solid var(--cx-border); }
.cx-preview-panel .offcanvas-body { background: var(--cx-steel-100); display: flex; }
.cx-preview-panel__frame { flex: 1; width: 100%; height: 100%; border: 0; }
.cx-preview-panel__image { max-width: 100%; height: auto; margin: auto; display: block; }

/* A "nothing set yet" dropdown that shouldn't compete with the values
   around it (the ledger's unassigned Assign and uncategorised Category
   cells): grey text with no
   border or fill, so a column of them reads as empty. It turns back into
   a normal dropdown while its row is hovered, or it has focus. */
.form-select.cx-select-quiet {
  color: var(--cx-steel-600);
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
  transition: border-color .12s ease, background-color .12s ease, color .12s ease;
}
tr:hover .form-select.cx-select-quiet,
.form-select.cx-select-quiet:focus {
  color: var(--cx-ink);
  background-color: var(--cx-surface);
  border-color: var(--bs-border-color);
}
.form-select.cx-select-quiet:focus { border-color: var(--cx-accent); }

/* Finance Portal ledger tracking cells (_tracking_cell.html): a narrow
   picker when untagged, and a small round "x" beside the tag to clear
   it once set. */
.form-select.cx-select-tracking { width: auto; max-width: 11rem; }
.btn.cx-tag-clear {
  width: 1.1rem;
  height: 1.1rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: .7rem;
  line-height: 1;
  color: var(--cx-steel-600);
  background: var(--bs-secondary-bg);
}
.btn.cx-tag-clear:hover { color: var(--cx-ink); }

/* Spend by account report: group rows read as subtotals, the accounts
   under them are indented. */
.cx-spend-table td, .cx-spend-table th { white-space: nowrap; }
.cx-spend-row--group td { font-weight: 600; }
.cx-spend-row--account td:first-child { padding-left: 1.5rem; }
.cx-spend-zero { color: var(--bs-secondary-color); }

/* Finance Portal ledger "Comment" column (_comment_cell.html). The
   cell only signals whether a comment exists -- an outline bubble when
   empty (faint until the row is hovered), a filled accent one when
   there is a comment. Hover shows it as a tooltip, and a click opens
   the editor, fixed-positioned beside the cell by transactions.html's
   script. */
.cx-comment-cell { text-align: center; cursor: pointer; width: 1%; }
.cx-comment__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--cx-radius-sm);
  background: transparent;
  color: var(--cx-steel);
  opacity: .35;
  transition: opacity .12s ease, background-color .12s ease, color .12s ease;
}
tr:hover .cx-comment__toggle,
.cx-comment__toggle:focus-visible,
.cx-comment-cell.is-editing .cx-comment__toggle { opacity: 1; }
.cx-comment__toggle:hover,
.cx-comment-cell.is-editing .cx-comment__toggle { background: var(--cx-sand); color: var(--cx-ink); }
.cx-comment--has .cx-comment__toggle { color: var(--cx-accent); opacity: 1; }
.cx-comment--has .cx-comment__toggle:hover { color: var(--cx-accent-700); }
.cx-comment__editor {
  position: fixed;
  z-index: 1060;
  width: min(22rem, calc(100vw - 16px));
  padding: .75rem;
  text-align: left;
  cursor: auto;
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  box-shadow: var(--cx-shadow-lg);
}
.cx-comment-tooltip .tooltip-inner {
  max-width: 22rem;
  text-align: left;
  white-space: pre-line;
}
.cx-pre-line { white-space: pre-line; }

/* Finance Portal Match Invoices (static/js/accounting_portal/
   match_invoices.js). Upload: the files queued in the dropzone, one
   removable row each. */
.cx-file-list { display: flex; flex-direction: column; gap: .35rem; }
.cx-file-list__item {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .45rem .75rem;
  background: var(--cx-steel-100);
  border-radius: var(--cx-radius-sm);
  font-size: .86rem;
}
.cx-file-list__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cx-file-list__size { color: var(--cx-steel-600); font-family: var(--cx-font-mono); font-size: .78rem; }
.cx-file-list__remove { font-size: .65rem; }

/* Review: one row per file, decided with Approve/Reject. A decided row
   is locked; a rejected one is faded so the pending ones stand out. */
.cx-match-row__invoice { min-width: 9rem; }
.cx-match-row__txn { min-width: 18rem; }
.cx-match-row__buttons { display: inline-flex; gap: .4rem; white-space: nowrap; }
.cx-match-row__status { min-width: 9rem; }
.cx-match-row--busy { cursor: progress; }
.cx-match-row[data-state="rejected"] > :not(:last-child),
.cx-match-row[data-state="stale"] > :not(:last-child) { opacity: .55; }
.cx-match-row--attention .cx-match-row__txn { border-color: var(--bs-danger); }
.cx-match-done {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .85rem 1rem;
  background: var(--cx-accent-soft);
  border-radius: var(--cx-radius);
}

/* A field row whose checkbox is currently required (i.e. extraction
   populated it and it needs a human look) gets a light accent tint so
   it's visually distinct from an ordinary optional/manual field. */
.cx-field-row.cx-needs-check {
  background: var(--cx-accent-soft);
  border-radius: var(--cx-radius-sm);
  padding: 6px 8px;
  margin-left: -8px;
  margin-right: -8px;
}

/* Receipt drag-and-drop zone (Employee Portal reimbursements). Scrolls
   internally (rather than the whole page stretching to match a tall
   receipt) so the two panels stay roughly level -- see
   .cx-dropzone--has-file below for why alignment changes once a
   document is actually showing. */
.cx-dropzone {
  border: 2px dashed var(--cx-border);
  border-radius: var(--cx-radius);
  cursor: pointer;
  min-height: 420px;
  max-height: 75vh;
  overflow-y: auto;
  padding: 24px;
  transition: border-color .15s ease, background-color .15s ease;
}
.cx-dropzone:hover,
.cx-dropzone.cx-dropzone--active {
  border-color: var(--cx-accent);
  background: var(--cx-accent-soft);
}
/* Once a document is loaded the preview is often taller than the
   panel -- centering it (the placeholder-prompt default) would clip
   both the top and bottom equally with no way to scroll to either.
   Left/top-aligned + scrollable reads top-to-bottom normally instead. */
.cx-dropzone.cx-dropzone--has-file {
  align-items: flex-start !important;
  cursor: default;
}
.cx-dropzone-icon { font-size: 2.5rem; opacity: .45; margin-bottom: 8px; }

/* Full-size preview, not a shrunk-to-fit thumbnail: scaled to the
   panel's width only (never height), so a tall receipt renders at a
   genuinely readable size and the panel scrolls to it -- clamping
   max-height as well as max-width is exactly what made this look like
   a thumbnail before. */
.cx-dropzone-preview {
  display: block;
  width: 100%;
  height: auto;
  margin: 0 auto;
  border-radius: var(--cx-radius-sm);
}
.cx-dropzone-preview.cx-zoomable { cursor: zoom-in; }

/* Magnifying-glass loupe: a circular zoomed-in view of the receipt
   under the cursor, for reading small print (totals, VAT lines) at
   full resolution without the whole document taking over the screen.
   Image preview only -- a PDF's native iframe viewer already has its
   own zoom/pan controls. */
.cx-magnifier {
  position: absolute;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  border: 3px solid var(--cx-surface);
  box-shadow: var(--cx-shadow-lg);
  pointer-events: none;
  background-repeat: no-repeat;
  display: none;
  z-index: 5;
}
.cx-magnifier.cx-magnifier--active { display: block; }

/* Scanning-bar overlay: a glowing accent line sweeping top-to-bottom
   over the receipt preview while extraction is in flight -- purely a
   loading indicator, removed from the layout (not just hidden) when
   inactive so it never intercepts a click/drop on the preview. */
.cx-scan-overlay {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  display: none;
}
.cx-scan-overlay.cx-scan-overlay--active { display: block; }
.cx-scan-bar {
  position: absolute;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--cx-accent), transparent);
  box-shadow: 0 0 14px 3px rgba(var(--cx-accent-rgb), .7);
  animation: cx-scan-sweep 1.8s ease-in-out infinite;
}
@keyframes cx-scan-sweep {
  0%   { top: 0; }
  50%  { top: calc(100% - 3px); }
  100% { top: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cx-scan-bar { animation: none; top: 50%; }
}

/* --------------------------------------------------------------------------
   10. Nav tabs & pills (in-page)
   -------------------------------------------------------------------------- */
.nav-tabs { --bs-nav-tabs-border-color: var(--cx-border); }
.nav-tabs .nav-link { color: var(--cx-steel-600); font-weight: 550; }
.nav-tabs .nav-link.active {
  color: var(--cx-ink);
  border-bottom: 2px solid var(--cx-accent);
  background: transparent;
  border-top-color: transparent;
  border-left-color: transparent;
  border-right-color: transparent;
}
.nav-pills .nav-link.active { background: var(--cx-accent); color: #fff; }

/* Sub-nav bar (Employee Portal's per-page tab row) -- a distinct light
   surface card the pills sit inside, rather than pills floating
   directly on the page's own (slightly darker) background. */
.cx-subnav {
  position: relative;
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  padding: .5rem;
  box-shadow: var(--cx-shadow-sm);
}
.cx-subnav .nav-pills { margin-bottom: 0 !important; }
.cx-subnav .nav-link { position: relative; z-index: 1; color: var(--cx-steel-600); font-weight: 550; }
.cx-subnav .nav-link:hover { background: var(--cx-sand); color: var(--cx-ink); }

/* Vertical variant (Employee Portal's own subnav) -- a left-hand nav
   column instead of a top-of-page bar. Same card/indicator/script as
   the horizontal version; only the stacking direction and each link's
   own width change. */
.cx-subnav--vertical { padding: .75rem; }
.cx-subnav--vertical .nav-link { width: 100%; }
/* Grouped variant (Employee Portal): the links split into separate
   cards, one per group, like the identity card above them. The wrapper
   itself is invisible but stays the positioning context, so the sliding
   indicator still travels between cards. */
.cx-subnav--grouped {
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}
.cx-subnav--grouped .cx-subnav__group {
  padding: .6rem;
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  box-shadow: var(--cx-shadow-sm);
}
.cx-subnav--grouped .cx-subnav__group + .cx-subnav__group { margin-top: .75rem; }
/* A group's small heading. Not a link: the sliding indicator and
   arrow-key navigation only ever target .nav-link, so it's skipped. */
.cx-subnav__label {
  margin: .1rem 0 .15rem;
  padding: 0 .75rem;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--cx-steel);
  list-style: none;
}

/* Identity card pinned above the vertical subnav -- "who am I", not a
   nav control (see _subnav.html:sidebar_employee). */
.cx-sidebar-identity {
  display: flex;
  align-items: center;
  gap: .65rem;
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  padding: .65rem .75rem;
  box-shadow: var(--cx-shadow-sm);
}
.cx-sidebar-identity__text { min-width: 0; }
.cx-sidebar-identity__name {
  font-weight: 650;
  font-size: .88rem;
  color: var(--cx-ink);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cx-sidebar-identity__subtitle {
  font-size: .76rem;
  color: var(--cx-steel-600);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Collapsible section headers (HR Portal Inbox) -- a plain-button
   header toggling a Bootstrap .collapse target; the chevron rotates
   based on aria-expanded, which Bootstrap's collapse JS manages on the
   toggle button itself, so no extra JS is needed for the rotation. */
.cx-accordion-toggle { color: inherit; }
.cx-accordion-toggle:hover .h6 { text-decoration: underline; }
.cx-accordion-chevron { transition: transform .15s ease; flex: none; }
.cx-accordion-toggle[aria-expanded="false"] .cx-accordion-chevron { transform: rotate(-90deg); }

/* Sliding active-tab indicator -- a purple pill that glides from the
   previously-active tab to the newly-active one on each subnav click,
   rather than the highlight just snapping straight to the new tab.
   This is a traditional multi-page app (every subnav link is a real
   full-page navigation, not an SPA route), so the "from" position is
   captured in sessionStorage by the click handler right before the
   browser navigates away, then read back and animated from on the next
   page's load -- see the script at the bottom of _subnav.html. Hidden
   by default (and the plain per-link .active background stays as the
   fallback) until that script actually runs and adds
   .cx-subnav--js, so a JS-disabled load still shows which tab is
   active. */
.cx-subnav-indicator {
  display: none;
  position: absolute;
  background: var(--cx-accent);
  border-radius: var(--cx-radius-pill);
  z-index: 0;
  pointer-events: none;
  transition: left .28s cubic-bezier(.4, 0, .2, 1), top .28s cubic-bezier(.4, 0, .2, 1),
    width .28s cubic-bezier(.4, 0, .2, 1), height .28s cubic-bezier(.4, 0, .2, 1);
}
.cx-subnav.cx-subnav--js .cx-subnav-indicator { display: block; }
.cx-subnav.cx-subnav--js .nav-link.active { background: transparent; }
@media (prefers-reduced-motion: reduce) {
  .cx-subnav-indicator { transition: none; }
}

/* Alerts keep semantic colours; just soften corners */
.alert { border-radius: var(--cx-radius-sm); border: 1px solid transparent; }

/* --------------------------------------------------------------------------
   11. Reusable CipherX components
   -------------------------------------------------------------------------- */

/* Page header (used by base.html page_header block) */
.cx-page-header .cx-page-title { font-size: 1.85rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: .15rem; }
.cx-page-header .cx-page-title__group { font-weight: 800; }
.cx-page-header .cx-page-title__name { font-weight: 400; }
.cx-page-header .cx-page-subtitle { color: var(--cx-steel-600); font-size: .9rem; margin: 0; }

/* Pagination — brand the active page with the accent */
.pagination {
  --bs-pagination-color: var(--cx-steel-600);
  --bs-pagination-hover-color: var(--cx-accent-700);
  --bs-pagination-focus-color: var(--cx-accent-700);
  --bs-pagination-focus-box-shadow: 0 0 0 .2rem rgba(var(--cx-accent-rgb), .2);
  --bs-pagination-active-bg: var(--cx-accent);
  --bs-pagination-active-border-color: var(--cx-accent);
}
.pagination .page-item.active .page-link { color: #fff; }

/* Stat / KPI tile */
.cx-stat {
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius-lg);
  padding: 1.25rem 1.4rem;
  box-shadow: var(--cx-shadow-sm);
  display: flex;
  flex-direction: column;
  gap: .6rem;
  height: 100%;
}
.cx-stat__head { display: flex; align-items: center; gap: .6rem; }
.cx-stat__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: var(--cx-radius-sm);
  background: var(--cx-steel-100);
  color: var(--cx-steel-600);
}
.cx-stat--accent .cx-stat__icon { background: var(--cx-accent-soft); color: var(--cx-accent-700); }
.cx-stat__label { font-size: .9rem; font-weight: 500; color: var(--cx-ink); }
.cx-stat__value { font-family: var(--cx-font-mono); font-size: 1.9rem; font-weight: 600; color: var(--cx-ink); line-height: 1.1; letter-spacing: -0.01em; }
.cx-stat__meta  { font-size: .8rem; color: var(--cx-steel-600); }

/* The value itself as an urgent red bubble (e.g. an unread count) instead
   of a second, separate badge next to the label -- see `danger=true` on
   the stat_card macro. `.badge` already gives the bg-danger/rounded-pill
   look; this only overrides its sizing so it reads at stat-tile scale and
   stays circular for a single digit, widening into a pill for more. */
.cx-stat__value--bubble {
  /* .cx-stat is a flex column, which would otherwise stretch the bubble
     to the tile's full width. */
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  height: 2rem;
  padding: 0 .3rem;
  font-size: 1.15rem;
  line-height: 1;
  color: #fff;
}

/* A stat tile that reveals a list when clicked (dashboard, see
   employee_portal/index.html's collapse panels) -- same tile, just
   with a pointer affordance and a hover tint so it reads as
   interactive rather than a plain readout. */
/* Draggable dashboard tiles (employee_portal/index.html): each tile's
   wrapper column can be dragged into a new order, with a grip in the
   bottom-right corner for the keyboard (arrow keys) -- see
   static/js/employee_portal/dashboard_layout.js. */
.cx-dash-tile { position: relative; will-change: transform; }
.cx-dash-tile .cx-stat {
  height: 100%;
  transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
}
/* While sorting, the other tiles sit back slightly so the one being
   moved -- and the gap it leaves -- stand out. */
.cx-dash-grid--sorting .cx-dash-tile:not(.cx-dash-tile--dragging) .cx-stat { opacity: .9; }
/* The dragged tile's own spot becomes an empty, dashed slot showing
   where it will land. */
.cx-dash-tile--dragging .cx-stat {
  background: var(--cx-accent-soft);
  border: 2px dashed rgba(var(--cx-accent-rgb), .55);
  box-shadow: none;
}
.cx-dash-tile--dragging .cx-stat > *,
.cx-dash-tile--dragging .cx-dash-tile__handle { visibility: hidden; }
/* Grabbing the grip lifts the tile a little before it moves. */
.cx-dash-tile:has(.cx-dash-tile__handle:active) .cx-stat {
  transform: scale(1.02);
  box-shadow: var(--cx-shadow-lg);
}
/* The tile that moved pulses once when it lands. */
.cx-dash-tile--dropped .cx-stat { animation: cx-dash-drop .8s cubic-bezier(.2, .8, .2, 1); }
@keyframes cx-dash-drop {
  0%   { transform: scale(1.04); box-shadow: 0 0 0 4px rgba(var(--cx-accent-rgb), .45), var(--cx-shadow-lg); }
  60%  { transform: scale(1); box-shadow: 0 0 0 3px rgba(var(--cx-accent-rgb), .25), var(--cx-shadow); }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(var(--cx-accent-rgb), 0), var(--cx-shadow-sm); }
}
@media (prefers-reduced-motion: reduce) {
  .cx-dash-tile .cx-stat { transition: none; }
  .cx-dash-tile--dropped .cx-stat { animation: none; }
}
.cx-dash-tile__handle {
  position: absolute;
  right: calc(var(--bs-gutter-x) * .5 + .5rem);
  bottom: .5rem;
  display: inline-flex;
  padding: .2rem;
  border: 0;
  border-radius: var(--cx-radius-sm);
  background: transparent;
  color: var(--cx-steel);
  cursor: grab;
  opacity: .5;
}
.cx-dash-tile:hover .cx-dash-tile__handle,
.cx-dash-tile__handle:focus-visible { opacity: 1; }
.cx-dash-tile__handle:hover { background: var(--cx-sand); color: var(--cx-ink); }
.cx-dash-tile__handle:active { cursor: grabbing; }

.cx-stat--clickable { cursor: pointer; transition: border-color .12s ease, box-shadow .12s ease; }
.cx-stat--clickable:hover,
.cx-stat--clickable:focus-visible { border-color: var(--cx-accent); box-shadow: 0 0 0 1px var(--cx-accent); }
.cx-stat--clickable .cx-stat__caret { margin-left: auto; color: var(--cx-steel); transition: transform .2s ease; }
.cx-stat--clickable[aria-expanded="true"] .cx-stat__caret { transform: rotate(180deg); }

/* Empty state */
.cx-empty {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--cx-steel-600);
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius-lg);
  box-shadow: var(--cx-shadow-sm);
}
.cx-empty__icon { color: var(--cx-steel); opacity: .6; margin-bottom: .75rem; }
.cx-empty__title { color: var(--cx-ink); font-weight: 600; margin-bottom: .25rem; }

/* Accent left-border highlight for callout cards */
.cx-callout { border-left: 4px solid var(--cx-accent); background: var(--cx-accent-soft); }

/* Info card: a bordered white panel with a bold title + optional
   right-aligned action in its header, a divider, then a stack of
   label/value display rows -- the "Personal information" / "Work
   details" card pattern (see macros/ui.html:info_card/field_row). */
.cx-info-card {
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius-lg);
  box-shadow: var(--cx-shadow-sm);
  overflow: hidden;
}
.cx-info-card + .cx-info-card { margin-top: 1rem; }
.cx-info-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--cx-border);
}
.cx-info-card__title { font-size: 1rem; font-weight: 650; margin: 0; }
.cx-info-card__action { flex: none; }
.cx-info-card__body { padding: .25rem 1.25rem .5rem; }

.cx-display-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem 1rem;
  padding: .85rem 0;
  border-bottom: 1px solid var(--cx-border);
}
.cx-display-row:last-child { border-bottom: none; }
.cx-display-row__label {
  flex: 0 0 auto;
  width: 12rem;
  max-width: 45%;
  font-weight: 600;
  color: var(--cx-ink);
  font-size: .88rem;
}
.cx-display-row__value {
  flex: 1 1 12rem;
  color: var(--cx-ink-soft);
  font-size: .92rem;
  display: flex;
  align-items: center;
  gap: .4rem;
}
.cx-verified-badge { color: #1E7A3D; display: inline-flex; align-items: center; }

/* Masked sensitive value + its reveal toggle (see
   macros/ui.html:masked_field_row / static/js/mask_toggle.js). */
.cx-masked-value { font-family: var(--cx-font-mono); letter-spacing: .05em; }
.cx-mask-toggle {
  appearance: none;
  border: none;
  background: transparent;
  padding: 0;
  display: inline-flex;
  align-items: center;
  color: var(--cx-steel-600);
  cursor: pointer;
}
.cx-mask-toggle:hover { color: var(--cx-accent-text); }

/* Small tinted "?" affordance for a field's help text -- not currently
   wired to a real tooltip library, just a title attribute; upgrade to
   a proper popover if a field ever needs more than one line. */
.cx-help-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--cx-steel-100);
  color: var(--cx-steel-600);
  font-size: .68rem;
  font-weight: 700;
  cursor: help;
}

/* Avatar: a real photo, or a tinted initials circle when none is set
   (see macros/ui.html:avatar). */
.cx-avatar {
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  display: inline-flex;
}
.cx-avatar--initials {
  align-items: center;
  justify-content: center;
  background: var(--cx-accent-soft);
  color: var(--cx-accent-700);
  font-weight: 650;
  letter-spacing: -0.02em;
}

/* --------------------------------------------------------------------------
   12. Auth / sign-in screen (full-bleed, no sidebar)
   -------------------------------------------------------------------------- */
.cx-auth {
  min-height: 100vh;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: #0E0E0E;
  background-image:
    radial-gradient(60rem 60rem at 50% -20%, rgba(var(--cx-accent-rgb), .16), transparent 60%);
}
.cx-auth__card {
  width: 100%;
  max-width: 400px;
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: .35rem;
  padding: 2.5rem 2rem;
  /* dark card so logo_black.svg's white fill reads correctly; sits on a
     darker page so the card still reads as a distinct square */
  background: var(--cx-black);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: var(--cx-radius-lg);
  box-shadow: var(--cx-shadow-lg);
}
.cx-auth__logo { width: 210px; height: auto; margin-bottom: .25rem; }
.cx-auth__title { color: #fff; font-size: 1.5rem; font-weight: 700; margin: 0; }
.cx-auth__subtitle { color: rgba(255, 255, 255, .55); font-size: .9rem; margin: 0 0 1.75rem; }

/* Google sign-in button — fully pill-shaped, matching the rest of .btn */
.cx-google-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .65rem;
  width: 100%;
  padding: .7rem 1.25rem;
  font-weight: 600;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-accent);
  color: #fff;
  border: none;
  transition: background-color .12s ease, transform .04s ease;
}
.cx-google-btn:hover  { background: var(--cx-accent-600); color: #fff; }
.cx-google-btn:active { background: var(--cx-accent-700); transform: translateY(1px); }
.cx-google-btn__g {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  background: #fff;
  border-radius: 5px;
  flex: none;
}
@media (max-width: 460px) {
  .cx-auth__card { aspect-ratio: auto; }
}

/* Generic surface helpers */
.cx-surface { background: var(--cx-surface); border: 1px solid var(--cx-border); border-radius: var(--cx-radius); }
.bg-cx-sand { background: var(--cx-sand) !important; }
.text-cx-accent { color: var(--cx-accent-text) !important; }
.text-cx-steel { color: var(--cx-steel-600) !important; }

/* --------------------------------------------------------------------------
   18. Investor Relations -- standalone public deck viewer
   (blueprints/investor_share/templates/investor_share/deck.html). This
   page does not extend base.html's authenticated app shell (no
   sidebar) -- it's its own minimal, full-height layout, still built
   from the shared --cx-* tokens so it reads as the same product.
   -------------------------------------------------------------------------- */
.cx-investor-viewer-body {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--cx-bg);
  font-family: var(--cx-font-sans);
  color: var(--cx-ink);
}
.cx-investor-viewer {
  height: 100vh;
  display: flex;
  flex-direction: column;
}
.cx-investor-viewer__bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .75rem 1.25rem;
  background: var(--cx-surface);
  border-bottom: 1px solid var(--cx-border);
}
.cx-investor-viewer__brand {
  display: flex;
  align-items: center;
  gap: .75rem;
  min-width: 0;
}
.cx-investor-viewer__brand > span:first-of-type {
  font-weight: 650;
  white-space: nowrap;
}
.cx-investor-viewer__divider {
  width: 1px;
  align-self: stretch;
  background: var(--cx-border);
}
.cx-investor-viewer__title {
  font-weight: 600;
  font-size: .95rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cx-investor-viewer__subtitle {
  font-size: .76rem;
  color: var(--cx-steel-600);
}
.cx-investor-viewer__bar-right {
  flex: none;
  display: flex;
  align-items: center;
  gap: .75rem;
}
.cx-investor-viewer__counter {
  font-family: var(--cx-font-mono);
  font-size: .82rem;
  color: var(--cx-steel-600);
}
.cx-investor-viewer__icon-btn {
  appearance: none;
  border: 1px solid var(--cx-border);
  background: var(--cx-surface);
  color: var(--cx-steel-600);
  width: 32px;
  height: 32px;
  border-radius: var(--cx-radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}
.cx-investor-viewer__icon-btn:hover { background: var(--cx-sand); color: var(--cx-ink); }

.cx-investor-viewer__stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 1.5rem;
}
.cx-investor-viewer__nav {
  flex: none;
  appearance: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--cx-border);
  background: var(--cx-surface);
  color: var(--cx-ink);
  box-shadow: var(--cx-shadow);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.cx-investor-viewer__nav:hover { background: var(--cx-sand); }
.cx-investor-viewer__nav:disabled { opacity: .35; cursor: not-allowed; }
.cx-investor-viewer__canvas-wrap {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cx-investor-viewer__canvas-wrap canvas {
  max-width: 100%;
  max-height: 100%;
  height: auto;
  box-shadow: var(--cx-shadow-lg);
  border-radius: var(--cx-radius-sm);
  background: #fff;
}
.cx-investor-viewer__loading {
  position: absolute;
  color: var(--cx-steel-600);
  font-size: .85rem;
}

.cx-investor-viewer__seek {
  flex: none;
  padding: .6rem 1.5rem 1rem;
  background: var(--cx-surface);
  border-top: 1px solid var(--cx-border);
}
.cx-investor-viewer__seek input[type="range"] {
  width: 100%;
  accent-color: var(--cx-accent);
}

@media (max-width: 640px) {
  .cx-investor-viewer__subtitle { display: none; }
  .cx-investor-viewer__nav { width: 36px; height: 36px; }
  .cx-investor-viewer__stage { padding: .75rem; gap: .5rem; }
}

/* --------------------------------------------------------------------------
   17. Org chart tree (Company Portal) -- a real top-down chart (parent
   card, horizontal branch line, child cards below), not just an
   indented list. Classic pure-CSS "family tree" technique: every <li>
   draws its own connector via ::before/::after (a T-junction to its
   siblings) sized to 50% of its own box, and a nested <ul> draws the
   short vertical stub down to its parent -- no JS, no SVG. An
   only-child needs no T-junction at all (nothing to branch from), so
   its connectors are suppressed and its top padding collapses to
   avoid a floating gap above the card.
   -------------------------------------------------------------------------- */
.cx-orgchart-scroll { overflow-x: auto; padding: .5rem .5rem 1rem; }
.cx-orgchart, .cx-orgchart ul {
  display: block;
  margin: 0;
  padding: 1.75rem 0 0;
  position: relative;
  text-align: center;
  white-space: nowrap;
}
.cx-orgchart { padding-top: 0; }
.cx-orgchart li {
  display: inline-block;
  vertical-align: top;
  list-style: none;
  position: relative;
  padding: 1.75rem 1rem 0;
}
/* Connector lines use --cx-steel at partial opacity rather than the
   near-invisible --cx-border hairline -- a chart's whole job is to
   show the shape of the reporting lines, so they need to actually
   read at a glance, not just be technically present. Rounded corners
   on the outer elbows (border-radius on the last/first child's own
   T-junction line) make each branch look like a drawn connector
   rather than two hard-edged rules meeting at a right angle. */
.cx-orgchart li::before,
.cx-orgchart li::after {
  content: '';
  position: absolute;
  top: 0;
  width: 50%;
  height: 1.75rem;
  border-top: 2px solid rgba(var(--cx-steel-rgb), .35);
}
.cx-orgchart li::before { right: 50%; }
.cx-orgchart li::after { left: 50%; border-left: 2px solid rgba(var(--cx-steel-rgb), .35); }
.cx-orgchart li:only-child { padding-top: 0; }
.cx-orgchart li:only-child::before,
.cx-orgchart li:only-child::after { display: none; }
.cx-orgchart li:first-child::before { border: 0 none; }
.cx-orgchart li:last-child::after { border: 0 none; }
.cx-orgchart li:last-child::before {
  border-right: 2px solid rgba(var(--cx-steel-rgb), .35);
  border-radius: 0 10px 0 0;
}
.cx-orgchart li:first-child::after { border-radius: 10px 0 0 0; }
.cx-orgchart ul::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: 0;
  height: 1.75rem;
  border-left: 2px solid rgba(var(--cx-steel-rgb), .35);
}
.cx-orgnode {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  min-width: 168px;
  max-width: 190px;
  white-space: normal;
  text-align: center;
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  box-shadow: var(--cx-shadow-sm);
  padding: 1rem .85rem .85rem;
  transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
}
.cx-orgchart li:hover > .cx-orgnode {
  box-shadow: var(--cx-shadow);
  border-color: rgba(var(--cx-steel-rgb), .4);
  transform: translateY(-1px);
}
.cx-orgnode__avatar { line-height: 0; }
.cx-orgnode__avatar .cx-avatar {
  box-shadow: 0 0 0 3px var(--cx-surface), 0 0 0 4px var(--cx-border);
}
.cx-orgnode--company {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  max-width: none;
  font-weight: 700;
  font-size: .9rem;
  color: var(--cx-ink);
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-bottom: 3px solid var(--cx-accent);
  padding: .7rem 1.1rem;
}
.cx-orgnode--company svg { color: var(--cx-accent-700); flex: none; }
.cx-orgnode__name {
  font-weight: 650;
  font-size: .88rem;
  color: var(--cx-ink);
  margin-top: .5rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cx-orgnode__title {
  font-size: .74rem;
  color: var(--cx-steel-600);
  margin-bottom: .5rem;
}
.cx-orgchart-legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .4rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--cx-border);
}
/* --------------------------------------------------------------------------
   19. Onboarding -- standalone public new-employee info form
   (blueprints/onboarding_share/templates/onboarding_share/form.html).
   Same "doesn't extend base.html's app shell" shape as the Investor
   Relations deck viewer above -- a minimal centered card, not a
   full-height app layout, since this is a short one-off form rather
   than an interactive viewer.
   -------------------------------------------------------------------------- */
.cx-onboarding-body {
  margin: 0;
  min-height: 100vh;
  background: var(--cx-bg);
  font-family: var(--cx-font-sans);
  color: var(--cx-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
}
.cx-onboarding-wrap { width: 100%; max-width: 640px; }
.cx-onboarding-card {
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  box-shadow: var(--cx-shadow-sm);
  padding: 2rem;
}
.cx-onboarding-footnote {
  text-align: center;
  color: var(--cx-steel-600);
  font-size: .78rem;
  margin: 1rem 0 0;
}

/* --------------------------------------------------------------------------
   20. Leave & Absence grouped bars (HR Portal)
   -------------------------------------------------------------------------- */
.cx-leave-row {
  padding: .85rem 0;
  border-bottom: 1px solid var(--cx-border);
}
.cx-leave-row:last-child { border-bottom: none; padding-bottom: 0; }
.cx-leave-row:first-child { padding-top: 0; }
.cx-leave-bar-group { display: flex; flex-direction: column; gap: .4rem; }
.cx-leave-bar-line { display: flex; align-items: center; gap: .6rem; }
.cx-leave-bar-label {
  flex: none;
  width: 4.4rem;
  font-size: .74rem;
  color: var(--cx-steel-600);
  white-space: nowrap;
}
.cx-leave-bar-track { flex: 1 1 auto; height: 16px; }
.cx-leave-bar-value {
  flex: none;
  width: 3rem;
  text-align: right;
  font-size: .78rem;
  color: var(--cx-ink-soft);
  font-variant-numeric: tabular-nums;
}
.cx-leave-legend-swatch {
  display: inline-block;
  width: .6rem;
  height: .6rem;
  border-radius: 2px;
}

/* --------------------------------------------------------------------------
   21. Departments page (Company Portal) -- the department-code badge
   shown next to each accordion header.
   -------------------------------------------------------------------------- */
.cx-dept-code {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  padding: .15rem .4rem;
  border-radius: 6px;
  background: var(--cx-accent-soft);
  color: var(--cx-accent-700);
  font-weight: 700;
  font-size: .72rem;
  letter-spacing: .02em;
}

/* --------------------------------------------------------------------------
   22. Tenure badge (Company/HR Portal directories) -- deliberately NOT
   styled like the plain job-title/email lines around it (uppercase,
   letter-spaced, tinted pill) so "time at CipherX" reads as a distinct
   fact, not just another line of body copy.
   -------------------------------------------------------------------------- */
.cx-tenure-badge {
  display: inline-block;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--cx-accent-700);
  background: var(--cx-accent-soft);
  padding: .18rem .55rem;
  border-radius: 999px;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   23. Salaries dropzone (HR Portal) -- a compact variant of the
   reimbursement receipt dropzone above: no preview pane needed here
   (this drops straight into a server-side review table on submit), so
   a short fixed height reads better than the 420px receipt-preview
   default.
   -------------------------------------------------------------------------- */
.cx-dropzone--compact { min-height: 140px; max-height: none; }

/* --------------------------------------------------------------------------
   27. Homepage (`/`, templates/home.html) -- a full-height hero, then the
   app launcher. The hero borrows from an architect's envelope: a white
   sheet, small type in the corners, and bold black shapes scattered
   across it. Here the shapes are the three pieces of the CipherX mark
   (partials/_cx_mark_symbols.html), which start assembled as the logo
   and break apart into this composition (static/js/home_hero.js). The
   resting composition lives entirely here -- the script only animates
   from the assembled logo to whatever these rules say.
   -------------------------------------------------------------------------- */
.cx-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 2rem;
  min-height: calc(100vh - 3rem);
  min-height: calc(100svh - 3rem);
  padding: clamp(1.25rem, 3vw, 2.5rem);
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius-lg);
  box-shadow: var(--cx-shadow-sm);
  color: var(--cx-black);
  overflow: hidden;
}
.cx-hero__field { position: absolute; inset: 0; pointer-events: none; }

.cx-hero__frag {
  position: absolute;
  display: block;
  height: auto;
  aspect-ratio: 44 / 20;
  overflow: visible;
  color: var(--cx-black);
  transition:
    translate .9s cubic-bezier(.2, .7, .2, 1),
    opacity .7s ease var(--cx-frag-delay, 0s),
    scale .7s cubic-bezier(.2, .7, .2, 1) var(--cx-frag-delay, 0s);
}
.cx-hero__frag[data-piece="c"],
.cx-hero__frag--4 { aspect-ratio: 1; }

/* The composition. Positions are % of the sheet, sizes follow the
   viewport, and each rotation is a quarter turn so the pieces read as
   letterforms rather than tilted logos. */
.cx-hero__frag--1 { left: 15%; top: 24%; width: clamp(72px, 10vw, 170px); }
.cx-hero__frag--2 { left: 64%; top: 10%; width: clamp(90px, 13vw, 220px); rotate: 90deg; }
.cx-hero__frag--3 { left: 42%; top: 42%; width: clamp(80px, 11vw, 190px); rotate: -90deg; }
.cx-hero__frag--4 { left: 80%; top: 44%; width: clamp(56px, 7vw, 120px); rotate: 180deg; --cx-frag-delay: 0s; }
.cx-hero__frag--5 { left: 64%; top: 70%; width: clamp(70px, 9vw, 150px); --cx-frag-delay: .12s; }
.cx-hero__frag--6 { left: 30%; top: 9%; width: clamp(48px, 5.5vw, 96px); rotate: 180deg; --cx-frag-delay: .24s; }

@media (max-width: 575.98px) {
  .cx-hero__frag--1 { left: 8%; top: 22%; }
  .cx-hero__frag--3 { left: 46%; top: 40%; }
  .cx-hero__frag--5,
  .cx-hero__frag--6 { display: none; }
}

.cx-hero__top,
.cx-hero__bottom { position: relative; }
.cx-hero__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}
.cx-hero__logo { display: block; height: clamp(20px, 2.2vw, 28px); width: auto; }
.cx-hero__date,
.cx-hero__cue,
.cx-launcher__count,
.cx-app-card__top {
  font-family: var(--cx-font-mono);
  font-size: .78rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.cx-hero__date { color: var(--cx-steel-600); text-align: right; padding-top: .2rem; }

.cx-hero__bottom {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
}
.cx-hero__eyebrow {
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
  margin-bottom: .75rem;
}
.cx-hero__title {
  font-size: clamp(2.4rem, 6vw, 5.25rem);
  font-weight: 650;
  line-height: .98;
  letter-spacing: -0.035em;
  color: var(--cx-black);
  margin: 0;
}
/* The greeting's text reveal: each line is a mask, and its words rise up
   through it one after another. It plays once the intro has finished
   (html.cx-home-intro is gone), or straight away when there's no intro.
   The padding gives descenders room inside the mask, and the negative
   margin keeps the line spacing as set. */
.cx-hero__line {
  display: block;
  overflow: hidden;
  padding-bottom: .12em;
  margin-bottom: -.12em;
}
.cx-hero__word { display: inline-block; transform: translateY(110%); }
html:not(.cx-home-intro) .cx-hero__word {
  animation: cx-hero-word-rise .95s cubic-bezier(.2, .8, .2, 1) both;
}
html:not(.cx-home-intro) .cx-hero__line:first-child .cx-hero__word + .cx-hero__word { animation-delay: .09s; }
html:not(.cx-home-intro) .cx-hero__line + .cx-hero__line .cx-hero__word { animation-delay: .2s; }
@keyframes cx-hero-word-rise {
  from { transform: translateY(110%); }
  to   { transform: none; }
}

.cx-hero__cue {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  padding: .55rem 1rem;
  color: var(--cx-black);
  background: var(--cx-surface);
  border: 1px solid var(--cx-black);
  border-radius: var(--cx-radius-pill);
  transition: background-color .2s ease, color .2s ease;
}
.cx-hero__cue:hover,
.cx-hero__cue:focus-visible { background: var(--cx-black); color: var(--cx-surface); }
.cx-hero__cue-arrow { display: inline-block; animation: cx-hero-bob 1.8s ease-in-out infinite; }
@keyframes cx-hero-bob { 50% { transform: translateY(3px); } }

/* Intro states. html.cx-home-intro is set before paint while the intro
   is due; the script removes it once the pieces have broken apart, and
   the echoes and text then transition in. */
.cx-hero__reveal { transition: opacity .8s ease, transform .8s cubic-bezier(.2, .7, .2, 1); }
.cx-hero__cue.cx-hero__reveal { transition-delay: .3s, .3s; }
html.cx-home-intro .cx-hero__frag { visibility: hidden; }
html.cx-home-intro .cx-hero.is-intro-running .cx-hero__frag[data-piece] { visibility: visible; }
html.cx-home-intro .cx-hero__frag--echo { opacity: 0; scale: .6; }
html.cx-home-intro .cx-hero__reveal { opacity: 0; transform: translateY(12px); }

/* App launcher */
.cx-launcher { max-width: 1200px; margin: clamp(2.5rem, 6vw, 4.5rem) auto 3rem; }
.cx-launcher__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: .75rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--cx-black);
}
.cx-launcher__title {
  font-size: clamp(1.6rem, 3vw, 2.25rem);
  font-weight: 650;
  letter-spacing: -0.02em;
  margin: 0;
}
.cx-launcher__count { color: var(--cx-steel-600); }
.cx-launcher__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
  gap: 1rem;
}

/* One app: a small poster -- index number, a piece of the mark, and the
   name set large at the bottom. Inverts to black on hover, and the
   piece turns violet with an eighth turn. */
.cx-app-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 270px;
  padding: 1.4rem;
  color: var(--cx-black);
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius-lg);
  overflow: hidden;
  transition: background-color .25s ease, color .25s ease, border-color .25s ease;
}
.cx-app-card:hover,
.cx-app-card:focus-visible {
  color: var(--cx-surface);
  background: var(--cx-black);
  border-color: var(--cx-black);
}
.cx-app-card:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--cx-accent-rgb), .45); }
.cx-app-card__top { display: flex; justify-content: space-between; opacity: .6; }
.cx-app-card__glyph {
  position: absolute;
  top: 3.25rem;
  right: 1.4rem;
  width: 96px;
  height: auto;
  aspect-ratio: 44 / 20;
  overflow: visible;
  color: var(--cx-black);
  transition: rotate .5s cubic-bezier(.2, .7, .2, 1), color .25s ease;
}
.cx-app-card__glyph--c { width: 72px; aspect-ratio: 1; }
.cx-app-card--r1 .cx-app-card__glyph { rotate: 90deg; }
.cx-app-card--r2 .cx-app-card__glyph { rotate: 180deg; }
.cx-app-card--r3 .cx-app-card__glyph { rotate: -90deg; }
.cx-app-card:hover .cx-app-card__glyph,
.cx-app-card:focus-visible .cx-app-card__glyph { color: var(--cx-accent); rotate: 45deg; }
.cx-app-card--r1:hover .cx-app-card__glyph,
.cx-app-card--r1:focus-visible .cx-app-card__glyph { rotate: 135deg; }
.cx-app-card--r2:hover .cx-app-card__glyph,
.cx-app-card--r2:focus-visible .cx-app-card__glyph { rotate: 225deg; }
.cx-app-card--r3:hover .cx-app-card__glyph,
.cx-app-card--r3:focus-visible .cx-app-card__glyph { rotate: -45deg; }
.cx-app-card__body { display: flex; flex-direction: column; gap: .35rem; max-width: 85%; }
.cx-app-card__name { font-size: 1.45rem; font-weight: 650; line-height: 1.15; letter-spacing: -0.02em; }
.cx-app-card__desc { font-size: .88rem; line-height: 1.4; opacity: .65; }

@media (prefers-reduced-motion: reduce) {
  .cx-hero__frag,
  .cx-hero__reveal,
  .cx-hero__cue,
  .cx-app-card,
  .cx-app-card__glyph { transition: none; }
  .cx-hero__cue-arrow { animation: none; }
  .cx-hero__word,
  html:not(.cx-home-intro) .cx-hero__word { animation: none; transform: none; }
}

/* Employee Portal notifications (notifications.html): one section per
   age bucket -- Today, This week, then each month -- under a small
   uppercase label. */
.cx-notif-group + .cx-notif-group { margin-top: 1.5rem; }
.cx-notif-group__label {
  margin: 0 0 .25rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
}

/* Inline editing on info cards (employee_portal/profile_personal.html):
   "Edit" swaps each value for its input in the same row. While editing,
   the card takes the accent tint so it's clearly separate from the
   read-only cards around it. */
.cx-editable:not(.is-editing) .cx-inline-input,
.cx-editable:not(.is-editing) .cx-edit-actions,
.cx-editable:not(.is-editing) .cx-inline-only { display: none; }
.cx-editable.is-editing .cx-inline-display { display: none; }
.cx-editable.is-editing .cx-info-card {
  background: var(--cx-accent-soft);
  border-color: rgba(var(--cx-accent-rgb), .35);
}
.cx-editable .cx-display-row__value { min-width: 0; }
.cx-editable.is-editing .cx-display-row__value { display: block; flex: 1 1 14rem; }
.cx-inline-stack { display: grid; gap: .375rem; }
.cx-edit-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .85rem 0 .5rem;
}
.cx-edit-actions .form-text { flex-basis: 100%; margin: 0; }

/* A request that goes to HR for approval (_correction_form.html) opens
   in its own grey panel, set apart from the card it sits in. */
.cx-request-panel {
  margin-top: .75rem;
  padding: 1rem;
  background: var(--cx-sand);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
}
.cx-request-panel > .row { margin-top: 0 !important; }

/* Employee Portal My Pay (pay.html): summary figures and the tax-year
   headings above each payslip table. */
.cx-pay-summary__label { font-size: .78rem; color: var(--cx-steel-600); }
.cx-pay-summary__value { font-size: 1.15rem; font-weight: 600; color: var(--cx-ink); }
.cx-pay-amount { font-variant-numeric: tabular-nums; }
.cx-pay-year {
  margin: 1rem 0 .5rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
}

/* Shifts calendar (shifts/calendar.html): Google Calendar-style day,
   week and month views of the rota.

   Day and week are a time grid: an hour gutter plus one column per
   day, each column 24 hours tall (--cx-cal-hour per hour). Each shift
   is positioned from inline custom properties the template sets --
   --start / --end (minutes from midnight) and --col / --cols (its place
   among the shifts it overlaps) -- so no colour or size is ever inline.
   Month is a 7-column grid of day cells listing shifts as pills. */
.cx-cal { --cx-cal-hour: 44px; --cx-cal-gutter: 3.5rem; }
.cx-cal-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-bottom: 1rem;
}
.cx-cal-toolbar__title { font-size: 1.15rem; font-weight: 600; margin: 0 .5rem 0 .25rem; }
.cx-cal-legend { display: flex; flex-wrap: wrap; gap: .35rem .9rem; font-size: .8rem; color: var(--cx-steel-600); }
.cx-cal-legend__item { display: inline-flex; align-items: center; gap: .35rem; }
.cx-cal-swatch {
  width: .65rem;
  height: .65rem;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-cal-edge, var(--cx-steel));
}

/* Colour slots: set the pair every event/pill/swatch reads from. */
.cx-cal-c--slate { --cx-cal-edge: var(--cx-cal-slate); --cx-cal-fill: var(--cx-cal-slate-soft); }
.cx-cal-c--teal  { --cx-cal-edge: var(--cx-cal-teal);  --cx-cal-fill: var(--cx-cal-teal-soft); }
.cx-cal-c--amber { --cx-cal-edge: var(--cx-cal-amber); --cx-cal-fill: var(--cx-cal-amber-soft); }
.cx-cal-c--rose  { --cx-cal-edge: var(--cx-cal-rose);  --cx-cal-fill: var(--cx-cal-rose-soft); }
.cx-cal-c--sky   { --cx-cal-edge: var(--cx-cal-sky);   --cx-cal-fill: var(--cx-cal-sky-soft); }
.cx-cal-c--olive { --cx-cal-edge: var(--cx-cal-olive); --cx-cal-fill: var(--cx-cal-olive-soft); }

/* Time grid (day + week) */
.cx-cal-frame { overflow-x: auto; }
.cx-cal-time { min-width: 100%; }
.cx-cal-time--week { min-width: 44rem; }
.cx-cal-row {
  display: grid;
  grid-template-columns: var(--cx-cal-gutter) repeat(var(--cx-cal-days), minmax(0, 1fr));
}
.cx-cal-head { border-bottom: 1px solid var(--cx-border); }
.cx-cal-head__day {
  padding: .35rem .25rem .5rem;
  text-align: center;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
}
.cx-cal-date {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.1rem;
  height: 2.1rem;
  margin-top: .15rem;
  border-radius: var(--cx-radius-pill);
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--cx-ink);
  text-decoration: none;
}
a.cx-cal-date:hover { background: var(--cx-sand); }
.cx-cal-date--today,
a.cx-cal-date--today:hover { background: var(--cx-accent); color: var(--cx-surface); }
.cx-cal-allday { border-bottom: 1px solid var(--cx-border); min-height: .5rem; }
.cx-cal-allday__label { font-size: .68rem; color: var(--cx-steel); padding: .3rem .4rem 0 0; text-align: right; }
.cx-cal-allday__cell { padding: .2rem; border-left: 1px solid var(--cx-border); }
.cx-cal-tag {
  display: block;
  margin-bottom: .15rem;
  padding: .05rem .4rem;
  border-radius: var(--cx-radius-sm);
  font-size: .72rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--cx-steel-600);
  background: var(--cx-sand);
}
.cx-cal-scroll { max-height: 68vh; overflow-y: auto; position: relative; }
.cx-cal-gutter { position: relative; height: calc(24 * var(--cx-cal-hour)); }
.cx-cal-gutter__hour {
  position: absolute;
  right: .45rem;
  transform: translateY(-50%);
  font-size: .68rem;
  color: var(--cx-steel);
  font-family: var(--cx-font-mono);
}
.cx-cal-col {
  position: relative;
  height: calc(24 * var(--cx-cal-hour));
  border-left: 1px solid var(--cx-border);
  background-image: repeating-linear-gradient(
    to bottom,
    var(--cx-border) 0,
    var(--cx-border) 1px,
    transparent 1px,
    transparent var(--cx-cal-hour)
  );
}
.cx-cal-col--today { background-color: rgba(var(--cx-accent-rgb), .035); }
.cx-cal-col--schedulable { cursor: copy; }
.cx-cal-event {
  position: absolute;
  top: calc(var(--start) * var(--cx-cal-hour) / 60);
  height: max(calc((var(--end) - var(--start)) * var(--cx-cal-hour) / 60 - 2px), 1.3rem);
  left: calc(var(--col) * (100% - 10px) / var(--cols) + 2px);
  width: calc((100% - 10px) / var(--cols) - 2px);
  display: flex;
  flex-direction: column;
  padding: .2rem .4rem;
  overflow: hidden;
  border: 0;
  border-left: 3px solid var(--cx-cal-edge, var(--cx-steel));
  border-radius: var(--cx-radius-sm);
  background: var(--cx-cal-fill, var(--cx-sand));
  color: var(--cx-ink);
  font-size: .76rem;
  line-height: 1.25;
  text-align: left;
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--cx-surface);
}
.cx-cal-event:hover,
.cx-cal-event:focus-visible { box-shadow: 0 0 0 1px var(--cx-surface), var(--cx-shadow); z-index: 2; outline: none; }
.cx-cal-event--mine { font-weight: 600; }
.cx-cal-event--cont-before { border-top-left-radius: 0; border-top-right-radius: 0; }
.cx-cal-event--cont-after { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.cx-cal-event__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cx-cal-event__meta { color: var(--cx-steel-600); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cx-cal-now {
  position: absolute;
  left: -1px;
  right: 0;
  top: calc(var(--now) * var(--cx-cal-hour) / 60);
  border-top: 2px solid var(--cx-accent);
  pointer-events: none;
  z-index: 3;
}
.cx-cal-now::before {
  content: "";
  position: absolute;
  left: -5px;
  top: -6px;
  width: 10px;
  height: 10px;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-accent);
}

/* Month */
.cx-cal-month { min-width: 40rem; }
.cx-cal-month__head,
.cx-cal-month__week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cx-cal-month__head > div {
  padding: .4rem;
  text-align: center;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
  border-bottom: 1px solid var(--cx-border);
}
.cx-cal-cell {
  min-height: 7.5rem;
  padding: .25rem .3rem .4rem;
  border-right: 1px solid var(--cx-border);
  border-bottom: 1px solid var(--cx-border);
}
.cx-cal-month__week > .cx-cal-cell:first-child { border-left: 1px solid var(--cx-border); }
.cx-cal-cell--out { background: var(--cx-bg); }
.cx-cal-cell--out .cx-cal-date { color: var(--cx-steel); }
.cx-cal-cell__date { text-align: center; margin-bottom: .15rem; }
.cx-cal-cell__date .cx-cal-date { font-size: .85rem; min-width: 1.7rem; height: 1.7rem; }
.cx-cal-cell--schedulable { cursor: copy; }
.cx-cal-pill {
  display: flex;
  align-items: center;
  gap: .3rem;
  width: 100%;
  margin-bottom: .1rem;
  padding: .05rem .3rem;
  border: 0;
  border-radius: var(--cx-radius-sm);
  background: transparent;
  color: var(--cx-ink);
  font-size: .74rem;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
}
.cx-cal-pill:hover,
.cx-cal-pill:focus-visible { background: var(--cx-cal-fill, var(--cx-sand)); outline: none; }
.cx-cal-pill__time { font-family: var(--cx-font-mono); color: var(--cx-steel-600); }
.cx-cal-pill__name { overflow: hidden; text-overflow: ellipsis; }
.cx-cal-more { display: block; padding: 0 .3rem; font-size: .72rem; font-weight: 600; color: var(--cx-steel-600); }

/* Work timetable (work/timetable.html): the whole schedule as a grid --
   a sticky first column (an employee, store or event), one column per
   day, a total on the right. Shifts are chips coloured by person with
   the same --cx-cal-* slots as the Shifts calendar, so someone looks
   the same in both. --compact is the month view's narrow columns. */
.cx-tt-toolbar,
.cx-tt-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-bottom: .75rem;
}
.cx-tt-filters { margin-bottom: 1rem; }
.cx-tt-toolbar__title { font-size: 1.15rem; font-weight: 600; margin: 0 .5rem 0 .25rem; }
.cx-tt-frame { overflow: auto; max-height: 75vh; }
.cx-tt {
  width: 100%;
  min-width: 46rem;
  border-collapse: separate;
  border-spacing: 0;
  font-size: .8rem;
}
.cx-tt--compact { min-width: 0; }
.cx-tt th,
.cx-tt td {
  border-bottom: 1px solid var(--cx-border);
  border-right: 1px solid var(--cx-border);
  padding: .35rem;
  vertical-align: top;
}
.cx-tt thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--cx-surface);
  text-align: center;
  font-weight: 600;
}
.cx-tt__corner,
.cx-tt__label {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 11rem;
  background: var(--cx-surface);
  text-align: left;
}
.cx-tt thead .cx-tt__corner { z-index: 3; text-align: left; color: var(--cx-steel-600); font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; }
.cx-tt__daylink { display: flex; flex-direction: column; align-items: center; color: var(--cx-ink); text-decoration: none; }
.cx-tt__dow { font-size: .68rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--cx-steel-600); }
.cx-tt__date {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.7rem;
  height: 1.7rem;
  border-radius: var(--cx-radius-pill);
  font-size: .95rem;
  font-weight: 500;
}
.cx-tt__daylink:hover .cx-tt__date { background: var(--cx-sand); }
.cx-tt__day--today .cx-tt__date { background: var(--cx-accent); color: var(--cx-surface); }
.cx-tt__day--weekend .cx-tt__dow { color: var(--cx-steel); }
.cx-tt__note { display: block; font-size: .66rem; font-weight: 500; color: var(--cx-steel-600); }
.cx-tt__section th {
  position: sticky;
  left: 0;
  background: var(--cx-bg);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
}
.cx-tt__name { font-weight: 600; }
.cx-tt__sub { font-size: .72rem; color: var(--cx-steel-600); font-weight: 400; }
.cx-tt__row--me > .cx-tt__label { box-shadow: inset 3px 0 0 var(--cx-accent); }
.cx-tt__cell { min-width: 6.5rem; }
.cx-tt--compact .cx-tt__cell { min-width: 2.6rem; padding: .2rem; }
.cx-tt__cell--today { background: rgba(var(--cx-accent-rgb), .04); }
.cx-tt__cell--event { background: var(--cx-steel-100); }
.cx-tt__chip {
  display: flex;
  flex-direction: column;
  margin-bottom: .2rem;
  padding: .15rem .35rem;
  border-left: 3px solid var(--cx-cal-edge, var(--cx-steel));
  border-radius: var(--cx-radius-sm);
  background: var(--cx-cal-fill, var(--cx-sand));
  color: var(--cx-ink);
  text-decoration: none;
  line-height: 1.25;
}
.cx-tt__chip:hover,
.cx-tt__chip:focus-visible { box-shadow: var(--cx-shadow-sm); color: var(--cx-ink); }
.cx-tt--compact .cx-tt__chip { padding: .1rem .2rem; font-size: .68rem; }
.cx-tt__time { font-family: var(--cx-font-mono); font-size: .74rem; }
.cx-tt__who { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cx-tt__meta { font-size: .7rem; color: var(--cx-steel-600); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cx-tt__tag {
  display: block;
  margin-bottom: .2rem;
  padding: .05rem .35rem;
  border-radius: var(--cx-radius-sm);
  font-size: .7rem;
  color: var(--cx-steel-600);
  background: var(--cx-sand);
}
.cx-tt__total { text-align: right; font-family: var(--cx-font-mono); white-space: nowrap; }
.cx-tt tfoot th,
.cx-tt tfoot td { font-weight: 600; background: var(--cx-bg); }

/* Blocked-out time on the calendar (employee_portal/calendar.html):
   someone's "can't be booked" period, drawn like a shift but hatched
   and neutral, so it reads as unavailable rather than work. */
.cx-cal-block {
  position: absolute;
  top: calc(var(--start) * var(--cx-cal-hour) / 60);
  height: max(calc((var(--end) - var(--start)) * var(--cx-cal-hour) / 60 - 2px), 1.3rem);
  left: calc(var(--col) * (100% - 10px) / var(--cols) + 2px);
  width: calc((100% - 10px) / var(--cols) - 2px);
  display: flex;
  flex-direction: column;
  padding: .2rem .4rem;
  overflow: hidden;
  border: 1px dashed var(--cx-steel);
  border-radius: var(--cx-radius-sm);
  background: repeating-linear-gradient(
    135deg,
    var(--cx-steel-100) 0,
    var(--cx-steel-100) 6px,
    var(--cx-surface) 6px,
    var(--cx-surface) 12px
  );
  color: var(--cx-steel-600);
  font-size: .76rem;
  line-height: 1.25;
  text-align: left;
  cursor: pointer;
}
.cx-cal-block:hover,
.cx-cal-block:focus-visible { border-style: solid; outline: none; z-index: 2; }
.cx-cal-tag--block {
  width: 100%;
  border: 1px dashed var(--cx-steel);
  background: repeating-linear-gradient(
    135deg,
    var(--cx-steel-100) 0,
    var(--cx-steel-100) 5px,
    var(--cx-surface) 5px,
    var(--cx-surface) 10px
  );
  text-align: left;
  cursor: pointer;
}
.cx-tt__tag--blocked {
  border: 1px dashed var(--cx-steel);
  background: repeating-linear-gradient(
    135deg,
    var(--cx-steel-100) 0,
    var(--cx-steel-100) 5px,
    var(--cx-surface) 5px,
    var(--cx-surface) 10px
  );
}

/* Employment-type marks on the calendar (managers only -- the template
   only adds these classes for shifts the viewer manages): a patterned
   strip down the block's right-hand edge, plus a short badge by the
   name. The pattern stays out from behind the text so it's always easy
   to read. Contractors also show their shift's estimated cost.
   --cx-cal-type-pattern holds the pattern; the strip (and the legend
   key) paint it. */
.cx-cal-type--stripes {
  --cx-cal-type-pattern: repeating-linear-gradient(
    135deg,
    rgba(var(--cx-steel-rgb), .55) 0,
    rgba(var(--cx-steel-rgb), .55) 2px,
    transparent 2px,
    transparent 5px
  );
}
.cx-cal-type--dots {
  --cx-cal-type-pattern: radial-gradient(rgba(var(--cx-steel-rgb), .8) 1px, transparent 1.5px) 0 0 / 5px 5px;
}
.cx-cal-type--grid {
  --cx-cal-type-pattern:
    linear-gradient(rgba(var(--cx-steel-rgb), .45) 1px, transparent 1px) 0 0 / 5px 5px,
    linear-gradient(90deg, rgba(var(--cx-steel-rgb), .45) 1px, transparent 1px) 0 0 / 5px 5px;
}
.cx-cal-event.cx-cal-type--stripes,
.cx-cal-event.cx-cal-type--dots,
.cx-cal-event.cx-cal-type--grid { padding-right: calc(.4rem + 9px); }
.cx-cal-event.cx-cal-type--stripes::after,
.cx-cal-event.cx-cal-type--dots::after,
.cx-cal-event.cx-cal-type--grid::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 9px;
  border-left: 1px solid rgba(var(--cx-steel-rgb), .25);
  background: var(--cx-surface);
  background: var(--cx-cal-type-pattern), var(--cx-surface);
  pointer-events: none;
}
.cx-cal-type-badge {
  display: inline-block;
  margin-right: .3rem;
  padding: 0 .3rem;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-surface);
  border: 1px solid var(--cx-cal-edge, var(--cx-steel));
  color: var(--cx-ink);
  font-family: var(--cx-font-mono);
  font-size: .62rem;
  font-weight: 600;
  line-height: 1.35;
  vertical-align: 1px;
}
.cx-cal-event__cost,
.cx-cal-pill__cost {
  font-family: var(--cx-font-mono);
  font-weight: 600;
  color: var(--cx-ink);
}
.cx-cal-pill__cost { margin-left: auto; font-size: .7rem; }
.cx-cal-type-key {
  display: inline-block;
  width: .7rem;
  height: .9rem;
  border-radius: 2px;
  border: 1px solid rgba(var(--cx-steel-rgb), .35);
  background: var(--cx-surface);
  background: var(--cx-cal-type-pattern, none), var(--cx-surface);
}

/* Hours per person under the calendar (employee_portal/calendar.html):
   a small horizontal bar chart. Bars are thin (<= 14px), grow from one
   baseline with a 4px rounded data-end, and wear the person's calendar
   colour; the name and value stay in text tokens beside them. --pct is
   the bar's share of the longest bar. */
.cx-cal-hours {
  margin-top: 1rem;
  padding: .9rem 1rem;
  background: var(--cx-surface);
  border: 1px solid var(--cx-border);
  border-radius: var(--cx-radius);
  box-shadow: var(--cx-shadow-sm);
}
.cx-cal-hours__title {
  margin: 0 0 .6rem;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cx-steel-600);
}
.cx-cal-hours__list { margin: 0; padding: 0; list-style: none; }
.cx-cal-hours__row {
  display: grid;
  grid-template-columns: minmax(7rem, 12rem) 1fr;
  align-items: center;
  gap: .75rem;
  padding: .2rem 0;
  font-size: .82rem;
}
.cx-cal-hours__name {
  color: var(--cx-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cx-cal-hours__track {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  border-left: 1px solid var(--cx-border);
}
.cx-cal-hours__bar {
  flex: 0 0 auto;
  width: calc(var(--pct) * (100% - 3.5rem) / 100);
  min-width: 2px;
  height: 14px;
  border-radius: 0 4px 4px 0;
  background: var(--cx-cal-edge, var(--cx-steel));
  transition: opacity .12s ease;
}
.cx-cal-hours__value {
  flex: 0 0 auto;
  font-family: var(--cx-font-mono);
  font-size: .78rem;
  color: var(--cx-steel-600);
}
.cx-cal-hours__list:hover .cx-cal-hours__row:not(:hover) .cx-cal-hours__bar { opacity: .45; }
@media (max-width: 575.98px) {
  .cx-cal-hours__row { grid-template-columns: 1fr; gap: .15rem; }
}

/* Events on the calendar (employee_portal/calendar.html): a blue tag
   under each day heading (and in month cells) while an event runs, and a
   thin blue bar down the right edge of the day column spanning that
   day's shifts on it -- the gap the shift blocks leave on the right. A
   second event on the same day sits one lane further in. */
.cx-cal-eventtag {
  display: block;
  max-width: 100%;
  margin-top: .25rem;
  padding: .1rem .45rem;
  border-radius: var(--cx-radius-sm);
  background: var(--cx-cal-event);
  color: var(--cx-surface);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: var(--cx-shadow-sm);
}
a.cx-cal-eventtag:hover,
a.cx-cal-eventtag:focus-visible { color: var(--cx-surface); box-shadow: var(--cx-shadow); }
/* The event's hours across the whole column -- a light wash behind the
   shift blocks (it comes first in the column, so they paint over it). */
.cx-cal-eventband {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--start) * var(--cx-cal-hour) / 60);
  height: calc((var(--end) - var(--start)) * var(--cx-cal-hour) / 60);
  background: var(--cx-cal-event-soft);
  border-top: 2px solid var(--cx-cal-event);
  opacity: .85;
  pointer-events: none;
}
.cx-cal-eventbar {
  position: absolute;
  top: calc(var(--start) * var(--cx-cal-hour) / 60);
  height: max(calc((var(--end) - var(--start)) * var(--cx-cal-hour) / 60), 6px);
  right: calc(var(--lane, 0) * 6px);
  width: 5px;
  border-radius: 3px;
  background: var(--cx-cal-event);
  z-index: 1;
}

/* Event status colours: each status sets the pair the event tag, band
   and bar read. Planning is also dashed and lighter, so an unconfirmed
   event looks tentative even without colour. */
.cx-cal-ev--planning { --cx-cal-event: var(--cx-ev-planning); --cx-cal-event-soft: var(--cx-ev-planning-soft); }
.cx-cal-ev--upcoming { --cx-cal-event: var(--cx-ev-upcoming); --cx-cal-event-soft: var(--cx-ev-upcoming-soft); }
.cx-cal-ev--running  { --cx-cal-event: var(--cx-ev-running);  --cx-cal-event-soft: var(--cx-ev-running-soft); }
.cx-cal-ev--finished { --cx-cal-event: var(--cx-ev-finished); --cx-cal-event-soft: var(--cx-ev-finished-soft); }
.cx-cal-eventtag.cx-cal-ev--planning {
  background: var(--cx-cal-event-soft);
  color: var(--cx-ink);
  border: 1px dashed var(--cx-cal-event);
  box-shadow: none;
}
a.cx-cal-eventtag.cx-cal-ev--planning:hover,
a.cx-cal-eventtag.cx-cal-ev--planning:focus-visible { color: var(--cx-ink); }
.cx-cal-eventband.cx-cal-ev--planning { border-top-style: dashed; }
.cx-cal-eventbar.cx-cal-ev--planning {
  background: repeating-linear-gradient(
    to bottom,
    var(--cx-cal-event) 0,
    var(--cx-cal-event) 6px,
    transparent 6px,
    transparent 10px
  );
}

/* Event status bubble (shifts/_macros.html:event_status_bubble): a
   small pill -- a dot in the status colour and the status's name -- used
   in the calendar's event tags and filter, the events list and page, and
   the timetable's event rows. The name means it never relies on colour. */
.cx-ev-status {
  --cx-ev: var(--cx-ev-upcoming);
  --cx-ev-soft: var(--cx-ev-upcoming-soft);
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  flex: 0 0 auto;
  padding: .05rem .45rem .05rem .35rem;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-ev-soft);
  border: 1px solid var(--cx-ev);
  color: var(--cx-ink);
  font-size: .66rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}
.cx-ev-status__dot {
  width: .45rem;
  height: .45rem;
  border-radius: var(--cx-radius-pill);
  background: var(--cx-ev);
}
.cx-ev-status--planning   { --cx-ev: var(--cx-ev-planning); --cx-ev-soft: var(--cx-ev-planning-soft); border-style: dashed; }
.cx-ev-status--upcoming   { --cx-ev: var(--cx-ev-upcoming); --cx-ev-soft: var(--cx-ev-upcoming-soft); }
.cx-ev-status--running    { --cx-ev: var(--cx-ev-running);  --cx-ev-soft: var(--cx-ev-running-soft); }
.cx-ev-status--finished   { --cx-ev: var(--cx-ev-finished); --cx-ev-soft: var(--cx-ev-finished-soft); }
.cx-ev-status--called_off { --cx-ev: var(--cx-ev-called-off); --cx-ev-soft: var(--cx-ev-called-off-soft); }

/* Inside a solid event tag the bubble goes white, so it stands out. */
.cx-cal-eventtag {
  display: flex;
  align-items: center;
  gap: .35rem;
}
.cx-cal-eventtag .cx-ev-status { background: var(--cx-surface); border-color: var(--cx-surface); }
.cx-cal-eventtag.cx-cal-ev--planning .cx-ev-status { border-color: var(--cx-ev-planning); }
.cx-cal-eventtag__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The calendar's event filter: a button dressed as the other filters'
   .form-select, opening a list with status bubbles. */
.cx-ev-select { display: inline-flex; align-items: center; gap: .35rem; max-width: 20rem; white-space: nowrap; overflow: hidden; }
.cx-ev-menu { min-width: 18rem; }
.cx-ev-menu .dropdown-item.active { color: var(--cx-surface); }

/* A manager's "view as" preview of the calendar: a strip above the
   toolbar saying whose view it is and that it's read-only. */
.cx-cal-preview {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem .75rem;
  margin-bottom: .75rem;
  padding: .55rem .8rem;
  border-radius: var(--cx-radius);
  border: 1px solid rgba(var(--cx-accent-rgb), .35);
  border-left: 4px solid var(--cx-accent);
  background: var(--cx-accent-soft);
  font-size: .85rem;
  color: var(--cx-ink);
}
/* A store's place bubble (shifts/_macros.html:place_bubble) -- neutral,
   so an event (in its status colour) stands out beside it. */
.cx-place--store { --cx-ev: var(--cx-steel); --cx-ev-soft: var(--cx-steel-100); }
/* Neither a store nor an event: a dashed outline, so it reads as
   "missing" rather than as one more store. */
.cx-place--none { --cx-ev: var(--cx-steel); --cx-ev-soft: transparent; border-style: dashed; color: var(--cx-steel); }

/* Event thumbnails (shifts/_macros.html:event_thumb): the event's
   picture as a small rounded square, or its initials on its status
   colour when it has none. */
.cx-ev-thumb {
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--cx-radius-sm);
  object-fit: cover;
  border: 1px solid var(--cx-border);
  background: var(--cx-sand);
}
.cx-ev-thumb--lg { width: 4.5rem; height: 4.5rem; border-radius: var(--cx-radius); }
.cx-ev-thumb--initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  gap: 0;
  border: 1px solid var(--cx-ev);
  background: var(--cx-ev-soft);
  color: var(--cx-ink);
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .02em;
}
.cx-ev-thumb--lg.cx-ev-thumb--initials { font-size: 1.3rem; }

/* The info button (ⓘ): opens an app's info page (/apps/<slug>). In
   every app page's header (templates/base.html) and pinned to each
   homepage tile's bottom-right corner (templates/home.html). */
.cx-info-btn {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: var(--cx-radius-pill);
  border: 1px solid var(--cx-border);
  background: var(--cx-surface);
  color: var(--cx-black);
  font-family: var(--cx-font-mono);
  font-size: .85rem;
  font-weight: 700;
  text-decoration: none;
}
.cx-info-btn:hover,
.cx-info-btn:focus-visible {
  border-color: var(--cx-accent);
  color: var(--cx-accent);
  outline: none;
  box-shadow: 0 0 0 3px rgba(var(--cx-accent-rgb), .3);
}
.cx-app-tile { position: relative; display: flex; }
.cx-app-tile > .cx-app-card { flex: 1 1 auto; }
.cx-app-tile__info { position: absolute; right: 1rem; bottom: 1rem; }
