/* ==========================================================================
   Attendance v2 — global theme
   --------------------------------------------------------------------------
   Loaded LAST, after Bootstrap/AdminBSB, so it can neutralise the parts of the
   old chrome it replaces. Everything here is prefixed `ti-` (Trivedi Infoway)
   so it cannot collide with Bootstrap or AdminBSB class names — the old theme
   styles `.navbar`, `.sidebar` and `#leftsidebar`, none of which we use.

   Light/dark is driven by `data-theme` on <html>, set before first paint by
   the inline script in header.php. Every colour is a custom property, so a
   page never hard-codes one.

   Sections
     1  Fonts
     2  Design tokens
     3  Reset & base
     4  Keyframes
     5  App shell
     6  Sidebar
     7  Topbar
     8  Components
     9  Utilities
    10  Legacy compatibility
    11  Responsive
   ========================================================================== */


/* 1 — Fonts ============================================================== */

/* Plus Jakarta Sans for UI, IBM Plex Mono for anything numeric: times, totals,
   dates. Monospace digits stop table columns from jittering as values change.
   The families are requested from a <link> in header.php, not an @import here —
   an @import is serialised behind this stylesheet, so the browser cannot even
   discover the font request until theme.css has finished downloading. */


/* 2 — Design tokens ====================================================== */

:root {
  /* --- Type ---
     The fallbacks matter: if the webfont is slow or blocked, Segoe UI (Windows)
     and system-ui are the closest widely-available geometric sans, so the
     layout does not visibly reflow when the real font arrives. */
  --ti-font: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI Variable Text', 'Segoe UI', Roboto, sans-serif;
  --ti-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', 'Cascadia Mono', Menlo, monospace;

  /* --- Metrics ---
     The design canvas was 1180px wide. Those px values read noticeably small
     on a real 1920px monitor, so the chrome is scaled up here: same
     proportions, sized for the screen it actually runs on. */
  --ti-header-h: 64px;
  --ti-sidebar-w: 236px;
  --ti-sidebar-w-collapsed: 72px;
  --ti-radius-sm: 10px;
  --ti-radius: 14px;
  --ti-radius-lg: 18px;
  --ti-gap: 14px;

  /* --- Motion --- */
  --ti-ease: cubic-bezier(.2, .8, .2, 1);
  --ti-fast: .2s;
  --ti-base: .3s;
  --ti-slow: .4s;

  /* --- Accent hues, shared by both themes --- */
  --ti-hue-violet: 275;
  --ti-hue-blue: 235;
  --ti-hue-teal: 195;
  --ti-hue-green: 150;
  --ti-hue-amber: 45;
  --ti-hue-red: 25;

  /* --- Status colours --- */
  --ti-green: oklch(.68 .15 150);
  --ti-green-bg: oklch(.6 .16 150 / .18);
  --ti-blue: oklch(.68 .14 235);
  --ti-blue-bg: oklch(.6 .15 235 / .2);
  --ti-amber: oklch(.7 .16 45);
  --ti-amber-bg: oklch(.62 .18 40 / .18);
  /* --ti-red is not here: a red light enough to read on near-black is far too
     light on paper -- the same value measured 6.3:1 on a dark card and 2.9:1
     on a white one. It is given per theme below. --ti-red-bg stays: it is a
     translucent wash and takes its weight from whatever is under it. */
  --ti-red-bg: oklch(.6 .2 25 / .18);
}

/* --- Light (default) --- */
:root,
:root[data-theme="light"] {
  --ti-bg: #f2f2ef;
  --ti-canvas-glow: none;
  --ti-text: #1b1a17;
  --ti-text-soft: #57544d;
  --ti-text-muted: #77736a;

  /* 6.6:1 on white, and 5.1:1 on the red-tinted chip .ti-badge--red draws. */
  --ti-red: oklch(.5 .19 25);

  --ti-header-grad: linear-gradient(100deg, oklch(.42 .16 285), oklch(.46 .17 258));

  --ti-sidebar-bg: #fff;
  /* A count sitting on a .ti-tabs strip, which is --ti-chip-bg itself. It
     needs its own step away from that or the pill is the same colour as the
     thing it is drawn on -- darker here, lighter in dark mode. */
  --ti-count-bg: #e4e1d8;
  --ti-border: #e8e6df;
  /* For controls that must read as interactive on their own -- an unchecked
     checkbox or radio. --ti-border is tuned for panel edges and is too faint
     to carry a control by itself, especially in dark. */
  --ti-border-strong: #b9b6ac;

  --ti-nav-color: #4f4c45;
  --ti-nav-icon: #6f6b62;
  --ti-nav-hover-bg: oklch(.95 .03 275);
  --ti-nav-hover-color: oklch(.42 .16 280);
  --ti-accent: oklch(.55 .19 290);
  --ti-accent-soft: oklch(.55 .19 290 / .12);

  --ti-card-bg: #fff;
  --ti-chip-bg: #f7f6f2;
  /* Same colour as --ti-chip-bg once composited, but opaque. A native <select>
     hands its own background straight to the OS popup, and a translucent one
     composites over white -- see .ti-select. */
  --ti-chip-solid: #f7f6f2;
  --ti-track-bg: #e9e7e0;
  /* A panel that holds its own fields, which on paper-white has to be deeper
     than a chip: the fields inside it are white, and --ti-chip-bg left them
     two per cent apart from their own background -- boxes you could not see. */
  --ti-panel-bg: #edebe4;

  /* A tooltip is an overlay, not a surface: on a light page it inverts, which
     is what the native one does and what reads fastest over any background. */
  --ti-tip-bg: #1f1e1b;
  --ti-tip-text: #f4f3ef;
  --ti-tip-border: rgb(255 255 255 / .12);

  --ti-shadow-sm: 0 8px 18px -14px rgb(0 0 0 / .35);
  --ti-shadow: 0 16px 30px -20px rgb(0 0 0 / .3);
  --ti-shadow-lg: 0 30px 60px -30px rgb(0 0 0 / .35);

  color-scheme: light;
}

/* --- Dark --- */
:root[data-theme="dark"] {
  --ti-bg: #0a0c11;
  --ti-canvas-glow: radial-gradient(120% 90% at 15% 0%, rgb(120 110 255 / .09), transparent 60%);
  --ti-text: #e8eaf1;
  --ti-text-soft: #a9adba;
  /* Lifted from #7e8290, which measured 5.1:1 on --ti-bg. That clears AA on
     paper, but the things wearing it are the smallest type in the app -- the
     11px tracked-out mono of .ti-page-eyebrow, table headers, card captions --
     and at that size on near-black it read as grey noise rather than text.
     6.5:1 now, still two clear steps below --ti-text (16:1) and --ti-text-soft
     (8.7:1), so secondary text still reads as secondary. */
  --ti-text-muted: #8f95a4;

  /* 6.3:1 on a dark card. */
  --ti-red: oklch(.7 .19 25);

  --ti-header-grad: linear-gradient(100deg, oklch(.31 .11 285), oklch(.34 .12 258));

  --ti-sidebar-bg: #10131a;
  --ti-count-bg: rgb(255 255 255 / .12);
  --ti-border: rgb(255 255 255 / .07);
  --ti-border-strong: rgb(255 255 255 / .3);

  --ti-nav-color: #9598a6;
  --ti-nav-icon: oklch(.72 .13 275);
  --ti-nav-hover-bg: rgb(255 255 255 / .07);
  --ti-nav-hover-color: #fff;
  --ti-accent: oklch(.72 .15 275);
  --ti-accent-soft: oklch(.72 .15 275 / .14);

  --ti-card-bg: #12151d;
  --ti-chip-bg: rgb(255 255 255 / .04);
  --ti-chip-solid: #14161b;
  --ti-track-bg: rgb(255 255 255 / .06);
  /* Dark needs no deepening: the fields inside a panel are #12151d, which
     already reads against this wash. */
  --ti-panel-bg: rgb(255 255 255 / .04);

  /* Dark already is the overlay colour, so this lifts instead of inverting. */
  --ti-tip-bg: #1e222c;
  --ti-tip-text: #e8eaf1;
  --ti-tip-border: rgb(255 255 255 / .12);

  --ti-shadow-sm: 0 8px 18px -14px rgb(0 0 0 / .7);
  --ti-shadow: 0 16px 30px -20px rgb(0 0 0 / .6);
  --ti-shadow-lg: 0 30px 60px -30px rgb(0 0 0 / .8);

  color-scheme: dark;
}


/* 3 — Reset & base ======================================================= */

/* Bootstrap sets this on every page it is loaded on, and the layout here
   assumes it -- without it `min-height: 100vh` plus padding overflows the
   viewport and puts a scrollbar on a page that fits. The login screen no
   longer loads Bootstrap, so it has to be stated. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--ti-bg);
}

/* `display: flow-root` is load-bearing, not decoration. The content container
   (`.ti-page` / `section.content`) clears the fixed topbar with
   `margin-top: var(--ti-header-h)`, and it is effectively the first in-flow
   child of an unpadded body -- so that margin collapsed *out* of the body box
   and pushed body down by 64px instead of moving the content inside it. With
   `min-height: 100vh` still asking for a full-viewport body, the document came
   out 64px taller than the screen and every page carried a small scrollbar
   even when the content fitted (most visible on the dashboard, which is one
   screen of tiles). flow-root gives body its own formatting context, so the
   margin stays inside and the page measures exactly 100vh. */
body.ti-body {
  margin: 0;
  display: flow-root;
  min-height: 100vh;
  background: var(--ti-bg);
  background-image: var(--ti-canvas-glow);
  background-attachment: fixed;
  color: var(--ti-text);
  font-family: var(--ti-font);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  transition: background-color var(--ti-slow) ease, color var(--ti-slow) ease;
}

/* WebKit's `antialiased` above turns subpixel rendering off. On a paper-white
   page that is what you want -- it stops dark-on-light text from rendering
   heavier than the weight asked for. Light-on-dark is the opposite case: the
   glyphs already thin out against a near-black ground, and dropping subpixel
   AA on top of that is what makes small labels look washed rather than set.
   Dark keeps whatever the platform does by default.

   No effect on Windows or Linux, where this property was never implemented --
   it is macOS and iOS that this is for. */
:root[data-theme="dark"] body.ti-body {
  -webkit-font-smoothing: auto;
}

/* AdminBSB sets a 0.5s transition on `body` for its own theme switcher; that
   makes our own toggle feel sluggish and fights the one above. */
body.ti-body {
  transition-property: background-color, color;
}

.ti-body h1,
.ti-body h2,
.ti-body h3,
.ti-body h4,
.ti-body h5,
.ti-body h6 {
  font-family: var(--ti-font);
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--ti-text);
}

.ti-body a {
  color: var(--ti-accent);
  text-decoration: none;
  transition: color var(--ti-fast) ease;
}

.ti-body a:hover,
.ti-body a:focus {
  color: var(--ti-accent);
  filter: brightness(1.15);
  text-decoration: none;
}

.ti-body :focus-visible {
  outline: 2px solid var(--ti-accent);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Numeric text. Applied to any figure that sits in a column so the digits
   line up: totals, clock, dates, durations. */
.ti-num {
  font-family: var(--ti-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}


/* --- Cross-document view transitions ------------------------------------
   The design is a single-page app: changing tab is a state change, so nothing
   blinks. This app navigates for real, and a full reload between tabs is what
   made it feel heavier than the mockup.

   `navigation: auto` lets the browser animate between two page loads instead
   of cutting to white. Naming the header and the sidebar takes them out of the
   page snapshot, so they hold still while only the content crosses over --
   which is what the sidebar staying put in an SPA actually looks like.

   Chrome and Edge honour this today; everywhere else simply navigates as
   before, so there is nothing to fall back to. */
@view-transition {
  navigation: auto;
}

.ti-header  { view-transition-name: ti-header; }
.ti-sidebar { view-transition-name: ti-sidebar; }

::view-transition-old(root) {
  animation: ti-vt-out .18s ease both;
}

::view-transition-new(root) {
  animation: ti-vt-in .26s var(--ti-ease) both;
}

@keyframes ti-vt-out {
  to { opacity: 0; transform: translateY(-6px); }
}

@keyframes ti-vt-in {
  from { opacity: 0; transform: translateY(8px); }
}

/* A transition is motion; someone who asked for less should not get it. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}


/* 4 — Keyframes ========================================================== */

@keyframes ti-rise-in {
  from { opacity: 0; transform: translateY(14px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes ti-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Diagonal highlight that travels across a tile. Used sparingly — on the
   dashboard metric cards only. */
@keyframes ti-sweep {
  0%        { transform: translateX(-120%) skewX(-18deg); }
  55%, 100% { transform: translateX(320%) skewX(-18deg); }
}

@keyframes ti-badge-pulse {
  0%, 100% { box-shadow: 0 0 0 0 oklch(.55 .2 20 / .55); }
  60%      { box-shadow: 0 0 0 6px oklch(.55 .2 20 / 0); }
}

@keyframes ti-shimmer {
  0%   { background-position: -160% 0; }
  100% { background-position: 160% 0; }
}

@keyframes ti-ripple {
  from { transform: scale(0); opacity: .6; }
  to   { transform: scale(32); opacity: 0; }
}

.ti-rise { animation: ti-rise-in .55s var(--ti-ease) both; }
.ti-fade { animation: ti-fade-in .4s ease both; }

/* Respect the OS setting. Everything still renders, it just arrives at once. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* 5 — App shell ========================================================== */

/* Each content view includes nav.php and left_sidebar.php itself, so there is
   no wrapper element to hang a flex layout on. Both are `position: fixed`
   instead and the offset lives on the content container — which also means a
   view needs no extra markup to sit correctly.

   `section.content` is the old AdminBSB container every un-migrated view still
   opens with; `.ti-page` is the class redesigned views use. Both get the same
   treatment so the two can coexist while the migration is in progress. */
.ti-body section.content,
.ti-body .ti-page {
  margin: var(--ti-header-h) 0 0 var(--ti-sidebar-w);
  padding: 26px 28px 34px;
  min-height: calc(100vh - var(--ti-header-h));
  transition: margin-left var(--ti-base) var(--ti-ease);
}

:root[data-sidebar="collapsed"] .ti-body section.content,
:root[data-sidebar="collapsed"] .ti-body .ti-page {
  margin-left: var(--ti-sidebar-w-collapsed);
}

/* The empty <section> that wraps the sidebar include must not add height. */
.ti-body > section:empty,
.ti-body section:has(> .ti-sidebar) {
  margin: 0;
  padding: 0;
  min-height: 0;
}

.ti-page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}

.ti-page-eyebrow {
  font-family: var(--ti-mono);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
  margin-bottom: 6px;
}

/* A title with something qualifying it on the same line -- the financial year
   the leave figures are counted over. Wraps rather than pushing the chip off
   the edge on a narrow screen; baseline, not centre, so the chip sits with the
   text and not with the cap height of a 27px heading. */
.ti-titlerow {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
}

/* These sit on headings as often as not, and `.ti-body h1..h6` in g3 is
   (0,1,1) -- one more than a bare class. Without the .ti-body in front, a
   heading quietly took the base colour and letter-spacing instead of the
   ones stated right here, which is exactly what happened to the Settings
   card titles. */
.ti-body .ti-page-title {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -.03em;
  margin: 0;
}


/* 6 — Sidebar ============================================================ */

.ti-sidebar {
  position: fixed;
  top: var(--ti-header-h);
  left: 0;
  bottom: 0;
  width: var(--ti-sidebar-w);
  display: flex;
  flex-direction: column;
  background: var(--ti-sidebar-bg);
  border-right: 1px solid var(--ti-border);
  transition: width var(--ti-base) var(--ti-ease), background-color var(--ti-slow) ease;
  z-index: 900;
}

:root[data-sidebar="collapsed"] .ti-sidebar {
  width: var(--ti-sidebar-w-collapsed);
}

/* --- Banner --- */
/* --- Collapse control --- */
.ti-sidebar-toggle {
  flex: none;
  display: flex;
  justify-content: flex-end;
  padding: 8px 12px 6px;
}

.ti-sidebar-toggle button {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ti-nav-icon);
  cursor: pointer;
  transition: background-color var(--ti-fast) ease;
}

.ti-sidebar-toggle button:hover {
  background: var(--ti-nav-hover-bg);
}

.ti-sidebar-toggle svg {
  transition: transform var(--ti-base) var(--ti-ease);
}

:root[data-sidebar="collapsed"] .ti-sidebar-toggle svg {
  transform: rotate(180deg);
}

/* --- Nav --- */
.ti-nav {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 2px 10px 14px;
  list-style: none;
  margin: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--ti-border) transparent;
}

.ti-nav::-webkit-scrollbar { width: 6px; }
.ti-nav::-webkit-scrollbar-thumb {
  background: var(--ti-border);
  border-radius: 6px;
}

.ti-nav li { margin: 0 0 2px; }

.ti-nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--ti-radius-sm);
  font-size: 14px;
  font-weight: 600;
  color: var(--ti-nav-color);
  white-space: nowrap;
  transition:
    background-color var(--ti-base) ease,
    color var(--ti-base) ease,
    transform var(--ti-base) var(--ti-ease),
    box-shadow var(--ti-base) ease;
}

.ti-nav a:hover,
.ti-nav a:focus-visible {
  background: var(--ti-nav-hover-bg);
  color: var(--ti-nav-hover-color);
  transform: translateX(5px);
  box-shadow: inset 3px 0 0 0 var(--ti-accent);
  filter: none;
}

.ti-nav li.is-active > a {
  background: var(--ti-nav-hover-bg);
  color: var(--ti-nav-hover-color);
  box-shadow: inset 3px 0 0 0 var(--ti-accent);
}

.ti-nav-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--ti-nav-icon);
  transition: transform var(--ti-base) var(--ti-ease);
}

.ti-nav a:hover .ti-nav-icon {
  transform: scale(1.15) rotate(-6deg);
}

.ti-nav li.is-active .ti-nav-icon {
  color: var(--ti-accent);
}

.ti-nav-label { flex: 1; }

.ti-nav-badge {
  flex: none;
  font-family: var(--ti-mono);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  padding: 3px 6px;
  border-radius: 20px;
  background: oklch(.55 .2 20);
  color: #fff;
  animation: ti-badge-pulse 2.6s ease-in-out infinite;
}

/* Collapsed: icons only, centred. Labels are removed from the a11y tree too —
   the link keeps its accessible name from `aria-label` set in the markup. */
:root[data-sidebar="collapsed"] .ti-nav a {
  justify-content: center;
  padding: 10px 0;
}

:root[data-sidebar="collapsed"] .ti-nav-label,
:root[data-sidebar="collapsed"] .ti-nav-badge {
  display: none;
}

:root[data-sidebar="collapsed"] .ti-nav a:hover {
  transform: none;
}

/* --- Legal footer --- */
.ti-sidebar-legal {
  flex: none;
  padding: 12px 16px;
  border-top: 1px solid var(--ti-border);
  font-size: 11px;
  line-height: 1.5;
  color: var(--ti-text-muted);
}

:root[data-sidebar="collapsed"] .ti-sidebar-legal {
  display: none;
}


/* 7 — Topbar ============================================================= */

.ti-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--ti-header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 24px;
  background: var(--ti-header-grad);
  border-bottom: 1px solid rgb(255 255 255 / .08);
  color: #fff;
  transition: background var(--ti-slow) ease;
  z-index: 1000;
}

/* Everything in the bar sits on a saturated gradient, so it is always white.
   Scoped under `.ti-body .ti-header` deliberately: the generic `.ti-body a`
   rule in §3 is (0,1,1) and would otherwise beat a plain `.ti-brand` (0,1,0)
   and paint these with --ti-accent -- which is a light violet in dark mode and
   therefore looked fine, but a dark violet in light mode, where it vanished
   into the blue. */
.ti-body .ti-header a,
.ti-body .ti-header a:hover,
.ti-body .ti-header a:focus {
  color: #fff;
  filter: none;
  text-decoration: none;
}

.ti-brand {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -.02em;
}

.ti-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
}

/* Given a hit area and a hover state, so they read as controls rather than
   loose words floating in the bar. */
.ti-header-link {
  padding: 7px 13px;
  border-radius: 9px;
  color: rgb(255 255 255 / .85);
  font-weight: 600;
  transition: color var(--ti-fast) ease, background-color var(--ti-fast) ease;
}

.ti-header-link:hover,
.ti-header-link:focus {
  background: rgb(255 255 255 / .14);
  color: #fff;
  filter: none;
}

.ti-header-user {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-right: 6px;
  padding: 7px 14px;
  border-radius: 20px;
  background: rgb(255 255 255 / .14);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
}

/* --- "Request Leave" call to action --- */
.ti-header-cta {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 15px;
  border-radius: 20px;
  background: rgb(255 255 255 / .14);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  transition: background-color var(--ti-fast) ease, transform var(--ti-fast) ease;
}

.ti-body .ti-header .ti-header-cta:hover,
.ti-body .ti-header .ti-header-cta:focus {
  background: rgb(255 255 255 / .26);
  transform: translateY(-1px);
}

/* --- Account menu --- */
.ti-usermenu { position: relative; }

.ti-usermenu-trigger {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 4px 12px 4px 4px;
  border: 0;
  border-radius: 24px;
  background: rgb(255 255 255 / .1);
  color: #fff;
  font-family: var(--ti-font);
  cursor: pointer;
  transition: background-color var(--ti-fast) ease;
}

.ti-usermenu-trigger:hover,
.ti-usermenu.is-open .ti-usermenu-trigger {
  background: rgb(255 255 255 / .2);
}

.ti-avatar {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 50%;
  background: linear-gradient(135deg, oklch(.6 .17 285), oklch(.48 .17 305));
  color: #fff;
  font-size: 12px;
  font-weight: 700;
}

.ti-usermenu-id {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  text-align: left;
}

.ti-usermenu-name {
  font-size: 12.5px;
  font-weight: 700;
  color: #fff;
}

.ti-usermenu-role {
  font-size: 10.5px;
  color: rgb(255 255 255 / .7);
}

/* Opened by toggling `.is-open` on the wrapper rather than :hover — a hover
   menu is unusable on touch and snaps shut when the pointer cuts a corner. */
.ti-usermenu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 186px;
  padding: 5px;
  border-radius: 13px;
  background: var(--ti-sidebar-bg);
  border: 1px solid var(--ti-border);
  box-shadow: var(--ti-shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity var(--ti-fast) ease, transform var(--ti-fast) var(--ti-ease), visibility var(--ti-fast);
  z-index: 1100;
}

.ti-usermenu.is-open .ti-usermenu-panel {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* The .ti-body .ti-header prefix is load-bearing. Every link in the topbar is
   forced to #fff so it reads against the gradient, and that rule -- (0,2,1) --
   outranks a plain `.ti-usermenu-panel a`. The panel is not on the gradient
   though: it sits on --ti-sidebar-bg, which is white in the light theme, so
   the items were white on white and the menu came up looking empty. */
.ti-body .ti-header .ti-usermenu-panel a,
.ti-usermenu-panel a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 9px;
  color: var(--ti-text);
  font-size: 12.5px;
  font-weight: 600;
  transition: background-color var(--ti-fast) ease;
}

.ti-body .ti-header .ti-usermenu-panel a:hover,
.ti-body .ti-header .ti-usermenu-panel a:focus,
.ti-usermenu-panel a:hover,
.ti-usermenu-panel a:focus {
  background: var(--ti-nav-hover-bg);
  color: var(--ti-text);
  filter: none;
}

.ti-usermenu-panel a svg { color: var(--ti-accent); }
.ti-usermenu-danger svg { color: var(--ti-red) !important; }

/* Theme switch */
.ti-theme-toggle {
  position: relative;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: rgb(255 255 255 / .14);
  color: #fff;
  cursor: pointer;
  overflow: hidden;
  transition: background-color var(--ti-fast) ease, transform var(--ti-base) ease;
}

.ti-theme-toggle:hover {
  background: rgb(255 255 255 / .26);
  transform: rotate(-8deg);
}

/* Only one of the two glyphs is shown, whichever the click will switch *to*. */
/* An inline <svg> sits on a text baseline, so the span around it is a line box
   taller than the glyph, with the descender space left under it -- which pushed
   the icon 1.5px above the middle of the button. Blocking the svg drops the
   baseline and the box becomes the glyph. */
.ti-theme-toggle .ti-icon { display: block; }

.ti-theme-toggle .ti-icon-sun  { display: none; }
:root[data-theme="dark"] .ti-theme-toggle .ti-icon-sun  { display: block; }
:root[data-theme="dark"] .ti-theme-toggle .ti-icon-moon { display: none; }

/* Water-drop feedback on the topbar when the theme flips. */
.ti-ripple {
  position: absolute;
  top: 14px;
  right: 96px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(255 255 255 / .55), transparent 70%);
  pointer-events: none;
  animation: ti-ripple .65s ease-out forwards;
}

/* Drawer backdrop. Only ever visible below the mobile breakpoint. */
.ti-scrim { display: none; }

/* Hamburger, mobile only */
.ti-nav-open {
  display: none;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: rgb(255 255 255 / .14);
  color: #fff;
  cursor: pointer;
}

.ti-header-left {
  display: flex;
  align-items: center;
  gap: 12px;
}


/* 8 — Components ========================================================= */

/* --- Card --- */
.ti-card {
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  border-radius: var(--ti-radius);
  transition:
    transform var(--ti-base) var(--ti-ease),
    border-color var(--ti-base) ease,
    box-shadow var(--ti-base) ease;
}

.ti-card--pad { padding: 16px 18px; }

.ti-card--hover:hover {
  transform: translateY(-3px);
  border-color: var(--ti-accent);
  box-shadow: var(--ti-shadow);
}

/* --- Chip: a small labelled value --- */
.ti-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: 20px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  font-size: 12.5px;
  color: var(--ti-text);
}

.ti-chip-label {
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  /* Soft, not muted: at this size the quiet token measured about 6:1 on the
     surface it sits on, which is AA for text twice as big and grey mush
     here. Soft takes it past 8. */
  color: var(--ti-text-soft);
}

/* A row of chips for the counts that do not warrant a card each. The value
   takes its colour from --chip-h, the same hue-per-figure arrangement the
   summary cards and the day cards use. */
.ti-chiprow {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

/* The fallback belongs inside var(), not as a declaration here -- a custom
   property set on the element itself shadows the one inherited from the chip,
   so every value would come out the same hue. */
.ti-chip-value {
  font-size: 14px;
  font-weight: 600;
  color: oklch(.48 .16 var(--chip-h, 275));
}

:root[data-theme="dark"] .ti-chip-value { color: oklch(.8 .13 var(--chip-h, 275)); }

/* --- Badge: status pill --- */
.ti-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 20px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.5;
}

.ti-badge--green { background: var(--ti-green-bg); color: var(--ti-green); }
.ti-badge--teal  { background: oklch(.62 .15 195 / .2); color: oklch(.75 .14 195); }
.ti-badge--blue  { background: var(--ti-blue-bg);  color: var(--ti-blue); }
.ti-badge--amber { background: var(--ti-amber-bg); color: var(--ti-amber); }
.ti-badge--red   { background: var(--ti-red-bg);   color: var(--ti-red); }

/* --- Buttons --- */
.ti-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: 12px;
  font-family: var(--ti-font);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--ti-fast) ease, box-shadow var(--ti-fast) ease,
              background-color var(--ti-fast) ease;
}

.ti-btn:hover { transform: translateY(-2px); }

.ti-btn--primary {
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258));
  color: #fff;
  box-shadow: 0 12px 24px -14px oklch(.5 .18 275 / .9);
}

.ti-btn--primary:hover,
.ti-btn--primary:focus { color: #fff; filter: none; }

.ti-btn--ghost {
  background: var(--ti-chip-bg);
  border-color: var(--ti-border);
  color: var(--ti-text);
}

.ti-btn--danger {
  background: var(--ti-red-bg);
  color: var(--ti-red);
}

/* --- Icon button --- */
.ti-icon-btn {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: var(--ti-chip-bg);
  color: var(--ti-text-soft);
  cursor: pointer;
  transition: transform var(--ti-fast) ease, background-color var(--ti-fast) ease;
}

.ti-icon-btn:hover { transform: translateY(-2px); }

/* --- Tabs --- */
.ti-tabs {
  display: inline-flex;
  gap: 6px;
  padding: 5px;
  border-radius: var(--ti-radius);
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
}

.ti-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 0;
  border-radius: var(--ti-radius-sm);
  background: transparent;
  color: var(--ti-text-soft);
  font-family: var(--ti-font);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--ti-base) ease, color var(--ti-base) ease,
              transform var(--ti-fast) ease;
}

.ti-tab:hover { transform: translateY(-1px); }

.ti-tab.is-active {
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258));
  color: #fff;
  box-shadow: 0 10px 22px -14px oklch(.5 .18 275 / .95);
}

/* Tabs that navigate rather than switch panels in place. `a` needs the link
   colour from §3 overridden, and the row sits above the page content. */
.ti-tabs--nav {
  margin-bottom: 22px;
  flex-wrap: wrap;
}

.ti-body .ti-tabs--nav .ti-tab { color: var(--ti-text-soft); }
.ti-body .ti-tabs--nav .ti-tab:hover { color: var(--ti-text); filter: none; }
.ti-body .ti-tabs--nav .ti-tab.is-active,
.ti-body .ti-tabs--nav .ti-tab.is-active:hover { color: #fff; filter: none; }

.ti-tab-count {
  font-family: var(--ti-mono);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  padding: 3px 7px;
  border-radius: 20px;
  background: oklch(.55 .2 20);
  color: #fff;
}

.ti-tab.is-active .ti-tab-count { background: rgb(255 255 255 / .26); }

/* --- Progress track --- */
.ti-track {
  height: 6px;
  border-radius: 20px;
  background: var(--ti-track-bg);
  overflow: hidden;
}

.ti-track-fill {
  height: 100%;
  border-radius: 20px;
  background: linear-gradient(90deg, oklch(.62 .16 195), oklch(.55 .17 215));
  transition: width .6s var(--ti-ease);
}

/* --- Form controls --- */
.ti-input,
.ti-select,
.ti-textarea {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--ti-border);
  border-radius: 11px;
  background: var(--ti-chip-bg);
  color: var(--ti-text);
  font-family: var(--ti-font);
  font-size: 13px;
  transition: border-color var(--ti-fast) ease, box-shadow var(--ti-fast) ease;
}

.ti-input:focus,
.ti-select:focus,
.ti-textarea:focus {
  outline: none;
  border-color: var(--ti-accent);
  box-shadow: 0 0 0 3px var(--ti-accent-soft);
}

/* The option list is drawn by the OS, not by us: it inherits the select's own
   background and colour, and a translucent background composites over white --
   which left dark-theme options as pale grey on near-white. Opaque both, and
   restate them on the options for the browsers that read them from there. */
.ti-select { background-color: var(--ti-chip-solid); }

.ti-select option,
.ti-select optgroup {
  background-color: var(--ti-chip-solid);
  color: var(--ti-text);
}

.ti-textarea { resize: vertical; min-height: 84px; }

.ti-label {
  display: block;
  margin-bottom: 6px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

/* --- Loading placeholder --- */
.ti-skeleton {
  border-radius: 6px;
  background: linear-gradient(
    90deg,
    var(--ti-track-bg) 25%,
    var(--ti-chip-bg) 50%,
    var(--ti-track-bg) 75%
  );
  background-size: 250% 100%;
  animation: ti-shimmer 1.3s ease-in-out infinite;
}

/* --- Bento metric tiles -------------------------------------------------
   The dashboard grid. Four columns of 132px rows; a tile claims more space
   with --wide (2 columns) or --hero (2x2). `grid-auto-flow: dense` lets the
   small tiles backfill the gaps a hero leaves behind.

   Colour is parameterised: each tile sets `--tile-h` (an OKLCH hue) inline and
   every gradient, icon wash and progress bar is derived from it here. That is
   the one thing a view is allowed to pass in — it keeps twelve near-identical
   gradient definitions out of the stylesheet without hard-coding colour in
   the markup. */

.ti-bento {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 132px;
  grid-auto-flow: dense;
  gap: var(--ti-gap);
}

/* Four equal tiles on one row — the dashboard's layout. Kept as a modifier so
   the dense bento above is still available for pages that mix tile sizes. */
.ti-bento--4 { grid-auto-flow: row; }

.ti-tile {
  --tile-h: 275; /* overridden per tile */
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 16px 18px;
  border-radius: var(--ti-radius-lg);
  border: 1px solid rgb(255 255 255 / .09);
  background: linear-gradient(
    135deg,
    oklch(.6 calc(.19 * var(--tile-c, 1)) var(--tile-h)),
    oklch(.44 calc(.17 * var(--tile-c, 1)) calc(var(--tile-h) + 16))
  );
  color: #fff;
  box-shadow: 0 12px 26px -16px rgb(0 0 0 / .6);
  animation: ti-rise-in .6s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease), box-shadow var(--ti-base) ease;
}

:root[data-theme="dark"] .ti-tile {
  background: linear-gradient(
    135deg,
    oklch(.44 calc(.15 * var(--tile-c, 1)) var(--tile-h)),
    oklch(.27 calc(.1 * var(--tile-c, 1)) calc(var(--tile-h) + 16))
  );
}

/* Travelling highlight. Each tile inherits its own animation-delay from the
   stagger below, so they do not all sweep in lockstep. */
.ti-tile::after {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / .18), transparent);
  animation: ti-sweep 3.4s ease-in-out infinite;
  animation-delay: inherit;
  pointer-events: none;
}

.ti-tile--wide { grid-column: span 2; }

.ti-tile--hero {
  grid-column: span 2;
  grid-row: span 2;
  padding: 22px 24px;
}

.ti-tile--link { cursor: pointer; }

/* Nobody is punched in. The tile keeps its gradient, its shape and its
   lightness and gives up only its colour -- `--tile-c` is a 0-1 multiplier on
   the chroma of every oklch() above, so 0 is the same tile drawn in grey.
   Hue alone could not do this: oklch(.6 .19 <any hue>) is always a colour.

   This is the old app's tell, carried over. It read the same max(check_status)
   parity and swapped the tile to a flat #7b7b7b when the day was not running.
   Only Today's tile ever wears it -- it is the only figure that can be live. */
.ti-tile--off { --tile-c: 0; }

.ti-tile--link:hover,
.ti-tile--link:focus-visible {
  transform: translateY(-5px) scale(1.015);
  box-shadow: 0 26px 44px -18px rgb(0 0 0 / .7);
}

.ti-tile-head {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.ti-tile-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 9px;
  background: rgb(255 255 255 / .18);
  color: #fff;
}

:root[data-theme="dark"] .ti-tile-icon {
  background: rgb(255 255 255 / .12);
  color: oklch(.78 calc(.15 * var(--tile-c, 1)) var(--tile-h));
}

.ti-tile-label {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  text-align: right;
  color: rgb(255 255 255 / .82);
}

:root[data-theme="dark"] .ti-tile-label { color: rgb(255 255 255 / .7); }

.ti-tile-body { position: relative; }

.ti-tile-value {
  font-family: var(--ti-mono);
  font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -.03em;
  line-height: 1.1;
}

.ti-tile--hero .ti-tile-value { font-size: 54px; }
.ti-tile--wide .ti-tile-value { font-size: 34px; }

.ti-tile-sub {
  font-family: var(--ti-mono);
  font-size: 12px;
  margin-top: 6px;
  color: rgb(255 255 255 / .78);
}

.ti-tile-note {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  font-size: 11.5px;
  color: rgb(255 255 255 / .8);
}

/* Pulsing dot shown on the Today tile while the clock is still running. */
.ti-live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: oklch(.86 .16 165);
  animation: ti-badge-pulse 1.6s ease-in-out infinite;
}

.ti-tile-bar {
  margin-top: 10px;
  height: 3px;
  border-radius: 3px;
  background: rgb(255 255 255 / .25);
  overflow: hidden;
}

:root[data-theme="dark"] .ti-tile-bar { background: rgb(255 255 255 / .16); }

.ti-tile-bar > span {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: #fff;
  transition: width .6s var(--ti-ease);
}

:root[data-theme="dark"] .ti-tile-bar > span {
  background: oklch(.78 calc(.15 * var(--tile-c, 1)) var(--tile-h));
}

/* Entrance stagger. Also feeds the sweep delay via `animation-delay: inherit`
   on ::after, so a tile's highlight starts when the tile itself lands. */
.ti-tile:nth-child(1) { animation-delay: 0ms; }
.ti-tile:nth-child(2) { animation-delay: 70ms; }
.ti-tile:nth-child(3) { animation-delay: 140ms; }
.ti-tile:nth-child(4) { animation-delay: 210ms; }
.ti-tile:nth-child(5) { animation-delay: 280ms; }
.ti-tile:nth-child(6) { animation-delay: 350ms; }
.ti-tile:nth-child(7) { animation-delay: 420ms; }
.ti-tile:nth-child(8) { animation-delay: 490ms; }
.ti-tile:nth-child(9) { animation-delay: 560ms; }

@media (max-width: 1280px) {
  .ti-bento { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 900px) {
  .ti-bento { grid-template-columns: repeat(2, 1fr); }
  .ti-tile--hero { grid-column: span 2; grid-row: span 1; }
  .ti-tile--hero .ti-tile-value { font-size: 40px; }
}

@media (max-width: 520px) {
  .ti-bento { grid-template-columns: 1fr; }
  .ti-tile--wide,
  .ti-tile--hero { grid-column: span 1; }
}

/* --- Banners ------------------------------------------------------------
   Full-width strips above the tiles: the birthday note and the rolling quote. */

.ti-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 20px;
  border-radius: var(--ti-radius);
  margin-bottom: var(--ti-gap);
  font-size: 13px;
  animation: ti-rise-in .45s var(--ti-ease) both;
}

.ti-banner-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 10px;
}

.ti-banner--birthday {
  background: oklch(.62 .16 40 / .12);
  border: 1px solid oklch(.62 .16 40 / .3);
  color: var(--ti-text);
}

.ti-banner--birthday .ti-banner-icon {
  background: oklch(.62 .16 40 / .18);
  color: oklch(.68 .16 42);
}

.ti-banner--quote {
  background: linear-gradient(120deg, oklch(.5 .15 258), oklch(.42 .14 285));
  color: #fff;
  box-shadow: 0 18px 36px -20px oklch(.45 .15 270 / .8);
}

.ti-banner--quote .ti-banner-icon { background: rgb(255 255 255 / .16); }

.ti-banner-close {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ti-text-muted);
  cursor: pointer;
  transition: color var(--ti-fast) ease, background-color var(--ti-fast) ease;
}

.ti-banner-close:hover {
  background: var(--ti-chip-bg);
  color: var(--ti-text);
}

/* Rolling quote. The text is duplicated in the markup and the track shifts by
   exactly half its width, so the loop has no visible gap where it wraps. */
/* The dashboard quote. It scrolled as a marquee once, which needed the text
   printed twice for a seamless loop -- and anything short enough to fit showed
   both copies side by side. It is one line of italic now, wrapping if the
   quote is long, and the banner grows to hold it. */
.ti-quote {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 13.5px;
  font-style: italic;
  line-height: 1.55;
}

/* Nothing set yet — a prompt rather than a quote, so it does not read as one
   somebody wrote. Admins only: the banner is not drawn empty for anyone else. */
.ti-banner--quote.is-empty .ti-quote {
  font-style: normal;
  opacity: .7;
}

/* --- Editing the quote in the banner (admin) ---------------------------- */

/* Everything here sits on the banner's saturated gradient, so it is drawn in
   white-on-white-alpha rather than from the surface tokens. */
.ti-quote-input {
  flex: 1;
  min-width: 0;
  padding: 8px 13px;
  border: 1px solid rgb(255 255 255 / .3);
  border-radius: 10px;
  background: rgb(255 255 255 / .12);
  color: #fff;
  font-family: var(--ti-font);
  font-size: 13.5px;
  transition: background-color var(--ti-fast) ease, border-color var(--ti-fast) ease;
}

.ti-quote-input::placeholder { color: rgb(255 255 255 / .58); }

.ti-quote-input:focus {
  outline: none;
  border-color: rgb(255 255 255 / .65);
  background: rgb(255 255 255 / .18);
}

.ti-quote-btn {
  flex: none;
  padding: 7px 15px;
  border: 1px solid rgb(255 255 255 / .3);
  border-radius: 10px;
  background: rgb(255 255 255 / .12);
  color: #fff;
  font-family: var(--ti-font);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, border-color var(--ti-fast) ease;
}

.ti-quote-btn:hover { background: rgb(255 255 255 / .24); }
.ti-quote-btn:disabled { opacity: .55; cursor: default; }

/* Save is the one that commits, so it is the one that is filled in. */
.ti-quote-btn--solid {
  background: #fff;
  border-color: #fff;
  color: oklch(.42 .14 285);
}

.ti-quote-btn--solid:hover { background: rgb(255 255 255 / .87); }

/* The pencil is an icon, not a word. */
.ti-quote-edit {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
}

/* One banner, two states. Reading is the default; the field and its two
   buttons only exist once .is-editing is on. Scoped to the banner because
   .is-editing is also what an open rule card on the Rules page wears. */
.ti-banner--quote .ti-quote-input,
.ti-banner--quote .ti-quote-save,
.ti-banner--quote .ti-quote-cancel { display: none; }

.ti-banner--quote.is-editing .ti-quote,
.ti-banner--quote.is-editing .ti-quote-edit { display: none; }

.ti-banner--quote.is-editing .ti-quote-input { display: block; }
.ti-banner--quote.is-editing .ti-quote-save,
.ti-banner--quote.is-editing .ti-quote-cancel { display: block; }

/* Too narrow for a field and two buttons on one line: the field takes the
   row and the buttons drop under it. */
@media (max-width: 640px) {
  .ti-banner--quote.is-editing { flex-wrap: wrap; }
  .ti-banner--quote.is-editing .ti-quote-input { flex: 1 0 100%; }
}

/* --- Sidebar stats ------------------------------------------------------ */

.ti-sidebar-stats {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px;
  border-top: 1px solid var(--ti-border);
}

.ti-sidebar-stat {
  --stat-h: 275;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  border-radius: var(--ti-radius-sm);
  cursor: pointer;
  transition: background-color var(--ti-fast) ease;
}

.ti-sidebar-stat:hover,
.ti-sidebar-stat:focus-visible { background: var(--ti-nav-hover-bg); }

.ti-sidebar-stat-icon {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: none;
  color: oklch(.75 .13 var(--stat-h));
}

:root[data-theme="light"] .ti-sidebar-stat-icon { color: oklch(.48 .16 var(--stat-h)); }

.ti-sidebar-stat-label {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  /* Soft, not muted: at this size the quiet token measured about 6:1 on the
     surface it sits on, which is AA for text twice as big and grey mush
     here. Soft takes it past 8. */
  color: var(--ti-text-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ti-sidebar-stat-value {
  flex: none;
  font-family: var(--ti-mono);
  font-size: 12px;
  font-weight: 700;
  color: oklch(.75 .13 var(--stat-h));
}

:root[data-theme="light"] .ti-sidebar-stat-value { color: oklch(.48 .16 var(--stat-h)); }

/* Collapsed: icon only, centred, with the value as the title tooltip. */
:root[data-sidebar="collapsed"] .ti-sidebar-stat { justify-content: center; }
:root[data-sidebar="collapsed"] .ti-sidebar-stat-label,
:root[data-sidebar="collapsed"] .ti-sidebar-stat-value { display: none; }

/* --- Filter control ----------------------------------------------------- */

.ti-filter {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 4px 4px 4px 13px;
  border-radius: 12px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  transition: border-color var(--ti-fast) ease;
}

.ti-filter:focus-within { border-color: var(--ti-accent); }

.ti-filter-icon {
  display: grid;
  color: var(--ti-accent);
}

.ti-filter input {
  border: 0;
  background: transparent;
  color: var(--ti-text);
  font-family: var(--ti-mono);
  font-size: 12.5px;
  outline: none;
}

/* Sized to their contents: "2026-08-04 - 2026-08-31" and "08 / 2026". The
   shared 120px they had before truncated the range mid-date. */
.ti-filter-range { width: 196px; }
.ti-filter-month { width: 74px; }
.ti-filter-date  { width: 96px; }

/* A filter with no button: the picker applying is the whole gesture, so the
   pill closes up to the same padding a plain chip has. */
.ti-filter--chip { padding: 9px 14px; }

/* A filter that takes words rather than a date: UI font, and room for a name.
   The search UI the browser adds to type="search" is dropped — it sits at the
   wrong baseline inside the pill, and there is an explicit clear button. */
.ti-filter-search {
  width: 178px;
  font-family: var(--ti-font);
  font-size: 13px;
}

.ti-filter-search::-webkit-search-cancel-button { display: none; }

.ti-filter-clear {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ti-text-muted);
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, color var(--ti-fast) ease;
}

.ti-filter-clear:hover { background: var(--ti-nav-hover-bg); color: var(--ti-text); }

/* --- Date pickers -------------------------------------------------------
   bootstrap-datepicker and daterangepicker both ship their own light styling
   and render into <body>, outside anything themed. Restyled here so they are
   not a white box over a dark page. */

.datepicker,
.daterangepicker {
  font-family: var(--ti-font) !important;
  background: var(--ti-sidebar-bg) !important;
  border: 1px solid var(--ti-border) !important;
  border-radius: var(--ti-radius) !important;
  box-shadow: var(--ti-shadow-lg) !important;
  color: var(--ti-text) !important;
  padding: 8px !important;
  z-index: 1200 !important;
}

/* Both draw a CSS-triangle callout in hardcoded white/grey. */
.datepicker-dropdown::before,
.datepicker-dropdown::after,
.daterangepicker::before,
.daterangepicker::after {
  display: none !important;
}

.datepicker table tr td,
.datepicker table tr th,
.daterangepicker td,
.daterangepicker th {
  color: var(--ti-text) !important;
  border-radius: 8px !important;
  font-size: 12.5px;
}

.datepicker table tr th,
.daterangepicker th {
  color: var(--ti-text-muted) !important;
  font-weight: 700;
}

.datepicker table tr td.day:hover,
.datepicker .month:hover,
.datepicker .year:hover,
.daterangepicker td.available:hover,
.datepicker .prev:hover,
.datepicker .next:hover,
.datepicker .datepicker-switch:hover {
  background: var(--ti-nav-hover-bg) !important;
  color: var(--ti-text) !important;
}

.datepicker table tr td.active,
.datepicker table tr td.active:hover,
.datepicker .month.focused,
.datepicker .month.active,
.datepicker .year.active,
.daterangepicker td.active,
.daterangepicker td.active:hover {
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258)) !important;
  color: #fff !important;
  text-shadow: none !important;
}

/* Today. The plugin paints this cell with a hardcoded pale-yellow gradient
   and leaves the text to inherit -- which here is a light colour, so the
   number disappeared into it.
   Outlined rather than filled, so today cannot be mistaken for the day that
   is actually selected, and drawn with an inset shadow rather than a border
   so the cell does not change size and shunt the grid. */
.datepicker table tr td.today,
.datepicker table tr td.today:hover,
.datepicker table tr td.today.disabled,
.datepicker table tr td.today.disabled:hover,
.datepicker .month.today,
.datepicker .year.today,
.daterangepicker td.today,
.daterangepicker td.today:hover {
  background: transparent !important;
  background-image: none !important;
  box-shadow: inset 0 0 0 1px var(--ti-accent) !important;
  color: var(--ti-accent) !important;
  font-weight: 700;
  text-shadow: none !important;
}

/* Today and selected at once: the selection wins, or the day you picked
   stops looking picked. */
.datepicker table tr td.today.active,
.datepicker table tr td.today.active:hover,
.daterangepicker td.today.active {
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258)) !important;
  box-shadow: none !important;
  color: #fff !important;
}

.daterangepicker td.in-range {
  background: var(--ti-accent-soft) !important;
  color: var(--ti-text) !important;
}

.datepicker table tr td.old,
.datepicker table tr td.new,
.daterangepicker td.off {
  color: var(--ti-text-muted) !important;
  opacity: .5;
}

.datepicker table tr td.disabled,
.daterangepicker td.disabled {
  color: var(--ti-text-muted) !important;
  opacity: .35;
}

.datepicker .month,
.datepicker .year {
  border-radius: 8px !important;
  line-height: 34px;
}

/* daterangepicker chrome */

/* The two months were stacking into one very tall column. Its own layout
   floats them side by side on width maths the global border-box reset changes,
   so the months are laid out here instead -- but on the months themselves, not
   on the container.

   The container's `display` has to be left alone: the plugin closes the panel
   with .hide(), which writes an inline `display: none`, and a `!important`
   display here would outrank it and pin the picker open forever. Buttons and
   ranges are already block, so they take their own row. */
.daterangepicker {
  width: auto !important;
  max-width: min(680px, calc(100vw - 32px)) !important;
}

.daterangepicker.show-calendar .drp-calendar {
  display: inline-block !important;
  float: none !important;
  vertical-align: top;
  max-width: 280px !important;
}

/* The !important above is there to beat the library's float layout, and it was
   also beating the inline display:none it puts on the second calendar --
   `singleDatePicker` hides .right with jQuery, which cannot outrank an
   !important rule. So every single-date picker in the app drew two months and
   two sets of time selects for one date. Put the hide back. */
.daterangepicker.single .drp-calendar.right { display: none !important; }

/* The hour and minute dropdowns are plain <select>s. Nothing had ever dressed
   them, so they came out in the browser's own grey inside an otherwise dark
   panel. */
.daterangepicker select {
  height: auto;
  padding: 4px 6px;
  border: 1px solid var(--ti-border) !important;
  border-radius: 8px;
  background: var(--ti-chip-solid) !important;
  color: var(--ti-text) !important;
  font-family: var(--ti-mono);
  font-size: 12px;
}

.daterangepicker .calendar-time { color: var(--ti-text-soft); }

.daterangepicker .calendar-table {
  background: transparent !important;
  border: 0 !important;
}

.daterangepicker .drp-buttons {
  border-top: 1px solid var(--ti-border) !important;
}

.daterangepicker .drp-selected {
  color: var(--ti-text-soft) !important;
  font-family: var(--ti-mono);
  font-size: 12px;
}

.daterangepicker .drp-buttons .btn {
  border-radius: 9px !important;
  font-size: 12px;
  font-weight: 700;
  padding: 7px 15px;
}

.daterangepicker .drp-buttons .applyBtn {
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258)) !important;
  border: 0 !important;
  color: #fff !important;
}

.daterangepicker .drp-buttons .cancelBtn {
  background: var(--ti-chip-bg) !important;
  border: 1px solid var(--ti-border) !important;
  color: var(--ti-text) !important;
}

.daterangepicker .calendar-table th.next span,
.daterangepicker .calendar-table th.prev span {
  border-color: var(--ti-text-muted) !important;
}

.ti-filter-go {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258));
  color: #fff;
  cursor: pointer;
  transition: transform var(--ti-fast) ease;
}

.ti-filter-go:hover { transform: translateY(-2px); }

/* --- Summary cards ------------------------------------------------------ */

.ti-summary {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 20px;
}

.ti-summary-card {
  --sum-h: 275;
  position: relative;
  overflow: hidden;
  padding: 14px 16px;
  border-radius: var(--ti-radius);
  border: 1px solid var(--ti-border);
  background: linear-gradient(135deg, oklch(.97 .025 var(--sum-h)), #fff);
  animation: ti-rise-in .5s var(--ti-ease) both;
}

:root[data-theme="dark"] .ti-summary-card {
  background: linear-gradient(135deg, oklch(.2 .045 var(--sum-h)), #12151d);
}

.ti-summary-label {
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  /* Soft, not muted: this names the card, and uppercase at 10.5px with a
     tenth of an em of tracking needs the extra contrast to stay a word
     rather than a texture. The note under the figure keeps muted, so the
     three lines still read title > figure > qualifier. */
  color: var(--ti-text-soft);
}

/* What stretch of the month the figure above covers. Deliberately quieter
   than .ti-summary-label: it qualifies the number, it does not name it. */
.ti-summary-note {
  margin-top: 4px;
  font-size: 11px;
  letter-spacing: .04em;
  /* No opacity on top of the token: --ti-text-muted is already the quiet
     one, and dimming it again puts an 11px line under 5:1 on the card --
     AA on paper and grey mush at that size. 6.1:1 as it stands. */
  color: var(--ti-text-muted);
}

.ti-summary-value {
  margin-top: 6px;
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -.03em;
  color: oklch(.5 .17 var(--sum-h));
}

:root[data-theme="dark"] .ti-summary-value { color: oklch(.78 .14 var(--sum-h)); }

/* Summary grid variants. The base is four columns; pages with fewer figures
   say so rather than leaving a gap. */
.ti-summary--3 { grid-template-columns: repeat(3, 1fr); }

/* --- Leave row ---------------------------------------------------------- */

.ti-leaverow {
  --leave-h: 285;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  border-radius: var(--ti-radius);
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .5s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease), border-color var(--ti-base) ease,
              box-shadow var(--ti-base) ease;
}

.ti-leaverow:hover {
  transform: translateY(-3px);
  border-color: oklch(.62 .15 var(--leave-h));
  box-shadow: var(--ti-shadow);
}

.ti-leaverow-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 58px;
  height: 58px;
  flex: none;
  border-radius: 14px;
  background: linear-gradient(135deg, oklch(.58 .18 var(--leave-h)), oklch(.48 .17 calc(var(--leave-h) + 18)));
  color: #fff;
}

.ti-leaverow-badge > span:first-child {
  font-family: var(--ti-mono);
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
}

.ti-leaverow-badge > span:last-child {
  margin-top: 3px;
  font-size: 9px;
  letter-spacing: .1em;
  text-transform: uppercase;
  opacity: .85;
}

.ti-leaverow-top {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.ti-leaverow-note {
  font-size: 15px;
  font-weight: 700;
}

.ti-leaverow-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--ti-text-muted);
}

.ti-leaverow-sep { opacity: .45; }

.ti-leaverow-status { flex: none; }

.ti-leaverow-actions {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: none;
}

.ti-icon-btn--danger {
  background: var(--ti-red-bg);
  color: var(--ti-red);
}

.ti-badge--muted {
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  color: var(--ti-text-muted);
}

/* Select styled to match .ti-filter, for toolbars that carry one. */
.ti-select--auto {
  width: auto;
  min-width: 180px;
  padding: 10px 14px;
  border-radius: 12px;
}

@media (max-width: 720px) {
  .ti-summary--3 { grid-template-columns: 1fr; }
  .ti-leaverow { flex-wrap: wrap; }
  .ti-leaverow-actions { margin-left: auto; }
}

/* --- Leave request form -------------------------------------------------
   The markup already carried a sensible set of lr-* classes; what it did not
   have was theme colours -- every value was a hardcoded light one, so the
   inputs and the request table came out white on a dark page. Same class
   names, driven by tokens, and living here rather than in 254 lines of
   <style> inside the view. */

.lr-form {
  border-radius: var(--ti-radius-lg);
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  overflow: hidden;
  animation: ti-rise-in .5s var(--ti-ease) both;
}

.lr-form-head {
  padding: 18px 22px;
  border-bottom: 1px solid var(--ti-border);
}

.lr-form-head h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -.02em;
}

.lr-form-head p {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--ti-text-muted);
}

.lr-section {
  padding: 20px 22px;
  border-bottom: 1px solid var(--ti-border);
}

.lr-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ti-accent);
}

.lr-section-head i,
.lr-section-head svg { font-size: 16px; }

/* The hint that sits beside a section title is prose, not part of the label,
   so it opts out of the uppercase treatment and sits at the far end. */
.lr-section-head .lr-help {
  margin: 0 0 0 auto;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ti-text-muted);
}

.lr-section-pair {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  padding: 0;
}

/* Half-day requests hide the exceptions column, so the row goes single-column
   rather than leaving 60% of it blank behind a divider. */
.lr-section-pair.is-solo { grid-template-columns: 1fr; }
.lr-section-pair.is-solo .lr-pair-left { border-right: 0; }

.lr-pair-left {
  padding: 20px 22px;
  border-right: 1px solid var(--ti-border);
}

.lr-pair-right { padding: 20px 22px; }

.lr-label {
  display: block;
  margin-bottom: 7px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ti-text);
}

.lr-req { color: oklch(.62 .2 25); }

.lr-help {
  display: block;
  margin-top: 6px;
  font-size: 11px;
  color: var(--ti-text-muted);
}

/* Bootstrap's .form-line draws its own underline under every field. */
.ti-body .lr-form .form-line { border: 0; }

.ti-body .lr-input {
  width: 100%;
  height: auto;
  padding: 11px 13px;
  border: 1px solid var(--ti-border);
  border-radius: 11px;
  background: var(--ti-chip-bg);
  color: var(--ti-text);
  font-family: var(--ti-mono);
  font-size: 13px;
  box-shadow: none;
  transition: border-color var(--ti-fast) ease, box-shadow var(--ti-fast) ease;
}

.ti-body .lr-input:focus {
  border-color: var(--ti-accent);
  box-shadow: 0 0 0 3px var(--ti-accent-soft);
  outline: none;
}

.ti-body .lr-textarea {
  font-family: var(--ti-font);
  resize: vertical;
  min-height: 84px;
}

/* --- Pills (radio driven) --- */

.lr-pills {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.lr-pills input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/* AdminBSB turns any label following a radio into a custom control: it hides
   the input and draws the circle with ::before/::after on the label, plus
   35px of left padding to clear it. These labels are pills, not radio rows,
   so that decoration has to go -- otherwise a stray circle sits on top of the
   pill, which is exactly what it was doing. */
.ti-body .lr-pills [type="radio"] + .lr-pill::before,
.ti-body .lr-pills [type="radio"] + .lr-pill::after {
  content: none;
  display: none;
}

.ti-body .lr-pills [type="radio"] + .lr-pill,
.lr-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 10px 18px;
  border-radius: 24px;
  border: 1px solid transparent;
  background: var(--ti-chip-bg);
  color: var(--ti-text-soft);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, color var(--ti-fast) ease,
              border-color var(--ti-fast) ease, transform var(--ti-fast) ease;
}

/* The glyph is sized here rather than left to AdminBSB's 24px default, so it
   stays in proportion whenever the pill is resized. */
.ti-body .lr-pill .material-icons { font-size: 17px; }

.lr-pill:hover { transform: translateY(-2px); }

/* The .ti-body-prefixed selector is not decoration -- without it this rule
   loses to the base pill above, which carries an extra attribute selector to
   outrank AdminBSB. `.lr-pills input:checked + .lr-pill` alone is (0,3,1)
   against its (0,4,0), so background and colour fell back to the unselected
   values and no pill ever looked picked. */
.ti-body .lr-pills [type="radio"]:checked + .lr-pill,
.lr-pills input:checked + .lr-pill {
  background: oklch(.55 .18 275 / .16);
  border-color: oklch(.6 .15 280);
  color: oklch(.42 .17 285);
}

:root[data-theme="dark"] .ti-body .lr-pills [type="radio"]:checked + .lr-pill,
:root[data-theme="dark"] .lr-pills input:checked + .lr-pill {
  color: oklch(.8 .13 280);
}

.ti-body .lr-pills [type="radio"]:focus-visible + .lr-pill,
.lr-pills input:focus-visible + .lr-pill {
  outline: 2px solid var(--ti-accent);
  outline-offset: 2px;
}

/* Morning / Afternoon sits on its own line under Full Day / Half Day. The
   design leaves this control out entirely, but the form posts
   half_day_session and the ledger reads it, so it stays -- same pill
   language, second row, shown only once Half Day is picked. */
.lr-pills-sub { margin-top: 10px; }

/* --- Half-day exceptions --- */

.lr-exc-list {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.lr-exc-list:not(:empty) { margin-bottom: 12px; }

/* One half-day exception: date, session pills, remove. Built in JS, so it
   carries the same pill classes as Day Type above rather than a second look. */
.he-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 10px 9px 12px;
  border-radius: 14px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .35s var(--ti-ease) both;
}

.ti-body .he-row .he-date {
  width: 150px;
  flex: none;
  padding: 9px 12px;
  border: 1px solid var(--ti-border);
  border-radius: 10px;
  background: var(--ti-card-bg);
  color: var(--ti-text);
  font-family: var(--ti-mono);
  font-size: 12.5px;
  box-shadow: none;
  transition: border-color var(--ti-fast) ease;
}

.ti-body .he-row .he-date:focus {
  border-color: var(--ti-accent);
  outline: none;
}

.he-row .he-session { gap: 6px; }

/* Smaller pills: these sit inside a row, not on their own line. */
.ti-body .lr-pills [type="radio"] + .lr-pill--sm,
.lr-pill--sm {
  padding: 7px 14px;
  font-size: 12.5px;
  background: var(--ti-card-bg);
}

.ti-body .lr-pill--sm .material-icons { font-size: 16px; }

.ti-body .he-row .he-remove {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  margin-left: auto;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: var(--ti-red-bg);
  color: var(--ti-red);
  cursor: pointer;
  transition: transform var(--ti-fast) ease;
}

.ti-body .he-row .he-remove:hover { transform: translateY(-2px); }

.lr-add-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 16px;
  border: 1px dashed var(--ti-accent);
  border-radius: 24px;
  background: transparent;
  color: var(--ti-accent);
  font-family: var(--ti-font);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, transform var(--ti-fast) ease;
}

/* Same line-height correction as the submit button -- this one sits in the
   column right beside it and was leaning the same way. */
.ti-body .lr-add-btn .material-icons {
  display: block;
  font-size: 17px;
  line-height: 1;
}

.lr-add-btn:hover {
  background: var(--ti-accent-soft);
  transform: translateY(-2px);
}

.lr-footer {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 16px 22px;
  border-bottom: 0;
}

/* Reads the leave balance the request would leave behind, so the decision is
   made with the number in view. */
.lr-footer-note {
  font-size: 12px;
  color: var(--ti-text-muted);
}

.lr-footer-note strong {
  color: var(--ti-text);
  font-weight: 700;
}

.ti-body .lr-submit {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 11px 22px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258));
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  box-shadow: 0 12px 26px -14px oklch(.5 .18 275 / .9);
  transition: transform var(--ti-fast) var(--ti-ease), box-shadow var(--ti-fast) ease,
              filter var(--ti-fast) ease;
}

.ti-body .lr-submit:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 32px -16px oklch(.5 .18 275 / .95);
}

.ti-body .lr-submit:active { transform: translateY(0); }

/* line-height 1 so the box is the glyph, nothing more. Left to Bootstrap's
   .btn line-height of 1.43 an 18px icon sits in a 26px box against the
   label's 19px one, and align-items centres two boxes of different heights --
   which reads as the icon riding low next to the text. Sizing it here also
   keeps it off AdminBSB's 24px default. */
.ti-body .lr-submit i {
  display: block;
  font-size: 17px;
  line-height: 1;
}

/* Mid-submit: dimmed, and it must stop inviting another click. */
.ti-body .lr-submit[disabled] {
  filter: saturate(.5) brightness(.9);
  transform: none;
  box-shadow: none;
  cursor: progress;
}

/* --- My requests toolbar --- */

.my-requests-toolbar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 18px 22px;
  border-bottom: 1px solid var(--ti-border);
}

/* .status-filter is the <ul>; the pills are its <li> children. */
.status-filter {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  list-style: none;
  margin: 0;
  padding: 0;
}

.status-filter li {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border-radius: 22px;
  border: 1px solid var(--ti-border);
  color: var(--ti-text-soft);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, color var(--ti-fast) ease,
              border-color var(--ti-fast) ease, transform var(--ti-fast) ease;
}

.status-filter li:hover { transform: translateY(-1px); }

.status-filter li.active {
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258));
  border-color: transparent;
  color: #fff;
}

.status-filter .cnt {
  font-family: var(--ti-mono);
  font-size: 10px;
  padding: 1px 7px;
  border-radius: 20px;
  background: var(--ti-chip-bg);
}

.status-filter li.active .cnt { background: rgb(255 255 255 / .26); }

/* --- Year dropdown --- */

.year-filter {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  font-size: 12px;
  color: var(--ti-text-muted);
}

.yf-dropdown { position: relative; }

.yf-trigger {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 14px;
  border: 1px solid var(--ti-border);
  border-radius: 10px;
  background: var(--ti-chip-bg);
  color: var(--ti-text);
  font-family: var(--ti-mono);
  font-size: 12.5px;
  cursor: pointer;
}

.yf-chevron { transition: transform var(--ti-fast) ease; }
.yf-dropdown.open .yf-chevron { transform: rotate(180deg); }

.yf-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 110px;
  padding: 5px;
  border-radius: 11px;
  background: var(--ti-sidebar-bg);
  border: 1px solid var(--ti-border);
  box-shadow: var(--ti-shadow-lg);
  display: none;
  z-index: 1100;
}

.yf-dropdown.open .yf-menu { display: block; }

.yf-menu > * {
  display: block;
  width: 100%;
  padding: 8px 11px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ti-text);
  font-family: var(--ti-mono);
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}

.yf-menu > *:hover { background: var(--ti-nav-hover-bg); }

/* --- Row actions and status labels --- */

.lr-action {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin: 0 3px;
  padding: 0;
  border: 0;
  border-radius: 9px;
  cursor: pointer;
  transition: transform var(--ti-fast) ease;
}

.lr-action:hover { transform: translateY(-2px); }

.lr-action-approve { background: var(--ti-green-bg); color: var(--ti-green); }
.lr-action-reject  { background: var(--ti-red-bg);   color: var(--ti-red); }
.lr-action-edit    { background: oklch(.6 .16 235 / .16); color: oklch(.68 .15 235); }
/* Delete is amber, not red: on this row it sits beside Reject, and two red
   buttons side by side is a good way to click the wrong one. */
.lr-action-delete  { background: var(--ti-amber-bg); color: var(--ti-amber); }

.ti-body .label {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 11px;
  font-weight: 700;
}

.ti-body .label-success { background: var(--ti-green-bg); color: var(--ti-green); }
.ti-body .label-warning { background: var(--ti-amber-bg); color: var(--ti-amber); }
.ti-body .label-danger  { background: var(--ti-red-bg);   color: var(--ti-red); }

@media (max-width: 860px) {
  .lr-section-pair { grid-template-columns: 1fr; }
  .lr-pair-left { border-right: 0; border-bottom: 1px solid var(--ti-border); }
  .year-filter { margin-left: 0; }
}

/* --- Themed table ------------------------------------------------------- */

/* Wide tables scroll inside their own box rather than pushing the page
   sideways. */
.ti-tablewrap {
  overflow-x: auto;
  padding: 0 4px 8px;
}

/* A long cell that must not set the column's width. The full text stays in
   the title attribute, so nothing is lost -- it just is not carried across
   the table. */
.ti-truncate {
  display: inline-block;
  max-width: 220px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* Buttons, not data: the column sizes to them and stops the row from
   reflowing as statuses change which buttons are present. */
.ti-body .ti-table td.ti-table-actions {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

.ti-body .ti-table {
  width: 100%;
  margin: 0;
  border-collapse: separate;
  border-spacing: 0 6px;
  background: transparent;
}

.ti-body .ti-table > thead > tr > th {
  padding: 10px 14px;
  border: 0;
  background: transparent;
  color: var(--ti-text-muted);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ti-body .ti-table > tbody > tr > td {
  padding: 13px 14px;
  border: 0;
  border-top: 1px solid var(--ti-border);
  border-bottom: 1px solid var(--ti-border);
  background: var(--ti-card-bg);
  color: var(--ti-text);
  font-size: 12.5px;
  vertical-align: middle;
}

.ti-body .ti-table > tbody > tr > td:first-child {
  border-left: 1px solid var(--ti-border);
  border-radius: 12px 0 0 12px;
}

.ti-body .ti-table > tbody > tr > td:last-child {
  border-right: 1px solid var(--ti-border);
  border-radius: 0 12px 12px 0;
}

/* Two libraries paint rows white and both have to be answered. Bootstrap's
   .table-striped hardcodes a near-white on odd rows; DataTables goes further
   and sets `table.dataTable tbody tr { background-color: #ffffff }` on every
   row, which is why one row stayed white even after the striping class was
   removed from the markup. */
.ti-body table.dataTable > tbody > tr,
.ti-body table.dataTable > tbody > tr.odd,
.ti-body table.dataTable > tbody > tr.even {
  background-color: transparent;
}

.ti-body .ti-table.table-striped > tbody > tr:nth-of-type(odd) > td,
.ti-body .ti-table > tbody > tr:hover > td {
  background: var(--ti-chip-bg);
}

/* The requests table is filtered by the status pills and the year dropdown
   above it, so DataTables' own page-length control is redundant -- and it was
   rendering as a bare "Show entries" label. The search box is kept and themed;
   the status filters use the same search API, so hiding it is not an option. */
#myRequestsTable_wrapper .dataTables_length { display: none; }

#myRequestsTable_wrapper .dataTables_filter {
  float: none;
  text-align: right;
  margin-bottom: 10px;
}

#myRequestsTable_wrapper .dataTables_filter label {
  font-size: 12px;
  color: var(--ti-text-muted);
  font-weight: 500;
}

/* --- Table as one card --------------------------------------------------
   .ti-table floats each row on its own. A long list of records reads better
   as one panel with ruled rows -- which is what the design draws for Manage
   Requests -- so this puts the rows back together inside a card. */

/* The requests panel carries its title on the same line as the filters, so
   the four pills and the year read as belonging to that list rather than to
   the page. */
.my-requests-toolbar--titled { gap: 14px; }

.ti-body .my-requests-title {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--ti-text);
}

.ti-tablecard {
  border-radius: var(--ti-radius);
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  overflow: hidden;
}

/* The 1px on the right is not spacing, it is slack.
   .ti-tablewrap scrolls horizontally, and a table at width:100% inside it
   distributes its columns in fractions: measured on Manage Requests they came
   to 1594.001px inside a 1594px box. A thousandth of a pixel, and the table's
   border box rounds up to 1595 -- one pixel wider than what holds it, which is
   all Chrome needs to draw a full scrollbar. It appears under the pager rather
   than under the columns, because DataTables puts the info line and the pager
   inside this box too, so the bar spans the whole card.

   Taking a pixel off the content box absorbs the rounding. Verified from 860
   to 1919: the phantom bar is gone at every one, and a table that genuinely
   does not fit still scrolls. */
.ti-tablecard .ti-tablewrap { padding: 0 1px 0 0; }

.ti-body .ti-table--card {
  border-collapse: collapse;
  border-spacing: 0;
}

.ti-body .ti-table--card > thead > tr > th {
  padding: 12px 20px;
  background: var(--ti-chip-bg);
  border-bottom: 1px solid var(--ti-border);
  font-size: 10px;
  letter-spacing: .1em;
}

.ti-body .ti-table--card > tbody > tr > td {
  padding: 13px 20px;
  border: 0;
  border-bottom: 1px solid var(--ti-border);
  border-radius: 0;
  background: transparent;
}

.ti-body .ti-table--card > tbody > tr:last-child > td { border-bottom: 0; }
.ti-body .ti-table--card > tbody > tr:hover > td { background: var(--ti-chip-bg); }

.ti-body .ti-table--card .ti-th-end,
.ti-body .ti-table--card td.ti-table-actions { text-align: right; }

/* The four status pills, outlined rather than filled: the design keeps the
   filled treatment for the tab strip above them, and two filled rows of
   pills stacked read as two navigations. */
.status-filter--outline li {
  background: transparent;
  border: 1px solid transparent;
  color: var(--ti-text-muted);
}

.status-filter--outline li.active {
  background: transparent;
  border-color: var(--ti-accent);
  color: var(--ti-accent);
  box-shadow: none;
}

.status-filter--outline li .cnt {
  background: var(--ti-chip-bg);
  color: inherit;
}

/* --- Late note row ------------------------------------------------------ */

.ti-laterow {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 14px 18px;
  border-radius: 15px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .5s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease), border-color var(--ti-base) ease,
              box-shadow var(--ti-base) ease;
}

.ti-laterow:hover {
  transform: translateY(-3px);
  border-color: oklch(.66 .18 40);
  box-shadow: var(--ti-shadow);
}

.ti-laterow-icon {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 11px;
  background: oklch(.62 .18 40 / .16);
  color: oklch(.7 .16 45);
}

/* Whose note it is. Only rendered when the picker is on All employees --
   on one person's own list it would be the same name forty times. */
.ti-laterow-emp {
  font-size: 13.5px;
  font-weight: 700;
  margin-bottom: 2px;
}

.ti-laterow-note {
  font-size: 14px;
  font-weight: 600;
}

.ti-laterow-date {
  margin-top: 3px;
  font-size: 11.5px;
  color: var(--ti-text-muted);
}

.ti-laterow-time {
  flex: none;
  text-align: right;
}

.ti-laterow-time > div:first-child {
  font-size: 15px;
  font-weight: 600;
  color: oklch(.8 .13 45);
}

:root[data-theme="light"] .ti-laterow-time > div:first-child { color: oklch(.55 .17 40); }

.ti-laterow-caption {
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

@media (max-width: 640px) {
  .ti-laterow { flex-wrap: wrap; }
  .ti-laterow-time { text-align: left; }
}

/* --- Legend ------------------------------------------------------------- */

.ti-legend {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-bottom: 12px;
  font-size: 11px;
  color: var(--ti-text-muted);
}

.ti-legend span { display: flex; align-items: center; gap: 6px; }

.ti-swatch {
  width: 18px;
  height: 7px;
  border-radius: 4px;
}

.ti-swatch--work  { background: linear-gradient(90deg, oklch(.62 .16 195), oklch(.55 .17 215)); }
.ti-swatch--break { background: var(--ti-track-bg); }

/* --- Day card ----------------------------------------------------------- */

/* The outline carries the day's status -- red for a short day, teal while the
   clock is running, blue for complete, green for extra. --day-h is the OKLCH
   hue, set per card by the view, the same arrangement as --tile-h. */
.ti-daycard {
  --day-h: 275;
  border-radius: var(--ti-radius);
  background: var(--ti-card-bg);
  border: 1px solid oklch(.62 .14 var(--day-h));
  animation: ti-rise-in .5s var(--ti-ease) both;
  transition: border-color var(--ti-base) ease, box-shadow var(--ti-base) ease,
              transform var(--ti-base) var(--ti-ease);
}

/* A day under the minimum is the one worth spotting from across the page, so
   it gets a wash of its colour rather than only an outline. */
.ti-daycard.is-incomplete {
  background: linear-gradient(120deg, oklch(.95 .045 25), var(--ti-card-bg) 65%);
  border-color: oklch(.6 .18 24);
}

:root[data-theme="dark"] .ti-daycard.is-incomplete {
  background: linear-gradient(120deg, oklch(.24 .07 22), var(--ti-card-bg) 65%);
}

.ti-daycard:hover {
  transform: translateY(-3px);
  box-shadow: var(--ti-shadow);
  filter: brightness(1.06);
}

.ti-daycard-head {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  cursor: pointer;
}

.ti-daycard-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  flex: none;
  border-radius: 14px;
  background: var(--ti-chip-bg);
  color: var(--ti-text-soft);
}

.ti-daycard-badge > span:first-child {
  font-family: var(--ti-mono);
  font-size: 19px;
  font-weight: 600;
  line-height: 1;
}

.ti-daycard-badge > span:last-child {
  margin-top: 3px;
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  /* No opacity: it took the inherited --ti-text-soft to 5.7:1 on a 9.5px
     uppercase line, and three letters of tracked-out day name have nothing
     to spare. The 19px figure above still leads on size alone. */
}

.ti-daycard.is-open .ti-daycard-badge {
  background: linear-gradient(135deg, oklch(.55 .18 285), oklch(.48 .18 258));
  color: #fff;
}

.ti-daycard-top {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 9px;
}

.ti-daycard-total {
  font-size: 15px;
  font-weight: 600;
}

.ti-daycard-meta {
  margin-left: auto;
  font-size: 11.5px;
  color: var(--ti-text-muted);
}

.ti-daycard-chev {
  display: grid;
  flex: none;
  color: var(--ti-text-muted);
  transition: transform var(--ti-base) ease;
}

.ti-daycard.is-open .ti-daycard-chev { transform: rotate(180deg); }

/* --- Timeline ----------------------------------------------------------- */

.ti-timeline {
  position: relative;
  height: 14px;
  border-radius: 8px;
  background: var(--ti-track-bg);
  overflow: hidden;
}

.ti-timeline-seg {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 6px;
  background: linear-gradient(90deg, oklch(.62 .16 195), oklch(.55 .17 215));
  transition: width .6s var(--ti-ease), left .6s var(--ti-ease);
}

.ti-timeline-seg.is-live {
  background: linear-gradient(90deg, oklch(.62 .16 195), oklch(.68 .17 175));
}

/* Pulsing head on the block that is still running. */
.ti-timeline-seg.is-live::after {
  content: '';
  position: absolute;
  top: 50%;
  right: -3px;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  border-radius: 50%;
  background: oklch(.82 .16 165);
  box-shadow: 0 0 0 3px oklch(.7 .16 175 / .35);
  animation: ti-badge-pulse 1.6s ease-in-out infinite;
}

/* --- Expanded punches --------------------------------------------------- */

/* grid-template-rows 0fr -> 1fr animates open without needing a fixed height
   or a JS measurement of the content. */
.ti-daycard-detail {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--ti-base) var(--ti-ease);
}

.ti-daycard.is-open .ti-daycard-detail { grid-template-rows: 1fr; }

.ti-punches {
  overflow: hidden;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 10px;
  margin: 0 18px;
  padding: 0;
}

.ti-daycard.is-open .ti-punches {
  padding: 14px 0 18px;
  border-top: 1px dashed var(--ti-border);
  margin-top: 0;
}

.ti-punch {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 11px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
}

.ti-punch-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  background: oklch(.62 .16 200);
}

.ti-punch.is-live .ti-punch-dot {
  background: oklch(.7 .17 175);
  animation: ti-badge-pulse 1.6s ease-in-out infinite;
}

.ti-punch-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: 12.5px;
}

.ti-punch-meta {
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  /* Soft, not muted: at this size the quiet token measured about 6:1 on the
     surface it sits on, which is AA for text twice as big and grey mush
     here. Soft takes it past 8. */
  color: var(--ti-text-soft);
}

@media (max-width: 1024px) {
  .ti-summary { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 640px) {
  .ti-summary { grid-template-columns: 1fr; }
  .ti-daycard-meta { margin-left: 0; width: 100%; }
}

/* --- Login -------------------------------------------------------------- */

/* `body.ti-login`, not `.ti-login`. body.ti-body up in the reset is (0,1,1)
   and sets display: flow-root, so a bare class never got its grid and the card
   sat at the top of the page. Matching the element too ties the specificity,
   and this rule comes later. */
body.ti-login {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 32px 20px;
  background: linear-gradient(160deg, #10131a, #0a0c11);
}

:root[data-theme="light"] .ti-login { background: #fff; }

.ti-login-toggle {
  position: fixed;
  top: 20px;
  right: 24px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  color: var(--ti-accent);
  z-index: 10;
}

.ti-login-toggle:hover { background: var(--ti-nav-hover-bg); }

.ti-login-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  animation: ti-fade-in .5s ease both;
}

/* Two-tone wordmark. background-clip: text needs a transparent fill, so the
   colour is carried entirely by the gradient. */
.ti-login-logo {
  display: flex;
  align-items: baseline;
  gap: 3px;
  font-size: 52px;
  font-weight: 800;
  letter-spacing: -.01em;
  line-height: 1.1;
}

.ti-login-logo:hover { filter: brightness(1.08); }

.ti-login-ti,
.ti-login-pl {
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.ti-login-ti { background-image: linear-gradient(120deg, #e8286e, #ff6a3d); }
.ti-login-pl { background-image: linear-gradient(120deg, oklch(.6 .18 255), oklch(.55 .19 285)); }

.ti-login-sub {
  margin-bottom: 18px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .32em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

.ti-login-card {
  width: 100%;
  max-width: 440px;
  padding: 36px 38px 32px;
  border-radius: var(--ti-radius-lg);
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  box-shadow: 0 26px 50px -24px rgb(0 0 0 / .5);
  display: flex;
  flex-direction: column;
  gap: 24px;
  animation: ti-rise-in .5s var(--ti-ease) both;
}

.ti-login-title {
  text-align: center;
  font-size: 17px;
  font-weight: 700;
  color: var(--ti-text);
}

.ti-login-fields {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ti-login-field {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
}

.ti-login-icon {
  display: grid;
  flex: none;
  color: var(--ti-accent);
}

.ti-login-field input {
  flex: 1;
  min-width: 0;
  padding: 13px 15px;
  border: 1px solid var(--ti-border);
  border-radius: 10px;
  background: var(--ti-chip-bg);
  color: var(--ti-text);
  font-family: var(--ti-font);
  font-size: 14.5px;
  outline: none;
  transition: border-color var(--ti-fast) ease, box-shadow var(--ti-fast) ease;
}

.ti-login-field input:focus {
  border-color: var(--ti-accent);
  box-shadow: 0 0 0 3px var(--ti-accent-soft);
}

.ti-login-error {
  font-size: 12px;
  text-align: center;
  color: oklch(.62 .2 25);
}

/* Sign in is the only thing left in this row now that Remember Me is gone. */
.ti-login-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}

.ti-login-btn {
  padding: 13px 30px;
  border: 0;
  border-radius: 8px;
  background: linear-gradient(120deg, #e8286e, #d81b60);
  color: #fff;
  font-family: var(--ti-font);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  cursor: pointer;
  box-shadow: 0 14px 26px -14px rgb(216 27 96 / .7);
  transition: transform var(--ti-fast) ease, box-shadow var(--ti-fast) ease;
}

.ti-login-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 30px -14px rgb(216 27 96 / .8);
}

/* This page has no AdminBSB behind it, so the loader needs its own rules.
   The inner .loader is centred with absolute positioning rather than grid:
   jQuery's .show() writes `display: block`, which would drop any grid or flex
   centring set on the wrapper here. */
.ti-login .page-loader-wrapper {
  position: fixed;
  inset: 0;
  display: none;
  background: var(--ti-bg);
  opacity: .88;
  z-index: 50;
}

.ti-login .page-loader-wrapper .loader {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--ti-text-muted);
  font-size: 13px;
}

@media (max-width: 420px) {
  .ti-login-card { padding: 24px 20px; }
  .ti-login-logo { font-size: 42px; }
  .ti-login-sub { letter-spacing: .22em; font-size: 10.5px; }
}

/* --- Employee row ------------------------------------------------------
   The Employees screen. The old page was an eleven-column table -- three
   figures and six buttons, all in 12px cells -- which is why this is a card
   per member instead: the person leads, the month's three figures sit in
   their own boxes, and the actions keep one hue each so the same colour
   always means the same thing (blue = look at, green = time log, teal =
   edit, violet = leave, red = remove). */

.ti-emprow {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 18px;
  border-radius: 15px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .45s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease),
              border-color var(--ti-fast) ease,
              box-shadow var(--ti-base) ease;
}

.ti-emprow:hover {
  transform: translateY(-2px);
  border-color: var(--ti-accent);
  box-shadow: var(--ti-shadow);
}

/* An ex-employee stays in the list but stops competing for attention. */
.ti-emprow--left { opacity: .62; }

.ti-emp-avatar {
  --emp-h: 275;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  flex: none;
  border-radius: 12px;
  background: linear-gradient(135deg,
    oklch(.6 .17 var(--emp-h)),
    oklch(.48 .17 calc(var(--emp-h) + 24)));
  color: #fff;
  font-size: 14px;
  font-weight: 700;
}

.ti-emp-id {
  flex: 1;
  min-width: 150px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.ti-emp-name {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 14px;
  font-weight: 700;
}

.ti-emp-user {
  font-family: var(--ti-mono);
  font-size: 11.5px;
  color: var(--ti-text-muted);
}

.ti-emp-stats {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* Late and Leave link through to their own screens, Early only has a tooltip
   — so this is styled to work as both an <a> and a <span>. */
.ti-emp-stat {
  --stat-h: 275;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 56px;
  padding: 6px 12px;
  border-radius: 10px;
  background: var(--ti-chip-bg);
  border: 1px solid transparent;
  text-align: center;
  transition: border-color var(--ti-fast) ease;
}

.ti-body a.ti-emp-stat:hover,
.ti-body a.ti-emp-stat:focus {
  filter: none;
  border-color: oklch(.6 .15 var(--stat-h));
}

.ti-emp-stat-value {
  font-family: var(--ti-mono);
  font-size: 14px;
  font-weight: 700;
  color: oklch(.5 .17 var(--stat-h));
}

:root[data-theme="dark"] .ti-emp-stat-value { color: oklch(.78 .14 var(--stat-h)); }

/* Nothing to report reads as grey rather than as a coloured zero. */
.ti-emp-stat--zero .ti-emp-stat-value,
:root[data-theme="dark"] .ti-emp-stat--zero .ti-emp-stat-value { color: var(--ti-text-muted); }

.ti-emp-stat-label {
  font-size: 9px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

.ti-emp-actions {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: none;
}

/* --- Action button (hue-coded icon button) --- */
.ti-act {
  --act-h: 275;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: oklch(.6 .16 var(--act-h) / .14);
  color: oklch(.42 .17 var(--act-h));
  cursor: pointer;
  transition: transform var(--ti-fast) ease, background-color var(--ti-fast) ease;
}

/* `.ti-body a` in §3 is (0,1,1) and would repaint an <a> action with the
   accent, so the anchor form is stated at (0,2,1). */
.ti-body a.ti-act,
.ti-body a.ti-act:hover,
.ti-body a.ti-act:focus {
  color: oklch(.42 .17 var(--act-h));
  filter: none;
}

:root[data-theme="dark"] .ti-act,
:root[data-theme="dark"] .ti-body a.ti-act,
:root[data-theme="dark"] .ti-body a.ti-act:hover,
:root[data-theme="dark"] .ti-body a.ti-act:focus {
  background: oklch(.3 .08 var(--act-h) / .5);
  color: oklch(.78 .14 var(--act-h));
}

.ti-act:hover { transform: translateY(-2px); }


/* --- Current hours card -------------------------------------------------
   One card per person who has punched today. --cur-h carries how far through
   the day they are, and colours the figure and the bar together, so a card
   that has met its target reads green without a second class. The whole card
   is the link through to that person's attendance. */

.ti-curgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 10px;
}

/* Changing the date fetches and swaps this block in place. Dimming it is the
   only "something is happening" the swap needs -- it is one request and the
   cards animate themselves back in. */
#curResults { transition: opacity var(--ti-base) ease; }
#curResults.is-busy { opacity: .45; pointer-events: none; }

/* `display: inline-flex` outranks the UA's [hidden] rule, so the sort control
   needs this to actually disappear when a day has nothing to sort. */
.ti-tabs[hidden] { display: none; }

.ti-curcard {
  --cur-h: 275;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px;
  border-radius: 15px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .45s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease),
              border-color var(--ti-fast) ease,
              box-shadow var(--ti-base) ease;
}

/* `.ti-body a` in §3 is (0,1,1) and would paint the whole card accent. */
.ti-body a.ti-curcard,
.ti-body a.ti-curcard:hover,
.ti-body a.ti-curcard:focus {
  color: var(--ti-text);
  filter: none;
}

.ti-curcard:hover {
  transform: translateY(-3px);
  border-color: var(--ti-accent);
  box-shadow: var(--ti-shadow);
}

.ti-curcard-body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  flex: 1;
  min-width: 0;
}

.ti-curcard-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

/* A long name gives way to the figure rather than wrapping the card taller. */
.ti-curcard-name {
  font-size: 13.5px;
  font-weight: 700;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ti-curcard-hours {
  flex: none;
  font-size: 14px;
  font-weight: 700;
  color: oklch(.46 .16 var(--cur-h));
}

:root[data-theme="dark"] .ti-curcard-hours { color: oklch(.8 .13 var(--cur-h)); }

/* The shared track is a block element elsewhere; inside an anchor it is spans. */
.ti-curcard .ti-track { display: block; height: 5px; }

.ti-curcard .ti-track-fill {
  display: block;
  background: linear-gradient(90deg,
    oklch(.6 .17 var(--cur-h)),
    oklch(.7 .15 calc(var(--cur-h) - 20)));
}


/* --- Monthly hours ------------------------------------------------------
   Eleven columns of figures. Everything here exists to make a wide table
   readable: the numbers carry their own meaning in colour (--fig-h per
   column, red/green for a signed total), a zero count goes grey rather than
   shouting in the column's hue, and the head stays put while the rows
   scroll. */

/* The line under a page title that qualifies the figures below it. */
.ti-page-sub {
  margin-top: 7px;
  font-size: 12.5px;
  color: var(--ti-text-muted);
}

.ti-page-sub-sep { opacity: .5; margin: 0 4px; }

/* Name cell: avatar, name, and the whole thing is the link for an admin. */
.ti-emp-avatar--sm {
  width: 28px;
  height: 28px;
  border-radius: 9px;
  font-size: 10.5px;
}

.ti-empcell {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.ti-body a.ti-empcell,
.ti-body a.ti-empcell:hover,
.ti-body a.ti-empcell:focus {
  color: var(--ti-text);
  filter: none;
}

.ti-empcell-name {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ti-body a.ti-empcell:hover .ti-empcell-name { color: var(--ti-accent); }

/* A figure in a table cell. --fig-h picks the hue; the modifiers override it
   for the cases where the value itself decides. */
.ti-fig {
  --fig-h: 275;
  font-size: 12.5px;
  font-weight: 600;
  color: oklch(.46 .16 var(--fig-h));
}

:root[data-theme="dark"] .ti-fig { color: oklch(.8 .13 var(--fig-h)); }

.ti-fig--pos { --fig-h: 150; }
.ti-fig--neg { --fig-h: 25; }

/* Nothing to report is not news, so it does not get a colour. */
.ti-fig--zero,
:root[data-theme="dark"] .ti-fig--zero { color: var(--ti-text-muted); font-weight: 500; }

/* Carries a tooltip or popover -- the underline is the only hint there is
   more behind the number. */
.ti-fig--info {
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

/* Remaining leave, as a pill so the row ends on the figure that matters. */
.ti-remain {
  display: inline-flex;
  align-items: center;
  padding: 4px 11px;
  border-radius: 20px;
  background: oklch(.6 .16 150 / .14);
  color: oklch(.44 .16 150);
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
}

.ti-remain--neg {
  background: oklch(.6 .2 25 / .14);
  color: oklch(.5 .19 25);
}

:root[data-theme="dark"] .ti-remain { color: oklch(.8 .13 150); }
:root[data-theme="dark"] .ti-remain--neg { color: oklch(.74 .16 25); }

.ti-remain-input {
  width: 74px;
  padding: 5px 9px;
  font-family: var(--ti-mono);
  font-size: 12.5px;
}

.ti-icon-btn--sm { width: 26px; height: 26px; border-radius: 7px; }

.remain-leave-cell {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Print sits back as a chip; Excel is the green of the file it produces,
   which is how the design separates them. Both are DataTables Buttons, so
   the library's own class has to be answered as well. */
.ti-body .ti-btn--chip,
.ti-body .dt-button.ti-btn--chip {
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  color: var(--ti-text);
}

.ti-body .ti-btn--chip:hover { border-color: var(--ti-accent); }
.ti-body .ti-btn--chip .ti-icon { color: var(--ti-accent); }

.ti-body .ti-btn--excel,
.ti-body .dt-button.ti-btn--excel {
  background: oklch(.6 .16 150 / .16);
  border: 1px solid transparent;
  color: oklch(.44 .16 150);
}

:root[data-theme="dark"] .ti-body .ti-btn--excel,
:root[data-theme="dark"] .ti-body .dt-button.ti-btn--excel { color: oklch(.8 .13 150); }

.ti-body .ti-btn--excel:hover { background: oklch(.6 .16 150 / .26); }

/* --- The table itself --- */

/* Rows are separated by 6px of nothing (see .ti-table), which a sticky head
   would show through, so the head gets the page background behind it. */
.ti-body .ti-table--month > thead > tr > th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--ti-bg);
  cursor: pointer;
  user-select: none;
}

.ti-body .ti-table--month > thead > tr > th:hover { color: var(--ti-accent); }

/* DataTables' own sort arrows are background images on ::after; replaced with
   a caret that follows the text colour in both themes. */
.ti-body .ti-table--month > thead > tr > th.sorting:after,
.ti-body .ti-table--month > thead > tr > th.sorting_asc:after,
.ti-body .ti-table--month > thead > tr > th.sorting_desc:after,
.ti-body .ti-table--month > thead > tr > th.sorting:before,
.ti-body .ti-table--month > thead > tr > th.sorting_asc:before,
.ti-body .ti-table--month > thead > tr > th.sorting_desc:before {
  content: none;
}

.ti-body .ti-table--month > thead > tr > th.sorting:after { content: "\2195"; opacity: .35; }
.ti-body .ti-table--month > thead > tr > th.sorting_asc:after { content: "\25B2"; }
.ti-body .ti-table--month > thead > tr > th.sorting_desc:after { content: "\25BC"; }

.ti-body .ti-table--month > thead > tr > th.sorting_asc,
.ti-body .ti-table--month > thead > tr > th.sorting_desc { color: var(--ti-accent); }

.ti-body .ti-table--month > thead > tr > th:after {
  margin-left: 6px;
  font-size: 8px;
  position: static;
  display: inline;
}

/* The last column is the answer; it sits against the right edge. */
.ti-body .ti-table--month .ti-th-end,
.ti-body .ti-table--month td.ti-td-end { text-align: right; }

/* Eleven columns do not fit a laptop without this. */
.ti-table--month { min-width: 1040px; }

/* --- DataTables chrome --- */

/* Print and Excel are moved out of the library's chrome and into the page
   header on init -- so these are scoped to the button container itself, not
   to the #myTable_wrapper it started inside. Scoping them to the wrapper is
   how they ended up with none of this applied. */
.ti-body .dt-buttons {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  float: none;
}

/* Sized to sit level with the month chip and the switch beside them, which
   is also what the design draws: 9/15 rather than the 10/18 a page-level
   button gets. */
.ti-body .dt-buttons .ti-btn {
  padding: 9px 15px;
  border-radius: 11px;
  font-size: 12.5px;
  line-height: 1;
}

/* DataTables wraps a button's text in its own <span>, so the icon and the
   label are a single child of .ti-btn and its gap never reaches between
   them. */
.ti-body .dt-buttons .ti-btn > span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

#myTable_wrapper .dataTables_length { display: none; }

#myTable_wrapper .dataTables_filter {
  float: none;
  text-align: right;
  margin: 0 4px 10px 0;
}

#myTable_wrapper .dataTables_filter label {
  font-size: 12px;
  font-weight: 500;
  color: var(--ti-text-muted);
}

/* The library's own .dataTables_processing is a full-width white gradient bar
   laid across the middle of the table -- reasonable on the paper-white theme
   it ships for, a bleached smear over whatever row it lands on here. Only its
   colours were being answered, and only on #myTable_wrapper, so Manage
   Requests still drew the library's version in full.

   Redrawn as a chip: it sits above the rows instead of washing them out, and
   it is not scoped to one table -- every DataTable in the app shows this, and
   scoping is how the last one was missed. */
.ti-body .dataTables_wrapper .dataTables_processing {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 5;
  width: auto;
  height: auto;
  margin: 0;
  padding: 9px 18px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--ti-border);
  border-radius: 999px;
  background: var(--ti-card-bg);
  background-image: none;
  box-shadow: var(--ti-shadow);
  color: var(--ti-text-soft);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
}

/* --- Switch -------------------------------------------------------------
   A checkbox that reads as on/off rather than ticked/unticked. The input is
   nested inside the label for the same reason .ti-check nests it: AdminBSB
   decorates the label *following* a checkbox, and a nested input is the one
   arrangement its rules cannot reach. */

.ti-switch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  padding: 8px 14px;
  border-radius: 12px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ti-text);
  cursor: pointer;
  user-select: none;
}

.ti-switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.ti-switch-track {
  display: flex;
  align-items: center;
  width: 36px;
  height: 19px;
  flex: none;
  padding: 2px;
  border-radius: 20px;
  background: var(--ti-track-bg);
  transition: background-color var(--ti-base) ease;
}

.ti-switch-knob {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgb(0 0 0 / .3);
  transition: transform var(--ti-base) var(--ti-ease);
}

.ti-switch input:checked + .ti-switch-track {
  background: linear-gradient(135deg, oklch(.58 .18 275), oklch(.48 .17 255));
}

.ti-switch input:checked + .ti-switch-track .ti-switch-knob { transform: translateX(17px); }

.ti-switch input:focus-visible + .ti-switch-track {
  outline: 2px solid var(--ti-accent);
  outline-offset: 2px;
}


/* --- Member record ------------------------------------------------------
   One employee's month, reached from the Employees list. A gradient band
   carries who it is and the four figures that frame the month, then the days
   are a table -- a finished month reads down a column, which is what the day
   cards on the Hours page are deliberately not for. */

.ti-back {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 16px;
  padding: 7px 13px;
  border-radius: 10px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  font-size: 12.5px;
  font-weight: 600;
}

.ti-back .ti-icon { transform: rotate(90deg); }

.ti-body a.ti-back { color: var(--ti-text-muted); }
.ti-body a.ti-back:hover { color: var(--ti-accent); border-color: var(--ti-accent); filter: none; }

.ti-hero {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  padding: 20px 24px;
  border-radius: 18px;
  margin-bottom: 18px;
  background: linear-gradient(120deg, oklch(.5 .18 285), oklch(.4 .16 255));
  color: #fff;
  box-shadow: 0 20px 40px -22px oklch(.45 .17 275 / .9);
  animation: ti-rise-in .45s var(--ti-ease) both;
}

/* The same light sweep the dashboard tiles carry. */
.ti-hero::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / .14), transparent);
  animation: ti-sweep 4s ease-in-out infinite;
}

.ti-hero > * { position: relative; }

.ti-hero-avatar {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  flex: none;
  border-radius: 16px;
  background: rgb(255 255 255 / .18);
  font-size: 17px;
  font-weight: 700;
}

.ti-hero-id { flex: 1; min-width: 160px; }

.ti-hero-eyebrow {
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  opacity: .82;
}

.ti-body .ti-hero-name {
  margin: 4px 0 0;
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -.02em;
  color: #fff;
}

.ti-hero-user { font-size: 12px; opacity: .85; margin-top: 4px; }

.ti-hero-stats { display: flex; gap: 8px; flex-wrap: wrap; }

.ti-hero-stat {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 82px;
  padding: 9px 14px;
  border-radius: 12px;
  background: rgb(255 255 255 / .14);
}

.ti-hero-stat-label {
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  opacity: .85;
}

.ti-hero-stat-value { font-size: 15px; font-weight: 700; }

.ti-hero-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 14px;
  border: 0;
  border-radius: 11px;
  background: rgb(255 255 255 / .16);
  color: #fff;
  font-family: var(--ti-font);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition: background-color var(--ti-fast) ease;
}

.ti-hero-btn:hover { background: rgb(255 255 255 / .26); }

/* Worked against required, across the foot of the band. Two of the four
   figures above are only meaningful next to each other, so they are also
   drawn as one line. */
.ti-hero-progress {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 2px;
}

.ti-hero-progress-track {
  position: relative;
  flex: 1;
  height: 6px;
  border-radius: 20px;
  background: rgb(255 255 255 / .18);
  overflow: hidden;
}

.ti-hero-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 20px;
  background: linear-gradient(90deg, rgb(255 255 255 / .75), #fff);
  /* Grows from nothing on load, so the band has one moment of movement. */
  animation: ti-hero-fill .9s var(--ti-ease) both;
}

@keyframes ti-hero-fill {
  from { transform: scaleX(0); }
  to { transform: none; }
}

.ti-hero-progress-fill { transform-origin: left; }

.ti-hero-progress-label {
  flex: none;
  font-size: 11.5px;
  opacity: .9;
}

.ti-hero-progress-pct {
  /* 14, matching the gap between the bar and this label, rather than the 8
     it had: a pill carries its own padding, so eight pixels of clear space
     leaves it looking stuck to the figure before it. */
  margin-left: 14px;
  padding: 2px 8px;
  border-radius: 20px;
  background: rgb(255 255 255 / .18);
  font-weight: 700;
}

.ti-detail-bar { margin-bottom: 18px; }
.ti-detail-bar .ti-tabs--nav { margin-bottom: 0; }

/* --- The month, row by row ---
   Fixed columns rather than a <table>: the five things on a row are known
   widths, and a table would let the widest late note decide where In/Out
   starts on every other row. */

/* In / Out is the column that grows, because it is the only one whose height
   and width depend on the day -- one pair or four. The late note is a badge or
   nothing, so it is capped rather than left to absorb the whole row. */
.ti-recgrid {
  --rec-cols: 104px minmax(96px, 168px) minmax(184px, 1fr) 96px 104px;
  min-width: 600px;
}

.ti-rechead {
  display: grid;
  grid-template-columns: var(--rec-cols);
  gap: 12px;
  padding: 0 18px 9px;
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

.ti-recrows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ti-recrow {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: var(--rec-cols);
  gap: 12px;
  align-items: center;
  padding: 13px 18px;
  border-radius: 14px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .45s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease),
              border-color var(--ti-fast) ease,
              box-shadow var(--ti-base) ease;
}

.ti-recrow:hover {
  transform: translateY(-2px);
  border-color: var(--ti-accent);
  box-shadow: var(--ti-shadow);
}

/* A coloured edge per row: red for a short day, amber when there is a late
   note, otherwise the accent. */
.ti-recrow::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  background: var(--rec-accent, var(--ti-accent));
}

.ti-rec-end { text-align: right; }

/* --- The punch editor, same idea, six columns --- */

.ti-log-bar { margin-bottom: 16px; }

.ti-loggrid {
  /* The time column carries a native `time` input, which needs room for the
     AM/PM segment on top of the digits -- 104px fitted the old plain text and
     clipped the control. */
  --log-cols: 40px 1fr 116px 122px 84px 88px;
  min-width: 678px;
}

.ti-loghead {
  display: grid;
  grid-template-columns: var(--log-cols);
  gap: 12px;
  padding: 0 18px 9px;
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

.ti-logrow {
  display: grid;
  grid-template-columns: var(--log-cols);
  gap: 12px;
  align-items: center;
  padding: 12px 18px;
  border-radius: 14px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  font-size: 12.5px;
  animation: ti-rise-in .45s var(--ti-ease) both;
  transition: transform var(--ti-fast) ease, border-color var(--ti-fast) ease;
}

.ti-logrow:hover { transform: translateY(-2px); }
/* A ticked row takes the same edge the ticked box does -- the design uses one
   value for both, so the row reads as an extension of the checkbox rather
   than as a second, brighter highlight. */
.ti-logrow:has(.logpick:checked) { border-color: oklch(.6 .16 275); }

/* Just the box.
   .ti-check is a chip -- padding, background, its own border -- which is
   right for a checkbox that carries a label ("Select all", "Admin"). In a
   grid cell with nothing to label, that chip is all anyone sees, and it
   swallows the 17px box inside it. This strips it back to the box. */
.ti-check--bare {
  gap: 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
}

.ti-check--bare:hover { transform: none; }
.ti-check--bare:has(input:checked) { border-color: transparent; }

/* Editable in place: a rule under the text rather than a boxed input, so a
   row of punches does not read as a form. */
.ti-logrow-note {
  width: 100%;
  padding: 6px 2px;
  border: 0;
  border-bottom: 1px solid var(--ti-border);
  background: transparent;
  color: var(--ti-text);
  font-family: var(--ti-font);
  font-size: 12.5px;
  transition: border-color var(--ti-fast) ease;
}

.ti-logrow-note:focus { outline: none; border-bottom-color: var(--ti-accent); }
.ti-logrow-note::placeholder { color: var(--ti-text-muted); }

/* The punch time is edited in place, the same underline the note uses. It is
   the native `time` control, which arrives with a clock button and a spinner
   of its own; both are taken off so a column of punches still reads as times
   rather than as a column of form fields. The rule only appears on hover and
   focus, for the same reason. */
.ti-logrow-time {
  width: 100%;
  padding: 6px 2px;
  border: 0;
  border-bottom: 1px solid transparent;
  background: transparent;
  font-family: var(--ti-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--ti-accent);
  transition: border-color var(--ti-fast) ease;
}

.ti-logrow-time:hover { border-bottom-color: var(--ti-border); }
.ti-logrow-time:focus { outline: none; border-bottom-color: var(--ti-accent); }
.ti-logrow-time::-webkit-calendar-picker-indicator { display: none; }
.ti-logrow-time::-webkit-inner-spin-button { display: none; }

.ti-logrow-cs { font-size: 12.5px; }

.ti-logrow-kind {
  margin-top: 1px;
  font-size: 9.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

.ti-logrow-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 7px;
}

.ti-logcount {
  margin: 14px 0 0;
  font-size: 11.5px;
  color: var(--ti-text-muted);
}

.ti-recrow--red { --rec-accent: oklch(.6 .19 25); }
.ti-recrow--amber { --rec-accent: oklch(.66 .17 45); }
.ti-recrow--accent { --rec-accent: var(--ti-accent); }

/* A badge or nothing. It used to carry a slim timeline of the day as well,
   which put a bar on most rows and read as a chart nobody could measure
   against -- the In / Out times beside it say the same thing exactly. */
.ti-recrow-note {
  display: flex;
  align-items: center;
  min-width: 0;
}

.ti-recrow-date { font-size: 13px; font-weight: 600; }

.ti-recrow-wd {
  margin-top: 2px;
  font-size: 10px;
  letter-spacing: .08em;
  color: var(--ti-text-muted);
}

/* In / Out is the cell that takes the row's slack, and left-aligned that left
   a hand's width of nothing between the times and Break. The block is centred
   in its cell; the pairs inside stay aligned to each other, so a column of
   times still reads down the row. */
.ti-recrow-pairs {
  display: grid;
  justify-content: center;
  min-width: 0;
}

.ti-rec-mid { text-align: center; }

.ti-recrow-pair {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
}

.ti-recrow-pair + .ti-recrow-pair { margin-top: 4px; }

.ti-recrow-arrow { display: grid; color: var(--ti-text-muted); }

.ti-recrow-break { font-size: 12px; color: var(--ti-text-muted); }


/* --- Profile ------------------------------------------------------------
   One column of your own details, one of the things only you can change. */

/* Centred the way the design does it: the page keeps its own margins -- the
   left one is the sidebar width, so it cannot be replaced with `auto` -- and
   centres a fixed column inside itself instead. */
.ti-page--narrow {
  display: flex;
  justify-content: center;
}

.ti-page-col {
  width: 100%;
  max-width: 760px;
}

.ti-hero--profile { --ann-h: 285; }

/* Locked, and shown as such: a disabled input still looks like somewhere to
   type. Username especially -- employ_details joins on it, so renaming one
   orphans that person's punch history. */
.ti-readonly {
  padding: 10px 13px;
  border-radius: 10px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  color: var(--ti-text-soft);
  font-size: 13.5px;
  font-weight: 600;
}

.ti-lockednote {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px;
  border-radius: 13px;
  background: var(--ti-chip-bg);
  border: 1px dashed var(--ti-border);
  font-size: 12px;
  line-height: 1.55;
  color: var(--ti-text-muted);
}

.ti-lockednote-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 9px;
  background: var(--ti-accent-soft);
  color: var(--ti-accent);
}

/* Pushed to the foot of its card so the two columns end level. */
.ti-profile-save { margin-top: auto; align-self: flex-start; }


/* --- Holidays -----------------------------------------------------------
   A calendar: what is next, then the year by month. --hol-h is the day type's
   hue, so a full day, a half day and an early leave are told apart at a
   glance down a month. */

.ti-holstats { margin-bottom: 0; }

/* The next-holiday banner reuses the member-record hero and swaps the flat
   avatar for a date. */
.ti-hero--next { --ann-h: 150; }

.ti-hero-datebadge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 70px;
  height: 70px;
  flex: none;
  border-radius: 18px;
  background: rgb(255 255 255 / .18);
}

.ti-hero-datebadge-day { font-size: 24px; font-weight: 600; line-height: 1; }

.ti-hero-datebadge-mon {
  margin-top: 3px;
  font-size: 9.5px;
  letter-spacing: .12em;
}

.ti-holmonths {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.ti-holmonth-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 11px;
}

.ti-body .ti-holmonth-title {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ti-text);
}

.ti-holmonth-count { font-size: 11px; color: var(--ti-text-muted); }
.ti-holmonth-rule { flex: 1; height: 1px; background: var(--ti-border); }

.ti-holgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 11px;
}

.ti-holcard {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 15px 17px 15px 20px;
  border-radius: 15px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .5s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease),
              border-color var(--ti-fast) ease,
              box-shadow var(--ti-base) ease,
              opacity var(--ti-fast) ease;
}

.ti-holcard:hover {
  transform: translateY(-3px);
  border-color: oklch(.6 .13 var(--hol-h, 150));
  box-shadow: var(--ti-shadow);
  opacity: 1;
}

/* A holiday that has been is still worth listing and no longer worth looking
   at. */
.ti-holcard.is-past { opacity: .62; }

.ti-holcard::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  background: linear-gradient(180deg,
    oklch(.65 .16 var(--hol-h, 150)),
    oklch(.5 .16 calc(var(--hol-h, 150) + 20)));
}

.ti-holcard-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 58px;
  min-height: 50px;
  flex: none;
  padding: 5px 6px;
  border-radius: 13px;
  background: linear-gradient(135deg,
    oklch(.58 .17 var(--hol-h, 150)),
    oklch(.46 .16 calc(var(--hol-h, 150) + 20)));
  color: #fff;
}

.ti-holcard-day { font-size: 15px; font-weight: 600; line-height: 1; white-space: nowrap; }

.ti-holcard-wd {
  margin-top: 3px;
  font-size: 7.5px;
  letter-spacing: .06em;
  opacity: .85;
  white-space: nowrap;
}

.ti-holcard-body { flex: 1; min-width: 0; }

.ti-body .ti-holcard-title {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--ti-text);
}

.ti-holcard-range {
  margin-top: 4px;
  font-size: 11px;
  color: var(--ti-text-muted);
}

.ti-holcard-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

.ti-holcard-type {
  padding: 3px 9px;
  border-radius: 20px;
  background: oklch(.6 .16 var(--hol-h, 150) / .2);
  color: oklch(.44 .16 var(--hol-h, 150));
  font-size: 10.5px;
  font-weight: 700;
}

:root[data-theme="dark"] .ti-holcard-type { color: oklch(.8 .13 var(--hol-h, 150)); }

.ti-holcard-spent { font-size: 11px; color: var(--ti-text-muted); }

.ti-holcard-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

/* A select that is the whole filter pill, with the pill drawing the chrome. */
.ti-select--bare {
  width: auto;
  min-width: 72px;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--ti-mono);
  font-size: 12.5px;
}

.ti-select--bare:focus { box-shadow: none; }


/* --- Announcements ------------------------------------------------------
   A feed. --ann-h is the tag's hue and colours the edge, the date badge and
   the pill together, so a holiday post and a policy change are told apart
   before either is read. */

.ti-annfilter { margin-bottom: 20px; }

.ti-anncard {
  position: relative;
  overflow: hidden;
  display: flex;
  gap: 16px;
  padding: 18px 20px 18px 24px;
  border-radius: var(--ti-radius);
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .5s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease),
              border-color var(--ti-fast) ease,
              box-shadow var(--ti-base) ease;
}

.ti-anncard[hidden] { display: none; }

.ti-anncard:hover {
  transform: translateY(-3px);
  border-color: oklch(.6 .14 var(--ann-h, 235));
  box-shadow: var(--ti-shadow);
}

.ti-anncard::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  background: linear-gradient(180deg,
    oklch(.65 .16 var(--ann-h, 235)),
    oklch(.5 .16 calc(var(--ann-h, 235) + 20)));
}

.ti-anncard-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 62px;
  height: 62px;
  flex: none;
  border-radius: 14px;
  background: linear-gradient(135deg,
    oklch(.58 .17 var(--ann-h, 235)),
    oklch(.46 .16 calc(var(--ann-h, 235) + 20)));
  color: #fff;
}

.ti-anncard-day { font-size: 19px; font-weight: 600; line-height: 1; }

.ti-anncard-mon {
  margin-top: 3px;
  font-size: 9px;
  letter-spacing: .1em;
  opacity: .85;
}

.ti-anncard-body { flex: 1; min-width: 0; }

.ti-anncard-top {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.ti-anncard-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 20px;
  background: oklch(.6 .16 var(--ann-h, 235) / .18);
  color: oklch(.44 .16 var(--ann-h, 235));
  font-size: 11px;
  font-weight: 700;
}

:root[data-theme="dark"] .ti-anncard-tag { color: oklch(.8 .13 var(--ann-h, 235)); }

.ti-body .ti-anncard-title {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--ti-text);
}

.ti-anncard-new {
  padding: 3px 8px;
  border-radius: 20px;
  background: oklch(.6 .2 20);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .1em;
  animation: ti-badge-pulse 2.4s ease-in-out infinite;
}

.ti-anncard-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

.ti-anncard-text {
  margin: 0;
  font-size: 15px;
  line-height: 1.65;
  color: var(--ti-text-soft);
  text-wrap: pretty;
}

/* --- Rules --------------------------------------------------------------
   A rule per card, numbered inside its section. The numbering is rendered,
   not counted from what is visible: a rule is "number 7 in its section"
   whether or not a search is narrowing the page. */

.ti-hero-sub { font-size: 12.5px; opacity: .85; margin-top: 5px; max-width: 520px; line-height: 1.5; }

.ti-rules-bar { margin-bottom: 20px; }

/* The section counts are not a badge demanding attention, so they lose the
   red the tab strip's own count carries. */
/* The neutral count, on the Announcements and Rules filter strips.
   It was --ti-chip-bg, which is what .ti-tabs paints the strip with -- so on
   every tab but the active one the pill was exactly the colour behind it and
   the number read as loose text. --ti-count-bg is a step away from the strip
   in both themes. */
.ti-tab-count--soft {
  background: var(--ti-count-bg);
  color: var(--ti-text-soft);
}

.ti-tab.is-active .ti-tab-count--soft {
  background: rgb(255 255 255 / .26);
  color: #fff;
}

/* The add panel. An outline in the accent rather than a shadow: it is a form
   that appeared in the flow, not something floating over it. */
.ti-ruleform {
  padding: 18px;
  margin-bottom: 20px;
  border-color: var(--ti-accent);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ti-ruleform[hidden] { display: none; }

.ti-ruleform-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.ti-field--section { width: 260px; }

/* A rule and the button that drops it. */
.ti-draftrow {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.ti-draftrow .ti-textarea { min-height: 62px; }
.ti-draftrow .draft-remove { margin-top: 4px; flex: none; }
.ti-draftrow .draft-remove[hidden] { display: none; }

.ti-ruleform-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* "Add another rule": an outline, because it adds a field rather than
   committing anything. */
.ti-btn--dashed {
  background: transparent;
  border: 1px dashed var(--ti-accent);
  color: var(--ti-accent);
}

.ti-btn--dashed:hover { background: var(--ti-accent-soft); }

.ti-rulegroups {
  display: flex;
  flex-direction: column;
  gap: 26px;
}

.ti-rulegroup[hidden] { display: none; }

.ti-rulegroup-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.ti-rulegroup-icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 10px;
  background: linear-gradient(180deg,
    oklch(.62 .18 var(--rule-h, 275)),
    oklch(.52 .17 calc(var(--rule-h, 275) + 20)));
  color: #fff;
}

.ti-body .ti-rulegroup-title {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: oklch(.48 .16 var(--rule-h, 275));
}

:root[data-theme="dark"] .ti-body .ti-rulegroup-title { color: oklch(.75 .13 var(--rule-h, 275)); }

.ti-rulegroup-count { font-size: 11px; color: var(--ti-text-muted); }

/* Fills the rest of the line, so a section reads as a divider. */
.ti-rulegroup-rule {
  flex: 1;
  height: 1px;
  background: var(--ti-border);
}

.ti-rulecard {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 15px 18px 15px 22px;
  border-radius: 14px;
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  animation: ti-rise-in .45s var(--ti-ease) both;
  transition: transform var(--ti-base) var(--ti-ease),
              border-color var(--ti-fast) ease,
              box-shadow var(--ti-base) ease;
}

.ti-rulecard[hidden] { display: none; }

.ti-rulecard:hover {
  transform: translateY(-3px);
  border-color: var(--ti-accent);
  box-shadow: var(--ti-shadow);
}

/* Being edited: no lift, and the accent edge says which one is open. */
.ti-rulecard.is-editing {
  transform: none;
  border-color: var(--ti-accent);
  flex-wrap: wrap;
}

.ti-rulecard::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  background: linear-gradient(180deg,
    oklch(.62 .18 var(--rule-h, 275)),
    oklch(.52 .17 calc(var(--rule-h, 275) + 20)));
}

.ti-rulecard-num {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 10px;
  background: linear-gradient(135deg,
    oklch(.6 .17 var(--rule-h, 275)),
    oklch(.5 .17 calc(var(--rule-h, 275) + 20)));
  color: #fff;
  font-size: 12.5px;
  font-weight: 700;
}

.ti-rulecard-body { flex: 1; min-width: 0; }

.ti-rulecard-text {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--ti-text-soft);
  text-wrap: pretty;
}

.ti-rulecard-subs {
  margin: 8px 0 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ti-rulecard-subs li {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ti-text-muted);
}

.ti-rulecard-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* One glyph, turned, rather than a second icon for the other direction. */
.ti-rulecard-actions .rule-move .ti-icon { transform: rotate(180deg); }
.ti-rulecard-actions .rule-move--down .ti-icon { transform: none; }

.ti-rulecard-edit {
  flex: 1 0 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px dashed var(--ti-border);
}

.ti-rulecard-edit[hidden] { display: none; }

/* Editing is the whole card's business; the read view steps aside. */
.ti-rulecard.is-editing .ti-rulecard-body,
.ti-rulecard.is-editing .ti-rulecard-actions { display: none; }


/* --- Settings -----------------------------------------------------------
   The old page was one long form with <hr>s between the sections. A card per
   setting instead, so what a Save button applies to is the box it sits in. */

.ti-setgrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 12px;
}

/* Every settings card: 18px all round, a 15px gap down the middle, and the
   card itself is the flex column so a section's parts space themselves. */
.ti-setcard {
  display: flex;
  flex-direction: column;
  gap: 15px;
  padding: 18px;
  margin-bottom: 12px;
}

.ti-setgrid > .ti-setcard { margin-bottom: 0; }

.ti-setcard-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.ti-body .ti-setcard-title {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ti-accent);
}

/* Radios, both sizes and the button share one baseline. */
.ti-setrow {
  display: flex;
  align-items: flex-end;
  gap: 22px;
  flex-wrap: wrap;
  margin: 0;
}

/* The fields beside it carry a label above them; this lines up with the
   inputs, not with the labels. */
.ti-setrow-btn { margin-bottom: 1px; }

.ti-field--sm { width: 92px; }

/* The manual-time field takes "2026-07-31 00:00" and nothing narrower. */
.ti-input--when { width: 180px; padding: 10px 13px; }

/* Rounded rectangles here, not the 24px pill the leave form uses. */
.ti-radio--box {
  border-radius: 11px;
  padding: 10px 15px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
}

.ti-radio--box:has(input:checked) { border-color: var(--ti-accent); }

.ti-radiorow { gap: 9px; }

/* --- Image picker --- */

/* The file input itself is visually hidden inside this label, so the whole
   box is the control. */
.ti-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 122px;
  margin: 0;
  padding: 14px;
  border: 1.5px dashed var(--ti-border);
  border-radius: 13px;
  background: var(--ti-chip-bg);
  color: var(--ti-text-muted);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--ti-fast) ease, color var(--ti-fast) ease;
}

.ti-drop:hover,
.ti-drop.is-over,
.ti-drop.is-picked {
  border-color: var(--ti-accent);
  color: var(--ti-accent);
}

.ti-drop-icon { display: grid; }

.ti-drop-name {
  font-size: 12.5px;
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ti-drop-hint { font-size: 11.5px; }

/* --- Staff grid --- */

.ti-staffgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: 7px;
}

.ti-staff {
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--ti-chip-bg);
  border: 1px solid transparent;
  font-size: 12.5px;
  transition: border-color var(--ti-fast) ease, transform var(--ti-fast) ease;
}

.ti-staff:hover { transform: translateY(-1px); }
.ti-staff:has(input:checked) { border-color: var(--ti-accent); }

/* --- Inline notice --- */

.ti-note {
  padding: 11px 15px;
  border-radius: 11px;
  margin-bottom: 14px;
  font-size: 12.5px;
  font-weight: 600;
}

.ti-note--ok {
  background: oklch(.6 .16 150 / .16);
  color: oklch(.44 .16 150);
}

.ti-note--bad {
  background: oklch(.6 .2 25 / .16);
  color: oklch(.5 .19 25);
}

:root[data-theme="dark"] .ti-note--ok { color: oklch(.8 .13 150); }
:root[data-theme="dark"] .ti-note--bad { color: oklch(.76 .16 25); }

/* The floating confirmation seven screens share ("Quote saved.", "Time added
   to 3 employees."). It used to live in custom.css pinned at top:10px with
   !important -- which is inside the 64px topbar, so every message landed on
   the account menu. It clears the header now, and sits above it: a toast that
   went under the bar it overlapped would be no better.

   The tint .ti-note carries is right for a note sitting on a card and much too
   thin for something floating over the page, so the box under it is a real
   surface. */
.message {
  position: fixed;
  top: calc(var(--ti-header-h) + 12px);
  right: 16px;
  z-index: 1100;
  max-width: min(360px, calc(100vw - 32px));
}

.message .ti-note {
  margin: 0;
  background-color: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  border-radius: var(--ti-radius);
  box-shadow: var(--ti-shadow);
}

/* Painted over the surface, not instead of it, so the status tint still reads
   as a tint and the text keeps its colour. */
.message .ti-note--ok  { background-image: linear-gradient(oklch(.6 .16 150 / .16), oklch(.6 .16 150 / .16)); }
.message .ti-note--bad { background-image: linear-gradient(oklch(.6 .2 25 / .16), oklch(.6 .2 25 / .16)); }

@media (max-width: 600px) {
  .message { left: 16px; right: 16px; max-width: none; }
}

@media (max-width: 860px) {
  .ti-setgrid { grid-template-columns: 1fr; }
}


/* --- jquery.datetimepicker ----------------------------------------------
   The third picker in the app (after bootstrap-datepicker and
   daterangepicker), and the only one that was never themed -- it rendered as
   a white calendar with a blue selection over a dark page. Settings' start
   time is the one field still using it. */

.xdsoft_datetimepicker {
  background: var(--ti-sidebar-bg) !important;
  border: 1px solid var(--ti-border) !important;
  border-radius: var(--ti-radius) !important;
  box-shadow: var(--ti-shadow-lg) !important;
  padding: 8px !important;
  font-family: var(--ti-font) !important;
}

.xdsoft_datetimepicker .xdsoft_label,
.xdsoft_datetimepicker .xdsoft_label i,
.xdsoft_datetimepicker .xdsoft_calendar th,
.xdsoft_datetimepicker .xdsoft_calendar td > div {
  background: transparent !important;
  color: var(--ti-text) !important;
}

.xdsoft_datetimepicker .xdsoft_calendar th { color: var(--ti-text-muted) !important; }

.xdsoft_datetimepicker .xdsoft_calendar td,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box > div > div {
  background: transparent !important;
  border-color: var(--ti-border) !important;
  color: var(--ti-text) !important;
}

.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month > div,
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled > div {
  color: var(--ti-text-muted) !important;
  opacity: .55;
}

.xdsoft_datetimepicker .xdsoft_calendar td:hover > div,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box > div > div:hover {
  background: var(--ti-nav-hover-bg) !important;
  color: var(--ti-text) !important;
}

/* Selected and "today" both existed as hardcoded blue and orange. */
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current > div,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box > div > div.xdsoft_current {
  background: linear-gradient(135deg, oklch(.58 .18 275), oklch(.48 .17 255)) !important;
  color: #fff !important;
  box-shadow: none !important;
}

.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_today > div {
  color: var(--ti-accent) !important;
  font-weight: 700;
}

.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select {
  background: var(--ti-sidebar-bg) !important;
  border: 1px solid var(--ti-border) !important;
  box-shadow: var(--ti-shadow) !important;
}

.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option:hover,
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option.xdsoft_current {
  background: var(--ti-nav-hover-bg) !important;
  color: var(--ti-text) !important;
}

.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box { border-color: var(--ti-border) !important; }

.xdsoft_datetimepicker .xdsoft_prev,
.xdsoft_datetimepicker .xdsoft_next,
.xdsoft_datetimepicker .xdsoft_today_button { filter: invert(1) opacity(.6); }

:root[data-theme="light"] .xdsoft_datetimepicker .xdsoft_prev,
:root[data-theme="light"] .xdsoft_datetimepicker .xdsoft_next,
:root[data-theme="light"] .xdsoft_datetimepicker .xdsoft_today_button { filter: none; }


/* --- Tooltip & popover --------------------------------------------------
   Two different things that both used to be a white box on a dark page.

   .ti-tip replaces the browser's native `title` tooltip (theme.js swaps the
   attribute out on hover). .popover is Bootstrap's, still driven by its own
   JS -- only repainted here. Both read the same tokens, so they follow the
   toggle without a second definition per theme. */

.ti-tip {
  position: fixed;
  z-index: 3000;
  max-width: 280px;
  padding: 7px 11px;
  border-radius: 9px;
  background: var(--ti-tip-bg);
  border: 1px solid var(--ti-tip-border);
  color: var(--ti-tip-text);
  font-family: var(--ti-font);
  font-size: 11.5px;
  line-height: 1.5;
  /* Several titles carry newlines (the early-leave dates are one per line). */
  white-space: pre-line;
  box-shadow: var(--ti-shadow);
  opacity: 0;
  visibility: hidden;
  transform: translateY(3px);
  transition: opacity var(--ti-fast) ease, transform var(--ti-fast) ease, visibility var(--ti-fast);
  pointer-events: none;
}

.ti-tip.is-open {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.ti-tip--below { transform: translateY(-3px); }
.ti-tip--below.is-open { transform: none; }

/* --- Bootstrap popover --- */

.ti-body .popover {
  background: var(--ti-sidebar-bg);
  border: 1px solid var(--ti-border);
  border-radius: var(--ti-radius);
  box-shadow: var(--ti-shadow-lg);
  font-family: var(--ti-font);
  color: var(--ti-text);
  padding: 0;
}

.ti-body .popover-title {
  background: var(--ti-chip-bg);
  border-bottom: 1px solid var(--ti-border);
  border-radius: var(--ti-radius) var(--ti-radius) 0 0;
  color: var(--ti-text);
  font-size: 12px;
  font-weight: 700;
}

.ti-body .popover-content {
  color: var(--ti-text-soft);
  font-size: 12px;
  padding: 10px 12px;
}

/* The arrow is two stacked borders: the outer one is the border colour, the
   inner one the fill. Both are hardcoded white by Bootstrap. */
.ti-body .popover > .arrow { border-width: 11px; }
.ti-body .popover > .arrow:after { border-width: 10px; }

.ti-body .popover.top > .arrow { border-top-color: var(--ti-border); }
.ti-body .popover.top > .arrow:after { border-top-color: var(--ti-sidebar-bg); }
.ti-body .popover.bottom > .arrow { border-bottom-color: var(--ti-border); }
.ti-body .popover.bottom > .arrow:after { border-bottom-color: var(--ti-sidebar-bg); }
.ti-body .popover.left > .arrow { border-left-color: var(--ti-border); }
.ti-body .popover.left > .arrow:after { border-left-color: var(--ti-sidebar-bg); }
.ti-body .popover.right > .arrow { border-right-color: var(--ti-border); }
.ti-body .popover.right > .arrow:after { border-right-color: var(--ti-sidebar-bg); }

/* The incomplete-days popover holds a small table, which Bootstrap paints
   white on white in dark. */
.ti-body .popover-content .table > thead > tr > th,
.ti-body .popover-content .table > tbody > tr > td {
  border-color: var(--ti-border);
  background: transparent;
  color: var(--ti-text);
  padding: 5px 8px;
}

.ti-body .popover-content .table > thead > tr > th {
  color: var(--ti-text-muted);
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.ti-body .popover-content .table-bordered { border-color: var(--ti-border); }

/* Bootstrap's own tooltip, for anything that opts into it. */
.ti-body .tooltip-inner {
  background: var(--ti-tip-bg);
  border: 1px solid var(--ti-tip-border);
  border-radius: 9px;
  color: var(--ti-tip-text);
  font-family: var(--ti-font);
  font-size: 11.5px;
  padding: 7px 11px;
}

.ti-body .tooltip.top .tooltip-arrow { border-top-color: var(--ti-tip-bg); }
.ti-body .tooltip.bottom .tooltip-arrow { border-bottom-color: var(--ti-tip-bg); }
.ti-body .tooltip.left .tooltip-arrow { border-left-color: var(--ti-tip-bg); }
.ti-body .tooltip.right .tooltip-arrow { border-right-color: var(--ti-tip-bg); }
.ti-body .tooltip.in { opacity: 1; }


/* --- Modal --------------------------------------------------------------
   Bootstrap still owns the behaviour (backdrop, ESC, data-dismiss); only the
   chrome is ours. `.ti-modal` goes on the outer `.modal` so the dialog can be
   sized from here, and the content column is a flex box with a scrolling body
   — the employee form is taller than a laptop screen, and letting the whole
   dialog scroll would take the title and the Update button with it. */

.ti-body .ti-modal .modal-content {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 60px);
  background: var(--ti-sidebar-bg);
  border: 1px solid var(--ti-border);
  border-radius: var(--ti-radius-lg);
  box-shadow: var(--ti-shadow-lg);
}

/* Some modals wrap head/body/foot in a <form>, which then sits between the
   height-capped column and the parts meant to fill it: the form is a plain
   block, so it grew to its content and spilled out of the box, top and bottom.
   It has to be the column instead. min-height: 0 because a flex child will not
   shrink below its content otherwise, which is what makes the body scroll. */
.ti-body .ti-modal .modal-content > form {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ti-body .ti-modal .modal-dialog { width: 460px; max-width: calc(100vw - 32px); }
.ti-body .ti-modal--sm .modal-dialog { width: 400px; }
.ti-body .ti-modal--md .modal-dialog { width: 520px; }
.ti-body .ti-modal--lg .modal-dialog { width: 660px; }

.ti-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex: none;
  padding: 20px 22px 14px;
  border-bottom: 1px solid var(--ti-border);
}

.ti-modal-eyebrow {
  font-family: var(--ti-mono);
  font-size: 10.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ti-text-muted);
}

.ti-body .ti-modal-title {
  margin: 4px 0 0;
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -.02em;
}

/* --- Password show/hide ---------------------------------------------------
   The button sits inside the field rather than beside it, so a row of inputs
   keeps one edge. The input is given room on the right for it; without that
   a long value runs under the icon.

   tabindex="-1" on the button (see ti_password_toggle): tabbing from the
   password should reach the next field, not a control for the one just left. */

.ti-pw {
  position: relative;
  display: block;
}

.ti-pw .ti-input { padding-right: 42px; }

.ti-pw-toggle {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ti-text-muted);
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, color var(--ti-fast) ease;
}

/* Same baseline story as the theme toggle: an inline svg would sit a pixel
   and a half high inside its box. */
.ti-pw-toggle .ti-icon { display: block; }

.ti-pw-toggle:hover,
.ti-pw-toggle:focus-visible {
  background: var(--ti-chip-bg);
  color: var(--ti-text);
}

/* Hidden value: offer to show it. Shown value: offer to hide it. */
.ti-pw-toggle .ti-pw-hide { display: none; }
.ti-pw.is-shown .ti-pw-toggle .ti-pw-show { display: none; }
.ti-pw.is-shown .ti-pw-toggle .ti-pw-hide { display: block; }

/* --- The announcement popup ---------------------------------------------
   The one modal here that is not a form. It carries a single sentence and
   asks for a single acknowledgement, so it reads down the middle rather than
   across: no eyebrow, a mark over the title, the button under the text. The
   close button leaves the flow so the title has the whole width to centre in. */

.ti-modal--announce .ti-modal-head {
  position: relative;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 28px 22px 18px;
}

.ti-modal--announce .ti-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
}

/* Something to rest on above the title, now that the eyebrow is gone. Same
   gradient the primary button wears, so the two ends of the card agree. */
.ti-announce-icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 50%;
  color: #fff;
  background: linear-gradient(135deg, oklch(.58 .19 285), oklch(.5 .18 258));
  box-shadow: 0 14px 26px -14px oklch(.5 .18 275 / .9);
}

.ti-modal--announce .ti-modal-body {
  text-align: center;
  padding: 22px 26px;
}

.ti-modal--announce .ti-modal-foot { justify-content: center; }

.ti-modal-close {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--ti-text-muted);
  cursor: pointer;
  transition: background-color var(--ti-fast) ease, color var(--ti-fast) ease;
}

.ti-modal-close:hover {
  background: var(--ti-nav-hover-bg);
  color: var(--ti-text);
}

.ti-modal-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ti-modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  flex: none;
  padding: 14px 22px 18px;
  border-top: 1px solid var(--ti-border);
}

.ti-modal-foot .ti-push { margin-right: auto; margin-left: 0; }


/* --- Form layout --------------------------------------------------------
   Field = label + control + optional hint, stacked. The grids are plain
   column counts so a form is laid out in the view rather than by adding a
   modifier per shape. */

.ti-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.ti-field .ti-label { margin: 0; }

.ti-req { color: var(--ti-red); }

.ti-hint {
  font-size: 11px;
  color: var(--ti-text-muted);
}

/* Directly after .ti-hint and not three thousand lines above it: both are a
   single class, so the one further down the file wins. Declared first, the
   modifier lost to the base every time and "The two passwords are different"
   read as ordinary grey help text rather than the error it is. */
.ti-hint--bad {
  color: var(--ti-red);
  font-weight: 600;
}

.ti-fieldgrid {
  display: grid;
  gap: 13px;
}

.ti-fieldgrid--2 { grid-template-columns: repeat(2, 1fr); }
.ti-fieldgrid--3 { grid-template-columns: repeat(3, 1fr); }

/* A titled panel inside a form, for a group that needs its own frame. */
.ti-formcard {
  padding: 16px 18px;
  border-radius: var(--ti-radius);
  background: var(--ti-panel-bg);
  border: 1px solid var(--ti-border);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ti-body .ti-formcard-title {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ti-accent);
}

.ti-formcard .ti-input { background: var(--ti-card-bg); }


/* --- Checkbox / radio controls -----------------------------------------
   AdminBSB hides every checkbox off-screen and draws its own box on the
   *label that follows it*. Both are avoided here: the input is nested inside
   the label (so no sibling label exists to decorate) and it is hidden by our
   own rule, which outranks theirs. The visible box is a span we draw. */

.ti-body .ti-check input,
.ti-body .ti-radio input {
  position: absolute;
  left: auto;
  width: 0;
  height: 0;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.ti-check,
.ti-radio {
  --ctl-h: 275;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  padding: 10px 14px;
  border-radius: 11px;
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ti-text);
  cursor: pointer;
  transition: border-color var(--ti-fast) ease, transform var(--ti-fast) ease;
}

.ti-check:hover,
.ti-radio:hover { transform: translateY(-1px); }

.ti-check:has(input:checked),
.ti-radio:has(input:checked) { border-color: oklch(.62 .17 var(--ctl-h)); }

.ti-check-box {
  display: grid;
  place-items: center;
  width: 17px;
  height: 17px;
  flex: none;
  border-radius: 5px;
  border: 1.5px solid var(--ti-border-strong);
  color: #fff;
  transition: background var(--ti-fast) ease, border-color var(--ti-fast) ease;
}

.ti-check-box svg { opacity: 0; transition: opacity var(--ti-fast) ease; }

.ti-check input:checked ~ .ti-check-box {
  background: linear-gradient(135deg, oklch(.58 .18 275), oklch(.48 .17 255));
  border-color: oklch(.6 .16 275);
}

.ti-check input:checked ~ .ti-check-box svg { opacity: 1; }

.ti-check input:focus-visible ~ .ti-check-box,
.ti-radio input:focus-visible ~ .ti-radio-dot {
  box-shadow: 0 0 0 3px var(--ti-accent-soft);
}

/* Wide variant: the control carries a sentence of explanation. */
.ti-check--block {
  display: flex;
  align-items: flex-start;
  width: 100%;
  padding: 12px 14px;
  border-radius: 12px;
  font-weight: 500;
}

.ti-check--block .ti-check-box { margin-top: 1px; }

.ti-check-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ti-text-soft);
}

.ti-radio-dot {
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--ti-border-strong);
  transition: border-color var(--ti-fast) ease;
}

.ti-radio-dot::after {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: transparent;
  transition: background var(--ti-fast) ease;
}

.ti-radio input:checked ~ .ti-radio-dot { border-color: oklch(.62 .17 var(--ctl-h)); }
.ti-radio input:checked ~ .ti-radio-dot::after { background: oklch(.62 .17 var(--ctl-h)); }

/* Pill radios — the Full/Half and Minus/Plus choices, which read as options
   rather than as a settings list. */
.ti-radio--pill {
  border-radius: 24px;
  padding: 9px 16px;
}

.ti-radiorow {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}


/* --- Confirm dialog ----------------------------------------------------- */
.ti-confirm {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 13px;
  padding: 26px 24px 22px;
  text-align: center;
}

.ti-confirm-icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 17px;
  background: var(--ti-red-bg);
  color: var(--ti-red);
}

.ti-confirm-title {
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -.02em;
}

.ti-confirm-text {
  max-width: 320px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ti-text-soft);
}

/* Filled danger, for the button that actually destroys something. The ghost
   `.ti-btn--danger` is for a row action; this is for the confirmation. */
.ti-btn--danger-solid {
  background: linear-gradient(135deg, oklch(.58 .2 25), oklch(.48 .19 20));
  color: #fff;
  box-shadow: 0 12px 24px -14px oklch(.5 .2 25 / .9);
}

.ti-btn--danger-solid:hover,
.ti-btn--danger-solid:focus { color: #fff; filter: none; }


/* --- Empty state --- */
/* `display: flex` outranks the UA's `[hidden] { display: none }`, so a page
   that toggles the block with the attribute needs this or it never hides. */
.ti-empty[hidden] { display: none; }

.ti-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 50px 20px;
  text-align: center;
  color: var(--ti-text-muted);
  font-size: 13px;
}


/* 9 — Utilities ========================================================== */

.ti-muted { color: var(--ti-text-muted); }
.ti-soft  { color: var(--ti-text-soft); }

.ti-stack { display: flex; flex-direction: column; gap: var(--ti-gap); }
.ti-row   { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ti-grow  { flex: 1; min-width: 0; }
.ti-push  { margin-left: auto; }

/* On paper only. The @media print block turns it back on -- a printed sheet
   needs to state things a reader on screen can see in the chrome. */
.ti-printonly { display: none; }

/* Visible to screen readers only. */
/* Wrapper that is not a box: lets a <form> sit inside a grid without becoming
   the grid item, so the card it wraps lines up with its siblings. */
.ti-contents {
  display: contents;
}

.ti-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}


/* 10 — Legacy compatibility ============================================= */

/* AdminBSB positions `section.content` with `margin: 100px 15px 0 315px` — its
   own header height and sidebar width. §5 overrides that with the new metrics.
   The rules below cover the widgets inside those views so un-migrated pages
   stay legible, particularly in dark mode. Delete this section once every view
   has been redesigned. */

/* AdminBSB's page loader covers the viewport until admin.js hides it; give it
   the themed background so it is not a white flash in dark mode. */
.ti-body .page-loader-wrapper {
  background: var(--ti-bg);
}

/* AdminBSB panels/cards inside the new shell, so un-migrated pages stay
   legible in dark mode instead of rendering white-on-white. */
.ti-body .card {
  background: var(--ti-card-bg);
  border: 1px solid var(--ti-border);
  border-radius: var(--ti-radius);
  box-shadow: none;
  color: var(--ti-text);
}

.ti-body .card .header h2 {
  color: var(--ti-text);
  font-size: 16px;
  font-weight: 700;
}

.ti-body .table > thead > tr > th,
.ti-body .table > tbody > tr > td {
  border-color: var(--ti-border);
  color: var(--ti-text);
}

.ti-body .table > thead > tr > th {
  color: var(--ti-text-muted);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.ti-body .table-striped > tbody > tr:nth-of-type(odd) {
  background: var(--ti-chip-bg);
}

/* DataTables chrome */
.ti-body .dataTables_wrapper,
.ti-body .dataTables_wrapper .dataTables_info,
.ti-body .dataTables_wrapper .dataTables_length,
.ti-body .dataTables_wrapper .dataTables_filter {
  color: var(--ti-text-soft);
}

.ti-body .dataTables_wrapper .dataTables_filter input,
.ti-body .dataTables_wrapper .dataTables_length select {
  background: var(--ti-chip-bg);
  border: 1px solid var(--ti-border);
  border-radius: 8px;
  color: var(--ti-text);
  padding: 5px 9px;
}

.ti-body .dataTables_wrapper .dataTables_paginate .paginate_button {
  color: var(--ti-text-soft) !important;
  border-radius: 8px;
}

.ti-body .dataTables_wrapper .dataTables_paginate .paginate_button.current {
  background: var(--ti-accent-soft) !important;
  border-color: var(--ti-accent) !important;
  color: var(--ti-text) !important;
}

/* The old theme's modal, still used by the announcement popup in footer.php. */
.ti-body .modal-content {
  background: var(--ti-card-bg);
  color: var(--ti-text);
  border: 1px solid var(--ti-border);
  border-radius: var(--ti-radius-lg);
}

.ti-body .modal-header,
.ti-body .modal-footer {
  border-color: var(--ti-border);
}


/* 11 — Responsive ======================================================= */

/* Eight columns at 20px a side is 320px of an .ti-table--card spent on gaps
   before a single value is drawn. A wide monitor has the room for that; a
   laptop does not -- measured at a 1024px window, Manage Requests wanted
   762px of table in 700px of card. This is the real overflow, separate from
   the rounding one the 1px of slack above deals with.

   1200 rather than the 1024 below it: the measurement was taken on one short
   row, and a longer name or a fuller reason needs the tighter gaps sooner. */
@media (max-width: 1200px) {
  .ti-body .ti-table--card > thead > tr > th,
  .ti-body .ti-table--card > tbody > tr > td {
    padding-left: 12px;
    padding-right: 12px;
  }
}

@media (max-width: 1024px) {
  .ti-page,
  .ti-body section.content { padding: 20px 18px 28px; }
}

@media (max-width: 860px) {
  .ti-nav-open { display: grid; }

  /* Sidebar becomes an overlay drawer. `data-sidebar` still drives it, so the
     same button works at every width — it just means "open" here rather than
     "expanded". */
  .ti-sidebar {
    transform: translateX(-100%);
    box-shadow: none;
    transition: transform var(--ti-base) var(--ti-ease);
    width: var(--ti-sidebar-w);
  }

  :root[data-sidebar="open"] .ti-sidebar {
    transform: translateX(0);
    box-shadow: var(--ti-shadow-lg);
  }

  /* Collapsed is meaningless on a phone — always show full-width labels. */
  :root[data-sidebar="collapsed"] .ti-sidebar {
    width: var(--ti-sidebar-w);
    transform: translateX(-100%);
  }

  :root[data-sidebar="collapsed"] .ti-nav a { justify-content: flex-start; padding: 9px 10px; }
  :root[data-sidebar="collapsed"] .ti-nav-label,
  :root[data-sidebar="collapsed"] .ti-nav-badge { display: revert; }
  :root[data-sidebar="collapsed"] .ti-sidebar-legal { display: block; }

  .ti-body section.content,
  .ti-body .ti-page,
  :root[data-sidebar="collapsed"] .ti-body section.content,
  :root[data-sidebar="collapsed"] .ti-body .ti-page { margin-left: 0; }

  .ti-sidebar-toggle { display: none; }

  .ti-scrim {
    display: block;
    position: fixed;
    inset: var(--ti-header-h) 0 0 0;
    background: rgb(0 0 0 / .45);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ti-base) ease;
    z-index: 890;
  }

  :root[data-sidebar="open"] .ti-scrim {
    opacity: 1;
    pointer-events: auto;
  }
}

@media (max-width: 720px) {
  /* Employee row: keep the person on the first line and let the figures and
     the actions wrap underneath, rather than squeezing six buttons and three
     boxes into what is left of the width. */
  .ti-emp-id { flex: 1 0 100%; }
  .ti-emp-stats { flex: 1; }
  .ti-emp-actions { flex-wrap: wrap; }

  /* Two- and three-column form grids are one column on a phone. */
  .ti-fieldgrid--2,
  .ti-fieldgrid--3 { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .ti-header { padding: 0 14px; }
  .ti-header-actions { gap: 10px; }
  .ti-header-user { display: none; }
  .ti-page-title { font-size: 22px; }

  /* Keep the avatar and the Request Leave icon, drop the words they carry —
     the header has room for one or the other, not both. */
  .ti-usermenu-id { display: none; }
  .ti-usermenu-trigger { padding: 4px; }
  .ti-header-cta-text { display: none; }
  .ti-header-cta { padding: 9px; }
}

/* --------------------------------------------------------------------------
   Printing

   The screen theme is built for a backlit panel: dark canvas, glows, colour
   carried in backgrounds. On paper that is either wasted toner or — since
   browsers drop backgrounds unless asked — nothing at all.

   So print re-declares the light tokens on :root. Every component reads its
   colour from a custom property, so that one override turns the whole app to
   ink; only the few things a token cannot express are patched afterwards.
   -------------------------------------------------------------------------- */
@media print {
  @page { margin: 12mm; }

  /* Force the light palette regardless of what the toggle is set to. */
  :root,
  :root[data-theme="dark"],
  :root[data-theme="light"] {
    --ti-bg: #fff;
    --ti-canvas-glow: none;
    --ti-text: #000;
    --ti-text-soft: #222;
    --ti-text-muted: #555;
    --ti-card-bg: #fff;
    --ti-sidebar-bg: #fff;
    --ti-chip-bg: #fff;
    --ti-chip-solid: #fff;
    --ti-panel-bg: #fff;
    --ti-track-bg: #e4e4e4;
    --ti-border: #c8c8c8;
    --ti-border-strong: #8a8a8a;
    --ti-shadow-sm: none;
    --ti-shadow: none;
    --ti-shadow-lg: none;
    color-scheme: light;
  }

  body.ti-body {
    background: #fff !important;
    color: #000 !important;
  }

  /* Chrome and controls — nothing on paper can be clicked. */
  .ti-header,
  .ti-sidebar,
  .ti-scrim,
  .page-loader-wrapper,
  .ti-filter,
  .ti-icon-btn,
  .ti-btn,
  .ti-tabs,
  .ti-daycard-chev,
  .ti-live-dot { display: none !important; }

  .ti-body section.content,
  .ti-body .ti-page {
    margin: 0 !important;
    padding: 0 !important;
    min-height: 0 !important;
  }

  /* The entrance animations fill backwards and are staggered by index, so a
     card whose delay has not elapsed when the print snapshot is taken would
     come out blank. Scoped to the page so it cannot reveal a hidden overlay. */
  .ti-page,
  .ti-page *,
  .ti-page *::before,
  .ti-page *::after {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    box-shadow: none !important;
    filter: none !important;
    opacity: 1 !important;
  }

  .ti-page-head { margin-bottom: 14px; }
  .ti-page-title { font-size: 22px; }

  /* Paper wants the attendance sheet, not the dashboard. The summary tiles are
     figures for the month, not for the days being printed; the timeline and
     its legend swatches are a way of reading a bar on screen. What is left is
     the sheet itself: the day, its punches, its total. */
  .ti-summary,
  .ti-chiprow,
  .ti-timeline,
  .ti-legend span:not(.ti-push) { display: none !important; }

  /* The legend row survives for one reason -- it carries the period, which is
     the one thing a printed sheet must state and the reader cannot infer. */
  .ti-legend {
    margin-bottom: 10px;
    padding-bottom: 7px;
    border-bottom: 1px solid var(--ti-border);
    font-size: 12px;
    color: #000;
  }

  /* Every day prints expanded — a collapsed card on paper is just a row the
     reader cannot open. */
  .ti-daycard-detail { grid-template-rows: 1fr !important; }

  .ti-stack { gap: 8px; }

  .ti-daycard-head {
    cursor: default;
    padding: 9px 12px;
    gap: 12px;
  }

  .ti-daycard-badge { width: 44px; height: 44px; }
  .ti-daycard-top { margin-bottom: 0; }

  .ti-punches {
    overflow: visible !important;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 6px;
    margin: 0 12px;
    padding: 8px 0 10px !important;
    border-top: 1px dashed var(--ti-border);
  }

  .ti-punch { padding: 5px 9px; }

  /* Keep a day whole: split across a page break it loses the head that says
     which day it is. */
  .ti-daycard,
  .ti-punch { break-inside: avoid; page-break-inside: avoid; }

  /* The day chip and the punch tiles sit on --ti-chip-bg, which is white here,
     so they need an outline to stay legible. */
  .ti-daycard-badge,
  .ti-punch { border: 1px solid var(--ti-border); }

  .ti-daycard-badge { color: #000; }

  /* Status pills as outlined text: readable whether or not the printer keeps
     backgrounds, and still colour-coded on a colour printer. */
  .ti-badge {
    background: transparent !important;
    border: 1px solid currentColor;
  }

  .ti-badge--green { color: oklch(.48 .15 150); }
  .ti-badge--teal  { color: oklch(.48 .12 210); }
  .ti-badge--blue  { color: oklch(.48 .15 235); }
  .ti-badge--amber { color: oklch(.48 .13 45); }
  .ti-badge--red   { color: oklch(.5 .18 25); }

  /* --- Member record --------------------------------------------------
     A month of somebody's attendance is the one screen in here people
     actually print, so it gets treated as a document rather than a
     screenshot. */

  .ti-printonly { display: block !important; }

  /* The band is a gradient with white text on it. Browsers drop backgrounds
     unless asked, which would print white on white -- so on paper it stops
     being a band and becomes the letterhead. */
  .ti-hero {
    background: none !important;
    color: #000 !important;
    border-bottom: 2px solid #000;
    border-radius: 0;
    padding: 0 0 12px;
    margin-bottom: 12px;
    gap: 14px;
  }

  .ti-hero-avatar {
    background: none !important;
    border: 1px solid var(--ti-border);
    color: #000;
  }

  .ti-body .ti-hero-name { color: #000 !important; font-size: 20px; }
  .ti-hero-eyebrow, .ti-hero-user { opacity: 1; color: #333; }

  .ti-hero-stat {
    background: none !important;
    border: 1px solid var(--ti-border);
    border-radius: 6px;
    padding: 6px 10px;
  }

  .ti-hero-stat-label { opacity: 1; color: #444; }

  /* The bar is the two figures beside it drawn as a length; the figures are
     already there in words. */
  .ti-hero-progress-track,
  .ti-hero-btn,
  .ti-back,
  .ti-detail-bar,
  .ti-log-bar,
  .ti-logrow-actions,
  .ti-check,
  .ti-logcount { display: none !important; }

  .ti-hero-progress { margin-top: 0; }
  .ti-hero-progress-pct { background: none !important; padding: 0; }

  .ti-print-period {
    margin-bottom: 12px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #000;
  }

  .ti-print-stamp {
    float: right;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: #555;
  }

  /* Let the grids reflow to the paper rather than run off its edge, and drop
     the checkbox column with the checkboxes. */
  .ti-recgrid { min-width: 0; }
  /* Paper is narrower than the row was sized for, so the columns give their
     slack back rather than pushing the total off the sheet. */
  .ti-recgrid { --rec-cols: 92px 92px 1fr 84px 92px; }
  .ti-loggrid { min-width: 0; }
  .ti-loggrid { --log-cols: 1fr 116px 104px 84px; }
  .ti-loghead > span:first-child,
  .ti-loghead > span:last-child { display: none; }

  .ti-tablewrap { overflow: visible !important; padding: 0; }

  /* A row split across a page break loses the date that says which day it
     is. The header only prints once, so the columns have to stay legible on
     their own -- which they do, every cell is labelled by its content. */
  .ti-recrow,
  .ti-logrow { break-inside: avoid; page-break-inside: avoid; }

  .ti-recrow,
  .ti-logrow {
    border: 0;
    border-bottom: 1px solid var(--ti-border);
    border-radius: 0;
    padding: 7px 4px;
  }

  .ti-recrows { gap: 0; }
  .ti-rechead, .ti-loghead { padding: 0 4px 6px; border-bottom: 1px solid #000; }

  /* The coloured edge is the one thing worth keeping in ink: it is how a
     short day is spotted down a page. */
  .ti-recrow::before { width: 2px; }

  /* An input on paper is just text. min-width lets the time control give its
     slack back with the rest of the columns instead of holding the intrinsic
     width a native `time` field asks for. */
  .ti-logrow-note,
  .ti-logrow-time {
    border: 0 !important;
    padding: 0;
    min-width: 0;
    color: #000;
  }

}
