/* =========================================================================
   OMC ERP Portal — Core stylesheet (self-contained, responsive)
   ========================================================================= */

/* Brand fonts (match vaspire.co.in) — falls back to system fonts offline */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@500;600;700&display=swap');

:root {
    /* ===== VAspire brand palette — matched to vaspire.co.in / brand logo ===== */
    --navy:      #0d1b2a;   /* deep brand navy — sidebar, headings, dark surfaces */
    --navy-700:  #14293d;
    --navy-600:  #1d3d5a;
    --primary:   #1aacdc;   /* brand cyan / sky-blue — the logo accent */
    --primary-600:#1591bd;  /* darker cyan — hover / active */
    --accent:    #f59e0b;   /* functional amber accent (warnings, tile variety) */
    --bg:        #f4f7fb;
    --surface:   #ffffff;
    --border:    #e2e8f0;
    --text:      #2d3748;   /* brand ink (body text) */
    --muted:     #64748b;
    --success:   #16a34a;
    --danger:    #dc2626;
    --warning:   #d97706;
    --info:      #0e8fbf;   /* cyan-leaning info to sit with the brand */
    --secondary: #64748b;
    --dark:      #334155;
    --radius:    14px;   /* cards, tiles — matches the sign-in card */
    --card-pad:  1.25rem;/* the one gutter: card head, card body, flush tables */
    --radius-sm: 4px;    /* buttons, inputs, nav — matches its controls */
    --shadow:    0 4px 14px rgba(13,27,42,.07);
    --shadow-lg: 0 10px 30px rgba(13,27,42,.12);
    --sidebar-w: 200px; /* 250px */
    --font:      'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-head: 'Poppins', 'Inter', -apple-system, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; max-width: 100%; }
/* The page itself must never scroll sideways on mobile: horizontal overflow
   inflates the viewport width, which breaks the responsive layout AND fools the
   mobile menu toggle (window.innerWidth) so the hamburger stops working.
   `overflow-x: clip` clips without creating a scroll container, so the sticky
   topbar keeps working. Wide content (tables) scrolls inside its own wrapper. */
html { overflow-x: clip; }
body {
    font-family: var(--font);
    background: var(--bg);
    color: var(--text);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    overflow-x: clip;
}
/* Let grid/flex children shrink below their content width so a wide table
   scrolls inside its .table-wrap instead of widening the whole page. This is
   the root-cause fix for the mobile horizontal-scroll / dead-hamburger bug. */
.main, .content, .card,
.split-aside > *, .split-main > *, .grid-2 > *, .form-grid > * { min-width: 0; }
.table-wrap { max-width: 100%; }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { margin: 0 0 .4rem; font-weight: 600; color: var(--navy); font-family: var(--font-head); }
svg.ic { flex: 0 0 auto; vertical-align: middle; }

/* ---------- App shell ---------- */
.app { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-w);
    /* Grades into the wave at its foot rather than meeting it at an edge.
       At the darkest point (--navy-600) nav text is still 7.6:1. */
    /* background: linear-gradient(180deg, var(--navy) 0%, var(--navy) 45%, var(--navy-600) 100%); */
    background: var(--navy);
    color: #cbd5e1;
    overflow: hidden;            /* clips the wave to the panel */
    padding-bottom: 56px;        /* the pinned Sign out row */
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 40;
    transition: transform .25s ease, width .2s ease;
}
.brand {
    display: flex; align-items: center; gap: .65rem;
    padding: var(--brand-pad-y, 1.1rem) 1.2rem;
    border-bottom: 1px solid rgba(255,255,255,.08);
}
/* A — the same treatment the sign-in card uses: filled navy, flat edges,
   the monogram reversed to white. Scales with the density tiers. */
.brand-logo { width: var(--brand-logo, 38px); height: var(--brand-logo, 38px);
              border-radius: 0; background: var(--navy);
              box-shadow: inset 0 0 0 1px rgba(255,255,255,.22);
              padding: 5px; flex: 0 0 auto; color: #fff;
              display: flex; align-items: center; justify-content: center; }
.brand-logo svg { width: 100%; height: 100%; display: block; }
.brand-text { display: flex; flex-direction: column; line-height: 1.1; }
.brand-text strong { color: #fff; font-size: 1.05rem; letter-spacing: .5px; }
.brand-text span { font-size: .72rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; }

.nav { flex: 1 1 auto; padding: var(--nav-wrap-pad-y, .75rem) .6rem; overflow-y: hidden; min-height: 0; }
.nav-link {
    display: flex; align-items: center; gap: var(--nav-gap, .7rem);
    position: relative; z-index: 2;   /* above the wave */
    padding: var(--nav-pad-y, .6rem) var(--nav-pad-x, .75rem);
    margin-bottom: var(--nav-mb, 2px);
    /*color: #cbd5e1;*/ color: #E6E8EA; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    font-size: var(--nav-font, .88rem); font-weight: 500;
    line-height: var(--nav-lh, 1.5);
    transition: background .15s, color .15s;
}
.nav-link:hover { background: rgba(255,255,255,.07); color: #fff; text-decoration: none; }
/* E — a tinted row with a cyan rail. White on solid --primary measured
   2.6:1, below the 4.5 bar; on this tint it is 13.4:1. */
.nav-link.active { background: rgba(26,172,220,.16); color: #fff;
                   box-shadow: inset 3px 0 0 var(--primary); font-weight: 600; }
.nav-link.active .pill { color: var(--text); }
.nav-link .ic { opacity: .9; width: var(--nav-icon, 18px); height: var(--nav-icon, 18px); }
.nav-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Pinned to the foot rather than sitting in the flow: the menu keeps that
   row for itself, which is what holds the density auto-fit healthy on a
   long menu, and the wave gets the height instead. It rides ON the wave,
   over its dark lower band — see the gradient note in layouts/layout.php. */
.sidebar-foot { padding: var(--foot-pad, .6rem);
                position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; }
.sidebar-foot .nav-link { color: #fff; }
.sidebar-foot .nav-link:hover { background: rgba(255,255,255,.12); color: #fff; }
.brand, .nav { position: relative; z-index: 2; }
/* A reserved strip at the very foot of the panel, below Sign out, matched
   by the padding-bottom on .sidebar. Nothing is ever laid out on top of
   it, which is the point: nav text on the cyan measures 2.4:1.
   Deliberately NOT sized to the space the menu leaves — the menu fills
   the panel by design, and shrinking nav to make room broke the density
   auto-fit, which reads nav.clientHeight. */
.sidebar-wave { position: absolute; left: 0; right: 0; bottom: 0; height: 190px;
                z-index: 1; pointer-events: none; }
.sidebar-wave svg { width: 100%; height: 100%; display: block; }
@media (prefers-reduced-motion: reduce) {
    /* app.js checks the same query and skips straight to the final shape. */
    .topbar::after { animation: none; }
}
@media (max-height: 760px) {
    /* Short window: the menu needs the room more than the panel needs a
       flourish, and losing a density tier to decoration is a bad trade. */
    /* The wave stands down, but Sign out is out of flow and still needs
       its row reserved, so the padding stays. */
    .sidebar-wave { display: none; }
}

/* ---------- Sidebar auto-fit ----------------------------------------------
   The menu grows as modules/permissions are added. Rather than scrolling, the
   sidebar steps down through density tiers (set by app.js on [data-density])
   until every item fits the viewport. Tier 0 is the comfortable default; the
   tightest tier still stays legible, and scrolling remains as a last resort. */
.sidebar {
    --nav-pad-y: .6rem;  --nav-pad-x: .75rem; --nav-font: .88rem; --nav-icon: 18px;
    --nav-gap: .7rem;    --nav-mb: 2px;       --nav-lh: 1.5;
    --nav-wrap-pad-y: .75rem; --brand-pad-y: 1.1rem; --brand-logo: 38px; --foot-pad: .6rem;
}
.sidebar[data-density="1"] { --nav-pad-y:.5rem;  --nav-font:.86rem; --nav-icon:17px; --nav-gap:.62rem; --nav-mb:2px; --nav-wrap-pad-y:.6rem;  --brand-pad-y:.95rem; --brand-logo:36px; }
.sidebar[data-density="2"] { --nav-pad-y:.42rem; --nav-font:.84rem; --nav-icon:17px; --nav-gap:.58rem; --nav-mb:1px; --nav-wrap-pad-y:.5rem;  --brand-pad-y:.85rem; --brand-logo:34px; --foot-pad:.5rem; }
.sidebar[data-density="3"] { --nav-pad-y:.36rem; --nav-font:.82rem; --nav-icon:16px; --nav-gap:.54rem; --nav-mb:1px; --nav-wrap-pad-y:.45rem; --brand-pad-y:.75rem; --brand-logo:32px; --foot-pad:.45rem; --nav-lh:1.45; }
.sidebar[data-density="4"] { --nav-pad-y:.3rem;  --nav-font:.8rem;  --nav-icon:16px; --nav-gap:.5rem;  --nav-mb:1px; --nav-wrap-pad-y:.4rem;  --brand-pad-y:.65rem; --brand-logo:30px; --foot-pad:.4rem;  --nav-lh:1.4; }
.sidebar[data-density="5"] { --nav-pad-y:.25rem; --nav-font:.78rem; --nav-icon:15px; --nav-gap:.46rem; --nav-mb:0;   --nav-wrap-pad-y:.35rem; --brand-pad-y:.55rem; --brand-logo:28px; --foot-pad:.35rem; --nav-lh:1.35; }
.sidebar[data-density="6"] { --nav-pad-y:.2rem;  --nav-font:.76rem; --nav-icon:15px; --nav-gap:.42rem; --nav-mb:0;   --nav-wrap-pad-y:.3rem;  --brand-pad-y:.5rem;  --brand-logo:26px; --foot-pad:.3rem;  --nav-lh:1.3; }
.sidebar[data-density="7"] { --nav-pad-y:.16rem; --nav-font:.74rem; --nav-icon:14px; --nav-gap:.38rem; --nav-mb:0;   --nav-wrap-pad-y:.25rem; --brand-pad-y:.45rem; --brand-logo:24px; --foot-pad:.28rem; --nav-lh:1.28; }
/* At the tightest tiers the brand subtitle is dropped to reclaim a line. */
.sidebar[data-density="6"] .brand-text span,
.sidebar[data-density="7"] .brand-text span { display: none; }

.main {
    flex: 1;
    margin-left: var(--sidebar-w);
    display: flex; flex-direction: column;
    min-width: 0;
    transition: margin-left .2s ease;
}

.topbar {
    height: 62px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 1rem;
    padding: 0 1.25rem;
    position: sticky; top: 0; z-index: 30;
}
/* W4 — the wave's gradient as a rule under the topbar, so the palette
   reaches every screen without entering the content area. */
.topbar::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
                 height: 6px; pointer-events: none;
                 background: linear-gradient(90deg, var(--primary) 0%, var(--primary-600) 50%, var(--navy-600) 100%);
                 /* Draws itself left to right on every page load. Pure CSS, so it
                    needs no script and costs nothing once it has run. Kept short:
                    this fires on every navigation, not once at sign-in. */
                 transform-origin: left center;
                 animation: topbarSweep 820ms cubic-bezier(.22,1,.36,1) both; }
@keyframes topbarSweep {
    from { transform: scaleX(0); opacity: .35; }
    to   { transform: scaleX(1); opacity: 1; }
}
.page-title { font-size: 1.1rem; margin: 0; flex: 1; white-space: nowrap; overflow: hidden; }
.topbar-right { display: flex; align-items: center; gap: .5rem; }
.icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; border-radius: 8px;
    background: transparent; border: none; color: var(--muted); cursor: pointer;
    position: relative;
}
.icon-btn:hover { background: var(--bg); color: var(--navy); }
.menu-toggle { display: inline-flex; }   /* collapse on desktop, off-canvas on mobile */
.badge-dot {
    position: absolute; top: 4px; right: 4px;
    background: var(--danger); color: #fff; font-size: .62rem;
    min-width: 16px; height: 16px; border-radius: 9px; padding: 0 4px;
    display: flex; align-items: center; justify-content: center; font-weight: 600;
}

.user-menu { position: relative; }
.user-btn {
    display: flex; align-items: center; gap: .55rem;
    background: transparent; border: none; cursor: pointer; padding: .25rem .4rem; border-radius: 8px;
}
.user-btn:hover { background: var(--bg); }
.avatar {
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--navy); color: #fff; font-weight: 600;
    display: flex; align-items: center; justify-content: center;
}
.user-meta { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
.user-meta strong { font-size: .85rem; color: var(--text); }
.user-meta small { font-size: .72rem; color: var(--muted); }
.dropdown {
    position: absolute; right: 0; top: 110%;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-lg);
    min-width: 190px; padding: .35rem; display: none; z-index: 50;
}
.dropdown.open { display: block; }
.dropdown a {
    display: flex; align-items: center; gap: .55rem;
    padding: .55rem .65rem; border-radius: var(--radius-sm);
    color: var(--text); font-size: .85rem;
}
.dropdown a:hover { background: var(--bg); text-decoration: none; }

.content { flex: 1; padding: 1.5rem; max-width: 1400px; width: 100%; }
.app-footer { padding: 1rem 1.5rem; color: var(--muted); font-size: .78rem; border-top: 1px solid var(--border); }

/* ---------- Page head ---------- */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.1rem; flex-wrap: wrap; }
.page-head h2 { margin: 0; font-size: 1.35rem; }
.page-head-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* ---------- Cards ---------- */
.card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: 1.25rem;
}
.card-head { padding: 1rem var(--card-pad); border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.card-head h3 { margin: 0; font-size: 1rem; }
.card-body { padding: var(--card-pad); }
.card-body.tight { padding: 0; }

/* ---------- Tight card bodies: one gutter, everywhere ----------
   `.tight` drops the body padding so a table can run edge to edge under the
   card head. That was fine at a small corner radius. At --radius 14px it is
   not, and two faults showed up across the portal:

   1. A table brings its own cell padding of .85rem, so the first column
      started 13.6px in — level with where the corner curve ends, reading as if
      it touched the border, and never lining up with the card head above it.
   2. Prose dropped into a tight body — a footnote, a lead line — had no gutter
      at all and sat hard against the edge.

   Both are fixed here rather than card by card. Eight inline `style="padding:…"`
   patches had already grown around the portal working around this, at four
   different values; .bday-list and .wish-list had independently settled on
   1.25rem "so the cards line up with every other card head on the page". That
   is now --card-pad, and this is the only place it is applied. */

/* A flush table's outer columns line up with the card head above them.
   Child combinators inside the table so a nested table keeps its own spacing. */
.card-body.tight table.table > thead > tr > th:first-child,
.card-body.tight table.table > thead > tr > td:first-child,
.card-body.tight table.table > tbody > tr > th:first-child,
.card-body.tight table.table > tbody > tr > td:first-child,
.card-body.tight table.table > tfoot > tr > th:first-child,
.card-body.tight table.table > tfoot > tr > td:first-child { padding-left: var(--card-pad); }

.card-body.tight table.table > thead > tr > th:last-child,
.card-body.tight table.table > thead > tr > td:last-child,
.card-body.tight table.table > tbody > tr > th:last-child,
.card-body.tight table.table > tbody > tr > td:last-child,
.card-body.tight table.table > tfoot > tr > th:last-child,
.card-body.tight table.table > tfoot > tr > td:last-child { padding-right: var(--card-pad); }

/* Prose in a tight body gets the gutter back. Only text elements: .nav, .filters
   and the lists already inset their own items, and pushing those over would
   double the indent. */
.card-body.tight > p,
.card-body.tight > small,
.card-body.tight > .help-text {
    display: block;
    padding-left: var(--card-pad); padding-right: var(--card-pad);
}
/* First or last in the body it also needs air, or it rests on the card head's
   rule or on the card's own bottom edge. */
.card-body.tight > p:first-child,
.card-body.tight > small:first-child,
.card-body.tight > .help-text:first-child { padding-top: .9rem; }
.card-body.tight > p:last-child,
.card-body.tight > small:last-child,
.card-body.tight > .help-text:last-child { padding-bottom: .9rem; }

/* ---------- Stat tiles ---------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 1rem; margin-bottom: 1.25rem; }
.stat {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 1.1rem 1.2rem; box-shadow: var(--shadow); display: flex; align-items: center; gap: 1rem;
}
.stat-ic { width: 46px; height: 46px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: #fff; flex: 0 0 auto; }
.stat-ic.blue { background: var(--primary); } .stat-ic.green { background: var(--success); }
.stat-ic.amber { background: var(--accent); } .stat-ic.navy { background: var(--navy); }
.stat-ic.red { background: var(--danger); } .stat-ic.cyan { background: var(--info); }
.stat-val { font-size: 1.6rem; font-weight: 700; color: var(--navy); line-height: 1; }
/* A flex item will not shrink below its content, and a figure like "₹ 15,27,722"
   has no break opportunity — so without this the number spills out of its tile. */
.stat > div { min-width: 0; }
.stat-val { overflow-wrap: anywhere; }

/* Report summaries carry five or six tiles rather than three, and most of them
   hold a rupee figure. A figure has no break opportunity, so a tile that is too
   narrow does not clip — it splits the number across two lines, which reads far
   worse. Everything here buys width back for the digits: a smaller icon, a
   smaller figure, and tighter padding. */
.stats.stats-report { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: .7rem; }
.stats.stats-report .stat { padding: .8rem .85rem; gap: .6rem; }
.stats.stats-report .stat-ic { width: 34px; height: 34px; border-radius: 9px; }
.stats.stats-report .stat-ic svg { width: 17px; height: 17px; }
.stats.stats-report .stat-val { font-size: 1.05rem; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.stats.stats-report .stat-lbl { font-size: .66rem; letter-spacing: .3px; margin-top: .18rem; }
@media (max-width: 620px) { .stats.stats-report { grid-template-columns: repeat(2, 1fr); } }
/* On the narrowest phones two columns leave ~150px per tile, which is not enough
   for a seven-digit figure — one column keeps every number on a single line. */
@media (max-width: 420px) { .stats.stats-report { grid-template-columns: 1fr; } }

.stat-lbl { font-size: .78rem; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; margin-top: .25rem; }

/* ---------- Tables ---------- */
.table-wrap { overflow-x: auto; }
table.table { width: 100%; border-collapse: collapse; font-size: .85rem; }
/* Table cells wrap by default. They used to be nowrap, which meant one long value
   — a 46-character job title, a full company name — pushed the whole table into a
   horizontal scrollbar. Wrapping keeps the table inside the page instead.
   Values that must NEVER break across lines (dates, times, money, action buttons)
   opt out with class="nowrap" on the cell. */
table.table th, table.table td { padding: .7rem .85rem; text-align: left; border-bottom: 1px solid var(--border); white-space: normal; }
table.table th.nowrap, table.table td.nowrap { white-space: nowrap; }
/* Some cells hold a single unbreakable token — a CSS selector, a long slug, a
   URL. Wrapping cannot help those, because there is no space to break at, so
   they get permission to break mid-token rather than widen the whole table. */
table.table th.breakall, table.table td.breakall { overflow-wrap: anywhere; word-break: break-word; }
/* Same permission, but for the cell only — never the header. Giving a header
   leave to break mid-word drops the column's minimum width to a single
   character, and the browser will happily starve it to that: the heading ends
   up reading downwards, one letter per line. Leaving the header alone keeps a
   sane floor (its longest word) while a pasted email or URL in the cell below
   still breaks instead of widening the whole grid. */
table.table td.breakcell { overflow-wrap: anywhere; }
/* Some cells hold free text that can run to a paragraph — a remark someone
   typed at length. Left alone, one of those sets the height of the entire row
   and every other row on screen has to live with it. Four lines, then an
   ellipsis; the full text is on the cell's title, and in the entry itself.

   Four rather than two because it is free: the Added By column already stands
   four lines tall, so the row is that height regardless. Measured at 2, 3 and 4
   lines the rows come out identical; only at 5 does a remark start driving
   them. */
.cell-clamp {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4;
    overflow: hidden;
}
/* A document chip is a link you click, not prose you read. Given leave to wrap
   it shreds a filename like "Naukri_NARAYANCHANDRASAMANTA[20y_0m].pdf" over five
   lines and doubles the height of every row; left on one line it makes the
   column as wide as the longest filename anyone has uploaded. So each chip gets
   a line of its own, capped, and truncates with an ellipsis — the full name is
   already on its title attribute, and on the file it opens. */
table.table td .attach-chip {
    display: block; max-width: 8rem; margin: .1rem 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
table.table td .attach-chip .ic { vertical-align: -2px; margin-right: .15rem; }
table.table th { background: #f8fafc; color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: .72rem; letter-spacing: .5px; }
table.table tbody tr:hover, table.table tbody tr:hover { background: #f8fafc; }
/* Now the default; kept so existing markup keeps working. */
table.table td.wrap, table.table th.wrap { white-space: normal; }
.table-empty { text-align: center; color: var(--muted); padding: 2.5rem 1rem; }
/* ...except inside a table, where it has never actually centred: `table.table td`
   above is (0,1,2) and plain `.table-empty` is (0,1,0), so its text-align:left
   won and every empty state has been sitting hard left. Same rule, enough
   specificity to land. (On a <p class="table-empty"> the original already works
   — there is no td rule to lose to.) */
table.table td.table-empty, table.table th.table-empty { text-align: center; }
/* A wide report table: let the text columns wrap instead of forcing a scrollbar,
   and drop the row-number column when space is short — it is a counter, so
   hiding it loses nothing. */
@media (max-width: 1280px) { table.table th.col-num, table.table td.col-num { display: none; } }
/* Ten columns of default padding cost ~270px before any content is drawn. A
   report table trades a little breathing room for fitting on a laptop. */
table.table.table-compact th, table.table.table-compact td { padding: .55rem .6rem; }
table.table.table-compact { font-size: .82rem; }



/* ---------- Badges ---------- */
.badge {
    display: inline-block; padding: .22rem .6rem; border-radius: 999px;
    font-size: .72rem; font-weight: 600; line-height: 1.4; text-transform: capitalize;
}
.badge-success { background: #dcfce7; color: #166534; }
.badge-danger  { background: #fee2e2; color: #991b1b; }
.badge-warning { background: #fef3c7; color: #92400e; }
.badge-info    { background: #cffafe; color: #155e75; }
.badge-secondary{ background: #e2e8f0; color: #334155; }
.badge-dark    { background: #334155; color: #fff; }
.badge-primary { background: #d6f1fb; color: #0b6e91; }

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .55rem 1rem; border-radius: var(--radius-sm);
    font-size: .85rem; font-weight: 600; cursor: pointer;
    border: 1px solid transparent; transition: background .15s, box-shadow .15s; text-decoration: none;
    line-height: 1.2; font-family: var(--font);
}
.btn:hover { text-decoration: none; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-600); color: #fff; }
.btn-navy { background: var(--navy); color: #fff; } .btn-navy:hover { background: var(--navy-700); }
.btn-success { background: var(--success); color: #fff; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-outline { background: #fff; border-color: var(--border); color: var(--text); }
.btn-outline:hover { background: var(--bg); }
.btn-sm { padding: .35rem .7rem; font-size: .78rem; }
.btn-icon { padding: .4rem; width: 32px; height: 32px; justify-content: center; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }
.btn-block { width: 100%; justify-content: center; }

/* ---------- Forms ---------- */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1rem 1.25rem; }
.form-group { display: flex; flex-direction: column; gap: .35rem; margin-bottom: .25rem; }
.form-group.col-span-2 { grid-column: span 2; }
.form-group.col-span-full { grid-column: 1 / -1; }
label { font-size: .8rem; font-weight: 600; color: var(--dark); }
label .req { color: var(--danger); }
input, select, textarea {
    width: 100%; padding: .55rem .7rem; font-size: .88rem; font-family: inherit;
    border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff; color: var(--text);
}
input[type="checkbox"],
input[type="radio"] {
    width: auto; padding: 0; border-radius: 0;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(26,172,220,.18); }
textarea { resize: vertical; min-height: 80px; }
.form-actions { display: flex; gap: .6rem; margin-top: 1.25rem; padding-top: 1.1rem; border-top: 1px solid var(--border); }
.help-text { font-size: .74rem; color: var(--muted); }
.checkbox-row { display: flex; align-items: center; gap: .5rem; }
.checkbox-row input { width: auto; }

/* ---------- Password field with show/hide toggle ---------- */
.pw-field { position: relative; }
.pw-field input { padding-right: 2.6rem; }
.pw-toggle {
    position: absolute; top: 50%; right: .5rem; transform: translateY(-50%);
    width: 30px; height: 30px; padding: 0; display: flex; align-items: center; justify-content: center;
    background: transparent; border: none; cursor: pointer; color: var(--muted); border-radius: 6px;
}
.pw-toggle:hover { color: var(--navy); background: var(--bg); }
.pw-toggle .ic-eye-off { display: none; }
.pw-toggle.is-on .ic-eye { display: none; }
.pw-toggle.is-on .ic-eye-off { display: inline-flex; }

/* ---------- Alerts ---------- */
.alert { padding: .75rem 1rem; border-radius: var(--radius-sm); margin-bottom: 1rem; font-size: .85rem; border: 1px solid transparent; }
.alert-success { background: #dcfce7; color: #166534; border-color: #bbf7d0; }
.alert-danger  { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
.alert-warning { background: #fef3c7; color: #92400e; border-color: #fde68a; }
.alert-info    { background: #cffafe; color: #155e75; border-color: #a5f3fc; }

/* ---------- Filters bar ---------- */
.filters { display: flex; gap: .6rem; flex-wrap: wrap; align-items: flex-end; margin-bottom: 1.1rem; }
.filters .form-group { margin: 0; min-width: 160px; }

/* A report's filter strip reads as one strip, not a block: on desktop it stays on
   a single line and the groups share the width instead of wrapping. Below the
   breakpoint it falls back to the wrapping default, which is right on a phone. */
@media (min-width: 769px) {
    .filters.filters-row { flex-wrap: nowrap; overflow-x: auto; padding-bottom: .15rem; }
    .filters.filters-row .form-group { min-width: 0; flex: 1 1 130px; }
    .filters.filters-row .form-group.search-box { flex: 2 1 190px; }
    .filters.filters-row input, .filters.filters-row select { width: 100%; min-width: 0; }
}

/* ---------- Toolbar/search ---------- */
/* The magnifier is a SIBLING of the input inside .form-group, and .search-box was
   the positioning context — so `top: 50%` meant halfway down the whole group,
   label and row-gap included, which lands above the input's middle rather than
   in it. The taller the label, the further out it drifts.

   Overlaying the icon and the input in a single grid cell fixes it with no magic
   number: align-self centres the icon on whatever height the input actually is,
   so it stays centred if the padding or font size ever change. The icon precedes
   the input in the DOM, so it needs z-index to sit above the field's background. */
.search-box { display: grid; grid-template-columns: 1fr; }
.search-box > label { grid-row: 1; }
.search-box > .ic {
    grid-row: 2; grid-column: 1;
    align-self: center; justify-self: start;
    margin-left: .6rem;        /* unchanged — only the vertical placement was wrong */
    color: var(--muted); z-index: 1;
    pointer-events: none;      /* a click on the glass still lands in the field */
}
.search-box > input { grid-row: 2; grid-column: 1; }
.search-box input { padding-left: 2.1rem; }

/* ---------- Utilities ---------- */
.text-muted { color: var(--muted); }
.text-right { text-align: right; }
.text-center { text-align: center; }
.mt-0{margin-top:0}.mt-1{margin-top:.5rem}.mt-2{margin-top:1rem}.mb-0{margin-bottom:0}.mb-2{margin-bottom:1rem}
.flex { display: flex; } .items-center { align-items: center; } .gap-1 { gap: .5rem; } .gap-2 { gap: 1rem; }
.justify-between { justify-content: space-between; } .flex-wrap { flex-wrap: wrap; }
.w-full { width: 100%; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
/* Fixed-width aside + fluid content; collapses to one column on small screens.
   Use these (with --aside for the fixed width) instead of an inline grid-template,
   so the responsive collapse actually applies — an inline style can't be overridden
   by a media query, which is what caused pages to overflow (inflating innerWidth). */
.split-aside, .split-main { display: grid; gap: 1.25rem; align-items: start; }
.split-aside { grid-template-columns: var(--aside, 360px) 1fr; }   /* fixed column on the LEFT  */
.split-main  { grid-template-columns: 1fr var(--aside, 340px); }   /* fixed column on the RIGHT */
@media (max-width: 992px) { .split-aside, .split-main { grid-template-columns: 1fr; } }
.nowrap { white-space: nowrap; }
.pill { display:inline-block; padding:.15rem .5rem; background:var(--bg); border:1px solid var(--border); border-radius:6px; font-size:.75rem; }

/* ---------- Auth (centred card) ---------- */
/* Signed-out screens: a card floating on a pale grid with one broad wave across
   the lower half. Shared by sign-in, forgot and reset via layouts/auth.php. */
.auth-body { min-height: 100vh; background: var(--surface); }

.auth-bg { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }

/* Faint graph paper. Drawn with gradients rather than an image so it scales at
   any density and costs no request. */
.auth-grid {
    position: absolute; inset: 0;
    background-image:
        repeating-linear-gradient(to right,  rgba(29,61,90,.055) 0 1px, transparent 1px 56px),
        repeating-linear-gradient(to bottom, rgba(29,61,90,.055) 0 1px, transparent 1px 56px);
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.35) 62%, transparent 88%);
            mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.35) 62%, transparent 88%);
}

.auth-wave { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* One band, morphed into place by assets/js/auth.js rather than animated
   here: the SVG `d` attribute is not reliably transitionable in CSS, and the
   shape has to land somewhere different on each load. */

/* Content sits above the backdrop. */
.auth-shell { position: relative; z-index: 1; min-height: 100vh; display: flex; flex-direction: column; }

.auth-topbar { padding: 1.6rem 2.2rem 0; }
.auth-topbar img { height: 54px; width: auto; display: block; }

.auth-main { flex: 1; display: flex; flex-direction: column; align-items: center;
             justify-content: center; padding: 2rem 1.25rem 2.5rem; }

.auth-card {
    width: 100%; max-width: 584px; background: var(--surface);
    border-radius: 16px; padding: 35px; box-shadow: 0 10px 36px rgba(13,27,42,.20);
}
/* One inner column, itself centred, so the heading, the sub-line and the
   fields share an axis at every width. Doing this with auto margins on each
   child did not hold: the .auth-heading rule comes later in this file and
   reset margin-left, which left the heading flush against the content box. */
.auth-cardinner { max-width: 420px; margin-left: auto; margin-right: auto; }

/* The brand ICON, reversed out of a filled navy tile with flat edges. The
   monogram is inlined from assets/img/logo.svg with its white backdrop path
   dropped, so `color` here drives it — the PNG could not be used, it carries
   a baked-in white square. */
.auth-cardhead { text-align: center; margin-bottom: 1.35rem; }
.auth-cardicon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 72px; height: 72px; padding: 13px;
    background: var(--navy); border-radius: 0;          /* flat edges, as asked */
    color: #fff;
}
.auth-cardicon svg { width: 100%; height: 100%; display: block; }

.auth-heading { font-size: 2rem; font-weight: 600; line-height: 1.22;
                text-align: center; color: var(--navy); margin: 0 0 .35rem; }
.auth-sub { text-align: center; color: var(--muted); font-size: .92rem; margin: 0 0 1.5rem; }

.auth-card .form-group { margin-bottom: 1rem; }
.auth-card label { display: block; font-size: .86rem; font-weight: 600; color: var(--navy); margin-bottom: .35rem; }
.auth-card input[type="email"], .auth-card input[type="password"], .auth-card input[type="text"] {
    width: 100%; height: 48px; padding: 0 .85rem; font-size: .95rem;
    border: 1px solid var(--border); border-radius: 4px; background: var(--surface); color: var(--text);
}
.auth-card input:focus { outline: 2px solid var(--primary); outline-offset: -2px; border-color: var(--primary); }
.auth-card .btn-block { height: 48px; font-size: 1rem; font-weight: 600; border-radius: 4px; }
.auth-card .alert { margin-bottom: 1rem; }

/* Remember me / forgot, on one line under the password. */
.auth-meta { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin: .1rem 0 1.15rem; }
.auth-meta label { margin: 0; font-weight: 500; cursor: pointer; font-size: .88rem; }
.auth-meta a { font-size: .88rem; }

.auth-alt { text-align: center; margin: 1.1rem 0 0; font-size: .9rem; }

/* Light type: the footer sits over the wave, where the old muted grey was
   effectively invisible. The shadow keeps it legible on the pale side of the
   diagonal too. */
.auth-foot { margin: 1.6rem 0 0; text-align: center; font-size: .8rem;
             color: #fff; line-height: 1.7;
             text-shadow: 0 1px 3px rgba(13,27,42,.55); }
.auth-foot span { opacity: .85; }

@media (prefers-reduced-motion: reduce) {
    .auth-wave-band { animation: none; opacity: 1; transform: none; }
}

/* Phones: the card takes the width, keeping the gutter the rest of the portal
   uses, and the heading steps down so it does not wrap to three lines. */
@media (max-width: 640px) {
    /* The top-left mark is desktop only; the card already carries the icon. */
    .auth-topbar { display: none; }
    /* On a narrow screen the diagonal is squeezed steep, which left the
       footer straddling it — half the line landed on white and vanished.
       Stretching the band up from the bottom puts the whole footer on the
       filled side. Applied to the svg, not the path, so it does not fight
       the entry animation. */
    /* The wave path closes along the bottom of its viewBox, so the very
       foot of the screen is always inside the fill. Pinning the footer
       there is what makes light type safe on a phone, where the squeezed
       diagonal otherwise cuts straight through the line. */
    .auth-foot { position: fixed; left: 0; right: 0; bottom: .55rem; margin: 0; padding: 0 1rem; }
    .auth-main { padding-bottom: 5.25rem; }
    .auth-main { padding: 1.5rem 1rem 2rem; justify-content: flex-start; }
    .auth-card { padding: 24px 20px; border-radius: 14px; max-width: none; }
    .auth-cardicon { width: 60px; height: 60px; padding: 11px; }
    .auth-heading { font-size: 1.5rem; }
    .auth-sub { font-size: .88rem; margin-bottom: 1.25rem; }
    .auth-meta { flex-wrap: wrap; gap: .5rem; }
}

/* ---------- Error screens ---------- */
.error-screen { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 2rem; }
.error-card { text-align: center; background: var(--surface); padding: 3rem; border-radius: var(--radius); box-shadow: var(--shadow-lg); max-width: 440px; }
.error-code { font-size: 4.5rem; font-weight: 800; color: var(--primary); line-height: 1; }

/* ---------- Congratulations banner ---------- */
.congrats {
    background: linear-gradient(135deg, #16a34a, #22c55e); color: #fff;
    padding: 1.25rem 1.5rem; border-radius: var(--radius); margin-bottom: 1.25rem;
    display: flex; align-items: center; gap: 1rem; box-shadow: var(--shadow-lg);
}
.congrats .emoji { font-size: 2rem; }

/* ---------- Definition list (detail views) ---------- */
.dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); gap: .35rem 1.5rem; }
.dl-row { display: flex; flex-direction: column; padding: .5rem 0; border-bottom: 1px dashed var(--border); }
.dl-row dt { font-size: .72rem; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); font-weight: 600; margin: 0; }
.dl-row dd { margin: .15rem 0 0; font-size: .9rem; color: var(--text); }

/* ---------- Pagination ---------- */
.pagination { display: flex; align-items: center; gap: .75rem; justify-content: flex-end; padding: 1rem 1.25rem; }
.page-info { font-size: .8rem; color: var(--muted); }

/* ---------- Attachments ---------- */
.attach-list { display:flex; flex-direction:column; gap:.4rem; }
.attach-item { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap; padding:.35rem .5rem; border:1px solid var(--border); border-radius:6px; background:var(--bg); }
.attach-item a { display:inline-flex; align-items:center; gap:.35rem; font-weight:600; }
.attach-remove { display:inline-flex; align-items:center; gap:.3rem; font-size:.8rem; color:var(--danger); margin-left:auto; cursor:pointer; }
.attach-chip { display:inline-flex; align-items:center; gap:.35rem; padding:.3rem .6rem; margin:.15rem .3rem .15rem 0; border:1px solid var(--border); border-radius:6px; background:var(--bg); font-size:.85rem; }

/* ---------- Candidate Tracker: column manager + filters ---------- */
.col-mgr { border-bottom:1px solid var(--border); background:var(--bg); }
.col-mgr-head { margin-bottom:.5rem; }
.col-list { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:.35rem .75rem; }
.col-item { display:flex; align-items:center; justify-content:space-between; gap:.5rem; padding:.3rem .5rem; border:1px solid var(--border); border-radius:6px; background:var(--card,#fff); }
.col-item label { display:flex; align-items:center; gap:.4rem; margin:0; cursor:pointer; }
.col-move { display:inline-flex; gap:.2rem; }
.th-sort { color:inherit; text-decoration:none; white-space:nowrap; }
.th-sort:hover { text-decoration:underline; }
/* Sortable header on an ordinary list: let a long label wrap, as a plain <th>
   would, so clickable headers do not push the table into a sideways scroll.
   The arrow is bound to the last word with a non-breaking space. */
.th-sort.th-sort-wrap { white-space:normal; }
/* The inline Candidate Tracker sorts in the browser, so its headers are
   buttons rather than links — they need the affordances an <a> gets free. */
.th-sort[role="button"] { cursor:pointer; user-select:none; }
.th-sort[role="button"]:focus-visible { outline:2px solid var(--primary); outline-offset:2px; border-radius:3px; }
.tracker-filters { display:none; flex-wrap:wrap; gap:.6rem .85rem; align-items:flex-end; padding:.7rem 1.25rem; border-bottom:1px solid var(--border); }
.tracker-filters.open { display:flex; }   /* collapsed by default; toggled via .open (not the [hidden] attr, which display:flex would override) */
.tracker-filters .form-group { margin:0; min-width:130px; }
.tracker-filters label { font-size:.7rem; text-transform:uppercase; letter-spacing:.4px; color:var(--muted); }

/* ---------- Guided tour ---------- */
.tour-backdrop { position:fixed; inset:0; z-index:12000; display:none; background:transparent; }
.tour-backdrop.tour-dim { background:rgba(15,23,42,.55); }
.tour-spot { position:fixed; z-index:12001; border-radius:8px; box-shadow:0 0 0 9999px rgba(15,23,42,.55); pointer-events:none; display:none; transition:top .2s ease,left .2s ease,width .2s ease,height .2s ease; }
.tour-pop { position:fixed; z-index:12002; width:min(340px, calc(100vw - 32px)); background:var(--surface); color:var(--text); border-radius:10px; box-shadow:0 14px 44px rgba(0,0,0,.28); padding:16px 18px; display:none; }
.tour-pop.tour-center { top:50% !important; left:50% !important; transform:translate(-50%,-50%); }
.tour-title { font-weight:700; font-size:1.05rem; margin-bottom:.4rem; color:var(--navy); }
.tour-body { font-size:.9rem; line-height:1.55; color:var(--muted); }
.tour-foot { display:flex; align-items:center; justify-content:space-between; margin-top:1.1rem; gap:.6rem; }
.tour-nav { display:flex; align-items:center; gap:.5rem; }
.tour-count { font-size:.78rem; color:var(--muted); margin-right:.25rem; }
.tour-skip { background:none; border:none; color:var(--muted); cursor:pointer; font-size:.85rem; padding:.3rem; }
.tour-back, .tour-next { border:1px solid var(--border); background:var(--surface); color:var(--text); border-radius:6px; padding:.4rem .85rem; cursor:pointer; font-size:.85rem; }
.tour-next { background:var(--primary); color:#fff; border-color:var(--primary); }
@media (prefers-reduced-motion: reduce) { .tour-spot { transition:none; } }

/* ---------- Attendance calendar ---------- */
.att-summary { display:grid; grid-template-columns:repeat(auto-fit,minmax(96px,1fr)); gap:.6rem; margin-bottom:1rem; }
.att-tile { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:.6rem .5rem; text-align:center; }
.att-tile .att-num { display:block; font-size:1.15rem; font-weight:700; color:var(--navy); }
.att-tile .att-lbl { display:block; font-size:.72rem; color:var(--muted); text-transform:uppercase; letter-spacing:.4px; }

.att-cal-wrap { overflow-x:auto; }
.att-cal { width:100%; border-collapse:separate; border-spacing:4px; table-layout:fixed; }
.att-cal th { font-size:.72rem; text-transform:uppercase; letter-spacing:.4px; color:var(--muted); padding:.3rem 0; text-align:center; font-weight:600; }
.att-cal td { height:64px; vertical-align:top; border-radius:8px; border:1px solid var(--border); padding:5px 6px; position:relative; }
.att-cal .cal-day { font-size:.8rem; font-weight:700; }
.att-cal .cal-code { position:absolute; bottom:5px; right:7px; font-size:.72rem; font-weight:700; opacity:.85; }
.att-cal td.cal-empty { border:none; background:transparent; }

/* Status colours (light + dark aware via mix with surface) */
.cal-present    { background:#dcfce7; border-color:#86efac; color:#14532d; }
.cal-half       { background:#fef9c3; border-color:#fde047; color:#713f12; }
.cal-absent     { background:#fee2e2; border-color:#fca5a5; color:#7f1d1d; }
.cal-leave      { background:#dbeafe; border-color:#93c5fd; color:#1e3a8a; }
.cal-weekoff    { background:#f1f5f9; border-color:#e2e8f0; color:#64748b; }
.cal-altoff     { background:#e2e8f0; border-color:#cbd5e1; color:#475569; }
.cal-holiday    { background:#ede9fe; border-color:#c4b5fd; color:#5b21b6; }
.cal-inprogress { background:#ecfdf5; border-color:#6ee7b7; color:#065f46; border-style:dashed; }
.cal-untracked, .cal-future { background:var(--surface); color:var(--muted); }

.att-cal td[data-date] { cursor:pointer; }
.att-cal .cal-ovr { position:absolute; top:2px; right:5px; font-size:.62rem; opacity:.75; }
.att-cal td.is-ovr { box-shadow: inset 0 0 0 2px rgba(37,99,235,.45); }

/* Discrepancy markers on a calendar day */
.att-cal .cal-disc { position:absolute; top:2px; left:5px; font-size:.62rem; font-weight:800; line-height:1;
    color:#b45309; width:13px; height:13px; border-radius:50%; text-align:center;
    background:rgba(245,158,11,.22); border:1px solid rgba(245,158,11,.6); }
.att-cal .cal-disc.disc-ok { color:#047857; background:rgba(16,185,129,.2); border-color:rgba(16,185,129,.6); }
.att-cal .cal-disc.disc-no { color:#b91c1c; background:rgba(239,68,68,.18); border-color:rgba(239,68,68,.55); }
.att-cal td.is-disc.disc-open { box-shadow: inset 0 0 0 2px rgba(245,158,11,.55); }
.att-disc-btn { width:100%; border:1px solid var(--border); background:var(--bg); color:var(--text); border-radius:6px;
    padding:.4rem .6rem; font-size:.82rem; cursor:pointer; }
.att-disc-btn:hover { border-color:var(--warning, #f59e0b); color:var(--warning, #b45309); }

/* Lightweight modal (discrepancy raise / reject) */
.att-modal { position:fixed; inset:0; z-index:14000; display:flex; align-items:center; justify-content:center;
    background:rgba(15,23,42,.5); padding:1rem; }
.att-modal[hidden] { display:none; }
.att-modal-card { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:12px;
    box-shadow:0 20px 60px rgba(0,0,0,.35); width:min(440px, 100%); max-height:90vh; overflow:auto; padding:1.1rem 1.2rem; }
.att-modal-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:.8rem; }
.att-modal-head strong { color:var(--navy); font-size:1.05rem; }
.att-modal-x { background:none; border:none; font-size:1.4rem; line-height:1; cursor:pointer; color:var(--muted); }
body.att-modal-open { overflow:hidden; }

/* Day-detail balloon popover */
.att-pop { position:fixed; z-index:13000; width:min(260px, calc(100vw - 24px)); background:var(--surface); color:var(--text);
    border:1px solid var(--border); border-radius:10px; box-shadow:0 12px 40px rgba(0,0,0,.22); padding:12px 14px; font-size:.85rem; }
.att-pop-head { display:flex; align-items:center; justify-content:space-between; gap:.5rem; margin-bottom:.5rem; }
.att-pop-head strong { color:var(--navy); }
.badge-cal { font-size:.72rem; font-weight:700; padding:.15rem .5rem; border-radius:999px; border:1px solid var(--border); }
.att-pop-rows { display:flex; flex-direction:column; gap:.2rem; }
.att-pop-row { display:flex; justify-content:space-between; gap:1rem; }
.att-pop-row span { color:var(--muted); }
.att-pop-ovr { border-top:1px solid var(--border); margin-top:.6rem; padding-top:.6rem; }
.att-pop-ovr-lbl { font-size:.72rem; text-transform:uppercase; letter-spacing:.4px; color:var(--muted); margin-bottom:.4rem; }
.att-pop-btns { display:flex; flex-wrap:wrap; gap:.35rem; }
.att-ovr-btn { border:1px solid var(--border); background:var(--bg); color:var(--text); border-radius:6px; padding:.3rem .6rem; font-size:.8rem; cursor:pointer; }
.att-ovr-btn:hover { border-color:var(--primary); color:var(--primary); }
.att-ovr-auto { margin-left:auto; }

.att-legend { display:flex; flex-wrap:wrap; gap:.5rem 1rem; margin-top:.9rem; font-size:.8rem; color:var(--muted); }
.att-leg { display:inline-flex; align-items:center; gap:.35rem; }
.att-swatch { width:14px; height:14px; border-radius:4px; border:1px solid var(--border); display:inline-block; }

@media (max-width: 560px) {
    .att-cal td { height:48px; padding:3px 4px; }
    .att-cal .cal-day { font-size:.72rem; }
    .att-cal .cal-code { font-size:.62rem; bottom:3px; right:4px; }
    .att-cal th { font-size:.6rem; }
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .cal-present    { background:#14532d; border-color:#166534; color:#dcfce7; }
    :root:not([data-theme="light"]) .cal-half       { background:#713f12; border-color:#854d0e; color:#fef9c3; }
    :root:not([data-theme="light"]) .cal-absent     { background:#7f1d1d; border-color:#991b1b; color:#fee2e2; }
    :root:not([data-theme="light"]) .cal-leave      { background:#1e3a8a; border-color:#1d4ed8; color:#dbeafe; }
    :root:not([data-theme="light"]) .cal-weekoff    { background:#1e293b; border-color:#334155; color:#94a3b8; }
    :root:not([data-theme="light"]) .cal-altoff     { background:#334155; border-color:#475569; color:#cbd5e1; }
    :root:not([data-theme="light"]) .cal-holiday    { background:#4c1d95; border-color:#6d28d9; color:#ede9fe; }
    :root:not([data-theme="light"]) .cal-inprogress { background:#064e3b; border-color:#059669; color:#d1fae5; }
}

/* ---------- Permissions grid ---------- */
.perm-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:.4rem .9rem; margin-bottom:.5rem; }

/* ---------- History timeline ---------- */
.timeline { list-style:none; margin:0; padding:0; position:relative; }
.timeline-item { position:relative; padding:0 0 1.1rem 1.25rem; border-left:2px solid var(--border); }
.timeline-item:last-child { border-left-color:transparent; }
.timeline-item::before { content:""; position:absolute; left:-6px; top:3px; width:10px; height:10px; border-radius:50%; background:var(--primary); }
.timeline-head { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; margin-bottom:.35rem; }
.timeline-body { display:flex; flex-wrap:wrap; gap:.35rem .9rem; }
.hist-field { font-size:.85rem; }
.hist-k { color:var(--muted); }

/* ---------- Live indicator ---------- */
.live-dot { display:inline-block; width:9px; height:9px; border-radius:50%; background:var(--success); margin-left:.4rem; vertical-align:middle; box-shadow:0 0 0 0 rgba(22,163,74,.6); animation:livePulse 2s infinite; }
@keyframes livePulse { 0%{box-shadow:0 0 0 0 rgba(22,163,74,.5)} 70%{box-shadow:0 0 0 7px rgba(22,163,74,0)} 100%{box-shadow:0 0 0 0 rgba(22,163,74,0)} }

/* A grouping row inside a table — the component list uses it to head each of
   earnings / deductions / employer contributions without a separate table. */
.table td.section-row {
    background: var(--bg); color: var(--navy);
    font-size: .74rem; text-transform: uppercase; letter-spacing: .6px;
    padding-top: .5rem; padding-bottom: .5rem;
}

/* ---------- Section title ---------- */
.section-title { font-size: .8rem; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); font-weight: 700; margin: 1.5rem 0 .75rem; padding-bottom: .4rem; border-bottom: 2px solid var(--border); }
.section-title:first-child { margin-top: 0; }

/* ---------- Backdrop (mobile sidebar) ---------- */
.sidebar-backdrop { display: none; position: fixed; inset: 0; background: rgba(15,23,42,.45); z-index: 35; }
.sidebar-backdrop.show { display: block; }

/* ---------- Collapsible sidebar (desktop) ----------
   Collapsed is the default; the server writes the class onto .app from the session
   so the rail is never painted wide and then snapped shut. Hovering the rail (or
   tabbing into it) slides the labels back out OVER the page: the sidebar is fixed
   and .main keeps its 66px margin, so nothing underneath reflows. */
@media (min-width: 769px) {
    .app.sidebar-collapsed .sidebar { width: 66px; }
    .app.sidebar-collapsed .brand { justify-content: center; padding-left: 0; padding-right: 0; }
    .app.sidebar-collapsed .brand-text { display: none; }
    /* Scoped to .sidebar. Without it these key off .app, which wraps the whole
       page — so collapsing the sidebar silently blanked the labels of anything
       in the main content that reused .nav-link (Masters' category list did). */
    .app.sidebar-collapsed .sidebar .nav-link { justify-content: center; padding-left: 0; padding-right: 0; }
    .app.sidebar-collapsed .sidebar .nav-link span { display: none; }
    .app.sidebar-collapsed .sidebar-foot .nav-link span { display: none; }
    .app.sidebar-collapsed .main { margin-left: 66px; }

    .app.sidebar-collapsed .sidebar:hover,
    .app.sidebar-collapsed .sidebar:focus-within {
        width: var(--sidebar-w);
        box-shadow: 4px 0 18px rgba(13, 27, 42, .28);
    }
    .app.sidebar-collapsed .sidebar:hover .brand,
    .app.sidebar-collapsed .sidebar:focus-within .brand {
        justify-content: flex-start; padding-left: 1.2rem; padding-right: 1.2rem;
    }
    .app.sidebar-collapsed .sidebar:hover .brand-text,
    .app.sidebar-collapsed .sidebar:focus-within .brand-text { display: flex; }
    .app.sidebar-collapsed .sidebar:hover .nav-link,
    .app.sidebar-collapsed .sidebar:focus-within .nav-link {
        justify-content: flex-start;
        padding-left: var(--nav-pad-x, .75rem); padding-right: var(--nav-pad-x, .75rem);
    }
    .app.sidebar-collapsed .sidebar:hover .nav-link span,
    .app.sidebar-collapsed .sidebar:hover .sidebar-foot .nav-link span,
    .app.sidebar-collapsed .sidebar:focus-within .nav-link span,
    .app.sidebar-collapsed .sidebar:focus-within .sidebar-foot .nav-link span { display: block; }
}
@media (prefers-reduced-motion: reduce) {
    .sidebar, .main { transition: none; }
}

/* ---------- Real-time tracker: fixed-height scroll + sticky header ---------- */
.tracker-wrap { overflow: auto; max-height: 460px; }

/* ---------- Candidate Tracker: automatic text size ----------
   A tracker can be shown with four columns or with seventeen, so no single font
   size is right. app.js measures the grid and puts one of these steps on the
   table: full size when the columns fit comfortably, stepping down only as far
   as it must to keep the grid inside the card. Padding tightens with the text,
   because whitespace is what actually costs the width. Step 0 is the normal
   table, so a narrow tracker looks exactly like every other list in the app. */
table.table[data-fit="1"] { font-size: .80rem; }
table.table[data-fit="1"] th, table.table[data-fit="1"] td { padding: .55rem .6rem; }
table.table[data-fit="2"] { font-size: .76rem; }
table.table[data-fit="2"] th, table.table[data-fit="2"] td { padding: .45rem .5rem; }
table.table[data-fit="3"] { font-size: .72rem; }
table.table[data-fit="3"] th, table.table[data-fit="3"] td { padding: .38rem .42rem; }
table.table[data-fit="3"] .badge, table.table[data-fit="3"] .pill { font-size: .66rem; padding: .1rem .32rem; }
table.table[data-fit="3"] .btn { padding: .2rem .3rem; }
/* Below this the text stops being readable, so the grid keeps its size and the
   card scrolls sideways instead — which is the honest outcome when someone asks
   for more columns than the screen can hold. Use the Columns button to trim. */

.tracker-wrap table.table thead th {
    position: sticky; top: 0; z-index: 2;
    background: #eef2f7;              /* opaque so rows don't show through when scrolling */
}

/* ---------- Responsive ---------- */
@media (max-width: 992px) {
    .grid-2 { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
    .sidebar { transform: translateX(-100%); }
    .sidebar.open { transform: translateX(0); }
    .main { margin-left: 0; }
    .menu-toggle { display: inline-flex; }
    .content { padding: 1rem; }
    .user-meta { display: none; }
    .form-group.col-span-2 { grid-column: auto; }
}

/* Rich-text editor (Official Documents) */
.rt-toolbar { display:flex; flex-wrap:wrap; gap:.25rem; border:1px solid var(--border); border-bottom:0; border-radius:8px 8px 0 0; padding:.35rem; background:var(--bg); }
.rt-toolbar button { border:1px solid var(--border); background:var(--surface); color:var(--text); border-radius:5px; min-width:34px; padding:.25rem .55rem; font-size:.85rem; cursor:pointer; line-height:1.2; }
.rt-toolbar button:hover { border-color:var(--primary); color:var(--primary); }
.rt-editor { border:1px solid var(--border); border-radius:0 0 8px 8px; padding:.7rem .9rem; min-height:230px; max-height:460px; background:var(--surface); color:var(--text); font-size:.95rem; line-height:1.6; overflow:auto; }
.rt-editor:focus { outline:2px solid var(--primary); outline-offset:-2px; }
.rt-editor p { margin:0 0 .6rem; }

/* Newsletter — seasonal template gallery + merge tags */
.nl-gallery { display:flex; flex-wrap:wrap; gap:.4rem; align-items:center; border:1px solid var(--border); border-radius:8px; padding:.6rem; background:var(--bg); max-height:190px; overflow:auto; }
.nl-gallery .nl-cat { width:100%; font-size:.7rem; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); margin-top:.3rem; }
.nl-tpl { display:inline-flex; align-items:center; gap:.4rem; border:1px solid var(--border); background:var(--surface); color:var(--text);
    border-radius:999px; padding:.28rem .7rem; font-size:.8rem; cursor:pointer; }
.nl-tpl:hover { border-color:var(--primary); color:var(--primary); }
.nl-tpl.is-on { border-color:var(--primary); background:var(--primary); color:#fff; }
.nl-tpl.is-on .nl-swatch { box-shadow:0 0 0 1px #fff; }
.nl-swatch { width:12px; height:12px; border-radius:50%; display:inline-block; flex:0 0 auto; }
code.nl-tag { cursor:pointer; background:var(--bg); border:1px solid var(--border); border-radius:4px; padding:.05rem .3rem; margin-right:.2rem; }
code.nl-tag:hover { border-color:var(--primary); color:var(--primary); }

/* Newsletter — occasion banner preview */
.nl-banner-box { border:1px solid var(--border); border-radius:8px; overflow:hidden; background:var(--bg); line-height:0; }
.nl-banner-box img { width:100%; height:auto; display:block; }

/* ============================ Team Activity =============================
   A manager reading one team member's Candidate Tracker: the same grid, plus
   a highlight for what is new to THEM and a change trail folded under each row.
   ======================================================================== */

/* The tracker is the whole point of this page rather than a card on a busy
   dashboard, so it is given most of the window instead of a fixed 460px — while
   keeping the sticky header that makes a long scroll readable. */
.tracker-wrap.tracker-tall { max-height: 72vh; }

/* ---- "New to you" --------------------------------------------------------
   Set on the cells rather than the row, so it survives the table's hover rule
   and a manager can still see what they are pointing at. The left edge carries
   the accent: a tint alone is easy to miss once four columns are badges. */
.row-unseen > td { background: #fff8e6; }
.row-unseen:hover > td { background: #fff3d6; }
.row-unseen > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.ma-flag { margin-top: .25rem; display: inline-block; }

/* ---- Legend + the filter recap ------------------------------------------ */
.ma-legend {
    display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
    padding: .55rem 1.1rem; border-bottom: 1px solid var(--border);
    background: var(--bg); color: var(--muted); font-size: .8rem;
}
.ma-key { width: 14px; height: 14px; border-radius: 4px; display: inline-block; flex: 0 0 auto; }
.ma-key-new { background: #fff8e6; box-shadow: inset 3px 0 0 var(--accent), 0 0 0 1px var(--border); }
.ma-legend .ma-filters { display: inline-flex; gap: .3rem; flex-wrap: wrap; margin-left: auto; }

/* ---- The refresh offer ---------------------------------------------------
   Deliberately an offer and not an automatic swap: replacing the rows under a
   manager who has a change trail open loses their place mid-read. */
.ma-bar {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    flex-wrap: wrap; padding: .6rem 1.1rem;
    background: #e8f6fc; border-bottom: 1px solid var(--border); font-size: .85rem;
}

/* ---- The change trail, nested under its row ------------------------------ */
.trail-toggle.is-open { border-color: var(--primary); color: var(--primary); }
.trail-detail > td { background: #f8fafc; padding: 0; }
.trail { padding: .8rem 1.1rem 1rem; }
.trail-head { font-size: .8rem; color: var(--muted); margin-bottom: .6rem; }
.trail-list { list-style: none; margin: 0; padding: 0; }
.trail-item {
    position: relative; padding: .5rem 0 .55rem 1.1rem;
    border-left: 2px solid var(--border);
}
.trail-item::before {
    content: ""; position: absolute; left: -5px; top: .85rem;
    width: 8px; height: 8px; border-radius: 50%; background: var(--border);
}
/* A change made since the viewer last reviewed this member — the reason the row
   above it is highlighted, so it is worth being able to find inside a long trail. */
.trail-item.is-fresh::before { background: var(--accent); }
.trail-item.is-fresh { border-left-color: var(--accent); }
.trail-item:last-child { border-left-color: transparent; }
.trail-meta { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; font-size: .82rem; }
.trail-diff { display: flex; flex-wrap: wrap; gap: .35rem .5rem; margin-top: .35rem; }

/* "Pipeline Stage  Screening → Interview" reads in one glance; the old value is
   struck through so the direction of the change needs no explaining. */
.chg {
    display: inline-flex; align-items: baseline; gap: .3rem; flex-wrap: wrap;
    background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
    padding: .15rem .6rem; font-size: .78rem; max-width: 100%;
}
.chg-k { color: var(--muted); font-weight: 600; }
.chg-was { color: var(--muted); text-decoration: line-through; }
.chg-arrow { color: var(--muted); }
.chg-now { color: var(--text); font-weight: 600; overflow-wrap: anywhere; }

/* [hidden] is display:none only until a rule sets display; .ma-bar's flex would
   win and the refresh offer would stand there permanently. */
.ma-bar[hidden] { display: none; }

/* The legend is a flex row so the key square lines up with the text, which makes
   every text run its own flex item — and the gap then falls INSIDE the sentence.
   One span, one item, one sentence. */
.ma-legend .ma-say { flex: 1 1 auto; }

/* A trail sits in a cell spanning a grid that is usually wider than the screen,
   so left alone it starts under whichever column you happen to be scrolled to and
   its right-hand half is off-screen. Sticking it to the left edge of the scroll
   port keeps the whole trail readable at any horizontal scroll position, and the
   cap gives the change chips a sane line length to wrap at. */
.trail { position: sticky; left: 0; max-width: 62rem; }

/* The card head names the order the grid is in, so it can be read without
   hunting for the arrow in a column that may be scrolled off to the right. */
.ma-sortnote { font-weight: 400; color: var(--muted); margin-left: .35rem; }

/* The reasons an entry needs attention, spelled out at the top of its change
   trail — where there is room for a sentence, unlike the badge on the row. */
.trail-why {
    display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
    background: #fdecec; border: 1px solid #f6c9c9; border-radius: 6px;
    padding: .4rem .6rem; margin-bottom: .55rem;
    font-size: .8rem; color: #b91c1c; font-weight: 600;
}
.trail-why .ic { flex: 0 0 auto; }

/* A set of options as tick-boxes rather than a multi-select, which is easier to
   read and does not hide the unselected half behind a scroll. */
.chk-grid { display: flex; flex-wrap: wrap; gap: .15rem 1.1rem; padding: .25rem 0; }
.chk-grid .checkbox-row { margin: 0; }
/* Same idea as .checkbox-row, named for how it reads at the call site. */
.check { display: inline-flex; align-items: center; gap: .4rem; font-size: .82rem; margin: .1rem 0; cursor: pointer; }
.check input[type="checkbox"],
.check input[type="radio"] {
    width: auto; flex: 0 0 auto; padding: 0; margin: 0;
}
.chk-grid .check { margin: 0; flex: 0 0 auto; }

/* ---- Reviewer notes -------------------------------------------------------
   A manager's memo against one candidate entry, shown inside the row's expanded
   detail. Blue rather than amber or red: it is a conversation between managers,
   not a fault the system found. */
.notes { background: #f3f8fc; border: 1px solid #d7e7f3; border-radius: 6px;
         padding: .6rem .75rem; margin-bottom: .7rem; }
.notes-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
              font-size: .82rem; margin-bottom: .5rem; }
.note-list { list-style: none; margin: 0 0 .55rem; padding: 0; }
.note-item { background: var(--surface); border: 1px solid var(--border);
             border-left: 3px solid var(--primary); border-radius: 5px;
             padding: .45rem .6rem; margin-bottom: .35rem; }
/* A note that has been acted on stops competing for attention but stays readable
   — the record that it was raised is the point of resolving rather than deleting. */
.note-item.is-done { border-left-color: var(--success); opacity: .68; }
.note-item.is-done .note-text { text-decoration: line-through; }
.note-text { font-size: .85rem; color: var(--text); }
.note-meta { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
             margin-top: .3rem; font-size: .74rem; color: var(--muted); }
.note-acts { margin-left: auto; display: inline-flex; gap: .25rem; }
.note-add { display: flex; gap: .4rem; align-items: flex-start; }
.note-add textarea { flex: 1 1 auto; font-size: .85rem; min-height: 2.6rem; }
/* The button must not wrap its label into two lines beside a flexible textarea. */
.note-add .btn { flex: 0 0 auto; white-space: nowrap; align-self: stretch; }

/* A BD lead whose follow-up date has passed and which has not empanelled — the
   one row state in these panels worth colouring, because it is already late. */
.row-overdue > td { background: #fdf3f3; }

/* The "updated just now" stamp on a live card — a brief flash so a silent
   refresh is noticeable without being a distraction. */
[data-live-stamp] { font-size: .72rem; color: var(--muted); font-weight: 400; }
[data-live-stamp].is-fresh { animation: liveflash 1.6s ease-out 1; }
@keyframes liveflash {
    0%   { color: var(--success); }
    100% { color: var(--muted); }
}
@media (prefers-reduced-motion: reduce) { [data-live-stamp].is-fresh { animation: none; } }

/* ===================== PROFILE PICTURE ===================== */
/* The top-bar avatar becomes a frame when there is a photo to put in it.
   object-fit keeps a portrait or a landscape snapshot circular instead of
   squeezing a face sideways, which is what a plain width/height would do. */
.avatar { flex: 0 0 auto; overflow: hidden; }
.avatar-img { background: var(--bg); padding: 0; }
.avatar-img img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }

/* The editor on My Profile: the picture beside its own form, so you can see
   what you already have while you choose what replaces it. */
.photo-edit { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
.photo-preview {
    width: 84px; height: 84px; flex: 0 0 auto;
    border-radius: 50%; object-fit: cover;
    border: 1px solid var(--border); background: var(--bg);
}
.photo-preview-empty {
    display: flex; align-items: center; justify-content: center;
    background: var(--navy); color: #fff; font-size: 1.9rem; font-weight: 600;
}
/* The composed full name is shown, not edited — say so rather than just
   greying it out and leaving people to click at it. */
.form-group input[disabled] { background: var(--bg); color: var(--muted); cursor: not-allowed; }
/* Two buttons in a 340px aside: let them stack rather than break a label in
   half — "Save\nPicture" reads like two controls. */
.photo-edit .form-actions { flex-wrap: wrap; }
.photo-edit .btn { white-space: nowrap; }
/* The default face is drawn, not fetched — one less request per row and
   nothing to 404 if a deploy misses a folder. */
.avatar-img svg { display: block; width: 100%; height: 100%; border-radius: 50%; }
/* A face beside a name in a people list. inline-flex so the pair stays on one
   line and the face keeps its size when the name wraps beside it. */
.name-cell { display: inline-flex; align-items: center; gap: .5rem; }
.name-cell .avatar { font-size: .8rem; }
/* The face costs the name cell ~34px, which was enough to wrap "Preetu Sachan"
   onto two lines in a table with width to spare. Let the column claim it. */
.name-cell strong { white-space: nowrap; }

/* ==================== FILTER BARS ON A PHONE ====================
   The bar collapses behind a button below 768px and is untouched above it.
   Both rules live inside the media query on purpose: the .is-collapsed class
   stays on the element at every width, so dragging a window across the
   breakpoint — or turning a phone — needs no JavaScript to put it right. */
.filters-toggle { display: none; }

@media (max-width: 768px) {
    .filters-toggle {
        display: inline-flex; align-items: center; gap: .45rem;
        margin: 0 0 .8rem; padding: .5rem .8rem;
        font: inherit; font-size: .85rem; font-weight: 600;
        color: var(--text); background: var(--surface);
        border: 1px solid var(--border); border-radius: var(--radius);
        cursor: pointer;
    }
    .filters-toggle:hover { background: var(--bg); }
    .filters-toggle .ft-caret { transition: transform .18s ease; opacity: .65; }
    .filters-toggle.is-open .ft-caret { transform: rotate(180deg); }

    /* The count of applied filters. Without it a collapsed bar hides the
       reason a list looks short — the one thing collapsing must not do. */
    .filters-toggle .ft-count { display: none; }
    .filters-toggle.has-active .ft-count {
        display: inline-flex; align-items: center; justify-content: center;
        min-width: 18px; height: 18px; padding: 0 5px;
        font-size: .7rem; font-weight: 700; line-height: 1;
        color: #fff; background: var(--primary); border-radius: 9px;
    }
    .filters-toggle.has-active { border-color: var(--primary); }

    /* Beats .filters{display:flex} — a bare [hidden] would not. */
    .filters.is-collapsed { display: none !important; }

    /* Expanded on a phone: one control per row, full width, so the grid is
       readable rather than three cramped columns. */
    .filters:not(.filters-row) .form-group { min-width: 0; width: 100%; }
    .filters:not(.filters-row) > .btn { width: 100%; justify-content: center; }
}

@media (max-width: 768px) and (prefers-reduced-motion: reduce) {
    .filters-toggle .ft-caret { transition: none; }
}

/* ======================= NOTIFICATIONS — THE BELL ========================= */
/* The panel hangs off the bell the way the user menu hangs off the avatar, so
   it inherits .dropdown's positioning, surface and open/closed behaviour. Only
   the width and the innards are new. */
.notif-menu { position: relative; }
.notif-panel {
    width: 352px; max-width: calc(100vw - 24px);
    padding: 0; overflow: hidden;
}
.notif-head {
    display: flex; align-items: center; gap: .6rem;
    padding: .65rem .85rem; border-bottom: 1px solid var(--border);
}
.notif-head strong {
    font-size: .74rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: var(--navy); margin-right: auto;
}
.notif-head form { margin: 0; }
.notif-markall {
    font: inherit; font-size: .74rem; font-weight: 600;
    color: var(--primary); background: none; border: 0; padding: 0; cursor: pointer;
}
.notif-markall:hover { text-decoration: underline; }

/* Eight rows is about 380px; past that it scrolls rather than running off the
   bottom of a laptop screen. */
.notif-scroll { max-height: 60vh; overflow-y: auto; }

/* Each row is a form, because marking something read is a state change and a
   GET link would let a prefetcher clear the badge. The form must not look like
   one — it is a row. */
.notif-item { margin: 0; display: block; border-bottom: 1px solid var(--border); }
.notif-item:last-child { border-bottom: 0; }
.notif-item > button {
    display: flex; gap: .6rem; align-items: flex-start; width: 100%;
    padding: .7rem .85rem; text-align: left; cursor: pointer;
    font: inherit; color: inherit; background: none; border: 0;
}
.notif-item > button:hover { background: var(--bg); }
.notif-item > button:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.notif-item.is-unread > button { background: #f0f9ff; }
.notif-item.is-unread > button:hover { background: #e0f2fe; }

.notif-dot {
    width: 7px; height: 7px; border-radius: 50%; margin-top: .42rem;
    flex: 0 0 auto; background: var(--primary);
}
.notif-item:not(.is-unread) .notif-dot { background: transparent; box-shadow: inset 0 0 0 1px var(--border); }
.notif-dot.tone-info    { background: var(--info); }
.notif-dot.tone-success { background: var(--success); }
.notif-dot.tone-warning { background: var(--warning); }
.notif-dot.tone-danger  { background: var(--danger); }
.notif-item:not(.is-unread) .notif-dot.tone-info,
.notif-item:not(.is-unread) .notif-dot.tone-success,
.notif-item:not(.is-unread) .notif-dot.tone-warning,
.notif-item:not(.is-unread) .notif-dot.tone-danger { background: transparent; }

.notif-txt { display: flex; flex-direction: column; gap: .1rem; min-width: 0; flex: 1 1 auto; }
.notif-t { font-size: .82rem; font-weight: 600; color: var(--navy); line-height: 1.35; }
.notif-b { font-size: .76rem; color: var(--muted); line-height: 1.45; }
.notif-time { font-size: .68rem; color: #94a3b8; margin-top: .12rem; }

.notif-empty {
    margin: 0; padding: 1.6rem 1rem; text-align: center;
    font-size: .8rem; color: var(--muted); line-height: 1.6;
}
.notif-all {
    display: block; padding: .6rem; text-align: center;
    font-size: .78rem; font-weight: 600; color: var(--primary);
    border-top: 1px solid var(--border);
}
.notif-all:hover { background: var(--bg); }

/* ---- The full list page: the same row, given room to breathe ---- */
.notif-list .notif-item > button { padding: .95rem 1.1rem; gap: .8rem; }
.notif-list .notif-t { font-size: .9rem; }
.notif-list .notif-b { font-size: .82rem; }
.notif-list .notif-time { font-size: .72rem; }
.notif-new {
    flex: 0 0 auto; align-self: flex-start;
    font-size: .64rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: #fff; background: var(--primary); border-radius: 20px; padding: .15rem .5rem;
}

@media (max-width: 768px) {
    /* Anchored to the right edge of the header rather than to the bell, or a
       352px panel hanging off an icon near the corner runs off the screen. */
    .notif-panel { position: fixed; left: 8px; right: 8px; top: 56px; width: auto; max-width: none; }
    .notif-scroll { max-height: 65vh; }
}

/* ========================= BIRTHDAY BANNER ==============================
   The same geometry as .congrats so it sits in the page identically — only
   the colour differs, and that difference is the point. Green below means a
   candidate joined a client; a birthday in that same green would read as the
   same kind of news at a glance. Amber to rose, borrowing the --accent amber
   the palette already has. */
.bday-banner {
    background: #f59e0b;                                   /* fallback */
    background: linear-gradient(135deg, #f59e0b, #e05d7a);
    color: #fff;
    padding: 1.25rem 1.5rem;
    border-radius: var(--radius);
    margin-bottom: 1.25rem;
    display: flex; align-items: center; gap: 1rem;
    box-shadow: var(--shadow-lg);
}
.bday-banner .emoji { font-size: 2rem; line-height: 1; flex: 0 0 auto; }
.bday-banner strong { font-weight: 700; }
.bday-banner .bday-sub { display: block; font-size: .86rem; opacity: .92; margin-top: .12rem; }

/* ---- Upcoming birthdays: a list, not a table. Three columns that stay in
   their lanes — face, name over how-soon, and the date pinned right so the
   dates line up down the card however long the names are. */
/* The card body is .tight (padding:0) so the row dividers can run the full
   width of the card. That means each ROW carries the inset instead — 1.25rem,
   the same as .card-head, so the first name lines up under "Upcoming
   Birthdays" and the date under "next 60 days" rather than touching the border. */
.bday-list { list-style: none; margin: 0; padding: .4rem 0; }
.bday-list li {
    display: flex; align-items: center; gap: .7rem;
    padding: .55rem 1.25rem; border-bottom: 1px solid var(--border);
}
.bday-list li:last-child { border-bottom: 0; }
.bday-who { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; flex: 1 1 auto; }
.bday-who strong { font-size: .87rem; color: var(--navy); }
.bday-who small { font-size: .72rem; color: var(--muted); }
.bday-date {
    flex: 0 0 auto; font-size: .8rem; font-weight: 600; color: var(--text);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ====================== BIRTHDAY WISHES ======================
   Shares the inset of .bday-list so the two birthday cards line up with each
   other and with every other card head on the page. */
.wish-list { list-style: none; margin: 0; padding: .4rem 0; }
.wish-list li {
    display: flex; align-items: flex-start; gap: .7rem;
    padding: .75rem 1.25rem; border-bottom: 1px solid var(--border);
}
.wish-list li:last-child { border-bottom: 0; }
.wish-body { flex: 1 1 auto; min-width: 0; }
.wish-body > strong,
.wish-body label > strong { font-size: .87rem; color: var(--navy); }
.wish-body > p {
    margin: .15rem 0 0; font-size: .86rem; line-height: 1.5; color: var(--text);
    overflow-wrap: anywhere;      /* a pasted URL must not widen the card */
}
.wish-body label { display: flex; align-items: center; gap: .5rem; margin-bottom: .3rem; }
.wish-sent { font-size: .7rem; font-weight: 600; color: var(--success); }
.wish-body textarea {
    width: 100%; resize: vertical; min-height: 46px;
    font: inherit; font-size: .85rem;
}
.wish-actions { display: flex; gap: .4rem; margin-top: .45rem; flex-wrap: wrap; }

/* ============ BIRTHDAY PANELS, AND WHAT ARRIVES WHILE YOU WATCH ========= */
/* ---- The banner as a toggle -------------------------------------------
   It is a real <button> now, so the whole coloured bar answers to a click,
   to Tab and to Enter. A button brings its own chrome with it — the reset
   below is what makes it look like the banner it replaced rather than like
   a very large button. */
.bday-banner.is-toggle {
    width: 100%;
    font: inherit;
    text-align: left;
    border: 0;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    transition: box-shadow .15s, filter .15s;
}
.bday-banner.is-toggle:hover { filter: brightness(1.04); }
.bday-banner.is-toggle:focus-visible {
    outline: 3px solid rgba(255, 255, 255, .85);
    outline-offset: -3px;
}
.bday-text { flex: 1 1 auto; min-width: 0; }

/* How many wishes are waiting, on the banner itself — so a wish arriving
   while the panel is folded away still announces itself. */
.bday-count {
    flex: 0 0 auto;
    min-width: 1.5rem; padding: .1rem .45rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, .25);
    color: #fff; font-size: .78rem; font-weight: 700; text-align: center;
}
.bday-caret { flex: 0 0 auto; transition: transform .18s ease; opacity: .9; }
.bday-banner.is-toggle.is-open .bday-caret { transform: rotate(180deg); }

/* The banner and its panel are one object: no gap between them, and the
   corners that meet are square so they read as joined rather than stacked. */
.bday-group { margin-bottom: 1.25rem; }
.bday-group .bday-banner { margin-bottom: 0; }
.bday-group .bday-banner.is-open { border-radius: var(--radius) var(--radius) 0 0; }
.bday-panel .card {
    border-top-left-radius: 0; border-top-right-radius: 0;
    border-top: 0;
}
.bday-panel.is-collapsed { display: none; }

.wish-empty {
    margin: 0; padding: 1rem 1.25rem;
    font-size: .86rem; color: var(--muted);
}

/* A wish that arrived while you were looking at the page. The tint fades out
   on its own — it marks what is new without leaving the card permanently
   striped once you have read it. */
@keyframes wishArrive {
    from { background: #fff5e6; transform: translateY(-4px); }
    to   { background: transparent; transform: none; }
}
.wish-list li.is-new { animation: wishArrive 2.2s ease-out; }

/* What the compose box says back after an in-page send. */
.wish-say { font-size: .78rem; align-self: center; color: var(--muted); }
.wish-say.is-ok  { color: var(--success); }
.wish-say.is-err { color: var(--danger); }

/* The bell's badge when a notification has just landed. Two beats and it
   stops: enough to catch the eye of somebody already on the page, not a
   thing that pulses at them all afternoon. */
@keyframes bellPop {
    0%, 100% { transform: scale(1); }
    30%      { transform: scale(1.45); }
    60%      { transform: scale(1); }
}
.badge-dot.is-new { animation: bellPop .55s ease-out 2; }

@media (prefers-reduced-motion: reduce) {
    .wish-list li.is-new,
    .badge-dot.is-new { animation: none; }
    .bday-caret { transition: none; }
}

/* [hidden] is display:none only until a rule sets display, and .badge-dot sets
   flex. Same trap as .ma-bar and .filters above. */
.badge-dot[hidden],
.bday-count[hidden] { display: none; }

/* When a wish was sent. Beside the name, quiet — the message is the thing,
   the time is what you glance at. */
.wish-when { font-size: .72rem; color: var(--muted); margin-left: .45rem; }

/* "Edited" — the fact, stated once and quietly, the way a messenger does
   it. Lighter than the time beside it: it is a footnote to the message,
   not news. */
.wish-edited {
    font-size: .68rem; color: var(--muted); margin-left: .4rem;
    font-style: italic; opacity: .85;
}

/* ---- The Super Admin's message log --------------------------------------
   The message column carries real prose, so it gets room to breathe and a
   ceiling on how wide it grows; the rest of the row stays scannable. */
.wish-log-msg { min-width: 260px; max-width: 460px; overflow-wrap: anywhere; }
.wish-log-was,
.wish-log-gone {
    display: block; margin-top: .3rem;
    font-size: .78rem; color: var(--muted);
}
.wish-log-was { padding-left: .6rem; border-left: 2px solid var(--border); }
.wish-log-gone { text-decoration: line-through; }

/* ========================= THE CHAT DOCK ================================
   Bottom-right, above everything, on every page. Open on desktop and shut on
   mobile — decided by the server so it does not flash open and collapse on
   each page load, the same way the birthday panels work.

   Layering: below the sidebar drawer (40) and its backdrop (35) so the mobile
   menu covers the dock rather than fighting it, and below the header
   dropdowns (50) so the bell panel opens over it. Above the sticky topbar
   (30), which it never overlaps anyway. */
.chat-dock {
    position: fixed; right: 18px; bottom: 0; z-index: 34;
    /* A ROW, bottom-aligned: private threads to the left, the main panel on
       the right. They used to be stacked above the roster, which pushed the
       whole dock up the screen as conversations opened. */
    display: flex; flex-direction: row; align-items: flex-end; gap: .4rem;
    font-size: .85rem;
}
.chat-main { display: flex; flex-direction: column; align-items: flex-end; }

/* ---- the tab you click ---- */
.chat-tab {
    display: flex; align-items: center; gap: .5rem;
    background: var(--navy); color: #fff;
    border: 0; border-radius: 10px 10px 0 0;
    padding: .55rem .9rem; cursor: pointer;
    font: inherit; font-weight: 600; font-size: .82rem;
    box-shadow: 0 -2px 12px rgba(19, 41, 75, .18);
}
.chat-tab:hover { filter: brightness(1.12); }
.chat-tab:focus-visible { outline: 3px solid var(--primary); outline-offset: -3px; }
.chat-tab-count {
    min-width: 1.35rem; padding: .05rem .4rem; border-radius: 999px;
    background: rgba(255, 255, 255, .22); font-size: .72rem; text-align: center;
}
.chat-caret { display: flex; transition: transform .18s ease; }
.chat-dock.is-open .chat-caret { transform: rotate(180deg); }

/* ---- the panel ----
   Which way it STARTS is decided here, in CSS, not by the server and not by
   script on load: open on desktop, shut on a phone. A media query is the only
   thing that knows the viewport at first paint, so this is the one way the
   dock cannot flash open and collapse on every page. A deliberate click then
   wins at any width, through .is-open / .is-shut below.

   display:none rather than a height animation: the roster is a live list, and
   animating a box whose contents change under you looks broken more often
   than it looks smooth. */
.chat-body {
    display: flex;
    width: 460px; max-width: calc(100vw - 36px);
    height: 340px; max-height: calc(100vh - 140px);
    background: var(--surface);
    border: 1px solid var(--border); border-bottom: 0;
    border-radius: 10px 0 0 0;
    box-shadow: 0 -6px 28px rgba(19, 41, 75, .16);
    overflow: hidden;
}
.chat-dock.is-open .chat-body { display: flex; }
.chat-dock.is-shut .chat-body { display: none; }

.chat-room { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.chat-room-head,
.chat-roster-head {
    display: flex; align-items: baseline; gap: .5rem;
    padding: .5rem .75rem; border-bottom: 1px solid var(--border);
    background: var(--bg, #f8fafc);
}
.chat-room-head strong,
.chat-roster-head strong { font-size: .78rem; color: var(--navy); }
.chat-room-head small { font-size: .68rem; }
.chat-room-body { flex: 1 1 auto; overflow-y: auto; padding: .6rem .75rem; }
.chat-soon { margin: 1.5rem 0 0; text-align: center; color: var(--muted); font-size: .8rem; }
.chat-soon small { font-size: .72rem; }

/* ---- the roster ---- */
.chat-roster {
    flex: 0 0 165px; display: flex; flex-direction: column;
    border-left: 1px solid var(--border); min-width: 0;
}
.chat-roster-head { justify-content: space-between; }
.chat-busy {
    font: inherit; font-size: .68rem; font-weight: 600;
    background: none; border: 1px solid var(--border); border-radius: 999px;
    padding: .1rem .45rem; color: var(--muted); cursor: pointer;
}
.chat-busy.is-on { background: #fee2e2; border-color: #fecaca; color: #b91c1c; }
.chat-busy:disabled { opacity: .5; cursor: default; }

.chat-people { list-style: none; margin: 0; padding: .25rem 0; overflow-y: auto; flex: 1 1 auto; }
.chat-people li {
    display: flex; align-items: center; gap: .45rem;
    padding: .3rem .75rem; min-width: 0;
}
/* Watchable, not writable — the Super Admin sees the whole office here, and
   some of them have not been given the dock. */
.chat-people li[data-no-chat] { opacity: .62; cursor: default; }
.chat-who { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.chat-who strong {
    font-size: .78rem; color: var(--navy); font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-who small { font-size: .66rem; color: var(--muted); }
.chat-none { padding: .75rem; font-size: .74rem; color: var(--muted); }

/* The dots. Five states, and only Busy is ever chosen by the person — the rest
   are worked out from when they were last seen. */
.chat-dot {
    flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%;
    background: #cbd5e1; box-shadow: 0 0 0 2px var(--surface);
}
.chat-dot.dot-online  { background: #16a34a; }
.chat-dot.dot-busy    { background: #dc2626; }
.chat-dot.dot-idle    { background: #f59e0b; }
.chat-dot.dot-away    { background: #f97316; }
.chat-dot.dot-offline { background: #cbd5e1; }

/* ---- phones ----
   Full width, and shorter, because a 340px panel over a 640px screen is most
   of the page. */
@media (max-width: 640px) {
    .chat-dock { right: 0; left: 0; align-items: stretch; }
    .chat-tab { border-radius: 0; justify-content: center; }
    .chat-body {
        display: none;                    /* shut by default on a phone */
        width: 100%; max-width: none; height: 300px;
        border-radius: 0; border-left: 0; border-right: 0;
    }
    .chat-dock.is-open .chat-body { display: flex; }
    /* The roster over the room, not beside it — 165px of names next to
       260px of messages is neither. */
    .chat-body { flex-direction: column-reverse; }
    .chat-roster { flex: 0 0 auto; min-height: 0; max-height: 130px; border-left: 0; border-top: 1px solid var(--border); }
    /* A name is the only way into a private chat, so the list has to stay
       reachable however many people are in it. */
    .chat-people { overflow-y: auto; }
    .chat-people { display: flex; flex-wrap: wrap; gap: .1rem .5rem; }
    .chat-people li { padding: .25rem .5rem; }
    .chat-who small { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .chat-caret { transition: none; }
}

/* ---- the room's contents -------------------------------------------------
   Activity entries sit quieter than messages on purpose: the room is mostly
   the portal talking, and if a candidate being added shouts as loudly as a
   colleague saying good morning, people stop reading both. */
.chat-empty { margin: 1.5rem 0 0; text-align: center; color: var(--muted); font-size: .8rem; }
.chat-empty small { font-size: .72rem; }

.chat-ev { display: flex; gap: .45rem; padding: .25rem 0; align-items: baseline; }
.chat-ev-dot {
    flex: 0 0 auto; width: 5px; height: 5px; border-radius: 50%;
    background: var(--border); margin-top: .35rem;
}
.chat-ev-txt { font-size: .76rem; color: var(--muted); line-height: 1.45; min-width: 0; }
.chat-ev-txt strong { color: var(--text); font-weight: 600; }
.chat-ev-txt a { color: inherit; }
.chat-ev-txt a:hover strong { color: var(--primary); }

.chat-msg { padding: .3rem 0 .35rem; }
.chat-msg-head { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; }
.chat-msg-head strong { font-size: .78rem; color: var(--navy); }
.chat-msg.is-mine .chat-msg-head strong { color: var(--primary-600); }
.chat-msg p {
    margin: .05rem 0 0; font-size: .82rem; line-height: 1.45; color: var(--text);
    overflow-wrap: anywhere;
}
/* The time sat flush against the end of the sentence. Give it room, hold it
   on one line, and set it apart with a middot so it reads as an aside rather
   than as the last word of the message. */
.chat-when {
    font-size: .66rem; color: var(--muted); white-space: nowrap;
    margin-left: .45rem;
}
.chat-ev-txt .chat-when:before,
.chat-msg-head .chat-when:before { content: '\00b7'; margin-right: .4rem; opacity: .65; }
/* An @mention is a private message in a public place — it should look like one
   to the two people who can see it, so nobody replies in the open by mistake. */
.chat-only {
    font-size: .62rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    color: #b45309; background: #fef6e7; border-radius: 999px; padding: .05rem .35rem;
}

/* ---- the composer ---- */
.chat-say {
    display: flex; align-items: center; gap: .35rem; position: relative;
    padding: .45rem .6rem; border-top: 1px solid var(--border); background: var(--bg);
}
.chat-say input[data-chat-input] {
    flex: 1 1 auto; min-width: 0; font: inherit; font-size: .8rem;
    padding: .35rem .5rem; border: 1px solid var(--border); border-radius: 999px;
    background: var(--surface);
}
.chat-say input:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.chat-send {
    flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; border: 0; border-radius: 50%;
    background: var(--primary); color: #fff; cursor: pointer;
}
.chat-send:hover { background: var(--primary-600); }
.chat-to-chip {
    flex: 0 0 auto; display: inline-flex; align-items: center; gap: .2rem;
    font-size: .64rem; font-weight: 600; color: #b45309;
    background: #fef6e7; border-radius: 999px; padding: .1rem .2rem .1rem .45rem;
}
.chat-to-x { border: 0; background: none; cursor: pointer; color: inherit; font-size: .8rem; line-height: 1; padding: 0 .2rem; }

/* The hidden attribute must actually hide.
   It is display:none only in the browser own stylesheet, so ANY author
   display declaration defeats it — a trap this file has fallen into several
   times already. Here it left the empty @-mention box permanently drawn
   above the composer and, being absolutely positioned at bottom:100%, sitting
   directly on top of the "... is typing" line. One rule for the whole dock,
   rather than remembering a [hidden] line for every element ever added. */
.chat-dock [hidden] { display: none !important; }

.chat-mention {
    position: absolute; bottom: 100%; left: .6rem; right: .6rem;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 8px; box-shadow: var(--shadow-lg); padding: .2rem;
    display: flex; flex-wrap: wrap; gap: .15rem; z-index: 2;
}
.chat-mention-hit {
    font: inherit; font-size: .74rem; border: 0; background: none; cursor: pointer;
    padding: .2rem .45rem; border-radius: 6px; color: var(--navy);
}
.chat-mention-hit:hover { background: var(--bg); }

.chat-flash {
    font-size: .72rem; color: #b91c1c; background: #fee2e2;
    padding: .3rem .6rem; border-top: 1px solid #fecaca;
}

/* ---- unread on the tab ---- */
.chat-unread {
    min-width: 1.2rem; padding: .05rem .35rem; border-radius: 999px;
    background: var(--danger); color: #fff; font-size: .68rem; font-weight: 700; text-align: center;
}
.chat-unread[hidden] { display: none; }
/* Shut, with something waiting: the tab has to earn a glance. */
.chat-dock.has-unread .chat-tab { background: var(--primary-600); }

/* Not listening. A quiet dock and a broken dock must not look the same. */
.chat-dock.is-stale .chat-tab { background: #7c2d12; }
.chat-dock.is-stale .chat-tab-label:after {
    content: "\2014  reconnecting"; font-weight: 400; opacity: .85; font-size: .74rem;
}

/* ---- private threads ------------------------------------------------------
   Docked beside the tab, the way a messenger does it, so a conversation stays
   in view while the reader carries on with the page behind it. */
.chat-dms { display: flex; align-items: flex-end; gap: .4rem; }
.chat-dm {
    width: 230px; background: var(--surface);
    border: 1px solid var(--border); border-bottom: 0; border-radius: 8px 8px 0 0;
    box-shadow: 0 -4px 18px rgba(19, 41, 75, .14);
    display: flex; flex-direction: column;
}
.chat-dm-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: .35rem .55rem; background: var(--navy); color: #fff;
    border-radius: 7px 7px 0 0; cursor: pointer;
}
.chat-dm-head strong { font-size: .76rem; }
.chat-dm-head { gap: .4rem; }
.chat-dm-x { border: 0; background: none; color: inherit; cursor: pointer; font-size: .95rem; line-height: 1; padding: 0 .15rem; }
.chat-dm-body { height: 190px; overflow-y: auto; padding: .4rem .55rem; }
/* Clicking the header folds a conversation away without closing it. */
.chat-dm.is-min .chat-dm-body,
.chat-dm.is-min .chat-dm-say { display: none; }
.chat-dm-unread {
    min-width: 1.1rem; padding: 0 .3rem; border-radius: 999px; margin-left: auto;
    background: var(--danger); color: #fff; font-size: .64rem; font-weight: 700; text-align: center;
}
.chat-dm-unread[hidden] { display: none; }
/* Each thread writes into its own box. One shared composer that re-aimed
   itself was the surest way to send something to the wrong person. */
.chat-dm-say { display: flex; gap: .3rem; padding: .35rem .45rem; border-top: 1px solid var(--border); background: var(--bg); }
.chat-dm-input {
    flex: 1 1 auto; min-width: 0; font: inherit; font-size: .76rem;
    padding: .28rem .45rem; border: 1px solid var(--border); border-radius: 999px;
    background: var(--surface);
}
.chat-dm-input:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.chat-dm-send {
    flex: 0 0 auto; width: 24px; height: 24px; border: 0; border-radius: 50%;
    background: var(--primary); color: #fff; cursor: pointer; font-size: .7rem; line-height: 1;
}
.chat-dm-send:hover { background: var(--primary-600); }
.chat-dm-msg { margin: 0 0 .4rem; max-width: 88%; }
.chat-dm-msg p {
    margin: 0; font-size: .78rem; line-height: 1.4; padding: .3rem .5rem;
    background: var(--bg); border-radius: 10px 10px 10px 2px;
    overflow-wrap: anywhere;
}
.chat-dm-msg.is-mine { margin-left: auto; }
.chat-dm-msg.is-mine p { background: #d6f1fb; border-radius: 10px 10px 2px 10px; }
.chat-dm-meta { display: flex; align-items: center; gap: .25rem; justify-content: flex-end; margin-top: .1rem; }

/* Sent, delivered, read — one tick, two ticks, two blue ticks. */
.chat-tick { font-size: .66rem; letter-spacing: -.12em; color: var(--muted); }
.chat-tick.tick-read { color: var(--primary); }

@media (max-width: 640px) {
    /* No room beside the dock on a phone: one conversation at a time, and it
       takes the whole width rather than three 230px slivers off the edge. */
    /* No room for a column beside the panel on a phone: the threads stack
       above it, full width. */
    .chat-dock { flex-direction: column; align-items: stretch; }
    .chat-dms { flex-direction: column; }
    .chat-main { align-items: stretch; }
    .chat-dm { width: auto; }
    .chat-dm-body { height: 150px; }
    .chat-dock.has-dms .chat-body { height: 190px; }
}

/* The joining banner's dismiss. Sits at the far right, quiet until hovered —
   it is a celebration, and a loud close button reads as an error dialog. */
.congrats-x { margin-left: auto; }
.congrats-x button {
    border: 0; background: none; cursor: pointer; padding: 0 .1rem;
    color: inherit; opacity: .55; font-size: 1.15rem; line-height: 1;
}
.congrats-x button:hover { opacity: 1; }

/* In the portal but not signed in for the day. A distinct colour, because it is
   a distinct fact from "available and working" — teal reads as present without
   claiming they have started. */
.chat-dot.dot-online-out { background: #0e7490; }

/* Settings -> Chat: the activity list, grouped by the thing each pair is
   about. Ninety-odd checkboxes in one run is a wall; in named groups it is a
   list of decisions. */
.ev-group + .ev-group { margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--border); }
.ev-group-head {
    margin: 0 0 .4rem; font-size: .72rem; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; color: var(--muted);
}

/* Last seen. Quieter than the status above it — it is the answer to a second
   question, not a competing one. */
.chat-who .chat-seen { font-size: .62rem; color: var(--muted); opacity: .85; line-height: 1.3; }
.chat-dm-who { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.chat-dm-seen { font-size: .62rem; opacity: .8; font-weight: 400;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-dm-seen[hidden] { display: none; }

/* The per-person mute. The office-wide switch is in Settings; this is so one
   person in a meeting can go quiet without silencing everybody. */
.chat-mute {
    font: inherit; font-size: .8rem; line-height: 1; background: none; border: 0;
    padding: .1rem .2rem; cursor: pointer; opacity: .75;
}
.chat-mute:hover { opacity: 1; }
.chat-mute.is-off { opacity: .4; }
.chat-mute[hidden] { display: none; }
.chat-roster-head { gap: .35rem; }

/* Settings -> Chat: one switch and its explanation as a single block. The
   help text lines up with the LABEL, not with the checkbox, so a column of
   them reads straight down instead of stepping in and out. */
.opt-list .opt { padding: .7rem 0; border-top: 1px solid var(--border); }
.opt-list > .opt:first-child { border-top: 0; padding-top: .2rem; }
.opt-list .opt .check { display: flex; align-items: flex-start; gap: .5rem;
                        font-weight: 600; margin: 0; }
.opt-list .opt .check input { margin-top: .15rem; }
.opt-list .opt > .help-text { display: block; margin: .25rem 0 0 1.55rem; max-width: 62ch; }
/* A switch that only means anything while its parent is on, shown as such. */
.opt-list .opt-child {
    margin: .6rem 0 0 1.55rem; padding: .6rem 0 0; border-top: 1px dashed var(--border);
}
.opt-list .opt-child.is-idle { opacity: .55; }

/* ---------------------------------------------------------------------------
   Leave balance meter
   How much of an entitlement is spoken for, next to the figures rather than
   instead of them — the bar is there to be read at a glance down a column,
   the number is there to be relied on. Colour matches the badge beside it.
--------------------------------------------------------------------------- */
.lv-bar {
    height: 8px; border-radius: 999px; background: var(--bg);
    border: 1px solid var(--border); overflow: hidden; min-width: 60px;
}
.lv-bar-fill { display: block; height: 100%; border-radius: 999px; transition: width .2s ease; }
.lv-bar-fill.lv-success   { background: var(--success); }
.lv-bar-fill.lv-warning   { background: var(--accent); }
.lv-bar-fill.lv-danger    { background: var(--danger); }
.lv-bar-fill.lv-secondary { background: var(--muted); }

/* "Puja is typing…". Sits between the last message and the box, so it never
   moves the conversation as it comes and goes. */
.chat-typing {
    padding: .15rem .75rem .25rem; font-size: .68rem; font-style: italic;
    color: var(--muted); line-height: 1.3; min-height: 0;
}
.chat-typing[hidden] { display: none; }
.chat-dm .chat-typing { padding: .15rem .55rem .25rem; font-size: .64rem; }

/* A refusal inside a private window. Same red as the room flash. */
.chat-dm-flash {
    font-size: .66rem; color: #b91c1c; background: #fee2e2;
    padding: .25rem .55rem; border-top: 1px solid #fecaca; line-height: 1.35;
}

/* ---------------------------------------------------------------------------
   CV Report
   The entry form's "or type it in" boxes sit under the select they belong to
   and stay hidden until that select asks for them, so the form reads as seven
   fields rather than fourteen. Hidden here rather than in JS so the box does
   not flash on its way out on a slow first paint.
--------------------------------------------------------------------------- */
/* Hidden until its select asks for it. The JS adds `is-on` rather than writing
   an inline style: clearing an inline display falls back to THIS rule, so a box
   toggled that way could never appear. */
.cv-custom { display: none; margin-top: .4rem; }
.cv-custom.is-on { display: block; width: 100%; }

/* A concern hangs under the row it is about, indented and tinted, so it reads
   as belonging to that entry rather than as another entry. */
.cv-concern-row > td { background: var(--bg); border-top: 0; font-size: .86rem; white-space: normal; }
.cv-concern-row + .cv-concern-row > td { border-top: 1px dashed var(--border); }

.cv-inline { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; align-items: center; }
.cv-inline input[type="text"] { flex: 1 1 16rem; min-width: 0; }

/* ---------- Mail ---------- */
/* The section's tab strip. Compose / Drafts / Sent are one place, not three
   sidebar entries, so the strip carries the sense of a single section. */
.mail-tabs { display:flex; flex-wrap:wrap; gap:.25rem; margin:0 0 1.25rem; border-bottom:1px solid var(--border); }
.mail-tab { padding:.55rem .95rem; border:1px solid transparent; border-bottom:0; border-radius:8px 8px 0 0;
            color:var(--muted); font-size:.92rem; font-weight:500; margin-bottom:-1px; white-space:nowrap; }
.mail-tab:hover { color:var(--primary); text-decoration:none; background:var(--bg); }
.mail-tab.is-active { background:var(--surface); border-color:var(--border); color:var(--navy); font-weight:600; }
.mail-tab-count { display:inline-block; min-width:1.25rem; padding:0 .35rem; margin-left:.3rem; border-radius:999px;
                  background:var(--primary); color:#fff; font-size:.72rem; line-height:1.25rem; text-align:center; }

/* Attachment drop zone and list. */
.mail-drop { display:flex; flex-wrap:wrap; gap:.6rem; align-items:center; padding:.75rem .9rem;
             border:1px dashed var(--border); border-radius:8px; background:var(--bg); font-size:.88rem; }
.mail-drop.is-over { border-color:var(--primary); background:var(--surface); }
.mail-atts { list-style:none; margin:.6rem 0 0; padding:0; display:flex; flex-direction:column; gap:.35rem; }
.mail-atts li { display:flex; align-items:center; gap:.6rem; padding:.4rem .6rem; border:1px solid var(--border);
                border-radius:6px; background:var(--surface); font-size:.87rem; }
.mail-att-name { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mail-att-x { flex:0 0 auto; border:0; background:transparent; color:var(--muted); font-size:1.15rem;
              line-height:1; cursor:pointer; padding:0 .2rem; }
.mail-att-x:hover { color:var(--danger); }

/* The first line of a draft, under its subject in the list. */
.mail-draft-peek { display:block; margin-top:.15rem; }

/* ---------- Productivity Report ---------- */
/* The three headline figures, the timeline, and the flags for a late start or
   an early finish. Shared by My Attendance and the all-members screen. */
.pres-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
              gap: 1rem; margin-bottom: 1.1rem; }
.pres-tile { background: var(--surface); border: 1px solid var(--border);
             border-radius: var(--radius); box-shadow: var(--shadow);
             padding: 1rem 1.15rem; border-left: 3px solid var(--border); }
.pres-tile.work  { border-left-color: var(--primary); }
.pres-tile.total { border-left-color: var(--navy-600); }
.pres-tile.brk   { border-left-color: var(--accent); }
.pres-tile .k { font-size: .72rem; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); }
.pres-tile .v { font: 700 1.85rem/1.1 var(--font-head); color: var(--navy); margin: .25rem 0 .1rem; }
.pres-tile .s { font-size: .78rem; color: var(--muted); }
.pres-tile .bar { height: 5px; border-radius: 99px; background: var(--bg); margin-top: .6rem; overflow: hidden; }
.pres-tile .bar i { display: block; height: 100%; background: var(--primary); }

/* Late in / early out, called out where it cannot be missed. */
.pres-flags { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.1rem; }
.pres-flag { display: inline-flex; align-items: center; gap: .4rem; padding: .4rem .75rem;
             border-radius: 999px; font-size: .82rem; font-weight: 600; }
.pres-flag.late  { background: #fef3c7; color: #92400e; border: 1px solid #f6dca8; }
.pres-flag.early { background: #e0f2fe; color: #075985; border: 1px solid #bae6fd; }
.pres-chip { display: inline-block; padding: .05rem .4rem; border-radius: 999px;
             font-size: .68rem; font-weight: 700; margin-right: .25rem; }
.pres-chip.late  { background: #fef3c7; color: #92400e; }
.pres-chip.early { background: #e0f2fe; color: #075985; }

/* The timeline. Blocks are positioned by real clock time across the measured
   window, so the shape of a day is legible before a single number is read. */
.pres-tl-wrap { padding: 1.1rem 1.25rem 1.2rem; }
.pres-scale { display: flex; justify-content: space-between; font-size: .7rem;
              color: var(--muted); margin-bottom: .3rem; }
.pres-tl { position: relative; height: 38px; border-radius: 7px; overflow: hidden;
           background: repeating-linear-gradient(90deg, #eef2f7 0 1px, transparent 1px 12.5%); }
.pres-tl.pres-tl-sm { height: 20px; border-radius: 5px; }
.pres-tl i { position: absolute; top: 0; bottom: 0; display: block; }
.pres-tl i.w { background: linear-gradient(180deg, var(--primary), var(--primary-600)); }
.pres-tl i.b { background: repeating-linear-gradient(45deg, #e8edf3 0 6px, #dfe6ee 6px 12px); }
.pres-legend { display: flex; gap: 1.1rem; flex-wrap: wrap; margin-top: .7rem;
               font-size: .78rem; color: var(--muted); }
.pres-legend span { display: inline-flex; align-items: center; gap: .4rem; }
.pres-legend .sw { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.pres-legend .sw.w { background: var(--primary); }
.pres-legend .sw.b { background: #e3e9f0; border: 1px solid #cfd8e3; }

/* The standing caveat. Quiet, but on every screen that shows these figures. */
.pres-note { font-size: .82rem; color: var(--muted); line-height: 1.6;
             background: var(--bg); border: 1px solid var(--border);
             border-radius: var(--radius); padding: .85rem 1.1rem; margin: 0 0 1.25rem; }
.pres-note strong { color: var(--navy); }

@media (max-width: 640px) {
    .pres-tile .v { font-size: 1.5rem; }
    .pres-tl-wrap { padding: .9rem 1rem 1rem; }
}

/* Productivity Report range picker: From/To appear only on Custom. A bare [hidden]
   would lose to .form-group{display:flex} — the same trap as .ma-bar above. */
.filters .pres-custom[hidden] { display: none; }

/* ---------------------------------------------------------------------------
   Preview as a user
   Deliberately loud. Somebody wearing another person's permissions should never
   be in any doubt about it, and the way out is always in the bar itself.
--------------------------------------------------------------------------- */
.view-as-bar {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    flex-wrap: wrap;
    background: repeating-linear-gradient(135deg, #7c2d12, #7c2d12 14px, #8a3413 14px, #8a3413 28px);
    color: #fff; border-radius: 10px; padding: .7rem 1rem; margin-bottom: 1.1rem;
    box-shadow: 0 2px 10px rgba(124, 45, 18, .25);
}
.view-as-bar strong { font-size: .98rem; }
.view-as-bar .pill { background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.3); color: #fff; margin-left: .35rem; }
.view-as-note { display: block; font-size: .78rem; opacity: .9; margin-top: .15rem; }
.view-as-bar form { margin: 0; flex: 0 0 auto; }
.view-as-bar .btn { background: #fff; color: #7c2d12; border-color: #fff; font-weight: 600; }
.view-as-bar .btn:hover { background: #fde8df; color: #7c2d12; }
@media (max-width: 600px) { .view-as-bar { flex-direction: column; align-items: flex-start; } }

/* ---------- Category list (Masters → lookup) ----------
   It used to borrow .nav / .nav-link from the sidebar, which broke it twice:

   1. `.app.sidebar-collapsed .nav-link span { display:none }` keys off .app,
      which wraps the whole page — so collapsing the sidebar blanked this list.
      That rule is now scoped to .sidebar, and this list no longer uses the
      class either, so neither route can reach it again.
   2. `.nav-link.active` is white on a 16% cyan tint. Over the navy sidebar that
      measures 13.42:1 — the 13.4:1 its own comment claims. Over a white card the
      tint resolves to #daf2f9 and the same white text measures 1.16:1, which is
      why the selected row read as empty.

   Same look, its own classes. --navy on that tint is 14.95:1. */
.cat-nav { display: flex; flex-direction: column; gap: .15rem; padding: .5rem; }
.cat-nav a {
    display: flex; align-items: center; gap: .5rem;
    padding: .5rem .7rem; border-radius: var(--radius-sm);
    color: var(--text); font-size: .88rem; font-weight: 500;
    text-decoration: none; transition: background .15s, color .15s;
}
/* A long category name ellipsises rather than widening the aside. */
.cat-nav a > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-nav a:hover { background: var(--bg); text-decoration: none; }
.cat-nav a.active {
    background: rgba(26,172,220,.16);
    box-shadow: inset 3px 0 0 var(--primary);
    color: var(--navy); font-weight: 600;
}
.cat-nav .pill { margin-left: auto; }
