/* ============================================
   PuNoObservation -- application styles

   The neutral ramp below is green-cast rather than the stock blue-grey
   slate, so the greys sit with the forest brand instead of against it.
   Type runs on the 12/14/16/18/20/24/32 scale and spacing on an 8px
   rhythm -- reach for a token rather than inventing a value.
   ============================================ */

/* ============================================================
   THEME TOKENS

   Every colour in this file resolves through the semantic tokens below,
   so a theme is a palette swap and nothing else. Light is the default;
   dark only applies when <html data-theme="dark"> is set.

   The dark palette follows a few rules worth stating, because breaking
   them is what makes a dark theme feel like a terminal:

     - The ground is near-black, never #000. Pure black is the floor, and
       a floor leaves nothing for a surface to sit below. Starting at
       #0e1512 gives every layer above it somewhere to go.
     - Elevation is lightness, not shadow. A black shadow on a dark ground
       is invisible, so each surface that sits higher is a step lighter:
       ground, card, band, fill.
     - Text never runs at full white. Pure white on a dark surface
       vibrates and burns after a paragraph. Primary sits at 87%,
       secondary at 60%, disabled at 38%.
     - Brand and status colours are lightened and desaturated. A saturated
       hue on a dark surface glows past its own edge and outshouts the
       content it is supposed to label.
     - Edges are white at low alpha rather than a fixed grey, so a hairline
       still reads as the surface underneath it gets lighter.
   ============================================================ */

:root {
    /* surfaces, ground upward */
    --surface-0: #f7faf8;
    --surface-1: #ffffff;
    --surface-2: #f7faf8;
    --surface-3: #eef4f0;

    /* edges */
    --line: #e2eae5;
    --line-strong: #c9d6ce;

    /* text, strongest to faintest */
    --text-heading: #12211a;
    --text-strong: #2f4239;
    --text-body: #41544a;
    --text-2: #5f6f66;
    --text-3: #8d9c94;

    /* brand */
    --brand-solid: #15803d;
    --brand-text: #15803d;
    --brand-deep: #14532d;
    --brand-deep-text: #14532d;
    --brand-deepest: #10241a;

    /* status: solid = a filled control, text = a label, bg/line = a tint */
    --ok-solid: #16a34a;
    --ok-text: #166534;
    --ok-bg: #f0fdf4;
    --ok-line: #cfe7d8;

    --danger-solid: #dc2626;
    --danger-text: #dc2626;
    --danger-strong: #b91c1c;
    --danger-bg: #fef2f2;
    --danger-line: #fecaca;

    --warn-solid: #f59e0b;
    --warn-text: #b45309;
    --warn-strong: #92400e;
    --warn-bg: #fffbeb;
    --warn-line: #fef3c7;

    --info-solid: #0ea5e9;
    --info-text: #0369a1;
    --info-strong: #075985;
    --info-bg: #f0f9ff;

    --violet-solid: #7c3aed;
    --violet-text: #6d28d9;
    --violet-bg: #ede9fe;

    --blue-solid: #2563eb;
    --blue-text: #1d4ed8;
    --blue-bg: #eff6ff;
    --blue-line: #d4e0f6;

    /* the sticky topbar sits over scrolling content */
    --surface-glass: rgba(255, 255, 255, 0.85);

    /* cast by the frozen table columns over what scrolls under them */
    --pin-shade: rgba(18, 33, 26, 0.16);

    /* photographs need no help on a light ground */
    --photo-dim: none;

    color-scheme: light;
}

:root[data-theme="dark"] {
    --surface-0: #0e1512;
    --surface-1: #161f1b;
    --surface-2: #1c2723;
    --surface-3: #24312c;

    --line: rgba(255, 255, 255, 0.09);
    --line-strong: rgba(255, 255, 255, 0.18);

    --text-heading: rgba(255, 255, 255, 0.92);
    --text-strong: rgba(255, 255, 255, 0.87);
    --text-body: rgba(255, 255, 255, 0.78);
    --text-2: rgba(255, 255, 255, 0.6);
    --text-3: rgba(255, 255, 255, 0.38);

    --brand-solid: #1f8a4c;
    --brand-text: #74d69b;
    --brand-deep: #17402a;
    --brand-deep-text: #9fe0ba;
    --brand-deepest: #0f2019;

    --ok-solid: #2f9e5c;
    --ok-text: #7ddba2;
    --ok-bg: rgba(45, 167, 94, 0.15);
    --ok-line: rgba(125, 219, 162, 0.28);

    --danger-solid: #c34a44;
    --danger-text: #f0a49e;
    --danger-strong: #f6bbb6;
    --danger-bg: rgba(220, 70, 60, 0.16);
    --danger-line: rgba(240, 164, 158, 0.3);

    --warn-solid: #c48a33;
    --warn-text: #e9bd77;
    --warn-strong: #f2d3a0;
    --warn-bg: rgba(226, 159, 54, 0.15);
    --warn-line: rgba(233, 189, 119, 0.28);

    --info-solid: #2b87b8;
    --info-text: #8ecbe8;
    --info-strong: #b3ddf2;
    --info-bg: rgba(43, 135, 184, 0.16);

    --violet-solid: #7a5cc4;
    --violet-text: #c0a9f0;
    --violet-bg: rgba(140, 110, 220, 0.18);

    --blue-solid: #3b6fd4;
    --blue-text: #9dbdf5;
    --blue-bg: rgba(60, 110, 210, 0.18);
    --blue-line: rgba(157, 189, 245, 0.28);

    --surface-glass: rgba(22, 31, 27, 0.85);

    /* A shadow tinted like the light theme's is invisible here -- it is
       nearly the colour of the surface it falls on. On a dark ground the
       shade has to be much closer to black, and much stronger, before it
       reads as depth at all. */
    --pin-shade: rgba(0, 0, 0, 0.55);

    /* a bright photograph on a dim page pulls every eye */
    --photo-dim: brightness(0.9);

    color-scheme: dark;
}

.app-body {
    /* The neutral ramp is kept as an alias of the semantic tokens, because
       rules written before the theme work still reach for --n-*. */
    --n-0: var(--surface-1);
    --n-50: var(--surface-2);
    --n-100: var(--surface-3);
    --n-200: var(--line);
    --n-300: var(--line-strong);
    --n-400: var(--text-3);
    --n-500: var(--text-2);
    --n-600: var(--text-body);
    --n-700: var(--text-strong);
    --n-800: var(--text-strong);
    --n-900: var(--text-heading);

    /* brand */
    --brand-900: var(--brand-deepest);
    --brand-700: var(--brand-deep);
    --brand-600: var(--brand-solid);
    --brand-500: var(--ok-solid);

    /* status */
    --ok: var(--ok-text);
    --warn: var(--warn-text);
    --danger: var(--danger-text);
    --info: var(--info-text);

    /* type scale */
    --fs-xs: 0.75rem;
    --fs-sm: 0.875rem;
    --fs-base: 1rem;
    --fs-lg: 1.125rem;
    --fs-xl: 1.25rem;
    --fs-2xl: 1.5rem;

    /* spacing rhythm */
    --sp-1: 0.5rem;
    --sp-2: 1rem;
    --sp-3: 1.5rem;
    --sp-4: 2rem;
    --sp-6: 3rem;

    /* radius */
    --r-sm: 0.5rem;
    --r-md: 0.75rem;
    --r-lg: 1rem;

    /* elevation: two layers read as depth, one flat blur reads as a smudge */
    --sh-sm: 0 1px 2px rgba(18, 33, 26, 0.04), 0 1px 3px rgba(18, 33, 26, 0.05);
    --sh-md: 0 1px 2px rgba(18, 33, 26, 0.04), 0 6px 16px rgba(18, 33, 26, 0.06);
    --sh-lg: 0 2px 4px rgba(18, 33, 26, 0.04), 0 12px 32px rgba(18, 33, 26, 0.09);

    --ease: cubic-bezier(0.4, 0, 0.2, 1);

    /* Fixed rather than derived from padding, because the map sizes itself
       against it and anything sticky sits below it. */
    --topbar-h: 4rem;   /* 64px at the full size; follows the desktop scale below */

    background-color: var(--surface-0);
    color: var(--text-body);
}

/* Shadows carry almost nothing on a dark ground, so they deepen only
   enough to separate a floating panel from what is behind it -- the
   surface lightness is what actually says "this is raised". */
:root[data-theme="dark"] .app-body {
    --sh-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --sh-md: 0 2px 6px rgba(0, 0, 0, 0.42), 0 8px 20px rgba(0, 0, 0, 0.34);
    --sh-lg: 0 4px 10px rgba(0, 0, 0, 0.45), 0 16px 40px rgba(0, 0, 0, 0.42);
}

/* Photographs and screenshots, dimmed rather than filtered into mush. The
   sidebar logo is a flat mark and reads better left alone. */
:root[data-theme="dark"] .app-body img:not(.logo-icon) {
    filter: var(--photo-dim);
}

.card {
    background: var(--surface-1);
    padding: 1.5rem;
    border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}

.btn-primary {
    display: inline-block;
    padding: 0.6rem 1.2rem;
    background-color: var(--brand-solid);
    color: white;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 600;
}






/* ============================================
   APP SHELL + SIDEBAR (Dashboard layout)
   ============================================ */

* {
    box-sizing: border-box;
}

/* Always reserve room for the scrollbar. Without this, a short page (log in)
   has no scrollbar and a tall one (sign up) does, so the viewport width jumps
   between them and right-anchored art appears to shift. */
html {
    scrollbar-gutter: stable;
    /* --text-scale is the Text size setting on My Account (set in
       Base.html before first paint). 1 when nothing is chosen. */
    font-size: calc(100% * var(--text-scale, 1));
}

/* Desktop scale. Everything in the interface is sized in rem, so the root
   size sets the scale of the whole thing: at 80% the app looks the way it
   did at 80% browser zoom, which reads better on laptop screens. It is a
   percentage, so someone who has raised the browser's own default font
   size still gets their larger text. Phones keep the full size. */
@media (min-width: 861px) {
    html {
        font-size: calc(80% * var(--text-scale, 1));
    }
}

/* Inside the app the window itself never scrolls (.main-area does), so
   the reserved gutter was only an empty strip down the right edge. */
html:has(> body.app-body) {
    scrollbar-gutter: auto;
    overflow: hidden;
}

.app-body {
    margin: 0;
    font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
}

/* The shell fills the window and the main column does the scrolling, so
   the sidebar stays put and anything sticky inside the content has a real
   scrollport to stick to. */
.app-shell {
    display: flex;
    height: 100vh;
}

/* Sidebar */
.sidebar {
    /* Its own text scale, deliberately not the page's. The sidebar ground
       is the same near-black green in light and dark, so its foreground
       must be too -- reading --line or --text-3 here made the labels almost
       vanish the moment the page went dark. */
    --side-text: rgba(255, 255, 255, 0.8);
    --side-text-strong: #ffffff;
    --side-text-dim: rgba(255, 255, 255, 0.52);
    --side-accent: #7fd8a8;

    /* the saved width when there is one (set in Base.html before paint) */
    width: var(--sidebar-init-w, 272px);
    flex-shrink: 0;
    transition: width 0.18s ease;
    background-color: var(--brand-deepest);
    color: var(--side-text);
    display: flex;
    flex-direction: column;
    padding: 1.2rem 1rem;
    overflow: hidden;
    white-space: nowrap;
    height: 100vh;
    position: sticky;
    top: 0;
}

/* Set for one frame while the saved state is restored on page load, so
   the rail does not animate into place. */
.sidebar.resizing {
    transition: none;
}

/* Icon rail: labels hide, icons stay centered, links still work as normal <a> tags.
   .sidebar-init-collapsed is the same state set before first paint, so a
   collapsed rail is already narrow on the first frame. */
.sidebar-init-collapsed .sidebar .logo-text,
.sidebar-init-collapsed .sidebar .role-pill-text,
.sidebar-init-collapsed .sidebar .nav-link-text,
.sidebar-init-collapsed .sidebar .sign-out-text,
.sidebar.collapsed .logo-text,
.sidebar.collapsed .role-pill-text,
.sidebar.collapsed .nav-link-text,
.sidebar.collapsed .sign-out-text {
    display: none;
}

.sidebar-init-collapsed .sidebar .sidebar-logo,
.sidebar-init-collapsed .sidebar .role-pill,
.sidebar-init-collapsed .sidebar .nav-link,
.sidebar-init-collapsed .sidebar .sign-out,
.sidebar.collapsed .sidebar-logo,
.sidebar.collapsed .role-pill,
.sidebar.collapsed .nav-link,
.sidebar.collapsed .sign-out {
    justify-content: center;
}

/* Logo and the collapse button share the top row, which at the default 240px
   rail is tight -- hence the trimmed gaps and the slightly smaller wordmark
   below. */
.sidebar-header {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.5rem 0.15rem 1rem 0.4rem;
}

/* The logo keeps its own padding when it stands alone, so it is cleared here
   and owned by the header instead. */
.sidebar-header .sidebar-logo {
    flex: 1;
    min-width: 0;
    padding: 0;
    gap: 0.7rem;
    font-size: 1.15rem;
    font-weight: 800;
    letter-spacing: -0.02em;
}

/* The logo is the brand's one moment in the app, so it is set large with a
   soft glow behind it. The glow is a background on the image itself, so
   the markup stays a single <img>. */
.sidebar-header .logo-icon {
    width: 3.25rem;
    height: 3.25rem;
    padding: 0.2rem;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(127, 216, 168, 0.3) 0%, rgba(127, 216, 168, 0.1) 45%, transparent 70%);
    filter: drop-shadow(0 0 6px rgba(127, 216, 168, 0.25));
    flex-shrink: 0;
}

/* The 64px icon rail only has 32px inside its padding. */
.sidebar-init-collapsed .sidebar .sidebar-header .logo-icon,
.sidebar.collapsed .sidebar-header .logo-icon {
    width: 2.25rem;
    height: 2.25rem;
    padding: 0.15rem;
}

/* Without this the wordmark refuses to shrink, shoves the button past the
   rail's edge, and the sidebar's overflow:hidden clips it out of reach. */
.sidebar-header .logo-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Round collapse button on the rail's right edge, half on the sidebar and
   half on the page. Fixed, so the sidebar's overflow:hidden cannot clip it;
   --sidebar-w (set by the rail logic) keeps it on the edge while the width
   animates. */
.sidebar-toggle {
    position: fixed;
    top: 2.25rem;
    left: calc(var(--sidebar-w, var(--sidebar-init-w, 272px)) - 1.05rem);
    z-index: 1100;
    display: grid;
    place-items: center;
    width: 2.1rem;
    height: 2.1rem;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 50%;
    background-color: var(--brand-deepest);
    color: rgba(255, 255, 255, 0.85);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    cursor: pointer;
    transition: left 0.18s ease, background-color 0.15s ease,
                border-color 0.15s ease, color 0.15s ease;
}

.sidebar-toggle:hover {
    background-color: #1c3a2b;
    border-color: rgba(255, 255, 255, 0.45);
    color: #ffffff;
}

.sidebar-toggle:focus-visible {
    outline: 2px solid var(--side-accent, #7fd8a8);
    outline-offset: 2px;
}

/* no slide while the saved state is restored on page load */
.sidebar.resizing ~ .sidebar-toggle {
    transition: none;
}

.sidebar-toggle-icon {
    width: 1.1rem;
    height: 1.1rem;
}

/* On the narrow rail there is no room beside the logo mark, so the button
   drops underneath it and both centre. */
.sidebar.collapsed .sidebar-header {
    flex-direction: column;
    gap: 0.55rem;
}

.sidebar-logo {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.4rem 1rem 0.4rem;
    font-weight: 700;
    font-size: 1.125rem;
    color: #ffffff;
    text-decoration: none;
    cursor: pointer;
}

.logo-icon {
    width: 1.8rem;
    height: 1.8rem;
    object-fit: contain;
}

.role-pill {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    background-color: rgba(255,255,255,0.06);
    padding: 0.7rem 0.9rem;
    border-radius: 12px;
    font-size: 1rem;
    color: var(--side-accent);
    margin-bottom: 1rem;
}

.role-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--ok-solid);
    display: inline-block;
    flex-shrink: 0;
}

.role-avatar {
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 50%;
    background-color: var(--ok-solid);
    color: #ffffff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1.05rem;
    flex-shrink: 0;
}

/* The 64px icon rail has 32px inside its padding: the pill loses its side
   padding and the avatar steps back down to fit. */
.sidebar-init-collapsed .sidebar .role-pill,
.sidebar.collapsed .role-pill {
    padding-left: 0;
    padding-right: 0;
}

.sidebar-init-collapsed .sidebar .role-avatar,
.sidebar.collapsed .role-avatar {
    width: 2rem;
    height: 2rem;
    font-size: 0.875rem;
}

/* Holds the avatar so the online dot can sit on its lower-right edge. */
.role-avatar-wrap {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
}

/* Green dot with a ring in the pill's colour, so it reads as cut into the
   photo. A slow soft pulse says "live" without pulling the eye. */
.online-dot {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 50%;
    background-color: #22c55e;
    box-shadow: 0 0 0 2.5px #1d2d26;
}

.online-dot::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-color: #22c55e;
    animation: online-pulse 2.4s ease-out infinite;
}

@keyframes online-pulse {
    0%   { transform: scale(1);   opacity: 0.55; }
    70%  { transform: scale(2.2); opacity: 0; }
    100% { transform: scale(2.2); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .online-dot::after { animation: none; opacity: 0; }
}

.sidebar-init-collapsed .sidebar .online-dot,
.sidebar.collapsed .online-dot {
    width: 0.6rem;
    height: 0.6rem;
    box-shadow: 0 0 0 2px #1d2d26;
}

/* Photographs fill their circle instead of stretching. Applies wherever
   partials/_avatar.html renders an image. */
.role-avatar-img,
.avatar-img {
    object-fit: cover;
}

.role-pill-text {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

.role-pill-username {
    color: #ffffff;
    font-weight: 600;
    font-size: 1rem;
}

.role-pill-role {
    color: var(--side-accent);
    font-size: 0.875rem;
}

/* overflow-x is pinned to hidden because setting only overflow-y makes the
   other axis compute to auto -- any nav label wider than the rail (or the
   rail mid-drag) then raises a horizontal scrollbar across the sidebar. */
.sidebar-nav {
    /* Logo and user box sit together above this one divider. */
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    padding-top: 1rem;
    /* Only the link list scrolls. min-height: 0 lets it shrink below its
       content, so Sign out underneath always stays on screen. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}

/* Group heading above the role-specific links. Set in small caps so it reads
   as a label for the links under it rather than as another link. */
.nav-section-title {
    margin: 1.1rem 0 0.4rem;
    padding: 0 0.7rem;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--side-text-dim);
}

/* No room for the label on the narrow rail, so it becomes the divider it was
   already acting as. Height and margins are kept so the icons above and below
   stay grouped instead of running together. */
.sidebar.collapsed .nav-section-title {
    height: 1px;
    margin: 0.7rem 0.5rem;
    padding: 0;
    overflow: hidden;
    text-indent: -999em;
    background-color: rgba(255, 255, 255, 0.1);
}

.nav-link {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    min-height: 3rem;
    padding: 0.7rem 0.9rem;
    margin-bottom: 0.25rem;
    border-radius: 12px;
    color: var(--side-text);
    text-decoration: none;
    font-size: 1rem;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.nav-icon {
    width: 1.375rem;
    height: 1.375rem;
    flex-shrink: 0;
    transition: stroke-width 0.15s ease;
}

.nav-link-text {
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-link:hover {
    background-color: rgba(255,255,255,0.06);
}

/* Active state is green text + a bolder icon on the same subtle background the
   hover state uses, so the current page reads without a solid filled pill. */
.nav-link.active,
.nav-link.active:hover {
    background-color: rgba(255, 255, 255, 0.06);
    color: var(--brand-text);
    font-weight: 600;
}

/* Thicker stroke is what makes the icon itself look bold. */
.nav-link.active .nav-icon {
    stroke-width: 2.75;
}

.sign-out {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    min-height: 3rem;
    padding: 0.7rem 0.9rem;
    color: var(--side-text);
    text-decoration: none;
    font-size: 1rem;
    border-top: 1px solid rgba(255,255,255,0.08);
    margin-top: 0.6rem;
    padding-top: 1.1rem;
    flex-shrink: 0;
}

.sign-out svg {
    width: 1.375rem;
    height: 1.375rem;
    flex-shrink: 0;
}

/* On the 64px icon rail there is only 32px inside the padding, so the
   bigger rows drop their side padding and just centre the icon. */
.sidebar-init-collapsed .sidebar .nav-link,
.sidebar-init-collapsed .sidebar .sign-out,
.sidebar.collapsed .nav-link,
.sidebar.collapsed .sign-out {
    padding-left: 0;
    padding-right: 0;
}

.sign-out:hover {
    color: var(--danger-text);
}

/* Main content area next to sidebar */
.main-area {
    flex: 1;
    min-width: 0;
    height: 100vh;
    overflow-y: auto;
    /* Scrolls by wheel, trackpad, touch and keyboard as usual, but draws no
       scrollbar: the bar was a stray line down the right edge, and with no
       bar there is no gutter to reserve, so content runs edge to edge and
       never shifts when a page grows past the window. */
    scrollbar-width: none;
    /* This is the page ground, the surface everything else sits above. In
       light it is the same value as --surface-2; in dark it must be the
       darkest layer or the cards have nothing to rise from. */
    background-color: var(--surface-0);
    overflow-x: hidden;
}

.main-area::-webkit-scrollbar {
    display: none;
}

.page-content {
    padding: 2rem;
    max-width: 1200px;
}

/* Wide table pages (Sites, Recommendations, User Management, Requests) --
   the 1200px cap above leaves their tables cramped and scrolling sooner
   than the screen actually needs. */
.page-content-wide {
    max-width: none;
}

.page-header {
    margin-bottom: 1.5rem;
}
/* Browsers give h2 its own default top/bottom margin -- without resetting it,
   headers using .page-header sit noticeably lower than the .um-header pages
   (User Management, Sites, Recommendation, Requests), which already zero it. */
.page-header h2 {
    margin: 0;
}

.page-header-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 1rem;
}

/* Dashboard clock: a small analog face beside the digital reading. The
   face is what makes it read as a clock at a glance; the digits give the
   exact time. */
.dashboard-clock {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background-color: var(--surface-1);
    /* A hairline as well as the shadow: a shadow alone carries nothing on a
       dark ground, so the chip had no edge there at all. */
    border: 1px solid var(--line);
    padding: 0.7rem 1.1rem 0.7rem 0.8rem;
    border-radius: 14px;
    box-shadow: var(--sh-sm);
}

.clock-face {
    flex: none;
    width: 3rem;
    height: 3rem;
    overflow: visible;
}

.clock-face-ring {
    fill: var(--surface-2);
    stroke: var(--line-strong);
    stroke-width: 1;
}

.clock-tick {
    stroke: var(--text-3);
    stroke-width: 1;
    stroke-linecap: round;
}

.clock-tick.is-major {
    stroke: var(--text-2);
    stroke-width: 1.6;
}

.clock-hand-h,
.clock-hand-m {
    stroke: var(--text-heading);
    stroke-linecap: round;
}

.clock-hand-h { stroke-width: 2.6; }
.clock-hand-m { stroke-width: 1.8; }

.clock-hand-s {
    stroke: var(--ok-solid);
    stroke-width: 1;
    stroke-linecap: round;
}

.clock-pin {
    fill: var(--ok-solid);
}

.clock-text {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

.clock-time {
    display: flex;
    align-items: baseline;
    /* Every digit the same width, so a ticking clock never nudges what
       sits beside it. */
    font-variant-numeric: tabular-nums;
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--text-heading);
}

/* The seconds change every tick, so they sit a size down and a shade back:
   the hour and minute are the reading, the seconds only say it is live. */
.clock-sec {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--text-2);
}

.clock-meridiem {
    margin-left: 0.35rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--text-2);
}

.clock-date {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.8125rem;
    color: var(--text-2);
    white-space: nowrap;
}

/* The clock is pinned to Manila whatever the machine is set to, so it says
   which zone it is showing. The dot says the time is live. */
.clock-zone {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    background-color: var(--ok-bg);
    border: 1px solid var(--ok-line);
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--ok-text);
    cursor: help;
}

.clock-live {
    position: relative;
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    background-color: var(--ok-solid);
}

.clock-live::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-color: var(--ok-solid);
    animation: clock-live-pulse 2s var(--ease) infinite;
}

@keyframes clock-live-pulse {
    0%   { transform: scale(1);   opacity: 0.6; }
    70%  { transform: scale(2.6); opacity: 0; }
    100% { transform: scale(2.6); opacity: 0; }
}

@media (max-width: 560px) {
    .dashboard-clock {
        width: 100%;
    }
}

/* ============================================
   NOTIFICATIONS (global topbar bell + full page)
   ============================================ */

.global-topbar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.6rem;
    height: var(--topbar-h);
    padding: 0 1.5rem;
    background-color: var(--surface-1);
    border-bottom: 1px solid var(--line);
}

.notif-bell-wrap {
    position: relative;
}

/* Page title on the left of the top bar (block topbar_title). The auto
   margin pushes the theme, bell and account controls to the right. */
.topbar-title {
    margin-right: auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-heading);
}

/* --- Account link ------------------------------------------------------
   Moved out of the sidebar: it belongs with the other personal controls,
   and the sidebar is for places in the product rather than for the person
   using it. */
.topbar-account {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
    border-radius: 50%;
    /* A ring rather than a fill, so the control reads as a target without
       putting a second shape around the face. */
    border: 2px solid transparent;
    text-decoration: none;
    transition: border-color 160ms var(--ease);
}

.topbar-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Same size as the avatar in the sidebar's user box. */
    width: 2.6rem;
    height: 2.6rem;
    flex: none;
    border-radius: 50%;
    background-color: var(--ok-solid);
    color: #ffffff;
    font-weight: 700;
    font-size: 1.05rem;
}

.topbar-account:hover {
    border-color: var(--line-strong);
}

.topbar-account.active {
    border-color: var(--brand-text);
}

/* --- Theme toggle -------------------------------------------------------
   A sliding switch. Thumb on the right = light, on the left = dark.
   The button is the tap area; the track inside it is what you see.
   While dragging, JS moves the thumb directly (no transition); on release
   the inline transform is cleared and the thumb springs to its side. */
.theme-toggle {
    --switch-w: 4.25rem;
    --switch-h: 2.125rem;
    --thumb: 1.75rem;
    --thumb-gap: 0.1875rem;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem 0.25rem;
    border: none;
    border-radius: 999px;
    background: none;
    cursor: pointer;
    touch-action: none;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
}

.theme-toggle:focus-visible {
    outline: none;
}

.theme-toggle:focus-visible .theme-switch-track {
    box-shadow: 0 0 0 2px var(--surface-1), 0 0 0 4px var(--brand-text);
}

.theme-switch-track {
    position: relative;
    display: block;
    width: var(--switch-w);
    height: var(--switch-h);
    border-radius: 999px;
    border: 1px solid var(--line);
    background-color: var(--surface-3);
    transition: background-color 260ms var(--ease), border-color 260ms var(--ease);
}

.theme-toggle:hover .theme-switch-track {
    border-color: var(--line-strong);
}

/* Faint icons on the track mark which side is which. */
.theme-track-icon {
    position: absolute;
    top: 50%;
    width: 0.95rem;
    height: 0.95rem;
    margin-top: -0.475rem;
    color: var(--text-3);
    transition: opacity 200ms var(--ease);
}

.theme-track-moon { left: 0.45rem; }
.theme-track-sun { right: 0.45rem; }

.theme-switch-thumb {
    position: absolute;
    top: calc(var(--thumb-gap) - 1px);
    left: calc(var(--thumb-gap) - 1px);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--thumb);
    height: var(--thumb);
    border-radius: 999px;
    background-color: #d97706;
    color: #ffffff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.22), 0 1px 1px rgba(0, 0, 0, 0.1);
    /* Light is the default, so the thumb starts on the right. */
    transform: translateX(calc(var(--switch-w) - var(--thumb) - var(--thumb-gap) * 2));
    transition:
        transform 340ms cubic-bezier(0.34, 1.36, 0.64, 1),
        width 160ms var(--ease),
        background-color 260ms var(--ease),
        color 260ms var(--ease);
}

.theme-switch-thumb svg {
    position: absolute;
    width: 1.1rem;
    height: 1.1rem;
    transition: opacity 220ms var(--ease), transform 340ms var(--ease);
}

/* Light: sun on the thumb, moon hidden. */
.theme-switch-thumb .theme-icon-moon {
    opacity: 0;
    transform: rotate(-90deg) scale(0.5);
}

.theme-toggle .theme-track-sun { opacity: 0; }

/* Dark: thumb slides left and turns into a moon. */
:root[data-theme="dark"] .theme-switch-track {
    background-color: var(--surface-3);
}

:root[data-theme="dark"] .theme-switch-thumb {
    transform: translateX(0);
    background-color: #3b4a8c;
    color: #ffffff;
}

:root[data-theme="dark"] .theme-switch-thumb .theme-icon-sun {
    opacity: 0;
    transform: rotate(90deg) scale(0.5);
}

:root[data-theme="dark"] .theme-switch-thumb .theme-icon-moon {
    opacity: 1;
    transform: none;
}

:root[data-theme="dark"] .theme-toggle .theme-track-sun { opacity: 1; }
:root[data-theme="dark"] .theme-toggle .theme-track-moon { opacity: 0; }

/* Pressing stretches the thumb a little, like a real switch. In light it
   grows to the left so it stays tucked against its right edge. */
.theme-toggle.is-pressed .theme-switch-thumb {
    width: calc(var(--thumb) + 0.3rem);
    margin-left: -0.3rem;
}

:root[data-theme="dark"] .theme-toggle.is-pressed .theme-switch-thumb {
    margin-left: 0;
}

/* Following the finger: no easing, or the thumb would lag behind it. */
.theme-toggle.is-dragging .theme-switch-thumb {
    transition: width 160ms var(--ease), background-color 260ms var(--ease);
}

@media (prefers-reduced-motion: reduce) {
    .theme-switch-thumb,
    .theme-switch-thumb svg,
    .theme-switch-track {
        transition-duration: 1ms;
    }
}

.notif-bell-btn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--text-body);
    cursor: pointer;
    padding: 0.5rem;
    border-radius: 8px;
}

.notif-bell-btn svg {
    /* Same size as the sidebar's nav icons. */
    width: 1.375rem;
    height: 1.375rem;
}

.notif-bell-btn:hover {
    background-color: var(--surface-3);
}

.notif-badge {
    position: absolute;
    top: -2px;
    right: -2px;
    background-color: var(--danger-solid);
    color: #ffffff;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1;
    padding: 0.2rem 0.35rem;
    border-radius: 999px;
    min-width: 16px;
    text-align: center;
}

.notif-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    width: 380px;
    max-width: calc(100vw - 2rem);
    max-height: min(560px, calc(100vh - var(--topbar-h) - 2rem));
    background-color: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 16px 40px rgba(15, 23, 42, 0.14);
    overflow: hidden;
    z-index: 2000;
    flex-direction: column;
    transform-origin: top right;
}

.notif-dropdown.active {
    display: flex;
    animation: notif-pop 160ms var(--ease);
}

@keyframes notif-pop {
    from { opacity: 0; transform: translateY(-4px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

.notif-dropdown-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 1rem 1rem 0.75rem 1.1rem;
}

.notif-dropdown-header-title {
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text-heading);
}

.notif-head-actions {
    display: flex;
    align-items: center;
    gap: 0.15rem;
}

.notif-head-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border: none;
    border-radius: 8px;
    background: none;
    color: var(--text-2);
    cursor: pointer;
    transition: background-color 160ms var(--ease), color 160ms var(--ease);
}

.notif-head-btn svg {
    width: 1.05rem;
    height: 1.05rem;
}

.notif-head-btn:hover:not(:disabled) {
    background-color: var(--surface-3);
    color: var(--text-heading);
}

.notif-head-btn:disabled {
    opacity: 0.4;
    cursor: default;
}

/* "Mark all as read" text link on the full Notifications page */
.notif-mark-all {
    background: none;
    border: none;
    padding: 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--brand-text);
    cursor: pointer;
}

.notif-mark-all:hover {
    color: var(--brand-text);
    text-decoration: underline;
}

/* Segmented filter: a tray with the active choice raised out of it.
   Spans the dropdown, the three choices sharing it equally. */
.notif-seg {
    display: flex;
    gap: 0.15rem;
    margin: 0 1.1rem 0.5rem;
    padding: 0.2rem;
    background-color: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 10px;
}

.notif-seg-btn {
    flex: 1 1 0;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    white-space: nowrap;
    padding: 0.35rem 0.7rem;
    border: 1px solid transparent;
    border-radius: 7px;
    background: none;
    color: var(--text-2);
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 160ms var(--ease), color 160ms var(--ease);
}

.notif-seg-btn:hover {
    color: var(--text-heading);
}

.notif-seg-btn.active {
    background-color: var(--surface-1);
    border-color: var(--line);
    color: var(--text-heading);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
}

.notif-seg-count {
    min-width: 1.15rem;
    padding: 0 0.3rem;
    border-radius: 5px;
    background-color: var(--surface-3);
    color: var(--text-body);
    font-size: 0.7rem;
    line-height: 1.15rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.notif-seg-count[hidden] {
    display: none;
}

#notif-dropdown-list {
    overflow-y: auto;
    flex: 1;
    min-height: 0;
    border-top: 1px dashed var(--line-strong);
}

#notif-dropdown-list .placeholder-text {
    margin: 0;
    padding: 2.5rem 1rem;
    text-align: center;
}

/* ---- shared notification row: dropdown items + full-page list ---- */
.notif-item,
.notif-page-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.9rem 1.1rem;
    border-bottom: 1px dashed var(--line-strong);
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.12s ease;
}

.notif-item:last-child,
.notif-page-item:last-child {
    border-bottom: none;
}

.notif-item:hover,
.notif-page-item:hover {
    background-color: var(--surface-2);
}

.notif-item:focus-visible {
    outline: 2px solid var(--brand-text);
    outline-offset: -2px;
}

.notif-icon {
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--surface-3);
    color: var(--text-2);
}

.notif-icon svg {
    width: 1.05rem;
    height: 1.05rem;
}

.notif-icon.type-update  { background-color: var(--blue-bg); color: var(--blue-text); }
.notif-icon.type-report  { background-color: var(--warn-bg); color: var(--warn-text); }

/* Notifications that name a specific person (e.g. "X requested...") get
   that person's initial instead of a generic icon, same avatar language
   as the sidebar and User Management. */
.notif-icon.avatar {
    background-color: var(--brand-solid);
    color: #ffffff;
    font-weight: 700;
    font-size: 0.875rem;
}

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

/* Message on the left; the relative time and unread dot hold the top
   right corner so they line up down the list. */
.notif-item-top {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
}

.notif-item-top .notif-message {
    flex: 1;
    min-width: 0;
}

.notif-message {
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.45;
    color: var(--text-body);
}

.notif-message strong {
    color: var(--text-heading);
    font-weight: 700;
}

.notif-item.unread .notif-message,
.notif-page-item.unread .notif-message {
    color: var(--text-heading);
}

.notif-ago {
    flex-shrink: 0;
    font-size: 0.72rem;
    color: var(--text-2);
    white-space: nowrap;
    line-height: 1.6;
}

.notif-time {
    display: block;
    font-size: 0.75rem;
    color: var(--text-2);
    margin-top: 0.15rem;
}

.notif-dot {
    flex-shrink: 0;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background-color: var(--ok-solid);
    margin-top: 0.4rem;
    visibility: hidden;
}

.notif-item.unread .notif-dot,
.notif-page-item.unread .notif-dot {
    visibility: visible;
}

/* A button-shaped cue inside the row link; the row itself is the target. */
.notif-item-action {
    display: inline-flex;
    margin-top: 0.6rem;
    padding: 0.35rem 0.75rem;
    border-radius: 7px;
    background-color: #12211a;
    color: #ffffff;
    font-size: 0.78rem;
    font-weight: 600;
}

.notif-item:hover .notif-item-action {
    background-color: #2f4239;
}

:root[data-theme="dark"] .notif-item-action {
    background-color: var(--surface-3);
    border: 1px solid var(--line);
    color: var(--text-heading);
}

/* Full notifications page */
.notif-page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

.notif-page-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

/* ---- toolbar: select all + bulk actions on the left, filters right ---- */
.notif-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    min-height: 40px;
    margin: 0 0 1rem;
}

.notif-bulk {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

/* Lines up with the row checkboxes below it (card padding + row padding) */
.notif-select-all {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.9rem 0.5rem calc(1rem + 1px);
    border: 1px solid var(--line);
    border-radius: 8px;
    background-color: var(--surface-1);
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-strong);
    cursor: pointer;
    user-select: none;
    font-variant-numeric: tabular-nums;
}

.notif-select-all[hidden] {
    display: none;
}

.notif-select-all input {
    width: 1rem;
    height: 1rem;
    margin: 0;
    accent-color: var(--brand-text);
    cursor: pointer;
}

.notif-bulk-actions {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    animation: notif-pop 140ms var(--ease);
}

.notif-bulk-actions[hidden] {
    display: none;
}

.notif-bulk-btn,
.notif-delete-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    min-height: 38px;
    padding: 0.45rem 0.85rem;
    border-radius: 8px;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}

.notif-bulk-btn {
    background-color: var(--surface-1);
    color: var(--text-strong);
    border: 1px solid var(--line);
}

.notif-bulk-btn:hover:not(:disabled) {
    background-color: var(--surface-3);
    border-color: var(--line-strong);
    color: var(--text-heading);
}

.notif-delete-btn {
    background-color: var(--surface-1);
    color: var(--danger-text);
    border: 1px solid var(--danger-line);
}

.notif-delete-btn:hover:not(:disabled) {
    background-color: var(--danger-solid);
    border-color: var(--danger-solid);
    color: #ffffff;
}

.notif-bulk-btn:disabled,
.notif-delete-btn:disabled {
    opacity: 0.55;
    cursor: progress;
}

.notif-bulk-btn svg,
.notif-delete-btn svg {
    width: 1rem;
    height: 1rem;
}

.notif-filters {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0;
}

.notif-filters-icon {
    width: 1rem;
    height: 1rem;
    color: var(--text-2);
}

.notif-filter-select {
    appearance: none;
    min-height: 38px;
    background-color: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.45rem 2rem 0.45rem 0.85rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-strong);
    cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, var(--text-2) 50%), linear-gradient(135deg, var(--text-2) 50%, transparent 50%);
    background-position: calc(100% - 14px) center, calc(100% - 9px) center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}

.notif-filter-select:hover {
    border-color: var(--line-strong);
}

/* A filter that is narrowing the list is marked, so an empty list never
   looks like missing data. */
.notif-filter-select.is-set {
    border-color: var(--ok-line);
    background-color: var(--ok-bg);
    color: var(--ok-text);
}

.notif-filter-clear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 38px;
    padding: 0 0.85rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background-color: var(--surface-1);
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text-strong);
    text-decoration: none;
    cursor: pointer;
    transition: background-color 150ms ease, border-color 150ms ease;
}

.notif-filter-clear[hidden],
.notif-filter-note[hidden],
.notif-empty[hidden],
.notif-row[hidden],
.notif-day-group[hidden] {
    display: none;
}

.notif-filter-note {
    margin-right: 0.25rem;
    font-size: 0.8rem;
    color: var(--text-2);
    font-variant-numeric: tabular-nums;
}

.notif-filter-clear:hover {
    background-color: var(--surface-3);
    border-color: var(--line-strong);
    color: var(--text-heading);
}

/* ---- day-grouped list ---- */
.notif-list-card {
    padding: 1.25rem 1rem;
}

.notif-day-group {
    margin-bottom: 1.25rem;
}

.notif-day-group:last-child {
    margin-bottom: 0;
}

.notif-day-heading {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-2);
    margin: 0 0 0.4rem;
    padding: 0 1rem;
}

.notif-row {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.8rem 1rem;
    border-radius: 10px;
    transition: background-color 120ms ease;
}

.notif-row + .notif-row {
    margin-top: 2px;
}

.notif-row:hover {
    background-color: var(--surface-2);
}

/* Unread rows carry a faint brand tint as well as bold text, so the state
   reads at a glance and not only from the eye icon. */
.notif-row.unread {
    background-color: var(--ok-bg);
}

.notif-row.unread:hover {
    background-color: var(--surface-2);
}

.notif-row.selected,
.notif-row.selected:hover {
    background-color: var(--surface-3);
}

.notif-row-checkbox {
    flex-shrink: 0;
    width: 1rem;
    height: 1rem;
    margin: 0;
    accent-color: var(--brand-text);
    cursor: pointer;
}

.notif-row .notif-icon {
    margin: 0;
}

.notif-row-body {
    flex: 1;
    min-width: 0;
}

.notif-row-body p {
    margin: 0;
    font-size: 0.875rem;
    color: var(--text-body);
    line-height: 1.4;
}

.notif-row.unread .notif-row-body p {
    color: var(--text-heading);
    font-weight: 600;
}

.notif-row-time {
    display: block;
    font-size: 0.75rem;
    color: var(--text-2);
    margin-top: 0.15rem;
}

.notif-row.unread .notif-row-state {
    color: var(--ok-text);
    font-weight: 600;
}

.notif-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 3rem 1rem;
    text-align: center;
    color: var(--text-2);
}

.notif-empty svg {
    width: 1.75rem;
    height: 1.75rem;
    opacity: 0.6;
}

.notif-empty p {
    margin: 0;
    font-size: 0.9rem;
}

@media (max-width: 640px) {
    .notif-toolbar {
        align-items: stretch;
        flex-direction: column;
    }
    .notif-row {
        flex-wrap: wrap;
        padding: 0.75rem;
    }
    .notif-row-body {
        flex-basis: calc(100% - 5rem);
    }
}

/* Read toggle: closed eye while unread (in brand green, since it still
   wants attention), open eye once seen. Only one icon is ever shown. */
.notif-row-eye,
.notif-row-trash {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid transparent;
    border-radius: 8px;
    background: none;
    color: var(--text-2);
    cursor: pointer;
    transition: background-color 160ms var(--ease), color 160ms var(--ease),
                border-color 160ms var(--ease);
}

.notif-row-eye svg,
.notif-row-trash svg {
    width: 1.15rem;
    height: 1.15rem;
}

.notif-row-eye:hover {
    background-color: var(--surface-3);
    color: var(--text-heading);
}

.notif-row-eye:disabled,
.notif-row-trash:disabled {
    opacity: 0.5;
    cursor: progress;
}

/* Sits tight against the eye so the two read as one icon group */
.notif-row-trash {
    margin-left: -0.6rem;
}

.notif-row-trash:hover {
    background-color: var(--danger-bg);
    border-color: var(--danger-line);
    color: var(--danger-text);
}

.notif-row.removing {
    opacity: 0;
    transform: translateX(8px);
    transition: opacity 180ms var(--ease), transform 180ms var(--ease);
}

.notif-row-eye .eye-closed { display: none; }
.notif-row.unread .notif-row-eye { color: var(--brand-text); }
.notif-row.unread .notif-row-eye:hover {
    background-color: var(--ok-bg);
    border-color: var(--ok-line);
}
.notif-row.unread .notif-row-eye .eye-open { display: none; }
.notif-row.unread .notif-row-eye .eye-closed { display: block; }

.notif-row-details {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    background-color: #12211a;
    color: #ffffff;
    border: 1px solid transparent;
    border-radius: 6px;
    padding: 0.45rem 1rem;
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.notif-row-details:hover {
    background-color: #2f4239;
}

/* Inverting a near-black button means going light, not going white: a
   raised surface with a hairline reads as a control without glaring. */
:root[data-theme="dark"] .notif-row-details {
    background-color: var(--surface-3);
    border-color: var(--line);
    color: var(--text-heading);
}

:root[data-theme="dark"] .notif-row-details:hover {
    background-color: var(--line-strong);
}


/*LOGIN PAGE*/

body.login-page {
    /* ---- auth design tokens ---- */
    --auth-ink: #16241f;
    --auth-body: #40514c;
    --auth-muted: #5b6b66;
    --auth-line: #d8e0dc;
    --auth-green: #0d7e5a;
    --auth-green-dark: #0a6d4e;
    --auth-green-deep: #0d3632;
    --auth-danger: #b42318;
    --auth-ok: #146c43;
    --auth-ease: cubic-bezier(0.4, 0, 0.2, 1);

    margin: 0;
    min-height: 100vh;
    background: var(--surface-2);
    /* Scoped to .login-page, so the rest of the app keeps its system font. */
    font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    color: var(--auth-body);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body.login-page :focus-visible {
    outline: 2px solid var(--auth-green);
    outline-offset: 2px;
    border-radius: 6px;
}

.login-shell {
    display: flex;
    min-height: 100vh;
    width: 100%;
    background: var(--surface-2);
}

/* ---------- Brand panel (left) ---------- */
.brand-panel {
    position: relative;
    flex: 1.12;
    /* Inset from the viewport so it reads as a card rather than a column.
       overflow:hidden above the radius is what clips the mesh to the
       rounded corners. */
    margin: 1rem;
    border-radius: 1.75rem;
    padding: 2.5rem 3.25rem;
    /* Base tone the mesh blobs sit on. */
    background: #0b2b23;
    color: var(--surface-2);
    overflow: hidden;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

/* ---- Animated mesh gradient ----
   Four large blurred blobs drifting on their own clocks. Because the cycle
   lengths (5/7/9/11s) are mutually prime, the pattern does not visibly
   repeat. Only transform is animated, so the work stays on the
   compositor instead of forcing layout each frame. */
.brand-mesh {
    position: absolute;
    /* Oversized so a blob never shows a hard edge as it drifts. */
    inset: -25%;
    z-index: 0;
    filter: blur(60px) saturate(1.25);
    pointer-events: none;
}

.brand-mesh .blob {
    position: absolute;
    display: block;
    width: 60%;
    aspect-ratio: 1;
    border-radius: 50%;
    will-change: transform;
}

/* Greens are kept dark on purpose: white body copy sits on top of this, and
   a brighter green (#16a34a is only 3.3:1 against white) would fail. */
.brand-mesh .blob-1 {
    top: -8%;
    left: -10%;
    background: #17795a;
    animation: mesh-drift-1 5s ease-in-out infinite alternate;
}

.brand-mesh .blob-2 {
    bottom: -12%;
    left: 5%;
    background: #0f5f46;
    animation: mesh-drift-2 11s ease-in-out infinite alternate;
}

.brand-mesh .blob-3 {
    top: 18%;
    right: -14%;
    background: #1a8a63;
    animation: mesh-drift-3 7s ease-in-out infinite alternate;
}

.brand-mesh .blob-4 {
    bottom: -6%;
    right: -8%;
    background: #0d4f52;
    animation: mesh-drift-4 9s ease-in-out infinite alternate;
}

@keyframes mesh-drift-1 {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(18%, 14%, 0) scale(1.25); }
}

@keyframes mesh-drift-2 {
    from { transform: translate3d(0, 0, 0) scale(1.15); }
    to   { transform: translate3d(22%, -18%, 0) scale(0.9); }
}

@keyframes mesh-drift-3 {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(-20%, 20%, 0) scale(1.3); }
}

@keyframes mesh-drift-4 {
    from { transform: translate3d(0, 0, 0) scale(1.1); }
    to   { transform: translate3d(-16%, -22%, 0) scale(0.95); }
}

/* Scrim over the mesh. Without it a blob drifting under the paragraph drops
   that text to about 3.8:1; this holds the whole panel above 6:1 while the
   colour still reads through. */
.brand-panel::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(160deg, rgba(6, 26, 20, 0.3), rgba(6, 26, 20, 0.5));
    pointer-events: none;
}

.brand-stack {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    max-width: 34rem;
    text-align: center;
}

/* Movement in the corner of the eye is exactly what the OS setting is for. */
@media (prefers-reduced-motion: reduce) {
    .brand-mesh .blob {
        animation: none;
    }
}

.brand-logo {
    display: block;
    width: 7.5rem;
    height: 7.5rem;
    object-fit: contain;
    filter: drop-shadow(0 10px 28px rgba(0, 0, 0, 0.35));
}

/* Small tracked label over the name. */
.brand-eyebrow {
    margin: 1.75rem 0 0;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: #8fe3ad;
}

.brand-heading {
    margin: 0.6rem 0 0;
    font-size: clamp(2.25rem, 3.4vw, 3.25rem);
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -0.035em;
    color: #ffffff;
}

.brand-lead {
    max-width: 40ch;
    margin: 1.25rem 0 0;
    font-size: 1rem;
    line-height: 1.7;
    color: rgba(236, 244, 238, 0.78);
}

/* the key phrases pick up the label colour */
.brand-lead strong {
    font-weight: 600;
    color: #a9f0c3;
}

/* ---------- Auth panel (right) ---------- */
.auth-panel {
    flex: 0.9;
    background: var(--surface-2);
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 2.5rem 2rem;
}

/* Desktop: log in and sign up are the same fixed frame, exactly one
   screen tall. The sign-up form is longer than the screen; before, it
   stretched the whole page, so the brand panel grew and its logo slid
   down, and a page scrollbar narrowed everything -- a visible jump when
   switching between the two. Now only the form column scrolls (and
   centres the form when it fits), and the brand panel never moves. */
@media (min-width: 981px) {
    html:has(> body.login-page) {
        overflow: hidden;
        scrollbar-gutter: auto;
    }

    .login-shell {
        height: 100vh;
        height: 100dvh;
        min-height: 0;
    }

    .auth-panel {
        overflow-y: auto;
        align-items: safe center;
        scrollbar-width: none;
    }

    .auth-panel::-webkit-scrollbar {
        display: none;
    }
}

.auth-wrapper {
    width: min(100%, 30rem);
    background: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 1rem;
    padding: 2.5rem;
    box-shadow: 0 1px 2px rgba(15, 42, 32, 0.04), 0 12px 32px rgba(15, 42, 32, 0.07);
}

.auth-wrapper h2 {
    margin: 0;
    font-size: clamp(1.875rem, 2.5vw, 2.25rem);
    line-height: 1.15;
    letter-spacing: -0.035em;
    font-weight: 800;
    color: var(--auth-ink);
}

.auth-subtitle {
    margin: 0.5rem 0 0;
    font-size: 1rem;
    color: var(--auth-muted);
}

.register-page .auth-wrapper {
    width: min(100%, 32rem);
}

/* ---------- Alerts ---------- */
.form-alert,
.flash-message {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin: 1.25rem 0 0;
    padding: 0.75rem 0.875rem;
    border-radius: 0.625rem;
    font-size: 0.875rem;
    line-height: 1.5;
    background: var(--danger-bg);
    color: var(--auth-danger);
    border: 1px solid var(--danger-line);
}

.form-alert svg,
.flash-message svg {
    width: 1.125rem;
    height: 1.125rem;
    flex-shrink: 0;
    margin-top: 0.0625rem;
}

.flash-message.warning {
    background: var(--warn-bg);
    color: var(--warn-strong);
    border-color: var(--warn-line, #fcd34d);
}

.flash-message.success {
    background: var(--ok-bg);
    color: var(--auth-ok);
    border-color: var(--ok-line);
}

/* ---------- Form ---------- */
.login-form {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    margin-top: 1.75rem;
}

.field-group {
    display: flex;
    flex-direction: column;
    gap: 0.4375rem;
}

.field-group label {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--auth-ink);
}

.field-hint {
    margin: 0;
    font-size: 0.875rem;
    color: var(--auth-muted);
}

.input-wrap {
    position: relative;
    display: flex;
    align-items: center;
}

/* One rule for both forms -- login and register previously had separate
   input styles that had drifted apart in padding, radius and border. */
.login-form input,
.login-form select {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--auth-line);
    border-radius: 0.625rem;
    background: var(--surface-1);
    padding: 0.75rem 0.875rem;
    min-height: 44px;
    font-family: inherit;
    font-size: 1rem;
    color: var(--auth-ink);
    outline: none;
    transition: border-color 160ms var(--auth-ease),
                box-shadow 160ms var(--auth-ease);
}

/* Room for the show/hide button. */
.input-wrap input {
    padding-right: 3rem;
}

.login-form input::placeholder {
    color: var(--text-2);
}

.login-form input:hover,
.login-form select:hover {
    border-color: var(--line-strong);
}

.login-form input:focus,
.login-form select:focus {
    border-color: var(--auth-green);
    box-shadow: 0 0 0 3px rgba(13, 126, 90, 0.12);
}

.login-form input.has-error,
.login-form input[aria-invalid="true"] {
    border-color: var(--auth-danger);
}

.login-form input.has-error:focus {
    box-shadow: 0 0 0 3px rgba(180, 35, 24, 0.12);
}

.login-form select {
    appearance: none;
    padding-right: 2.5rem;
    cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, var(--auth-muted) 50%), linear-gradient(135deg, var(--auth-muted) 50%, transparent 50%);
    background-position: calc(100% - 18px) center, calc(100% - 13px) center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}

/* Register: a location list waiting on the one above it. */
.login-form select:disabled {
    cursor: not-allowed;
    opacity: 0.6;
    border-color: var(--auth-line);
}

/* ---------- Show / hide password ---------- */
.field-toggle {
    position: absolute;
    right: 0.375rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: none;
    border-radius: 0.5rem;
    background: none;
    color: var(--auth-muted);
    cursor: pointer;
    transition: color 160ms var(--auth-ease), background-color 160ms var(--auth-ease);
}

.field-toggle:hover {
    color: var(--auth-ink);
    background: var(--surface-2);
}

.field-toggle svg {
    width: 1.125rem;
    height: 1.125rem;
}

/* Swap the icon to match what the button will do next. */
.field-toggle .icon-eye-off,
.field-toggle[aria-pressed="true"] .icon-eye {
    display: none;
}

.field-toggle[aria-pressed="true"] .icon-eye-off {
    display: block;
}

.password-error {
    display: flex;
    margin: 0;
    color: var(--auth-danger);
    font-size: 0.875rem;
    font-weight: 600;
}

.password-error[hidden] {
    display: none;
}

/* ---------- Buttons and links ---------- */
.primary-btn {
    border: none;
    border-radius: 0.625rem;
    background: var(--auth-green);
    color: #fff;
    min-height: 46px;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
    margin-top: 0.25rem;
    /* The old rule had a large coloured glow and no hover, active or
       disabled state at all. */
    transition: background-color 160ms var(--auth-ease),
                box-shadow 160ms var(--auth-ease);
}

.primary-btn:hover {
    background: var(--auth-green-dark);
    box-shadow: 0 4px 12px rgba(13, 126, 90, 0.24);
}

.primary-btn:active {
    background: #085b41;
    box-shadow: none;
}

.primary-btn:disabled {
    background: var(--surface-3);
    color: var(--text-3);
    cursor: not-allowed;
    box-shadow: none;
}

.register-link {
    margin: 1.5rem 0 0;
    text-align: center;
    color: var(--auth-muted);
    font-size: 1rem;
}

.register-link a {
    color: var(--auth-green);
    text-decoration: none;
    font-weight: 700;
}

.register-link a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* ---------- Responsive ---------- */
@media (max-width: 980px) {
    .login-shell {
        flex-direction: column;
    }

    .brand-panel {
        min-height: auto;
        margin: 0.75rem;
        border-radius: 1.25rem;
        padding: 2.5rem 1.75rem;
    }

    .brand-logo {
        width: 5.5rem;
        height: 5.5rem;
    }

    .auth-panel {
        padding: 2rem 1.75rem 3rem;
    }
}

@media (max-width: 580px) {
    /* On a phone the panel is a short header -- mark, label and name --
       so the form starts on the first screen. The line about the
       platform drops out. Stacked, the two panels no longer share the
       screen height: the header takes what it needs, the form the rest. */
    .brand-panel {
        flex: none;
        margin: 0.5rem;
        border-radius: 1rem;
        padding: 1.4rem 1.15rem 1.5rem;
        padding-top: calc(1.4rem + env(safe-area-inset-top));
    }

    .brand-logo {
        width: 3.75rem;
        height: 3.75rem;
    }

    .brand-eyebrow {
        margin-top: 0.9rem;
        font-size: 0.65rem;
        letter-spacing: 0.2em;
    }

    .brand-heading {
        margin-top: 0.35rem;
        font-size: 1.6rem;
    }

    .brand-lead {
        display: none;
    }

    .auth-panel {
        flex: 1 0 auto;
        align-items: flex-start;
        padding: 0.75rem 0.5rem calc(2rem + env(safe-area-inset-bottom));
    }

    .auth-wrapper {
        padding: 1.75rem 1.25rem;
    }
}

/* Phones draw overlay scrollbars, so the gutter the page reserves for a
   desktop scrollbar was only an empty strip down the right edge. */
@media (max-width: 860px) {
    html {
        scrollbar-gutter: auto;
    }
}

/* generic muted placeholder text -- used across Notifications, ReferenceDataset,
   and anywhere showing an empty-state or hint message */
.placeholder-text {
    color: var(--text-2);
    font-size: 0.875rem;
}

/* Minimal Bootstrap-named utility classes -- this project doesn't load
   Bootstrap, but several templates use its class names (fw-semibold,
   d-flex, text-end, ...) expecting them to work. Defined here so they
   actually do, instead of silently no-op-ing. */
.d-flex { display: flex; }
.align-items-center { align-items: center; }
.justify-content-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.text-end { text-align: right; }
.text-secondary { color: var(--text-2); }
.text-success { color: var(--brand-text); }
.text-danger { color: var(--danger-text); }
.fw-semibold { font-weight: 600; }
.fw-bold { font-weight: 700; }
.fst-italic { font-style: italic; }
.small { font-size: 0.875rem; }
.text-uppercase { text-transform: uppercase; }
.w-100 { width: 100%; }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 0.35rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 1rem; }
.mb-4 { margin-bottom: 1.5rem; }
.mt-1 { margin-top: 0.35rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 1rem; }
.mt-4 { margin-top: 1.5rem; }

/* ============================================
   REFERENCE DATASET (excel import page)
   ============================================ */

/* .um-table-card has no padding of its own (it's built for tables) --
   this page's cards hold form controls and text instead, so add it back. */
.ref-card {
    padding: 1.4rem 1.5rem;
    margin-bottom: 1.5rem;
}

.ref-card h3 {
    margin: 0 0 0.4rem;
    font-size: 1.125rem;
    color: var(--text-heading);
}

.upload-form {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 1rem;
    flex-wrap: wrap;
}

.upload-file-input {
    border: 1px dashed var(--line-strong);
    border-radius: 8px;
    padding: 0.6rem 0.9rem;
    font-size: 0.875rem;
    background-color: var(--surface-2);
}

.import-info-list {
    margin: 0.6rem 0 0 1.1rem;
    padding: 0;
    font-size: 0.875rem;
    color: var(--text-strong);
    line-height: 1.7;
}

#Table
.table-scroll {
    display: block;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table-scroll table {
    width: max-content;      /* table width fit columns */
    min-width: 100%;         /* still fill if small */
}

.table-responsive table {
    min-width: 1200px;
}
/* Table Card Wrapper */
.table-card {
    background: var(--surface-1);
    border-radius: 10px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
    border: 1px solid var(--line);
    overflow: hidden;
    margin-bottom: 2rem;
}

/* Scroll Container */
.table-scroll-container {
    max-height: 600px;
    overflow-x: auto;
    overflow-y: auto;
}

.barangay-heading {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.barangay-heading svg {
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
}

/* Custom Table Base */
.custom-table {
    width: max-content;
    min-width: 100%;
    margin-bottom: 0;
    white-space: nowrap;
    font-size: 0.875rem;
    border-collapse: separate;
    border-spacing: 0;
}

.custom-table th, 
.custom-table td {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--surface-2);
    border-right: 1px solid var(--surface-3);
}

/* Sticky Header */
.custom-table thead th {
    position: sticky;
    top: 0;
    background-color: var(--surface-2);
    color: var(--text-body);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.5px;
    z-index: 2;
    border-bottom: 2px solid var(--line-strong);
}

/* Site Card */
.site-card {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.7rem 0.8rem;
  margin-bottom: 0.6rem;
}
.site-card-title {
  font-weight: 700;
  color: var(--brand-deep-text);
  margin-bottom: 0.3rem;
}
/* Pin First Column (Site Name) on Horizontal Scroll */
.custom-table th:first-child,
.custom-table td:first-child {
    position: sticky;
    left: 0;
    background-color: var(--surface-1);
    z-index: 1;
    font-weight: 600;
    color: var(--text-strong);
    box-shadow: 2px 0 5px rgba(0, 0, 0, 0.04);
}

.custom-table thead th:first-child {
    z-index: 3; /* Keeps top-left corner above both sticky row & sticky column */
    background-color: var(--surface-2);
}

/* Hover States */
.custom-table tbody tr:hover td {
    background-color: var(--surface-3);
}
.custom-table tbody tr:hover td:first-child {
    background-color: var(--surface-3);
}

/* Typography & Badges */
.num-cell {
    text-align: right;
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
}
/* Status reads faster as a pill with a leading dot than as coloured text:
   the dot carries the state at a glance, the tinted ground gives the label
   an edge so it stops floating in the row. */
.badge-zone-production,
.badge-zone-protection {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    border: 1px solid transparent;
    font-weight: 600;
    font-size: 0.75rem;
    line-height: 1.3;
    white-space: nowrap;
}

.badge-zone-production::before,
.badge-zone-protection::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: currentColor;
    flex: none;
}

.badge-zone-production {
    color: var(--brand-text);
    background-color: var(--ok-bg);
    border-color: var(--ok-line);
}

.badge-zone-protection {
    color: var(--blue-text);
    background-color: var(--blue-bg);
    border-color: var(--blue-line);
}

/* ============================================
   USER MANAGEMENT (admin)
   ============================================ */

.um-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 0.35rem;
}

.um-title-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.um-title-row h2 {
    margin: 0;
    font-size: 1.5rem;
    color: var(--text-heading);
}

.um-count-pill {
    background-color: var(--surface-3);
    color: var(--text-body);
    font-size: 0.75rem;
    font-weight: 700;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
}

.um-subtitle {
    margin: 0.3rem 0 1.4rem;
    color: var(--text-2);
    font-size: 0.875rem;
}

.um-btn-add {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background-color: var(--brand-solid);
    color: #ffffff;
    border: none;
    border-radius: 8px;
    padding: 0.6rem 1.1rem;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    flex-shrink: 0;
}

.um-btn-add:hover {
    background-color: var(--ok-solid);
}

.um-btn-add:disabled,
.um-btn-add:disabled:hover {
    background-color: var(--surface-3);
    color: var(--text-3);
    cursor: not-allowed;
}

/* These classes also go on <a> tags (Export to Excel, New monitoring
   report, row links), which carry the browser's link underline. */
a.um-btn-add,
a.um-btn-ghost,
a.um-btn-row,
a.um-btn-add:hover,
a.um-btn-ghost:hover,
a.um-btn-row:hover {
    text-decoration: none;
}

.um-btn-add svg {
    width: 0.95rem;
    height: 0.95rem;
}

/* --- toolbar: role filter + search --- */
.um-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: 1.1rem;
}

/* Search + Clear stay grouped on the right, so switching the role filter
   label (which changes the filter button's own width) never shifts them --
   space-between keeps this group pinned to the far edge regardless of how
   wide the filter button on the left becomes. */
.um-search-group {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.um-filter-wrap {
    position: relative;
}

.um-filter-btn {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    min-width: 10.5rem;
    background-color: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.5rem 0.85rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-strong);
    cursor: pointer;
}

.um-filter-btn:hover {
    border-color: var(--line-strong);
}

.um-filter-btn-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.um-filter-btn svg:first-child {
    width: 0.95rem;
    height: 0.95rem;
    color: var(--text-2);
}

.um-filter-btn svg:last-child {
    width: 0.75rem;
    height: 0.75rem;
    color: var(--text-2);
}

.um-filter-menu {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 11rem;
    max-height: 18rem;
    overflow-y: auto;
    background-color: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 8px;
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.1);
    padding: 0.35rem;
    z-index: 20;
}

.um-filter-menu.open {
    display: block;
}

.um-filter-menu button,
.um-filter-menu a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    padding: 0.5rem 0.6rem;
    font-size: 0.875rem;
    color: var(--text-strong);
    text-decoration: none;
    border-radius: 6px;
    cursor: pointer;
    box-sizing: border-box;
}

.um-filter-menu button:hover,
.um-filter-menu a:hover {
    background-color: var(--surface-3);
}

.um-filter-menu button.active,
.um-filter-menu a.active {
    color: var(--brand-text);
    font-weight: 700;
}

.um-search-wrap {
    position: relative;
    flex: 1;
    min-width: 16rem;
    max-width: 26rem;
}

.um-search-wrap svg {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    width: 0.9rem;
    height: 0.9rem;
    color: var(--text-2);
    pointer-events: none;
}

.um-search-input {
    width: 100%;
    padding: 0.5rem 0.85rem 0.5rem 2.2rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 0.875rem;
    color: var(--text-strong);
    background-color: var(--surface-1);
    outline: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.um-search-input::placeholder {
    color: var(--text-2);
}

.um-search-input:focus {
    border-color: var(--brand-text);
    box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.12);
}

.um-btn-ghost {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background-color: var(--surface-1);
    color: var(--text-body);
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
}

/* An inline SVG with no width or height falls back to the replaced-element
   default of 300x150, which is what turned this button into a tile with its
   label underneath. Matches the icon size .um-btn-add uses. */
.um-btn-ghost svg {
    width: 0.95rem;
    height: 0.95rem;
    flex-shrink: 0;
}

/* An author `display` beats the browser's own [hidden] rule, so the two
   "Clear" buttons that ship with the hidden attribute need it restored. */
.um-btn-ghost[hidden] {
    display: none;
}

.um-btn-ghost:hover {
    border-color: var(--line-strong);
    color: var(--text-heading);
}

/* --- table --- */
.um-table-card {
    background-color: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 12px;
    overflow: hidden;
}

.um-table {
    width: 100%;
    /* Fixed layout locks column widths to the <th> widths below instead of
       recalculating them from whatever rows are currently visible -- with
       the default "auto" layout, filtering rows in/out changes which text
       is visible and the whole table reflows column-by-column. */
    table-layout: fixed;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.um-table col.col-name { width: 30%; }
.um-table col.col-email { width: 36%; }
.um-table col.col-role { width: 22%; }
.um-table col.col-actions { width: 12%; }

.um-table td,
.um-table th {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.um-table th {
    text-align: left;
    padding: 0.75rem 1.1rem;
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--text-2);
    background-color: var(--surface-2);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

/* .um-table th sets text-align:left with more weight than .text-end, so a
   numeric column's header sat at the left while its figures sat right. */
.um-table th.text-end,
.um-table td.text-end,
.um-table td.num-cell {
    text-align: right;
}

.um-th-flex {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.um-th-flex svg {
    width: 0.85rem;
    height: 0.85rem;
    color: var(--text-2);
}

.um-table td {
    padding: 0.7rem 1.1rem;
    border-bottom: 1px solid var(--surface-3);
    color: var(--text-body);
    vertical-align: middle;
}

.um-table tbody tr:last-child td {
    border-bottom: none;
}

.um-table tbody tr:hover {
    background-color: var(--surface-2);
}

.um-name-cell {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    min-width: 0;
}

.um-avatar {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    font-weight: 700;
    font-size: 0.75rem;
    flex-shrink: 0;
    background-color: var(--brand-solid);
}

.um-avatar.av-admin { background-color: var(--violet-solid); }
.um-avatar.av-officer { background-color: var(--blue-solid); }

.um-name {
    font-weight: 600;
    color: var(--text-heading);
    overflow: hidden;
    text-overflow: ellipsis;
}

.um-you {
    margin-left: 0.35rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--brand-text);
    background-color: var(--ok-bg);
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
}

.um-email-cell {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

.um-email-text {
    overflow: hidden;
    text-overflow: ellipsis;
}

.um-email-cell svg {
    width: 0.9rem;
    height: 0.9rem;
    color: var(--text-2);
    flex-shrink: 0;
}

.um-role {
    display: inline-block;
    padding: 0.25rem 0.65rem;
    border-radius: 6px;
    font-size: 0.75rem;
    font-weight: 600;
    background-color: var(--surface-3);
    color: var(--text-body);
}

.um-role.role-admin { background-color: var(--violet-bg); color: var(--violet-text); }
.um-role.role-officer { background-color: var(--blue-bg); color: var(--blue-text); }
.um-role.role-user { background-color: var(--ok-bg); color: var(--brand-text); }

.um-actions-cell {
    text-align: right;
}

/* --- Identity cell -----------------------------------------------------
   Name and email are one fact about one person, so they share a cell: the
   name reads first, the address sits under it as the quieter line. That
   buys back a whole column and stops the eye crossing the table twice to
   tie a person to their address. */
#um-table .um-identity {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-width: 0;
    line-height: 1.35;
}

#um-table .um-identity-sub {
    font-size: 0.8125rem;
    color: var(--n-500);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#um-table th {
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* --- Status --- */
.um-status {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    border: 1px solid transparent;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
}
.um-status svg { width: 0.875rem; height: 0.875rem; flex: none; }

.um-status-active {
    color: var(--brand-text);
    background-color: var(--ok-bg);
    border-color: var(--ok-line);
}
.um-status-off {
    color: var(--n-500);
    background-color: var(--n-100);
    border-color: var(--n-200);
}

/* --- Row actions: one button, one menu ---------------------------------
   Two labelled buttons per row put the same two words on every line and
   made the table shout. The actions live behind a single control now, and
   the row goes quiet. */
.um-menu-wrap {
    position: relative;
    display: inline-flex;
}

.um-kebab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border: 1px solid transparent;
    border-radius: 8px;
    background: none;
    color: var(--n-500);
    cursor: pointer;
    transition: background-color 140ms var(--ease), color 140ms var(--ease),
                border-color 140ms var(--ease), box-shadow 140ms var(--ease);
}
.um-kebab svg { width: 1.05rem; height: 1.05rem; }
.um-kebab:hover { background-color: var(--n-100); color: var(--n-800); }
.um-kebab[aria-expanded="true"] {
    background-color: var(--surface-1);
    border-color: var(--n-200);
    color: var(--n-800);
    box-shadow: var(--sh-sm);
}

/* Fixed, and positioned by the script: .um-table-card clips its children,
   so a menu laid out inside the row would be cut off at the card edge. */
.um-row-menu {
    position: fixed;
    display: none;
    z-index: 2100;
    min-width: 13rem;
    padding: 0.3rem;
    background-color: var(--surface-1);
    border: 1px solid var(--n-200);
    border-radius: 10px;
    box-shadow: var(--sh-lg);
}
.um-row-menu.open { display: block; }

.um-row-menu button {
    display: block;
    width: 100%;
    padding: 0.5rem 0.65rem;
    border: none;
    border-radius: 7px;
    background: none;
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    text-align: left;
    color: var(--n-700);
    cursor: pointer;
    transition: background-color 120ms var(--ease), color 120ms var(--ease);
}
.um-row-menu button:hover { background-color: var(--n-100); color: var(--n-900); }

.um-row-menu button.is-danger { color: var(--danger); }
.um-row-menu button.is-danger:hover { background-color: var(--danger-bg); color: var(--danger-strong); }

.um-row-menu-sep {
    height: 1px;
    margin: 0.3rem 0.15rem;
    background-color: var(--n-200);
}

.um-row-menu-note {
    display: block;
    padding: 0.5rem 0.65rem;
    font-size: 0.8125rem;
    color: var(--n-400);
}

.um-btn-icon-danger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    background: none;
    border: none;
    border-radius: 6px;
    color: var(--text-2);
    cursor: pointer;
}

.um-btn-icon-danger svg {
    width: 1rem;
    height: 1rem;
}

.um-btn-icon-danger:hover {
    background-color: var(--danger-bg);
    color: var(--danger-text);
}

.um-btn-icon-danger:disabled {
    color: var(--line);
    cursor: not-allowed;
}

.um-btn-icon-danger:disabled:hover {
    background-color: transparent;
    color: var(--line);
}

/* --- empty state --- */
.um-empty {
    padding: 3rem 1.5rem;
    text-align: center;
    color: var(--text-2);
}

.um-empty svg {
    width: 2rem;
    height: 2rem;
    color: var(--text-3);
    margin-bottom: 0.6rem;
}

.um-empty strong {
    display: block;
    color: var(--text-heading);
    margin-bottom: 0.25rem;
}

.um-empty p {
    margin: 0;
    font-size: 0.875rem;
}

/* --- modals --- */
.um-modal-overlay {
    position: fixed;
    inset: 0;
    background-color: rgba(15, 23, 42, 0.45);
    display: none;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    z-index: 4000;
}

.um-modal-overlay.open {
    display: flex;
}

/* "Session timed out" (Base.html). Like a modal overlay, but darker and
   above everything, and pages' close-all-dialogs code leaves it alone. */
.session-overlay {
    position: fixed;
    inset: 0;
    z-index: 6000;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background-color: rgba(15, 23, 42, 0.65);
}

.session-overlay.open {
    display: flex;
}

.um-modal {
    background-color: var(--surface-1);
    border-radius: 12px;
    width: min(100%, 26rem);
    box-shadow: 0 24px 50px rgba(15, 23, 42, 0.25);
    overflow: hidden;
}

.um-modal-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: 1.2rem 1.4rem 0.9rem;
}

.um-modal-head h3 {
    margin: 0;
    font-size: 1.125rem;
    color: var(--text-heading);
}

.um-modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    background: none;
    border: none;
    color: var(--text-2);
    cursor: pointer;
    border-radius: 4px;
}

.um-modal-close svg {
    width: 1rem;
    height: 1rem;
}

.um-modal-close:hover {
    color: var(--text-heading);
    background-color: var(--surface-3);
}

.um-modal-body {
    padding: 0 1.4rem 1.2rem;
}

.um-modal-text {
    margin: 0;
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--text-body);
}

/* A confirm button that is a link (Sign Out goes to a GET route). */
a.um-btn-confirm {
    display: block;
    text-align: center;
    text-decoration: none;
}

.um-warn {
    display: flex;
    gap: 0.7rem;
    background-color: var(--danger-bg);
    border: 1px solid var(--danger-line);
    border-radius: 8px;
    padding: 0.9rem 1rem;
    font-size: 0.875rem;
    color: var(--danger-strong);
    line-height: 1.55;
}

.um-warn svg {
    width: 1.05rem;
    height: 1.05rem;
    flex-shrink: 0;
    margin-top: 0.1rem;
    color: var(--danger-text);
}

/* --- Bootstrap-named form classes ---------------------------------------
   Several forms arrived written against Bootstrap, which this project does
   not load. Without these, .form-text rendered at full body size and read
   louder than the field it was explaining, and .form-control fell back to
   the browser's own control. Matched to .um-field so a form does not change
   character halfway down. */
.form-label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-strong);
}

.form-text {
    margin-top: 0.4rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--text-2);
}

.form-control {
    width: 100%;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 0.875rem;
    font-family: inherit;
    color: var(--text-strong);
    background-color: var(--surface-1);
    outline: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.form-control:focus {
    border-color: var(--brand-text);
    box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.12);
}

/* The file input's own button is drawn by the browser, so it has to be
   styled through its pseudo-element or it stays a grey system control. */
.form-control[type="file"] {
    padding: 0.45rem 0.6rem;
    color: var(--text-2);
}

.form-control[type="file"]::file-selector-button {
    margin-right: 0.75rem;
    padding: 0.35rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: 6px;
    background-color: var(--surface-2);
    color: var(--text-strong);
    font-family: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.form-control[type="file"]::file-selector-button:hover {
    background-color: var(--surface-3);
    border-color: var(--line-strong);
}

.um-field {
    margin-top: 1rem;
}

.um-field label {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-strong);
    margin-bottom: 0.35rem;
}

.um-field input,
.um-field select,
.um-field textarea {
    width: 100%;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 0.875rem;
    color: var(--text-strong);
    background-color: var(--surface-1);
    outline: none;
    font-family: inherit;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.um-field textarea {
    resize: vertical;
}

.um-field input::placeholder,
.um-field textarea::placeholder {
    color: var(--text-2);
}

.um-field input:focus,
.um-field select:focus,
.um-field textarea:focus {
    border-color: var(--brand-text);
    box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.12);
}

.um-field select:disabled {
    background-color: var(--surface-2);
    color: var(--text-2);
    cursor: not-allowed;
}

.um-modal-foot {
    display: flex;
    gap: 0.7rem;
    padding: 0 1.4rem 1.4rem;
    margin-top: 1.3rem;
}

.um-modal-foot > * {
    flex: 1;
}

.um-btn-cancel {
    padding: 0.65rem 1rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background-color: var(--surface-1);
    color: var(--text-body);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
}

.um-btn-cancel:hover {
    border-color: var(--line-strong);
    color: var(--text-heading);
}

.um-btn-confirm-danger {
    padding: 0.65rem 1rem;
    border: none;
    border-radius: 8px;
    background-color: var(--danger-solid);
    color: #ffffff;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
}

.um-btn-confirm-danger:hover {
    background-color: var(--danger-solid);
}

/* Delete Account waits for the username to be typed. */
.um-btn-confirm-danger:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.um-btn-confirm {
    padding: 0.65rem 1rem;
    border: none;
    border-radius: 8px;
    background-color: var(--brand-solid);
    color: #ffffff;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
}

.um-btn-confirm:hover {
    background-color: var(--ok-solid);
}

@media (max-width: 640px) {
    .um-table-card {
        overflow-x: auto;
    }
}

/* ============================================
   SPECIES RECOMMENDATION (municipality/barangay picker)
   ============================================ */

.rec-picker-card {
    padding: 1.1rem 1.4rem 1.4rem;
    margin-bottom: 1.5rem;
}

.rec-picker-row {
    display: flex;
    align-items: flex-end;
    gap: 1.2rem;
    flex-wrap: wrap;
}

.rec-picker-row .um-field {
    margin-top: 0;
    flex: 1 1 220px;
    min-width: 200px;
}

@media (max-width: 640px) {
    .rec-picker-row {
        flex-direction: column;
        align-items: stretch;
        gap: 0.8rem;
    }

    /* stacked, the 220px flex-basis became a 220px height per field */
    .rec-picker-row .um-field {
        flex: 0 0 auto;
    }
}

.rec-table {
    /* Fixed layout + the <colgroup> widths above lock every column in place --
       otherwise toggling "Show all" changes which rows are visible and the
       browser recalculates column widths from just those rows, resizing the
       whole table (same bug the Municipality filter had on User Management). */
    table-layout: fixed;
    width: max-content;
    min-width: 100%;
}

.rec-table td,
.rec-table th {
    white-space: normal;
}

/* --- Species table: a scrollport of its own -----------------------------
   58 rows is well past a screen, and the column headings are the only
   thing that says what each figure is. The wrapper already sets
   overflow-x, which quietly makes it a scroll container; giving it a
   ceiling makes it actually scroll, and that is what a sticky header needs
   to stick to. Short result sets (the default top 5) never reach the
   ceiling, so no inner scrollbar appears for them. */
#rec-scroll {
    max-height: max(24rem, calc(100vh - 22rem));
    overflow-y: auto;
    /* The gutter is reserved either way, so switching between "top 5" and
       "all 58" does not shift the columns sideways. */
    scrollbar-gutter: stable;
}

#rec-scroll thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    /* .um-table th already carries an opaque ground; the border below it is
       redrawn as an inset shadow because a collapsed-border table drops
       borders on sticky cells. */
    box-shadow: inset 0 -1px 0 var(--line);
}

/* ============================================
   REFORESTATION SITES (flat table + horizontal scroll)
   ============================================ */

/* The horizontal-scroll table pattern below is shared with the monitoring
   reports list, so each rule also matches a neutral .um-scroll-* name. */
.sites-scroll-wrap,
.um-scroll-wrap {
    overflow-x: auto;
    scroll-behavior: smooth;
}

/* The same treatment for every table built on this wrapper -- the audit
   log, my reports, the provincial lists, the review queues. The ceiling is
   what makes a sticky header possible: the wrapper already sets overflow-x,
   which quietly makes it a scroll container, but without a height it never
   scrolls and a sticky header has nothing to stick to. A short table never
   reaches the ceiling, so nothing about it changes. */
.um-scroll-wrap {
    max-height: max(24rem, calc(100vh - 22rem));
    overflow-y: auto;
    /* Reserved either way, so filtering the rows cannot shift the columns
       sideways. */
    scrollbar-gutter: stable;
}

.um-scroll-wrap thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    /* .um-table th already carries an opaque ground; the rule beneath it is
       redrawn as an inset shadow because a collapsed-border table drops
       borders on sticky cells. */
    box-shadow: inset 0 -1px 0 var(--line);
}

/* A ceiling rather than a fixed height: a short page of rows still ends
   where it ends and shows no inner scrollbar, while a long one scrolls
   inside the card so the header can stay pinned to the top of it. The
   floor keeps the table usable on a short laptop screen. */
#sites-scroll {
    max-height: max(24rem, calc(100vh - 16rem));
    overflow-y: auto;
    /* Same reason as .main-area: changing row density or page size must not
       move the columns sideways. */
    scrollbar-gutter: stable;
}

.sites-table,
.um-scroll-table {
    /* Overrides .um-table's fixed layout -- this table has far too many
       columns to squeeze into 100% width, so let it grow naturally and
       scroll horizontally instead. */
    width: max-content;
    min-width: 100%;
    table-layout: auto;
}

.sites-name-cell {
    font-weight: 600;
    color: var(--text-heading);
}

.sites-fw-semibold {
    font-weight: 600;
}

/* Duration on one line, with its date range kept on one line beneath. */
.sites-duration-cell {
    white-space: nowrap;
}

.sites-actual {
    color: var(--brand-text);
}

/* --- Data-table treatment ---------------------------------------------
   The sites card carries its own toolbar and footer rather than floating
   loose controls above and below it, so the whole thing reads as one
   object. Headers are small caps that sort on click, the name column
   leads with an initial chip, and rows are paged. Light ground: this
   product has no dark surfaces. */

.dt-card {
    display: flex;
    flex-direction: column;
    /* .um-table-card clips its children, which would cut off the
       municipality dropdown now that the filter lives inside the card. The
       toolbar and footer round their own outer corners instead. */
    overflow: visible;
}

.dt-toolbar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    padding: 0.85rem 1.1rem;
    border-bottom: 1px solid var(--n-200);
    background-color: var(--n-50);
    border-radius: 12px 12px 0 0;
}

/* Pushes the row count to the far edge whatever the filter label says. */
.dt-toolbar .um-search-group { margin-left: auto; }

.dt-count {
    font-size: 0.8125rem;
    color: var(--n-500);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.dt-count strong { color: var(--n-800); font-weight: 600; }

/* Headers are labels, not controls: small caps, tracked out, quiet. They
   carry no hover state and no chevron, so nothing in the header row reads
   as a button. */
#sites-table th {
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* The header row stays at the top of the scrollport, so the reader never
   has to scroll back up to remember which column they are reading.
   border-collapse drops borders on sticky cells, so the rule under the
   header is an inset shadow. */
#sites-table thead th {
    position: sticky;
    top: 0;
    z-index: 4;
}

/* Codes are identifiers, not prose -- mono, tracked out, quiet. */
#sites-table td code,
.dt-code {
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    color: var(--n-500);
    background: none;
    padding: 0;
}

/* --- Row height --------------------------------------------------------
   One height, set in one place. This was briefly a three-way switch
   (40/48/56); the choice was not worth the toolbar space, so the roomiest
   setting became the only one. */
/* .um-table collapses its borders, and a browser will not paint an outer
   box-shadow on a cell of a collapsed-border table -- which is what kept
   the frozen-column shade invisible. Separate borders with zero spacing
   look identical (the cells only ever draw a bottom border) and let the
   shadow through. */
#sites-table {
    border-collapse: separate;
    border-spacing: 0;
}

#sites-table { --dt-row-h: 56px; }

/* Height owns the row outright, so the cells give up their vertical
   padding -- otherwise padding plus line-height would overshoot 40px and
   compact would not actually be compact. */
#sites-table tbody tr {
    height: var(--dt-row-h);
    transition: background-color 140ms var(--ease);
}
#sites-table tbody td {
    padding-top: 0;
    padding-bottom: 0;
}

/* --- Pinned (frozen) first columns -------------------------------------
   Twenty-odd columns means the reader loses track of which row they are on
   as soon as they scroll sideways, so the two identifying columns stay put
   and the rest slide underneath them. The left offsets are measured in JS
   because the table is auto-layout -- the code column is only as wide as
   its content.

   The edge shade is a box-shadow rather than a pseudo-element: a shadow
   paints outside the cell without needing overflow:visible, so the cells
   keep the ellipsis they get from .um-table. */
#sites-table .dt-pin {
    position: sticky;
    left: 0;                      /* replaced per column by the script */
    z-index: 2;
    background-color: var(--surface-1);
    transition: box-shadow 160ms var(--ease), background-color 140ms var(--ease);
}

#sites-table thead .dt-pin {
    z-index: 5;
    background-color: var(--n-50);
}

#sites-table tbody tr:hover .dt-pin { background-color: var(--n-50); }

/* Long site names would otherwise let the frozen pair eat half the screen. */
#sites-table .sites-name-cell { max-width: 20rem; }

/* Nothing marks the edge while the table sits at its left-most position --
   the frozen columns should look like ordinary columns until something is
   actually passing underneath them. Scrolled sideways, a soft shade fades
   in and that is the only edge treatment there is.

   The spread is only -4px: pulled in any further the shade lands inside
   the cell's own edge and disappears. */
#sites-scroll.is-scrolled .dt-pin-last {
    box-shadow: 10px 0 12px -4px var(--pin-shade);
}

/* --- Footer: range on the left, pager on the right -------------------- */
.dt-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.7rem 1.1rem;
    border-top: 1px solid var(--n-200);
    background-color: var(--n-50);
    border-radius: 0 0 12px 12px;
}

.dt-range {
    font-size: 0.75rem;
    color: var(--n-500);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
    .dt-footer { justify-content: center; }
}

/* ============================================
   FROZEN TABLE COLUMNS (Excel-style freeze panes)
   Any wide table can keep its header row and its first one or two
   columns in place while the rest scrolls. Opt in from the template:

     <div class="um-scroll-wrap table-freeze-wrap">
       <table class="um-table" data-freeze="2">

   data-freeze="1" freezes the first column, "2" the first two.
   app/static/js/table-freeze.js (loaded in Base.html) tells column 2
   where column 1 ends, and adds .is-scrolled-x for the edge shadow.

   Opt-in on purpose: several pages share .sites-table and .um-scroll-*,
   and freezing "column 2" by class alone pinned the wrong column on
   pages whose column 2 is something else.
   ============================================ */

/* The table scrolls inside its own box, so the sideways scrollbar stays
   on screen instead of at the bottom of a long page. The box shrinks to
   fit short tables. An inline max-height on the wrap still wins. */
.table-freeze-wrap {
    overflow: auto;
    /* same ceiling as .um-scroll-wrap, so both wrappers agree */
    max-height: max(24rem, calc(100vh - 22rem));
}

/* collapsed borders do not stick with sticky cells, so separate them */
table[data-freeze] {
    border-collapse: separate;
    border-spacing: 0;
}

/* Plain rows get a solid surface colour for the frozen cells to copy
   (theme token, so dark mode stays dark). :where() keeps this weaker
   than any row highlight (hover, focus, .row-bad). */
:where(table[data-freeze]) tbody tr {
    background-color: var(--surface-1);
}

/* matches .um-scroll-wrap thead th: same layer, same inset rule underneath */
table[data-freeze] thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    box-shadow: inset 0 -1px 0 var(--line);
}

table[data-freeze="1"] td:nth-child(1),
table[data-freeze="2"] td:nth-child(-n+2) {
    position: sticky;
    z-index: 1;
    /* copy the row's colour: highlights still show, and the columns
       sliding underneath stay hidden */
    background-color: inherit;
}

table[data-freeze] th:nth-child(1),
table[data-freeze] td:nth-child(1) {
    left: 0;
}

table[data-freeze="2"] th:nth-child(2),
table[data-freeze="2"] td:nth-child(2) {
    left: var(--freeze-col1-width, 0px);
}

/* top-left corner sits above both the header row and the frozen columns */
table[data-freeze="1"] thead th:nth-child(1),
table[data-freeze="2"] thead th:nth-child(-n+2) {
    z-index: 4;
}

/* Divider on the last frozen column, and a soft shade once columns scroll
   under it (same shade token as the Sites table's pinned columns).
   Header cells repeat the inset rule underneath, because box-shadow
   replaces rather than adds to the header's own. */
table[data-freeze="1"] td:nth-child(1),
table[data-freeze="2"] td:nth-child(2) {
    box-shadow: inset -1px 0 0 var(--line);
}

table[data-freeze="1"] th:nth-child(1),
table[data-freeze="2"] th:nth-child(2) {
    box-shadow: inset -1px 0 0 var(--line), inset 0 -1px 0 var(--line);
}

table[data-freeze="1"].is-scrolled-x td:nth-child(1),
table[data-freeze="2"].is-scrolled-x td:nth-child(2) {
    box-shadow: inset -1px 0 0 var(--line), 10px 0 12px -4px var(--pin-shade);
}

table[data-freeze="1"].is-scrolled-x th:nth-child(1),
table[data-freeze="2"].is-scrolled-x th:nth-child(2) {
    box-shadow: inset -1px 0 0 var(--line), inset 0 -1px 0 var(--line),
                10px 0 12px -4px var(--pin-shade);
}

@media (max-width: 640px) {
    /* a phone is too narrow for two frozen columns: freeze the first only */
    table[data-freeze="2"] td:nth-child(2) {
        position: static;
    }

    table[data-freeze="2"] th:nth-child(2) {
        left: auto;
    }

    table[data-freeze="2"] td:nth-child(2),
    table[data-freeze="2"].is-scrolled-x td:nth-child(2) {
        box-shadow: none;
    }

    /* header cell keeps only its rule underneath */
    table[data-freeze="2"] th:nth-child(2),
    table[data-freeze="2"].is-scrolled-x th:nth-child(2) {
        box-shadow: inset 0 -1px 0 var(--line);
    }

    /* the divider and shade move to column 1 */
    table[data-freeze="2"] td:nth-child(1) {
        box-shadow: inset -1px 0 0 var(--line);
    }

    table[data-freeze="2"] th:nth-child(1) {
        box-shadow: inset -1px 0 0 var(--line), inset 0 -1px 0 var(--line);
    }

    table[data-freeze="2"].is-scrolled-x td:nth-child(1) {
        box-shadow: inset -1px 0 0 var(--line), 10px 0 12px -4px var(--pin-shade);
    }

    table[data-freeze="2"].is-scrolled-x th:nth-child(1) {
        box-shadow: inset -1px 0 0 var(--line), inset 0 -1px 0 var(--line),
                    10px 0 12px -4px var(--pin-shade);
    }
}

/* ============================================
   MONITORING REPORTS
   Shared by Reports.html (officer), Reportsadmin.html (review list)
   and Reportdetail.html (single report). These pages arrived using
   Bootstrap class names, which this project does not load -- the
   equivalents below are built on the same tokens as the .um-* system
   so the reports section matches the rest of the app.
   ============================================ */

/* --- flash messages --- */
.um-alert {
    border: 1px solid var(--line);
    border-left: 3px solid var(--text-2);
    border-radius: 8px;
    padding: 0.8rem 1rem;
    margin-bottom: 1.1rem;
    font-size: 0.875rem;
    color: var(--text-strong);
    background-color: var(--surface-2);
}

.um-alert-success {
    border-left-color: var(--brand-text);
    background-color: var(--ok-bg);
    color: var(--brand-text);
}

.um-alert-danger,
.um-alert-error {
    border-left-color: var(--danger-text);
    background-color: var(--danger-bg);
    color: var(--danger-strong);
}

.um-alert-warning {
    border-left-color: var(--warn-text);
    background-color: var(--warn-bg);
    color: var(--warn-strong);
}

.um-alert-info {
    border-left-color: var(--info-text);
    background-color: var(--info-bg);
    color: var(--info-strong);
}

/* --- approval status pill --- */
.um-status {
    display: inline-block;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    white-space: nowrap;
}

.um-status.st-pending { background-color: var(--warn-bg); color: var(--warn-strong); }
.um-status.st-approved { background-color: var(--ok-bg); color: var(--brand-text); }
.um-status.st-rejected { background-color: var(--danger-bg); color: var(--danger-strong); }

/* --- panel: a card holding form controls or prose rather than a table,
       so unlike .um-table-card it carries its own padding --- */
.um-panel {
    background-color: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 1.3rem 1.5rem 1.5rem;
    margin-bottom: 1.4rem;
}

.um-panel-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding-bottom: 0.8rem;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--line);
}

.um-panel-head h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-heading);
}

.um-panel-head svg {
    width: 1.05rem;
    height: 1.05rem;
    color: var(--brand-text);
    flex-shrink: 0;
}

.um-step-num {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background-color: var(--brand-solid);
    color: #ffffff;
    font-size: 0.75rem;
    font-weight: 700;
    flex-shrink: 0;
}

/* --- form grid (replaces Bootstrap's .row / .col-md-*) --- */
.um-field-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11.875rem, 1fr));
    gap: 1rem;
}

/* .um-field carries a top margin for stacked modal fields; inside a grid
   row the gap already spaces them, so drop it. */
.um-field-row .um-field {
    margin-top: 0;
}

.um-field-hint {
    margin: 0.35rem 0 0;
    font-size: 0.75rem;
    color: var(--text-2);
}

.um-req {
    color: var(--danger-text);
}

/* --- callouts: guidance (blue) and caveats (amber) --- */
.um-note {
    background-color: var(--info-bg);
    border-left: 3px solid var(--info-text);
    border-radius: 8px;
    padding: 0.8rem 1rem;
    margin-bottom: 1.1rem;
    font-size: 0.875rem;
    line-height: 1.65;
    color: var(--text-strong);
}

.um-note strong {
    color: var(--info-strong);
}

.um-note-warn {
    background-color: var(--warn-bg);
    border-left: 3px solid var(--warn-text);
    border-radius: 8px;
    padding: 0.8rem 1rem;
    margin-top: 1rem;
    font-size: 0.875rem;
    line-height: 1.65;
    color: var(--warn-strong);
}

.um-note-warn strong {
    color: var(--warn-strong);
}

/* Toolbar variant for pages whose only control is the search box, which
   still belongs on the right like every other table page. */
.um-toolbar-end {
    justify-content: flex-end;
}

/* --- compact row-action button --- */
.um-btn-row {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.35rem 0.75rem;
    border: none;
    border-radius: 7px;
    background-color: var(--brand-solid);
    color: #ffffff;
    font-size: 0.75rem;
    font-weight: 600;
    font-family: inherit;
    text-decoration: none;
    cursor: pointer;
}

.um-btn-row:hover {
    background-color: var(--ok-solid);
}

.um-btn-row svg {
    width: 0.8rem;
    height: 0.8rem;
}

/* --- secondary/outline button --- */
.um-btn-outline {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--ok-line);
    border-radius: 8px;
    background-color: var(--ok-bg);
    color: var(--brand-text);
    font-size: 0.875rem;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}

.um-btn-outline:hover {
    border-color: var(--brand-text);
    background-color: var(--ok-bg);
}

.um-btn-outline svg {
    width: 0.9rem;
    height: 0.9rem;
}

.um-back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 0.9rem;
    color: var(--text-2);
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
}

.um-back-link:hover {
    color: var(--brand-text);
}

.um-back-link svg {
    width: 0.9rem;
    height: 0.9rem;
}

/* --- labelled figure grid, used for site facts and report totals --- */
.um-fact-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 1.4rem;
}

.um-fact .lbl {
    display: block;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-2);
}

.um-fact .val {
    display: block;
    margin-top: 0.15rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-heading);
}

.um-fact .val-sm {
    font-size: 0.875rem;
}

/* Dark variant: the selected-site summary sits inside the officer's form
   and needs to read as a result panel rather than another input group. */
.rep-site-facts {
    display: none;
    background-color: var(--brand-deep);
    border-radius: 10px;
    padding: 1.1rem 1.2rem;
    margin-top: 1.2rem;
}

.rep-site-facts.show {
    display: block;
}

.rep-site-facts .site-title {
    margin-bottom: 0.85rem;
    color: #ffffff;
    font-size: 1rem;
    font-weight: 700;
}

/* This panel is dark green under both themes, so its label takes a fixed
   light green -- --ok-line is a border tint and drops to low alpha in
   dark, which would all but erase it here. */
.rep-site-facts .lbl { color: #9fe0ba; }
.rep-site-facts .val { color: #ffffff; }

/* --- sampling plot rows --- */
.rep-plot-head,
.rep-plot-row {
    display: grid;
    grid-template-columns: 2.2rem 7rem 1fr 2rem;
    gap: 0.5rem;
    align-items: center;
}

.rep-plot-head {
    margin-bottom: 0.45rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-2);
}

.rep-plot-row {
    margin-bottom: 0.5rem;
}

.rep-plot-row input {
    width: 100%;
    padding: 0.45rem 0.65rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 0.875rem;
    font-family: inherit;
    color: var(--text-strong);
    outline: none;
}

.rep-plot-row input:focus {
    border-color: var(--brand-text);
    box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.12);
}

.rep-plot-num {
    font-weight: 700;
    color: var(--text-2);
    text-align: center;
    font-size: 0.875rem;
}

.rep-plot-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    border: 1px solid var(--danger-line);
    border-radius: 8px;
    background-color: var(--danger-bg);
    color: var(--danger-strong);
    font-size: 1rem;
    line-height: 1;
    font-family: inherit;
    cursor: pointer;
}

.rep-plot-remove:hover {
    border-color: var(--danger-text);
    background-color: var(--danger-bg);
}

/* --- live survival calculation --- */
.rep-calc {
    background-color: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 1rem 1.1rem;
    margin-top: 1.1rem;
    font-size: 0.875rem;
}

.rep-calc-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.32rem 0;
    border-bottom: 1px solid var(--surface-2);
}

.rep-calc-line:last-child {
    border-bottom: none;
}

.rep-calc-line .lbl { color: var(--text-2); }
.rep-calc-line .val { font-weight: 600; color: var(--text-strong); }

.rep-calc-result {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    background-color: var(--brand-deep);
    color: #ffffff;
    border-radius: 10px;
    padding: 0.85rem 1.1rem;
    margin-top: 0.85rem;
    font-weight: 600;
}

.rep-calc-result.fail {
    background-color: var(--danger-solid);
}

.rep-calc-result .big {
    font-size: 1.5rem;
    font-weight: 700;
}

.rep-calc-warn {
    background-color: var(--warn-bg);
    border-left: 3px solid var(--warn-text);
    border-radius: 6px;
    padding: 0.55rem 0.8rem;
    margin-top: 0.5rem;
    font-size: 0.75rem;
    color: var(--warn-strong);
}

/* --- plot plan: plots needed on the chosen site, and progress ---
   Target: see TARGET_INTENSITY in monitoring.py.
   Sits above the plot rows so the target is known before counting starts. */
.rep-plan {
    background-color: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 0.9rem 1.1rem;
    margin-bottom: 1.1rem;
    font-size: 0.875rem;
}

/* .um-btn-outline sets display, which would beat the hidden attribute */
.rep-plan [hidden] {
    display: none;
}

.rep-plan-empty {
    margin: 0;
    color: var(--text-2);
}

.rep-plan-head {
    margin-bottom: 0.35rem;
    font-weight: 700;
    color: var(--text-heading);
}

.rep-plan-status {
    margin-top: 0.6rem;
    padding: 0.55rem 0.8rem;
    border-radius: 8px;
    background-color: var(--warn-bg);
    color: var(--warn-strong);
    font-weight: 600;
}

.rep-plan-status.done {
    background-color: var(--ok-bg);
    color: var(--ok-text);
}

.rep-plan-tip {
    margin: 0.5rem 0 0;
    font-size: 0.8125rem;
    color: var(--text-2);
}

/* --- report detail: headline survival banner --- */
/* Baseline alignment was pulling the figure up to sit level with the first
   of the two lines beside it, which left it floating clear of its own
   block. Centring the row settles the pair against each other. */
.rep-headline {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1.5rem;
    flex-wrap: wrap;
    background-color: var(--brand-deep);
    color: #ffffff;
    border-radius: 12px;
    padding: 1.15rem 1.4rem;
    margin-bottom: 1.4rem;
}

/* The text column takes the slack, so the figure stays hard right without
   the two ends drifting apart on a wide screen. */
.rep-headline > div {
    flex: 1 1 16rem;
    min-width: 0;
}

.rep-headline.fail {
    background-color: var(--danger-solid);
}

.rep-headline .lbl {
    display: block;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    opacity: 0.75;
}

.rep-headline .sub {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.875rem;
    line-height: 1.4;
    opacity: 0.9;
}

.rep-headline .big {
    /* Tight leading and tabular figures: the number is set on its own, and
       at 2rem a default line-height adds space it does not need. */
    font-size: clamp(1.75rem, 1.2rem + 1.6vw, 2.25rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* --- plot count bars --- */
.rep-bar-track {
    width: 100%;
    height: 0.45rem;
    background-color: var(--surface-3);
    border-radius: 999px;
    overflow: hidden;
}

.rep-bar-fill {
    height: 100%;
    background-color: var(--brand-solid);
}

.rep-bar-fill.low {
    background-color: var(--warn-solid);
}

/* --- photo evidence --- */
.rep-photo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11.25rem, 1fr));
    gap: 0.9rem;
}

.rep-photo-card {
    border: 1px solid var(--line);
    border-radius: 10px;
    overflow: hidden;
    background-color: var(--surface-2);
}

.rep-photo-card img {
    display: block;
    width: 100%;
    height: 140px;
    object-fit: cover;
}

.rep-photo-meta {
    padding: 0.55rem 0.65rem;
    font-size: 0.75rem;
    line-height: 1.55;
    color: var(--text-body);
}

.rep-badge-ok,
.rep-badge-flag {
    display: inline-block;
    margin: 0.1rem 0.15rem 0.1rem 0;
    padding: 0.1rem 0.45rem;
    border-radius: 6px;
    font-size: 0.75rem;
    font-weight: 700;
}

.rep-badge-ok { background-color: var(--ok-bg); color: var(--brand-text); }
.rep-badge-flag { background-color: var(--warn-bg); color: var(--warn-strong); }

.rep-map {
    height: 320px;
    border: 1px solid var(--line);
    border-radius: 10px;
}

/* --- review list: status count chips --- */
/* --- review decision row: status / note / submit --- */
.rep-decision-row {
    display: grid;
    grid-template-columns: minmax(8.75rem, 1fr) minmax(13.75rem, 2fr) auto;
    gap: 1rem;
    align-items: end;
}

.rep-checkbox {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1rem;
    font-size: 0.875rem;
    color: var(--text-strong);
}

.rep-checkbox input {
    width: 1rem;
    height: 1rem;
    accent-color: var(--brand-text);
    cursor: pointer;
}

@media (max-width: 720px) {
    .rep-decision-row {
        grid-template-columns: 1fr;
    }
}

/* ============================================
   DASHBOARD (analytics / data storytelling)
   Moved out of a <style> block in Dashboard.html so every page keeps
   its CSS in one place, and so the cards use the same border/radius
   tokens as .um-table-card and .um-panel instead of their own shadow.
   ============================================ */

.section-head {
    margin: 2rem 0 1rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--line);
}

.section-head:first-of-type {
    margin-top: 1rem;
}

.section-head .eyebrow {
    display: block;
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--brand-600);
}

.section-head h4 {
    margin: 0.25rem 0 0.25rem;
    font-size: var(--fs-xl);
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--n-900);
}

.section-head p {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: var(--n-500);
}

.kpi-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11.875rem, 1fr));
    gap: 0.85rem;
    margin-bottom: 1.5rem;
}

.kpi {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    background-color: var(--n-0);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    padding: var(--sp-2);
    box-shadow: var(--sh-sm);
}

.kpi-text {
    min-width: 0;
}

/* The icon names the measure without competing with it: a tinted square,
   the figure's own hue, and a size that keeps it clearly second. */
.kpi-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 2.85rem;
    height: 2.85rem;
    border-radius: var(--r-md);
    background-color: var(--ok-bg);
    border: 1px solid var(--ok-line);
    color: var(--brand-text);
}

.kpi-icon svg {
    width: 1.5rem;
    height: 1.5rem;
}

.kpi.alert .kpi-icon {
    background-color: var(--danger-bg);
    border-color: var(--danger-line);
    color: var(--danger-text);
}

/* --brand-700 is a deep forest green: right on paper, and all but invisible
   on a dark card, which is what it became when the theme flipped.
   --brand-deep-text is the same role written for both grounds. */
.kpi .num {
    display: block;
    font-size: var(--fs-2xl);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--brand-deep-text);
    /* Figures line up across the strip instead of jittering per glyph. */
    font-variant-numeric: tabular-nums;
}

.kpi .lbl {
    display: block;
    margin-top: 0.25rem;
    font-size: var(--fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--n-500);
}

.kpi .sub {
    display: block;
    margin-top: 0.25rem;
    font-size: var(--fs-xs);
    color: var(--n-500);
}

.kpi.alert .num {
    color: var(--danger-strong);
}

.chart-card {
    background-color: var(--n-0);
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    padding: var(--sp-3);
    margin-bottom: var(--sp-2);
    box-shadow: var(--sh-sm);
}

.chart-card h5 {
    margin: 0 0 0.25rem;
    font-size: var(--fs-base);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--n-900);
}

.chart-card .takeaway {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: var(--n-500);
    max-width: 68ch;
}

/* The table inside a chart card brings its own card chrome, so drop the
   bottom margin .um-table-card would otherwise add inside this one. */
.chart-card .um-table-card {
    margin-bottom: 0;
}

.chart-box {
    position: relative;
    height: 300px;
}

.chart-box.tall {
    height: 420px;
}

/* Highest-suitability barangays (Dashboard): on a wide screen the row
   carries both species on one line, "Pili · Yakal-yamban", with tighter
   padding, so the 15 rows and the species chart beside them stop running
   far down the page. Phones keep the two-line cells. */
@media (min-width: 861px) {
    #unplantedBody td {
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
    }

    #unplantedBody td > div {
        display: inline;
    }

    #unplantedBody td > div + div::before {
        content: " \00B7  ";
    }
}

/* A chart that stretches to the height of the card beside it in the row
   (the species chart next to the unplanted-barangays table). */
.chart-card-fill {
    display: flex;
    flex-direction: column;
}

.chart-card-fill .chart-box {
    flex: 1 1 auto;
    height: auto;
    min-height: 300px;
}

.chart-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
}

/* A grid item's automatic minimum size is its content, so a Chart.js canvas
   that has already been laid out wide refuses to shrink when the rail
   expands. The column then grows past the row, and .main-area's overflow-x
   clips whatever spills - which is why only the right-hand card looked cut
   off. min-width:0 lets the column shrink, and Chart.js resizes the canvas
   to match. */
.chart-row > * {
    min-width: 0;
}

@media (max-width: 992px) {
    .chart-row {
        grid-template-columns: 1fr;
    }
}

/* Reuses the .um-note look so a dashboard finding reads the same as the
   guidance callouts on the monitoring report pages. */
.finding {
    background-color: var(--info-bg);
    border-left: 3px solid var(--info-text);
    border-radius: 8px;
    padding: 0.85rem 1rem;
    margin-top: 0.9rem;
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--text-strong);
}

.finding strong {
    color: var(--info-strong);
}

.finding.warn {
    background-color: var(--warn-bg);
    border-left-color: var(--warn-text);
    color: var(--warn-strong);
}

.finding.warn strong {
    color: var(--warn-strong);
}

.loading {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--text-2);
}


/* ============================================
   SHARED REFINEMENTS
   Applied across every role's pages via the common chrome.
   ============================================ */

/* ---- Keyboard focus was invisible app-wide ---- */
.app-body :focus-visible {
    outline: 2px solid var(--brand-500);
    outline-offset: 2px;
    border-radius: 4px;
}

/* ---- Cards: layered elevation and a hairline instead of a flat blur ---- */
.card,
.table-card,
.um-table-card {
    border: 1px solid var(--n-200);
    box-shadow: var(--sh-sm);
}

/* ---- Empty states are content, so they need to clear 4.5:1 ---- */
/* --n-400 sits near 2.9:1, which is fine behind a placeholder but not for
   a message that is the only thing on the page. */
.placeholder-text {
    color: var(--n-500);
}

/* ---- Topbar: sticks, and stops looking like a stray white strip ---- */
.global-topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    background-color: var(--surface-glass);
    backdrop-filter: blur(8px) saturate(1.4);
    -webkit-backdrop-filter: blur(8px) saturate(1.4);
}

/* ---- Interactive feedback: nothing moved before ---- */
.nav-link,
.notif-bell-btn,
.card,
.table-card {
    transition: background-color 160ms var(--ease),
                border-color 160ms var(--ease),
                box-shadow 160ms var(--ease),
                color 160ms var(--ease);
}

/* ---- Tables: numbers line up when they share a width ---- */
.um-table td,
.table-card td {
    font-variant-numeric: tabular-nums;
}

/* ---- Respect the OS motion setting ---- */
@media (prefers-reduced-motion: reduce) {
    .app-body *,
    .app-body *::before,
    .app-body *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}


/* ---- Chart key ----
   The delivery and survival bars are banded by threshold. Without this the
   banding is carried by colour alone, which is unreadable for a red-green
   colour-vision deficiency and disappears entirely in print. */
.chart-key {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-2);
    margin: 0 0 var(--sp-2);
    padding: 0;
    list-style: none;
    font-size: var(--fs-xs);
    color: var(--n-500);
}

.chart-key li {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

.chart-key .dot {
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 3px;
    flex-shrink: 0;
}

/* These swatches are the chart's written key, so they hold the same hex the
   canvas draws with and do not move between themes -- a legend that changes
   shade is a legend the reader has to learn twice. Keep them in step with
   GREEN / AMBER / RED in Dashboard.html. */
.chart-key .dot-ok { background: #16a34a; }
.chart-key .dot-warn { background: #b45309; }
.chart-key .dot-bad { background: #dc2626; }

/* ---- Loading skeleton ----
   Holds the page's shape while the analytics fetch is in flight, instead of
   a line of text that the real content then shoves aside. */
.dash-skeleton .sk-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11.875rem, 1fr));
    gap: 0.85rem;
    margin-bottom: var(--sp-3);
}

.dash-skeleton .sk-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-2);
}

@media (max-width: 992px) {
    .dash-skeleton .sk-row {
        grid-template-columns: 1fr;
    }
}

.dash-skeleton .sk-box,
.dash-skeleton .sk-chart {
    border: 1px solid var(--n-200);
    border-radius: var(--r-md);
    background: linear-gradient(90deg, var(--n-50) 25%, var(--n-100) 37%, var(--n-50) 63%);
    background-size: 400% 100%;
    animation: sk-shimmer 1.4s ease-in-out infinite;
}

.dash-skeleton .sk-box { height: 5.5rem; }
.dash-skeleton .sk-chart { height: 22rem; }

@keyframes sk-shimmer {
    from { background-position: 100% 50%; }
    to { background-position: 0 50%; }
}

/* Announced to screen readers, invisible on screen. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* A filter button at the right edge of the toolbar has to drop its menu
   leftwards, or the panel runs off the page. */
.um-filter-menu-end {
    left: auto;
    right: 0;
}

/* Carries the per-status counts that used to sit in the card strip, so
   moving the filter into a dropdown does not lose them. */
.um-filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.375rem;
    padding: 0 0.375rem;
    border-radius: 999px;
    background: var(--surface-3);
    color: var(--text-body);
    font-size: 0.75rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.um-filter-menu a.active .um-filter-count,
.um-filter-menu button.active .um-filter-count {
    background: var(--ok-bg);
    color: var(--brand-text);
}

/* ---- Dashboard card hover ----
   Same lift as the landing page cards: a small rise plus a deeper shadow,
   so the card the pointer is on separates from the grid. */
.kpi,
.chart-card {
    transition: transform 260ms var(--ease), box-shadow 260ms var(--ease);
}

.kpi:hover,
.chart-card:hover {
    transform: translateY(-5px);
    box-shadow: var(--sh-md);
}

/* The nested table card inside a chart card must not lift on its own --
   the parent already moves, and two stacked transforms read as a glitch. */
.chart-card .um-table-card:hover {
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .kpi:hover,
    .chart-card:hover {
        transform: none;
    }
}

/* ---- Leaflet maps stay under the app chrome ----
   Leaflet gives its panes and controls z-index 400-1000. Without a
   stacking context of their own they compete with the sticky top bar
   (z-index 30), and the bell dropdown and account menu got drawn behind
   the map. isolate keeps every map layer inside the map. */
.leaflet-container {
    isolation: isolate;
}

/* ============================================
   MOBILE (Android + iOS)
   Below 860px the sidebar leaves the layout and becomes a drawer that
   slides in over the page from the hamburger in the top bar. Everything
   else here is phone polish: dynamic viewport height (so iOS Safari's
   toolbar never hides the bottom of the app), notch / home-bar safe
   areas, 16px form text (anything smaller makes iOS zoom in on focus),
   and touch-sized targets.
   ============================================ */

/* Phone-only controls; the desktop layout never shows them. */
.nav-open-btn,
.sidebar-close,
.sidebar-backdrop {
    display: none;
}

/* 100vh on mobile browsers is the height with the toolbar hidden, so the
   bottom of a 100vh shell sits under it. dvh tracks what is visible, but
   older Android browsers (Samsung Internet, older WebViews) lack it --
   there the sidebar's Sign Out ended up under the address bar. So Base.html
   writes the real visible height (innerHeight) to --app-h, with dvh and
   then vh as the fallbacks before its script runs. */
:root {
    --app-h: 100vh;
}

@supports (height: 100dvh) {
    :root {
        --app-h: 100dvh;
    }
}

.app-shell,
.main-area,
.sidebar {
    height: var(--app-h);
}

.app-body {
    -webkit-tap-highlight-color: transparent;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

@media (max-width: 860px) {
    :root {
        --topbar-h: 56px;
    }

    /* ---- Sidebar as a drawer ---- */
    .sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        z-index: 3000;
        /* beats the inline width the desktop rail logic writes */
        width: min(288px, 86vw) !important;
        /* pinned to the top and bottom of what is visible, not to a height,
           so Sign Out sits above the browser's bottom edge on every phone */
        height: auto;
        padding: calc(1.2rem + env(safe-area-inset-top)) 1rem
                 calc(1rem + env(safe-area-inset-bottom))
                 calc(1rem + env(safe-area-inset-left));
        transform: translateX(-100%);
        /* hidden when shut, so its links are not tab stops off-screen */
        visibility: hidden;
        transition: transform 260ms cubic-bezier(0.32, 0.72, 0, 1),
                    visibility 0s linear 260ms;
    }

    body.nav-open .sidebar {
        transform: none;
        visibility: visible;
        box-shadow: 0 0 48px rgba(0, 0, 0, 0.35);
        transition: transform 260ms cubic-bezier(0.32, 0.72, 0, 1),
                    visibility 0s;
    }

    /* The icon-rail state means nothing in a drawer: always full labels. */
    .sidebar.collapsed .logo-text,
    .sidebar.collapsed .nav-link-text,
    .sidebar.collapsed .sign-out-text {
        display: inline;
    }

    .sidebar.collapsed .role-pill-text {
        display: flex;
    }

    .sidebar.collapsed .sidebar-logo,
    .sidebar.collapsed .role-pill,
    .sidebar.collapsed .nav-link,
    .sidebar.collapsed .sign-out {
        justify-content: flex-start;
    }

    .sidebar.collapsed .sidebar-header {
        flex-direction: row;
        gap: 0.3rem;
    }

    .sidebar-toggle {
        display: none;
    }

    .sidebar-close {
        flex-shrink: 0;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 2.5rem;
        height: 2.5rem;
        margin: -0.4rem -0.4rem -0.4rem 0;
        padding: 0;
        border: 0;
        border-radius: 10px;
        background: none;
        color: var(--side-text);
        cursor: pointer;
    }

    .sidebar-close svg {
        width: 1.25rem;
        height: 1.25rem;
    }

    .sidebar-close:active {
        background-color: rgba(255, 255, 255, 0.1);
    }

    /* Roomier rows: these are thumbs, not a pointer. */
    .sidebar .nav-link {
        min-height: 44px;
    }

    .sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 2999;
        background-color: rgba(6, 14, 10, 0.5);
        opacity: 0;
        pointer-events: none;
        transition: opacity 260ms ease;
    }

    body.nav-open .sidebar-backdrop {
        opacity: 1;
        pointer-events: auto;
    }

    /* ---- Top bar ---- */
    .global-topbar {
        height: calc(var(--topbar-h) + env(safe-area-inset-top));
        padding: env(safe-area-inset-top)
                 calc(0.6rem + env(safe-area-inset-right)) 0
                 calc(0.6rem + env(safe-area-inset-left));
        gap: 0.1rem;
    }

    .nav-open-btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 2.75rem;
        height: 2.75rem;
        margin-right: auto;
        padding: 0;
        border: 0;
        border-radius: 10px;
        background: none;
        color: var(--text-heading);
        cursor: pointer;
    }

    /* With a page title beside it, the title takes the free space instead */
    .nav-open-btn:has(+ .topbar-title) {
        margin-right: 0;
    }

    .nav-open-btn svg {
        width: 1.35rem;
        height: 1.35rem;
    }

    .nav-open-btn:active {
        background-color: var(--surface-3);
    }

    .topbar-title {
        font-size: 0.95rem;
        padding-left: 0.15rem;
    }

    .theme-toggle {
        height: 2.75rem;
    }

    .notif-bell-btn {
        width: 2.75rem;
        height: 2.75rem;
        padding: 0;
    }

    .topbar-account {
        margin-left: 0.15rem;
    }

    /* The bell dropdown spans the screen instead of hanging off the bell. */
    .notif-dropdown {
        position: fixed;
        top: calc(var(--topbar-h) + env(safe-area-inset-top) + 6px);
        left: calc(0.5rem + env(safe-area-inset-left));
        right: calc(0.5rem + env(safe-area-inset-right));
        width: auto;
        max-width: none;
        max-height: calc(var(--app-h) - var(--topbar-h) - env(safe-area-inset-top) - 1.5rem);
        transform-origin: top center;
    }

    /* Taller choices, easier to tap */
    .notif-seg-btn {
        min-height: 2.5rem;
        padding: 0.35rem 0.4rem;
    }

    /* ---- Page ---- */
    .page-content {
        padding: 1.25rem calc(1rem + env(safe-area-inset-right))
                 calc(2rem + env(safe-area-inset-bottom))
                 calc(1rem + env(safe-area-inset-left));
    }

    .page-header h2,
    .um-header h2 {
        font-size: 1.35rem;
    }

    /* iOS zooms the page into any field under 16px when it gets focus. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
    select,
    textarea {
        font-size: 16px !important;
    }

    /* Wide tables scroll sideways inside their card instead of widening
       the page. Only the innermost box scrolls: a card is made a scroller
       only when the table sits directly in it, never around a wrapper
       that already scrolls. */
    .um-table-card:not(.dt-card):has(> table),
    .table-card,
    .um-scroll-wrap,
    .sites-scroll-wrap {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    /* On desktop these wrappers also scroll vertically under a pinned
       header. On a phone that is a scroller inside the page's scroller:
       the thumb gets trapped in the table, and the frozen first column
       drifts out of line with the rest while it scrolls. Here the page
       does all vertical scrolling and the table only moves sideways (its
       first column still stays pinned for that). */
    .um-scroll-wrap,
    .table-freeze-wrap,
    #sites-scroll {
        max-height: none;
        overflow-y: visible;
    }

    /* A table of five or more columns keeps a readable width and scrolls,
       rather than squeezing every column down to an ellipsis. */
    .um-table:has(thead th:nth-child(5)) {
        min-width: 720px;
    }

    /* Search rows: the field takes the full width, the count drops below. */
    .um-search-group {
        flex-wrap: wrap;
        width: 100%;
    }

    .dt-toolbar .um-search-group {
        margin-left: 0;
    }

    .um-search-wrap {
        flex: 1 1 100%;
        min-width: 0;
        max-width: none;
    }

    /* Tall modals scroll inside the sheet instead of running off-screen. */
    .um-modal-overlay {
        padding: 1rem;
        align-items: flex-end;
    }

    .um-modal {
        width: 100%;
        max-height: calc(var(--app-h) - 2rem - env(safe-area-inset-top));
        overflow-y: auto;
        margin-bottom: env(safe-area-inset-bottom);
    }
}

/* ============================================
   TABLE SCROLLBARS
   Chrome on Windows 11 draws overlay scrollbars: thin, and only there
   while something is already scrolling. A mouse user had no bar to grab,
   so columns past the right edge of a wide table were out of reach.
   Styling the bar with ::-webkit-scrollbar makes Chromium use a classic,
   always-visible one that can be dragged. Firefox has no such pseudo
   element, so it gets the standard properties instead -- they must not
   be set in Chromium, where they would switch the styling below off.
   ============================================ */
.um-scroll-wrap::-webkit-scrollbar,
.sites-scroll-wrap::-webkit-scrollbar,
.table-freeze-wrap::-webkit-scrollbar,
.um-table-card::-webkit-scrollbar,
.table-card::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

.um-scroll-wrap::-webkit-scrollbar-track,
.sites-scroll-wrap::-webkit-scrollbar-track,
.table-freeze-wrap::-webkit-scrollbar-track,
.um-table-card::-webkit-scrollbar-track,
.table-card::-webkit-scrollbar-track {
    background-color: var(--surface-2);
}

.um-scroll-wrap::-webkit-scrollbar-thumb,
.sites-scroll-wrap::-webkit-scrollbar-thumb,
.table-freeze-wrap::-webkit-scrollbar-thumb,
.um-table-card::-webkit-scrollbar-thumb,
.table-card::-webkit-scrollbar-thumb {
    background-color: var(--text-3);
    border: 2px solid var(--surface-2);
    border-radius: 999px;
}

.um-scroll-wrap::-webkit-scrollbar-thumb:hover,
.sites-scroll-wrap::-webkit-scrollbar-thumb:hover,
.table-freeze-wrap::-webkit-scrollbar-thumb:hover,
.um-table-card::-webkit-scrollbar-thumb:hover,
.table-card::-webkit-scrollbar-thumb:hover {
    background-color: var(--text-2);
}

.um-scroll-wrap::-webkit-scrollbar-corner,
.sites-scroll-wrap::-webkit-scrollbar-corner,
.table-freeze-wrap::-webkit-scrollbar-corner,
.um-table-card::-webkit-scrollbar-corner,
.table-card::-webkit-scrollbar-corner {
    background-color: var(--surface-2);
}

@supports not selector(::-webkit-scrollbar) {
    .um-scroll-wrap,
    .sites-scroll-wrap,
    .table-freeze-wrap,
    .um-table-card,
    .table-card {
        scrollbar-width: auto;
        scrollbar-color: var(--text-3) var(--surface-2);
    }
}

/* ============================================
   FILE UPLOAD (shared)
   The upload card used by every file field: a drop zone, the chosen
   files, warnings for files that are too large or the wrong type, and
   a progress card while a form is sent. app/static/js/upload.js drives
   it; Reference Dataset uses the same card in its own page script.
   ============================================ */
/* ---- upload card ----
   A drop zone that turns into a progress readout: the file's name and
   percentage, a bar of segments that fill as it uploads, bytes sent,
   time left and speed, and Cancel. Colours come from the page tokens,
   so it follows light and dark. */
.up-card {
  --up-accent: var(--ok-solid);
  width: 100%;
  max-width: 34rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--surface-1);
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
}
.up-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.15rem 0.25rem 0.85rem;
}
.up-title {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-heading);
}
.up-title svg { width: 1.2rem; height: 1.2rem; }
.up-status {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-2);
}
.up-status i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-3);
}
.up-card[data-state="selected"] .up-status i,
.up-card[data-state="done"] .up-status i { background: var(--ok-solid); }
.up-card[data-state="uploading"] .up-status i,
.up-card[data-state="processing"] .up-status i {
  background: var(--up-accent);
  animation: up-blink 1s ease-in-out infinite;
}
.up-card[data-state="error"] .up-status { color: var(--danger-text); }
.up-card[data-state="error"] .up-status i { background: var(--danger-solid); }
@keyframes up-blink { 50% { opacity: 0.25; } }

.up-body {
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-2);
}

.up-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  padding: 1.6rem 1rem;
  border: 1.5px dashed var(--line-strong);
  border-radius: 10px;
  text-align: center;
  cursor: pointer;
  transition: border-color 150ms ease, background-color 150ms ease;
}
.up-drop:hover,
.up-drop.is-over {
  border-color: var(--up-accent);
  background: var(--ok-bg);
}
.up-drop:focus-within {
  outline: 2px solid var(--brand-text);
  outline-offset: 2px;
}
.up-input {
  position: absolute;
  top: 0;
  left: 0;
  /* !important: form rules such as .um-field input set width: 100%,
     which stretched this hidden input past the right edge of the page */
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  border: 0 !important;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}
.up-drop-icon {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 12px;
  background: var(--surface-3);
  color: var(--text-heading);
}
.up-drop-icon svg { width: 1.2rem; height: 1.2rem; }
.up-drop-text { font-weight: 600; color: var(--text-heading); }
.up-browse { color: var(--brand-text); }
.up-drop-hint {
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.75rem;
  color: var(--text-2);
}

.up-file-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.up-file-name {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  flex: 1;
  color: var(--text-strong);
  font-size: 0.9rem;
}
.up-file-name svg { flex: none; width: 1rem; height: 1rem; color: var(--text-2); }
.up-file-name span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.up-pct {
  font-size: 1.9rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text-heading);
  font-variant-numeric: tabular-nums;
}
.up-remove {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text-2);
  cursor: pointer;
}
.up-remove:hover { background: var(--surface-3); color: var(--text-heading); }
.up-remove svg { width: 1rem; height: 1rem; }
.up-card[data-state="uploading"] .up-remove,
.up-card[data-state="processing"] .up-remove { display: none; }

/* the bar: a row of segments; filled ones take the accent, the one being
   filled takes a lighter share of it */
.up-bar {
  display: grid;
  grid-template-columns: repeat(24, 1fr);
  gap: 5px;
  height: 2.4rem;
  margin: 0.9rem 0 0.6rem;
}
.up-bar span {
  border-radius: 3px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  transition: background-color 160ms ease, border-color 160ms ease;
}
.up-bar span.is-on { background: var(--up-accent); border-color: var(--up-accent); }
.up-bar span.is-part { background: var(--ok-line); border-color: var(--ok-line); }
.up-card[data-state="selected"] .up-bar,
.up-card[data-state="selected"] .up-pct { display: none; }
.up-card[data-state="processing"] .up-bar span.is-on { animation: up-blink 1.1s ease-in-out infinite; }

.up-meta {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.78rem;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.up-bytes,
.up-speed { color: var(--brand-text); }

.up-actions { display: flex; gap: 0.6rem; margin-top: 1rem; }
.up-btn {
  flex: 1;
  min-height: 2.5rem;
  padding: 0 1rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-1);
  color: var(--text-heading);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}
.up-btn:hover { background: var(--surface-3); }
.up-btn-sm { flex: none; min-height: 2.2rem; }

.up-error {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem 0.75rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--danger-line);
  border-radius: 10px;
  background: var(--danger-bg);
  color: var(--danger-strong);
}
.up-error[hidden] { display: none; }
.up-error-icon svg { width: 1.25rem; height: 1.25rem; margin-top: 0.1rem; }
.up-error strong { display: block; font-size: 0.9rem; }
.up-error p { margin: 0.2rem 0 0; font-size: 0.85rem; color: var(--text-body); }
.up-error .up-btn { grid-column: 2; justify-self: start; margin-top: 0.4rem; }

.up-drop[hidden],
.up-file[hidden],
.up-actions[hidden],
.up-list[hidden],
.up-zone .up-error[hidden] { display: none; }

/* ---- a file field turned into a drop zone (upload.js) ---- */
.up-zone .up-drop { padding: 1.2rem 1rem; }
.up-zone .up-drop-icon { width: 2.4rem; height: 2.4rem; }

.up-list {
    list-style: none;
    margin: 0.6rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.4rem;
}
.up-list li {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.4rem 0.5rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface-1);
    font-size: 0.85rem;
    color: var(--text-strong);
}
.up-list li svg { flex: none; width: 0.95rem; height: 0.95rem; color: var(--text-2); }
.up-list .up-li-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.up-list .up-li-size {
    font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
    font-size: 0.75rem;
    color: var(--text-2);
}
.up-list .up-remove { width: 1.8rem; height: 1.8rem; }
.up-zone .up-error { margin-top: 0.6rem; grid-template-columns: auto 1fr auto; align-items: start; }
.up-zone .up-error .up-btn { grid-column: auto; margin-top: 0; }
.up-zone .up-error ul { margin: 0.3rem 0 0; padding-left: 1.1rem; font-size: 0.82rem; color: var(--text-body); }

/* ---- the progress card while a form is being sent ---- */
.up-overlay {
    position: fixed;
    inset: 0;
    z-index: 5000;
    display: grid;
    place-items: center;
    padding: 1rem;
    background: rgba(8, 18, 12, 0.45);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.up-overlay .up-card { width: min(100%, 30rem); }
.up-overlay .up-remove { display: none; }

@media (prefers-reduced-motion: reduce) {
    .up-status i, .up-bar span { animation: none !important; transition: none; }
}

/* ---- dismissible messages ----
   Flash messages carry an X on the right that removes them. */
.um-alert.is-dismissible {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    transition: opacity 180ms ease, transform 180ms ease;
}
.um-alert.is-dismissible .um-alert-text { flex: 1; min-width: 0; }
.um-alert.is-leaving { opacity: 0; transform: translateY(-4px); }

.alert-close {
    flex: none;
    display: inline-grid;
    place-items: center;
    width: 1.6rem;
    height: 1.6rem;
    margin: -0.2rem -0.35rem -0.2rem auto;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: inherit;
    opacity: 0.7;
    cursor: pointer;
    transition: opacity 150ms ease, background-color 150ms ease;
}
.alert-close:hover { opacity: 1; background: rgba(0, 0, 0, 0.06); }
:root[data-theme="dark"] .alert-close:hover { background: rgba(255, 255, 255, 0.08); }
.alert-close:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
.alert-close svg { width: 0.95rem; height: 0.95rem; }
.flash-message .alert-close svg { width: 0.95rem; height: 0.95rem; margin: 0; }

/* ======================================================================
   HELP PANEL (Base.html)
   The "?" button in the top bar opens this page's FAQ in a panel that
   slides in from the right. Full width on phones.
   ====================================================================== */
.help-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--text-body);
    cursor: pointer;
    padding: 0.5rem;
    border-radius: 8px;
}

.help-btn svg {
    /* Same size as the bell next to it. */
    width: 1.375rem;
    height: 1.375rem;
}

.help-btn:hover,
.help-btn[aria-expanded="true"] {
    background-color: var(--surface-3);
}

.help-btn:focus-visible {
    outline: 2px solid var(--brand-solid);
    outline-offset: 2px;
}

.help-backdrop {
    position: fixed;
    inset: 0;
    z-index: 3900;
    background-color: rgba(15, 23, 42, 0.45);
}

.help-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 3950;
    display: flex;
    flex-direction: column;
    width: min(28rem, 100%);
    background-color: var(--surface-1);
    border-left: 1px solid var(--line);
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.12);
    animation: help-slide-in 0.2s ease-out;
}

.help-panel[hidden],
.help-backdrop[hidden] {
    display: none;
}

@keyframes help-slide-in {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}

/* the page behind does not scroll while the panel is open */
body.help-open {
    overflow: hidden;
}

.help-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-height: var(--topbar-h);
    padding: 0 1.25rem;
    border-bottom: 1px solid var(--line);
}

.help-panel-head h2 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text-heading);
}

.help-panel-head .um-modal-close {
    width: 2.25rem;
    height: 2.25rem;
}

.help-panel-body {
    flex: 1;
    overflow-y: auto;
    padding: 0.5rem 1.25rem 2rem;
}

.faq-group {
    margin: 1.25rem 0 0.4rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-2);
}

.faq-item {
    border-bottom: 1px solid var(--line);
}

.faq-item summary {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.8rem 0;
    cursor: pointer;
    list-style: none;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--text-heading);
}

.faq-item summary::-webkit-details-marker {
    display: none;
}

/* a chevron that turns when the answer is open */
.faq-item summary::after {
    content: "";
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    margin-top: 0.35rem;
    border-right: 2px solid var(--text-2);
    border-bottom: 2px solid var(--text-2);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.faq-item[open] summary::after {
    transform: rotate(-135deg);
    margin-top: 0.55rem;
}

.faq-item summary:hover {
    color: var(--brand-text);
}

.faq-item summary:focus-visible {
    outline: 2px solid var(--brand-solid);
    outline-offset: 2px;
    border-radius: 4px;
}

.faq-answer {
    padding: 0 0 0.9rem;
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--text-body);
}

.faq-answer p {
    margin: 0 0 0.5rem;
}

.faq-answer ul {
    margin: 0 0 0.5rem;
    padding-left: 1.2rem;
}

.faq-answer li {
    margin-bottom: 0.25rem;
}

.faq-answer a {
    color: var(--brand-text);
    font-weight: 600;
}

@media (max-width: 860px) {
    .help-btn {
        width: 2.75rem;
        height: 2.75rem;
        padding: 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .help-panel { animation: none; }
    .faq-item summary::after { transition: none; }
}

/* ======================================================================
   FIRST-TIME TOUR (partials/_tour.html)
   Driver.js's popover, restyled with the app's colours so it follows
   the light and dark theme. Scoped to .pno-tour (its popoverClass).
   ====================================================================== */
.driver-popover.pno-tour {
    max-width: min(22rem, calc(100vw - 2rem));
    padding: 1.1rem 1.15rem 1rem;
    border-radius: 12px;
    background-color: var(--surface-1);
    color: var(--text-body);
    border: 1px solid var(--line);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}

.driver-popover.pno-tour * {
    font-family: inherit;
}

.driver-popover.pno-tour .driver-popover-title {
    padding-right: 1.5rem;
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1.35;
    color: var(--text-heading);
}

.driver-popover.pno-tour .driver-popover-description {
    margin-top: 0.4rem;
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--text-body);
}

.driver-popover.pno-tour .driver-popover-progress-text {
    font-size: 0.8rem;
    color: var(--text-2);
}

.driver-popover.pno-tour .driver-popover-close-btn {
    width: 2.25rem;
    height: 2.25rem;
    font-size: 1.25rem;
    color: var(--text-3);
}

.driver-popover.pno-tour .driver-popover-close-btn {
    top: 0.4rem;
    right: 0.4rem;
    border-radius: 8px;
}

.driver-popover.pno-tour .driver-popover-close-btn:hover,
.driver-popover.pno-tour .driver-popover-close-btn:focus-visible {
    color: var(--text-heading);
    background-color: var(--surface-3);
    outline: none;
}

/* Phones: dock the box to the top or bottom edge, full width, so it never
   covers what it points at or runs off the screen (_tour.html). The
   !important beats the position Driver.js writes inline. */
@media (max-width: 860px) {
    .driver-popover.pno-tour.pno-dock-top,
    .driver-popover.pno-tour.pno-dock-bottom {
        left: 0.75rem !important;
        right: 0.75rem !important;
        width: auto;
        min-width: 0;
        max-width: none;
    }

    .driver-popover.pno-tour.pno-dock-top {
        top: 0.75rem !important;
        bottom: auto !important;
    }

    .driver-popover.pno-tour.pno-dock-bottom {
        top: auto !important;
        bottom: 0.75rem !important;
    }

    .driver-popover.pno-tour.pno-dock-top .driver-popover-arrow,
    .driver-popover.pno-tour.pno-dock-bottom .driver-popover-arrow {
        display: none;
    }
}

.driver-popover.pno-tour .driver-popover-footer button {
    min-height: 2.5rem;
    padding: 0.5rem 1rem;
    border-radius: 8px;
    border: 1px solid var(--line-strong);
    background-color: var(--surface-1);
    color: var(--text-strong);
    font-size: 0.9rem;
    font-weight: 600;
    text-shadow: none;
}

.driver-popover.pno-tour .driver-popover-footer button:hover {
    background-color: var(--surface-3);
}

/* Next and Finish are the main action */
.driver-popover.pno-tour .driver-popover-footer .driver-popover-next-btn {
    border-color: var(--brand-solid);
    background-color: var(--brand-solid);
    color: #ffffff;
}

.driver-popover.pno-tour .driver-popover-footer .driver-popover-next-btn:hover {
    background-color: var(--brand-deep);
}

.driver-popover.pno-tour .driver-popover-footer button:focus-visible {
    outline: 2px solid var(--brand-solid);
    outline-offset: 2px;
}

.driver-popover.pno-tour .driver-popover-navigation-btns {
    gap: 0.5rem;
}

/* the little pointer takes the popover's colour */
.driver-popover.pno-tour .driver-popover-arrow {
    border-color: var(--surface-1);
}
.driver-popover.pno-tour .driver-popover-arrow-side-left   { border-right-color: transparent; border-top-color: transparent; border-bottom-color: transparent; }
.driver-popover.pno-tour .driver-popover-arrow-side-right  { border-left-color: transparent; border-top-color: transparent; border-bottom-color: transparent; }
.driver-popover.pno-tour .driver-popover-arrow-side-top    { border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; }
.driver-popover.pno-tour .driver-popover-arrow-side-bottom { border-left-color: transparent; border-top-color: transparent; border-right-color: transparent; }

/* "Take the tour" at the top of the help panel */
.help-tour-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    margin: 0.75rem 0 0.25rem;
    padding: 0.65rem 1rem;
    border: 1px solid var(--ok-line);
    border-radius: 10px;
    background-color: var(--ok-bg);
    color: var(--brand-text);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
}

.help-tour-btn svg {
    width: 1.1rem;
    height: 1.1rem;
}

.help-tour-btn:hover {
    border-color: var(--brand-solid);
}

.help-tour-btn:focus-visible {
    outline: 2px solid var(--brand-solid);
    outline-offset: 2px;
}
