/* =====================================================================
   V-Recruit - VALOOBUY Recruitment System
   Stylesheet: mobile-first, compact, red/white brand, light + dark mode.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Design tokens - LIGHT (default)

   Brand approach: white surfaces with VALOOBUY red used as an accent on
   buttons, active tabs and the logo. A full red chrome was considered and
   rejected - staff stare at this screen for hours and a large saturated
   area is tiring to read against.
   --------------------------------------------------------------------- */
:root {
    /* VALOOBUY brand */
    --brand:       #a31f24;
    --brand-600:   #8a1a1f;   /* hover / pressed */
    --brand-700:   #711418;
    --brand-050:   #fdf2f2;   /* faint tint for active nav rows */
    --brand-100:   #f8e3e4;

    /* Surfaces */
    --bg:          #f5f6f8;
    --surface:     #ffffff;
    --surface-2:   #fafbfc;
    --border:      #e6e8ec;
    --border-2:    #ced3da;

    /* Text */
    --text:        #14181f;
    --text-2:      #4a5260;
    --text-3:      #89929f;

    /* Interactive = brand */
    --primary:     var(--brand);
    --primary-bg:  var(--brand-050);

    /* Status colours. Danger is a brighter red than the brand so an error
       never reads as "just branding". */
    --success:     #14804a;
    --success-bg:  #eaf6ef;
    --warning:     #a86100;
    --warning-bg:  #fdf5e7;
    --danger:      #d92d20;
    --danger-bg:   #fdeceb;

    /* Chart / badge accents */
    --violet:      #6d3bd1;
    --violet-bg:   #f3effd;
    --teal:        #0d7a72;
    --teal-bg:     #e9f7f5;
    --amber:       #a86100;
    --amber-bg:    #fdf5e7;
    --blue:        #1f5fb0;
    --blue-bg:     #eef4fb;
    --green:       #14804a;
    --green-bg:    #eaf6ef;
    --red:         #d92d20;
    --red-bg:      #fdeceb;
    --slate:       #4a5260;
    --slate-bg:    #f1f3f6;
    --gray:        #6b7280;
    --gray-bg:     #f1f3f6;

    /* Top bar is light in this theme; these let components adapt. */
    --topbar-bg:     #ffffff;
    --topbar-text:   #14181f;
    --topbar-muted:  #6b7280;
    --topbar-border: #e6e8ec;
    --topbar-hover:  #f1f3f6;

    /* Icon sizes - tokens so the whole UI scales together. Deliberately
       small: a dense admin screen reads better with restrained icons. */
    --icon-xs:     10px;
    --icon-sm:     12px;
    --icon-base:   14px;
    --icon-nav:    15px;
    --icon-md:     17px;
    --icon-lg:     18px;
    --icon-empty:  30px;

    --radius:      10px;
    --radius-sm:   7px;
    --shadow:      0 1px 2px rgba(20,24,31,.04), 0 1px 3px rgba(20,24,31,.07);
    --shadow-lg:   0 4px 6px rgba(20,24,31,.04), 0 12px 32px rgba(20,24,31,.14);

    --sidebar-w:   232px;
    --header-h:    54px;
    --bottomnav-h: 58px;

    --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    color-scheme: light;
}

/* ---------------------------------------------------------------------
   Design tokens - DARK

   Two selectors on purpose:
     1. [data-theme="dark"]              -> the user chose dark explicitly
     2. prefers-color-scheme + :not(...) -> follow the OS, unless the user
                                            has explicitly chosen light
   That combination lets the in-app toggle override the operating system in
   BOTH directions instead of only being able to force dark.

   KEEP THESE TWO BLOCKS IDENTICAL. If you add a token to one, add it to
   the other, otherwise one of the two dark paths will be missing a colour.
   --------------------------------------------------------------------- */
:root[data-theme="dark"] {
    --brand:       #e2606a;   /* lifted so red stays legible on dark */
    --brand-600:   #ec7d85;
    --brand-700:   #f2969c;
    --brand-050:   #2a1417;
    --brand-100:   #3a1b1f;

    --bg:          #0f1216;
    --surface:     #171b22;
    --surface-2:   #1e232b;
    --border:      #2a303a;
    --border-2:    #3a424e;

    --text:        #e9ecf1;
    --text-2:      #a8b0bd;
    --text-3:      #6f7887;

    --success:     #3ecf8e;  --success-bg: #10281d;
    --warning:     #e0a132;  --warning-bg: #2a2013;
    --danger:      #f2726a;  --danger-bg:  #2c1615;

    --violet:      #a78bfa;  --violet-bg:  #211a35;
    --teal:        #2dd4bf;  --teal-bg:    #0d2725;
    --amber:       #e0a132;  --amber-bg:   #2a2013;
    --blue:        #6aa6f0;  --blue-bg:    #14202f;
    --green:       #3ecf8e;  --green-bg:   #10281d;
    --red:         #f2726a;  --red-bg:     #2c1615;
    --slate:       #a8b0bd;  --slate-bg:   #1e232b;
    --gray:        #8b93a1;  --gray-bg:    #1e232b;

    --topbar-bg:     #171b22;
    --topbar-text:   #e9ecf1;
    --topbar-muted:  #8b93a1;
    --topbar-border: #2a303a;
    --topbar-hover:  #262c36;

    --shadow:    0 1px 2px rgba(0,0,0,.3), 0 1px 3px rgba(0,0,0,.36);
    --shadow-lg: 0 4px 6px rgba(0,0,0,.32), 0 12px 32px rgba(0,0,0,.5);

    color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --brand:       #e2606a;
        --brand-600:   #ec7d85;
        --brand-700:   #f2969c;
        --brand-050:   #2a1417;
        --brand-100:   #3a1b1f;

        --bg:          #0f1216;
        --surface:     #171b22;
        --surface-2:   #1e232b;
        --border:      #2a303a;
        --border-2:    #3a424e;

        --text:        #e9ecf1;
        --text-2:      #a8b0bd;
        --text-3:      #6f7887;

        --success:     #3ecf8e;  --success-bg: #10281d;
        --warning:     #e0a132;  --warning-bg: #2a2013;
        --danger:      #f2726a;  --danger-bg:  #2c1615;

        --violet:      #a78bfa;  --violet-bg:  #211a35;
        --teal:        #2dd4bf;  --teal-bg:    #0d2725;
        --amber:       #e0a132;  --amber-bg:   #2a2013;
        --blue:        #6aa6f0;  --blue-bg:    #14202f;
        --green:       #3ecf8e;  --green-bg:   #10281d;
        --red:         #f2726a;  --red-bg:     #2c1615;
        --slate:       #a8b0bd;  --slate-bg:   #1e232b;
        --gray:        #8b93a1;  --gray-bg:    #1e232b;

        --topbar-bg:     #171b22;
        --topbar-text:   #e9ecf1;
        --topbar-muted:  #8b93a1;
        --topbar-border: #2a303a;
        --topbar-hover:  #262c36;

        --shadow:    0 1px 2px rgba(0,0,0,.3), 0 1px 3px rgba(0,0,0,.36);
        --shadow-lg: 0 4px 6px rgba(0,0,0,.32), 0 12px 32px rgba(0,0,0,.5);

        color-scheme: dark;
    }
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.5;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; }

/* Inline SVG icons have no intrinsic size - only a viewBox - so without an
   explicit size they stretch to fill their container. This gives every icon
   a sane default; the component rules further down override it where a
   different size is wanted. Never remove this. */
svg {
    width: var(--icon-base);
    height: var(--icon-base);
    flex: none;          /* stops flex containers from squashing or stretching it */
    vertical-align: middle;
}

h1, h2, h3, h4 { margin: 0; font-weight: 650; line-height: 1.25; }
h1 { font-size: 19px; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }

p { margin: 0 0 10px; }
p:last-child { margin-bottom: 0; }

/* =====================================================================
   Bilingual labels
   ===================================================================== */
.bi { display: inline-flex; flex-direction: column; line-height: 1.15; }
.bi-en { font-weight: inherit; }
.bi-bm { font-size: .74em; font-weight: 400; color: var(--text-3); font-style: italic; }

.bi-inline { white-space: nowrap; }
.bi-inline .bi-sep { margin: 0 4px; color: var(--text-3); }
.bi-inline .bi-bm { font-size: .85em; color: var(--text-3); font-style: italic; }

/* =====================================================================
   App shell
   ===================================================================== */
.app { min-height: 100vh; }

/* ---------- Top bar ---------- */
.topbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 50;
    height: var(--header-h);
    display: flex; align-items: center; gap: 10px;
    padding: 0 12px;
    background: var(--topbar-bg);
    color: var(--topbar-text);
    border-bottom: 1px solid var(--topbar-border);
}

.topbar-brand {
    display: flex; align-items: center; gap: 9px;
    font-weight: 700; font-size: 14.5px; letter-spacing: .2px;
    color: var(--topbar-text); text-decoration: none;
    min-width: 0;
}
.topbar-brand:hover { text-decoration: none; }

/* The logo image itself is the mark - no tinted tile behind it. */
.brand-mark {
    width: 30px; height: 30px; flex: 0 0 30px;
    display: block;
}
.brand-mark img, .brand-mark svg {
    width: 100%; height: 100%; display: block;
}

.brand-text { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
/* Big line = the product name, small line = the registered company.
   The legal name is long, so it gets a tighter size and is allowed to
   ellipsis rather than pushing the top bar out of shape. */
.brand-text span  { font-weight: 700; color: var(--topbar-text); }
.brand-text small {
    font-size: 9.5px; font-weight: 600; letter-spacing: .2px;
    color: var(--brand);
    max-width: 190px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 420px) {
    /* On a narrow phone the legal name would dominate the bar. */
    .brand-text small { max-width: 120px; }
}
.brand-text span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.topbar-spacer { flex: 1; }

.icon-btn {
    position: relative;
    width: 36px; height: 36px;
    display: grid; place-items: center;
    background: transparent; border: 0; border-radius: 9px;
    color: var(--topbar-muted); cursor: pointer;
    transition: background .15s, color .15s;
}
.icon-btn:hover, .icon-btn:focus-visible {
    background: var(--topbar-hover);
    color: var(--topbar-text);
}
.icon-btn svg { width: var(--icon-md); height: var(--icon-md); }

.badge-dot {
    position: absolute; top: 4px; right: 4px;
    min-width: 16px; height: 16px; padding: 0 4px;
    display: grid; place-items: center;
    background: #ef4444; color: #fff;
    font-size: 10px; font-weight: 700; line-height: 1;
    border-radius: 8px;
    border: 2px solid var(--topbar-bg);
}

.menu-toggle { display: grid; }

/* ---------- Sidebar ---------- */
.sidebar {
    position: fixed; top: var(--header-h); bottom: 0; left: 0; z-index: 45;
    width: var(--sidebar-w);
    background: var(--surface);
    border-right: 1px solid var(--border);
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform .22s ease;
    padding: 10px 0 20px;
}
.sidebar.open { transform: translateX(0); }

.sidebar-section {
    padding: 12px 16px 5px;
    font-size: 10.5px; font-weight: 700; letter-spacing: .7px; text-transform: uppercase;
    color: var(--text-3);
}

.nav-item {
    display: flex; align-items: center; gap: 11px;
    padding: 9px 16px;
    color: var(--text-2);
    font-size: 13.5px; font-weight: 500;
    border-left: 3px solid transparent;
    transition: background .12s, color .12s;
}
.nav-item:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.nav-item.active {
    background: var(--primary-bg);
    color: var(--primary);
    border-left-color: var(--primary);
    font-weight: 600;
}
.nav-item svg { width: var(--icon-nav); height: var(--icon-nav); flex: 0 0 var(--icon-nav); }
.nav-item .count {
    margin-left: auto;
    min-width: 19px; padding: 1px 6px;
    background: var(--danger); color: #fff;
    font-size: 10.5px; font-weight: 700; text-align: center;
    border-radius: 9px;
}
.nav-item.active .count { background: var(--primary); }

.sidebar-backdrop {
    position: fixed; inset: var(--header-h) 0 0; z-index: 44;
    background: rgba(15,23,42,.45);
    opacity: 0; pointer-events: none;
    transition: opacity .22s;
}
.sidebar-backdrop.show { opacity: 1; pointer-events: auto; }

/* ---------- Main ---------- */
.main {
    padding: calc(var(--header-h) + 12px) 12px calc(var(--bottomnav-h) + 20px);
    max-width: 1180px;
    margin: 0 auto;
}

.page-head {
    display: flex; align-items: flex-start; gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}
.page-head .page-title { flex: 1; min-width: 0; }
.page-head h1 { display: block; }
.page-sub { color: var(--text-2); font-size: 12.5px; margin-top: 3px; }
.page-actions { display: flex; gap: 7px; flex-wrap: wrap; }

/* ---------- Bottom nav (mobile only) ---------- */
.bottomnav {
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 46;
    height: var(--bottomnav-h);
    display: flex;
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom);
}
.bottomnav a {
    flex: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    color: var(--text-3);
    font-size: 10px; font-weight: 600;
    text-decoration: none;
    position: relative;
}
.bottomnav a.active { color: var(--primary); }
.bottomnav svg { width: var(--icon-lg); height: var(--icon-lg); }
.bottomnav .count {
    position: absolute; top: 6px; left: 50%; margin-left: 4px;
    min-width: 15px; padding: 0 4px;
    background: var(--danger); color: #fff;
    font-size: 9px; font-weight: 700; line-height: 15px; text-align: center;
    border-radius: 8px;
}

/* =====================================================================
   Cards
   ===================================================================== */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    margin-bottom: 12px;
    overflow: hidden;
}
.card-head {
    display: flex; align-items: center; gap: 8px;
    padding: 11px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}
.card-head h2, .card-head h3 { flex: 1; min-width: 0; font-size: 13.5px; }
/* The leading icon in a card header. */
.card-head > svg {
    width: var(--icon-nav); height: var(--icon-nav);
    color: var(--text-3);
}
.card-body { padding: 14px; }
.card-body.tight { padding: 0; }
.card-foot {
    padding: 10px 14px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    display: flex; gap: 7px; flex-wrap: wrap;
}

/* =====================================================================
   Stat tiles
   ===================================================================== */
.stats {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 9px;
    margin-bottom: 14px;
}
.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px;
    box-shadow: var(--shadow);
    display: block;
    color: inherit;
}
.stat:hover { text-decoration: none; border-color: var(--border-2); }
.stat-label {
    font-size: 10.5px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
    color: var(--text-3);
    display: flex; align-items: center; gap: 5px;
}
.stat-label svg { width: var(--icon-sm); height: var(--icon-sm); }
.stat-value { font-size: 25px; font-weight: 700; line-height: 1.15; margin-top: 5px; letter-spacing: -.5px; }
.stat-hint { font-size: 11.5px; color: var(--text-2); margin-top: 2px; }
.stat.accent-blue   .stat-value { color: var(--blue); }
.stat.accent-green  .stat-value { color: var(--green); }
.stat.accent-amber  .stat-value { color: var(--amber); }
.stat.accent-violet .stat-value { color: var(--violet); }
.stat.accent-red    .stat-value { color: var(--red); }

/* =====================================================================
   Buttons
   ===================================================================== */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 8px 13px;
    font-family: inherit; font-size: 13px; font-weight: 600; line-height: 1.2;
    border: 1px solid var(--border-2);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    transition: background .13s, border-color .13s, opacity .13s;
}
.btn:hover { background: var(--surface-2); text-decoration: none; }
.btn:active { transform: translateY(.5px); }
.btn:disabled, .btn.disabled { opacity: .55; cursor: not-allowed; }
.btn svg { width: var(--icon-base); height: var(--icon-base); }

.btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-600); border-color: var(--brand-600); }

.btn-success { background: var(--success); border-color: var(--success); color: #fff; }
.btn-success:hover { background: #047857; }

.btn-danger  { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { background: #b91c1c; }

.btn-warning { background: var(--warning); border-color: var(--warning); color: #fff; }
.btn-warning:hover { background: #b45309; }

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }

.btn-sm { padding: 5px 9px; font-size: 12px; }
.btn-sm svg { width: var(--icon-sm); height: var(--icon-sm); }
.btn-block { width: 100%; }

.btn-group { display: flex; gap: 7px; flex-wrap: wrap; }

/* =====================================================================
   Forms
   ===================================================================== */
.form-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
.form-grid.cols-2 { grid-template-columns: 1fr; }
.form-grid.cols-3 { grid-template-columns: 1fr; }

.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field.span-2 { grid-column: 1 / -1; }

.field > label {
    font-size: 12.5px; font-weight: 600; color: var(--text-2);
    display: flex; align-items: baseline; gap: 4px;
}
.field .req { color: var(--danger); font-weight: 700; }
.field .hint { font-size: 11.5px; color: var(--text-3); font-weight: 400; }

.input, .select, .textarea {
    width: 100%;
    padding: 9px 11px;
    font-family: inherit; font-size: 14px; /* 16px-ish avoids iOS zoom; 14 is fine on Android */
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-2);
    border-radius: var(--radius-sm);
    transition: border-color .13s, box-shadow .13s;
    -webkit-appearance: none; appearance: none;
}
.input:focus, .select:focus, .textarea:focus {
    outline: 0;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 22%, transparent);
}
.input:disabled, .select:disabled, .textarea:disabled {
    background: var(--surface-2); color: var(--text-3); cursor: not-allowed;
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); }

.textarea { min-height: 84px; resize: vertical; line-height: 1.5; }

.select {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2394a3b8' stroke-width='1.8'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 15px;
    padding-right: 30px;
}

.checkline { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; color: var(--text-2); }
.checkline input[type=checkbox], .checkline input[type=radio] {
    width: 16px; height: 16px; margin: 1px 0 0; flex: 0 0 16px; accent-color: var(--primary);
}

.field-error { font-size: 12px; color: var(--danger); }
.input.has-error, .select.has-error, .textarea.has-error { border-color: var(--danger); }

fieldset {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 13px;
    margin: 0 0 12px;
}
legend {
    padding: 0 6px;
    font-size: 12px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
    color: var(--text-3);
}

/* ---------- Star rating ---------- */
.stars { display: inline-flex; gap: 2px; }
.stars svg { width: 13px; height: 13px; }
.stars .on   { color: #f59e0b; fill: #f59e0b; }
.stars .off  { color: var(--border-2); fill: none; }

.star-input { display: flex; gap: 4px; }
.star-input input { position: absolute; opacity: 0; pointer-events: none; }
.star-input label {
    cursor: pointer; color: var(--border-2);
    display: grid; place-items: center;
    width: 34px; height: 34px;
    border: 1px solid var(--border-2); border-radius: var(--radius-sm);
    font-size: 13px; font-weight: 700;
    transition: all .12s;
}
.star-input input:checked + label,
.star-input label.picked { background: var(--primary); border-color: var(--primary); color: #fff; }

/* =====================================================================
   Tables -> cards on mobile
   ===================================================================== */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

table.data {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
table.data th {
    text-align: left;
    padding: 9px 12px;
    font-size: 11px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
    color: var(--text-3);
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
table.data td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--surface-2); }
table.data .num { text-align: right; font-variant-numeric: tabular-nums; }

/* Card-style list used instead of a table on narrow screens */
.list-item {
    display: block;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border);
    color: inherit;
}
.list-item:last-child { border-bottom: 0; }
.list-item:hover { background: var(--surface-2); text-decoration: none; }
.list-item-top { display: flex; align-items: center; gap: 9px; }
.list-item-main { flex: 1; min-width: 0; }
.list-item-title {
    font-weight: 620; font-size: 13.5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.list-item-sub {
    font-size: 12px; color: var(--text-2); margin-top: 1px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.list-item-meta {
    display: flex; flex-wrap: wrap; gap: 5px 10px;
    margin-top: 7px;
    font-size: 11.5px; color: var(--text-3);
}
.list-item-meta span { display: inline-flex; align-items: center; gap: 4px; }
.list-item-meta svg { width: var(--icon-xs); height: var(--icon-xs); }

/* =====================================================================
   Avatar
   ===================================================================== */
.avatar {
    width: 32px; height: 32px; flex: 0 0 32px;
    display: grid; place-items: center;
    border-radius: 50%;
    color: #fff; font-size: 12px; font-weight: 700;
    letter-spacing: .3px;
    text-transform: uppercase;
}
.avatar.sm { width: 24px; height: 24px; flex-basis: 24px; font-size: 9.5px; }
.avatar.lg { width: 48px; height: 48px; flex-basis: 48px; font-size: 16px; }

/* =====================================================================
   Badges / pills
   ===================================================================== */
.badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 8px;
    font-size: 11px; font-weight: 650; line-height: 1.6;
    border-radius: 20px;
    white-space: nowrap;
}
.badge svg { width: var(--icon-xs); height: var(--icon-xs); }

.badge-slate  { background: var(--slate-bg);  color: var(--slate); }
.badge-gray   { background: var(--gray-bg);   color: var(--gray); }
.badge-blue   { background: var(--blue-bg);   color: var(--blue); }
.badge-violet { background: var(--violet-bg); color: var(--violet); }
.badge-amber  { background: var(--amber-bg);  color: var(--amber); }
.badge-green  { background: var(--green-bg);  color: var(--green); }
.badge-red    { background: var(--red-bg);    color: var(--red); }
.badge-teal   { background: var(--teal-bg);   color: var(--teal); }
.badge-success{ background: var(--success-bg);color: var(--success); }
.badge-warning{ background: var(--warning-bg);color: var(--warning); }
.badge-danger { background: var(--danger-bg); color: var(--danger); }
.badge-info   { background: var(--blue-bg);   color: var(--blue); }

.pill-row { display: flex; flex-wrap: wrap; gap: 5px; }

/* =====================================================================
   Alerts / flash
   ===================================================================== */
.alert {
    display: flex; align-items: flex-start; gap: 9px;
    padding: 11px 13px;
    border-radius: var(--radius-sm);
    border: 1px solid;
    font-size: 13px;
    margin-bottom: 12px;
}
.alert svg { width: var(--icon-md); height: var(--icon-md); flex: 0 0 var(--icon-md); margin-top: 1px; }
.alert-success { background: var(--success-bg); border-color: var(--success); color: var(--success); }
.alert-danger  { background: var(--danger-bg);  border-color: var(--danger);  color: var(--danger); }
.alert-warning { background: var(--warning-bg); border-color: var(--warning); color: var(--warning); }
.alert-info    { background: var(--primary-bg); border-color: var(--primary); color: var(--primary); }
.alert strong { font-weight: 700; }
.alert a { color: inherit; text-decoration: underline; }

.flash-stack { position: relative; }

/* =====================================================================
   Empty state
   ===================================================================== */
.empty {
    padding: 34px 20px;
    text-align: center;
    color: var(--text-3);
}
.empty > svg { width: var(--icon-empty); height: var(--icon-empty); opacity: .45; margin-bottom: 10px; }
.empty h3 { font-size: 14px; color: var(--text-2); margin-bottom: 4px; }
.empty p { font-size: 12.5px; max-width: 340px; margin: 0 auto 12px; }

/* =====================================================================
   Pipeline / stage bar
   ===================================================================== */
.pipeline {
    display: flex; gap: 6px; overflow-x: auto;
    padding-bottom: 4px; margin-bottom: 14px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
}
.pipeline-step {
    flex: 0 0 auto;
    min-width: 96px;
    padding: 9px 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    text-align: center;
    color: inherit;
}
.pipeline-step:hover { text-decoration: none; border-color: var(--border-2); }
.pipeline-step.current { border-color: var(--primary); background: var(--primary-bg); }
.pipeline-step .n { font-size: 19px; font-weight: 700; line-height: 1.1; }
.pipeline-step .l { font-size: 10.5px; font-weight: 600; color: var(--text-3); text-transform: uppercase; letter-spacing: .3px; }

/* =====================================================================
   Timeline (activity, comments)
   ===================================================================== */
.timeline { position: relative; padding-left: 26px; }
.timeline::before {
    content: ''; position: absolute; left: 8px; top: 6px; bottom: 6px;
    width: 2px; background: var(--border);
}
.timeline-item { position: relative; padding-bottom: 15px; }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-dot {
    position: absolute; left: -25px; top: 3px;
    width: 11px; height: 11px;
    border-radius: 50%;
    background: var(--border-2);
    border: 2px solid var(--surface);
    box-shadow: 0 0 0 2px var(--border);
}
.timeline-dot.green  { background: var(--green); }
.timeline-dot.red    { background: var(--red); }
.timeline-dot.blue   { background: var(--blue); }
.timeline-dot.amber  { background: var(--amber); }
.timeline-dot.violet { background: var(--violet); }
.timeline-title { font-size: 13px; font-weight: 600; }
.timeline-meta  { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }
.timeline-body  { font-size: 13px; color: var(--text-2); margin-top: 5px; }

/* ---------- Comment bubbles ---------- */
.comment { display: flex; gap: 9px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.comment:last-child { border-bottom: 0; }
.comment-main { flex: 1; min-width: 0; }
.comment-head { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.comment-author { font-size: 13px; font-weight: 650; }
.comment-time { font-size: 11.5px; color: var(--text-3); }
.comment-body {
    font-size: 13px; color: var(--text-2); margin-top: 3px;
    white-space: pre-wrap; word-break: break-word;
}

/* =====================================================================
   Document list
   ===================================================================== */
.doc-row {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
}
.doc-row:last-child { border-bottom: 0; }
.doc-icon {
    width: 32px; height: 32px; flex: 0 0 32px;
    display: grid; place-items: center;
    border-radius: var(--radius-sm);
    font-size: 9.5px; font-weight: 800; letter-spacing: .3px;
    background: var(--slate-bg); color: var(--slate);
}
.doc-icon svg { width: var(--icon-nav); height: var(--icon-nav); }
.doc-icon.pdf  { background: var(--red-bg);   color: var(--red); }
.doc-icon.doc  { background: var(--blue-bg);  color: var(--blue); }
.doc-icon.img  { background: var(--violet-bg);color: var(--violet); }
.doc-icon.vid  { background: var(--teal-bg);  color: var(--teal); }
.doc-main { flex: 1; min-width: 0; }
.doc-name {
    font-size: 13px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.doc-meta { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }
.doc-actions { display: flex; gap: 4px; flex: 0 0 auto; }
.doc-actions svg { width: var(--icon-sm); height: var(--icon-sm); }

/* ---------- Drop zone ---------- */
.dropzone {
    border: 2px dashed var(--border-2);
    border-radius: var(--radius);
    padding: 20px 14px;
    text-align: center;
    background: var(--surface-2);
    cursor: pointer;
    transition: border-color .15s, background .15s;
}
.dropzone:hover, .dropzone.dragover {
    border-color: var(--primary);
    background: var(--primary-bg);
}
.dropzone svg { width: 21px; height: 21px; color: var(--text-3); margin-bottom: 7px; }
.dropzone-title { font-size: 13px; font-weight: 600; }
.dropzone-hint  { font-size: 11.5px; color: var(--text-3); margin-top: 3px; }
.dropzone input[type=file] { display: none; }

.file-chosen {
    display: flex; align-items: center; gap: 8px;
    margin-top: 9px; padding: 8px 11px;
    background: var(--primary-bg);
    border: 1px solid var(--primary);
    border-radius: var(--radius-sm);
    font-size: 12.5px;
}
.file-chosen .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }

.progress {
    height: 5px; background: var(--border); border-radius: 3px; overflow: hidden; margin-top: 8px;
}
.progress-bar { height: 100%; width: 0; background: var(--primary); transition: width .2s; }

/* =====================================================================
   Score display
   ===================================================================== */
.score-ring {
    display: inline-grid; place-items: center;
    width: 54px; height: 54px;
    border-radius: 50%;
    font-size: 15px; font-weight: 700;
    background: conic-gradient(var(--ring-color, var(--primary)) calc(var(--pct,0) * 1%), var(--border) 0);
    position: relative;
}
.score-ring::before {
    content: ''; position: absolute; inset: 4px;
    background: var(--surface); border-radius: 50%;
}
.score-ring span { position: relative; z-index: 1; }

.score-bars { display: grid; gap: 7px; }
.score-bar-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; font-size: 12.5px; }
.score-bar-label { color: var(--text-2); }
.score-bar-track { grid-column: 1 / -1; height: 5px; background: var(--border); border-radius: 3px; overflow: hidden; }
.score-bar-fill  { height: 100%; background: var(--primary); border-radius: 3px; }

/* =====================================================================
   Filters
   ===================================================================== */
.filters {
    display: grid; grid-template-columns: 1fr; gap: 8px;
    padding: 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 12px;
    box-shadow: var(--shadow);
}
.filters .filter-actions { display: flex; gap: 7px; }
.filters .filter-actions .btn { flex: 1; }

.search-box { position: relative; }
.search-box svg {
    position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    width: 15px; height: 15px; color: var(--text-3); pointer-events: none;
}
.search-box .input { padding-left: 33px; }

/* =====================================================================
   Pagination
   ===================================================================== */
.pagination {
    display: flex; align-items: center; justify-content: center; gap: 5px;
    flex-wrap: wrap;
    margin-top: 14px;
}
.pagination a, .pagination span {
    min-width: 32px; padding: 6px 9px;
    text-align: center;
    font-size: 12.5px; font-weight: 600;
    border: 1px solid var(--border-2);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-2);
}
.pagination a:hover { background: var(--surface-2); text-decoration: none; }
.pagination svg { width: var(--icon-sm); height: var(--icon-sm); }
.pagination .current { background: var(--brand); border-color: var(--brand); color: #fff; }
.pagination .disabled { opacity: .45; pointer-events: none; }
.pagination .info { border: 0; background: transparent; color: var(--text-3); font-weight: 400; }

/* =====================================================================
   Dropdown (notification bell)
   ===================================================================== */
.dropdown { position: relative; }
.dropdown-panel {
    position: absolute; top: calc(100% + 7px); right: 0; z-index: 60;
    width: min(340px, calc(100vw - 20px));
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    display: none;
}
.dropdown-panel.open { display: block; }
.dropdown-head {
    display: flex; align-items: center; gap: 8px;
    padding: 10px 13px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}
.dropdown-head h3 { flex: 1; font-size: 13px; }
.dropdown-head > svg { width: var(--icon-sm); height: var(--icon-sm); color: var(--text-3); }
.dropdown-list { max-height: min(58vh, 400px); overflow-y: auto; }

.notif-item {
    display: flex; gap: 9px;
    padding: 10px 13px;
    border-bottom: 1px solid var(--border);
    color: inherit;
}
.notif-item:last-child { border-bottom: 0; }
.notif-item:hover { background: var(--surface-2); text-decoration: none; }
.notif-item.unread { background: var(--primary-bg); }
.notif-item.unread:hover { background: var(--primary-bg); filter: brightness(.98); }
.notif-dot {
    width: 8px; height: 8px; flex: 0 0 8px; margin-top: 5px;
    border-radius: 50%; background: var(--border-2);
}
.notif-item.unread .notif-dot { background: var(--primary); }
.notif-dot.info { background: var(--blue); }
.notif-dot.success { background: var(--green); }
.notif-dot.warning { background: var(--amber); }
.notif-dot.danger { background: var(--red); }
.notif-main { flex: 1; min-width: 0; }
.notif-title { font-size: 12.5px; font-weight: 640; line-height: 1.35; }
.notif-body  { font-size: 12px; color: var(--text-2); margin-top: 2px; }
.notif-time  { font-size: 11px; color: var(--text-3); margin-top: 3px; }

/* =====================================================================
   Modal
   ===================================================================== */
.modal-backdrop {
    position: fixed; inset: 0; z-index: 100;
    background: rgba(15,23,42,.5);
    display: none;
    align-items: center; justify-content: center;
    padding: 14px;
    overflow-y: auto;
}
.modal-backdrop.open { display: flex; }
.modal {
    background: var(--surface);
    border-radius: 14px;
    box-shadow: var(--shadow-lg);
    width: 100%; max-width: 520px;
    max-height: calc(100vh - 28px);
    display: flex; flex-direction: column;
    overflow: hidden;
}
.modal-head {
    display: flex; align-items: center; gap: 8px;
    padding: 13px 16px;
    border-bottom: 1px solid var(--border);
}
.modal-head h2 { flex: 1; font-size: 15px; }
.modal-head > svg { width: var(--icon-nav); height: var(--icon-nav); color: var(--text-3); }
/* min-height: 0 is what makes this scroll instead of pushing the footer out.
   A flex item defaults to min-height:auto, which refuses to shrink below its
   content - so overflow-y:auto never engaged, the body stayed full height,
   and .modal's overflow:hidden clipped the footer away. On a 720px screen the
   New User modal ended at 708 and its Save button was drawn at 733: present
   in the HTML, invisible on screen, and impossible to click. */
/* Most modals wrap their contents in a <form>, which made .modal a flex
   container with exactly ONE child - the form - and left the head, body and
   foot as grandchildren the flex layout never reached. The form grew to its
   content height (759px inside a 671px modal) and .modal overflow:hidden
   clipped the last 57px away: the Save button existed in the HTML, was drawn
   below the visible box, and could not be clicked.
   display:contents removes the form's own box while leaving the form itself
   working, so its children become the flex items they were always meant to be. */
.modal > form,
.modal > div > form { display: contents; }

.modal-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 16px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.modal-foot {
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    display: flex; gap: 8px; justify-content: flex-end;
}
.modal-close {
    width: 30px; height: 30px;
    display: grid; place-items: center;
    background: transparent; border: 0; border-radius: 7px;
    color: var(--text-3); cursor: pointer; font-size: 19px; line-height: 1;
}
.modal-close:hover { background: var(--surface-2); color: var(--text); }

/* =====================================================================
   Tabs
   ===================================================================== */
.tabs {
    display: flex; gap: 2px;
    overflow-x: auto;
    border-bottom: 1px solid var(--border);
    margin-bottom: 13px;
    -webkit-overflow-scrolling: touch;
}
.tab {
    flex: 0 0 auto;
    padding: 9px 13px;
    font-size: 13px; font-weight: 600;
    color: var(--text-3);
    border-bottom: 2px solid transparent;
    white-space: nowrap;
    background: none; border-top: 0; border-left: 0; border-right: 0;
    cursor: pointer; font-family: inherit;
}
.tab:hover { color: var(--text-2); text-decoration: none; }
.tab.active { color: var(--primary); border-bottom-color: var(--primary); }
.tab svg { width: var(--icon-sm); height: var(--icon-sm); }
.tab .count {
    display: inline-block; margin-left: 4px; padding: 0 5px;
    background: var(--border); color: var(--text-2);
    font-size: 10.5px; font-weight: 700; border-radius: 8px;
}
.tab.active .count { background: var(--primary); color: #fff; }

.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* =====================================================================
   Detail rows (key/value)
   ===================================================================== */
.kv { display: grid; grid-template-columns: 1fr; gap: 0; }
.kv-row {
    display: grid; grid-template-columns: 118px 1fr; gap: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--border);
    font-size: 13px;
}
.kv-row:last-child { border-bottom: 0; }
.kv-key { color: var(--text-3); font-size: 12px; }
.kv-val { color: var(--text); font-weight: 500; word-break: break-word; }
.kv-val svg, .timeline svg { width: var(--icon-sm); height: var(--icon-sm); }

/* =====================================================================
   Login page
   ===================================================================== */
.login-page {
    position: relative;
    isolation: isolate;
    /* Keep the animated background inside the viewport without blocking
       access to the lower part of a tall login form on mobile screens. */
    overflow-x: hidden;
    overflow-y: auto;
    min-height: 100vh;
    display: grid; place-items: center;
    padding: 20px 14px;
    background:
        radial-gradient(1000px 520px at 15% -10%, color-mix(in srgb, var(--brand) 12%, transparent), transparent 62%),
        radial-gradient(820px 460px at 110% 110%, color-mix(in srgb, var(--brand) 9%, transparent), transparent 62%),
        var(--bg);
}
.login-orb {
    position: fixed; z-index: -1; width: 42vmax; height: 42vmax; border-radius: 50%;
    filter: blur(18px); opacity: .18; pointer-events: none;
    background: radial-gradient(circle at 35% 35%, #ff6b78, var(--brand) 52%, transparent 70%);
    animation: loginFloat 8s ease-in-out infinite alternate;
}
.login-orb-one { top: -26vmax; left: -14vmax; }
.login-orb-two { right: -20vmax; bottom: -29vmax; opacity: .12; animation-delay: -7s; }
@keyframes loginFloat { to { transform: translate(9vmax, 6vmax) scale(1.12); } }
@media (prefers-reduced-motion: reduce) { .login-orb { animation: none; } }
.login-card {
    position: relative; z-index: 1;
    width: 100%; max-width: 390px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
    padding: 26px 22px;
}
/* Sign-in screen shows the V-Recruit lockup. */
.login-logo {
    width: 152px;
    display: block;
    margin: 0 auto 16px;
}
.login-logo img, .login-logo svg { width: 100%; height: auto; display: block; }
/* The round mark on its own - sign-in, offline and access-denied pages. */
.login-logo.compact { width: 78px; }

.login-card h1 { text-align: center; font-size: 18px; }
.login-card .sub { text-align: center; color: var(--text-2); font-size: 12.5px; margin: 4px 0 20px; }

.demo-accounts {
    margin-top: 18px; padding-top: 16px;
    border-top: 1px dashed var(--border-2);
}
.demo-accounts h4 {
    font-size: 11px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
    color: var(--text-3); margin-bottom: 8px; text-align: center;
}
.demo-btn {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 7px 10px; margin-bottom: 5px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-family: inherit; font-size: 12px; text-align: left;
    color: var(--text-2); cursor: pointer;
}
.demo-btn:hover { border-color: var(--primary); color: var(--text); }
.demo-btn strong { font-size: 12.5px; color: var(--text); }
.demo-btn svg { width: var(--icon-sm); height: var(--icon-sm); }
.demo-btn .role { margin-left: auto; }

.notif-row { display: flex; align-items: stretch; border-bottom: 1px solid var(--border); }
.notif-row .notif-item { flex: 1; border-bottom: 0; }
.notif-delete { display: grid; place-items: center; padding-right: 8px; }
.list-item-action { padding: 0 12px 12px; }
.approval-decision-note { background: var(--green-bg); border-left: 3px solid var(--green); padding: 10px 12px; border-radius: 0 6px 6px 0; }

/* =====================================================================
   Utilities
   ===================================================================== */
.muted     { color: var(--text-3); }
.small     { font-size: 12px; }
.xsmall    { font-size: 11px; }
.bold      { font-weight: 650; }
.mono      { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .92em; }
.nowrap    { white-space: nowrap; }
.truncate  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.center    { text-align: center; }
.right     { text-align: right; }
.flex      { display: flex; }
.flex-col  { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: 4px; }  .gap-2 { gap: 8px; }  .gap-3 { gap: 12px; }
.wrap  { flex-wrap: wrap; }
.grow  { flex: 1; min-width: 0; }
.mt-0 { margin-top: 0; }    .mt-1 { margin-top: 6px; }   .mt-2 { margin-top: 12px; }  .mt-3 { margin-top: 18px; }
.mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: 6px; } .mb-2 { margin-bottom: 12px; } .mb-3 { margin-bottom: 18px; }
.hidden { display: none !important; }
.divider { height: 1px; background: var(--border); margin: 12px 0; }

/* =====================================================================
   Responsive - tablet and up
   ===================================================================== */
@media (min-width: 640px) {
    .stats { grid-template-columns: repeat(4, 1fr); }
    .form-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
    .filters { grid-template-columns: 2fr 1fr 1fr auto; align-items: end; }
    .filters .filter-actions { grid-column: auto; }
    .kv-row { grid-template-columns: 150px 1fr; }
    h1 { font-size: 21px; }
}

@media (min-width: 900px) {
    :root { --header-h: 56px; }

    .menu-toggle { display: none; }

    .sidebar { transform: translateX(0); }
    .sidebar-backdrop { display: none; }

    .main {
        margin-left: var(--sidebar-w);
        max-width: calc(1180px + var(--sidebar-w));
        padding: calc(var(--header-h) + 18px) 20px 40px;
    }

    .bottomnav { display: none; }

    .form-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
    .stats { gap: 12px; }
}

@media (min-width: 1100px) {
    .split { display: grid; grid-template-columns: 1fr 340px; gap: 14px; align-items: start; }
    .split-main { min-width: 0; }
    .split-side { min-width: 0; }
}

/* =====================================================================
   Print - used by the Letter of Offer page
   ===================================================================== */
@media print {
    .topbar, .sidebar, .bottomnav, .sidebar-backdrop, .no-print, .page-actions { display: none !important; }
    body { background: #fff; color: #000; font-size: 12pt; }
    .main { margin: 0; padding: 0; max-width: none; }
    .card { border: 0; box-shadow: none; margin: 0; }
    a { color: #000; text-decoration: none; }
    @page { margin: 18mm 16mm; }
}


/* =====================================================================
   Theme toggle
   Shows a sun in dark mode and a moon in light mode - the icon indicates
   what pressing it will switch TO, which is what users expect.
   ===================================================================== */
.theme-toggle .icon-sun  { display: none; }
.theme-toggle .icon-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-sun  { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .icon-sun  { display: block; }
    :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}

/* ---------------------------------------------------------------------
   Theme switching guard

   Chrome keeps the OLD computed colour when a transitioned property gets
   its value from a custom property that has just changed - so a button
   with `transition: background` plus `background: var(--brand)` stays the
   previous red after the theme flips. Suppressing every transition for the
   one frame the switch happens fixes it while keeping hover animations.
   --------------------------------------------------------------------- */
:root.theme-switching *,
:root.theme-switching *::before,
:root.theme-switching *::after {
    transition: none !important;
    animation-duration: 0s !important;
}

/* NOTE: do not add a background-color transition here.
   Animating a background whose value comes from a custom property that
   changes at the same moment leaves Chrome showing the OLD colour
   indefinitely - the topbar stayed dark in light mode because of exactly
   that. Theme switching is instant on purpose. */


/* =====================================================================
   ==  REFINEMENT LAYER  ==============================================
   =====================================================================

   Everything above defines the system: colours, spacing, components.
   Everything below refines how it FEELS: depth, motion, focus, and the
   small affordances that tell you a thing is interactive before you
   touch it.

   It is a separate layer, appended last, for two reasons:
     1. It relies on the cascade rather than editing rules in place, so
        the component definitions above stay readable as definitions.
     2. If any of this is ever unwanted, deleting from the banner down
        returns the UI to its previous state with nothing else to undo.

   ONE RULE THAT MUST NOT BE BROKEN
   Do not add a transition to any `background` whose value comes from a
   custom property. Chrome keeps the old computed colour when the property
   changes at the same moment, which is what left the top bar stuck dark
   in light mode. See the note at the bottom of the theme section.
   ===================================================================== */

:root {
    /* Motion. One duration scale and one easing curve, so nothing in the
       interface moves at a speed the rest of it does not. The curve is a
       gentle ease-out with no overshoot: fast to start, settling at the
       end, which reads as responsive rather than floaty. */
    --ease:     cubic-bezier(.22, .61, .36, 1);
    --dur-fast: .12s;
    --dur:      .2s;
    --dur-slow: .38s;

    /* A hairline ring drawn with a shadow rather than a border, so adding
       it never changes an element size and never shifts layout. */
    --ring:       0 0 0 1px rgba(20, 24, 31, .05);
    --elev-hover: 0 2px 4px rgba(20,24,31,.05), 0 8px 20px rgba(20,24,31,.09);
    --elev-brand: 0 1px 2px rgba(163,31,36,.22), 0 6px 16px rgba(163,31,36,.20);
}
:root[data-theme="dark"] {
    --ring:       0 0 0 1px rgba(255,255,255,.05);
    --elev-hover: 0 2px 4px rgba(0,0,0,.35), 0 10px 24px rgba(0,0,0,.5);
    --elev-brand: 0 1px 2px rgba(0,0,0,.4), 0 6px 16px rgba(226,96,106,.22);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --ring:       0 0 0 1px rgba(255,255,255,.05);
        --elev-hover: 0 2px 4px rgba(0,0,0,.35), 0 10px 24px rgba(0,0,0,.5);
        --elev-brand: 0 1px 2px rgba(0,0,0,.4), 0 6px 16px rgba(226,96,106,.22);
    }
}

/* ---------------------------------------------------------------------
   Typography polish
   --------------------------------------------------------------------- */

/* Headings set slightly tighter than body text. At 19-21px the default
   tracking looks loose next to 14px body copy. */
h1 { letter-spacing: -.3px; }
h2 { letter-spacing: -.2px; }

/* Stop a two-word heading breaking to leave one word alone on line two.
   Unsupported browsers simply ignore it. */
h1, h2, .empty h3 { text-wrap: balance; }

/* Every number a reader compares down a column needs fixed-width digits,
   otherwise 11 is visibly narrower than 88 and the column looks ragged.
   Applied to the places numbers actually line up. */
.stat-value,
.score-ring,
.pipeline-step .n,
.badge .count,
.nav-item .count,
.tab .count,
.mono {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* Selected text picks up the brand instead of the browser default blue,
   which clashes with red on every page. */
::selection { background: color-mix(in srgb, var(--brand) 24%, transparent); }

/* ---------------------------------------------------------------------
   Scrollbars

   Only the WebKit form is styled. Firefox uses scrollbar-width/-color,
   already set where it matters, and leaving the rest to the platform is
   better than a half-matching custom scrollbar.
   --------------------------------------------------------------------- */
* { scrollbar-color: var(--border-2) transparent; }

::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--border-2);
    border: 3px solid transparent;   /* inset the thumb without extra elements */
    border-radius: 8px;
    background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-3); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* ---------------------------------------------------------------------
   Focus visibility

   :focus-visible, not :focus - a mouse click should not leave a ring
   behind, but every keyboard path must be traceable. One consistent ring
   across links, buttons, tabs and rows so the eye learns one shape.
   --------------------------------------------------------------------- */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.tab:focus-visible,
.nav-item:focus-visible,
.list-item:focus-visible,
.stat:focus-visible,
.pipeline-step:focus-visible,
.demo-btn:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
    text-decoration: none;
}

/* Inputs already show a brand ring through box-shadow on :focus, so an
   outline on top would double it. */
.input:focus-visible, .select:focus-visible, .textarea:focus-visible { outline: none; }

/* ---------------------------------------------------------------------
   Entrance motion

   Deliberately limited to the page top-level blocks. Animating every
   card, row and badge produces a screen that appears to be still loading
   after it has finished; animating the four or five blocks a person
   actually scans reads as the page assembling itself and then stopping.

   `both` keeps the pre-animation state during the delay, so nothing
   flashes at full opacity before its turn.
   --------------------------------------------------------------------- */
@keyframes vrRise { from { opacity: 0; transform: translateY(7px); } }
@keyframes vrFade { from { opacity: 0; } }

.main > .page-head,
.main > .stats,
.main > .filters,
.main > .tabs,
.main > .card,
.main > .pipeline,
.tab-panel.active > .card {
    animation: vrRise var(--dur-slow) var(--ease) both;
}

/* Stagger the stat tiles individually - they sit side by side, so a single
   block animation would not read as four separate figures arriving. */
.stats > .stat { animation: vrRise var(--dur-slow) var(--ease) both; }
.stats > .stat:nth-child(2) { animation-delay: .05s; }
.stats > .stat:nth-child(3) { animation-delay: .1s; }
.stats > .stat:nth-child(4) { animation-delay: .15s; }
.stats > .stat:nth-child(n+5) { animation-delay: .2s; }

/* NOTE: there is deliberately no per-card cascade here.
   The obvious way to write one is .main > .card:nth-of-type(2), but
   :nth-of-type counts ELEMENTS of the same tag among the siblings, not
   elements matching the class - and .main is full of other divs (page head,
   stats, filters, alerts, tabs). A lone card on a list page was picking up
   a third-card delay it had not earned, and CSS has no nth-of-class to fix
   it with. The stats stagger below is safe because .stats contains nothing
   but .stat, so there nth-child really does mean what it reads as. */

/* The single most important accessibility rule in this file. Someone with
   vestibular sensitivity gets a static interface, and everything below
   still works because none of it depends on the animation running. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ---------------------------------------------------------------------
   Cards - depth on hover
   --------------------------------------------------------------------- */
.card {
    box-shadow: var(--shadow), var(--ring);
    transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* Only cards that lead somewhere lift. A static panel that moves under
   the cursor suggests it can be clicked when it cannot. */
a.card:hover, .card.clickable:hover {
    border-color: var(--border-2);
    box-shadow: var(--elev-hover), var(--ring);
}

/* ---------------------------------------------------------------------
   Stat tiles

   Each tile gets a coloured rail along its top edge, scaled to nothing
   until hover. It gives the row of figures a quiet colour identity, and
   the growth on hover confirms the tile is a link to a filtered list.
   --------------------------------------------------------------------- */
.stat {
    position: relative;
    overflow: hidden;
    box-shadow: var(--shadow), var(--ring);
    transition: box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.stat::before {
    content: '';
    position: absolute; top: 0; left: 0; right: 0; height: 3px;
    background: var(--stat-accent, var(--brand));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--dur-slow) var(--ease);
}
.stat:hover::before, .stat:focus-visible::before { transform: scaleX(1); }

.stat:hover {
    border-color: var(--border-2);
    box-shadow: var(--elev-hover), var(--ring);
    transform: translateY(-2px);
}

.stat.accent-blue   { --stat-accent: var(--blue); }
.stat.accent-green  { --stat-accent: var(--green); }
.stat.accent-amber  { --stat-accent: var(--amber); }
.stat.accent-violet { --stat-accent: var(--violet); }
.stat.accent-red    { --stat-accent: var(--red); }

/* The label icon takes the tile accent so the colour appears twice - once
   in the figure, once in the label - and reads as intentional. */
.stat.accent-blue   .stat-label svg,
.stat.accent-green  .stat-label svg,
.stat.accent-amber  .stat-label svg,
.stat.accent-violet .stat-label svg,
.stat.accent-red    .stat-label svg { color: var(--stat-accent); }

/* ---------------------------------------------------------------------
   Sidebar navigation

   The active row becomes an inset pill rather than a full-bleed band.
   A pill has an obvious start and end, so at a glance you see one item
   selected instead of a stripe across the panel.
   --------------------------------------------------------------------- */
.nav-item {
    margin: 1px 8px;
    padding: 8px 10px;
    border-left: 0;                       /* the accent bar is drawn below */
    border-radius: 8px;
    position: relative;
    transition: background var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease);
}
.nav-item svg { color: var(--text-3); transition: color var(--dur-fast) var(--ease); }
.nav-item:hover svg { color: var(--text-2); }

.nav-item.active { border-left: 0; }
.nav-item.active svg { color: var(--primary); }

/* A short vertical bar to the left of the active pill, so the selection is
   still findable when the pill tint is subtle in dark mode. */
.nav-item.active::before {
    content: '';
    position: absolute; left: -8px; top: 50%;
    width: 3px; height: 18px;
    margin-top: -9px;
    background: var(--primary);
    border-radius: 0 3px 3px 0;
}

.sidebar-section { padding-left: 18px; }

/* ---------------------------------------------------------------------
   Top bar

   Translucent with a blur, so content scrolling underneath is suggested
   rather than hidden behind a solid slab. The @supports guard matters:
   without backdrop-filter the fallback must stay fully opaque, or text
   would scroll through the bar.
   --------------------------------------------------------------------- */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .topbar {
        background: color-mix(in srgb, var(--topbar-bg) 82%, transparent);
        -webkit-backdrop-filter: blur(14px) saturate(1.5);
        backdrop-filter: blur(14px) saturate(1.5);
    }
}

.brand-mark { transition: transform var(--dur) var(--ease); }
.topbar-brand:hover .brand-mark { transform: scale(1.06); }

.icon-btn { transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
.icon-btn:active { transform: scale(.93); }

/* The unread count pulses twice and stops. An indefinite pulse in the
   corner of the screen is a distraction for the rest of the session. */
@keyframes vrPing { 0%,100% { transform: scale(1); } 50% { transform: scale(1.18); } }
.badge-dot { animation: vrPing 1.1s var(--ease) 2; }

/* ---------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------- */
.btn {
    transition: background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease),
                box-shadow var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease),
                opacity var(--dur-fast) var(--ease);
}
.btn:active { transform: translateY(1px) scale(.985); }

/* The primary action carries a brand-tinted shadow, which separates it
   from the neutral buttons beside it without making it larger. */
.btn-primary  { box-shadow: var(--elev-brand); }
.btn-primary:hover { box-shadow: var(--elev-brand); transform: translateY(-1px); }
.btn-primary:active { transform: translateY(1px) scale(.985); box-shadow: var(--shadow); }

.btn-success:hover, .btn-danger:hover, .btn-warning:hover { transform: translateY(-1px); }

/* A disabled button must not react to hover at all - a lift would promise
   something the click cannot deliver. */
.btn:disabled, .btn.disabled,
.btn:disabled:hover, .btn.disabled:hover { transform: none; box-shadow: none; }

/* ---------------------------------------------------------------------
   Form controls
   --------------------------------------------------------------------- */
.input, .select, .textarea {
    box-shadow: var(--ring);
    transition: border-color var(--dur-fast) var(--ease),
                box-shadow var(--dur-fast) var(--ease);
}
.input:hover:not(:disabled), .select:hover:not(:disabled), .textarea:hover:not(:disabled) {
    border-color: var(--text-3);
}
.input:focus, .select:focus, .textarea:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent);
}
.input.has-error:focus, .select.has-error:focus, .textarea.has-error:focus {
    border-color: var(--danger);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 18%, transparent);
}

/* Star buttons scale under the cursor - the rating is a judgement call and
   should feel like one, not like ticking a box. */
.star-input label {
    transition: transform var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease);
}
.star-input label:hover { transform: scale(1.08); border-color: var(--primary); color: var(--primary); }
.star-input input:checked + label:hover { color: #fff; }

.checkline { transition: color var(--dur-fast) var(--ease); }
.checkline:hover { color: var(--text); }

/* ---------------------------------------------------------------------
   Tabs

   The underline grows from the centre when a tab becomes active, so the
   eye follows the selection across instead of the line simply appearing.
   --------------------------------------------------------------------- */
.tab {
    position: relative;
    border-bottom-color: transparent;
    transition: color var(--dur-fast) var(--ease);
}
.tab.active { border-bottom-color: transparent; }
.tab::after {
    content: '';
    position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
    background: var(--primary);
    transform: scaleX(0);
    transition: transform var(--dur) var(--ease);
}
.tab.active::after { transform: scaleX(1); }
.tab:hover::after { transform: scaleX(.35); }
.tab.active:hover::after { transform: scaleX(1); }
.tab .count { transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }

/* ---------------------------------------------------------------------
   Tables and list rows

   A 2px accent appears on the left edge of the row under the cursor. On a
   long list this marks your place far better than a background tint alone,
   which is easy to lose while reading across.
   --------------------------------------------------------------------- */
table.data tbody tr { transition: background var(--dur-fast) var(--ease); }
table.data tbody td:first-child { position: relative; }
table.data tbody tr:hover td:first-child::before {
    content: '';
    position: absolute; left: 0; top: 0; bottom: 0;
    width: 2px; background: var(--primary);
}

.list-item {
    position: relative;
    transition: background var(--dur-fast) var(--ease), padding-left var(--dur-fast) var(--ease);
}
.list-item::before {
    content: '';
    position: absolute; left: 0; top: 0; bottom: 0;
    width: 2px; background: var(--primary);
    transform: scaleY(0);
    transition: transform var(--dur-fast) var(--ease);
}
a.list-item:hover::before, .list-item:focus-visible::before { transform: scaleY(1); }
a.list-item:hover { padding-left: 17px; }   /* 14 + 3, so the text clears the bar */

/* ---------------------------------------------------------------------
   Badges and avatars
   --------------------------------------------------------------------- */
.badge {
    /* An inset hairline in the badge own colour keeps a pale tint from
       dissolving into the card behind it, especially in dark mode. */
    box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 16%, transparent);
}

/* A top highlight and a bottom shade turn the flat colour disc into a lit
   surface, which is enough to read as a portrait placeholder.
   Done entirely with inset shadows, for two reasons that both rule out the
   obvious alternatives:
     - NOT background-image: every avatar carries an inline
       `background: <colour>` derived from the person's name, and that
       shorthand resets background-image to none, so a stylesheet gradient
       is silently discarded.
     - NOT an ::after overlay: the initials are a bare text node, so there
       is no child element to lift above it with z-index, and a positioned
       pseudo-element paints over in-flow text.
   An inset shadow paints above the background but below the content, which
   is exactly the layer this effect belongs in. */
.avatar {
    box-shadow:
        inset 0  1px 1px rgba(255,255,255,.30),
        inset 0 -1px 1px rgba(0,0,0,.16),
        inset 0  0   0 1px rgba(0,0,0,.08),
                 0  1px 2px rgba(20,24,31,.14);
    text-shadow: 0 1px 1px rgba(0,0,0,.20);
}

/* ---------------------------------------------------------------------
   Alerts and flash messages

   A 3px bar down the leading edge, and a slide-in from above so a message
   that appears after an action is noticed without needing to flash.
   --------------------------------------------------------------------- */
@keyframes vrDrop { from { opacity: 0; transform: translateY(-8px); } }

.alert {
    position: relative;
    padding-left: 15px;
    overflow: hidden;
    animation: vrDrop var(--dur-slow) var(--ease) both;
}
.alert::before {
    content: '';
    position: absolute; left: 0; top: 0; bottom: 0;
    width: 3px; background: currentColor;
}

/* ---------------------------------------------------------------------
   Empty states

   The icon sits inside a tinted disc. Without it the glyph floats in the
   middle of a large blank panel and reads as a rendering fault rather
   than a deliberate "nothing here yet".
   --------------------------------------------------------------------- */
.empty svg {
    opacity: 1;
    color: var(--text-3);
    box-sizing: content-box;
    padding: 15px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 50%;
}

/* ---------------------------------------------------------------------
   Pipeline tiles and pagination
   --------------------------------------------------------------------- */
.pipeline-step {
    box-shadow: var(--ring);
    transition: border-color var(--dur-fast) var(--ease),
                box-shadow var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
}
a.pipeline-step:hover { transform: translateY(-2px); box-shadow: var(--shadow), var(--ring); }
.pipeline-step.current { box-shadow: inset 0 0 0 1px var(--primary), var(--ring); }

.pagination a {
    transition: background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease);
}
.pagination a:hover { border-color: var(--primary); color: var(--primary); }
.pagination .current { box-shadow: var(--elev-brand); }

/* ---------------------------------------------------------------------
   Timeline
   --------------------------------------------------------------------- */

/* The rail fades out at the bottom instead of stopping at a hard edge, so
   it does not read as a list that was cut off. */
.timeline::before {
    background: linear-gradient(to bottom, var(--border) 0%, var(--border) 82%, transparent 100%);
}

/* The most recent entry is the one being looked for, so its dot is larger
   and carries a halo in its own colour. */
.timeline-item:first-child .timeline-dot { width: 13px; height: 13px; left: -26px; }
.timeline-item:first-child .timeline-dot.green  { box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 22%, transparent); }
.timeline-item:first-child .timeline-dot.red    { box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 22%, transparent); }
.timeline-item:first-child .timeline-dot.blue   { box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 22%, transparent); }
.timeline-item:first-child .timeline-dot.amber  { box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber) 22%, transparent); }
.timeline-item:first-child .timeline-dot.violet { box-shadow: 0 0 0 3px color-mix(in srgb, var(--violet) 22%, transparent); }

/* ---------------------------------------------------------------------
   Documents and upload
   --------------------------------------------------------------------- */
.doc-row { transition: background var(--dur-fast) var(--ease); }
.doc-row:hover { background: var(--surface-2); }
.doc-icon { transition: transform var(--dur-fast) var(--ease); }
.doc-row:hover .doc-icon { transform: scale(1.06); }

/* Only the drag state pulses. Making the plain hover pulse would mean the
   zone is always moving as the cursor crosses the page. */
@keyframes vrPulse {
    0%,100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 30%, transparent); }
    50%     { box-shadow: 0 0 0 7px transparent; }
}
.dropzone.dragover { animation: vrPulse 1.1s var(--ease) infinite; }
.progress-bar { transition: width var(--dur-slow) var(--ease); }

/* ---------------------------------------------------------------------
   Scores

   The ring sweeps up to its value on load. A score is a verdict, and
   watching it arrive makes the number register; a ring drawn instantly
   is just a shape with a digit in it.

   @property is what makes this possible - a plain custom property is not
   an animatable type, so without the registration the conic-gradient
   would jump straight to its final angle.
   --------------------------------------------------------------------- */
@property --vr-pct { syntax: "<number>"; inherits: false; initial-value: 0; }

@supports (background: conic-gradient(red calc(var(--x, 0) * 1%), blue 0)) {
    @keyframes vrSweep { from { --vr-pct: 0; } }
    .score-ring {
        --vr-pct: var(--pct, 0);
        background: conic-gradient(var(--ring-color, var(--primary)) calc(var(--vr-pct) * 1%), var(--border) 0);
        animation: vrSweep .85s var(--ease) both;
        box-shadow: var(--ring);
    }
}

.score-bar-fill { transition: width var(--dur-slow) var(--ease); }

/* ---------------------------------------------------------------------
   Modal and dropdown
   --------------------------------------------------------------------- */
@keyframes vrModalIn { from { opacity: 0; transform: translateY(12px) scale(.97); } }

.modal-backdrop.open { animation: vrFade var(--dur) var(--ease) both; }
.modal-backdrop.open .modal {
    animation: vrModalIn var(--dur-slow) var(--ease) both;
    box-shadow: var(--shadow-lg), var(--ring);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .modal-backdrop { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
}

.dropdown-panel { animation: vrRise var(--dur) var(--ease) both; }

/* ---------------------------------------------------------------------
   Login page
   --------------------------------------------------------------------- */
@keyframes vrCardIn { from { opacity: 0; transform: translateY(14px) scale(.985); } }

.login-card {
    animation: vrCardIn .5s var(--ease) both;
    box-shadow: var(--shadow-lg), var(--ring);
}
.login-logo { animation: vrRise .6s var(--ease) .12s both; }
.login-card .form-grid { animation: vrRise .5s var(--ease) .2s both; }
.demo-accounts { animation: vrFade .5s var(--ease) .34s both; }

.demo-btn {
    transition: border-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
}
.demo-btn:hover { transform: translateX(2px); }

/* ---------------------------------------------------------------------
   Stage rail

   Where a candidate sits in the process, drawn as a track rather than a
   single badge. A badge answers "which stage"; the rail also answers "how
   far along, and what is left" - which is the question actually being
   asked when the page is opened.

   Terminal outcomes (rejected, withdrawn, talent pool) are not points on
   this track. They are appended as a final step and the track behind them
   is greyed, because progress did not continue.
   --------------------------------------------------------------------- */
.stage-rail {
    display: flex;
    overflow-x: auto;
    padding: 3px 0 2px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.stage-rail::-webkit-scrollbar { height: 0; }

/* flex-shrink is 0 and the basis is auto, so a step never becomes narrower
   than its own label. The grow factor still spreads them across the full
   width when there is room. Shrinking was the wrong default: on a 375px
   screen the six-step closed rail squeezed "Talent Pool" onto two lines,
   which made the last step taller than the rest and read as a broken row.
   Overflowing into the rail's own horizontal scroll is the better failure. */
/* The numbers matter: at 375px the rail has 322px of room, and the five
   labels plus this padding come to 313px. Going back up to 5px padding put
   it at 323px and cost a pointless 1px scroll on the most common view. */
.stage-rail-step {
    flex: 1 0 auto;
    min-width: 58px;
    position: relative;
    padding: 17px 4px 0;
    text-align: center;
}

/* Two half-width connectors per step rather than one line behind them all,
   so each segment can be coloured independently - that is what shows how
   far the track has been travelled. */
.stage-rail-step::before,
.stage-rail-step::after {
    content: '';
    position: absolute; top: 5px; height: 2px;
    background: var(--border);
}
.stage-rail-step::before { left: 0;   right: 50%; }
.stage-rail-step::after  { left: 50%; right: 0; }
.stage-rail-step:first-child::before,
.stage-rail-step:last-child::after { display: none; }

.stage-rail-dot {
    position: absolute; top: 0; left: 50%;
    width: 12px; height: 12px;
    margin-left: -6px;
    display: grid; place-items: center;
    background: var(--surface);
    border: 2px solid var(--border-2);
    border-radius: 50%;
    z-index: 1;
}
.stage-rail-dot svg { width: 7px; height: 7px; color: #fff; }

.stage-rail-label {
    display: block;
    font-size: 10px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
    color: var(--text-3);
    white-space: nowrap;   /* see the note on .stage-rail-step */
}

/* ---- Passed ---- */
.stage-rail-step.is-done .stage-rail-dot { background: var(--primary); border-color: var(--primary); }
.stage-rail-step.is-done::before,
.stage-rail-step.is-done::after { background: var(--primary); }
.stage-rail-step.is-done .stage-rail-label { color: var(--text-2); }

/* ---- Where they are now ---- */
.stage-rail-step.is-current .stage-rail-dot {
    background: var(--primary);
    border-color: var(--primary);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 20%, transparent);
}
.stage-rail-step.is-current::before { background: var(--primary); }
.stage-rail-step.is-current .stage-rail-label { color: var(--primary); }

/* ---- Ended here ----
   --rail-end is set inline per candidate from the stage's own colour token,
   so a rejection closes in red while talent pool closes in teal. The fallback
   only applies if the markup omitted it. */
.stage-rail-step.is-closed .stage-rail-dot {
    background: var(--rail-end, var(--danger));
    border-color: var(--rail-end, var(--danger));
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--rail-end, var(--danger)) 18%, transparent);
}
.stage-rail-step.is-closed .stage-rail-label { color: var(--rail-end, var(--danger)); }

/* A closed application greys the whole track: the steps were not reached,
   and showing them in brand red would imply they were. */
.stage-rail.is-ended .stage-rail-step:not(.is-closed) .stage-rail-dot {
    border-color: var(--border);
    background: var(--surface-2);
}
.stage-rail.is-ended .stage-rail-step:not(.is-closed)::before,
.stage-rail.is-ended .stage-rail-step:not(.is-closed)::after { background: var(--border); }

/* ---------------------------------------------------------------------
   Print

   Nothing may animate or be translucent on paper. Cards printed with a
   blurred bar or mid-animation opacity come out faint or clipped.
   --------------------------------------------------------------------- */
@media print {
    *, *::before, *::after { animation: none !important; transition: none !important; opacity: 1 !important; }
    .card, .stat, .modal { box-shadow: none !important; transform: none !important; }
    .empty svg { background: none; border: 0; }
}


/* =====================================================================
   ==  GRAPHICS LAYER  ================================================
   =====================================================================

   The refinement layer above makes the existing components feel better.
   This layer adds things that are there to be LOOKED at: the sign-in
   backdrop, the dashboard banner, the funnel bar, stage colour on list
   rows.

   Restraint rule for everything in here: staff have this open for a whole
   shift. Decoration is allowed on the screens they pass through (sign-in)
   and on the summary they scan (dashboard banner). It is not allowed on
   the screens they work in - a candidate record and a form stay plain.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Sign-in backdrop

   A pipeline drawn as five nodes on a rising path, with pulses running
   along it. Sits behind the card at low opacity: it has to survive being
   looked past, not looked at.
   --------------------------------------------------------------------- */
.login-scene {
    position: fixed; inset: 0; z-index: -1;
    display: grid; place-items: center;
    pointer-events: none;
    opacity: .5;
}
.login-scene svg {
    /* Overrides the global 14px svg default, which exists to stop unsized
       inline icons stretching. This one is meant to be large. */
    width: min(880px, 94vmin);
    height: min(880px, 94vmin);
}

/* Texture. Faint enough that it reads as paper grain rather than dots. */
.login-scene-dot { fill: var(--brand); opacity: .1; }

/* The unlit line. */
.login-scene-track {
    fill: none;
    stroke: var(--brand);
    stroke-width: 2;
    stroke-linecap: round;
    opacity: .2;
}

/* A travelling pulse. 50 of 1000 path units lit, the rest gap, and the
   whole dash pattern slides from one end to the other. */
@keyframes vrPulseTravel { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }

.login-scene-pulse {
    fill: none;
    stroke: var(--brand);
    stroke-width: 3.5;
    stroke-linecap: round;
    stroke-dasharray: 50 950;
    animation: vrPulseTravel 6s linear infinite;
}

/* Each node brightens as a pulse reaches it, then settles back. */
@keyframes vrNodeLight {
    0%, 62%, 100% { opacity: .34; r: 7; }
    12%           { opacity: 1;   r: 9; }
}
.login-scene-node {
    fill: var(--brand);
    animation: vrNodeLight 6s var(--ease) infinite;
}

/* The ring that expands away from a node as it lights. */
@keyframes vrNodeHalo {
    0%        { opacity: .5; transform: scale(.6); }
    45%, 100% { opacity: 0;  transform: scale(2.6); }
}
.login-scene-halo {
    fill: none;
    stroke: var(--brand);
    stroke-width: 1.5;
    transform-box: fill-box;      /* scale about the circle, not the SVG origin */
    transform-origin: center;
    animation: vrNodeHalo 6s var(--ease) infinite;
}

@media (prefers-reduced-motion: reduce) {
    /* The composition still reads with nothing moving, so the scene stays
       rather than being hidden - one pulse is simply parked mid-line. */
    .login-scene-pulse, .login-scene-node, .login-scene-halo { animation: none; }
    .login-scene-node { opacity: .7; }
    .login-scene-halo { opacity: 0; }
}

/* On a short phone screen the form fills the viewport and the scene would
   only be visible as noise behind text. */
@media (max-height: 620px) {
    .login-scene { display: none; }
}

/* ---------------------------------------------------------------------
   Dashboard banner

   The dashboard page head becomes a brand panel. It is the same markup
   every other page uses - title, subtitle, actions - re-skinned by one
   class, so nothing had to be duplicated into dashboard.php to get it.
   --------------------------------------------------------------------- */
.page-head-hero {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: 20px 18px;
    border-radius: 14px;
    background: linear-gradient(118deg, var(--brand-700) 0%, var(--brand) 52%, #c8383d 100%);
    box-shadow: var(--elev-brand);
    align-items: center;
}

/* Two soft lights, so the panel is not a flat wash of red. */
.page-head-hero::before {
    content: '';
    position: absolute; z-index: -1;
    top: -55%; right: -12%;
    width: 60%; aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255,255,255,.30), transparent 62%);
}
.page-head-hero::after {
    content: '';
    position: absolute; z-index: -1;
    bottom: -70%; left: 22%;
    width: 52%; aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255,255,255,.15), transparent 64%);
}

.page-head-hero h1 { color: #fff; }
.page-head-hero .page-sub { color: rgba(255,255,255,.9); font-size: 13px; }
.page-head-hero .page-sub strong { color: #fff; }
.page-head-hero .bi-bm { color: rgba(255,255,255,.72); }

/* .muted resolves to a grey that disappears on red, so it is re-pointed
   here rather than the subtitle being rewritten for this one page. */
.page-head-hero .muted { color: rgba(255,255,255,.78); }

/* The role chip cannot keep its violet/amber tint on a red panel. Glass
   instead: same shape, readable, no second colour introduced. */
.page-head-hero .badge {
    background: rgba(255,255,255,.18);
    color: #fff;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,.28);
}

/* A red primary button on a red panel would vanish, so it inverts.

   The text colour is a literal, NOT var(--brand-700). That token is lifted
   to a pale pink in dark mode so red stays legible on a dark background -
   correct everywhere else, and wrong here, because this button is white in
   both themes. Using the token gave pale pink text on white: measured at
   rgb(242,150,156) on #fff, nowhere near readable. The panel behind it is
   hardcoded per theme for the same reason, so the pair stays consistent. */
.page-head-hero .btn-primary {
    background: #fff;
    border-color: #fff;
    color: #8a1a1f;
    box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 4px 12px rgba(0,0,0,.16);
}
.page-head-hero .btn-primary:hover { background: rgba(255,255,255,.9); border-color: rgba(255,255,255,.9); }
.page-head-hero .btn-primary svg { color: #8a1a1f; }

/* Any non-primary button in the banner becomes an outline. */
.page-head-hero .btn:not(.btn-primary) {
    background: rgba(255,255,255,.12);
    border-color: rgba(255,255,255,.34);
    color: #fff;
}
.page-head-hero .btn:not(.btn-primary):hover { background: rgba(255,255,255,.2); }

.page-head-hero a:focus-visible, .page-head-hero .btn:focus-visible {
    outline-color: #fff;
}

/* Dark mode already dims --brand-700; keep the panel from going muddy. */
:root[data-theme="dark"] .page-head-hero {
    background: linear-gradient(118deg, #7a1a20 0%, #a8262d 52%, #c04048 100%);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .page-head-hero {
        background: linear-gradient(118deg, #7a1a20 0%, #a8262d 52%, #c04048 100%);
    }
}

/* ---------------------------------------------------------------------
   Funnel bar

   One bar showing how the pipeline is distributed, in the stage colours
   already used by the badges and tiles. The tiles above it give the exact
   figures; this gives the shape - whether work is piling up at screening,
   whether anything is reaching offer at all.
   --------------------------------------------------------------------- */
.funnel {
    display: flex;
    height: 10px;
    margin-top: 14px;
    border-radius: 6px;
    overflow: hidden;
    background: var(--border);
    box-shadow: var(--ring);
}
.funnel-seg {
    /* Width is set inline from the real proportion. min-width keeps a
       single candidate in a stage from becoming an invisible sliver. */
    min-width: 4px;
    background: var(--seg, var(--slate));
    transition: filter var(--dur-fast) var(--ease);
}
.funnel-seg:hover { filter: brightness(1.15); }

@keyframes vrFunnelGrow { from { transform: scaleX(0); } }
.funnel-seg {
    transform-origin: left;
    animation: vrFunnelGrow .6s var(--ease) both;
}
.funnel-seg:nth-child(2) { animation-delay: .06s; }
.funnel-seg:nth-child(3) { animation-delay: .12s; }
.funnel-seg:nth-child(4) { animation-delay: .18s; }
.funnel-seg:nth-child(n+5) { animation-delay: .24s; }

.funnel-key {
    display: flex; flex-wrap: wrap; gap: 4px 14px;
    margin-top: 10px;
    font-size: 11.5px; color: var(--text-2);
}
.funnel-key span { display: inline-flex; align-items: center; gap: 5px; }
.funnel-key i {
    width: 8px; height: 8px; border-radius: 3px;
    background: var(--seg, var(--slate));
}

/* ---------------------------------------------------------------------
   Stage colour on candidate rows

   The badge already names the stage. This puts the same colour on the
   row's leading edge so a list can be read by shape - you see three
   violet rows clustered together without reading a single word.

   Opt-in via .stage-edge, because .list-item is also used by interviews,
   notifications and approvals, where the accent would mean nothing.
   --------------------------------------------------------------------- */
.list-item.stage-edge::before {
    width: 3px;
    background: var(--row-accent, var(--primary));
    transform: scaleY(1);         /* always on, not only on hover */
}
a.list-item.stage-edge { padding-left: 17px; }
a.list-item.stage-edge:hover { padding-left: 19px; }
a.list-item.stage-edge:hover::before { width: 4px; }

/* ---------------------------------------------------------------------
   Empty states - dashed halo

   A ring of dashes around the icon disc. It reads as an outline waiting to
   be filled, which is what an empty list is.

   Drawn with `outline` rather than an absolutely-positioned pseudo-element.
   The pseudo-element version had to be placed with a hardcoded `top`, which
   assumed .empty's padding was always 34px - and it is not: the copy inside
   the notification dropdown uses 26px, so the ring sat 13px below the disc
   it was supposed to surround. An outline is anchored to the element's own
   border box, follows its border-radius, and needs no opt-out rules for
   panels that pad differently.
   --------------------------------------------------------------------- */
.empty svg {
    outline: 1.5px dashed var(--border-2);
    outline-offset: 8px;
    /* The ring extends ~10px past the 62px disc, so the gap to the heading
       below has to clear it or the two would touch. */
    margin-bottom: 16px;
}


/* ---------------------------------------------------------------------
   Branch auto-fill panel

   Appears under the branch dropdown on the candidate form and confirms
   which place of work was picked. It is read-only on purpose: the details
   live in Settings, and letting them be edited here would mean two places
   claiming to hold the same fact.
   --------------------------------------------------------------------- */
.branch-info {
    padding: 11px 13px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-left: 3px solid var(--primary);
    border-radius: var(--radius-sm);
}

.branch-info-head {
    display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
    margin-bottom: 9px;
}
.branch-info-head svg { color: var(--primary); }
.branch-info-head strong { font-size: 13.5px; }

.branch-info-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 7px 18px;
    font-size: 12.5px;
}
@media (min-width: 640px) {
    .branch-info-grid { grid-template-columns: 1fr 1fr; }
}

.branch-info-key {
    display: block;
    font-size: 10.5px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
    color: var(--text-3);
    margin-bottom: 1px;
}


/* ---------------------------------------------------------------------
   Drawer

   A panel that slides in from the right edge. Used where a form is too
   long for a centred modal but does not deserve its own page - the role
   editor is twenty-one checkboxes plus six fields, which squeezed inline
   made the list underneath it feel cramped.

   Why not reuse .modal: a modal is centred and sized to its content, so a
   tall form either overflows the viewport or forces the whole page to
   scroll behind it. A drawer is full-height by definition and scrolls
   inside itself, which is the right shape for a long form.
   --------------------------------------------------------------------- */
.drawer-backdrop {
    position: fixed; inset: 0; z-index: 100;
    background: rgba(15, 23, 42, .5);
    display: none;
}
.drawer-backdrop.open { display: block; }

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .drawer-backdrop { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
}

.drawer {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 101;
    width: min(640px, 100vw);
    display: flex; flex-direction: column;
    background: var(--surface);
    border-left: 1px solid var(--border);
    box-shadow: var(--shadow-lg);

    /* Parked off-screen rather than display:none, so the slide has
       something to animate from. */
    transform: translateX(100%);
    transition: transform var(--dur-slow) var(--ease);
}
.drawer.open { transform: translateX(0); }

/* On a phone the panel is the whole screen; a peek of the page behind it
   would just be a strip too narrow to read. */
@media (max-width: 560px) {
    .drawer { width: 100vw; border-left: 0; }
}

.drawer-head {
    display: flex; align-items: center; gap: 9px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
    flex: 0 0 auto;
}
.drawer-head > svg { width: var(--icon-md); height: var(--icon-md); color: var(--text-3); }
.drawer-head h2 { flex: 1; min-width: 0; font-size: 15px; }

.drawer-close {
    width: 32px; height: 32px;
    display: grid; place-items: center;
    background: transparent; border: 0; border-radius: 8px;
    color: var(--text-3); cursor: pointer; font-size: 20px; line-height: 1;
    text-decoration: none;
    transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.drawer-close:hover { background: var(--border); color: var(--text); text-decoration: none; }

/* The only scrolling region. Keeping the header and footer fixed means the
   Save button never scrolls out of reach on a long form. */
.drawer-body {
    flex: 1 1 auto;
    min-height: 0;          /* see the note on .modal-body - same trap */
    overflow-y: auto;
    padding: 16px;
    -webkit-overflow-scrolling: touch;
}

.drawer-foot {
    flex: 0 0 auto;
    display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    /* Clears the home indicator on a phone. */
    padding-bottom: max(12px, env(safe-area-inset-bottom));
}
.drawer-foot .btn { flex: 0 0 auto; }

@media (max-width: 560px) {
    .drawer-foot .btn { flex: 1 1 auto; }
}

/* A section heading inside a drawer body. */
.drawer-section {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; flex-wrap: wrap;
    margin: 18px 0 10px;
}
.drawer-section:first-child { margin-top: 0; }
.drawer-section h3 { font-size: 13.5px; }

/* Permission groups. fieldset already has a border; inside a drawer the
   nesting reads better with a tinted body. */
.drawer-body fieldset { background: var(--surface-2); }

@media (prefers-reduced-motion: reduce) {
    .drawer { transition: none; }
}


/* ---------------------------------------------------------------------
   Role cards

   Three fixed roles, side by side, each with its own accent. A table would
   have been denser but these are read once in a while and acted on, not
   scanned - so they get room and a clear Edit button each.
   --------------------------------------------------------------------- */
.role-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
    margin-bottom: 14px;
}
@media (min-width: 720px) {
    .role-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
    .role-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Each card is a flex column so the footer sits at the bottom no matter how
   long the description runs - otherwise the three Edit buttons would sit at
   three different heights. */
.role-card {
    display: flex;
    flex-direction: column;
    margin-bottom: 0;
    position: relative;
    overflow: hidden;
}
.role-card > .card-body { flex: 1 1 auto; }

/* A colour band along the top edge, matching the avatar. Unlike the stat
   tiles this one is always visible: it is identity, not an affordance. */
.role-card::before {
    content: '';
    position: absolute; top: 0; left: 0; right: 0; height: 3px;
    background: var(--role-accent, var(--brand));
}
.role-card.accent-amber  { --role-accent: var(--amber); }
.role-card.accent-violet { --role-accent: var(--violet); }
.role-card.accent-blue   { --role-accent: var(--blue); }
.role-card.accent-slate  { --role-accent: var(--slate); }

.role-card h2 { font-size: 15px; }
.role-card .card-foot { margin-top: auto; }

/* How much of the total this role has been granted. */
.role-meter {
    height: 6px;
    margin-top: 12px;
    background: var(--border);
    border-radius: 4px;
    overflow: hidden;
}
.role-meter-fill {
    height: 100%;
    border-radius: 4px;
    transform-origin: left;
    animation: vrFunnelGrow .6s var(--ease) both;
}


/* ---------------------------------------------------------------------
   Animated dot-grid background

   The empty space below a short page was a flat slab. This fills it with a
   faint dot grid and runs a slow wave of brightness across it.

   HOW THE WAVE WORKS
   Two copies of the same grid. The lower one is static and barely visible.
   The upper one is brighter but masked by a soft diagonal band, and the band
   is what moves - so dots appear to light up as it passes and fade behind it.
   Nothing is redrawn: only a transform changes, which the compositor handles
   on its own thread. Animating background-position instead would repaint the
   whole layer every frame.

   WHY IT IS A SEPARATE ELEMENT AND NOT A BODY BACKGROUND
   body already carries the page colour, and an animated background-image on
   it would repaint behind every card on every frame. A single fixed layer at
   the start of .app paints once and is then only composited.

   SCOPE
   Only inside .app - the authenticated shell. The sign-in page has its own
   backdrop with the pipeline motif and orbs; adding this there would put two
   competing animations on one screen.
   --------------------------------------------------------------------- */
:root {
    /* Dots are drawn from the text colour, not the brand: at this size a red
       grid reads as a pattern you are meant to look at, and a neutral one
       reads as texture. */
    --dot-quiet: rgba(20, 24, 31, .055);
    --dot-lit:   rgba(20, 24, 31, .13);
    --dot-gap:   26px;
}
:root[data-theme="dark"] {
    --dot-quiet: rgba(255, 255, 255, .045);
    --dot-lit:   rgba(255, 255, 255, .11);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --dot-quiet: rgba(255, 255, 255, .045);
        --dot-lit:   rgba(255, 255, 255, .11);
    }
}

.app-bg {
    position: fixed;
    inset: 0;
    z-index: 0;              /* above the body colour, below everything in .app */
    overflow: hidden;
    pointer-events: none;    /* never intercepts a click */
}

/* position: relative ONLY - deliberately no z-index.
 *
 * .main needs to be a positioned element so it paints in the same layer as
 * .app-bg (z-index 0) and therefore above it, since it comes later in the DOM.
 *
 * Adding z-index: 1 also works for that, and BREAKS the modals. Every modal
 * and drawer lives inside .main, and a z-index makes .main a stacking context
 * - so a backdrop asking for z-index 100 is capped inside .main and can no
 * longer rise above the topbar at 50. Measured: the dimmed overlay stopped at
 * the topbar and the logo stayed clickable through it.
 *
 * z-index: auto keeps .main out of the stacking-context business, so the
 * modals keep competing globally the way they were written to. */
.main { position: relative; }

/* ---- the quiet grid ---- */
.app-bg::before,
.app-bg::after {
    content: '';
    position: absolute;
    /* Oversized so the translate below never drags an edge into view. */
    inset: -40%;
    background-image: radial-gradient(circle at center, var(--dot-quiet) 1.15px, transparent 1.15px);
    background-size: var(--dot-gap) var(--dot-gap);
}

/* A very slow drift on the base grid alone. Without it the untouched parts of
   the screen are completely still and the wave looks like it is sliding over
   a printed sheet rather than through the texture. */
@keyframes vrDotDrift {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(var(--dot-gap), var(--dot-gap), 0); }
}
.app-bg::before {
    animation: vrDotDrift 48s linear infinite;
}

/* ---- the lit band ---- */
.app-bg::after {
    background-image: radial-gradient(circle at center, var(--dot-lit) 1.35px, transparent 1.35px);

    /* The band. Soft edges, so dots fade in and out rather than switching. */
    -webkit-mask-image: linear-gradient(115deg,
        transparent 34%, rgba(0,0,0,.55) 44%, #000 50%, rgba(0,0,0,.55) 56%, transparent 66%);
            mask-image: linear-gradient(115deg,
        transparent 34%, rgba(0,0,0,.55) 44%, #000 50%, rgba(0,0,0,.55) 56%, transparent 66%);
    -webkit-mask-size: 260% 260%;
            mask-size: 260% 260%;
    -webkit-mask-position: 0% 0%;
            mask-position: 0% 0%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;

    animation: vrDotWave 14s ease-in-out infinite alternate;
}

/* Transform, not mask-position: a transform is composited, a mask-position
   change repaints. 14s each way - slow enough to read as ambient rather than
   as something demanding attention. */
@keyframes vrDotWave {
    from { transform: translate3d(-16%, -14%, 0); }
    to   { transform: translate3d(16%,  14%, 0); }
}

/* No mask support means no band, which would leave the brighter grid covering
   the whole screen at once. Hide it and keep the quiet grid. */
@supports not ((mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000))) {
    .app-bg::after { display: none; }
}

/* The texture is the point; the motion is not. Someone who asked for less
   motion keeps the grid and loses the movement. */
@media (prefers-reduced-motion: reduce) {
    .app-bg::before, .app-bg::after { animation: none; }
    .app-bg::after { transform: translate3d(0, 0, 0); }
}

/* On a phone this is behind a full-screen card stack almost all the time, and
   a permanently animating layer is a battery cost for something nobody sees. */
@media (max-width: 640px) {
    .app-bg::after { animation-duration: 22s; }
}

/* Paper does not need texture. */
@media print {
    .app-bg { display: none; }
}


/* ---------------------------------------------------------------------
   Tick-list picker

   Used for the interview panel: a scrollable list of people, each a
   checkbox, with a type-to-filter box above it.

   Why not a <select multiple>: it needs ctrl-click to pick a second name,
   which is not obvious and is unusable on a phone. Checkboxes say "more
   than one is allowed" just by being there.
   --------------------------------------------------------------------- */
.picker {
    border: 1px solid var(--border-2);
    border-radius: var(--radius-sm);
    background: var(--surface);
    max-height: 260px;
    overflow-y: auto;
    box-shadow: var(--ring);
}

.picker-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    font-size: 13px;
    transition: background var(--dur-fast) var(--ease);
}
.picker-row:last-of-type { border-bottom: 0; }
.picker-row:hover { background: var(--surface-2); }

/* A ticked row is tinted and marked on its leading edge, so the choices are
   readable at a glance without counting checkboxes. */
.picker-row.on {
    background: var(--primary-bg);
    box-shadow: inset 3px 0 0 var(--primary);
}

.picker-row input[type="checkbox"] {
    width: 17px;
    height: 17px;
    flex: 0 0 17px;
    accent-color: var(--primary);
    margin: 0;
}

.picker-main { flex: 1; min-width: 0; }

.picker-name {
    display: block;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.picker-sub {
    display: block;
    font-size: 11.5px;
    color: var(--text-3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The first ticked row leads the panel. Numbering them makes that visible -
   without it "the first one ticked" is a rule you have to remember. */
.picker-row.on .picker-name::after {
    content: attr(data-order);
    display: inline-block;
    margin-left: 7px;
    padding: 0 6px;
    font-size: 10px;
    font-weight: 700;
    line-height: 16px;
    color: #fff;
    background: var(--primary);
    border-radius: 8px;
    vertical-align: 1px;
}

.picker .empty-filter { padding: 18px 12px; margin: 0; }

/* =====================================================================
   PROCESS TRACE
   Jejak Proses
   =====================================================================
   One horizontal line, one circle per phase, label underneath - the same
   shape as the stage rail above it, so the two read as the same idea at two
   levels of detail.

   HOW THE LINE IS DRAWN
   Two half-segments per node rather than one line behind the list. A single
   background line cannot stop at the first and last circle, so it always
   overshot both ends; and per-node segments let each one colour its own half,
   which is what makes the line change colour exactly where progress stops.
   ===================================================================== */

.trace-card .card-head { flex-wrap: wrap; gap: 8px; }

.trace-head-meta {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.trace-owner-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-2);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    white-space: nowrap;
}
.trace-owner-chip svg { width: var(--icon-xs); height: var(--icon-xs); }

.trace-late { color: var(--red); font-weight: 700; }

/* ---------- Blockers -------------------------------------------------
   Above the rail, not below it. The rail says where things stopped; only
   this says why, and it is the reason the panel gets opened. */
.trace-blockers {
    padding: 14px 16px 0;
    display: grid;
    gap: 8px;
}

.trace-blocker {
    display: flex;
    gap: 10px;
    padding: 11px 13px;
    background: var(--red-bg);
    border: 1px solid color-mix(in srgb, var(--red) 22%, transparent);
    border-left: 3px solid var(--red);
    border-radius: 8px;
    line-height: 1.55;
}
.trace-blocker > svg {
    flex: 0 0 auto;
    width: var(--icon-sm);
    height: var(--icon-sm);
    margin-top: 3px;
    color: var(--red);
}
.trace-blocker-head {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    margin-bottom: 2px;
}

/* ---------- The rail -------------------------------------------------
   Scrolls sideways when it will not fit. It must not wrap: a wrapped rail
   stops reading as one line, and left-to-right progress is the whole point. */
.trace-rail-wrap {
    overflow-x: auto;
    overflow-y: hidden;
    margin: 0 -4px;
    padding: 2px 4px 0;
    scrollbar-width: thin;
}

.trace-rail {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: flex-start;
    min-width: max-content;
}

.trace-node {
    flex: 1 1 0;
    min-width: 120px;
    max-width: 190px;
    text-align: center;
}

/* Circle plus its two line halves, on one row. The halves are flex:1 so they
   always meet the neighbouring node's half exactly at the midpoint, whatever
   the column width turns out to be. */
.trace-node-top {
    display: flex;
    align-items: center;
    gap: 0;
}

.trace-seg {
    flex: 1 1 auto;
    height: 2px;
    background: var(--border-2);
    border-radius: 2px;
}
/* Nothing to the left of the first circle, or the right of the last. */
.trace-node:first-child  .trace-seg-l,
.trace-node:last-child   .trace-seg-r { background: transparent; }

.trace-dot {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--surface);
    border: 2px solid var(--border-2);
    color: var(--text-3);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}
.trace-dot svg { width: var(--icon-sm); height: var(--icon-sm); }

/* ---------- Labels --------------------------------------------------- */
.trace-node-label { padding: 9px 8px 0; }

.trace-title {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .3px;
    text-transform: uppercase;
    color: var(--text-3);
    line-height: 1.3;
}

.trace-bm {
    font-size: 10.5px;
    color: var(--text-3);
    opacity: .75;
    margin-top: 1px;
}

.trace-role {
    display: inline-block;
    margin-top: 5px;
    padding: 1px 7px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .2px;
    color: var(--text-2);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
}

.trace-detail {
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--text-3);
    margin-top: 5px;
}

.trace-when {
    font-size: 10px;
    color: var(--text-3);
    opacity: .8;
    margin-top: 3px;
    font-variant-numeric: tabular-nums;
}

/* ---------- Per-status colouring -------------------------------------
   The line INTO a node is coloured by that node's own state, so the colour
   changes at the exact point progress stopped rather than a step early. */
.trace-node.is-done .trace-dot {
    background: var(--green);
    border-color: var(--green);
    color: #fff;
}
.trace-node.is-done .trace-seg-l,
.trace-node.is-done .trace-seg-r { background: var(--green); }
.trace-node.is-done .trace-title { color: var(--green); }

.trace-node.is-current .trace-dot {
    background: var(--blue);
    border-color: var(--blue);
    color: #fff;
}
.trace-node.is-current .trace-seg-l { background: var(--green); }
.trace-node.is-current .trace-title { color: var(--blue); }

.trace-node.is-blocked .trace-dot {
    background: var(--red);
    border-color: var(--red);
    color: #fff;
}
.trace-node.is-blocked .trace-seg-l { background: var(--green); }
.trace-node.is-blocked .trace-title  { color: var(--red); }
.trace-node.is-blocked .trace-detail { color: var(--red); }

.trace-node.is-waiting .trace-dot { border-style: dashed; }

.trace-node.is-skipped { opacity: .45; }
.trace-node.is-skipped .trace-title { text-decoration: line-through; }

/* Where the work actually sits gets a halo, so the eye lands on it before
   reading a word. Only current and blocked - two halos defeat the purpose. */
.trace-node.is-current .trace-dot,
.trace-node.is-blocked .trace-dot {
    box-shadow: 0 0 0 5px color-mix(in srgb, currentColor 18%, transparent);
}

/* Motion only on the one node that is stuck, and only when the reader has not
   asked for less. Five pulsing circles is noise; one is a pointer. */
@media (prefers-reduced-motion: no-preference) {
    .trace-node.is-blocked .trace-dot { animation: trace-pulse 2.4s ease-in-out infinite; }
}
@keyframes trace-pulse {
    0%, 100% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 18%, transparent); }
    50%      { box-shadow: 0 0 0 10px color-mix(in srgb, var(--red) 4%, transparent); }
}

/* Hover lifts the circle and brings its detail up to full strength - the rail
   stays quiet at rest and answers when pointed at. */
@media (hover: hover) {
    .trace-node:hover .trace-dot { transform: translateY(-2px); }
    .trace-node:hover .trace-detail,
    .trace-node:hover .trace-bm { color: var(--text-2); opacity: 1; }
}

/* Narrow screens keep the rail and drop the second language and the detail
   line, because the phase name and its circle colour still carry the state. */
@media (max-width: 640px) {
    .trace-head-meta { margin-left: 0; width: 100%; }
    .trace-node { min-width: 96px; max-width: none; }
    .trace-bm, .trace-detail, .trace-when { display: none; }
    .trace-dot { width: 26px; height: 26px; }
}

/* ---------------------------------------------------------------------
   DOCUMENT TRACKER
   Jejak Dokumen
   ---------------------------------------------------------------------
   The same rail as the process trace, with two differences that keep the
   two cards from being mistaken for each other at a glance:

     - a lighter head, because this is the supporting view
     - wider nodes, because there are fewer of them and each one carries a
       person's name rather than a phase name

   Everything else is inherited on purpose. Two rails styled differently
   would read as two unrelated features rather than one idea seen twice.
   --------------------------------------------------------------------- */

.doc-card .card-head { background: var(--surface-2); }

.doc-rail .trace-node {
    min-width: 150px;
    max-width: 240px;
}

/* Names are proper nouns - uppercasing them the way phase names are
   uppercased makes them read as labels rather than people. */
.doc-rail .trace-title {
    text-transform: none;
    letter-spacing: 0;
    font-size: 13px;
}

.doc-rail .trace-bm {
    opacity: 1;
    font-weight: 600;
    color: var(--text-2);
}

/* Nobody is "blocked" in a custody chain - a desk either has the file, has
   passed it on, or has not seen it. So the pulse is dropped here: the only
   animated dot on the page should be the one that means something is wrong,
   and that lives in the process trace above. */
.doc-rail .trace-node.is-current .trace-dot { animation: none; }
