/* BORG - CYBERPUNK THEME */
/* Classic cyberpunk (neon on near-black) is the default "dark" theme.
   The "light" toggle is a holo-daylight variant: pale cyan grid with
   neon accents - so the theme switch always has a visible effect. */

/* HOLO LIGHT (theme = light) */
:root {
    /* Tell every native control (dropdowns, date/time pickers, number
       spinners, scrollbars, focus rings) to render in the LIGHT scheme,
       matching the pale holo UI. Without this the OS default leaks through
       and form popups render with the wrong (often dark) chrome. */
    color-scheme: light;
    --primary: #0090a8;          /* deep cyan - readable on pale bg */
    /* Corner radius shared by every button (see the BUTTON SHAPE block at the end).
       Declared on the BASE theme: a shape token must not live only in the dark block,
       or every button renders square in light mode (invalid var() -> no radius). */
    --btn-radius: 8px;
    --primary-dark: #00718a;
    --success: #0e9f1e;
    --danger: #e01252;
    --critical-red: #c81e1e;     /* CRITICAL-level red for light backgrounds (see the dark theme's #ff5252) */
    --warning: #c98a00;
    --accent2: #d410ad;
    --violet: #6a3bd6;           /* cyber violet (matches dark theme accent) */
    --on-primary: #f2fdff;
    --dark: #e8f6fa;             /* page backdrop (pale holo) */
    --light: #06222c;
    --border: #9fd4e0;
    --border-glow: #00b8cc;
    --text-primary: #0a2833;
    --text-secondary: #3f6c7c;
    --bg-base: #f2fbfe;
    --bg-alt: #e4f5fa;
    --bg-card: #ffffff;          /* cards: lifted above the pale page for contrast */
    --accent: #d9f2f8;
    --surface-1: rgba(0, 144, 168, 0.07);
    --surface-2: rgba(0, 144, 168, 0.04);
    /* THE CARD SURFACE — every card in the app (tasks, clients, notes/work-logs/bug reports, inventory,
       dashboard, settings) draws its fill + border from these two. Requested: "instead of just a thin
       line on the left side of the card, i would like a thin border around each of the cards and a
       slightly brighter background shade." In the light theme the card is already lifted (#ffffff on a
       #f2fbfe page) and --border already reads, so these match the existing values. */
    --card-bg: #ffffff;
    --card-border: #9fd4e0;
    --surface-focus: rgba(0, 144, 168, 0.12);
    --danger-text: #c00d47;
    --warning-text: #9c6a00;
    --login-1: #d7f0f7;
    --login-2: #eefafd;
    --login-3: #d2f4ef;

    --radius-sm: 0px;
    --radius-md: 0px;
    --radius-lg: 0px;

    --glow-cyan: 0 0 0 1px rgba(0, 176, 204, 0.4);
    --glow-green: 0 0 0 1px rgba(14, 159, 30, 0.35);
    --glow-pink: 0 0 0 1px rgba(212, 16, 173, 0.35);
    --shadow-sm: 0 0 0 1px rgba(0, 144, 168, 0.3);
    --shadow-md: 0 2px 10px rgba(0, 144, 168, 0.15);
    --shadow-lg: 0 4px 20px rgba(0, 144, 168, 0.2);

    --transition: all 0.18s ease;
}

/* DARK-CYBERPUNK (theme = dark, and the app default)
   Deep violet-black, electric cyan, hot magenta, violet, terminal green. */
:root.dark-mode {
    color-scheme: dark;           /* native popups (dropdowns, date/time pickers) render dark */
    --primary: #00d9ff;          /* electric cyan (VIVID - keep) */
    --primary-dark: #00a8cc;
    --success: #00ffa3;          /* neon green (VIVID - keep) */
    --danger: #9d7575;           /* muted clay (was crimson) */
    --critical-red: #ff5252;     /* CRITICAL-level red (dashboard tile, and the rotate guard's X) */
    --warning: #ffb700;          /* amber (VIVID - keep) */
    --accent2: #687080;          /* muted slate (was hot magenta) */
    --violet: #6a7385;           /* muted slate (was cyber violet) */
    --on-primary: #041018;
    --dark: #05050c;             /* violet-black backdrop */
    --light: #ece9fb;
    --border: #1c1b30;
    --border-glow: #00d9ff;
    --text-primary: #ece9fb;
    --text-secondary: #8b87ad;
    --bg-base: #0a0a14;
    --bg-alt: #0e0d1a;
    --bg-card: #151329;          /* cards: lifted off the near-black page for contrast */
    --accent: #15132a;
    --surface-1: rgba(100, 108, 125, 0.06);
    --surface-2: rgba(0, 217, 255, 0.03);   
    /* THE CARD SURFACE (dark): a step brighter than the old card fill (#151329 gradient / a 6% wash) and
       a border that actually reads on #0a0a14 — --border (#1c1b30) sat within a few units of the page and
       the modal fill, which is why a card looked like nothing but its left accent line. */
    --card-bg: #1a1836;
    --card-border: #34324f;
    --surface-focus: rgba(0, 217, 255, 0.10);
    --danger-text: #c98f8f;
    --warning-text: #ffcf5c;
    --login-1: #05050c;
    --login-2: #0a0a14;
    --login-3: #100e20;

    --glow-cyan: 0 0 6px rgba(0, 217, 255, 0.55), 0 0 18px rgba(0, 217, 255, 0.25);
    --glow-green: 0 0 6px rgba(0, 255, 163, 0.5), 0 0 16px rgba(0, 255, 163, 0.2);
    --glow-pink: 0 0 10px rgba(100, 108, 125, 0.12);  /* muted slate (was hot pink) */
    --glow-violet: 0 0 10px rgba(100, 108, 125, 0.12);
    --shadow-sm: 0 0 0 1px rgba(0, 217, 255, 0.25);
    --shadow-md: 0 0 10px rgba(0, 217, 255, 0.18);
    --shadow-lg: 0 0 22px rgba(0, 217, 255, 0.25);

    --transition: all 0.18s ease;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html, body {
    height: 100%;
    width: 100%;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: var(--dark);
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.045) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.045) 1px, transparent 1px),
        radial-gradient(circle at 50% 0%, rgba(0, 217, 255, 0.10) 0%, transparent 55%),
        radial-gradient(circle at 85% 100%, rgba(255, 47, 179, 0.07) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    color: var(--text-primary);
    line-height: 1.6;
    overflow: hidden;
    position: relative;
}

/* CRT scanline overlay (behind content, above backdrop) */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    background: repeating-linear-gradient(
        0deg,
        rgba(0, 0, 0, 0.10) 0px,
        rgba(0, 0, 0, 0.10) 1px,
        transparent 1px,
        transparent 4px
    );
    opacity: 0.5;
}

:root:not(.dark-mode) body::before {
    opacity: 0.18;
}

/* Screen Management */
.screen {
    display: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
}

.screen.active {
    display: flex;
    flex-direction: column;
    z-index: 10;
}

/* Loading Screen */
#loadingScreen {
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, var(--login-1) 0%, var(--login-2) 50%, var(--login-3) 100%);
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        radial-gradient(circle at 20% 50%, rgba(0, 217, 255, 0.18) 0%, transparent 50%),
        radial-gradient(circle at 80% 80%, rgba(255, 47, 179, 0.12) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    min-height: 100vh;
    min-height: 100dvh; /* mobile: center within the VISIBLE viewport (excludes address bar) */
    height: auto;       /* let content grow past dvh on very short screens instead of clipping both ends */
}

.loading-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 24px;
}

/* The loading logo reuses .login-header (same BORG gradient/glow/glitch + dashed subtitle).
   Only tweak: the login header's bottom margin is sized for a form underneath — tighten it here. */
.loading-logo {
    margin-bottom: 0;
}

.loading-spinner {
    width: 48px;
    height: 48px;
    border: 3px solid rgba(0, 217, 255, 0.1);
    border-top: 3px solid var(--primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Login Screen */
#loginScreen {
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, var(--login-1) 0%, var(--login-2) 50%, var(--login-3) 100%);
    background-image:
        linear-gradient(rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0, 217, 255, 0.06) 1px, transparent 1px),
        radial-gradient(circle at 20% 50%, rgba(0, 217, 255, 0.18) 0%, transparent 50%),
        radial-gradient(circle at 80% 80%, rgba(255, 47, 179, 0.12) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    min-height: 100vh;
    min-height: 100dvh; /* mobile: center within the VISIBLE viewport (excludes address bar) */
    height: auto;       /* let content grow past dvh on very short screens instead of clipping both ends */
}

.login-container {
    width: 100%;
    max-width: 450px;
    padding: 20px;
}

.login-card {
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    padding: 50px 40px;
    box-shadow: var(--shadow-lg), var(--glow-cyan), inset 0 1px 0 rgba(255, 255, 255, 0.06);
    backdrop-filter: blur(10px);
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%);
}

.login-header {
    text-align: center;
    margin-bottom: 40px;
}

/* The same tear the header used to run, on the login logo — the one place a glitch is a welcome
   sight rather than a distraction. One ~0.4s burst per 4.5s cycle; the logo slices itself, nudges
   ±2px and briefly splits its shadow into green/cyan. `steps(1, end)` keeps the stops hard. */
@keyframes loginTear {
    0%, 68% { clip-path: none; transform: none; text-shadow: none; }
    68.8% { clip-path: inset(12% 0 58% 0); transform: translate(-2px, 0);    text-shadow: -3px 0 var(--success), 3px 0 var(--primary); }
    71.0% { clip-path: inset(52% 0 20% 0); transform: translate(2px, 1px);   text-shadow: 3px 0 var(--primary), -3px 0 var(--success); }
    73.2% { clip-path: inset(30% 0 44% 0); transform: translate(-2px, -1px); text-shadow: -2px 0 var(--success), 2px 0 var(--primary); }
    75.4% { clip-path: inset(66% 0 8% 0);  transform: translate(2px, 0);     text-shadow: 2px 0 var(--primary), -2px 0 var(--success); }
    77.8%, 100% { clip-path: none; transform: none; text-shadow: none; }
}

.login-header h1 {
    position: relative;
    font-size: 52px;
    font-weight: 900;
    background-image:
        repeating-linear-gradient(0deg, rgba(4, 8, 16, 0.32) 0px, rgba(4, 8, 16, 0.32) 2px, transparent 2px, transparent 5px),
        linear-gradient(180deg, #7df9ff 0%, var(--primary) 45%, var(--success) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    margin-bottom: 8px;
    letter-spacing: 6px;
    text-transform: uppercase;
    font-family: 'Arial Black', 'Arial', sans-serif;
    filter: drop-shadow(0 0 4px rgba(0, 217, 255, 0.7)) drop-shadow(0 0 18px rgba(0, 255, 163, 0.35));
    animation: logoFlicker 6s linear infinite, loginTear 4.5s steps(1, end) infinite;
}

/* Cyberpunk glitch slices - brief cyan/green separation bursts */
.login-header h1::before,
.login-header h1::after {
    content: "BORG";   /* literal, NOT attr(data-text): a stale cached index.html without the
                                attribute would render the copies EMPTY (the phone showed no header
                                glitch for exactly that reason) */
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background: none;               /* the host's gradient+clip must not leak onto the copy */
    -webkit-background-clip: border-box;
    background-clip: border-box;
}

/* The header logo is deliberately PLAIN. It used to tear, but it is on screen the whole time the
   user works, and they asked for the effect to live on the login page instead. The recipe (clip-path
   slices + a green/cyan text-shadow split) is on `.login-header h1` below — see
   `loginTear`, and the notes in the borg-app skill for how to bring it back here. */
.app-title {
    position: relative;
}


.login-header h1::before {
    color: var(--success);
    -webkit-text-fill-color: currentColor;
    animation: glitchA 2s linear infinite;    /* was 3.2s: same burst, more often */
}

.login-header h1::after {
    color: var(--primary);
    -webkit-text-fill-color: currentColor;
    animation: glitchB 2.8s linear infinite;  /* was 4.7s, and a different length than glitchA
                                                 so the two copies never burst in lockstep */
}

@keyframes glitchA {
    0%, 34%, 70% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    35% { clip-path: inset(12% 0 74% 0); transform: translate(-5px, -1px); opacity: 0.95; }
    37% { clip-path: inset(64% 0 8% 0); transform: translate(5px, 1px); opacity: 0.95; }
    39% { clip-path: inset(32% 0 52% 0); transform: translate(-4px, 0); opacity: 0.75; }
    41% { clip-path: inset(80% 0 4% 0); transform: translate(4px, -1px); opacity: 0.95; }
    43% { clip-path: inset(46% 0 40% 0); transform: translate(-5px, 1px); opacity: 0.85; }
    46% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    71% { clip-path: inset(58% 0 22% 0); transform: translate(5px, -1px); opacity: 0.95; }
    73% { clip-path: inset(10% 0 76% 0); transform: translate(-5px, 1px); opacity: 0.9; }
    75% { clip-path: inset(70% 0 12% 0); transform: translate(4px, 0); opacity: 0.8; }
    77% { clip-path: inset(26% 0 62% 0); transform: translate(-4px, -1px); opacity: 0.95; }
    79% { clip-path: inset(52% 0 34% 0); transform: translate(5px, 1px); opacity: 0.85; }
    82% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    100% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
}

@keyframes glitchB {
    0%, 27%, 63% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    28% { clip-path: inset(70% 0 14% 0); transform: translate(5px, 1px); opacity: 0.95; }
    30% { clip-path: inset(8% 0 78% 0); transform: translate(-5px, -1px); opacity: 0.95; }
    32% { clip-path: inset(44% 0 42% 0); transform: translate(4px, 0); opacity: 0.75; }
    34% { clip-path: inset(58% 0 26% 0); transform: translate(-4px, 1px); opacity: 0.95; }
    36% { clip-path: inset(18% 0 68% 0); transform: translate(5px, -1px); opacity: 0.85; }
    38% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    64% { clip-path: inset(36% 0 52% 0); transform: translate(-5px, 1px); opacity: 0.95; }
    66% { clip-path: inset(82% 0 6% 0); transform: translate(5px, -1px); opacity: 0.9; }
    68% { clip-path: inset(22% 0 66% 0); transform: translate(-4px, 0); opacity: 0.8; }
    70% { clip-path: inset(62% 0 20% 0); transform: translate(4px, 1px); opacity: 0.95; }
    72% { clip-path: inset(46% 0 40% 0); transform: translate(-5px, -1px); opacity: 0.85; }
    74% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
    100% { clip-path: inset(50% 0 50% 0); transform: translate(0, 0); opacity: 0; }
}

/* Respect a reduced-motion request: the static gradient logo, no tearing.
   The copies must be HIDDEN, not merely un-animated, or they'd paint on top of it. */
@media (prefers-reduced-motion: reduce) {
    .login-header h1 {
        animation: none;
    }
    .login-header h1::before,
    .login-header h1::after {
        display: none;
    }
}

@keyframes logoFlicker {
    0%, 91%, 95%, 100% { opacity: 1; }
    92% { opacity: 0.84; }
    93% { opacity: 1; }
    94% { opacity: 0.9; }
}

.login-header .subtitle {
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 4px;
    text-transform: uppercase;
    line-height: 1.5;
    position: relative;
    display: inline-block;
    padding: 0 14px;
}

.login-header .subtitle::before,
.login-header .subtitle::after {
    content: '';
    position: absolute;
    top: 10px; /* center of line 1 ("BATCH OPERATIONS") */
    width: 26px;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--primary));
}

.login-header .subtitle::before { left: -18px; }
.login-header .subtitle::after { right: -18px; transform: scaleX(-1); }

.login-form {
    display: flex;
    flex-direction: column;
    gap: 20px;
    margin-bottom: 24px;
}

.form-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.form-group label {
    font-weight: 600;
    font-size: 13px;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.form-group input,
.form-group textarea,
.form-group select {
    padding: 14px 16px;
    border: 1px solid var(--border);
    background-color: var(--surface-1);
    border-radius: 0;
    font-size: 14px;
    font-family: inherit;
    transition: var(--transition);
    color: var(--text-primary);
}

/* Select dropdown options (fix white-on-white in dark mode) */
.form-group select option {
    background-color: var(--bg-base);
    color: var(--text-primary);
    padding: 8px;
}

/* ============ THEMED IN-PAGE DROPDOWNS (task card edit) ============
   Native <select> lists are OS-drawn windows — impossible to theme with CSS.
   These render the menu as an in-page panel that matches the site:
   square corners, surface fill, primary top accent, cyan glow, dark list. */
.select-wrap {
    position: relative;
    width: 100%;
}
.select-wrap > select {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    opacity: 0;
    pointer-events: none;
}
.select-btn,
.dt-btn {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    text-align: left;
    padding: 14px 16px;
    border: 1px solid var(--border);
    background-color: var(--surface-1);
    border-radius: var(--btn-radius);
    font-size: 14px;
    font-family: inherit;
    color: var(--text-primary);
    cursor: pointer;
    transition: var(--transition);
}
.select-btn:hover,
.dt-btn:hover {
    background-color: var(--surface-focus);
}
.select-btn:focus-visible,
.dt-btn:focus-visible {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
    background-color: var(--surface-focus);
}
.select-btn.is-open,
.dt-btn.is-open {
    border-color: var(--primary);
    background-color: var(--surface-focus);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
}
.select-btn-label,
.dt-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dt-label.dt-empty {
    color: var(--text-secondary);
}
.dt-icon {
    flex-shrink: 0;
    width: 15px;
    height: 15px;
    opacity: 0.7;
}
.dt-btn.is-open .dt-icon {
    opacity: 1;
}
.dt-panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 60;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
    padding: 10px;
    width: 240px;
    max-width: min(240px, calc(100vw - 16px));
    animation: selectPop 0.14s ease;
}
.dt-panel.is-above {
    top: auto;
    bottom: calc(100% + 4px);
}
.dt-panel-time {
    width: 212px;
}
.dt-cal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    margin-bottom: 8px;
}
.dt-cal-month {
    flex: 1;
    text-align: center;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-primary);
}
.dt-cal-nav {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 0;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    transition: var(--transition);
}
.dt-cal-nav:hover {
    border-color: var(--primary);
    color: var(--primary);
    background-color: var(--surface-focus);
}
.dt-cal-today {
    flex-shrink: 0;
    padding: 5px 9px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--primary);
    background: transparent;
    border: 1px solid var(--primary);
    border-radius: 0;
    cursor: pointer;
    transition: var(--transition);
}
.dt-cal-today:hover {
    background-color: rgba(0, 217, 255, 0.1);
    box-shadow: var(--glow-cyan);
}
.dt-cal-week {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    margin-bottom: 2px;
}
.dt-cal-wd {
    text-align: center;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-secondary);
    padding: 4px 0;
}
.dt-cal-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}
.dt-day {
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-family: inherit;
    color: var(--text-primary);
    background: transparent;
    border: 0;
    border-radius: 0;
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease;
}
.dt-day:hover {
    background-color: var(--surface-2);
}
.dt-day.empty {
    cursor: default;
    pointer-events: none;
}
.dt-day.today {
    border: 1px solid var(--primary);
    color: var(--primary);
}
.dt-day.selected {
    background-color: var(--primary);
    color: #02060c;
    font-weight: 700;
    box-shadow: var(--glow-cyan);
}
.dt-day.selected.today {
    border-color: var(--primary);
}
.dt-time-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
    max-height: 240px;
    overflow-y: auto;
}
.dt-col {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.dt-col-title {
    text-align: center;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-secondary);
    padding: 4px 0;
}
.dt-t {
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
    background: transparent;
    border: 0;
    border-radius: 0;
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease;
}
.dt-t:hover {
    background-color: var(--surface-2);
}
.dt-t.selected {
    background-color: var(--primary);
    color: #02060c;
    font-weight: 700;
    box-shadow: var(--glow-cyan);
}
.select-caret {
    flex-shrink: 0;
    width: 10px;
    height: 10px;
    border-right: 2px solid var(--text-secondary);
    border-bottom: 2px solid var(--text-secondary);
    transform: translateY(-2px) rotate(45deg);
    transition: transform 0.18s ease;
}
.select-btn.is-open .select-caret {
    transform: translateY(1px) rotate(225deg);
    border-color: var(--primary);
}
.select-panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    /* NOT `right: 0`: that clamped the panel to the CONTROL's width, so any option longer than the
       control got an ellipsis. Measured at 390px: the Tasks sort row "Due Date (Newest)" needs 120px
       and its rows had 116px, "Newest First" needed 78 and had 76. Grow to the content instead, capped
       so a long value can never push the panel off the screen. */
    right: auto;
    min-width: 100%;
    width: max-content;
    max-width: min(92vw, 360px);
    z-index: 60;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
    max-height: min(260px, 55vh);
    overflow-y: auto;
    animation: selectPop 0.14s ease;
}
.select-panel.is-above {
    top: auto;
    bottom: calc(100% + 4px);
}
/* A panel that grew wider than its control and cannot fit to the right of it flips to the control's
   right edge instead (set by the opener in _enhanceSelects — the filter/sort controls sit at the right
   end of their toolbars). */
.select-panel.is-right {
    left: auto;
    right: 0;
}
.select-opt {
    padding: 11px 14px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    border-bottom: 1px solid rgba(125, 125, 165, 0.12);
    display: flex;
    align-items: center;
    gap: 10px;
}
.select-opt:last-child { border-bottom: none; }
.select-opt:hover,
.select-opt.is-hover {
    background-color: var(--surface-focus);
}
.select-opt.is-selected {
    color: var(--primary);
    font-weight: 600;
}
/* The client dropdown's 'Add Client' row is an ACTION, not a client. It is the app's own Add Client
   button flattened into the list: a leading plus glyph + the label, on a faint tint. Kept faint on
   purpose — the selected row is marked by the accent diamond, and a strong wash here would read as
   "selected", competing with it. Full-bleed, no border, no radius: same row shape as every option. */
.select-opt.is-special {
    color: var(--primary);
    font-weight: 700;
    background-color: color-mix(in srgb, var(--primary) 7%, transparent);
}
.select-opt.is-special:hover,
.select-opt.is-special.is-hover {
    background-color: color-mix(in srgb, var(--primary) 15%, transparent);
}
.select-opt-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--primary);
}
.select-opt-icon svg { width: 12px; height: 12px; }
.select-opt-opt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-opt-check {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border: 2px solid var(--primary);
    transform: rotate(45deg);
    opacity: 0;
}
.select-opt.is-selected .select-opt-check { opacity: 1; }
.select-panel::-webkit-scrollbar { width: 6px; }
.select-panel::-webkit-scrollbar-thumb { background: var(--border); }
@keyframes selectPop {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

.form-group input::placeholder {
    color: var(--text-secondary);
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
    background-color: var(--surface-focus);
}

.error-message {
    background-color: rgba(255, 46, 99, 0.12);
    color: var(--danger-text);
    padding: 12px;
    border-radius: 0;
    font-size: 13px;
    margin-bottom: 16px;
    border: 1px solid var(--danger);
    border-left: 3px solid var(--danger);
    box-shadow: inset 0 0 14px rgba(255, 46, 99, 0.10);
}

.login-hint {
    text-align: center;
    color: var(--primary);
    font-size: 13px;
    letter-spacing: 1.5px;
    margin-top: 20px;
    animation: hintGlow 3s ease-in-out infinite;
}

.login-hint span {
    display: block;
    line-height: 1.8;
}

@keyframes hintGlow {
    0%, 100% {
        filter: drop-shadow(0 0 3px rgba(0, 217, 255, 0.6)) drop-shadow(0 0 9px rgba(0, 217, 255, 0.3));
        opacity: 0.9;
    }
    50% {
        filter: drop-shadow(0 0 5px rgba(0, 217, 255, 0.95)) drop-shadow(0 0 14px rgba(0, 217, 255, 0.55));
        opacity: 1;
    }
}

/* Main Dashboard */
#mainScreen {
    flex-direction: column;
    background-color: var(--dark);
}

.app-header {
    background: linear-gradient(180deg, var(--accent) 0%, var(--bg-base) 100%);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 4px 24px rgba(0, 217, 255, 0.10);
    position: sticky;
    top: 0;
    /* Must sit ABOVE the pinned page action bars (Add Task / Add Client, z:150)
       because the mobile TOOLS dropdown is nested inside nav (z:200) which is a
       child of this header — the menu's own high z-index can't escape the header's
       stacking context. 200 keeps it below body-level modals (z:1001+). */
    z-index: 200;
}

.header-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 20px;
    gap: 12px;
}

.app-title {
    font-size: 18px;
    font-weight: 900;
    background-image:
        repeating-linear-gradient(0deg, rgba(4, 8, 16, 0.3) 0px, rgba(4, 8, 16, 0.3) 1.5px, transparent 1.5px, transparent 4px),
        linear-gradient(180deg, #7df9ff 0%, var(--primary) 45%, var(--success) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    text-transform: uppercase;
    letter-spacing: 4px;
    filter: drop-shadow(0 0 3px rgba(0, 217, 255, 0.6)) drop-shadow(0 0 10px rgba(0, 255, 163, 0.3));
}

/* The header clock, stacked as date OVER time at both breakpoints. It sits against the USER DROPDOWN (mobile
   too, on request) and the free space goes to its left: the row is `justify-content: space-between` with three
   children, so a plain `space-between` would park the clock in the middle and `margin-right: auto` would park
   it beside the logo. `margin-left: auto` + the row's 12px gap + a little right padding is what puts it next
   to the button with a bit of air. Tabular numerals stop the ticking seconds changing the row's width. */
.header-clock {
    display: inline-flex;
    margin-left: auto;
    margin-right: 0;
    padding: 0 4px 0 0;
    font-size: 11px;
    line-height: 1.2;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    min-width: 0;
}

/* Both breakpoints stack date over time — desktop included, on request. The two blocks differ only in the
   date's length; the stylesheet shows one of them. */
/* Right-aligned: the two lines share their RIGHT edge (asked for after seeing the left-aligned version), so the
   block reads as a tidy column against whatever sits beside it — the user dropdown on desktop, the logo on
   mobile. The wider line (the date, on both breakpoints) sets the width, so the time tucks under its right end. */
.header-clock .hc-block { display: flex; flex-direction: column; align-items: flex-end; }
.header-clock .hc-long  { display: none; }
.header-clock .hc-short { display: flex; }
.header-clock .hc-time  { color: var(--text-primary); font-weight: 700; }

.header-actions {
    display: flex;
    gap: 12px;
}

.btn-icon {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.10), rgba(255, 47, 179, 0.05));
    border: 1px solid var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: 0;
    transition: var(--transition);
    font-size: 14px;
    color: var(--text-primary);
}

.btn-icon svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.btn-icon:hover {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.2), rgba(255, 47, 179, 0.1));
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

.icon-small {
    width: 18px;
    height: 18px;
    color: var(--primary);
    flex-shrink: 0;
}

/* Theme icon: show moon in light mode, sun in dark mode */
.icon-sun {
    display: none;
}

:root.dark-mode .icon-sun {
    display: inline-block;
}

:root.dark-mode .icon-moon {
    display: none;
}

.username {
    font-weight: 600;
    font-size: 13px;
}

/* User Dropdown */
.user-dropdown {
    position: relative;
}

/* Mobile TOOLS button lives in the pinned bottom bar (see @media 768px) */
.mobile-user-dropdown {
    display: none;
}

/* Desktop TOOLS button — header dropdown that mirrors the mobile bottom-bar
   TOOLS menu (which is display:none on desktop). Hidden on mobile where the
   bottom-bar TOOLS owns that role; shown on desktop. */
.desktop-tools-dropdown {
    display: none;
    position: relative;
}

/* Toolbox icon on the desktop TOOLS button — match the user button's icon size */
.desktop-tools-dropdown .dna-icon {
    width: 20px;
    height: 20px;
    color: var(--primary);
}

/* Desktop: reveal the header TOOLS button; align its dropdown to the right edge.
   Also: collapse the entire nav into one header row.
   ── Strategy ─────────────────────────────────────────────────────────────────
   <nav class="nav-tabs"> was moved inside <header class="app-header"> (HTML
   change, safe for mobile because position:fixed pops it out of flow on ≤768px).
   On desktop we use display:contents on .header-top so its children (.app-title
   and .header-actions) participate directly in the .app-header flex row, giving:
     [BORG logo] [DASH] [SCHEDULE] [TASKS] [CLIENTS] [TOOLS▾] [user▾]
   Mobile layout is completely untouched (all rules are min-width:768px).
   ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 768px) {
    .desktop-tools-dropdown {
        display: block;
    }
    .desktop-tools-dropdown .dropdown-menu {
        right: 0;
        left: auto;
        top: 100%;
        min-width: 170px;
    }

    /* Header is one horizontal flex row */
    .app-header {
        display: flex;
        flex-direction: row;
        align-items: stretch;
    }

    /* Dissolve the header-top wrapper so its children (.app-title + .header-actions)
       become direct flex items of .app-header — lets us order them independently */
    .header-top {
        display: contents;
    }

    /* Logo — leftmost, vertically centred */
    .app-title {
        order: 1;
        flex-shrink: 0;
        align-self: center;
        padding: 0 16px 0 20px;
    }

    /* Clock — immediately LEFT of the user dropdown, which keeps the right edge. The `margin-left: auto` moves
       from the user group to the clock (see `.header-actions` below): with the free space soaked up to the
       clock's left, the clock and the user group sit together at the right end in source order, so the button
       stays rightmost and the clock reads as part of that group. Same `order: 4`, earlier in the source — no
       wrapper element, because .header-top is `display: contents` here and the `order` values are load-bearing. */
    .header-clock {
        order: 4;
        align-self: center;
        margin-left: auto;
        margin-right: 0;
        /* The visible air between the clock's text and the button: the user group's own 12px flex gap does not
           reach an element OUTSIDE the group, so this padding IS the gap. 8px measured as "touching". */
        padding: 0 14px;
        font-size: 12px;
        line-height: 1;
    }
    /* Stacks here too, so the desktop block is the visible one and the whole thing is one column of text
       rather than a long line. `align-items: flex-start` keeps the two lines sharing a left edge. */
    .header-clock .hc-long  { display: flex; }
    .header-clock .hc-short { display: none; }
    .header-clock { line-height: 1.25; }

    /* Nav tabs — sit flush left after the logo; the free space goes to the
       RIGHT of TOOLS (via .header-actions' margin-left:auto) so the header
       reads [logo][tabs][TOOLS] …gap… [user]. Element prefix (nav.nav-tabs)
       beats the base single-class .nav-tabs rule that appears LATER in the
       source (padding:0 30px) — without it a 30px gap stays left of TOOLS. */
    nav.nav-tabs {
        order: 2;
        flex: 0 1 auto;
        flex-direction: row;
        position: static;
        height: auto;
        border: none;
        border-bottom: none;
        box-shadow: none;
        background: transparent;
        padding: 0;
        overflow: visible;
        gap: 0;
        /* cancel the top-border the base rule adds */
        border-top: none;
    }

    /* Individual tabs — inline, full header height, bottom-border indicator */
    .nav-tab {
        order: 2; /* within the nav-tabs flex container; irrelevant for .app-header */
        flex: 0 0 auto;
        min-width: 0;
        width: auto;
        flex-direction: row;
        padding: 0 18px;
        font-size: 12px;
        letter-spacing: 1.5px;
        border-bottom: 2px solid transparent;
        border-top: none;
        border-left: none;
        justify-content: center;
        align-items: center;
        gap: 7px;
        height: 100%;
    }

    .nav-tab:hover {
        box-shadow: none;
        text-shadow: 0 0 8px rgba(0, 217, 255, 0.5);
        background: rgba(0, 217, 255, 0.06);
    }

    .nav-tab.active {
        border-top: none;
        border-bottom-color: var(--primary);
        box-shadow: inset 0 -2px 0 var(--primary), 0 0 14px rgba(0, 217, 255, 0.25);
        text-shadow: 0 0 8px rgba(0, 217, 255, 0.7);
        background: linear-gradient(180deg, transparent 40%, rgba(0, 217, 255, 0.08) 100%);
    }

    /* Nav icons — slightly smaller than mobile bottom-bar */
    .nav-tabs .nav-icon {
        width: 15px;
        height: 15px;
    }

    /* Mobile TOOLS slot lives in the nav; hide it on desktop */
    .nav-tabs .mobile-user-dropdown {
        display: none;
    }

    /* TOOLS dropdown — right of tabs */
    .desktop-tools-dropdown {
        order: 3;
        flex-shrink: 0;
        align-self: stretch;
    }

    /* TOOLS button — adopt the flat .nav-tab look (no .btn-icon border/box).
       Was a bordered gradient pill; now matches Dash/Schedule/Tasks/Clients:
       full header height, flat text+icon, bottom underline when the menu opens. */
    #desktopToolsBtn {
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 7px;
        background: none;
        border: none;
        border-bottom: 2px solid transparent;
        box-shadow: none;
        padding: 0 18px;
        font-size: 13px;
        font-weight: 700;
        letter-spacing: 2px;
        text-transform: uppercase;
        color: var(--text-secondary);
        white-space: nowrap;
        transition: var(--transition);
        cursor: pointer;
    }

    #desktopToolsBtn .dna-icon {
        width: 15px;
        height: 15px;
        color: currentColor;
    }

    #desktopToolsBtn .icon-chevron {
        width: 14px;
        height: 14px;
        margin-left: 0;
        color: currentColor;
    }

    #desktopToolsBtn:hover {
        color: var(--primary);
        background: rgba(0, 217, 255, 0.06);
        border-color: transparent;
        box-shadow: none;
        text-shadow: 0 0 8px rgba(0, 217, 255, 0.5);
    }

    /* Menu open → bottom underline indicator, mirroring .nav-tab.active */
    .desktop-tools-dropdown.active #desktopToolsBtn {
        color: var(--primary);
        border-bottom-color: var(--primary);
        box-shadow: inset 0 -2px 0 var(--primary), 0 0 14px rgba(0, 217, 255, 0.25);
        text-shadow: 0 0 8px rgba(0, 217, 255, 0.7);
        background: linear-gradient(180deg, transparent 40%, rgba(0, 217, 255, 0.08) 100%);
    }

    .desktop-tools-dropdown.active #desktopToolsBtn .icon-chevron {
        transform: rotate(180deg);
    }

    /* User dropdown — rightmost, vertically centred. margin-left:auto pushes it
       to the right edge so the row's free space lands BETWEEN TOOLS and user
       (nav-tabs no longer grows — see .nav-tabs above). */
    .header-actions {
        order: 4;
        flex-shrink: 0;
        align-self: center;
        /* Was `margin-left: auto`. That responsibility moved to the CLOCK so the clock can sit between the nav
           and this group (the user asked for it just left of the user dropdown). Two auto margins on the same
           row would have SPLIT the free space between them and stranded the clock mid-header. */
        margin-left: 0;
        padding: 0 20px 0 0;
    }
}

.icon-chevron {
    width: 14px;
    height: 14px;
    margin-left: 4px;
    transition: transform 0.2s ease;
}

.user-dropdown.active .icon-chevron {
    transform: rotate(180deg);
}

.dropdown-menu {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 180px;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 0;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
    z-index: 1000;
    padding: 4px;
}

/* Dark mode: give the dropdown a deeper, more opaque background */
:root.dark-mode .dropdown-menu {
    background: rgba(16, 14, 32, 0.97);
}

/* Light mode: match dark mode opacity — solid panel, not glassy */
:root:not(.dark-mode) .dropdown-menu {
    background: rgba(235, 245, 250, 0.97);
}

.dropdown-menu.active {
    display: block;
}

.dropdown-item {
    width: 100%;
    padding: 12px 14px;
    background: none;
    border: none;
    text-align: left;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    cursor: pointer;
    transition: var(--transition);
    display: flex;
    align-items: center;
    gap: 10px;
    border-radius: 0;
}

.dropdown-item:hover {
    background: var(--surface-2);
    color: var(--primary);
}

/* Mobile TOOLS menu — username header + divider at the top of the list */
.dropdown-username {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px 10px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--primary);
}

.dropdown-username svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.dropdown-divider {
    border: none;
    border-top: 1px solid var(--border);
    margin: 2px 4px 6px;
}

.dropdown-icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.nav-tabs {
    display: flex;
    gap: 0;
    padding: 0 30px;
    overflow-x: auto;
    border-top: 1px solid var(--border);
    background: var(--bg-base);
    position: relative;  /* Explicit: normal flow on desktop */
    bottom: auto;
    left: auto;
    right: auto;
}

.nav-tab {
    flex: 1;
    min-width: 120px;
    padding: 16px 20px;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 13px;
    font-weight: 700;
    color: var(--text-secondary);
    border-bottom: 2px solid transparent;
    transition: var(--transition);
    white-space: nowrap;
    display: flex;
    gap: 8px;
    align-items: center;
    justify-content: center;
    text-transform: uppercase;
    letter-spacing: 2px;
    position: relative;
}

.nav-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: currentColor;
}

/* ---- Dash alarm badge ------------------------------------------------------
   The badge sits over the Dash tab's ICON (not the tab's corner): the icon is wrapped so the dot annotates
   the glyph the user named, and it lands the same way in the desktop header row and the mobile bottom bar.
   Colour language matches the cards it reports on: --warning amber = something is low, --critical-red = an
   item is out, a client is dark, or extreme heat. Text colour comes from --bg, which is dark ink on the
   light theme's red and near-black on the dark theme's amber — legible both ways by construction.

   `[hidden]` MUST be re-declared: the UA sheet's `[hidden]{display:none}` loses to the author
   `display:inline-flex` below, so without it the badge would show a permanent dot. */
.nav-icon-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.dash-alarm-badge {
    position: absolute;
    top: -7px;
    right: -6px;           /* the desktop nav tab puts its label 8px to the icon's right: sit tight enough
                              that the badge never grazes the first letter (measured 2px clear at 1280px) */
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    border: 1px solid var(--bg-base);     /* a hairline of the page colour so it reads over any backdrop */
    background: var(--critical-red);
    color: var(--bg-base);
    font-size: 9px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0;
    pointer-events: none;                 /* the tab keeps the tap */
}
.dash-alarm-badge[hidden] { display: none; }
.dash-alarm-badge.is-red { background: var(--critical-red); animation: dashAlarmPulse 2s ease-in-out infinite; }
.dash-alarm-badge.is-yellow { background: var(--warning); color: #1a1200; }

@keyframes dashAlarmPulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.18); }
}
@media (prefers-reduced-motion: reduce) {
    .dash-alarm-badge.is-red { animation: none; }
}

.nav-tab:hover {
    color: var(--primary);
    background: rgba(0, 217, 255, 0.06);
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.5);
}

.nav-tab.active {
    color: var(--primary);
    border-bottom-color: var(--primary);
    box-shadow: inset 0 -2px 0 var(--primary), 0 0 14px rgba(0, 217, 255, 0.25);
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.7);
    background: linear-gradient(180deg, transparent 40%, rgba(0, 217, 255, 0.08) 100%);
}

/* Tab Content */
.tab-content {
    display: none;
    flex: 1;
    overflow-y: auto;
    padding: 30px;
    gap: 20px;
    flex-direction: column;
}

.tab-content.active {
    display: flex;
}

.section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
}

.section-header h2 {
    font-size: 20px;
    font-weight: 800;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 3px;
    position: relative;
    padding-left: 16px;
}

.section-header h2::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 70%;
    background: linear-gradient(180deg, var(--primary), var(--accent2));
    box-shadow: var(--glow-cyan);
}

/* Buttons */
.btn {
    padding: 12px 20px;
    border: 1px solid var(--border);
    border-radius: var(--btn-radius);
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    transition: var(--transition);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-family: inherit;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    position: relative;
}

.icon-btn {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.btn-primary {
    background: linear-gradient(135deg, var(--primary), var(--accent2));
    color: #021016;
    border: 1px solid var(--primary);
}

.btn-primary:hover {
    box-shadow: var(--glow-cyan);
    transform: translateY(-2px);
    background: linear-gradient(135deg, var(--primary), var(--success));
    color: #021016;
}

/* Same treatment as the viewer bar's buttons: no fill, one accent outline, rounded. The old
   gradient carried a pink stop that is off-palette in the dark theme. */
.btn-secondary {
    background: transparent;
    color: var(--primary);
    border: 1px solid var(--primary);
}

.btn-secondary:hover {
    background: var(--surface-focus);
    box-shadow: var(--glow-cyan);
}

.btn-danger {
    background: transparent;
    color: var(--danger-text);
    border: 1px solid currentColor;
}

.btn-danger:hover {
    background: rgba(255, 46, 99, 0.14);
    box-shadow: none;
    color: var(--danger-text);
}

:root.dark-mode .btn-danger {
    color: #ff8fb0;
}

.btn-sm {
    padding: 10px 16px;
    font-size: 12px;
}

.btn-sm svg {
    width: 16px;
    height: 16px;
    display: block;
    pointer-events: none;
}

/* Icon-only btn: square, no text padding */
.btn-sm:has(svg):not(:has(svg + *)):not(:has(* + svg)) {
    padding: 10px 12px;
    min-width: 38px;
}

.btn-large {
    padding: 16px 28px;
    font-size: 15px;
    width: 100%;
}

/* ============ ONE card surface, app-wide ============
   Requested: "on all of the various cards across the app, such as, tasks, clients, inventory, and maybe
   more … instead of just a thin line on the left side of the card, i would like a thin border around each
   of the cards and a slightly brighter background shade."
   ---- The 3px LEFT ACCENT IS KEPT: it is not decoration, it encodes state (task/client status, note vs
   work-log vs bug report, critical/high/medium priority). Only the fill and the other three sides change,
   so nothing that reads the accent breaks. Flat fill (not the old per-family gradients) is deliberate: the
   four families had drifted into different surfaces, and "a slightly brighter shade" is one value. */
.task-item,
.client-item,
.reference-card,
.calendar-jobs-detail,
.settings-group,
.dash-outage-card,
.dash-inv-alert {
    background: var(--card-bg);
    border-top: 1px solid var(--card-border);
    border-right: 1px solid var(--card-border);
    border-bottom: 1px solid var(--card-border);
    /* border-left is whatever the family declares (the 3px accent) */
}

/* The inventory rows were plain list rows — a divider and no fill, so they never read as cards at all.
   Now they are cards like the rest (all four sides, since they carry no accent), separated by a gap.
   `:last-child` is spelled out because `.inv-item:last-child { border-bottom: none }` out-specifies a
   bare `.inv-item`. */
.inv-item,
.inv-cat-row,
.dash-wx-card,
.inv-item:last-child,
.inv-cat-row:last-child {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    margin-bottom: 10px;
}

.inv-item:last-child,
.inv-cat-row:last-child {
    margin-bottom: 0;   /* no stray gap after the last card */
}

/* ---- LOW-STOCK THRESHOLD (inventory form) ----
   The same stepper as Quantity, in red: the box, the value and both buttons take the danger token so the
   field reads as "this one warns you". --danger-text is the app's muted-clay danger (never a pink).
   ID-scoped on purpose: a class-level `color` lost to the form's generic input rule, and the box has to
   match #invQtyInput's 128px × 48px exactly or the two steppers do not line up. */
.inv-thresh-row label { color: var(--danger-text); }
#invThreshInput {
    flex: none; width: 128px; min-width: 0;      /* same 5-digit box as #invQtyInput */
    height: 48px; padding-top: 0; padding-bottom: 0;
    text-align: center;                          /* the value sits centred in the box, like Quantity */
    -moz-appearance: textfield; appearance: textfield; /* hide native spinners (also like Quantity) */
    color: var(--danger-text);
    border-color: var(--danger-text);
}
#invThreshInput::-webkit-outer-spin-button,
#invThreshInput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#invThreshInput:focus {
    border-color: var(--danger-text);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--danger-text) 30%, transparent);
}
.inv-thresh-btn {
    color: var(--danger-text) !important;
    border-color: var(--danger-text) !important;
}

/* The red chip marking a low item on its inventory row. */
.inv-low-chip {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 7px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--danger-text);
    border: 1px solid color-mix(in srgb, var(--danger-text) 45%, transparent);
    background: color-mix(in srgb, var(--danger-text) 14%, transparent);
}

.inv-thresh-value.is-low { color: var(--danger-text); font-weight: 700; }

/* ---- DASHBOARD: Power Outages card ----
   Live PG&E feed for the whole state. Its surface comes from the ONE card rule above; the left accent is
   cyan because this is live data (the low-stock card's accent is red because that is a warning). Hidden
   until there IS data — the dashboard never shows a feed error, same rule as the weather card. */
.dash-outage-card {
    /* The card is FULLY framed now, so it carries no left accent — a 3px bar beside a 1px frame read as a
       leftover. The FULL border shorthand is deliberate: the shared card rule only sets top/right/bottom
       (the left width used to come from the accent), so removing the accent alone leaves the left edge at
       0px and the frame open on one side. `.has-affected` (two classes, so more specific) turns every side
       red the moment one of our clients is inside an outage. */
    border: 1px solid var(--primary);
    padding: 12px 14px;
    margin-bottom: 18px;
    cursor: pointer;
}

.dash-outage-card:active { background: var(--surface-2); }

.dash-outage-head { display: flex; align-items: baseline; gap: 8px; }

.dash-outage-title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--primary);
}

.dash-outage-when { margin-left: auto; font-size: 11px; color: var(--text-secondary); }

.dash-outage-stats { display: flex; align-items: flex-end; gap: 18px; margin-top: 8px; }

.dash-outage-stat { display: flex; align-items: baseline; gap: 5px; min-width: 0; }

.dash-outage-stat .n {
    font-size: 20px;
    font-weight: 700;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}

.dash-outage-stat .l {
    font-size: 10px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* PSPS is a shutoff, not an outage — it carries the warning token so it never reads as one more row. */
.dash-outage-stat.is-psps .n { color: var(--warning); }

/* How many of OUR clients are inside an active outage — the one number on this card that needs acting on,
   so it stays even when the other stats are quiet (but a zero is still hidden, like every zero here). */
.dash-outage-stat.is-client .n { color: var(--critical-red); }

/* When one of our clients IS dark, the card's own label pulses red — the same "critical" motion language
   as the calendar/task critical pulse (slow, red glow), so it reads as the same kind of alarm. Turned UP
   after the first pass read as too subtle: the peak is a much paler red with a double glow and a slight
   scale, so the beat is unmistakable at a glance. Motion is reserved for state in this app, and a
   reduced-motion device keeps the red without the pulse. */
/* ...and the whole card is FRAMED in that red while a client is dark (all four sides: the left accent
   included, so the frame is complete rather than three-sided). */
.dash-outage-card.has-affected { border-color: var(--critical-red); }

.dash-outage-card.has-affected .dash-outage-title {
    display: inline-block;                 /* transform needs a box */
    color: var(--critical-red);
    animation: criticalTextPulse 2s ease-in-out infinite;
}

/* The app's one "this text is alarming" pulse — shared by the outage card's label when a client is dark and
   by an out-of-stock row on the inventory alert card. Slow, red, with a glow and a slight size beat; the
   amplitude was raised on request because the first pass read as too subtle. */
@keyframes criticalTextPulse {
    0%, 100% {
        color: var(--critical-red);
        text-shadow: 0 0 6px color-mix(in srgb, var(--critical-red) 55%, transparent);
        transform: scale(1);
        opacity: 0.9;
    }
    50% {
        color: color-mix(in srgb, var(--critical-red) 30%, var(--text-primary));
        text-shadow: 0 0 12px var(--critical-red), 0 0 22px color-mix(in srgb, var(--critical-red) 70%, transparent);
        transform: scale(1.07);
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dash-outage-card.has-affected .dash-outage-title {
        animation: none;
        color: var(--critical-red);
    }
}












/* The card's own footer (a "PG&E · California" source label + a "Map ›" affordance) was removed by
   request — the whole card is the tap target, and the title already says what it is. */

/* ============ Power outage window (the dashboard card opens it) ============ */
/* Ported from C:\hermes\pgemap and re-dressed in the app's own chrome: no header, the map owning the
   window, and Close in the pinned bar — the same shape as TOOLS → Map. */
.outage-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 700px;
    width: 100%;
    height: 88dvh;
    background: var(--bg-base);
    /* Anchors the pinned bar to the CARD. Left unset, its containing block is the full-screen modal, so
       on a desktop window (88dvh card, centred) the bar lands 48px BELOW the card's bottom edge and the
       map ends short of it. */
    position: relative;
}

.outage-map-wrap { position: relative; min-height: 300px; }

#outageLeafletMap { width: 100%; height: 100%; min-height: 300px; }

.outage-empty { z-index: 805; }

/* The list is a REAL ROW of the window — the map above it is a flex sibling, so the map's own centre is
   always in the visible area. (As an overlay it hid the lower half AND put every fitBounds centre behind
   itself, so flying to an outage flew it out of sight.)
   It takes what its content needs, capped at `50% - 63px`: that leaves the map exactly half the screen
   with the bar (63px) under it — the sizes the user asked for, measured at 390×844. */
.outage-sheet {
    position: relative;
    flex: none;
    display: flex;
    flex-direction: column;
    max-height: calc(50% - 63px);
    background: var(--bg-base);
    border-top: 2px solid var(--primary);
    animation: outage-sheet-up 0.18s ease-out;
}

@keyframes outage-sheet-up {
    from { transform: translateY(14px); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
}

.outage-sheet-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
}

.outage-sheet-title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--primary);
}

.outage-sheet-count {
    font-size: 12px;
    font-weight: 700;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}

.outage-sheet-when { margin-left: auto; font-size: 11px; color: var(--text-secondary); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* How many of the listed outages have one of OUR clients inside them — and a control in its own right:
   tapping it re-frames the map on every down client, like the band's heading. */
.outage-sheet-hits {
    flex: none;
    padding: 1px 6px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 700;
    color: var(--critical-red);
    border: 1px solid color-mix(in srgb, var(--critical-red) 45%, transparent);
    background: color-mix(in srgb, var(--critical-red) 12%, transparent);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}
.outage-sheet-hits:active { background: color-mix(in srgb, var(--critical-red) 22%, transparent); }

@keyframes outage-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

.outage-sheet-list {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 6px 12px 12px;
}

.outage-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    min-width: 0;
}

.outage-row:last-child { border-bottom: none; }
.outage-row.selected { background: color-mix(in srgb, var(--primary) 10%, transparent); }

/* An outage with one of OUR clients inside it: red dot, red chip, and a faint red wash on the row. */
.outage-row.has-client { background: color-mix(in srgb, var(--critical-red) 8%, transparent); }

.outage-row-dot { width: 9px; height: 9px; flex: none; background: var(--primary); }
.outage-row-dot.psps { background: var(--warning); }
.outage-row-dot.client { background: var(--critical-red); }

.outage-row-body { min-width: 0; flex: 1; }

.outage-row-top { display: flex; align-items: baseline; gap: 8px; min-width: 0; }

.outage-row-cause {
    font-size: 13px;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.outage-row-id { flex: none; margin-left: auto; font-size: 11px; color: var(--text-secondary); }

.outage-row-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 3px;
    font-size: 11px;
    color: var(--text-secondary);
}

.outage-row-meta .cust { color: var(--primary); font-weight: 600; font-variant-numeric: tabular-nums; }
.outage-row-meta .restored { color: var(--success); }
.outage-row-meta .waiting { color: var(--warning); }
.outage-row-meta .client-hit {
    padding: 0 5px;
    font-weight: 700;
    color: var(--critical-red);
    border: 1px solid color-mix(in srgb, var(--critical-red) 45%, transparent);
    background: color-mix(in srgb, var(--critical-red) 12%, transparent);
}

/* The affected client names in the map popup (the row itself only has space for the count). */
.outage-pop-clients {
    margin-top: 5px;
    padding-top: 5px;
    border-top: 1px solid var(--border);
    font-size: 12px;
    font-weight: 600;
    color: var(--critical-red);
}

/* A small STROBING red dot on every affected client's own address, shown in area mode (z11+) where the
   outage borders are on screen and "is my address inside one" is a question worth answering at a glance.
   Keyframe convention copied from the Map window's "you are here" pulse (map-you-pulse) — a round pulse is
   the one shape in this app that is deliberately not square, because it is a map beacon, not a control. */
.outage-client-ping-icon { background: transparent; border: 0; }

.outage-client-ping {
    /* Centred on the marker box by absolute positioning, NOT by `margin: auto` or flex: Leaflet's own
       `.leaflet-marker-icon { display: block }` outranks anything this stylesheet says about the icon's
       display (equal specificity, later in the cascade), and a flex-centred child collapsed to 0x0 when it
       won. 50% + half the size cannot be affected by display, and it is the same mechanism the ring uses. */
    position: absolute;
    left: 50%;
    top: 50%;
    margin: -4.5px 0 0 -4.5px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--critical-red);
    box-shadow: 0 0 7px color-mix(in srgb, var(--critical-red) 85%, transparent);
}

.outage-client-ping::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 9px;
    height: 9px;
    margin: -4.5px 0 0 -4.5px;
    border-radius: 50%;
    border: 2px solid var(--critical-red);
    /* The global reset is `* { box-sizing: border-box }`, and `*` does NOT match pseudo-elements — so this
       ring was content-box: the 2px border grew it to 13x13 while it stayed offset by half of 9, putting the
       ring's centre 2px right and 2px down of the dot's — the pulse looked off-centre, and worse as it
       scaled. Keep this line. */
    box-sizing: border-box;
    animation: outage-client-strobe 1.5s ease-out infinite;
}

@keyframes outage-client-strobe {
    0%   { transform: scale(1);   opacity: 0.85; }
    70%  { transform: scale(2.8); opacity: 0; }
    100% { transform: scale(2.8); opacity: 0; }
}

/* Respect a device that asked for less motion: a steady dot still marks the address. */
@media (prefers-reduced-motion: reduce) {
    .outage-client-ping::after { animation: none; opacity: 0.5; transform: scale(1.8); }
}

/* ---- The affected clients, named individually, at the TOP of the one list ----
   They used to be a pinned panel with a 132px cap and their own scroll; the user wanted them flowing at the
   top of the SAME scroller as the outage areas, so this is now just the first block of that list — no cap,
   no own scrollbar. The red wash + bottom rule still group them, and the negative margins pull the band out
   to the panel's full width (the list carries the 12px side padding). Tapping a row flies to that address —
   the red ping on the map is otherwise anonymous. */
.outage-affected {
    margin: -6px -12px 6px;
    padding: 9px 12px 11px;
    background: color-mix(in srgb, var(--critical-red) 7%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--critical-red) 35%, transparent);
}

/* The heading is a CONTROL, not a caption: a bordered label reading "Affected Clients: 3" that re-frames
   the map on every down client (the header's "# clients" chip does the same). Bordered so it reads as
   tappable, and tall enough to be a real target on a phone. */
.outage-affected-head {
    display: inline-flex;
    align-items: center;
    min-height: 40px;          /* it is a control now, so it needs a thumb-sized target too */
    margin: 0 0 8px;
    padding: 6px 12px;
    font-family: inherit;
    border: 1px solid color-mix(in srgb, var(--critical-red) 45%, transparent);
    background: color-mix(in srgb, var(--critical-red) 12%, transparent);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}
.outage-affected-head:active { background: color-mix(in srgb, var(--critical-red) 22%, transparent); }

.outage-affected-title {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--critical-red);
}

/* Roomier rows: the user asked for an easier target, so each name is a ~52px band with a hairline between
   neighbours (a 5px-padding row was ~34px and hard to hit with a thumb). */
.outage-affected-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 52px;
    padding: 6px 4px;
    cursor: pointer;
    min-width: 0;
    border-top: 1px solid color-mix(in srgb, var(--critical-red) 18%, transparent);
}
.outage-affected-row:active { background: color-mix(in srgb, var(--critical-red) 12%, transparent); }

.outage-affected-dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--critical-red);
}

.outage-affected-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.outage-affected-name {
    font-size: 13px;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.outage-affected-where {
    font-size: 11px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.outage-affected-outage { flex: none; font-size: 11px; color: var(--critical-red); font-variant-numeric: tabular-nums; }

.outage-sheet-note { padding: 10px 0; font-size: 12px; color: var(--text-secondary); }

/* The bar is a normal row at the bottom of the window (the map no longer runs under it), so the provider
   credit sits at the map's own bottom edge and needs no lift. */
#outageModal .form-actions.outage-actions {
    flex: none;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    background: var(--bg-base);
}

#outageModal .form-actions.outage-actions .btn { flex: 1; white-space: nowrap; }

/* The refresh spin belongs to the bar button now that the ⟳ moved out of the list header. */
#outageModal .outage-actions .btn.is-busy .icon-btn { animation: outage-spin 0.9s linear infinite; }

/* The outage popup + its tooltip. Leaflet's own sheet is loaded AFTER css/style.css, so any override
   needs to out-specify `.leaflet-popup-content-wrapper` — hence the three-class selectors. The shared
   popup surface theming already exists for the client map; these are the outage's own contents. */
.outage-pop { min-width: 190px; }

.outage-pop-cause {
    font-size: 13px;
    font-weight: 700;
    color: var(--primary);
    letter-spacing: 0.3px;
}

.outage-pop-id { margin-left: 8px; font-size: 11px; font-weight: 400; color: var(--text-secondary); }

/* The client's own name on THEIR tag — red, because this popup is about one of our sites being dark
   (the outage's own popup keeps the cyan cause title). */
.outage-pop-client {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.3px;
    color: var(--critical-red);
}

.outage-pop-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-top: 4px;
    font-size: 12px;
}

.outage-pop-row span { color: var(--text-secondary); }
.outage-pop-row b { margin-left: auto; color: var(--text-primary); font-weight: 600; text-align: right; }

/* The hover tooltip over a dot/outline carries the outage's headline. */
.leaflet-container .leaflet-tooltip.outage-pop-wrap,
.leaflet-container .leaflet-tooltip {
    background: rgba(5, 5, 12, 0.92);
    border: 1px solid var(--border);
    border-radius: 0;
    color: #ece9fb;
    font-size: 11px;
    font-weight: 600;
    box-shadow: none;
}

/* The list never overlays the map now, so Leaflet's own bottom controls are never covered and no lift
   is needed. The list scrolls itself; the map keeps its own gestures. */
#outageModal .leaflet-container { touch-action: pan-x pan-y; }

@media (max-width: 768px) {
    .outage-modal-content {
        max-width: 100%;
        height: 100dvh;
        border-radius: 0;
    }
    /* No long-press text selection anywhere in the window — the list and the map are touch surfaces, and a
       long press on a row should never start selecting the text under the finger. Same rule and same mobile
       scope as the Team roster and the calendar's day card; desktop keeps real text selection. */
    #outageModal,
    #outageModal * {
        -webkit-user-select: none;
        user-select: none;
    }
}

/* ---- DASHBOARD: Inventory Alert card ----
   Its surface comes from the ONE card rule (it carries a red left accent, so it joins the accent group). */
.dash-inv-alert {
    /* Two severities, one uniform frame (no left accent — the card is fully framed):
         low            → amber ("frame it in yellow")
         anything OUT   → critical red, via `.has-out` below
       `.has-out` is two classes, so it out-specifies this one whatever the source order. */
    border: 1px solid var(--warning);      /* full shorthand — see the outage card: the shared rule has no left width */
    padding: 12px 14px;
    margin-bottom: 18px;
}

/* One item with nothing left turns the whole card red. */
.dash-inv-alert.has-out { border-color: var(--critical-red); }

.dash-inv-alert-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.dash-inv-alert-title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--warning);                 /* tracks the frame: amber while merely low … */
}

.dash-inv-alert.has-out .dash-inv-alert-title { color: var(--critical-red); }   /* … red once anything is out */

.dash-inv-alert-count {
    font-size: 12px;
    color: var(--text-secondary);
}

/* One row per low item — tappable, opens that item's page. */
.dash-inv-alert-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 2px;
    border-top: 1px solid var(--border);
    cursor: pointer;
}
.dash-inv-alert-row:hover { color: var(--primary); }
.dash-inv-alert-row:first-child { border-top: none; }
/* Every row on this card is a low item, so the row reads red — name and numbers together, which is the
   ask ("also red for low items"). */
.dash-inv-alert-name { font-size: 14px; color: var(--critical-red); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-inv-alert-row:hover .dash-inv-alert-name { color: var(--primary); }
.dash-inv-alert-qty { flex-shrink: 0; font-size: 12px; font-weight: 700; color: var(--critical-red); }

/* OUT of stock (quantity 0) burns: the name AND the count pulse with the app's critical text pulse, on one
   rule so the two always beat together. No `display` tweak is needed — the row is a flex container, so its
   children are blockified and the keyframe's transform applies (verified live: the computed colour moves
   through the beat on both). */
.dash-inv-alert-row.is-out .dash-inv-alert-name,
.dash-inv-alert-row.is-out .dash-inv-alert-qty {
    animation: criticalTextPulse 2s ease-in-out infinite;
}

/* A device that asked for less motion still gets the red, without the beat. */
@media (prefers-reduced-motion: reduce) {
    .dash-inv-alert-row.is-out .dash-inv-alert-name,
    .dash-inv-alert-row.is-out .dash-inv-alert-qty { animation: none; }
}

/* Cards and Lists */
.task-filters {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    align-items: flex-end;
    margin-bottom: 20px;
}

/* Consolidated task filters dropdown — one button, all options inside */
.filters-dropdown {
    position: relative;
    width: auto;
    max-width: 100%;
}

/* ---- Filter/sort toolbar controls: ONE matched box ----
   The Filters chip and the Sort dropdown sit side by side in the tasks and gallery
   toolbars, but rendered at different heights (37px chip vs 49px select) because the chip
   is a compact 13px bordered chip while an enhanced <select>'s visible button inherits the
   FORM-FIELD scale (14px, 14px 16px padding). Pin every control in a filter surface to the
   app's compact standard (38px, same as .btn-sm) so the row always lines up. */
.filters-btn,
.select-wrap .filter-select + .select-btn,
.filters-menu .dt-wrap .dt-btn,
.filters-menu .select-wrap .select-btn {
    height: 38px;
    min-height: 38px;
    padding-top: 0;
    padding-bottom: 0;
    align-items: center;
    font-size: 13px;
    letter-spacing: 0.5px;
}
.filters-btn { padding-left: 14px; padding-right: 14px;
    border-radius: var(--btn-radius);
}
.select-wrap .filter-select + .select-btn { padding-left: 14px; padding-right: 14px; }
/* Inside the panel the controls are full-width, so they keep a tighter text inset. */
.filters-menu .select-wrap .select-btn,
.filters-menu .dt-wrap .dt-btn { padding-left: 10px; padding-right: 10px; }

.filters-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    /* height + horizontal inset come from the matched-box block above */
    background-color: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: 0;
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.5px;
    cursor: pointer;
    transition: var(--transition);
}

.filters-btn svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.filters-btn:hover,
.filters-dropdown.active .filters-btn {
    border-color: var(--primary);
    color: var(--primary);
}

.filters-dropdown.active .icon-chevron {
    transform: rotate(180deg);
}

/* Active-filter count badge (hidden when nothing is filtered) */
.filters-count {
    display: none;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    align-items: center;
    justify-content: center;
    background: var(--primary);
    color: #04101a;
    font-size: 11px;
    font-weight: 700;
    border-radius: 9px;
}

.filters-count.visible {
    display: inline-flex;
}

/* The options panel — reuses the app's dropdown-menu look */
.filters-menu {
    left: 0;
    right: auto;
    top: calc(100% + 8px);
    width: 280px;
    min-width: 0;
    padding: 14px;
    display: none;
}
.filters-menu.active {
    display: block;
}

/* Themed in-page dropdowns inside the gallery filter panel. The select-panel
   must sit above the menu's own stacking context and never clip the menu. */
.filters-menu .select-panel {
    z-index: 1200;
}
/* (the panel's select/date controls are height-matched to the toolbar in the
   "Filter/sort toolbar controls" block above — only their horizontal inset is set here) */

/* Stack the filter groups vertically inside the panel */
.filters-menu .filter-group {
    margin-bottom: 12px;
}

.filters-menu .filter-group:last-child {
    margin-bottom: 0;
}

.filters-menu .filter-select,
.filters-menu .filter-input {
    width: 100%;
    min-width: 0;
}

/* Custom date range: From / To side by side */
#customDateRange {
    flex-direction: row !important;
    align-items: center;
    gap: 8px;
}

#customDateRange .filter-input {
    flex: 1;
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.filter-label {
    font-size: 11px;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.filter-select,
.filter-input {
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 0;
    font-size: 13px;
    background-color: var(--bg-base);
    color: var(--text-primary);
    cursor: pointer;
    transition: var(--transition);
    min-width: 150px;
}

.filter-input {
    cursor: text;
}

.filter-select:hover,
.filter-input:focus {
    border-color: var(--primary);
    outline: none;
}

.task-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.task-item {
    /* fill + the three non-accent sides come from the ONE card rule above; the accent stays here */
    border-left: 3px solid var(--primary);
    border-radius: 0;
    padding: 18px;
    cursor: pointer;
    transition: var(--transition);
    display: flex;
    justify-content: space-between;
    align-items: start;
    gap: 12px;
}

/* ---- Tasks: multi-select (long-press a card, or the Select button) ---- */
/* The add bar holds two actions; matched pairs keep it the standard 12+38+12+1 = 63px. */
#tasksTab .tasks-add-bar { gap: 8px; }
#tasksTab .tasks-add-bar .btn { flex: 1; white-space: nowrap; }

.task-select-count {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}

/* Delete (primary) LEFT / Cancel RIGHT, each an equal half of the remaining row */
.task-select-actions { display: flex; flex: 1; gap: 8px; }
.task-select-actions .btn { flex: 1; }

/* Checkbox ring — mirrors .notes-card-check; only exists in select mode */
/* ---- Clients: multi-select (same interaction as the tasks page) ---- */
#clientsTab .clients-add-bar { gap: 8px; }
#clientsTab .clients-add-bar .btn { flex: 1; white-space: nowrap; }

.clients-select-count {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}

.clients-select-actions { display: flex; flex: 1; gap: 8px; }
.clients-select-actions .btn { flex: 1; }

.client-item.selectable { position: relative; }
.client-item.selectable .client-header { padding-right: 30px; }

.clients-select-check {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}

.clients-select-check svg { width: 13px; height: 13px; stroke: #fff; opacity: 0; transition: opacity 0.12s; }

.client-item.selected { border-color: var(--primary); box-shadow: var(--glow-cyan); }
.client-item.selected .clients-select-check { background: var(--primary); border-color: var(--primary); }
.client-item.selected .clients-select-check svg { opacity: 1; }

/* Dim unselected cards slightly, same as the tasks list */
#clientsList.clients-selecting .client-item:not(.selected) { opacity: 0.55; }

/* ---- Schedule day card: multi-select (long-press a task row) ----
   Same interaction, chrome and classes as the tasks/clients lists above, so the two bar slots (the day
   card swaps its Add/Close bar for the count + Delete/Cancel bar) already lay themselves out. */
#daySelectBar { align-items: center; gap: 12px; }
#daySelectBar .task-select-actions { flex: 1; }

.calendar-job-item.selectable { position: relative; }
/* The ring needs its own gutter: the title and the badge line both run the full width otherwise. */
.calendar-job-item.selectable .calendar-job-title,
.calendar-job-item.selectable .calendar-job-time { padding-right: 30px; }

.day-select-check {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}

.day-select-check svg { width: 13px; height: 13px; stroke: #fff; opacity: 0; transition: opacity 0.12s; }

/* Border, not box-shadow-only: these rows are gradient cards with a coloured LEFT edge, so the picked
   state has to read through the priority colour. The glow is the same one the other lists use. */
.calendar-job-item.selected { border-color: var(--primary); border-left-color: var(--primary); box-shadow: var(--glow-cyan); }
.calendar-job-item.selected .day-select-check { background: var(--primary); border-color: var(--primary); }
.calendar-job-item.selected .day-select-check svg { opacity: 1; }

/* Dim unselected rows slightly, same as the tasks/clients lists and the gallery */
#dayDetailContent.day-selecting .calendar-job-item:not(.selected) { opacity: 0.55; }

@media (max-width: 768px) {
    /* Long-press IS the multi-select gesture on the day card, so a browser text selection (and the
       iOS/Android copy callout) must never start — the two fight and the native callout can eat the
       press. Set on the row AND its children: `user-select` does not inherit reliably across engines.
       Desktop keeps text selectable, same rule as the tasks and clients cards. */
    #dayDetailContent .calendar-job-item,
    #dayDetailContent .calendar-job-item * {
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }
}

.task-item.selectable { position: relative; }

.task-item.selectable .task-info { padding-right: 30px; }

.task-select-check {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}

.task-select-check svg { width: 13px; height: 13px; stroke: #fff; opacity: 0; transition: opacity 0.12s; }

.task-item.selected { border-color: var(--primary); box-shadow: var(--glow-cyan); }
.task-item.selected .task-select-check { background: var(--primary); border-color: var(--primary); }
.task-item.selected .task-select-check svg { opacity: 1; }

/* Dim unselected cards slightly, same as the notes list and the gallery */
#tasksList.task-selecting .task-item:not(.selected) { opacity: 0.55; }

.task-item:hover {
    border-color: var(--primary);
    border-left-color: var(--primary);
    box-shadow: var(--glow-cyan);
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.06), var(--surface-2));
}

/* Critical priority task - slow pulsing red glow */
.task-item.task-critical,
.calendar-job-item.task-critical {
    border-left-color: #c81e1e;
    animation: criticalPulse 3s ease-in-out infinite;
}

@keyframes criticalPulse {
    0%, 100% {
        box-shadow: 0 0 0 rgba(200, 30, 30, 0);
    }
    50% {
        box-shadow: 0 0 20px rgba(200, 30, 30, 0.6), 0 0 40px rgba(255, 30, 30, 0.3);
    }
}

.task-info {
    flex: 1;
}

.task-title {
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 6px;
    font-size: 15px;
}

.task-description {
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-secondary);
    margin-bottom: 10px;
    /* Limit to ~10 lines — full text in the detail card */
    max-height: calc(13px * 1.6 * 10);
    overflow: hidden;
}

/* Soft fade only when the description is actually truncated (class added by JS) */
.task-description.truncated {
    -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 24px), transparent 100%);
    mask-image: linear-gradient(to bottom, black calc(100% - 24px), transparent 100%);
}

.task-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    font-size: 12px;
    color: var(--text-secondary);
}

.task-meta span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.meta-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.meta-icon.warn {
    color: var(--danger);
}

.task-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 9px;
    background-color: rgba(0, 217, 255, 0.08);
    border: 1px solid rgba(0, 217, 255, 0.35);
    border-radius: 0;
    font-size: 10px;
    font-weight: 700;
    color: var(--primary);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.task-badge.priority-high {
    background-color: rgba(255, 102, 0, 0.12);
    border-color: rgba(255, 102, 0, 0.5);
    color: #ff6600;
    box-shadow: inset 0 0 10px rgba(255, 102, 0, 0.15);
}

.task-badge.priority-critical {
    background-color: rgba(200, 30, 30, 0.15);
    border-color: rgba(255, 30, 30, 0.7);
    color: #ff6060;
    font-weight: 800;
    box-shadow: inset 0 0 12px rgba(255, 30, 30, 0.2), 0 0 8px rgba(255, 30, 30, 0.3);
}

.task-badge.priority-medium {
    background-color: rgba(255, 215, 0, 0.10);
    border-color: rgba(255, 215, 0, 0.45);
    color: #e6c200;
    box-shadow: inset 0 0 10px rgba(255, 215, 0, 0.12);
}

.task-badge.priority-low {
    background-color: rgba(0, 255, 163, 0.08);
    border-color: rgba(0, 255, 163, 0.4);
    color: var(--success);
}

.task-badge.priority-none {
    background-color: rgba(120, 130, 140, 0.08);
    border-color: rgba(140, 150, 160, 0.3);
    color: var(--text-secondary);
}

.task-badge.status-pending {
    background-color: rgba(160, 160, 160, 0.08);
    border-color: rgba(160, 160, 160, 0.35);
    color: var(--text-secondary);
}

.task-badge.status-in-progress {
    background-color: rgba(0, 217, 255, 0.10);
    border-color: rgba(0, 217, 255, 0.5);
    color: var(--primary);
    box-shadow: inset 0 0 10px rgba(0, 217, 255, 0.12);
}

.task-badge.status-completed {
    background-color: rgba(0, 255, 163, 0.10);
    border-color: rgba(0, 255, 163, 0.5);
    color: var(--success);
    box-shadow: inset 0 0 10px rgba(0, 255, 163, 0.12);
}

.task-actions {
    display: flex;
    gap: 8px;
}

.task-btn {
    background: none;
    border: 1px solid var(--border);
    cursor: pointer;
    font-size: 14px;
    padding: 8px;
    border-radius: var(--btn-radius);
    transition: var(--transition);
    color: var(--text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.task-btn:hover {
    border-color: var(--primary);
    color: var(--primary);
    background-color: rgba(0, 217, 255, 0.1);
    box-shadow: var(--glow-cyan);
}

.task-btn svg {
    width: 15px;
    height: 15px;
}

.empty-state {
    text-align: center;
    padding: 60px 20px;
    color: var(--text-secondary);
}

.empty-state-icon {
    margin-bottom: 16px;
    opacity: 0.7;
    display: flex;
    align-items: center;
    justify-content: center;
}

.empty-state-icon svg {
    width: 48px;
    height: 48px;
    color: var(--primary);
    filter: drop-shadow(0 0 8px rgba(0, 217, 255, 0.5));
}

.empty-state-text {
    font-size: 16px;
    margin-bottom: 12px;
    font-weight: 600;
    letter-spacing: 1px;
}

/* Schedule Controls */
.schedule-controls {
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
}

.btn.active-view {
    background: linear-gradient(135deg, var(--primary), var(--accent2));
    color: #021016;
    border: 1px solid var(--primary);
    box-shadow: var(--glow-cyan);
}

/* Calendar & List View Display */
.calendar-view,
.list-view {
    display: none;
}

.calendar-view.active-schedule-view,
.list-view.active-schedule-view {
    /* flex column (not block) so .calendar-days can stretch to fill the box */
    display: flex;
    flex-direction: column;
}

/* Calendar View */
.calendar-view {
    background: linear-gradient(135deg, var(--bg-base) 0%, var(--surface-2) 100%);
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    box-shadow: 0 0 24px rgba(0, 217, 255, 0.08);
    /* Fill the space between the top and bottom bars (all viewports) */
    flex: 1;
    min-height: 0;
}

.calendar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
}

.calendar-header h3 {
    font-size: 15px;
    font-weight: 800;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 3px;
    min-width: 140px;
    text-align: center;
    flex: 1;
    text-shadow: 0 0 10px rgba(0, 217, 255, 0.3);
}

.calendar-nav-btn {
    background: transparent;
    border: none;
    color: var(--primary);
    cursor: pointer;
    padding: 6px 10px;
    border-radius: var(--btn-radius);
    font-size: 14px;
    font-weight: bold;
    transition: var(--transition);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    flex-shrink: 0;
}

.calendar-nav-btn:hover {
    color: #fff;
    text-shadow: 0 0 12px rgba(0, 217, 255, 0.9);
}

/* Month/year header is tappable — opens the month picker */
.calendar-month {
    cursor: pointer;
    user-select: none;
}

.calendar-month:hover {
    color: var(--primary);
    text-shadow: 0 0 14px rgba(0, 217, 255, 0.6);
}

/* Month/Year picker modal */
.mp-year-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 14px;
}

.mp-step-btn { padding: 0; font-size: 16px; }

.mp-year-label {
    font-size: 18px;
    font-weight: 800;
    letter-spacing: 2px;
    color: var(--primary);
    text-shadow: 0 0 10px rgba(0, 217, 255, 0.3);
}

/* Scrollable year strip — always visible */
.mp-year-strip {
    display: flex;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    gap: 6px;
    padding: 4px 2px 10px;
    margin-bottom: 8px;
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 217, 255, 0.5) transparent;
}

.mp-year-strip.open { display: flex; }

.mp-year-chip {
    flex: 0 0 auto;
    min-width: 54px;
    min-height: 44px;   /* thumb-sized: it is tapped to jump years */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 8px 4px;
    text-align: center;
    background-color: var(--bg-base);
    border: 1px solid var(--border);
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.5px;
    cursor: pointer;
    transition: var(--transition);
}

.mp-year-chip:hover {
    border-color: var(--primary);
    color: var(--primary);
}

.mp-year-chip.selected {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.25), rgba(0, 255, 163, 0.1));
    border-color: var(--primary);
    color: var(--primary);
}

.mp-year-strip::-webkit-scrollbar { height: 6px; }
.mp-year-strip::-webkit-scrollbar-track { background: transparent; }
.mp-year-strip::-webkit-scrollbar-thumb { background: rgba(0, 217, 255, 0.4); }

.mp-month-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}

.mp-month-btn {
    min-height: 44px;   /* thumb-sized: this is the month you tap to choose */
    padding: 12px 6px;
    background-color: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--btn-radius);
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.5px;
    cursor: pointer;
    transition: var(--transition);
}

.mp-month-btn:hover {
    border-color: var(--primary);
    color: var(--primary);
}

.mp-month-btn.selected {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.25), rgba(0, 255, 163, 0.1));
    border-color: var(--primary);
    color: var(--primary);
}

@media (max-width: 768px) {
    .mp-month-grid { grid-template-columns: repeat(3, 1fr); }
}

.calendar-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
    margin-bottom: 6px;
}

.weekday {
    text-align: center;
    font-weight: 700;
    color: var(--primary);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 1px;
    padding: 4px 0;
    text-shadow: 0 0 6px rgba(0, 217, 255, 0.35);
}

/* Clips the sliding grid during month transitions */
#calendarDaysTrack {
    overflow: hidden;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.calendar-days {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
    margin-bottom: 12px;
    /* Scale to fit: rows take an equal share of the available height and can
       never grow past it, so the month always fits between the bars. */
    flex: 1;
    min-height: 0;
    grid-auto-rows: minmax(0, 1fr);
    will-change: transform; /* hint GPU compositing for slide animations */
}

.calendar-day {
    min-height: 0; /* row height comes from the grid's equal share */
    aspect-ratio: auto;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    cursor: pointer;
    transition: var(--transition);
    font-weight: 600;
    color: var(--text-secondary);
    font-size: 11px;
    position: relative;
    padding: 6px 8px;
    gap: 4px;
    overflow: hidden;
}

.calendar-day-num {
    align-self: flex-start;
    font-size: 11px;
    line-height: 1.2;
    flex-shrink: 0;
}

.calendar-day-dots {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
    justify-content: center;
    flex: 1;
}

.priority-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    border-radius: 50%;
    font-size: 10px;
    font-weight: 700;
    padding: 2px 4px;
    color: var(--bg-base);
    transition: var(--transition);
}

.priority-dot:hover {
    transform: scale(1.15);
}

.priority-dot.priority-critical {
    background: #c81e1e;
    color: white;
    font-weight: 800;
    animation: criticalPulse 3s ease-in-out infinite;
}

.priority-dot.priority-high {
    background: #ff6600;
    color: white;
}

.priority-dot.priority-medium {
    background: #ffd700;
    color: #21160a;
}

.priority-dot.priority-low {
    background: #00ffa3;
    color: #021016;
}

.priority-dot.priority-none {
    background: #7a8590;
    color: white;
}

.calendar-day:hover {
    border-color: var(--primary);
    background: rgba(0, 217, 255, 0.1);
    box-shadow: var(--glow-cyan);
}

.calendar-day.today {
    border-color: var(--primary);
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.18), rgba(0, 255, 163, 0.10));
    color: var(--primary);
    box-shadow: inset 0 0 12px rgba(0, 217, 255, 0.15);
}

.calendar-day.today .calendar-day-num {
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.7);
}

.calendar-day.selected {
    background: linear-gradient(135deg, var(--primary), var(--accent2));
    color: #021016;
    border-color: var(--primary);
    font-weight: 700;
    box-shadow: var(--glow-cyan);
}

.calendar-day.selected .calendar-day-num {
    color: #021016;
}

/* (dot indicator retired - entry titles in the day frame serve as the marker) */

.calendar-day.other-month {
    color: var(--text-secondary);
    opacity: 0.3;
    cursor: default;
}

.calendar-day.other-month:hover {
    border-color: var(--border);
    background: var(--surface-1);
    box-shadow: none;
}

.calendar-jobs-detail {
    border-left: 3px solid var(--primary);
    border-radius: 0;
    padding: 12px;
    max-height: 280px;
    overflow-y: auto;
    box-shadow: inset 0 0 20px rgba(0, 217, 255, 0.04);
    /* Shrinkable on short viewports so the calendar always fits */
    flex-shrink: 1;
    min-height: 0;
}

.calendar-jobs-detail h4 {
    font-size: 12px;
    font-weight: 800;
    color: var(--primary);
    margin-bottom: 8px;
    text-transform: uppercase;
    letter-spacing: 2px;
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.4);
}

.calendar-job-item {
    background: linear-gradient(135deg, var(--bg-card) 0%, var(--surface-2) 100%);
    border-left: 2px solid var(--primary);
    padding: 8px;
    border-radius: 0;
    margin-bottom: 6px;
    cursor: pointer;
    transition: var(--transition);
    font-size: 11px;
}

.calendar-job-item:hover {
    box-shadow: var(--glow-cyan);
}

.calendar-job-item:last-child {
    margin-bottom: 0;
}

.calendar-section-label {
    font-size: 10px;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 1px;
    margin: 12px 0 6px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--border);
}

.calendar-section-label:first-child {
    margin-top: 0;
}

/* Priority group frames in the day-detail task list — each group sits in its own
   bordered box tinted/bordered with that priority's color (replaces old dividers) */
.day-priority-frame {
    border: 1px solid var(--border);
    background-color: transparent;
    padding: 8px;
    margin-bottom: 10px;
}

.day-priority-frame:last-of-type {
    margin-bottom: 0;
}

.day-priority-critical {
    border-color: rgba(255, 30, 30, 0.5);
    background-color: rgba(255, 30, 30, 0.06);
}

.day-priority-high {
    border-color: rgba(255, 102, 0, 0.45);
    background-color: rgba(255, 102, 0, 0.06);
}

.day-priority-medium {
    border-color: rgba(255, 215, 0, 0.4);
    background-color: rgba(255, 215, 0, 0.05);
}

.day-priority-low {
    border-color: rgba(0, 255, 163, 0.35);
    background-color: rgba(0, 255, 163, 0.05);
}

/* Priority group header in the day-detail task list (reuses cal-badge priority colors) */
.calendar-priority-label {
    display: inline-block;
    margin: 0 0 8px;
}

/* Day-detail task cards: left bar matches the task's priority */
.task-item-cal {
    border-left-color: var(--success); /* fallback (legacy/unknown) = low green */
}

.task-item-cal.task-priority-critical {
    border-left-color: #c81e1e;
}

.task-item-cal.task-priority-high {
    border-left-color: #ff6600;
}

.task-item-cal.task-priority-medium {
    border-left-color: #ffd700;
}

.task-item-cal.task-priority-low,
.task-item-cal.task-priority-none {
    border-left-color: #00ffa3;
}

.cal-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 7px;
    background-color: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 0;
    font-size: 9px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: capitalize;
}

.cal-badge.priority-high {
    background-color: rgba(255, 102, 0, 0.12);
    border-color: rgba(255, 102, 0, 0.5);
    color: #ff6600;
}

.cal-badge.priority-critical {
    background-color: rgba(200, 30, 30, 0.15);
    border-color: rgba(255, 30, 30, 0.7);
    color: #ff6060;
    font-weight: 800;
}

.cal-badge.priority-medium {
    background-color: rgba(255, 215, 0, 0.10);
    border-color: rgba(255, 215, 0, 0.45);
    color: #e6c200;
}

.cal-badge.priority-low {
    background-color: rgba(0, 255, 163, 0.08);
    border-color: rgba(0, 255, 163, 0.4);
    color: var(--success);
}

.cal-badge.priority-none {
    background-color: rgba(120, 130, 140, 0.08);
    border-color: rgba(140, 150, 160, 0.3);
    color: var(--text-secondary);
}

.cal-badge.status-completed {
    background-color: rgba(0, 255, 163, 0.10);
    border-color: rgba(0, 255, 163, 0.5);
    color: var(--success);
}

.cal-badge.status-in-progress {
    background-color: rgba(0, 217, 255, 0.10);
    border-color: rgba(0, 217, 255, 0.5);
    color: var(--primary);
}

/* Time-of-day badge on calendar task cards */
.cal-badge.time-badge {
    background-color: rgba(0, 217, 255, 0.08);
    border-color: rgba(0, 217, 255, 0.45);
    color: var(--primary);
    font-weight: 700;
}

/* Date-range badge on calendar task cards (ranged tasks appear on every day of the span) */
.cal-badge.range-badge {
    background-color: rgba(106, 59, 214, 0.10);
    border-color: rgba(106, 59, 214, 0.5);
    color: var(--violet);
    font-weight: 700;
}

.calendar-job-title {
    font-weight: 700;
    color: var(--text-primary);
    font-size: 11px;
    margin-bottom: 2px;
}

.calendar-job-time {
    font-size: 10px;
    color: var(--text-secondary);
    line-height: 1.3;
}

.calendar-empty-message {
    text-align: center;
    color: var(--text-secondary);
    padding: 12px;
    font-size: 11px;
}

/* Two-button group in a section header (Scheduling tab) */
.section-header .header-actions {
    display: flex;
    gap: 10px;
    flex-shrink: 0;
}

/* Per-day "Add a task" button inside the day detail panel */
.cal-add-task-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    width: 100%;
    margin-top: 10px;
    padding: 8px 10px;
    background: transparent;
    border: 1px dashed var(--primary);
    border-radius: var(--btn-radius);
    color: var(--primary);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    cursor: pointer;
    transition: var(--transition);
}

.cal-add-task-btn:hover {
    background: var(--surface-focus);
    box-shadow: var(--glow-cyan);
    border-style: solid;
}

/* Schedule View Layout */
.schedule-view {
    display: grid;
    gap: 20px;
}

@media (min-width: 768px) {
    .schedule-view {
        grid-template-columns: 1fr 2fr;
    }
}

/* Clients */
.clients-search {
    display: flex;
}

.search-input {
    flex: 1;
    padding: 14px 16px;
    border: 1px solid var(--border);
    border-radius: 0;
    font-size: 14px;
    background-color: var(--bg-base);
    color: var(--text-primary);
    transition: var(--transition);
}

.search-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
}

.clients-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.client-item {
    border-left: 3px solid var(--primary);
    border-radius: 0;
    padding: 18px;
    cursor: pointer;
    transition: var(--transition);
}

.client-item:hover {
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

.client-header {
    display: flex;
    justify-content: space-between;
    align-items: start;
    gap: 12px;
    margin-bottom: 10px;
}

.client-name {
    font-weight: 700;
    color: var(--text-primary);
    font-size: 15px;
}

.client-contact-info {
    font-size: 12px;
    color: var(--text-secondary);
    margin-top: 4px;
    line-height: 1.5;
}

.client-contact-info strong {
    color: var(--text-primary);
    font-weight: 600;
}

.client-category {
    display: inline-flex;
    padding: 4px 9px;
    background-color: rgba(0, 217, 255, 0.08);
    border: 1px solid rgba(0, 217, 255, 0.35);
    border-radius: 0;
    font-size: 10px;
    font-weight: 700;
    color: var(--primary);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.client-content {
    color: var(--text-secondary);
    font-size: 13px;
    margin-bottom: 10px;
    line-height: 1.6;
}

/* Settings */
.settings-group {
    border-radius: 0;
    padding: 24px;
    margin-bottom: 20px;
    border-left: 3px solid var(--primary);   /* surface: the ONE card rule */
}

.settings-group h3 {
    font-size: 15px;
    font-weight: 800;
    margin-bottom: 18px;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 2px;
}

/* Change Password (self-service) */
.password-change {
    margin-top: 24px;
    padding-top: 20px;
    border-top: 1px solid var(--border);
}

.password-change-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 800;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 2px;
    margin-bottom: 16px;
}

.password-change-title .icon-small {
    color: var(--primary);
    filter: drop-shadow(0 0 6px rgba(0, 217, 255, 0.5));
}

.password-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.password-message {
    padding: 10px 12px;
    border-radius: 0;
    font-size: 13px;
    border: 1px solid;
}

.password-message.error {
    background-color: rgba(255, 46, 99, 0.12);
    color: var(--danger-text);
    border-color: var(--danger);
    border-left: 3px solid var(--danger);
}

.password-message.success {
    background-color: rgba(0, 255, 163, 0.10);
    color: var(--success);
    border-color: var(--success);
    border-left: 3px solid var(--success);
    box-shadow: inset 0 0 12px rgba(0, 255, 163, 0.08);
}

.logout-btn {
    margin-top: 20px;
}

.setting-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 14px;
}

.setting-item label {
    font-weight: 600;
    color: var(--text-secondary);
    font-size: 13px;
}

.setting-value {
    font-weight: 700;
    color: var(--primary);
    font-size: 13px;
}

/* Database Connection (admin) — status line + hint in the child modal */
/* My Account: the username is fixed for non-admins — show it as locked, not as broken. */
#userModal .form-group.self-locked input { opacity: .6; cursor: not-allowed; }


.dbconn-hint {
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-secondary);
    background-color: rgba(0, 217, 255, 0.05);
    border-left: 3px solid var(--primary);
    padding: 10px 12px;
}

.dbconn-status {
    padding: 10px 12px;
    border-radius: 0;
    font-size: 13px;
    border: 1px solid;
    margin-bottom: 4px;
}
.dbconn-status.ok {
    background-color: rgba(0, 255, 163, 0.10);
    color: var(--success);
    border-color: var(--success);
    border-left: 3px solid var(--success);
}
.dbconn-status.error {
    background-color: rgba(224, 18, 82, 0.12);
    color: var(--danger-text);
    border-color: var(--danger);
    border-left: 3px solid var(--danger);
}

.about-text {
    color: var(--text-secondary);
    font-size: 13px;
    margin-bottom: 8px;
}

.about-text.small {
    font-size: 12px;
}

/* Modal */
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(2, 6, 12, 0.82);
    z-index: 9100;
    align-items: flex-end;
    justify-content: center;
    backdrop-filter: blur(6px);
}

.modal.active {
    display: flex;
}

/* Edit/form modals must render above detail-view modals.
   roleModal opens ON TOP of configModal (Settings → Roles), so it needs the
   same bump — without it the card renders behind Settings and looks dead. */
/* ================= LAYER TIERS (single source of truth) =================
   A card opened FROM a card must sit above it or it renders underneath and looks dead. Three tiers,
   asserted by test-layering.js:
     9200  page-level cards (a list page, a settings page, a detail card off a list)
     9300  a card opened from one of those (a member card from the Collective, a stats page from Team)
     9350  a surface opened from a 9300 card (the month picker from the hire date, the avatar gallery and
           the camera from a member card) — these also sit above every 9200 card, which is why the month
           picker used to be clipped behind the member card it was launched from.
   9400 lightbox · 9500 inline panels · 9600 inline confirm · 9999 orientation guard. */
#taskModal.active, #clientModal.active, #configModal.active, #collectiveModal.active,
#teamModal.active, #inventoryModal.active, #notesListModal.active, #workLogsModal.active,
#galleryModal.active, #bugReportModal.active, #mapModal.active, #weatherModal.active,
#scheduleSearchModal.active, #dayDetailModal.active, #taskDetailModal.active,
#installModal.active, #syncModal.active, #clientMapModal.active, #cameraModal.active {
    z-index: 9200;
}
#userModal.active, #teamStatsModal.active, #inventoryItemModal.active, #inventoryDetailModal.active,
#invCategoriesModal.active, #invCameraModal.active, #notesFormModal.active, #workLogsFormModal.active,
#bugFormModal.active, #bugDetailModal.active, #clientDetailModal.active, #roleModal.active,
#changePasswordModal.active, #dbConnModal.active, #aiAvatarModal.active {
    z-index: 9300;
}
#avatarGalleryModal.active, #userCameraModal.active, #userCropModal.active, #monthPickerModal.active,
#invCropModal.active {
    z-index: 9350;
}

/* The map picker opens FROM the client form (map icon) — it must sit ABOVE
   clientModal or the window renders behind the still-open edit card and looks
   dead. One step above the 9200 form tier keeps it under nothing else. */
#clientMapModal.active {
    z-index: 9300;
}

/* The camera is also launched FROM the Notes form (Take) — notesFormModal now sits
   at the 9200 form tier (edit-over-detail, like taskModal), so the camera must
   stay above it (but under the 9300 map picker) or the viewfinder renders
   behind the still-open notes card. */
#cameraModal.active {
    z-index: 9250;
}

.modal-content {
    background: linear-gradient(135deg, var(--bg-base), var(--accent));
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    width: 100%;
    max-width: 600px;
    max-height: 90vh;
    overflow-y: auto;
    animation: slideUp 0.3s ease;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
}

@keyframes slideUp {
    from {
        transform: translateY(100%);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

@media (min-width: 768px) {
    .modal {
        align-items: center;
    }
    
    .modal-content {
        border-radius: 0;
        max-height: 80vh;
    }
}

/* Mobile: modals fill the entire screen */
@media (max-width: 768px) {
    .modal {
        z-index: 1001; /* Above the fixed bottom nav so it truly fills the screen */
    }
    
    .modal-content {
        width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
        display: flex;
        flex-direction: column;
    }
    
    /* Detail card: body stretches so the action bar stays pinned to the bottom */
    .task-detail-body {
        flex: 1;
    }
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 24px; /* slimmer bar — was 24px all around, looked chunky */
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    background: var(--bg-base);
    z-index: 10;
}

.modal-header h2 {
    font-size: 16px;
    font-weight: 800;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 2px;
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.25);
}

.modal-close {
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: var(--text-secondary);
    transition: var(--transition);
    padding: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0;
}

/* Ghost frame's close glyph — pure visual (the sliding card is not
   interactive), sized/colored like the real .modal-close so the two frames
   look identical mid-swipe. pointer-events:none from JS keeps it inert. */
.ghost-close-x {
    background: none;
    border: none;
    font-size: 24px;
    color: var(--text-secondary);
    padding: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0;
}

.modal-close:hover {
    background-color: rgba(0, 217, 255, 0.1);
    color: var(--primary);
    box-shadow: var(--glow-cyan);
}

.modal-form {
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

@media (max-width: 600px) {
    .form-row {
        grid-template-columns: 1fr;
    }
}

.date-input-row {
    display: flex;
    gap: 8px;
    align-items: stretch;
}

.date-input-row input {
    flex: 1;
    min-width: 0;
}

.clear-date-btn {
    white-space: nowrap;
    flex-shrink: 0;
}

.task-detail-actions {
    justify-content: space-between;
    /* Vertical only: the horizontal inset comes from .form-actions (16px), so every pinned bar in
       the app shares one edge margin. The old `12px 24px` here never applied — .form-actions wins
       the equal-specificity tie by source order — so it is not left as a second source of truth. */
    padding-top: 12px;
    padding-bottom: 12px;
}

/* Task detail card: pin the action bar to the bottom of the card (all viewports).
   The body scrolls internally so Delete/Edit never scroll out of sight. */
#taskDetailModal .modal-content,
#clientDetailModal .modal-content {
    display: flex;
    flex-direction: column;
}

#taskDetailModal .task-detail-body,
#clientDetailModal .task-detail-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* Hide the clients/description box entirely when there's nothing to show */
.task-detail-desc:empty {
    display: none;
}

/* Mobile day card (fullscreen on mobile): body scrolls, header stays pinned */
#dayDetailModal {
    /* The incoming day's frame is a SIBLING that starts off-screen and slides
       in from the edge — the overlay must not clip it. */
    overflow: visible;
}

#dayDetailModal .modal-content {
    display: flex;
    flex-direction: column;
    /* The frame itself is dragged (JS translateX on this element) during a
       swipe, so clip its own interior instead of letting it scroll the page.
       The inner task list keeps its own scroll via .task-detail-body. */
    overflow: hidden;
    /* The card's entrance animation is the base bottom-sheet rise only; the
       two-panel swipe drives its own transforms, so no replay of slideUp on
       re-open after a swipe. */
    animation: slideUp 0.3s ease;
    /* We own the horizontal swipe gesture on the day card (raw touch
       handlers in setupDayDetailSwipe). pan-y keeps vertical task-list
       scrolling native; the handler flips this to `none` while a horizontal
       drag is locked in so the browser never claims the gesture. */
    touch-action: pan-y;
}

#dayDetailModal .task-detail-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* Pinned "Add Task" bar at the bottom of the day card — same treatment as
   the other windows' action bars (solid bg, never shrinks or scrolls away) */
#dayDetailModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */ /* solid so scrolling tasks don't show through */
}

/* Directional slide for swiping between days on the mobile card. The card
   enters from the swipe side and settles; a short fade masks the content swap.
   (The default bottom-sheet slideUp still plays when a day is tapped open.) */
@keyframes dayDetailSwipeInLeft {
    from { transform: translateX(48px); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}
@keyframes dayDetailSwipeInRight {
    from { transform: translateX(-48px); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}


.task-detail-body {
    padding: 20px 24px;
}

.task-detail-badges {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.task-detail-rows {
    display: flex;
    flex-direction: column;
}

.task-detail-row {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
    font-size: 13px;
}

.task-detail-row:last-child {
    border-bottom: none;
}

.task-detail-label {
    color: var(--text-secondary);
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    flex-shrink: 0;
    min-width: 90px;
}

.task-detail-value {
    color: var(--text-primary);
    font-weight: 500;
    flex: 1;
    word-break: break-word;
}

.task-detail-desc {
    margin-top: 16px;
    padding: 12px;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-left: 3px solid var(--primary);
    border-radius: 0;
    font-size: 13px;
    line-height: 1.6;
}

.task-detail-desc strong {
    display: block;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

.task-detail-desc p {
    margin: 0;
    color: var(--text-primary);
}

/* Client References Section */
.client-references {
    margin-top: 24px;
    padding-top: 24px;
    border-top: 2px solid var(--border);
}

.references-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 1.5px;
    margin-bottom: 12px;
    text-shadow: 0 0 8px rgba(0, 217, 255, 0.25);
}

.references-list {
    max-height: 300px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 2px;
}

.reference-card {
    border-left: 3px solid var(--primary);
    padding: 12px 14px;
    font-size: 13px;
    transition: var(--transition);
    cursor: pointer;
}

.reference-card:hover {
    background: color-mix(in srgb, var(--card-bg) 94%, var(--primary) 6%);   /* was --surface-2, which is nearly transparent and would darken the card */
    border-left-color: var(--success);
    box-shadow: var(--glow-cyan);
}

.reference-card-title {
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 6px;
}

.reference-card-meta {
    display: flex;
    gap: 12px;
    font-size: 11px;
    color: var(--text-secondary);
}

.reference-card-type {
    text-transform: uppercase;
    font-weight: 600;
    letter-spacing: 0.5px;
}

.references-empty {
    padding: 20px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 13px;
    font-style: italic;
}

/* Notes section on the client detail card */
.notes-card {
    border-left: 3px solid var(--success);
    display: flex;
    flex-direction: column;
    gap: 7px;
}
.notes-card:hover { border-left-color: var(--primary); }
.notes-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
}
.notes-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--success);
    background: color-mix(in srgb, var(--success) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--success) 35%, transparent);
    padding: 3px 9px;
    border-radius: 20px;
}
.notes-when {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 11px;
    color: var(--text-secondary);
}
.notes-when .meta-icon { width: 13px; height: 13px; }
.notes-when > span { display: inline-flex; align-items: center; gap: 4px; }
.notes-note {
    font-size: 12.5px;
    color: var(--text-secondary);
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ================= BOTTOM-BAR STANDARD (single source of truth) =================
   Reference geometry is TOOLS -> Notes (the Add Notes / Select bar), which the user
   asked every page to match:
       padding 12px above + 38px button (.btn-sm metrics) + 12px below + 1px border
       = 63px total
   Every bottom action bar / form footer / detail action row in the app renders 63px.
   Horizontal padding stays per-context so the buttons keep lining up with the content
   above them (form footers bleed to the card edge; list bars inset 16px; detail rows
   inset 24px like their body). */
.form-actions {
    display: flex;
    gap: 8px;
    /* Standard pinned-bar box: 12px vertical (the 63px bar) and the SAME 16px horizontal inset
       every other bar uses (.gallery-bottom-nav). This was `12px 0`, which left these bars'
       buttons touching the screen edge while the inventory / notes / gallery bars sat 16px in. */
    padding: 12px 16px;
    border-top: 1px solid var(--border);
}

/* .btn inside ANY bottom bar uses the compact scale, so the bar lands on 63px. */
.form-actions .btn,
.task-detail-actions .btn,
.confirm-actions .btn,
.notes-media-select-actions .btn,
.gallery-select-actions .btn,
.inline-sheet-actions .btn,
.lb-edit-actions .btn,
.lb-delete-btns .btn,
.inv-cat-form-actions .btn {
    padding: 10px 16px;
    font-size: 12px;
}

/* Add/Edit Task form: fields scroll, Cancel/Save stay pinned to the card's bottom */
#taskModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden; /* inner form scrolls instead of the whole card */
}

#taskModal .modal-form {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

#taskModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */ /* solid so scrolling fields don't show through */
}

/* Add/Edit Client form: same treatment — fields scroll, Cancel/Save pinned to the bottom */
#clientModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden; /* inner form scrolls instead of the whole card */
}

#clientModal .modal-form {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* Every remaining form/settings modal gets the SAME pinned-chrome treatment as
   task/client/notes above: the card itself does NOT scroll — the inner body does — so the
   header and the Save/Cancel row always stay put. Without this the whole card scrolled and
   Save/Cancel ended up BELOW the fold (measured: User form -236px, Item form -128px off the
   card bottom at a 760px-tall desktop window). */
#userModal .modal-content,
#roleModal .modal-content,
#dbConnModal .modal-content,
#changePasswordModal .modal-content,
#configModal .modal-content,
#collectiveModal .modal-content,
.inventory-item-modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
#userModal .modal-form,
#roleModal .modal-form,
#dbConnModal .modal-form,
#changePasswordModal .modal-form,
#configModal .modal-form,
#collectiveModal .modal-form,
#syncModal .modal-form,
.inventory-item-modal-content .modal-form {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}
#userModal .form-actions,
#roleModal .form-actions,
#dbConnModal .form-actions,
#changePasswordModal .form-actions,
#syncModal .form-actions,
.inventory-item-modal-content .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */ /* solid so scrolling fields don't show through */
}

/* Work Logs window (TOOLS → Work Logs): cards scroll, Cancel pinned to the bottom.
   Same layout treatment as the task/client form modals above. */
#workLogsModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden; /* card list scrolls instead of the whole card */
}

#workLogsModal #workLogsList {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 16px; /* .task-list has no own padding — cards need room inside the window */
}

/* Pinned bottom bar — solid so scrolling cards don't bleed through, matches the other windows */
#workLogsModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */
}

.work-log-client {
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 6px;
    letter-spacing: 0.5px;
}

.work-log-date {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.5px;
    color: var(--text-secondary);
    white-space: nowrap;
}

#clientModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */ /* solid so scrolling fields don't show through */
}

/* Notes LIST (TOOLS → Notes): the card list scrolls; header + Add bar pin.
   The notes FORM (notesFormModal) has its own rules below (fields scroll,
   Save/Cancel pinned). Mirrors the work-logs / inventory windows. */
#notesListModal .modal-content,
#workLogsModal .modal-content,
#bugReportModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

#notesListModal .notes-list,
#workLogsModal .notes-list,
#bugReportModal .notes-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 16px; /* .notes-card has no own margin room — cards need it inside the window */
}

/* Pinned bottom bar (Add Notes) — solid so scrolling cards don't bleed through.
   Standard action-bar metrics, shared with the inventory and gallery select bars. */
#notesListModal .notes-bottom-nav,
#workLogsModal .notes-bottom-nav,
#bugReportModal .notes-bottom-nav {
    flex-shrink: 0;
    border-top: 1px solid var(--border);
    padding: 12px 16px;
    gap: 8px;
}

/* Add bar holds two actions (Add Notes | Select) — matched pairs: equal width.
   An unstyled button in a pinned bar sits at its natural width and looks off. */
#notesListModal .notes-add-bar .btn,
#workLogsModal .notes-add-bar .btn,
#bugReportModal .notes-add-bar .btn {
    flex: 1;
    /* Three equal 113px buttons: a wrapped label ("ADD NOTES" needs ~129px) made the row 54px
       and the BAR 79px, breaking the 63px standard the user compares every page against.
       Labels stay short ("Add", "Select", "Close") and can never wrap. */
    white-space: nowrap;
}

/* ---- Notes list: multi-select mode (long-press a card, or the Select button) ---- */
.notes-select-bar {
    display: flex;
    align-items: center;
    gap: 8px;
}

.notes-select-count {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}

/* Delete (primary) LEFT / Cancel RIGHT, each an equal half of the remaining row */
.notes-select-actions {
    display: flex;
    flex: 1;
    gap: 8px;
}

.notes-select-actions .btn {
    flex: 1;
}

/* Checkbox ring — mirrors .gallery-thumb-check; only in select mode */
#notesListModal .notes-card.selectable,
#bugReportModal .notes-card.selectable {
    position: relative;
}

/* Keep the head's right-hand content clear of the ring */
#notesListModal .notes-card.selectable .notes-card-head,
#bugReportModal .notes-card.selectable .notes-card-head {
    padding-right: 30px;
}

.notes-card-check {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}

.notes-card-check svg {
    width: 13px;
    height: 13px;
    stroke: #fff;
    opacity: 0;
    transition: opacity 0.12s;
}

.notes-card.selected {
    border-left-color: var(--primary);
    background: color-mix(in srgb, var(--card-bg) 90%, var(--primary) 10%);
}

.notes-card.selected .notes-card-check {
    background: var(--primary);
    border-color: var(--primary);
}

.notes-card.selected .notes-card-check svg {
    opacity: 1;
}

/* Dim unselected cards slightly, same as the gallery */
#notesListModal .notes-list.notes-selecting .notes-card:not(.selected),
#bugReportModal .notes-list.notes-selecting .notes-card:not(.selected) {
    opacity: 0.55;
}

/* Client-name line on notes cards (the client-detail version only has badge +
   when + note; the TOOLS list spans all clients, so the client is shown). */
/* ---- Bug Report (TOOLS → Bug Report): viewer body + card text ---- */
#bugDetailModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.bug-detail {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 16px;
}

/* Bug cards are tap / long-press targets and long-press IS the multi-select gesture, so a text
   selection or the iOS/Android copy callout must never start — the same rule the tasks/clients cards
   use. Set on the card AND its children: `user-select` does not inherit reliably across engines. */
#bugList .notes-card,
#bugList .notes-card * {
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

/* The VIEWER is deliberately left selectable: that is where a report is read (and copied from). */

/* Who filed it — a line of its own so it is never mistaken for the description. */
.bug-by {
    margin: 2px 0 6px;
}

/* The description is free text — keep the user's line breaks and long words. */
.bug-detail-body {
    white-space: pre-wrap;
    word-break: break-word;
    line-height: 1.55;
    font-size: 14px;
}

.bug-detail-body .bug-detail-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 10px;
    white-space: pre-wrap;
}

/* The report type chip: the notes badge's pill, tinted per type so a card is scannable at a glance.
   Bug is the fallback (an entry with no type, or one from before the TYPE dropdown existed). */
.bug-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    padding: 3px 9px;
    border-radius: 20px;
    color: var(--text-secondary);
    background: color-mix(in srgb, var(--text-secondary) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--text-secondary) 35%, transparent);
}

.bug-badge.bug-type-bug {
    color: var(--danger-text);
    background: color-mix(in srgb, var(--danger-text) 14%, transparent);
    border-color: color-mix(in srgb, var(--danger-text) 35%, transparent);
}

.bug-badge.bug-type-feature {
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 14%, transparent);
    border-color: color-mix(in srgb, var(--primary) 35%, transparent);
}

.bug-badge.bug-type-suggestion {
    color: var(--warning-text);
    background: color-mix(in srgb, var(--warning-text) 14%, transparent);
    border-color: color-mix(in srgb, var(--warning-text) 35%, transparent);
}

.bug-badge.bug-type-other {
    color: var(--text-secondary);
    background: color-mix(in srgb, var(--text-secondary) 14%, transparent);
    border-color: color-mix(in srgb, var(--text-secondary) 35%, transparent);
}

.notes-client {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--primary);
    margin-top: 6px;
}

#notesFormModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

#notesFormModal .modal-form {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

#notesFormModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */
}

/* Work Logs add form: identical treatment to the notes/task/client forms — the fields scroll inside
   the card and Save/Cancel stay pinned to its bottom edge. Without this the bar sat wherever the
   fields ended instead of on the bar line. */
#workLogsFormModal .modal-content,
#bugFormModal .modal-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

#workLogsFormModal .modal-form,
#bugFormModal .modal-form {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

#workLogsFormModal .form-actions,
#bugFormModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */ /* solid so scrolling fields don't show through */
}

/* Themed confirm/notice dialog — compact card, same chrome as the other modals.
   Body scrolls if the message is long; buttons stay pinned to the bottom. */
.confirm-dialog {
    max-width: 420px;
    display: flex;
    flex-direction: column;
}

.confirm-body {
    padding: 24px;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-primary);
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.confirm-body p {
    margin: 0;
}

/* ===== THE CONFIRM / NOTICE DIALOG IS A COMPACT CARD (`20260915confirmcard1`) =====
   Every delete in the app that asks first comes through this one dialog (tasks, clients, notes, media
   batches, users, roles, inventory, the day card's batches, the AI key), so this is the rule that makes
   them all match the card the user picked out in Clients → client → media: dimmed + blurred backdrop, a
   320px card with the cyan top edge and glow, a centred title, one line of detail, and the two buttons
   INSIDE the card side by side (Delete danger / Cancel secondary).
   Previously the mobile `@media (max-width: 768px) .modal-content { width:100%; height:100% }` sheet rule
   applied, so every delete question took over the whole screen. Id-scoped rules here outrank that class
   rule, so the card stays a card at every width. `#mediaDeleteConfirm` and the lightbox's
   `#lightboxDeleteOverlay` are already this card and are untouched. */
#confirmDialog {
    align-items: center;    /* the base .modal is a bottom sheet — this one is centred */
    padding: 20px;
}
#confirmDialog .confirm-dialog {
    max-width: 320px;
    height: auto;           /* beats the mobile full-screen sheet */
    max-height: 80vh;       /* a long warning scrolls inside the card, never off the screen */
    padding: 0;             /* the inner blocks carry the card's own padding */
}
#confirmDialog .modal-header {
    position: static;       /* no sticky header inside a card that fits on screen */
    z-index: auto;
    justify-content: center;
    background: transparent;
    border-bottom: none;
    padding: 24px 24px 0;
}
#confirmDialog .modal-header h2 {
    /* the card's own title type: 15px sentence case, not the app's uppercase letterspaced bar */
    font-size: 15px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    text-align: center;
    text-shadow: none;
}
#confirmDialog .confirm-body {
    padding: 8px 24px 24px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
    text-align: center;
}
#confirmDialog .confirm-actions {
    position: static;       /* was a pinned bottom bar (the shared sticky-bar rule) */
    margin-top: 0;
    z-index: auto;
    border-top: none;
    padding: 0 24px 24px;
    gap: 10px;
}
#confirmDialog .confirm-actions .btn { flex: 1; white-space: nowrap; }

.confirm-actions {
    padding: 12px 24px; /* standard bottom-bar vertical */
    /* Frosted with every other bar now. Safe here: .confirm-body is its own scroller (flex:1;
       overflow-y:auto), so a long message never sits behind this bar. */
    flex-shrink: 0;
}

.form-actions .btn {
    flex: 1;
}

/* Users List */
#usersList {
    margin-top: 16px;
}

/* Responsive Design */
@media (max-width: 768px) {
    .section-header {
        flex-direction: column;
        align-items: stretch;
    }
    
    .section-header .btn {
        width: 100%;
    }

    
    .section-header .header-actions {
        width: 100%;
        flex-direction: column;
        gap: 8px;
    }
    
    .section-header .header-actions .btn {
        width: 100%;
    }
    
    .task-item {
        flex-direction: column;
    }

    /* Task cards are tap / long-press targets — long-press IS the multi-select gesture, so a
       browser text selection or the iOS/Android copy callout must never start. Set on the card
       AND its children: `user-select` does not inherit reliably across engines. */
    #tasksList .task-item,
    #tasksList .task-item *,
    #clientsList .client-item,
    #clientsList .client-item * {
        -webkit-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }
    
    .nav-tab {
        font-size: 11px;
        padding: 12px 8px;
    }
    
    .header-top {
        padding: 12px 16px;
    }
    
    .login-card {
        padding: 30px 20px;
    }
    
    .tab-content {
        padding: 16px;
        gap: 12px;
    }
    
    .calendar-view {
        padding: 12px;
    }
    
    .calendar-header h3 {
        font-size: 13px;
        min-width: 110px;
    }
    
    .calendar-nav-btn {
        width: 32px;
        height: 32px;
        padding: 4px;
        font-size: 14px;
    }
    
    .calendar-day {
        min-height: 80px;
        font-size: 10px;
        padding: 5px 6px;
        gap: 3px;
    }
    
    .calendar-day-num {
        font-size: 10px;
    }
    
    /* Dots in a compact 2x2 grid (max 4 priority levels) so they never
       stack vertically and make the day frames too tall */
    .calendar-day-dots {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, auto));
        justify-content: center;
        align-content: center;
        gap: 3px;
    }
    
    .priority-dot {
        width: 16px;
        height: 16px;
        min-width: 16px;
        padding: 0;
        font-size: 8px;
    }
    
    .weekday {
        font-size: 9px;
        padding: 3px 0;
    }
    
    .calendar-jobs-detail {
        display: none; /* Mobile uses a fullscreen day card instead of inline list */
    }
    
    /* Calendar fills the screen on mobile: header + weekdays stay fixed,
       the 6 rows of days stretch to fill all remaining space. */
    #scheduleTab.tab-content.active {
        padding: 10px 10px 84px; /* keep the bottom reservation for the fixed nav */
        gap: 8px;
    }
    
    /* NOTE: must match specificity of .calendar-view.active-schedule-view
       (display:block) or the flex layout below is silently ignored. */
    .calendar-view.active-schedule-view {
        flex: 1;
        min-height: 0;
        display: flex;
        flex-direction: column;
        padding: 12px;
        gap: 8px;
        /* Horizontal swipes are handled by JS (month change); vertical page
           scroll stays native. Same pattern as the gallery lightbox. */
        touch-action: pan-y;
    }
    
    .calendar-days {
        flex: 1;
        min-height: 0;
        margin-bottom: 0;
        /* minmax(0, 1fr): rows can never grow past their equal share of the
           available height — even a day packed with dots stays inside its row
           (overflow is clipped), so the month always fits without scrolling. */
        grid-auto-rows: minmax(0, 1fr);
    }
    
    .calendar-day {
        min-height: 0; /* row size comes from the grid, not a fixed minimum */
    }
    
    .task-detail-actions {
        position: sticky;
        bottom: 0;
        border-top: 1px solid var(--border);
        padding-bottom: max(12px, env(safe-area-inset-bottom));
    }
    
    .task-detail-actions .btn {
        flex: 1;
    }
    
    /* Add/Edit Task + day card: keep the pinned button bars clear of the home-indicator area */
    #taskModal .form-actions,
    #dayDetailModal .form-actions {
        padding-bottom: max(12px, env(safe-area-inset-bottom));
    }

    /* Confirm/notice dialog: same safe-area treatment for its pinned buttons */
    .confirm-actions {
        padding-bottom: max(12px, env(safe-area-inset-bottom));
    }

    /* Tasks page: pin "+ Add Task" to the bottom of the screen (just above the
       fixed nav bar) so it's always one thumb-tap away — same treatment as the
       other pages' pinned action bars. Desktop keeps it in the section header. */
    #tasksTab .section-header.tasks-add-bar {
        position: fixed;
        left: 0;
        right: 0;
        bottom: var(--mobile-nav-h); /* sits flush on top of the pinned nav */
        z-index: 150; /* below modals (1000/1001), above page content */
        /* 17px sides, not 16px: the modal bars' buttons sit 17px from the screen edge (16px of
           padding inside a card with a 1px edge) and every pinned bar must match. */
        padding: 12px 17px;
        border-top: 1px solid var(--border);   /* surface: the shared frosted bottom-bar rule */
        box-shadow: 0 -4px 24px rgba(0, 217, 255, 0.10);
        /* The generic mobile `.section-header { flex-direction: column }` would stack this
           bar's actions (and the select bar's) into TWO rows — 90px instead of the 63px
           standard. This rule is more specific, so it restores the single row. */
        flex-direction: row;
        align-items: center;
    }

    #tasksTab .section-header.tasks-add-bar .btn {
        /* Two matched actions (Add Task | Select) share the row — width:100% would stack them
           to two lines and the bar would grow past the 63px standard. */
        flex: 1;
        width: auto;
        white-space: nowrap;
    }

    /* Keep the last task card clear of the pinned bar */
    #tasksTab.tab-content.active {
        padding-bottom: calc(var(--mobile-nav-h) + 80px);
    }

    /* Clients page: pin "Add Client" to the bottom of the screen (just above
       the fixed nav bar), same treatment as the tasks page. Desktop keeps it in
       the section header. */
    #clientsTab .section-header.clients-add-bar {
        /* The generic mobile `.section-header { flex-direction: column }` would stack this bar's
           actions (and the select bar's) into two rows — this rule is more specific. */
        flex-direction: row;
        align-items: center;
        position: fixed;
        left: 0;
        right: 0;
        bottom: var(--mobile-nav-h); /* sits flush on top of the pinned nav */
        z-index: 150; /* below modals (1000/1001), above page content */
        /* 17px sides, same as the tasks bar: the visible edge margin every pinned bar shares. */
        padding: 12px 17px;
        border-top: 1px solid var(--border);
        box-shadow: 0 -4px 24px rgba(0, 217, 255, 0.10);
    }

    #clientsTab .section-header.clients-add-bar .btn {
        /* Two matched actions (Add Client | Select) share the row — width:100% would stack them. */
        flex: 1;
        width: auto;
        white-space: nowrap;
    }

    /* Keep the last client card clear of the pinned bar */
    #clientsTab.tab-content.active {
        padding-bottom: calc(var(--mobile-nav-h) + 80px);
    }

    /* SELECT MODE OWNS THE BOTTOM ROW: the tab nav steps aside and the select bar drops into its slot,
       instead of the bar + nav stacking as two rows (user: "when i multi select in clients it brings up
       the new button bar, can you make that cover the main button bar also").
       These selectors must match the specificity of the pinned-bar rules above (#tab + 2 classes) and
       come AFTER them in the source, or the old bottom:var(--mobile-nav-h) wins. */
    body.page-select-mode .nav-tabs { display: none; }
    body.page-select-mode #tasksTab .section-header.task-select-bar,
    body.page-select-mode #clientsTab .section-header.clients-select-bar { bottom: 0; }
    /* The list keeps its reserved space, so the bar never lands on the last card. */
    body.page-select-mode #tasksTab.tab-content.active,
    body.page-select-mode #clientsTab.tab-content.active { padding-bottom: 80px; }

    /* A member who cannot add clients has no pinned add bar at all (`_applyClientPerms()` removes it), so the
       room reserved for it goes back to the list. Higher specificity than the rule above, so order is moot. */
    body.clients-readonly #clientsTab.tab-content.active { padding-bottom: var(--mobile-nav-h); }
}

/* Utility Classes */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

/* Loading State */
.loading {
    opacity: 0.6;
    pointer-events: none;
}

.spinner {
    display: inline-block;
    width: 16px;
    height: 16px;
    border: 2px solid rgba(0, 212, 255, 0.2);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* Scrollbar Styling */
::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track {
    background: var(--dark);
}

::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--primary);
}


/* ===== DARK MODE: muted pinks/magentas only (cyan+green stay vivid) ===== */
:root.dark-mode body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: var(--dark);
    background-image:
    linear-gradient(rgba(0, 217, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0, 217, 255, 0.045) 1px, transparent 1px),
    radial-gradient(circle at 50% 0%, rgba(0, 217, 255, 0.10) 0%, transparent 55%),
    radial-gradient(circle at 85% 100%, rgba(95, 100, 110, 0.07) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    color: var(--text-primary);
    line-height: 1.6;
    overflow: hidden;
    position: relative;
}
:root.dark-mode #loginScreen {
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, var(--login-1) 0%, var(--login-2) 50%, var(--login-3) 100%);
    background-image:
    linear-gradient(rgba(0, 217, 255, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0, 217, 255, 0.06) 1px, transparent 1px),
    radial-gradient(circle at 20% 50%, rgba(0, 217, 255, 0.18) 0%, transparent 50%),
    radial-gradient(circle at 80% 80%, rgba(95, 100, 110, 0.12) 0%, transparent 50%);
    background-size: 34px 34px, 34px 34px, 100% 100%, 100% 100%;
    min-height: 100vh;
    min-height: 100dvh; /* mobile: center within the VISIBLE viewport (excludes address bar) */
    height: auto;       /* let content grow past dvh on very short screens instead of clipping both ends */
}
:root.dark-mode .error-message {
    background-color: rgba(130, 100, 100, 0.12);
    color: var(--danger-text);
    padding: 12px;
    border-radius: 0;
    font-size: 13px;
    margin-bottom: 16px;
    border: 1px solid var(--danger);
    border-left: 3px solid var(--danger);
    box-shadow: inset 0 0 14px rgba(130, 100, 100, 0.10);
}
:root.dark-mode .btn-icon {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.10), rgba(95, 100, 110, 0.05));
    border: 1px solid var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: var(--btn-radius);
    transition: var(--transition);
    font-size: 14px;
    color: var(--text-primary);
}
:root.dark-mode .btn-icon:hover {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.2), rgba(95, 100, 110, 0.1));
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}
:root.dark-mode .btn-secondary {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.18), rgba(95, 100, 110, 0.08));
    color: var(--primary);
    border-color: var(--primary);
}
:root.dark-mode .btn-secondary:hover {
    background: linear-gradient(135deg, rgba(0, 217, 255, 0.3), rgba(95, 100, 110, 0.12));
    box-shadow: var(--shadow-md);
}
:root.dark-mode .btn-danger {
    background: linear-gradient(135deg, rgba(130, 100, 100, 0.25), rgba(130, 100, 100, 0.08));
    color: var(--danger-text);
    border-color: var(--danger);
}
:root.dark-mode .btn-danger:hover {
    background: linear-gradient(135deg, rgba(130, 100, 100, 0.4), rgba(130, 100, 100, 0.15));
    box-shadow: var(--glow-pink);
    color: var(--danger-text);
}
:root.dark-mode .task-badge.priority-high {
    background-color: rgba(255, 102, 0, 0.12);
    border-color: rgba(255, 102, 0, 0.5);
    color: #ff6600;
    box-shadow: inset 0 0 10px rgba(255, 102, 0, 0.15);
}
/* NOTE: do NOT re-add a `:root.dark-mode .calendar-nav-btn` block. It was a verbatim
   duplicate of the base rule above, and its higher specificity (0,2,0 vs 0,1,0) beat
   the `@media (max-width: 768px)` override below — so the 32x32 mobile size never
   applied and the calendar arrows rendered 36x36 on phones. The base rule already
   uses var(--primary), which is theme-aware, so the dark block added nothing. */
:root.dark-mode .cal-badge.priority-high {
    background-color: rgba(255, 102, 0, 0.12);
    border-color: rgba(255, 102, 0, 0.5);
    color: #ff6600;
}
:root.dark-mode .password-message.error {
    background-color: rgba(130, 100, 100, 0.12);
    color: var(--danger-text);
    border-color: var(--danger);
    border-left: 3px solid var(--danger);
}

/* ============================================================
   MOBILE BOTTOM NAVIGATION
   Pins Tasks / Schedule / Clients to the bottom of the screen
   as a full-width, evenly-split app-style tab bar.
   Desktop keeps the bar in its natural position under the header.
   ============================================================ */
@media (max-width: 768px) {
    /* Single source of truth for the pinned bottom-nav height — the Tasks page's
       pinned "+ Add Task" bar sits at exactly this offset so it touches the nav. */
    :root { --mobile-nav-h: 64px; }

    /* Reserve room at the bottom of the page for the pinned bar */
    .tab-content {
        padding-bottom: 84px;
    }

    .nav-tabs {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 200;
        padding: 0;
        height: var(--mobile-nav-h); /* explicit so the add-task bar can sit flush on top */
        box-sizing: border-box;
        border-top: 1px solid var(--border);
        border-bottom: none;
        box-shadow: 0 -4px 24px rgba(0, 217, 255, 0.10);
        overflow-x: hidden;
        /* Same frosted surface as every other bottom bar — but carried by a PSEUDO-ELEMENT, not by the
           nav itself. `backdrop-filter` (like filter/transform) makes an element the CONTAINING BLOCK for
           its fixed-position descendants, and the TOOLS dropdown menu (#mobileUserDropdownMenu) is one:
           with the frost on the nav, `position: fixed` re-anchored to the 64px nav box and the nav's
           overflow clipped the menu away — the button looked dead (measured: menu DOM rect fine, but
           elementFromPoint at its centre returned the page behind it). A ::before is nobody's ancestor,
           so the menu keeps the viewport as its containing block.
           Scoped to THIS media block: the same class is the app's top nav on desktop, which stays solid. */
        background: transparent;
    }

    .nav-tabs::before {
        content: '';
        position: absolute;
        inset: 0;
        background: color-mix(in srgb, var(--bg-base) 78%, transparent);
        -webkit-backdrop-filter: blur(4px);
        backdrop-filter: blur(4px);
        pointer-events: none;
    }

    /* the tabs paint above that surface */
    .nav-tabs > * { position: relative; z-index: 1; }

    .nav-tab {
        flex: 1 1 0;          /* equal share of the full screen width */
        min-width: 0;
        width: auto;
        flex-direction: column;   /* icon above label, like a mobile app */
        justify-content: center;
        align-items: center;
        gap: 5px;
        padding: 10px 4px 12px;
        font-size: 10px;
        letter-spacing: 1.5px;
        border-bottom: none;
        border-top: 2px solid transparent;
        border-left: 1px solid var(--border);
    }

    .nav-tab:first-child {
        border-left: none;
    }

    .nav-icon {
        width: 20px;
        height: 20px;
    }

    .nav-tab:hover {
        box-shadow: none;
        text-shadow: none;
    }

    .nav-tab.active {
        border-top-color: var(--primary);
        border-bottom-color: transparent;
        box-shadow: inset 0 2px 0 var(--primary), 0 0 14px rgba(0, 217, 255, 0.25);
    }

    /* TOOLS button — pinned as the rightmost slot of the bottom bar */
    .mobile-user-dropdown {
        display: block;
        flex: 1 1 0;
        min-width: 0;
        position: relative;
        border-left: 1px solid var(--border);
    }

    /* Keep the top-right user dropdown visible on mobile too */
    .header-top .user-dropdown:not(.mobile-user-dropdown) {
        display: block;
    }

    /* Compact user button on mobile — hide person icon, keep username + chevron */
    #userMenuBtn .icon-small {
        display: none;
    }

    #userMenuBtn {
        padding: 6px 10px;
        font-size: 12px;
        gap: 5px;
    }

    /* Dropdown opens below and aligns to the right edge */
    .header-top .user-dropdown:not(.mobile-user-dropdown) .dropdown-menu {
        right: 0;
        left: auto;
        top: 100%;
        min-width: 160px;
    }

    .mobile-tools-btn {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 5px;
        background: none;
        border: none;
        cursor: pointer;
        color: var(--text-secondary);
        font-family: inherit;
        text-transform: uppercase;
        letter-spacing: 1.5px;
        font-size: 10px;
        font-weight: 700;
        white-space: nowrap;   /* "TOOLS" must never wrap — match .nav-tab */
        transition: var(--transition);
    }

    .mobile-tools-btn:hover {
        color: var(--primary);
        background: rgba(0, 217, 255, 0.06);
        text-shadow: 0 0 8px rgba(0, 217, 255, 0.5);
    }

    .mobile-user-dropdown.active .mobile-tools-btn {
        color: var(--primary);
        text-shadow: 0 0 8px rgba(0, 217, 255, 0.7);
    }

    .dna-icon {
        width: 20px;
        height: 20px;
        color: currentColor;
    }

    .mobile-user-dropdown.active .dna-icon,
    .mobile-tools-btn:hover .dna-icon {
        color: var(--primary);
    }

    /* Menu opens upward from the TOOLS slot (fixed so it can't be clipped by the bar).
       TOOLS is the LAST of 4 equal slots in a full-width bar, so its right edge IS
       the screen's right edge — pin flush there. */
    .mobile-dropdown-menu {
        position: fixed;
        bottom: calc(var(--mobile-nav-h) + 8px); /* just above the pinned nav bar */
        right: 0;                                /* pinned to the right side of the button */
        top: auto;
    }

    .mobile-user-dropdown.active {
        border-top-color: var(--primary);
        box-shadow: inset 0 2px 0 var(--primary), 0 0 14px rgba(0, 217, 255, 0.25);
    }
}

/* ================================================================
   LEAFLET MARKERCLUSTER — BORG theme
   Overrides the default blue circles with cyan/green palette.
================================================================ */

/* Base cluster icon shell — all sizes share this */
.marker-cluster {
    background-clip: padding-box;
    border-radius: 50%;
    /* Flex-center the inner div so the count is always perfectly centred */
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Small cluster (2–999) */
.marker-cluster-small {
    background-color: rgba(0, 255, 163, 0.22);
    width: 40px !important;
    height: 40px !important;
}
.marker-cluster-small div {
    width: 36px;
    height: 36px;
    background-color: rgba(0, 255, 163, 0.80);
    border-radius: 50%;
    box-shadow: 0 0 10px rgba(0, 255, 163, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Medium cluster (1K) */
.marker-cluster-medium {
    background-color: rgba(0, 217, 255, 0.22);
    width: 40px !important;
    height: 40px !important;
}
.marker-cluster-medium div {
    width: 36px;
    height: 36px;
    background-color: rgba(0, 217, 255, 0.80);
    border-radius: 50%;
    box-shadow: 0 0 12px rgba(0, 217, 255, 0.65);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Large cluster (1K+) */
.marker-cluster-large {
    background-color: rgba(200, 255, 0, 0.22);
    width: 40px !important;
    height: 40px !important;
}
.marker-cluster-large div {
    width: 36px;
    height: 36px;
    background-color: rgba(200, 255, 0, 0.80);
    border-radius: 50%;
    box-shadow: 0 0 14px rgba(200, 255, 0, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Count label inside every cluster div */
.marker-cluster div span {
    font-size: 11px;
    font-weight: 900;
    font-family: 'Segoe UI', monospace;
    color: #05050c;
    letter-spacing: 0;
    line-height: 1;
    text-align: center;
    display: block;
}

/* ================================================================
   GALLERY — SELECTION MODE
================================================================ */

/* Floating toolbar that rises above the grid when items are selected */
.gallery-select-toolbar {
    position: sticky;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-top: 2px solid var(--danger);
    box-shadow: 0 -4px 20px rgba(255, 46, 99, 0.18);   /* surface comes from the shared bottom-bar rule */
    gap: 8px; /* standard action-bar gap, matches the notes/inventory bottom bars */
}

.gallery-select-count {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}

.gallery-select-actions {
    display: flex;
    gap: 10px;
}

.gallery-select-actions .btn-danger {
    display: flex;
    align-items: center;
    gap: 6px;
}

.gallery-select-actions .btn-danger svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}

/* Thumbnail: selection mode — show checkmark overlay */
.gallery-thumb.selectable,
.cluster-panel-thumb.selectable {
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;  /* iOS: suppress "Save Image" / "Open in…" sheet */
}

/* Checkbox ring — always present in selection mode, filled when selected */
.gallery-thumb-check {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255,255,255,0.85);
    background: rgba(0,0,0,0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}

.gallery-thumb-check svg {
    width: 13px;
    height: 13px;
    stroke: #fff;
    opacity: 0;
    transition: opacity 0.12s;
}

/* Selected state */
.gallery-thumb.selected .gallery-thumb-check,
.cluster-panel-thumb.selected .gallery-thumb-check {
    background: var(--primary);
    border-color: var(--primary);
}

.gallery-thumb.selected .gallery-thumb-check svg,
.cluster-panel-thumb.selected .gallery-thumb-check svg {
    opacity: 1;
}

/* Dim unselected thumbnails slightly when others are selected */
.gallery-selecting .gallery-thumb:not(.selected) {
    opacity: 0.55;
}

.cluster-panel-grid.gallery-selecting .cluster-panel-thumb:not(.selected) {
    opacity: 0.55;
}

/* Per-group match tag pill (shown above day heading when filtering) */
.gallery-match-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    margin: 8px 0 2px;
    background: rgba(0, 217, 255, 0.12);
    border: 1px solid rgba(0, 217, 255, 0.3);
    border-radius: 20px;
    font-size: 11px;
    font-weight: 700;
    color: var(--primary);
    letter-spacing: 0.4px;
}

.gallery-match-tag svg {
    width: 11px;
    height: 11px;
    stroke: var(--primary);
    flex-shrink: 0;
}

/* Gallery active-filter banner */
.gallery-filter-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    margin-bottom: 8px;
    background: rgba(0, 217, 255, 0.07);
    border: 1px solid rgba(0, 217, 255, 0.2);
    border-radius: 6px;
    font-size: 12px;
    color: var(--primary);
    font-weight: 600;
}

.gallery-filter-banner svg {
    width: 13px;
    height: 13px;
    stroke: var(--primary);
    flex-shrink: 0;
}

.gallery-filter-banner span {
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.gallery-filter-count {
    flex: 0 !important;
    color: var(--text-secondary);
    font-weight: 400;
    white-space: nowrap;
}

/* Schedule search bar */
.schedule-search-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-base);
}

.schedule-search-wrap svg {
    width: 16px;
    height: 16px;
    stroke: var(--text-secondary);
    flex-shrink: 0;
}

/* Gallery search bar */
.gallery-search-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-base);
}

.gallery-search-wrap svg {
    width: 16px;
    height: 16px;
    stroke: var(--text-secondary);
    flex-shrink: 0;
}

.gallery-search-input {
    flex: 1;
    background: none;
    border: none;
    outline: none;
    color: var(--text-primary);
    font-size: 14px;
    font-family: inherit;
}

/* Hide native browser clear button on search inputs */
.gallery-search-input::-webkit-search-cancel-button,
.gallery-search-input::-webkit-search-decoration,
.gallery-search-input::-ms-clear {
    display: none;
    appearance: none;
}

.gallery-search-input::placeholder {
    color: var(--text-secondary);
}

.gallery-search-clear {
    background: none;
    border: none;
    cursor: pointer;
    padding: 2px;
    color: var(--text-secondary);
    display: flex;
    align-items: center;
}

.gallery-search-clear svg {
    width: 14px;
    height: 14px;
}

/* Gallery bottom nav bar */
.gallery-bottom-nav {
    display: flex;
    flex-shrink: 0;
    border-top: 1px solid var(--border);
}

/* TOOLS -> Gallery bottom bar uses the app's standard buttons, not the old bespoke
   icon-over-label tab styling (user: "change the style of the tools>gallery buttons at the
   bottom to match the rest of the page"). .gallery-bottom-nav is shared with the notes and
   inventory bars, so the bar padding lives on .gallery-view-nav (this bar only). The buttons
   are plain .btn-sm; the view-switch JS swaps btn-primary / btn-secondary with .active, so
   the look comes from the real button classes instead of a copy of them. */
.gallery-view-nav {
    padding: 12px 16px;
    gap: 8px;
}

.gallery-bottom-nav .btn {
    flex: 1;
}

/* Client/task media grid — selection mode */
.client-media-thumb.selectable { cursor: pointer; }
.client-media-thumb.selected { border-color: var(--primary); opacity: 1; }
.client-media-thumb.selectable:not(.selected) { opacity: 0.6; }

.media-check {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid var(--border);
    background: var(--bg-base);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
}

.client-media-thumb.selected .media-check {
    background: var(--primary);
    border-color: var(--primary);
}

.media-check svg {
    width: 12px;
    height: 12px;
    stroke: #000;
    opacity: 0;
}

.client-media-thumb.selected .media-check svg {
    opacity: 1;
}

/* Client detail — bound media grid */
.client-media-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 5px;
    margin-top: 6px;
}

.client-media-thumb {
    position: relative;
    aspect-ratio: 1;
    overflow: hidden;
    background: var(--accent);
    border: 1px solid var(--border);
    cursor: pointer;
    border-radius: 4px;
    -webkit-touch-callout: none;
    user-select: none;
}

.client-media-thumb img,
.client-media-thumb video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.client-media-thumb:hover {
    border-color: var(--primary);
}

.client-media-ts {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0,0,0,0.6);
    color: #fff;
    font-size: 9px;
    padding: 2px 4px;
    text-align: center;
}

/* ================================================================
   GALLERY EDIT PANEL — bind media to client / task
================================================================ */
.gallery-edit-panel {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 9500;
    border-top: 2px solid var(--primary);
    box-shadow: 0 -4px 24px rgba(0,217,255,0.15);
    flex-direction: column;
    animation: clusterPanelUp 0.22s ease;
    /* display controlled entirely by JS — no default display here */
}

.gallery-edit-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.gallery-edit-panel-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--primary);
}

.gallery-edit-panel-body {
    padding: 14px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.gallery-edit-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-secondary);
    margin-bottom: 5px;
}

.gallery-edit-select {
    width: 100%;
    background: var(--surface-1);
    border: 1px solid var(--border);
    color: var(--text-primary);
    border-radius: 0;
    padding: 8px 10px;
    font-size: 14px;
    font-family: inherit;
    transition: var(--transition);
}

.gallery-edit-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
    background-color: var(--surface-focus);
}

.gallery-edit-select option {
    background-color: var(--bg-base);
    color: var(--text-primary);
    padding: 8px;
}

.gallery-edit-panel-actions {
    display: flex;
    gap: 10px;
    padding: 10px 14px 14px;
    border-top: 1px solid var(--border);
}

.gallery-edit-panel-actions .btn {
    flex: 1;
}

/* ================================================================
   GALLERY LIGHTBOX — full-screen in-page viewer with swipe nav
================================================================ */
.gallery-lightbox {
    position: fixed;
    inset: 0;
    /* Stacking slot: must outrank EVERY modal it can be opened from — .modal (9100,
       or 1001 on mobile), form modals (9200), camera (9250), client map (9300) — or
       the form paints over the viewer and it looks like nothing happened. Stays below
       .gallery-edit-panel (9500), which opens FROM the lightbox, and
       .inline-sheet-overlay (9600), so those still sit on top. */
    z-index: 9400;
    /* Fully opaque: at 0.97 the form underneath ghosted through — field labels and
       the Save/Cancel row showed as faint text behind the media. */
    background: #05050c;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    touch-action: pan-y;
}

.lightbox-media {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    overflow: hidden;
    padding: 48px 12px 0;
}

.lightbox-media img,
.lightbox-media video {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: 4px;
    user-select: none;
    -webkit-user-drag: none;
}

.lightbox-caption {
    width: 100%;
    padding: 10px 16px 14px;
    text-align: center;
    font-size: 11px;
    color: var(--text-secondary);
    letter-spacing: 0.5px;
    flex-shrink: 0;
}

/* In-lightbox overlays (delete confirm + bind sheet) — same themed card language
   as the modal system so they don't read as off-brand popups. */
.lb-delete-overlay {
    display: none;
    position: absolute;
    inset: 0;
    z-index: 11;
    background: rgba(2, 6, 12, 0.82);
    backdrop-filter: blur(4px);
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.lb-delete-card {
    background: linear-gradient(135deg, var(--bg-base), var(--accent));
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
    padding: 24px;
    width: 100%;
    max-width: 320px;
    text-align: center;
    animation: slideUp 0.3s ease;
}

.lb-delete-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 8px;
}

.lb-delete-sub {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 0 0 24px;
}

.lb-delete-btns { display: flex; gap: 12px; }
.lb-delete-btns .btn { flex: 1; }

.lb-edit-overlay {
    display: none;
    position: absolute;
    inset: 0;
    z-index: 10;
    background: linear-gradient(135deg, var(--bg-base), var(--accent));
    border-top: 2px solid var(--primary);
    flex-direction: column;
    box-shadow: var(--glow-cyan);
}

.lb-edit-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.lb-edit-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--primary);
}

.lb-edit-x {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 24px;
    cursor: pointer;
    line-height: 1;
    padding: 2px 6px;
}

.lb-edit-body {
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    flex: 1;
    overflow-y: auto;
}

.lb-edit-field label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

.lb-edit-field select {
    width: 100%;
    background: var(--surface-1);
    border: 1px solid var(--border);
    color: var(--text-primary);
    border-radius: 0;
    padding: 9px 10px;
    font-size: 14px;
    font-family: inherit;
    transition: var(--transition);
}

.lb-edit-field select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
    background-color: var(--surface-focus);
}

.lb-edit-field select option {
    background-color: var(--bg-base);
    color: var(--text-primary);
    padding: 8px;
}

.lb-edit-actions {
    display: flex;
    gap: 10px;
    padding: 12px 14px;
    border-top: 1px solid var(--border);
}

.lb-edit-actions .btn { flex: 1; }

.lightbox-close {
    position: absolute;
    top: 10px;
    right: 12px;
    background: rgba(255,255,255,0.08);
    border: none;
    color: #fff;
    font-size: 28px;
    line-height: 1;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10;
}

.lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255,255,255,0.08);
    border: none;
    color: #fff;
    font-size: 36px;
    line-height: 1;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10;
    padding-bottom: 3px;
}

.lightbox-prev { left: 8px; }
.lightbox-next { right: 8px; }

.lightbox-nav:active,
.lightbox-close:active {
    background: rgba(0, 217, 255, 0.2);
}

/* Hide nav arrows when only one item */
.lightbox-nav.hidden { display: none; }
/* Lightbox photo action bar — download + delete */
/* The viewer's bottom bar follows the app's pinned-bar standard — solid themed surface, 12px
   vertical padding, 38px pill buttons — instead of the old translucent, vertically-divided strip
   (the dividers are what made it read as a different app). */
.lightbox-action-bar {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    align-items: center;
    gap: 6px;
    /* max(), not calc(+): the app's other pinned bars use max(12px, env(safe-area-inset-bottom)),
       so a home-indicator phone gets the same 34px, not 12+34. */
    /* 17px sides, not 12px: the visible button margin must match every other pinned bar (16px of
       padding in a container with a 1px edge). This bar sat 5px closer to the screen edge. */
    padding: 12px 17px max(12px, env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border);
    z-index: 10;
}

.lightbox-action-btn {
    flex: 1;
    min-width: 0;
    height: 38px;                 /* the app's standard bar button height */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0 6px;
    /* No double-tap-zoom window and no 300ms wait to see whether the touch is a scroll:
       the tap on Edit/Delete/Download/Close is delivered immediately. */
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    background: transparent;
    border: 1px solid var(--primary);
    border-radius: 8px;
    color: var(--primary);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    cursor: pointer;
    text-decoration: none;
    text-transform: uppercase;
    white-space: nowrap;          /* a wrapped label would grow the bar past 63px */
}

.lightbox-action-btn svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.lightbox-action-delete {
    color: var(--danger-text, #ff4d4d);
    border-color: currentColor;   /* danger outline, same language as .btn-danger */
}

.lightbox-action-btn:active {
    background: var(--surface-focus, rgba(255,255,255,0.06));
}

/* Hide the native video fullscreen button entirely */
video::-webkit-media-controls-fullscreen-button,
video::-webkit-media-controls-toggle-closed-captions-button {
    display: none !important;
}
/* Fullscreen expand button on video in lightbox */


/* ================================================================
   CLUSTER PANEL — slide-up thumbnail sheet when tapping a cluster dot
================================================================ */
.cluster-panel {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    background: var(--bg-base);
    border-top: 2px solid var(--primary);
    box-shadow: 0 -4px 24px rgba(0, 217, 255, 0.15);
    max-height: 55%;
    display: flex;
    flex-direction: column;
    animation: clusterPanelUp 0.22s ease;
}

@keyframes clusterPanelUp {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

.cluster-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.cluster-panel-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--primary);
}

.cluster-panel-close {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
}

.cluster-panel-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    padding: 8px;
    overflow-y: auto;
}

.cluster-panel-thumb {
    position: relative;
    /* Padding-box square (not aspect-ratio): the panel's max-height constrains
       this grid, and constrained grids can collapse aspect-ratio rows. The
       padding trick sizes rows from real content so squares hold everywhere. */
    width: 100%;
    height: 0;
    padding-bottom: 100%;
    overflow: hidden;
    background: var(--accent);
    cursor: pointer;
    border: 1px solid var(--border);
    -webkit-touch-callout: none;
    user-select: none;
}

.cluster-panel-thumb img,
.cluster-panel-thumb video {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.cluster-panel-thumb:hover {
    border-color: var(--primary);
}

.cluster-panel-ts {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0,0,0,0.6);
    color: #fff;
    font-size: 9px;
    padding: 2px 4px;
    text-align: center;
    letter-spacing: 0.3px;
}

/* ================================================================
   CAMERA MODAL
================================================================ */

.camera-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 520px;
    width: 100%;
    background: var(--bg-base);
    height: 100%;
    justify-content: flex-end;
}

/* Mode toggle button — sits to the left of the shutter */
.camera-mode-toggle {
    width: 40px;
    height: 40px;
    border-radius: var(--btn-radius);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.camera-mode-toggle:hover {
    border-color: var(--primary);
    background: rgba(0,217,255,0.10);
    color: var(--primary);
}
.camera-mode-toggle svg {
    width: 20px;
    height: 20px;
}

/* Icon visibility logic:
   data-mode="photo" → currently IN photo mode → show video icon (tap = switch to video)
   data-mode="video" → currently IN video mode → show photo icon (tap = switch to photo) */
.camera-mode-toggle[data-mode="photo"] .cam-icon-photo { display: none; }
.camera-mode-toggle[data-mode="photo"] .cam-icon-video { display: block; }
.camera-mode-toggle[data-mode="video"] .cam-icon-photo { display: block; }
.camera-mode-toggle[data-mode="video"] .cam-icon-video { display: none; }

/* Spacer balances the layout (mirrors the mode toggle on the right) */
.camera-ctrl-spacer {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
}

/* Viewfinder */
.camera-viewfinder-wrap {
    position: relative;
    width: 100%;
    background: #000;
    aspect-ratio: 4/3;
    overflow: hidden;
}

#cameraViewfinder {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* REC badge */
.camera-rec-badge {
    position: absolute;
    top: 10px;
    left: 12px;
    display: none;
    align-items: center;
    gap: 5px;
    background: rgba(0,0,0,.6);
    color: var(--danger-text);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 2px;
    padding: 3px 8px;
    border-radius: 3px;
}

.camera-rec-badge.active {
    display: flex;
}

.rec-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--danger);
    animation: recBlink 1s step-start infinite;
}

@keyframes recBlink {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0; }
}

/* Geo pill */
.camera-geo-pill {
    position: absolute;
    bottom: 10px;
    left: 12px;
    display: flex;
    align-items: center;
    gap: 5px;
    background: rgba(0,0,0,.55);
    color: var(--primary);
    font-size: 10px;
    letter-spacing: 1px;
    padding: 3px 8px;
    border-radius: 20px;
    backdrop-filter: blur(4px);
}

.camera-geo-pill svg {
    width: 11px;
    height: 11px;
    flex-shrink: 0;
}

/* Preview panel (shown after capture) */
.camera-preview-wrap {
    width: 100%;
    background: #000;
    aspect-ratio: 4/3;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.camera-preview-wrap img,
.camera-preview-wrap video {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    display: block;
}

/* Shutter controls */
.camera-controls {
    padding: 12px 16px;
    background: var(--bg-base);
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: center;
    flex-shrink: 0;
}

/* Tap-to-focus ring */
.cam-focus-ring {
    position: absolute;
    width: 56px;
    height: 56px;
    border: 2px solid var(--primary);
    border-radius: 4px;
    transform: translate(-50%, -50%);
    pointer-events: none;
    animation: camFocusRing 0.9s ease forwards;
    z-index: 50;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.4);
}

@keyframes camFocusRing {
    0%   { opacity: 1; transform: translate(-50%,-50%) scale(1.4); }
    30%  { opacity: 1; transform: translate(-50%,-50%) scale(1.0); }
    70%  { opacity: 1; transform: translate(-50%,-50%) scale(1.0); }
    100% { opacity: 0; transform: translate(-50%,-50%) scale(1.0); }
}


.camera-ctrl-live {
    /* 1fr | auto | 1fr keeps the shutter dead-centre with one, two or three controls present.
       space-between used to shove it against the right edge whenever the flip button was absent. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    width: 100%;
    padding: 0;
}

/* The mode + flip buttons HUG the shutter instead of sitting out at the bar edges:
   justify-self: end|start pulls each one into the column beside the shutter, and the
   margin is the visible gap. The shutter itself stays dead-centre with 1, 2 or 3 controls. */
.camera-ctrl-live > #camModeToggle      { justify-self: end;   margin-right: 24px; }
.camera-ctrl-live > .camera-shutter-btn { justify-self: center; }
.camera-ctrl-live > .camera-flip-btn    { justify-self: start; margin-left: 24px; }

.camera-ctrl-review {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
}

/* Same as every other action row in the app: equal-width buttons that can't wrap
   (a wrapped label made "Save to Gallery" 54px tall next to a 38px Retake). */
.camera-ctrl-review .btn-sm {
    flex: 1;
    white-space: nowrap;
}

/* Classic round shutter button */
.camera-shutter-btn {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    border: 3px solid var(--primary);
    background: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: var(--glow-cyan);
    transition: transform 0.12s;
}

.camera-shutter-btn:active {
    transform: scale(0.91);
}

.shutter-inner {
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: var(--primary);
    opacity: 0.88;
    display: block;
    transition: background 0.12s;
}

/* When video is recording — shutter becomes a square stop button */
.camera-shutter-btn.recording .shutter-inner {
    border-radius: 6px;
    background: var(--danger);
}

/* Status line */
/* Status line collapses to zero height when empty so the viewfinder fills all space */
.camera-status {
    font-size: 12px;
    letter-spacing: 1px;
    text-align: center;
    color: var(--text-secondary);
}

.camera-status:not(:empty) {
    min-height: 28px;
    padding: 4px 16px 10px;
}

.camera-status.error { color: var(--danger-text); }
.camera-status.success { color: var(--success); }

/* ================================================================
   INLINE DIALOGS (JS-built) — themed to match every .modal-content.
   These were raw inline-styled overlays (border-radius:12px, no top
   accent, no glow) that read as "generic" next to the app's modals.
   Now they inherit the same card language: gradient bg, 2px primary
   top border, square corners, cyan glow, blurred backdrop.
   ================================================================ */

/* Full-screen darkened backdrop — same surface as .modal (rgba(2,6,12,.82) + blur). */
.inline-confirm-overlay,
.inline-sheet-overlay {
    position: fixed;
    inset: 0;
    z-index: 9600;
    background-color: rgba(2, 6, 12, 0.82);
    backdrop-filter: blur(6px);
    display: flex;
}

/* Centered confirm card (gallery → delete selection). */
.inline-confirm-overlay {
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.inline-confirm-card {
    background: linear-gradient(135deg, var(--bg-base), var(--accent));
    border: 1px solid var(--border);
    border-top: 2px solid var(--primary);
    border-radius: 0;
    box-shadow: var(--shadow-lg), var(--glow-cyan);
    padding: 24px;
    width: 100%;
    max-width: 320px;
    text-align: center;
    animation: slideUp 0.3s ease;
}

.inline-confirm-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 8px;
}

/* Bottom-sheet card (gallery → bind client/task). Sits at the bottom like the
   full-screen mobile modals do, full width, themed top accent. */
.inline-sheet-overlay {
    align-items: flex-end;
}

.inline-sheet-card {
    width: 100%;
    background: linear-gradient(180deg, var(--bg-base), var(--bg-base));
    border-top: 2px solid var(--primary);
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
    border-radius: 0;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.5), var(--glow-cyan);
    display: flex;
    flex-direction: column;
    animation: slideUp 0.3s ease;
}

.inline-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border);
}

.inline-sheet-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--primary);
}

.inline-sheet-x {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 24px;
    cursor: pointer;
    line-height: 1;
    padding: 2px 6px;
}

.inline-sheet-body {
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.inline-sheet-field label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

.inline-sheet-field select {
    width: 100%;
    background: var(--surface-1);
    border: 1px solid var(--border);
    color: var(--text-primary);
    border-radius: 0;
    padding: 9px 10px;
    font-size: 14px;
    font-family: inherit;
    transition: var(--transition);
}

.inline-sheet-field select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--glow-cyan), inset 0 0 12px rgba(0, 217, 255, 0.08);
    background-color: var(--surface-focus);
}

.inline-sheet-field select option {
    background-color: var(--bg-base);
    color: var(--text-primary);
    padding: 8px;
}

.inline-sheet-actions {
    display: flex;
    gap: 10px;
    padding: 12px 14px; /* standard bottom-bar vertical */
}

.inline-sheet-actions .btn { flex: 1; }

/* ================================================================
   GALLERY MODAL
   ================================================================ */

.gallery-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 700px;
    width: 100%;
    height: 88dvh;
    background: var(--bg-base);
}

/* Explicit flex order so the selection toolbar always sits at the bottom,
   above the nav bar — in BOTH grid view and map view (cluster select). */
/* Select mode owns the bottom row: the main bar is hidden so #gallerySelectToolbar takes its pinned
   slot instead of stacking above it (same "the bar IS the action row" model as the notes/work-log/
   inventory media select bars). Same 12px 16px padding as .gallery-view-nav, so the swap is seamless;
   margin-top:auto keeps the toolbar glued to the bottom once the bar underneath is gone. */
.gallery-modal-content.gallery-select-takeover .gallery-bottom-nav { display: none; }
.gallery-modal-content.gallery-select-takeover .gallery-select-toolbar { margin-top: auto; }

.gallery-modal-content .modal-header        { order: 1; }
.gallery-modal-content #galleryGridView     { order: 2; }
.gallery-modal-content #galleryMapView      { order: 3; }
.gallery-modal-content .gallery-select-toolbar { order: 4; position: relative; }
.gallery-modal-content .gallery-loading     { order: 5; }
.gallery-modal-content .gallery-bottom-nav  { order: 6; }

.gallery-modal-content .modal-header {
    padding: 14px 18px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
}

.gallery-header-actions {
    display: flex;
    gap: 6px;
    margin-left: auto;
}

.gallery-view-btn {
    width: 32px;
    height: 32px;
    border: 1px solid var(--border);
    background: none;
    color: var(--text-secondary);
    border-radius: var(--btn-radius);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color 0.15s, border-color 0.15s;
}

.gallery-view-btn svg {
    width: 16px;
    height: 16px;
}

.gallery-view-btn.active {
    color: var(--primary);
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

/* Grid view */
.gallery-grid-view {
    flex: 1;
    overflow-y: auto;
    padding: 12px;
}

.gallery-grid {
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* Per-day sub-grid of thumbnails */
.gallery-day-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
    margin-bottom: 16px;
}

/* A thumb whose file is missing/undecodable says so instead of rendering an empty tile. */
.gallery-thumb-missing {
    background: repeating-linear-gradient(45deg, rgba(255, 46, 99, 0.10) 0 6px, transparent 6px 12px);
    border-color: var(--danger);
}
.gallery-thumb-missing img,
.gallery-thumb-missing video { opacity: 0.10; }
.gallery-thumb-missing::after {
    content: 'Unavailable';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 4px;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    text-align: center;
    color: var(--danger-text);
}

/* Day section heading */
.gallery-day-heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 14px 2px 8px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
}

.gallery-day-label {
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--primary);
}

.gallery-day-count {
    font-size: 11px;
    color: var(--text-secondary);
    letter-spacing: 0.5px;
}

.gallery-thumb {
    position: relative;
    aspect-ratio: 1;
    overflow: hidden;
    background: var(--accent);
    cursor: pointer;
    border: 1px solid var(--border);
    transition: border-color 0.15s;
    -webkit-touch-callout: none;  /* suppress iOS long-press image sheet on all thumbs */
    user-select: none;
    -webkit-user-select: none;
}

.gallery-thumb:hover {
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

.gallery-thumb img,
.gallery-thumb video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Video badge overlay */
.gallery-thumb-video-badge {
    position: absolute;
    bottom: 5px;
    left: 6px;
    background: rgba(0,0,0,.65);
    color: var(--primary);
    font-size: 9px;
    letter-spacing: 1px;
    padding: 2px 5px;
    font-weight: 700;
}

/* Geo pin badge */
.gallery-thumb-geo {
    position: absolute;
    bottom: 5px;
    right: 5px;
    color: var(--success);
    opacity: 0.9;
}

.gallery-thumb-geo svg {
    width: 13px;
    height: 13px;
    drop-shadow: 0 0 4px var(--success);
}

/* ============================================================
   NOTES — media (photo / video) section. Mirrors the gallery
   grid look; thumbs are previewable + removable before Save.
   ============================================================ */
.notes-media-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}
.notes-media-head > label {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--text-secondary);
    margin: 0;
}
.notes-media-actions { display: flex; gap: 8px; }
/* Standard .btn metrics — these are the same Upload/Take control the inventory item and
   user forms show in .inv-photo-actions, so they must not be a bespoke smaller size
   (they were 6px 12px / 13px / line-height 1 = 29px tall vs the standard 43px). Only the
   icon gap is tightened for the label row. */
.notes-media-btn { gap: 6px; }
.notes-media-btn svg { width: 15px; height: 15px; }
.notes-media-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
    margin-top: 2px;
}
.notes-media-thumb {
    position: relative;
    aspect-ratio: 1;
    overflow: hidden;
    background: var(--accent);
    border: 1px solid var(--border);
    cursor: pointer;
    -webkit-touch-callout: none;
    user-select: none;
    -webkit-user-select: none;
    transition: border-color 0.15s;
}
.notes-media-thumb:hover { border-color: var(--primary); box-shadow: var(--glow-cyan); }
.notes-media-thumb img,
.notes-media-thumb video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
.notes-media-thumb .notes-media-video-badge {
    position: absolute;
    bottom: 5px;
    left: 6px;
    background: rgba(0,0,0,.65);
    color: var(--primary);
    font-size: 9px;
    letter-spacing: 1px;
    padding: 2px 5px;
    font-weight: 700;
    pointer-events: none;
}
/* Selection ring (mirrors .notes-card-check / .gallery-thumb-check) — only while
   picking items to delete; the old always-visible X is gone (tap = view). */
.notes-media-check {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}
.notes-media-check svg { width: 13px; height: 13px; stroke: #fff; opacity: 0; transition: opacity 0.12s; }
.notes-media-thumb.selected { border-color: var(--primary); box-shadow: var(--glow-cyan); }
.notes-media-thumb.selected .notes-media-check { background: var(--primary); border-color: var(--primary); }
.notes-media-thumb.selected .notes-media-check svg { opacity: 1; }
.notes-media-grid.notes-media-selecting .notes-media-thumb:not(.selected) { opacity: 0.55; }

/* A thumb whose file is missing or undecodable says so instead of rendering as an empty tile. */
.notes-media-missing {
    border-color: var(--danger);
    background: repeating-linear-gradient(45deg, rgba(255, 46, 99, 0.10) 0 6px, transparent 6px 12px);
}
.notes-media-missing img,
.notes-media-missing video { opacity: 0.10; }
.notes-media-missing::after {
    content: 'Missing';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 8px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}

/* Delete bar shown while selecting media — pinned to the card's bottom IN PLACE OF
   the Save/Cancel row (same padding/gap/border as .form-actions so it lands exactly
   where the thumb expects the primary actions). */
.notes-media-select-bar {
    flex-shrink: 0;
    align-items: center;
    gap: 8px;
    padding: 12px 0; /* standard bottom-bar vertical */
    border-top: 1px solid var(--border);
}
#notesFormModal .modal-content.notes-media-selecting .form-actions { display: none; }
.notes-media-select-count {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}
.notes-media-select-actions { display: flex; flex: 1; gap: 12px; }
.notes-media-select-actions .btn { flex: 1; }
.notes-media-empty {
    grid-column: 1 / -1;
    padding: 18px 10px;
    text-align: center;
    font-size: 12px;
    color: var(--text-secondary);
    border: 1px dashed var(--border);
    letter-spacing: 0.4px;
}
/* ---- "Saving…" frame, shared by the notes + work-log media grids ----
   A file that is still uploading reserves its slot in the grid immediately, so a slow upload (a video
   over a phone connection) reads as work in progress instead of an unchanged grid. This replaces the
   old `.notes-media-uploading` overlay, which was never rendered by anything.
   NOTE: the pending tile deliberately does NOT carry the grid's own thumb class — those are styled
   with modal-scoped ID selectors (`#workLogsFormModal .wlog-media-thumb`), so a shared class could not
   override their cursor/hover, and keeping it class-free also keeps it out of the tap/long-press
   handlers and the select-mode counts for free. */
.media-thumb-pending {
    position: relative;
    aspect-ratio: 1 / 1;
    border: 1px dashed var(--primary);
    border-radius: var(--btn-radius);
    background: rgba(0, 0, 0, 0.25);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 7px;
    cursor: default;
    user-select: none;
    -webkit-user-select: none;
}
.media-thumb-pending .media-thumb-pending-spinner { width: 15px; height: 15px; }
.media-thumb-pending-label {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--primary);
}
.media-thumb-pending::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: inset 0 0 0 1px var(--primary);
    opacity: 0.5;
    animation: mediaPendingPulse 1.5s ease-in-out infinite;
    pointer-events: none;
}
@keyframes mediaPendingPulse {
    0%, 100% { opacity: 0.15; }
    50%      { opacity: 0.7; }
}
@media (prefers-reduced-motion: reduce) {
    .media-thumb-pending::after { animation: none; opacity: 0.5; }
}

/* Empty state */
.gallery-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 60px 20px;
    color: var(--text-secondary);
    text-align: center;
}

.gallery-empty svg {
    width: 52px;
    height: 52px;
    opacity: 0.35;
}

.gallery-empty p {
    font-size: 14px;
    letter-spacing: 0.5px;
}

.gallery-empty-sub {
    font-size: 12px;
    opacity: 0.6;
}

/* Map view */
.gallery-map-view {
    flex: 1;
    position: relative;
}

.gallery-leaflet-map {
    width: 100%;
    height: 100%;
    min-height: 300px;
}

/* Override Leaflet tiles to look less jarring in dark mode */
/* Street tiles get inverted in dark mode for readability.
   Satellite tiles must NOT be filtered — add .sat-mode class to skip. */
:root.dark-mode .gallery-leaflet-map:not(.sat-mode) .leaflet-tile {
    filter: invert(1) hue-rotate(180deg) brightness(0.82) saturate(0.7);
}

/* Gallery loading */
.gallery-loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    background: rgba(5,5,12,0.75);
    color: var(--text-secondary);
    font-size: 13px;
    letter-spacing: 1px;
    z-index: 10;
}

/* Mobile: full-screen modals for camera + gallery */
@media (max-width: 768px) {
/* height: 100% of the fixed backdrop, NOT 100dvh — dvh tracks the mobile URL bar, so any
   address-bar movement resizes the card under the user (the flashing the crop window had). */
.camera-modal-content,
    .gallery-modal-content {
        max-width: 100%;
        height: 100%;
        border-radius: 0;
    }

    .camera-viewfinder-wrap,
    .camera-preview-wrap {
        aspect-ratio: unset;
        flex: 1;
    }

    .gallery-day-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}


/* ============ Dashboard (DASH) — live overview tab ============ */

.dash-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    margin-bottom: 18px;
}

.dash-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 14px 8px;
    background: linear-gradient(160deg, var(--bg-card), var(--surface-2));
    border: 1px solid var(--border);
    cursor: pointer;
    transition: var(--transition);
    user-select: none;
}

.dash-tile:hover {
    border-color: var(--primary);
    box-shadow: var(--glow-cyan);
}

.dash-tile-num {
    font-size: 26px;
    font-weight: 800;
    line-height: 1.1;
    color: var(--text-primary);
}

.dash-tile-label {
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Tile accent variants */
.dash-tile.tile-accent .dash-tile-num   { color: var(--primary); }
.dash-tile.tile-danger .dash-tile-num   { color: var(--warning-text); }
.dash-tile.tile-critical .dash-tile-num { color: #ff5252; }

/* Live tiles (count > 0) get a subtle breathing border */
.dash-tile.is-live::after {
    content: '';
    position: absolute;
    inset: -1px;
    pointer-events: none;
    animation: dashLivePulse 3s ease-in-out infinite;
}

@keyframes dashLivePulse {
    0%, 100% { box-shadow: 0 0 0 rgba(255, 82, 82, 0); }
    50%      { box-shadow: 0 0 8px rgba(255, 82, 82, 0.35); }
}

.dash-tile.tile-accent.is-live::after   { animation-name: dashLivePulseCyan; }
@keyframes dashLivePulseCyan {
    0%, 100% { box-shadow: 0 0 0 rgba(0, 217, 255, 0); }
    50%      { box-shadow: 0 0 8px rgba(0, 217, 255, 0.35); }
}

.dash-tile.tile-danger.is-live::after   { animation-name: dashLivePulseAmber; }
@keyframes dashLivePulseAmber {
    0%, 100% { box-shadow: 0 0 0 rgba(255, 183, 0, 0); }
    50%      { box-shadow: 0 0 8px rgba(255, 183, 0, 0.35); }
}

/* ---- Dashboard: today's 7 AM - 7 PM temperature graph -----------------------
   A FIXED card: no heading, no fold control, no chip, no scroller. One line spanning the full
   width, with every hour's temperature printed directly above that hour. The curve is an SVG
   that stretches to whatever width it's given (normalized viewBox + preserveAspectRatio="none"),
   and the columns under it are plain equal-width flex cells — a point at x = i + 0.5 is exactly
   the centre of the i-th cell, so the numbers sit under the curve with no measuring. */
/* The card's one label, in the same voice as `.dash-outage-title` (uppercase, letterspaced, cyan) — a plain
   caption, not the old collapsible-section heading: there is still no fold control on this card. */
/* The label row: "LOCAL WEATHER:" then the current conditions — the same glyph the weather page uses, tinted
   cyan like the label, with the temperature beside it in the card's text colour so the number reads first. */
.dash-wx-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
}

.dash-wx-now { display: inline-flex; align-items: center; gap: 4px; color: var(--primary); }
.dash-wx-now .wx-glyph { display: block; }
.dash-wx-now-temp {
    font-size: 13px;
    font-weight: 700;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}

.dash-wx-title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--primary);
}

.dash-wx-card {
    padding: 12px 14px 8px;
    margin-bottom: 18px;
    /* surface: the ONE card rule (four-sided — this card carries no left accent) */
    /* ...but the frame itself carries the day's HEAT: `--wx-heat` is set on this card by _dashWxHeatPaint()
       from the theme's cyan up to 76 °F toward the theme's red at 100 °F (see _wxHeatColor — the hue walks
       DOWN from the cyan, so the ramp passes green/yellow/orange and never magenta). Falls back to the
       standard card border until there is a temperature. */
    border-color: var(--wx-heat, var(--card-border));
}

/* `hidden` spelled out: a class rule that sets `display` would out-specify the UA's
   `[hidden] { display: none }` and the empty card would render as a gap. */
.dash-wx-card[hidden] { display: none; }

/* The card is a tap target → the weather page (20260912wxtap1). Bound in _wireDashWeatherCard(). */
.dash-wx-card { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.dash-wx-card:active { filter: brightness(1.12); }
.dash-wx-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ---------- DAY VIEW: one day's hours (20260912wxture1) ----------
   A sibling of #wxForecast. BOTH carry .weather-forecast, which sets display:flex — that out-specifies
   the UA's [hidden] { display: none }, so without the re-assert below a "hidden" pane would stay on
   screen (the same trap the dashboard card hit). */
#wxForecast[hidden], #wxDayView[hidden] { display: none; }

.wx-day-head {
    display: flex; align-items: center; gap: 24px;   /* day ↔ H/L spacing; the button is held right by its own auto margin */
    padding-bottom: 12px; border-bottom: 1px solid var(--border);
}
/* The back control is a real filled primary button — the app's own .btn/.btn-primary (cyan gradient,
   dark label, themed radius), so it matches every other filled button in the window. Only the icon
   size and the padding are tightened, to keep the bar on one line at 390px. */
.wx-day-back { flex: 0 0 auto; margin-left: auto; padding: 10px 14px; font-size: 12px; letter-spacing: 1px; }
.wx-day-back svg { width: 15px; height: 15px; flex-shrink: 0; }
/* flex: 0 1 auto (not 1) — the title keeps its natural width so the H/L pair can sit right beside it;
   the slack is pushed past the H/L by .wx-day-back's margin-left: auto, which is what keeps the button
   pinned to the right end. */
.wx-day-title { flex: 0 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.2; font-size: 15px; color: var(--text-primary); }
.wx-day-title i { font-style: normal; font-size: 10.5px; color: var(--text-secondary); opacity: 0.8; }
/* H above L, and sized to MIRROR the day stack beside it: the high matches the weekday (15px) and the
   low matches the date (10.5px), so the bar reads as two matched two-line blocks (20260912wxhl3). */
.wx-day-hl { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
.wx-day-hl .wx-hl-hi { font-size: 15px; line-height: 1.2; }
.wx-day-hl .wx-hl-lo { font-size: 10.5px; line-height: 1.2; }
/* The H/L pair in the day header reuses the hero's .wx-hl-hi / .wx-hl-lo — one definition. */

.wx-hours { display: flex; flex-direction: column; }
.wx-hour {
    display: grid;
    grid-template-columns: 44px 22px minmax(0, 1fr) 36px 30px;
    align-items: center; gap: 8px;
    padding: 5px 0; border-top: 1px solid var(--border);
}
.wx-hour:first-child { border-top: none; }
.wx-hour-t { font-size: 11px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.wx-hour-glyph { display: flex; color: var(--text-secondary); }
.wx-hour-cond { font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wx-hour-temp { font-size: 13px; font-weight: 600; color: var(--text-primary); text-align: right; font-variant-numeric: tabular-nums; }
.wx-hour-pop { font-size: 10px; text-align: right; color: var(--primary); opacity: 0.85; font-variant-numeric: tabular-nums; }
.wx-hour.is-now .wx-hour-t, .wx-hour.is-now .wx-hour-temp { color: var(--primary); }

@media (max-width: 480px) {
    .wx-hour { grid-template-columns: 38px 20px minmax(0, 1fr) 32px 28px; gap: 6px; }
}

.dash-wx-graph {
    position: relative;      /* the "now" dot is positioned in % of this box */
    height: 104px;
}

.dash-wx-svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* A flat wash under the curve — reads as a temperature band, not a filled card. */
.dash-wx-area { fill: var(--primary); fill-opacity: 0.10; }

/* A dashed guide up from every hour column, so the numbers below can be traced to their point.
   Deliberately faint: it's a reading aid, not a grid the eye lands on. */
.dash-wx-grid {
    stroke: var(--text-secondary);
    stroke-width: 1;
    stroke-dasharray: 3 5;
    opacity: 0.28;
    vector-effect: non-scaling-stroke;
}

.dash-wx-line,
.dash-wx-line-past {
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;   /* the non-uniform scale must not fatten the stroke */
}

.dash-wx-line      { stroke: var(--primary); }
.dash-wx-line-past { stroke: var(--text-secondary); opacity: 0.45; }   /* hours already gone */

/* The "now" marker: an HTML dot so it stays a circle under the stretched SVG. */
.dash-wx-dot {
    position: absolute;
    width: 9px;
    height: 9px;
    margin: -4.5px 0 0 -4.5px;
    border-radius: 50%;
    background: var(--primary);
    box-shadow: var(--glow-cyan);
}

/* One column per hour: its temperature sits directly above its label. */
.dash-wx-axis { display: flex; }

.dash-wx-axis-cell {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
}

.dash-wx-cell-temp {
    font-size: 11px;
    font-weight: 700;
    line-height: 1.15;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}

.dash-wx-cell-hour {
    font-size: 9.5px;
    line-height: 1.15;
    letter-spacing: 0;
    text-transform: uppercase;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* The current hour is the one thing called out: its temp and label go cyan. */
.dash-wx-axis-cell.is-now .dash-wx-cell-temp,
.dash-wx-axis-cell.is-now .dash-wx-cell-hour { color: var(--primary); }
.dash-wx-axis-cell.is-now .dash-wx-cell-hour { font-weight: 700; }

/* Narrow screens give each column ~27px. Keep every hour — that's the point of the card —
   and let the type shrink instead; hiding labels would leave temps floating on their own. */
@media (max-width: 480px) {
    .dash-wx-graph { height: 96px; }
    .dash-wx-cell-temp { font-size: 10px; }
    .dash-wx-cell-hour { font-size: 8.5px; }
}


/* Mobile: stack sections, shrink tiles */
@media (max-width: 768px) {
    .dash-stats { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 14px; }
    .dash-tile-num { font-size: 20px; }
}

/* ============ Map window (TOOLS → Map) — client location map ============ */

.map-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 700px;
    width: 100%;
    height: 88dvh;
    background: var(--bg-base);
}

.map-view-wrap {
    flex: 1;
    position: relative;   /* anchors the loading overlay + empty state to the map area */
    min-height: 300px;
}

/* Base-style toggle (street / satellite) floated ON a map — ONE control for every map in the app
   (client location picker, TOOLS → Map, Gallery map view). It floats rather than living in a pinned
   action bar so those bars keep their own jobs (readout + Use, Close, view switch).
   Anchored top-right — Leaflet's zoom control owns top-left — and stacked above Leaflet's panes (~700)
   and the loading overlays (800) so it stays tappable mid-load.
   Colour note: the pill's own background is always dark (it sits over arbitrary imagery), so the
   inactive text is a FIXED light value — theme tokens would make it unreadable on the light theme. */
.map-base-toggle {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 810;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 11px;
    border-radius: 20px;
    background: rgba(5, 5, 12, 0.82);
    border: 1px solid var(--border);
    color: #ece9fb;
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.3px;
    backdrop-filter: blur(6px);
    cursor: pointer;
}

.map-base-toggle .icon-btn {
    width: 15px;
    height: 15px;
}

.map-base-toggle.active {
    background: linear-gradient(135deg, var(--primary), var(--accent2));
    color: #021016;
    border: 1px solid var(--primary);
    box-shadow: var(--glow-cyan);
}

/* Pinned action bar: base-style toggle (left) + Close (right). The map card has no padding
   of its own, so the bar carries the standard 12px 16px (12 + 38 + 12 + 1 = 63px, the same
   geometry as every other bar) and a solid background so map tiles can't show through. */
/* The map fills the window and this bar sits IN FRONT of its bottom edge (requested: "extend the bottom
   edge of the map down to the bottom of the screen and let the pinned button bar at the bottom sit in
   front of the map"). Absolutely positioned so it costs the map no height; above Leaflet's panes (~700)
   and the loading/empty overlays (800). Translucent + blurred, the treatment the weather radar's
   timeline strip uses, so the map still reads through it. */
#mapModal .form-actions.map-actions {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 950;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.4);   /* the surface itself is the shared bottom-bar rule */
}

/* The bar owns the bottom edge, so Leaflet's attribution rides just above it — the OSM credit stays
   visible (unlike the weather map's timeline, which hides it). 63px = 12 + 38 + 12 + 1. */
#mapModal .leaflet-bottom {
    bottom: 63px;
}

/* SEARCH: the app's standard search row (same classes and metrics as Clients / Tasks / Notes), inset by
   the same 16px edge margin those pages' rows get from their container — full-bleed it read as a
   different, thinner control than the rest of the site's search bars. */
.map-search-wrap {
    margin: 0 16px;
    background: transparent;   /* the card behind it is --bg-base already: only the icon, the input and the
                                  row's own bottom rule show, exactly like the page rows */
}

.map-search-results {
    max-height: 34vh;
}

#mapModal .form-actions.map-actions .btn { flex: 1; white-space: nowrap; }

/* ============ ONE translucent bottom-bar surface, app-wide ============
   Requested: "i like what you did with the opacity setting of the bottom bar on this page. can you apply
   that same effect to ALL bottom button bars across the entire site?" The Map window's action bar is the
   reference: 78% background + a 4px blur, so whatever sits behind a bar reads through it. The colour is
   mixed from --bg-base, so the light theme frosts pale instead of going dark (a hardcoded dark rgba only
   works over a map).
   On a bar with nothing behind it — the last row of a flex column — this is deliberately invisible: it
   shows on the bars that actually overlay content (the sticky/fixed action bars, the lightbox bar, the
   gallery edit panel, the phone tab nav). Each bar keeps its OWN border and shadow; only the surface is
   shared, so one source of truth cannot drift from the others. */
.form-actions,
.notes-bottom-nav,
.notes-media-select-bar,
.wlog-media-select-bar,
.gallery-bottom-nav,
.gallery-select-toolbar,
.gallery-edit-panel,
.lightbox-action-bar,
.lb-edit-actions,
.client-map-footer,
#tasksTab .section-header.tasks-add-bar,
#clientsTab .section-header.clients-add-bar,
.inv-cam-controls {
    background: color-mix(in srgb, var(--bg-base) 78%, transparent);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

/* The toggle reads as "on" while satellite is showing (same treatment as .btn.active-view). */
/* Loading overlay and empty state must sit ABOVE Leaflet's panes (z-index up to ~700) */
.map-loading, .map-empty { z-index: 800; }

.map-empty {
    position: absolute;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    background: var(--bg-base);
}

.map-empty p {
    font-size: 13px;
    opacity: 0.8;
    max-width: 260px;
    margin: 0 auto;
}

/* Mobile: fill the entire screen (same as gallery) */
@media (max-width: 768px) {
    .map-modal-content {
        max-width: 100%;
        height: 100dvh;
        border-radius: 0;
    }
}

/* ============ Weather window (TOOLS → Weather) ============ */
.weather-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 700px;
    width: 100%;
    height: 88dvh;   /* same as the gallery/map/inventory windows — one standard for tool windows */
    background: var(--bg-base);
}

/* Location readout — the window's top row now that the header is gone. The GPS re-center
   button sits at its right end; 6px + 32px + 6px keeps the row as tight as it was. */
#wxLocationBar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 16px;
    border-bottom: 1px solid var(--border);
    background: linear-gradient(90deg, rgba(0, 217, 255, 0.07), transparent 55%);
    font-size: 13px;      /* four levels on one line, ellipsized if it ever needs to be */
    letter-spacing: 0.3px;
    color: var(--text-primary);
    flex-shrink: 0;
}

.wx-gps-btn {
    margin-left: auto;
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: var(--primary);
    cursor: pointer;
}

.wx-gps-btn:hover, .wx-gps-btn:active {
    border-color: var(--border);
    background: rgba(0, 217, 255, 0.08);
}

.wx-gps-btn svg { width: 17px; height: 17px; }

#wxLocationBar svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    color: var(--primary);
}

/* Wraps rather than ellipsizes: all four levels have to be readable on a 390px phone, and a
   flat ellipsis hid the country entirely. Clamped to 2 lines as a guard for pathological names. */
.wx-place-primary { font-size: 14px; color: var(--text-primary); }
.wx-place-tail { font-size: 12px; color: var(--text-secondary); }

#wxLocationName {
    flex: 1;
    min-width: 0;
    white-space: normal;
    line-height: 1.3;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.weather-map-wrap { min-height: 200px; max-height: 46%; }   /* map takes the top, forecast scrolls below */

/* No-geolocation hint — FLOATING (does not cover the map: pan/locate still work); auto-fades after 10s.
   Id selector beats .map-empty's inset:0 full-cover defaults. */
#wxNoGeolocation {
    position: absolute;
    top: 10px; left: 50%; transform: translateX(-50%);
    right: auto; bottom: auto;
    flex-direction: row; align-items: center; gap: 8px;
    width: calc(100% - 24px); max-width: 320px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
    padding: 9px 12px;
    z-index: 900;   /* above Leaflet panes (~700) */
    text-align: left;
}

#wxNoGeolocation .empty-state-icon { width: 18px; height: 18px; margin: 0; flex-shrink: 0; color: var(--primary); }
#wxNoGeolocation .empty-state-icon svg { width: 16px; height: 16px; }
#wxNoGeolocation .empty-state-text { font-size: 12.5px; line-height: 1.35; margin: 0; flex: 1; }
#wxNoGeolocation p { display: none; }   /* long sub-copy only for the full-cover fallback */

/* Dark-theme Leaflet chrome — attribution bar (zoom controls removed by design) */
#weatherLeafletMap .leaflet-control-attribution,
.client-map-modal-content .gallery-leaflet-map .leaflet-control-attribution {
    background: rgba(10, 10, 20, 0.72);
    color: var(--text-secondary);
}

#weatherLeafletMap .leaflet-control-attribution a { color: var(--primary); }

/* When the playback timeline is showing it owns the full bottom edge of the map
   section, so the Leaflet attribution (bottom-right) is hidden to avoid it
   doubling up. (If :has() is unsupported, the bar's higher z-index covers it anyway.) */
.weather-map-wrap:has(#wxRadarTimeline:not([hidden])) #weatherLeafletMap .leaflet-control-attribution { display: none; }

/* Non-interactive radar playback timeline — a full-width strip PINNED to the
   bottom edge of the map section (above Leaflet panes ~700). Shows the current
   scan time + a progress track (marker now→55m). Decorative: pointer-events:none,
   so panning/clicking the map through it still works. Hidden until the radar is
   active. Owning the bottom edge, the Leaflet attribution is hidden on this map
   (see below). */
.wx-radar-timeline {
    position: absolute;
    left: 0; right: 0; bottom: 0;   /* pinned to the bottom edge of the map section */
    z-index: 950;
    display: flex; align-items: center; gap: 10px;
    pointer-events: none;      /* non-interactive — pass map interaction through */
    width: 100%;
    padding: 6px 14px;
    background: rgba(10, 10, 20, 0.78);
    border-top: 1px solid var(--border);   /* only a top edge — the bar IS the bottom edge */
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    font-size: 10.5px;
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}
.wx-radar-timeline[hidden] { display: none; }
.wx-radar-tl-time {
    color: var(--primary);
    font-weight: 600;
    letter-spacing: 0.3px;
    white-space: nowrap;
}
.wx-radar-tl-now { white-space: nowrap; opacity: 0.75; font-size: 10px; }
.wx-radar-tl-track {
    position: relative;
    flex: 1;               /* stretch the track across the full bar width */
    min-width: 40px;
    height: 4px;
    background: rgba(255, 255, 255, 0.14);
    overflow: visible;
}
.wx-radar-tl-fill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 0%;
    background: var(--primary);
    opacity: 0.55;
    transition: width 0.4s linear;
}
.wx-radar-tl-marker {
    position: absolute;
    top: 50%; left: 0%;
    width: 8px; height: 8px;
    transform: translate(-50%, -50%);
    background: var(--primary);
    box-shadow: var(--glow-cyan);
    border-radius: 50%;
    transition: left 0.4s linear;
}

/* Location dot (user's geolocation fix, or the 339 Maple St home fallback).
   Same cyan pin language as the client map's picker pin, but non-interactive:
   it just marks where "you" are on the weather map. Sits above the satellite
   base and below the radar overlay so rain reads over it. */
.wx-loc-dot-wrap { background: transparent; border: none; }
.wx-loc-dot {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #00d9ff;
    border: 3px solid var(--bg-base);
    box-shadow: 0 0 0 2px #00d9ff, 0 0 10px rgba(0, 217, 255, 0.8), 0 2px 6px rgba(0, 0, 0, 0.5);
}

/* Radar/locate header buttons match the client map's street/sat toggle */
#weatherModal .gallery-view-btn svg { width: 16px; height: 16px; }

/* Pinned Cancel bar — solid background so scrolling forecast cards never show through (same as task/workLogs/notes) */
#weatherModal .form-actions{
    flex-shrink: 0;
    /* surface: the shared bottom-bar rule */
}

/* "Updating…" hint — shown while the map has moved to a NEW area but the 15s
   refresh throttle hasn't elapsed yet, so the forecast cards below still show
   the PREVIOUS location. A quiet chip (not a spinner): it says "stale, coming"
   rather than "fetching" (no request is in flight while throttled). Pulsing
   cyan dot + small text sits in the gap above the cards. pointer-events:none
   so it never blocks scrolling the forecast. */
.wx-stale-hint {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 16px;
    background: rgba(0, 217, 255, 0.07);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    font-size: 11.5px;
    letter-spacing: 0.3px;
    pointer-events: none;
}
.wx-stale-hint[hidden] { display: none; }
.wx-stale-dot {
    width: 7px; height: 7px;
    border-radius: 50%;
    background: #00d9ff;
    box-shadow: 0 0 6px rgba(0, 217, 255, 0.8);
    animation: wxStalePulse 1.6s ease-in-out infinite;
}
.wx-stale-text { font-variant-numeric: tabular-nums; }
@keyframes wxStalePulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.35; transform: scale(0.7); }
}

/* Forecast area — today card + 6-day grid scroll together above the pinned Cancel */
.weather-forecast {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 10px 16px 8px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* ---------- right now ----------
   PINNED: the slot sits above .weather-forecast (the only scroller), so the hero keeps still while
   the days scroll under it. [hidden] is re-asserted because a flex child would otherwise keep its
   box (same trap as the forecast panes). Compacted (20260912wxhero1): measured 129px → 110px
   (glyph 46→40, temp 40→32px, paddings and chip gap tightened; nothing was removed). */
.wx-now-slot, .wx-dayhead-slot { flex: 0 0 auto; padding: 12px 16px 0; }
.wx-now-slot[hidden], .wx-dayhead-slot[hidden] { display: none; }

.wx-now {
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 10px 14px;
    background: linear-gradient(135deg, var(--bg-card) 0%, var(--surface-2) 100%);
}

.wx-now-top { display: flex; align-items: center; gap: 12px; }

.wx-now-glyph { display: flex; flex-shrink: 0; color: var(--primary); }

.wx-now-read { flex: 1; min-width: 0; }

.wx-now-temp {
    font-size: 32px;
    font-weight: 700;
    line-height: 1;
    color: var(--primary);
    font-variant-numeric: tabular-nums;
    text-shadow: 0 0 18px color-mix(in srgb, var(--primary) 45%, transparent);   /* token-driven: adapts to the light theme */
}

.wx-deg { font-size: 21px; opacity: 0.65; }

.wx-now-desc {
    font-size: 11.5px;
    color: var(--text-secondary);
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wx-now-hl { display: flex; align-items: baseline; gap: 9px; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.wx-hl-hi, .wx-hl-lo { display: flex; align-items: baseline; gap: 2px; }
.wx-now-hl i { font-style: normal; font-size: 9px; letter-spacing: 0.5px; opacity: 0.55; text-transform: uppercase; }
.wx-hl-hi { font-size: 17px; font-weight: 600; color: var(--text-primary); }
.wx-hl-lo { font-size: 14px; color: var(--text-secondary); }

.wx-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }

.wx-chip {
    display: flex;
    align-items: baseline;
    gap: 5px;
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 3px 10px;
}

.wx-chip-v { font-size: 12px; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.wx-chip-v i { font-style: normal; font-size: 10px; opacity: 0.6; margin-left: 1px; }
.wx-chip-k { font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.7px; color: var(--text-secondary); opacity: 0.8; }

/* ---------- the week: every bar shares one temperature scale ---------- */
.wx-days { display: flex; flex-direction: column; }

.wx-row {
    display: grid;
    grid-template-columns: 44px 22px 32px minmax(0, 1fr) 32px 28px;
    align-items: center;
    gap: 8px;
    /* 11px vertical padding makes the whole row ~48px tall — a real tap target. The row IS the
       button (role=button, data-wx-day), so this is also the hit area, not just cosmetics. */
    padding: 11px 0;
    min-height: 44px;
    border-top: 1px solid var(--border);
}
.wx-row:first-child { border-top: none; }

.wx-row-day { display: flex; flex-direction: column; line-height: 1.2; font-size: 13px; color: var(--text-primary); }
.wx-row-day i { font-style: normal; font-size: 10.5px; color: var(--text-secondary); opacity: 0.7; }
.wx-row.is-today .wx-row-day { color: var(--primary); font-weight: 600; }

.wx-row-glyph { display: flex; color: var(--text-secondary); }
.wx-row.is-today .wx-row-glyph { color: var(--primary); }

.wx-row-lo, .wx-row-hi { font-size: 13px; font-variant-numeric: tabular-nums; }
.wx-row-lo { color: var(--text-secondary); text-align: right; }
.wx-row-hi { color: var(--text-primary); font-weight: 600; }

.wx-row-track { position: relative; height: 5px; border-radius: 3px; background: rgba(127, 127, 127, 0.18); }
.wx-row-bar { position: absolute; top: 0; height: 5px; border-radius: 3px; background: linear-gradient(90deg, var(--primary), var(--accent2)); }
.wx-row.is-today .wx-row-bar { background: linear-gradient(90deg, var(--accent2), var(--primary)); }

.wx-row-pop { font-size: 10px; text-align: right; color: var(--primary); opacity: 0.85; font-variant-numeric: tabular-nums; }


/* Narrow phones: drop the date line and the precip column, keep the bars readable */
@media (max-width: 400px) {
    .wx-row { grid-template-columns: 40px 20px 30px minmax(0, 1fr) 30px; gap: 6px; }
    .wx-row-pop { display: none; }
}

.weather-error {
    padding: 10px 16px 0;
    font-size: 13px;
    color: #ff7b7b;
}

.weather-empty-note { font-size: 13px; color: var(--text-secondary); padding: 8px 2px; }


/* Mobile: fill the entire screen (same as the client map window) */
@media (max-width: 768px) {
    .weather-modal-content {
        max-width: 100%;
        height: 100dvh;
        border-radius: 0;
    }
    /* The week is the point of this window on a phone: give it the room the 7-day list needs
       instead of letting the panel scroll one row short. */
    /* 33% (not 28%): the header is gone, so the map takes the freed space and the forecast
       still fits its 7 rows exactly — measured, and guarded by the verifier. */
    .weather-map-wrap { max-height: 32%; }
    /* .gallery-leaflet-map carries min-height:300px (it is shared with the gallery map). Left
       alone it wins over the 28% cap, pushes the map ~65px past its box, and drags the radar
       timeline down over the forecast hero. Scoped to this window only. */
    .weather-map-wrap .gallery-leaflet-map { min-height: 0; }
}

/* Leaflet popups — theme them (applies to both gallery + client maps) */
/* The client info tag on the maps. Scoped through .leaflet-container so it (3 classes) outranks
   Leaflet's own `.leaflet-popup-content-wrapper` (1 class) — Leaflet's stylesheet is loaded AFTER
   this file, so at equal specificity it won the tie and painted a stock WHITE box over the app's
   dark theme. */
.leaflet-container .leaflet-popup .leaflet-popup-content-wrapper {
    background: var(--bg-card);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
}
.leaflet-container .leaflet-popup .leaflet-popup-content { margin: 12px 14px; }
.leaflet-container .leaflet-popup .leaflet-popup-tip { background: var(--bg-card); border: 1px solid var(--border); }
.leaflet-container .leaflet-popup .leaflet-popup-close-button {
    color: var(--text-primary); opacity: 0.75; font-size: 18px; padding: 6px 8px 0 0;
}
.leaflet-container .leaflet-popup .leaflet-popup-close-button:hover { opacity: 1; color: var(--primary); }
.wx-client-popup { cursor: pointer; }

/* Contents of the tag: the customer on top, the address under it, then an explicit way in. */
.wx-pop { min-width: 172px; }
.wx-pop-name { font-size: 14px; font-weight: 700; letter-spacing: 0.3px; color: var(--primary); margin-bottom: 4px; }
.wx-pop-line { font-size: 12.5px; line-height: 1.35; color: var(--text-secondary); }
.wx-pop-go {
    display: flex; align-items: center; gap: 5px;
    margin-top: 10px; padding-top: 9px;
    border-top: 1px solid var(--border);
    font-size: 12px; font-weight: 700; letter-spacing: 0.4px;
    color: var(--primary);
}

/* "You are here" pulse on the client map */
@keyframes map-you-pulse {
    0%   { stroke-opacity: 0.8; r: 10; }
    70%  { stroke-opacity: 0; r: 18; }
    100% { stroke-opacity: 0; r: 18; }
}
.map-you-here circle {
    animation: map-you-pulse 2s ease-out infinite;
}

/* ============ Inventory (TOOLS → Inventory) — items w/ make/model + photo ============ */

.inventory-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 560px;
    width: 100%;
    height: 88dvh;   /* standard tool-window height (was 84dvh) */
    background: var(--bg-base);
}

.inventory-modal-content .modal-header { flex-shrink: 0; }

/* Bottom bar — two matched action buttons (CATEGORIES + ADD ITEM). Standard action-bar
   metrics (12px 16px / gap 8px), matching the notes list bar and the gallery select toolbar. */
.inventory-modal-content .gallery-bottom-nav { flex-shrink: 0; gap: 8px; padding: 12px 16px; }
.inv-cats-btn, .inventory-add-btn, .inv-close-btn { flex: 1; width: auto; margin: 0; justify-content: center; }
/* Three equal 113px actions — labels must never wrap, or the bar grows past the 63px standard. */
.inventory-modal-content .gallery-bottom-nav .btn { white-space: nowrap; }

/* ITEM LIST — the scrollable region */
.inventory-list-wrap { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.inventory-empty { padding: 48px 24px; text-align: center; color: var(--text-secondary); }

.inv-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    cursor: pointer;
    transition: background .15s, filter .15s;
}
.inv-item:hover { filter: brightness(1.12); }   /* a --bg-alt wash would now read DARKER than the card */
.inv-item:active { filter: brightness(1.06); }
/* :last-child keeps its bottom border — every inventory row is a card now (see the card rule) */

/* Photo thumbnail (or placeholder when no photo) */
.inv-item-photo {
    width: 52px; height: 52px;
    object-fit: cover;
    flex-shrink: 0;
    border: 1px solid var(--border);
    background: var(--bg-alt);
}
.inv-item-photo-empty {
    display: flex; align-items: center; justify-content: center;
    color: var(--text-secondary); opacity: .55;
}
.inv-thumb-ic { width: 24px; height: 24px; }

.inv-item-body { flex: 1; min-width: 0; }
.inv-item-name {
    font-weight: 600; color: var(--text-primary);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inv-item-sub {
    font-size: .82rem; color: var(--text-secondary);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inv-item-model { opacity: .85; }

/* ---- Item details page (tap an item to open it) ---- */
.inventory-detail-modal-content { padding: 0; max-width: 480px; display: flex; flex-direction: column; }
.inventory-detail-modal-content .modal-header { flex-shrink: 0; }

.inv-detail-photo {
    position: relative;
    /* A SQUARE frame (was 16/9): the item photo is the hero of this page, and every other photo well in the
       app (avatars, the item form's crop target) is square too. `min(100%, 62vh)` keeps it square while
       stopping it eating a short viewport — on a phone or a normal desktop card the 100% wins, so it is
       simply the full card width; on a squat window the square shrinks and centres instead of squashing.
       The img keeps `object-fit: cover`, so a landscape photo crops to the square rather than distorting. */
    width: min(100%, 62vh);
    aspect-ratio: 1 / 1;
    margin: 0 auto;
    background: var(--bg-alt);
    border-bottom: 1px solid var(--border);
    overflow: hidden;
    flex-shrink: 0;
}
#invDetailPhoto { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Tapping an existing photo expands it; the "No photo" placeholder is not a target, so the cursor and the
   press feedback only appear once there is something to open. */
.inv-detail-photo.is-expandable { cursor: zoom-in; }
.inv-detail-photo.is-expandable:active #invDetailPhoto { opacity: .88; }
.inv-detail-photo-empty {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
    color: var(--text-secondary); opacity: .7; font-size: .9rem;
}
.inv-detail-photo-empty svg { width: 40px; height: 40px; }

.inventory-detail-modal-content .task-detail-body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.inventory-detail-modal-content .form-actions.task-detail-actions { flex-shrink: 0; margin-top: auto; }

/* ---- Item form (add / edit) ---- */
.inventory-item-modal-content { padding: 0; overflow: hidden; max-width: 480px; display: flex; flex-direction: column; }

/* Photo zone — preview fills the top of the card */
.inv-photo-zone {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: var(--bg-alt);
    border-bottom: 1px solid var(--border);
    overflow: hidden;
}
#invPhotoPreview { width: 100%; height: 100%; object-fit: cover; display: block; }
#userAvatarPreview { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The avatar area: a compact square frame top-left, its actions stacked to the right. The frame is a
   viewer (tap → full screen), not a banner, so it no longer spans the card the way the inventory
   item's 16/9 photo does — everything here is scoped to #userModal. */
#userModal .modal-header { padding: 18px 20px; }

#userModal .user-photo-row {
    /* Two equal halves: the frame takes 50% of the width, the buttons the other 50%.
       The frame's WIDTH is therefore a percentage — a definite number — and its square ratio derives
       the HEIGHT from it, so nothing here depends on a measurement of its own sibling. (Driving it the
       other way round is a feedback loop: the frame's ratio fed the row height back into the button
       column, and every attempt settled on a nonsense size — 302px, 210x231, 344px.)
       MUST NOT GROW: .modal-content is a column flex, so without `flex: 0 0 auto` this row stretches to
       fill the card. */
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 14px;
    align-items: stretch;
    padding: 16px 20px 18px;
    flex: 0 0 auto;
}
/* The prompt box: NO label (by request), and pulled up against the photo it applies to instead of sitting
   a field-gap below it. -24px is exactly the form's own top padding — the furthest it can move before the
   form (a scroll container) would clip it — so the box now starts one photo-row padding under the photo
   rather than a full field gap under it. */
#userModal .user-prompt-group { margin-top: -24px; }
#userModal .user-prompt-group .form-note { margin-top: -2px; }
/* ...and while that box is on screen the photo row gives back most of its bottom padding, so the photo and
   the box read as one block. Scoped to the flag the form sets, so a card WITHOUT the box is untouched. */
#userModal.prompt-open .user-photo-row { padding-bottom: 8px; }
#userAvatarZone {
    /* A long press here opens the gallery in select mode, so the OS sheet (iOS "Save Image" / Android
       "Open image in new tab") must never appear - the same suppression the media thumbs carry. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-user-drag: none;
    /* Half the row, square: the height comes from the width via the ratio. */
    width: 100%;
    aspect-ratio: 1 / 1;
    min-width: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    cursor: zoom-in;
    background: var(--bg-alt);
}
#userAvatarZone.no-photo { cursor: default; }
/* ================= CYBERPUNK FRAME — the member photo =================
   A cyan neon edge, four hard corner brackets, and a soft outer bloom. Drawn from the theme's own cyan
   (--primary / --border-glow), not a hard-coded colour: vivid #00d9ff in dark, the deeper cyan in the
   light theme, so it stays in the palette either way. The brackets are four L-shapes made of background
   gradients on the ::before, so a zone that is ALSO a button needs no extra markup inside it. They live
   INSIDE the edge (inset 5px) because .inv-photo-zone clips its own overflow — brackets hung outside
   would simply disappear.
   Deliberately still: the bloom opens on hover/focus, and nothing animates on its own, so the card keeps
   its calm (motion here is reserved for state, e.g. the critical pulse). */
#userModal #userAvatarZone {
    border: 2px solid var(--primary);
    border-radius: 3px;
    box-shadow: var(--glow-cyan), inset 0 0 16px rgba(0, 217, 255, 0.14);
    transition: box-shadow 0.2s ease;
}
#userModal #userAvatarZone::before {
    content: '';
    position: absolute;
    inset: 4px;
    pointer-events: none;
    background:
        linear-gradient(var(--border-glow), var(--border-glow)) 0    0    / 22px 3px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 0    0    / 3px 22px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 100% 0    / 22px 3px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 100% 0    / 3px 22px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 0    100% / 22px 3px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 0    100% / 3px 22px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 100% 100% / 22px 3px no-repeat,
        linear-gradient(var(--border-glow), var(--border-glow)) 100% 100% / 3px 22px no-repeat;
    filter: drop-shadow(0 0 5px var(--border-glow));
    opacity: 0.85;
}
#userModal #userAvatarZone:hover,
#userModal #userAvatarZone:focus-visible {
    box-shadow: 0 0 10px rgba(0, 217, 255, 0.7), 0 0 30px rgba(0, 217, 255, 0.35),
                inset 0 0 22px rgba(0, 217, 255, 0.2);
}
#userModal #userAvatarZone:hover::before,
#userModal #userAvatarZone:focus-visible::before { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    #userModal #userAvatarZone { transition: none; }
}
#userModal .user-photo-actions {
    display: flex;
    flex-direction: column;
    /* SPREAD: the first button's top sits on the frame's top edge and the last button's bottom on its
       bottom edge. The column is stretched to the row, whose height is the frame's — so the spacing
       between the buttons is whatever is left over. */
    justify-content: space-between;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
}
/* The shared button is `flex: 1` for the inventory's horizontal row. In a COLUMN that makes each
   button grow to fill the row's height — and the frame's height is derived from that very column,
   so the two feed each other (measured: a 344px avatar). Natural height breaks the loop. */
#userModal .user-photo-actions .inv-photo-btn { flex: 0 0 auto; width: 100%; }
/* 'Use as Avatar' is the primary action of that viewer, so it reads as one. */
.lightbox-action-primary {
    background: var(--primary);
    color: #04121a;
    border-color: var(--primary);
    font-weight: 600;
}

/* The gallery's actions stay PINNED to the bottom of the card while the images scroll behind them. */
#avatarGalleryModal .modal-content { display: flex; flex-direction: column; max-height: 88vh; }
#avatarGalleryModal .modal-header { flex: 0 0 auto; }
#avatarGalleryModal .modal-form { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; }
#avatarGalleryModal .form-actions { flex: 0 0 auto; }
@media (max-width: 600px) {
    #avatarGalleryModal .modal-content { max-height: none; height: 100%; }
}

/* ---- Themed date picker (Collective: hire date) ----
   The OS draws the native date picker, so it can never match the theme. The native input is hidden but
   kept as the source of truth (.value = ISO date, which is what the save path reads), and this pair of
   button + panel is what the user actually sees and taps — the same shape as the app's select-wrap. */
#userModal .date-wrap { position: relative; }
#userModal .date-native { display: none !important; }
#userModal .date-btn {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 14px 16px;
    font-size: 14px;
    font-family: inherit;
    text-align: left;
    color: var(--text-primary);
    background: rgba(100, 108, 125, 0.06);
    border: 1px solid var(--border);
    border-radius: 0;
    cursor: pointer;
}
#userModal .date-btn.is-open { border-color: var(--primary); }
#userModal .date-btn .icon-btn { width: 18px; height: 18px; color: var(--primary); flex: 0 0 auto; }
#userModal .date-value.is-empty { color: var(--text-secondary); }
/* FIXED, not absolute: inside the modal's scroller an absolutely-positioned panel is clipped by that
   container (the calendar's top was cut off) and could slide under the avatar row. Measured and placed
   by _dpOpen, above every modal layer. */
#userModal .date-panel {
    position: fixed;
    z-index: 1400;
    max-height: calc(100vh - 24px);
    overflow-y: auto;
    padding: 12px;
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: 0 14px 34px rgba(0, 0, 0, .55);
}
#userModal .date-panel.up { /* kept for older callers/tests; placement is measured now */ }
#userModal .date-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
/* The month IS the handle for the Schedule's month/year picker, so it is a real button with the same
   44px tap target as the arrows — and a chevron, the app's idiom for "this opens a chooser". */
#userModal .date-month {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 0 8px;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--btn-radius);
    color: var(--text-primary);
    font-family: inherit;
    font-size: 12px;
    letter-spacing: .5px;
    text-transform: uppercase;
    cursor: pointer;
}
#userModal .date-month:hover,
#userModal .date-month:active { border-color: var(--border); }
#userModal .date-month .icon-chevron { width: 14px; height: 14px; color: var(--primary); flex: 0 0 auto; }
/* The month arrows were a 32px box — too small to hit with a thumb. They now carry the Schedule's own
   calendar-nav look (flat, accent-coloured, glow on hover) at a 44px tap target. */
#userModal .date-nav {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: var(--btn-radius);
    color: var(--primary);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}
#userModal .date-nav:hover,
#userModal .date-nav:active {
    color: #fff;
    text-shadow: 0 0 12px rgba(0, 217, 255, 0.9);
}
#userModal .date-dow, #userModal .date-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
#userModal .date-dow span { text-align: center; font-size: 10px; color: var(--text-secondary); padding: 4px 0; }
#userModal .date-day {
    height: 36px;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    color: var(--text-primary);
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
}
#userModal .date-day { min-height: 40px; }
#userModal .date-day:hover { border-color: var(--border); }
#userModal .date-day.dim { color: var(--text-secondary); opacity: .45; }
#userModal .date-day.today { border-color: var(--primary); }
#userModal .date-day.sel { background: var(--primary); color: #04121a; font-weight: 600; }
#userModal .date-foot { display: flex; justify-content: space-between; margin-top: 10px; }
#userModal .date-quick {
    background: none; border: none;
    padding: 6px 2px;
    color: var(--primary);
    font-family: inherit;
    font-size: 11px; letter-spacing: .5px; text-transform: uppercase;
    cursor: pointer;
}

/* POWER LEVEL — the app's own accents, no new palette. Shared by the user form and the Team cards. */
.borg-power {
    display: block;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: 1px;
    color: var(--primary);
    text-shadow: 0 0 14px color-mix(in srgb, var(--primary) 45%, transparent);
}


/* ---- TEAM → STATS ---- */
#teamStatsModal .modal-content { display: flex; flex-direction: column; max-height: 88vh; }
@media (max-width: 600px) { #teamStatsModal .modal-content { max-height: none; height: 100%; } }
.team-stats-body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 18px 20px; }
.team-stats-avatar {
    width: 176px; height: 176px;
    margin: 2px auto 18px;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
    background: rgba(100, 108, 125, 0.10);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    cursor: zoom-in;
}
.team-stats-avatar.no-photo { cursor: default; }
.team-stats-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.team-stats-avatar svg { width: 84px; height: 84px; color: var(--text-secondary); }
.team-stats-row {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 12px 0;
    border-top: 1px solid var(--border);
}
.team-stats-row:first-of-type { border-top: none; }
.team-stats-label { font-size: 11px; letter-spacing: .5px; text-transform: uppercase; color: var(--text-secondary); }
.team-stats-value { font-size: 15px; color: var(--text-primary); }
.team-stats-power-row { flex-direction: column; align-items: flex-start; gap: 2px; }
.team-stats-power-row .borg-power { font-size: 34px; }

/* ---- TEAM (TOOLS → Team) ---- */
#teamModal .modal-content { display: flex; flex-direction: column; max-height: 88vh; }
#teamModal .modal-header { flex: 0 0 auto; }
#teamModal .form-actions { flex: 0 0 auto; }
@media (max-width: 600px) { #teamModal .modal-content { max-height: none; height: 100%; } }
#teamGrid {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.team-card {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
/* The strongest member on the roster, marked rather than ranked — no numbers, no medals. */
.team-card.leader { border-color: var(--primary); }
/* A card is a control: it opens the member's STATS. */
.team-card { cursor: pointer; transition: var(--transition); }
.team-card:hover, .team-card:focus-visible { border-color: var(--primary); }
.team-card:active { background: var(--surface-1); }
.team-avatar {
    width: 56px; height: 56px;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
    background: rgba(100, 108, 125, 0.10);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.team-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.team-avatar svg { width: 26px; height: 26px; color: var(--text-secondary); }
.team-name { font-size: 15px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.team-group { margin-top: 2px; font-size: 11px; letter-spacing: .5px; text-transform: uppercase; color: var(--text-secondary); }
.team-power { text-align: right; }
.team-power .borg-power { font-size: 20px; letter-spacing: 0; }
.team-power-label { display: block; margin-bottom: 2px; font-size: 9px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-secondary); }
.team-empty { padding: 24px 16px; text-align: center; color: var(--text-secondary); font-size: 13px; }

/* ---- TEAM profile card: the tap-to-call / hold-to-copy CHIP (`20260915teamchip1`) ----
   A bordered control (icon + value). TAP follows the tel: / mailto: link; LONG PRESS copies the value and
   flashes "Copied" — so the chip must NOT be a text-selection surface, or the OS would start a selection
   and its copy callout would fight the app's own gesture. They are the only place contact details appear
   in Team: the roster list deliberately shows none, because that list is readable by every member. */
.team-contact-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);   /* 0px — the app is square-cornered */
    color: var(--primary);
    text-decoration: none;
    cursor: pointer;
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
    transition: border-color .12s, color .12s, background .12s;
}
.team-contact-chip:hover,
.team-contact-chip:focus-visible { border-color: var(--primary); background: rgba(0, 217, 255, 0.07); }
.team-contact-chip:active { background: rgba(0, 217, 255, 0.14); }
.team-contact-chip-icon { flex: 0 0 auto; width: 13px; height: 13px; display: flex; }
.team-contact-chip-icon svg { width: 13px; height: 13px; }
.team-contact-chip-text { font-size: 13px; overflow-wrap: anywhere; }
/* The long-press result, said in the chip itself — no dialog for a copy. */
.team-contact-chip.copied { border-color: var(--success); color: var(--success); background: rgba(0, 255, 163, 0.08); }
.team-contact-chip.copy-failed { border-color: var(--danger); color: var(--danger-text); }
/* Both rows wrap rather than overflowing the stats card: an address can be longer than the card is wide. */
#teamStatsPhoneRow .team-stats-value,
#teamStatsEmailRow .team-stats-value { font-size: 13px; text-align: right; overflow-wrap: anywhere; }

/* ---- The user form: a field the save refused (`20260915teamcontact1`) ----
   Red edge + soft glow on the INPUT that has to be fixed (the icon/label tint follows from the group),
   and the flag clears as soon as the user edits that field. */
#userModal input.field-invalid,
#userModal textarea.field-invalid {
    border-color: var(--danger);
    /* The glow comes from the SAME token as the edge, so it stays correct in both themes (a hardcoded
       dark-theme rgba left the light theme with a muted clay border and a vividly pink halo). */
    box-shadow: 0 0 0 1px var(--danger), 0 0 12px -1px var(--danger);
}
#userModal .form-group.field-error > label { color: var(--danger-text); }

@media (max-width: 768px) {
    /* A roster card is a tap target (it opens that member's stats), so a long press must not start a text
       selection or raise the iOS/Android copy callout — the same rule the task, client, notes and day-card
       rows use. Set on the card AND its children: `user-select` does not inherit reliably across engines.
       Desktop keeps text selectable. */
    #teamGrid .team-card,
    #teamGrid .team-card * {
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }
}

/* Avatar gallery: the generated images for one user. Tiles are viewport-scaled so a phone fits two
   columns and a desktop fits more, and the picked state is a filled check, not a colour wash. */
.avatar-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: 10px;
    margin-top: 4px;
}
.avatar-gallery-tile {
    position: relative;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--bg-alt);
    cursor: pointer;
}
.avatar-gallery-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-gallery-tile.current { border-color: var(--primary); }
.avatar-gallery-badge {
    position: absolute; left: 4px; bottom: 4px;
    font-size: 9px; letter-spacing: .5px; text-transform: uppercase;
    background: rgba(0, 0, 0, .66); color: var(--primary);
    padding: 2px 5px; border-radius: 3px;
}
.avatar-gallery-check {
    position: absolute; right: 4px; top: 4px; width: 20px; height: 20px;
    border: 2px solid var(--border); border-radius: 4px; background: rgba(0, 0, 0, .5);
    display: none; align-items: center; justify-content: center;
}
#avatarGalleryModal.selecting .avatar-gallery-check { display: flex; }
#avatarGalleryModal.selecting .avatar-gallery-tile.picked .avatar-gallery-check {
    background: var(--primary); border-color: var(--primary);
}
#avatarGalleryModal.selecting .avatar-gallery-tile.picked .avatar-gallery-check::after {
    content: '\2713'; color: #04121a; font-size: 13px; font-weight: 700; line-height: 1;
}
.avatar-gallery-empty { color: var(--text-secondary); font-size: 13px; padding: 10px 0; }

/* The plate has to read inside a 104px frame. */
#userAvatarZone .avatar-loading { gap: 4px; }
#userAvatarZone .avatar-loading-main { font-size: 12px; letter-spacing: 1.5px; }
#userAvatarZone .avatar-loading-sub { font-size: 7.5px; letter-spacing: 1.2px; }
#userAvatarZone .inv-photo-placeholder { gap: 4px; font-size: 10.5px; }
#userAvatarZone img { -webkit-user-drag: none; user-drag: none; -webkit-touch-callout: none; }
#userAvatarZone .inv-photo-placeholder svg { width: 26px; height: 26px; }

/* While an avatar is being generated, the uploaded photo is covered by a torn LOADING plate. It uses
   the logo's own keyframes (loginTear + logoFlicker) so the two read as one system, not two looks. */
.avatar-loading {
    position: absolute; inset: 0;
    display: none;
    flex-direction: column; align-items: center; justify-content: center; gap: 8px;
    background: var(--bg-alt);
    z-index: 2;
}
#userAvatarZone.avatar-busy .avatar-loading { display: flex; }
#userAvatarZone.avatar-busy #userAvatarPreview,
#userAvatarZone.avatar-busy .inv-photo-placeholder { visibility: hidden; }
.avatar-loading-main {
    font-family: 'Arial Black', 'Arial', sans-serif;
    font-size: 22px; letter-spacing: 5px; text-transform: uppercase;
    background-image: linear-gradient(180deg, #7df9ff 0%, var(--primary) 45%, var(--success) 100%);
    -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 0 4px rgba(0, 217, 255, 0.7)) drop-shadow(0 0 18px rgba(0, 255, 163, 0.35));
    animation: logoFlicker 6s linear infinite, loginTear 4.5s steps(1, end) infinite;
}
.avatar-loading-sub {
    font-size: 11px; letter-spacing: 3px; text-transform: uppercase;
    color: var(--text-secondary);
}
@media (prefers-reduced-motion: reduce) {
    .avatar-loading-main { animation: none; }
}

.inv-photo-placeholder {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
    color: var(--text-secondary); opacity: .7; font-size: .9rem;
}
.inv-photo-placeholder svg { width: 40px; height: 40px; }

/* Take / Upload buttons under the photo — standard action-bar metrics so they match the
   notes form's media buttons and the pinned action bars. */
.inv-photo-actions {
    display: flex; gap: 8px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
}
.inv-photo-btn { flex: 1; justify-content: center; white-space: nowrap; letter-spacing: .5px; }

/* Item form compact layout (inv33): Name+Category share a row (60/40),
   Make+Model stay side-by-side at every width, Quantity stepper centered with
   a 5-digit input — frees vertical room for the photo zone.
   minmax(0,…): content can never stretch a track past its exact share — a long
   Make value used to make that column visibly wider than Model (inv34). */
.inventory-item-modal-content .form-row.inv-name-cat-row { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
.inventory-item-modal-content .form-row.inv-make-model-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

.inventory-item-modal-content .modal-form { padding: 4px 0; }

/* ---- Inventory CATEGORIES (create / rename / delete groups) ---- */
/* Stacking: sits right after inventoryModal in the DOM, so at the base modal
   z-index it renders above the list — no explicit z-index needed. */
.inv-categories-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 480px;
    width: 100%;
    height: 88dvh;   /* standard tool-window height (was 72dvh) */
    background: var(--bg-base);
}

.inv-categories-modal-content .modal-header { flex-shrink: 0; }
.inv-categories-modal-content .gallery-bottom-nav { flex-shrink: 0; }

/* Bottom bar (list view): Add Category — same treatment as the inventory list's ADD ITEM */
/* Categories window bottom bar — same 12px 16px / gap 8px as every other action bar. */
#invCatBottomNav { padding: 12px 16px; gap: 8px; }
.inv-cats-add-btn { width: 100%; margin: 0; flex: 1; justify-content: center; }

/* Edit view actions: standard .form-actions row (same pattern as task/client/item forms) */
.inv-categories-modal-content .form-actions{ flex-shrink: 0; /* surface: the shared bottom-bar rule */ }
.inv-cat-form-actions { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }

/* LIST VIEW — the scrollable region */
.inv-cat-list-wrap { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.inv-cat-empty { padding: 40px 24px; text-align: center; color: var(--text-secondary); }

.inv-cat-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 13px 16px;
    cursor: pointer;
    transition: background .15s, filter .15s;
}
.inv-cat-row:hover { filter: brightness(1.12); }
.inv-cat-row:active { filter: brightness(1.06); }
.inv-cat-row:last-child { border-bottom: none; }

.inv-cat-name {
    font-weight: 600; color: var(--text-primary);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inv-cat-count {
    flex-shrink: 0;
    font-size: .8rem; color: var(--text-secondary);
    background: var(--bg-alt);
    border: 1px solid var(--border);
    padding: 2px 8px;
}

/* EDIT VIEW — add / rename form */
.inv-cat-edit-wrap { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.inv-cat-delete-zone { padding: 0 24px 24px; flex-shrink: 0; }
.inv-cat-del-btn { width: 100%; justify-content: center; }

/* Category frames — items grouped under a bordered header per category (frames alphabetical) */
.inv-cat-frame { margin: 12px; border: 1px solid var(--border); background: var(--bg-card); }
.inv-cat-frame-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-alt);
    position: sticky; top: 0; z-index: 1;
}
.inv-cat-frame-title {
    font-size: .72rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
    color: var(--primary);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Quantity stepper on item cards (right side of each row): [-] number [+] —
   buttons use the site-standard .btn.btn-secondary treatment; input matches
   form inputs (--surface-1 / --border, sharp corners), compact for card rows */
.inv-card-qty {
    flex-shrink: 0;
    display: flex; align-items: center; gap: 4px;
}
.inv-card-qty-btn {
    width: 32px; min-width: 32px; height: 32px; padding: 0;
    font-size: 15px; line-height: 1; letter-spacing: 0;
}
.inv-card-qty-input {
    width: 46px; height: 32px; padding: 0 2px;
    text-align: center; font-size: .85rem;
    border: 1px solid var(--border); background-color: var(--surface-1);
    color: var(--text-primary); border-radius: 0;
    transition: var(--transition);
    -moz-appearance: textfield; appearance: textfield; /* hide native spinners */
}
.inv-card-qty-input:focus { outline: none; border-color: var(--primary); }
.inv-card-qty-input::-webkit-outer-spin-button,
.inv-card-qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Search matched nothing (items exist, none fit the query) */
.inv-search-empty { padding: 48px 24px; text-align: center; color: var(--text-secondary); }

/* Detail page: "No category" renders muted */
.inv-detail-cat-none { color: var(--text-secondary); opacity: .75; }

/* Quantity stepper: [-] integer [+] — buttons match the input height exactly */
.inv-qty-stepper { display: flex; gap: 8px; align-items: stretch; }
/* Quantity + Threshold sit on ONE row (20260912qtyrow1). Equal tracks with minmax(0, 1fr) — a bare 1fr
   lets a track stretch with its content, the Make/Model lesson from inv34 — and the stepper fills its track
   instead of being a centred fixed-width block, so the pair fits however narrow the screen is. */
/* The two groups are CENTRED as a pair (20260912qtyrow3). They are fixed-width units, so the leftover space
   splits evenly at both ends — equal padding against the screen edge left and right, at any width — instead
   of the quantity group sitting flush against the left while the threshold group trails short of the right.
   Flex rather than a grid track pair, because the even split IS the point; the scoped selector still
   outranks the @media 600px stacking rule, so the pair never stacks.
   The gap is wider than the 6px gaps inside each stepper on purpose — it has to read as "two separate
   controls" — and the row is centred, so every pixel it takes comes off the two edge paddings equally.
   `min(34px, 100% - 336px)` spends the leftover on the gap until only 8px of padding is left per side:
   336 = the two 160px groups + 2 x 8px minimum padding. Percentages resolve against the row's own width, so
   a 390px screen gets the full 34px gap (17px edges) and a 360px one gets 22px (8px edges) instead of the
   edges being crushed to nothing. */
.inventory-item-modal-content .form-row.inv-qty-thresh-row {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: min(34px, calc(100% - 336px));
}
.inventory-item-modal-content .inv-qty-thresh-row .form-group { flex: 0 0 auto; }
.inventory-item-modal-content .inv-qty-thresh-row .form-group.inv-qty-row .inv-qty-stepper {
    justify-content: stretch;
    width: auto;
    margin: 0;
    gap: 6px;
}
.inv-qty-btn {
    width: 52px; min-width: 52px;
    padding: 0; height: 48px;
    font-size: 16px; line-height: 1;
}
/* Compact inside that row (20260912qtyrow2): smaller buttons, and a value box sized for the 5-digit
   maximum (INV_QTY_MAX) rather than stretching to fill the half. The boxes hold 5 digits at 14px with room
   to spare, and the space the old full-width boxes used to eat becomes the visible gap between the
   quantity "+" and the threshold "−" — so the groups also stop touching the row's outer edges. */
.inv-qty-thresh-row .inv-qty-btn { width: 42px; min-width: 42px; height: 44px; font-size: 15px; }
/* The threshold's box carries the danger border, so the quantity's box takes the app's cyan in the same
   slot (--primary = #00d9ff in dark, the vivid cyan kept per the theme rules). Only the RESTING border is set
   here: the generic `.form-group input:focus` rule already gives this box the cyan border + glow on focus,
   and the threshold mirrors that with its own red focus ring. */
.inv-qty-thresh-row #invQtyInput { border-color: var(--primary); }
.inv-qty-thresh-row #invQtyInput,
.inv-qty-thresh-row #invThreshInput {
    flex: 0 0 auto;
    width: 64px;                 /* 48px of content after padding — "99999" needs ~38px at 14px */
    min-width: 0;
    height: 44px;
    padding-left: 8px;
    padding-right: 8px;
}
#invQtyInput {
    flex: none; width: 128px; min-width: 0; /* ~5 digits (inv33) */
    height: 48px; padding-top: 0; padding-bottom: 0;
    text-align: center;
    -moz-appearance: textfield; appearance: textfield; /* hide native spinners */
}
#invQtyInput::-webkit-outer-spin-button,
#invQtyInput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ===== Inventory photo CAMERA (live 1:1 viewfinder) + CROP (frame/scale/approve) ===== */
/* Stacking: both modals sit AFTER inventoryItemModal in the DOM, so at the base
   modal z-index they render above it — no explicit z-index needed.

   CRITICAL: #userCameraModal (the user avatar camera) REUSES every .inv-cam-* class below and has NO CSS of
   its own ("same 1:1 square pattern as inventory"). So the shared rules here keep describing the SQUARE
   VIEWFINDER layout, and everything specific to the inventory camera's full-screen design is scoped to
   #invCameraModal further down. Editing a shared .inv-cam-* rule changes the avatar camera too. */

.inv-camera-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 480px;
    width: 100%;
    background: var(--bg-base);
}

/* Shared (avatar camera layout): the square viewfinder, the frame overlay and the bar beneath it. */
.inv-cam-viewfinder-wrap {
    position: relative;
    width: min(100%, 56vh);
    margin: 0 auto;
    aspect-ratio: 1 / 1;
    background: #000;
    overflow: hidden;
}
/* Both feeds fill their box the same way. (#userCamViewfinder previously had NO rule and rendered at the
   sensor's intrinsic size, then got clipped — cover crops it properly instead.) */
#invCamViewfinder,
#userCamViewfinder {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* Corner brackets marking the capture frame (overlay only) */
.inv-cam-frame {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 2;
}
.inv-cam-frame::before, .inv-cam-frame::after {
    content: '';
    position: absolute;
    width: 26px;
    height: 26px;
    border: 2px solid var(--primary);
}
.inv-cam-frame::before { top: 8px; left: 8px; border-right: none; border-bottom: none; }
.inv-cam-frame::after  { bottom: 8px; right: 8px; border-left: none; border-top: none; }

/* Status line overlays the frame's bottom edge (empty = invisible) */
.inv-cam-status {
    position: absolute;
    left: 0; right: 0; bottom: 6px;
    text-align: center;
    color: #fff;
    font-size: 12px;
    letter-spacing: 1px;
    pointer-events: none;
    z-index: 3;
}

/* Controls row: [Retake] … [shutter] — shutter reuses .camera-shutter-btn */
.inv-cam-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 20px calc(18px + env(safe-area-inset-bottom));
    flex: 0 0 auto;
}

/* ===== USER AVATAR CAMERA — "Take Photo" (`20260915usercam3`) =====
   The same screen as the inventory camera: the feed fills the card EDGE TO EDGE and a square BOX floats over
   it marking the region that gets saved — dimmed outside, brackets on the corners. The user asked for that
   by name ("just like how the inventory camera works"), along with the buttons pinned to the bottom and
   Capture dead centre with a flip button to its right. The bar is the TOOLS camera's own markup
   (.camera-controls + .camera-ctrl-live), so its spacing, surface and shutter size match that screen exactly
   rather than being approximated here. Scoped to #userCameraModal throughout: #invCameraModal has its own
   identical-but-separate rules and must not inherit any of this. */
#userCameraModal .inv-camera-modal-content {
    /* A camera screen, not a content-sized card: the shell carries the height itself, because every child
       (feed, box, header, bar) is absolutely positioned over it.
       `position: relative` is load-bearing: the header and the bar anchor to THIS box. Without it they anchor
       to the OVERLAY — which on a phone is the same box (so it looked right) but on a desktop put the bar
       80px below the card, where `overflow: hidden` clipped it out of sight entirely. */
    position: relative;
    border: none;
    height: min(100dvh, 820px);
    --inv-cam-bar-h: 96px;      /* the floating bottom bar's height — the status line sits above it */
}
#userCameraModal .inv-cam-viewfinder-wrap {
    /* FULL BLEED: the wrap is the feed layer now, not a square window. The shared rule sizes it as a square
       with `margin: 0 auto`, which is what made the old layout a window instead of a screen. */
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    aspect-ratio: auto;
    margin: 0;
}
#userCameraModal #userCamViewfinder {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
/* THE BOX — centred, and it IS the saved region: the capture maps it back through the feed's cover scale
   (`_camBoxSourceRect`), so what leaves the shutter is what the box framed. min(92vw, 100dvh - 240px) keeps
   it the largest square that still clears the floating header and bottom bar at any viewport height. */
#userCameraModal .inv-cam-frame {
    left: 50%;
    top: 50%;
    right: auto;
    bottom: auto;
    transform: translate(-50%, -50%);
    width: min(92vw, calc(100dvh - 240px));
    aspect-ratio: 1 / 1;
    /* one enormous spread shadow dims everything outside the box — no extra mask elements */
    box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.55);
}
/* The status line belongs to the free area above the floating bar, not to the box. */
#userCameraModal .inv-cam-status { bottom: calc(var(--inv-cam-bar-h) + 6px); }
#userCameraModal .modal-header,
#userCameraModal .user-cam-bar {
    /* Header and bar FLOAT over the feed, like the inventory camera's own header and bar. */
    position: absolute;
    left: 0;
    right: 0;
}
#userCameraModal .modal-header {
    top: 0;
    z-index: 10;
    /* FLOATS OVER THE FEED: no opaque bar, no hairline — the inventory camera's header does the same. Without
       this the feed stopped dead at the title bar instead of reading as one full-bleed screen. The shadow is
       what keeps white text legible over a bright frame. */
    background: transparent;
    border-bottom: none;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
#userCameraModal .user-cam-bar { bottom: 0; z-index: 6; }
/* The flip button sits in the right column, nudged toward the shutter the same way it is on the TOOLS camera
   (there it has a mode toggle on the left; here that column is an empty spacer holding the shutter centred). */
#userCameraModal .camera-ctrl-live > .camera-flip-btn { justify-self: start; margin-left: 24px; }
@media (max-width: 768px) {
    /* Scoped like the rule it overrides: an unscoped override loses to the id-scoped base rule above. */
    #userCameraModal .inv-camera-modal-content { height: 100dvh; }
    #userCameraModal .inv-cam-frame { width: min(92vw, calc(100dvh - 230px)); }
}

/* ===== INVENTORY CAMERA — FULL SCREEN (`20260912invcam3`) =====
   The feed fills the whole screen and the bright square drawn over it IS the crop that gets saved. Scoped to
   #invCameraModal so the avatar camera keeps the layout above. */
#invCameraModal .inv-camera-modal-content {
    /* Every child is absolutely positioned over the feed, so the shell carries the height itself.
       No border: the modal's own 1px hairline left the feed 2px short of the screen edges. */
    position: relative;
    border: none;
    height: min(100dvh, 820px);
    --inv-cam-bar-h: 96px;      /* the floating bottom bar's height — the status line sits above it */
}
/* THE FEED FILLS THE SCREEN. object-fit (from the shared rule) crops the sensor's overflow, so the whole
   screen is live video; the square below is the part that actually gets saved. */
#invCameraModal #invCamViewfinder {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
/* THE SQUARE CROP BOX — centred. min(92vw, 100dvh - 240px) keeps it the largest square that still clears the
   floating header and bottom bar at any viewport height.
   NOTE: this box is a FRAMING GUIDE, not the saved region. The capture uses the centred min(vw,vh) square, so
   when the feed's aspect differs from the card's (a landscape sensor feed in a portrait phone screen, say) what
   is saved is WIDER than the box — measured 454% more area with a 1280x720 feed at 390x844. The avatar camera's
   box is exact because it maps the box back through the cover scale (`_camBoxSourceRect`); switching this one
   over is a one-line change in `_invCamCapture` but it TIGHTENS every item photo taken afterwards, so it is a
   deliberate behaviour change rather than a cleanup — ask first. */
#invCameraModal .inv-cam-frame {
    left: 50%;
    top: 50%;
    right: auto;
    bottom: auto;
    transform: translate(-50%, -50%);
    width: min(92vw, calc(100dvh - 240px));
    aspect-ratio: 1 / 1;
    /* one enormous spread shadow dims everything outside the box — no extra mask elements */
    box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.55);
}
/* The frozen shot: pixel-for-pixel what the live view showed (same full-bleed cover, so the crop box still
   frames exactly what will be saved) */
#invCameraModal .inv-cam-shot {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    z-index: 2;          /* above the video, below the crop box */
}
/* Status line — floats just above the bottom bar instead of the frame's edge (empty = invisible) */
#invCameraModal .inv-cam-status {
    bottom: calc(var(--inv-cam-bar-h) + 6px);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
    z-index: 4;
}
/* The floating title bar: no X (Cancel is the exit), no chrome — it sits over the feed */
.inv-cam-header {
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 5;
    background: transparent;
    border-bottom: none;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
/* Bottom bars float OVER the feed, pinned to the screen's bottom edge (frost comes from the app's single
   shared bottom-bar rule). Review bar: RETAKE left | USE PHOTO right; live bar is the shutter + Cancel. */
#invCameraModal .inv-cam-controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 6;
    height: var(--inv-cam-bar-h);
    padding: 0 20px calc(env(safe-area-inset-bottom));
}

/* Live bar: shutter dead CENTRE, Cancel pinned to the right. A 3-column grid (1fr auto 1fr) does the
   centring without a spacer element's width mattering. Two classes throughout: a single-class modifier ties
   with the base .inv-cam-controls rule and loses it by source order (the shutter sat at the left edge,
   measured centre 53px instead of 195px on a 390px screen) — same specificity-tie trap as .nav-tabs. */
.inv-cam-controls.inv-cam-controls--live {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    justify-items: center;
}
.inv-cam-controls--live .inv-cam-bar-side { width: 100%; }        /* the empty left half */
.inv-cam-controls--live .inv-cam-bar-side--right { justify-self: end; display: flex; justify-content: flex-end; }
.inv-cam-cancel { min-width: 0; }

/* Review bar: RETAKE (left) | USE PHOTO (right), spread apart by the base rule's space-between.
   NOTE the DELIBERATE reversal of the site's usual primary-left order — the user asked for it by name
   ("switch the buttons left and right") on 2026-09-12; do not "fix" it back. Use keeps the primary style
   and Retake the secondary (never a danger one). */
.inv-cam-controls.inv-cam-controls--review { gap: 10px; }
.inv-cam-controls--review .btn { min-width: 0; }

/* Crop modal: the square stage is the crop window */
.inv-crop-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 480px;
    width: 100%;
    background: var(--bg-base);
}
.inv-crop-stage {
    position: relative;
    align-self: center;
    width: min(84vw, 52vh);
    aspect-ratio: 1 / 1;
    margin: 16px auto;
    background: #000;
    border: 1px solid var(--border);
    overflow: hidden;
    touch-action: none;   /* we own all gestures on the frame */
}
/* Shared by BOTH crop surfaces: absolute + max-width:none + origin 0 0 is what makes the
   translate/scale math (and the canvas mapping back to source pixels) correct. The avatar crop's
   image was missing these rules entirely, which is why its scaling looked wrong. */
#invCropImg, #userCropImg {
    position: absolute;
    top: 0;
    left: 0;
    max-width: none;      /* allow scaling beyond the frame */
    transform-origin: 0 0;
    will-change: transform;
    user-select: none;
    -webkit-user-drag: none;
}
/* Corner brackets marking the crop window (overlay only) */
.inv-crop-frame {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 2;
}
.inv-crop-frame::before, .inv-crop-frame::after {
    content: '';
    position: absolute;
    width: 26px;
    height: 26px;
    border: 2px solid var(--primary);
}
.inv-crop-frame::before { top: 8px; left: 8px; border-right: none; border-bottom: none; }
.inv-crop-frame::after  { bottom: 8px; right: 8px; border-left: none; border-top: none; }

/* Crop actions reuse .form-actions (flex + border-top); add the bottom safe area */
.inv-crop-actions {
    padding-bottom: calc(18px + env(safe-area-inset-bottom));
    /* Pinned to the bottom of the card: the stage keeps its square and centres in the room left over. */
    margin-top: auto;
    flex: 0 0 auto;
}
/* The stage centres in whatever room is left; the actions take the bottom. Two auto margins split the
   free space between them, which is what puts the buttons on the floor of the card. */
#userCropModal .inv-crop-stage { margin: auto; }

/* Mobile: all inventory modals fill the screen */
@media (max-width: 768px) {
    .inv-camera-modal-content,
    .inv-crop-modal-content {
        max-width: 100%;
        width: 100%;
        height: 100%;   /* NOT 100dvh: it resizes as the phone's URL bar moves, which redraws the whole screen */
        border-radius: 0;
    }
    /* Full bleed on phones: the shell takes the whole viewport and the crop box is centred over it.
       The crop-box rule stays scoped — the avatar camera's frame is still `inset: 0` in its square
       viewfinder and must not be given a width. */
    /* Scoped like the rule it overrides: the id-scoped base rule (2,0,0) outranks a bare class here, so
       an unscoped override silently lost and the shell stayed 820px tall and got centred, leaving the feed
       short of the screen's bottom edge (measured 388x817 instead of 390x844). */
    #invCameraModal .inv-camera-modal-content { height: 100dvh; }
    #invCameraModal .inv-cam-frame { width: min(92vw, calc(100dvh - 230px)); }
    .inv-crop-stage { width: min(92vw, 46vh); }
    .inventory-modal-content,
    .inv-categories-modal-content,
    .inventory-detail-modal-content,
    .inventory-item-modal-content {
        max-width: 100%;
        width: 100%;
        height: 100dvh;
        border-radius: 0;
    }
}

/* ============ Portrait guard (20260912rotate1) ============ */
/* A phone held in landscape gets a cover: browser tabs cannot lock orientation (see _lockOrientation) and the
   manifest only binds installed apps. Gated to HANDSET landscape on purpose — a coarse pointer (touch) in a
   viewport no taller than 520px — so a desktop window (landscape by nature, fine pointer) and a tablet keep
   their normal landscape layout. */
#rotateGuard { display: none; }
@media (orientation: landscape) and (pointer: coarse) and (max-height: 520px) {
    #rotateGuard {
        display: flex;
        position: fixed;
        inset: 0;
        z-index: 9999;            /* above every modal: the app's own ceiling is 9600 */
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 14px;
        padding: 24px;
        background: var(--bg-base);
        text-align: center;
    }
}
/* The icon is a stack: the phone rocks, and a red X fades over it while it lies horizontal. */
#rotateGuard .rotate-icon { position: relative; width: 52px; height: 52px; }
#rotateGuard .rotate-phone {
    width: 52px;
    height: 52px;
    color: var(--primary);
    /* Rocks portrait → landscape → portrait with a beat at each end, so the eye reads "turn me" instead of
       "spinning". Pure CSS, no JS. */
    animation: rotateHint 2.8s ease-in-out infinite;
    transform-origin: 50% 50%;
}
@keyframes rotateHint {
    0%, 18%   { transform: rotate(0deg); }
    42%, 64%  { transform: rotate(-90deg); }
    88%, 100% { transform: rotate(0deg); }
}
/* The X rides the SAME 2.8s timeline as the rotation, so the two can never drift apart: opaque for exactly
   the window the phone is horizontal, fading in on the way down and out on the way back up. Base opacity 0
   also means it is invisible when animations do not run at all (reduced motion, or before the first frame). */
#rotateGuard .rotate-cross {
    position: absolute;
    inset: 0;
    width: 52px;
    height: 52px;
    /* CRITICAL-level red, the same value the dashboard's CRITICAL tile uses — the user asked for the
       critical-task red by name, so this is that colour, via a shared token rather than a new hex. */
    color: var(--critical-red);
    opacity: 0;
    animation: rotateHintCross 2.8s ease-in-out infinite;
}
@keyframes rotateHintCross {
    0%, 22%   { opacity: 0; }         /* upright: no X */
    42%, 64%  { opacity: 1; }         /* horizontal: X held */
    86%, 100% { opacity: 0; }         /* back upright: faded out */
}
/* Respect the OS setting: the captions carry the message, the motion is decoration. The X stays hidden too —
   an X frozen over a phone that is not moving would just look broken. */
@media (prefers-reduced-motion: reduce) {
    #rotateGuard .rotate-phone,
    #rotateGuard .rotate-cross { animation: none; }
}
#rotateGuard p {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--text-primary);
}
#rotateGuard span {
    display: block;
    margin-top: -6px;               /* the flex gap already separates the icon; keep the two lines tight */
    font-size: 11px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* ============ Page-level zoom lock (20260912nopinch1) ============ */
/* `manipulation` = pan + pinch allowed, double-tap zoom off. Deliberately NOT `pan-x pan-y`: that would read
   as "pinch not allowed" up the ancestor chain and could clamp the crop stage / camera / Leaflet pinch, which
   the app genuinely uses. A descendant asking for `touch-action: none` keeps getting none. */
html, body { touch-action: manipulation; }

/* ============ Client form — framed address group + location picker ============ */

/* The frame that consolidates the street/city/state/zip fields into one unit,
   with a header row carrying the label and the map-pin button. */
.client-addr-frame {
    border: 1px solid var(--border);
    background: var(--bg-alt);
    padding: 10px;
    margin-bottom: 14px;
}

.client-addr-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}

.client-addr-title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Map-pin button — icon-only, sharp corners per site convention */
.client-map-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 6px 8px;
    gap: 0;
}

/* ---- Location picker modal (clientMapModal) ---- */
.client-map-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    max-width: 700px;
    width: 100%;
    height: 88dvh;
    background: var(--bg-base);
}

.client-map-searchbar {
    display: flex;
    gap: 8px;
    padding: 10px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-alt);
}

.client-map-searchbar input {
    flex: 1;
    min-width: 0;
    background: var(--bg-base);
    color: var(--text-primary);
    border: 1px solid var(--border);
    padding: 9px 12px;
    font-size: 14px;
}

.client-map-search-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 12px;
}

/* Search results strip (single "no hit" row) */
.client-map-results {
    border-bottom: 1px solid var(--border);
    background: var(--bg-alt);
    max-height: 96px;
    overflow-y: auto;
}

.client-map-result-row {
    padding: 8px 12px;
    font-size: 13px;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border);
}

.client-map-result-none {
    color: var(--text-secondary);
    font-style: italic;
}

/* Live-search hit row — tappable, narrows as you type */
.client-map-result-hit {
    cursor: pointer;
    transition: background 0.12s ease;
}
.client-map-result-hit:hover {
    background: var(--surface-2);
    color: var(--primary);
}
/* Approximate distance from the user's location — muted, right of the name */
.client-map-result-dist {
    color: var(--text-secondary);
    font-size: 12px;
    white-space: nowrap;
}

/* Footer: the window's action bar — Apply left, Close right (the app's 2-button row). It carries the
   same metrics as .form-actions so every bottom bar in the app lines up, and it is now the only way out
   of the picker (the header and its X were removed on request).
   NO `align-items: center`: the buttons stretch to one row height (the .form-actions default). */
.client-map-footer {
    display: flex;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    flex-shrink: 0;
}

.client-map-footer .btn {
    padding: 10px 16px;
    font-size: 12px;
}

/* App buttons are UPPERCASE with 1.5px letter-spacing, so "USE THIS LOCATION" needs ~168px — wider
   than half this bar. Splitting the row evenly wrapped it onto two lines: the bar grew to 79px with a
   54px button next to a 38px one. The primary takes twice the room Close needs: one line each, equal
   heights, and Close still lands ~116px wide. */
.client-map-footer .client-map-use-btn { flex: 2; }
.client-map-footer .client-map-close-btn { flex: 1; }

/* Custom pin marker (cyan dot, matches the client map's accent) */
.client-picker-pin { background: transparent; border: none; }
.client-picker-pin-dot {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #00d9ff;
    border: 3px solid var(--bg-base);
    box-shadow: 0 0 0 2px #00d9ff, 0 2px 8px rgba(0, 0, 0, 0.5);
}

/* Mobile: fill the entire screen (same as the client map) */
@media (max-width: 768px) {
    .client-map-modal-content {
        max-width: 100%;
        height: 100dvh;
        border-radius: 0;
    }
}

/* ---- Install App modal (TOOLS → Install App) ---- */
.install-modal-content {
    display: flex;
    flex-direction: column;
    max-width: 520px;
    width: 100%;
}

.install-onetap {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 8px 0;
}

.install-onetap p {
    color: var(--text-secondary);
    font-size: 14px;
}

#installNowBtn {
    width: 100%;
    justify-content: center;
}

.install-steps p {
    margin-bottom: 10px;
    font-size: 14px;
}

.install-steps ol {
    margin: 0 0 8px 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.install-steps li {
    color: var(--text-secondary);
    font-size: 14px;
}

.install-steps li::marker {
    color: var(--primary);
    font-weight: 700;
}

/* Safari's Share button, drawn inline (square + up arrow) */
.ios-share {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: 1.5px solid var(--text-secondary);
    font-size: 12px;
    line-height: 1;
    vertical-align: -4px;
    color: var(--text-primary);
}

.install-divider {
    border: none;
    border-top: 1px solid var(--border);
    margin: 18px 0 12px;
}

.install-note {
    color: var(--text-secondary);
    font-size: 12.5px;
    line-height: 1.6;
}

/* Mobile: fill the entire screen (same as other tool modals) */
@media (max-width: 768px) {
    .install-modal-content {
        max-width: 100%;
        height: 100dvh;
        border-radius: 0;
    }
}


/* ---- Cloud sync status (chip + issues modal) ---- */
.sync-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 11px;
    background-color: rgba(0, 217, 255, 0.10);
    border: 1px solid rgba(0, 217, 255, 0.35);
    border-radius: 999px;
    color: var(--primary);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.5px;
    cursor: pointer;
}
.sync-chip-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--primary);
    box-shadow: 0 0 6px rgba(0, 217, 255, 0.85);
    animation: syncPulse 2s ease-in-out infinite;
}
/* Repeated failures escalate to the danger tone (slow pulse, no pink) */
.sync-chip.is-error {
    background-color: rgba(255, 77, 109, 0.10);
    border-color: rgba(255, 77, 109, 0.40);
    color: var(--danger-text);
}
.sync-chip.is-error .sync-chip-dot {
    background: var(--danger);
    box-shadow: 0 0 8px rgba(255, 77, 109, 0.9);
    animation-duration: 1.1s;
}
@keyframes syncPulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.35; }
}
.sync-lead {
    margin: 0 0 14px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
}
.sync-list { display: flex; flex-direction: column; gap: 8px; }
.sync-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background-color: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 8px;
}
.sync-row-info { flex: 1; min-width: 0; }
.sync-row-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sync-row-meta {
    margin-top: 2px;
    font-size: 11px;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
}
.sync-row-retry { flex: 0 0 auto; }
.sync-empty { margin: 0; font-size: 13px; color: var(--text-secondary); }


/* ---- One-time gesture hint (notes media grid) ---- */
.gesture-hint {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 6px;
    padding: 9px 12px;
    background-color: rgba(0, 217, 255, 0.08);
    border: 1px solid rgba(0, 217, 255, 0.28);
    border-radius: 8px;
    color: var(--text-secondary);
    font-size: 12px;
    letter-spacing: 0.3px;
    opacity: 1;
    transition: opacity 0.6s ease;
}
.gesture-hint strong { color: var(--primary); font-weight: 700; }
.gesture-hint.is-gone { opacity: 0; }
.gesture-hint-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--primary);
    box-shadow: 0 0 6px rgba(0, 217, 255, 0.85);
    animation: syncPulse 2s ease-in-out infinite;
}


/* ---- Notes list toolbar (Client + Date filters, sort) ----
   Pinned under the modal header; the card list keeps its own scroll below it.
   Buttons/selects inherit the standard 38px filter-control sizing from .filters-btn /
   .filter-select, so nothing here touches their metrics. */
#notesListModal .notes-filters,
#workLogsModal .notes-filters {
    flex-shrink: 0;
    margin: 0;
    padding: 12px 16px;
    background: var(--bg-base);
    border-bottom: 1px solid var(--border);
    align-items: center;
    flex-wrap: nowrap;
}
/* The chip sizes to its content and the sort keeps the standard 150px min-width — the
   same as the tasks and gallery toolbars. Stretching them (flex:1 / width:100%) made the
   notes chip 220px wide where every other page's is 121px. */
#notesListModal .notes-filters > .filter-group,
#workLogsModal .notes-filters > .filter-group { flex: 0 0 auto; }

/* "Entries exist but none match" — deliberately NOT the empty state, which would read
   like the notess were lost. */
.notes-no-match {
    padding: 30px 16px 6px;
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}
.notes-no-match-clear { display: block; margin: 14px auto 0; }


/* ================================================================
   BUTTON SHAPE — app-wide
   Every button in the app shares one corner radius and one outline
   language, matching the media viewer's bar buttons. Kept in ONE
   place so the shape cannot drift per screen; it sits at the end of
   the stylesheet on purpose, so it wins against the per-feature
   `border-radius: 0` declarations above.
   Deliberate exceptions (still round/circular by design): the
   camera shutter, the lightbox prev/next and its ×, the media
   select checkmarks, .sync-chip (already a full pill).
================================================================ */
.btn,
.btn-icon,
.modal-close,
.dt-btn,
.select-btn,
.filters-btn,
.task-btn,
.calendar-nav-btn,
.mp-step-btn,
.mp-month-btn,
.cal-add-task-btn,
.logout-btn,
.clear-date-btn,
.gallery-view-btn,
.wx-gps-btn,
.mobile-tools-btn,
.cluster-panel-close,
.client-map-btn,
.client-map-use-btn,
.inv-qty-btn,
.inv-card-qty-btn,
.inv-card-qty-minus,
.inv-card-qty-plus,
.inv-cats-btn,
.inv-cats-add-btn,
.inv-close-btn,
.inventory-add-btn,
.inv-photo-btn,
.inv-cam-retake,
.camera-mode-toggle,
.notes-add-btn,
.notes-select-btn,
.notes-media-btn,
.lb-edit-x,
.lightbox-action-btn {
    border-radius: var(--btn-radius);
}

/* Icon-only buttons keep a square-ish footprint but the same corners. */
.btn-sm:has(svg):not(:has(svg + *)):not(:has(* + svg)),
.btn-icon,
:root.dark-mode .btn-icon,
:root.light-mode .btn-icon {
    border-radius: var(--btn-radius);
}

/* Work-log media (add): same language as the notes form's media block, but scoped to this modal so it
   cannot drift with the notes one. Thumbnails fill a 3-up grid; the empty line shows until one lands. */
#workLogsFormModal .wlog-media-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
#workLogsFormModal .wlog-media-head label { margin: 0; }
#workLogsFormModal .wlog-media-btns { display: flex; gap: 8px; }
#workLogsFormModal .wlog-media-btns .btn { white-space: nowrap; }
#workLogsFormModal .wlog-media-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
}
#workLogsFormModal .wlog-media-grid:empty { display: none; }
#workLogsFormModal .wlog-media-thumb {
    position: relative;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--border);
    border-radius: var(--btn-radius);
    overflow: hidden;
    background: var(--surface, rgba(255,255,255,0.04));
    cursor: pointer;
    -webkit-touch-callout: none;
    user-select: none;
    -webkit-user-select: none;
    transition: border-color 0.15s;
}
#workLogsFormModal .wlog-media-thumb:hover { border-color: var(--primary); box-shadow: var(--glow-cyan); }
#workLogsFormModal .wlog-media-thumb img,
#workLogsFormModal .wlog-media-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
/* Video tiles carry a badge — without it a video thumb is indistinguishable from a photo. */
#workLogsFormModal .wlog-media-thumb .wlog-media-video-badge {
    position: absolute;
    bottom: 5px;
    left: 6px;
    background: rgba(0,0,0,.65);
    color: var(--primary);
    font-size: 9px;
    letter-spacing: 1px;
    padding: 2px 5px;
    font-weight: 700;
    pointer-events: none;
}
/* Selection ring — only while picking items to delete (tap = view, long-press = select). */
#workLogsFormModal .wlog-media-check {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background 0.12s, border-color 0.12s;
}
#workLogsFormModal .wlog-media-check svg { width: 13px; height: 13px; stroke: #fff; opacity: 0; transition: opacity 0.12s; }
#workLogsFormModal .wlog-media-thumb.selected { border-color: var(--primary); box-shadow: var(--glow-cyan); }
#workLogsFormModal .wlog-media-thumb.selected .wlog-media-check { background: var(--primary); border-color: var(--primary); }
#workLogsFormModal .wlog-media-thumb.selected .wlog-media-check svg { opacity: 1; }
#workLogsFormModal .wlog-media-grid.wlog-media-selecting .wlog-media-thumb:not(.selected) { opacity: 0.55; }
/* A thumb whose file is missing or undecodable says so instead of rendering as an empty tile. */
#workLogsFormModal .wlog-media-thumb.wlog-media-missing {
    border-color: var(--danger);
    background: repeating-linear-gradient(45deg, rgba(255, 46, 99, 0.10) 0 6px, transparent 6px 12px);
}
#workLogsFormModal .wlog-media-thumb.wlog-media-missing img,
#workLogsFormModal .wlog-media-thumb.wlog-media-missing video { opacity: 0.10; }
#workLogsFormModal .wlog-media-thumb.wlog-media-missing::after {
    content: 'Missing';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 8px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}
/* Delete bar shown while selecting media — takes over the card's pinned bottom slot, so the
   Delete/Cancel buttons land exactly where Save/Cancel were (same metrics as .form-actions). */
#workLogsFormModal .wlog-media-select-bar {
    flex-shrink: 0;
    align-items: center;
    gap: 8px;
    padding: 12px 0;
    border-top: 1px solid var(--border);
}
#workLogsFormModal .modal-content.wlog-media-selecting .form-actions { display: none; }
#workLogsFormModal .wlog-media-select-count {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--danger-text);
}
#workLogsFormModal .wlog-media-select-actions { display: flex; flex: 1; gap: 12px; }
#workLogsFormModal .wlog-media-select-actions .btn { flex: 1; }
#workLogsFormModal .wlog-media-empty {
    font-size: 12px;
    color: var(--text-muted, var(--text-secondary));
    text-align: center;
    padding: 10px 8px;
    border: 1px dashed var(--border);
    border-radius: var(--btn-radius);
}
#workLogsFormModal .wlog-media-grid:not(:empty) + .wlog-media-empty { display: none; }

/* Work logs listed on a client's card — same row language as the detail rows above them. */
.client-worklogs { margin-top: 4px; }
.client-worklogs-list { display: flex; flex-direction: column; }
.client-worklog {
    display: flex;
    gap: 10px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
}
.client-worklog:last-child { border-bottom: none; }
.client-worklog-when {
    flex-shrink: 0;
    min-width: 92px;
    font-size: 12px;
    font-weight: 600;
    color: var(--primary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.client-worklog-note {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}
.client-worklog-media { flex-shrink: 0; font-size: 11px; color: var(--text-muted, var(--text-secondary)); align-self: center; }

/* ---------------------------------------------------------------------------
   TAP TARGETS: the year steppers in the month/year picker.
   A 32px box was too small to hit with a thumb — the same complaint that came
   back for the hire-date calendar's month arrows, and the picker is now reached
   from there too. This lives at the END of the file on purpose: an earlier rule
   in this sheet sets these buttons back to 32px, so a same-specificity rule in
   the calendar block loses on order. Tighter selector AND last position, no
   !important needed.
   --------------------------------------------------------------------------- */
.mp-year-row .mp-step-btn {
    width: 44px;
    height: 44px;
    padding: 0;
    font-size: 20px;   /* the hit area is 44px; this makes the glyph itself read at that size */
}

/* The uploaded original, in the gallery: its own colour so it never reads as another "Current". */
.avatar-gallery-badge-source {
    background: var(--bg); color: var(--text-secondary); border: 1px solid var(--border);
}

/* The admin-only prompt box: a quiet note under the field, not a second label. */
.form-note {
    margin: 6px 0 0; font-size: 12px; line-height: 1.5; color: var(--text-secondary);
}

/* Gallery segments: Generated first, then Originals. The heading spans the full grid width. */
.avatar-gallery-heading {
    grid-column: 1 / -1; margin: 10px 0 2px; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--text-secondary);
}

/* ================= BOTTOM BARS STICK (every card) =================
   The header has been sticky since the slim-bar pass; the matching behaviour for the bars is that they
   stay on the floor of the card while its content scrolls, instead of riding up with it. `margin-top:auto`
   keeps the short-content case identical to before, and the shared translucent bar surface is untouched. */
.form-actions, .modal-actions, .confirm-actions, .task-detail-actions, .map-actions,
.inv-crop-actions, .inv-cat-form-actions, .month-picker-actions, .day-detail-actions,
.notes-media-select-actions, .wlog-media-select-actions, .gallery-select-actions {
    position: sticky;
    bottom: 0;
    margin-top: auto;
    z-index: 6;            /* above the card's scrolling content, below its sticky header (10) */
}
/* A bar in its own scroller already sits at the floor — sticky is a no-op there, but the map card pins
   with position:absolute and must not be double-pinned. */
#mapModal .map-actions { position: absolute; }
/* The Close in a bar is a normal themed button, not the × that used to sit in the header. */
.modal-dismiss { flex: 1; }

/* ================= FILTER BARS: the themed date control =================
   The filters use a narrower control scale than the forms, so the date button matches it rather than
   inheriting the form-sized one. Same widget, same theme, no OS picker anywhere. */
.task-filters .date-wrap, .filters-dropdown .date-wrap { width: 100%; }
.task-filters .date-btn, .filters-dropdown .date-btn { min-height: 38px; font-size: 13px; padding: 0 10px; }
.task-filters .date-value, .filters-dropdown .date-value { font-size: 13px; }

/* ================= ONE TYPE SCALE =================
   Interactive text is one size everywhere (14px, the form-field size). The filter FIELDS used 13px, so a
   filter menu and the form it filtered disagreed side by side. Height is held at the filter bar's 38px by
   trimming the padding instead — vary padding, not type size. Bars keep their documented compact scale,
   because their 63px geometry depends on it. */
.filter-select, .filter-input { font-size: 14px; padding: 8px 12px; }
.filters-menu .filter-select, .filters-menu .filter-input { height: 38px; }

/* The filter menus now carry the SAME translucent surface as every bottom bar (they were opaque, which
   read as a different material). */
.filters-menu {
    background: color-mix(in srgb, var(--bg-base) 78%, transparent);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
}

/* ---------------------------------------------------------------------------
   Themed tooltips
   A native title= renders in an OS-styled bubble — the last unthemed UI element
   here — and on touch it either does nothing or shows an OS popup. [data-tip]
   draws the app's own bubble instead; the paired aria-label keeps the name for
   screen readers, since an icon button has no text of its own.
   --------------------------------------------------------------------------- */
[data-tip] { position: relative; }
[data-tip]:hover::after,
[data-tip]:focus-visible::after {
    content: attr(data-tip);
    position: absolute;
    left: 50%;
    bottom: calc(100% + 8px);
    transform: translateX(-50%);
    background: color-mix(in srgb, var(--bg-card) 94%, transparent);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 6px 10px;
    font-size: 12px;
    line-height: 1.3;
    color: var(--text-primary);
    white-space: nowrap;
    max-width: 70vw;
    z-index: 9550;
    pointer-events: none;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

