/* =====================================================================
   AllowMeIn Access — single stylesheet.
   Built mobile-first; the "driving" view scales up to fill the screen.
   ===================================================================== */

:root {
    --bg:         #17191d;
    --bg-2:       #22252c;
    --bg-3:       #2b2f38;
    --line:       #383c46;
    --fg:         #f3f4f7;
    --mid:        #a5a9b3;
    --dim:        #707086;
    --ok:         #1b944b;
    --ok-bright:  #4ade80;
    --warn:       #f5a524;
    --bad:        #dc2626;
    --bad-bright: #f87171;
    --info:       #295797;
    --accent:     #3b82f6;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--fg);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
    min-height: 100vh;
}

/* Bottom-nav room + iPhone home-bar safe area.
   Calibrated to the actual nav height (icon ~22px + 4px gap + label ~13px
   + 10px top padding + 8px bottom padding = ~68px), plus a comfortable
   buffer so the last card never tucks under. */
body {
    padding-bottom: calc(96px + env(safe-area-inset-bottom));
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; }

/* ======================================================================
   TOP BAR
   Mobile: logo + proximity chip + menu button.  User info lives behind
   the menu button so it never overflows.
   Desktop: everything inline.
   ====================================================================== */

.topbar {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 10px;
    align-items: center;
    padding: 10px 14px;
    background: rgba(34, 37, 44, 0.92);
    border-bottom: 1px solid var(--line);
    position: sticky;
    top: 0;
    z-index: 50;
    min-height: 60px;
    backdrop-filter: blur(12px);
}
.topbar .logo { display: flex; align-items: center; }
.topbar .logo img { height: 38px; display: block; }

.topbar .badges {
    display: flex;
    gap: 6px;
    flex-wrap: nowrap;
    overflow: hidden;
    justify-self: start;
    align-items: center;
}

.topbar .menu-btn {
    border: 1px solid var(--line);
    background: var(--bg-3);
    color: var(--fg);
    width: 36px; height: 36px;
    border-radius: 10px;
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s;
}
.topbar .menu-btn:hover { background: var(--line); }

.user-menu {
    position: fixed;
    top: 60px;
    right: 12px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(0,0,0,0.5);
    padding: 14px 16px;
    min-width: 200px;
    z-index: 60;
}
.user-menu .user-name {
    display: block;
    font-weight: 600;
    margin-bottom: 2px;
}
.user-menu .user-email {
    display: block;
    color: var(--mid);
    font-size: 12px;
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--line);
}
.user-menu a {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 0;
    color: var(--fg);
    text-decoration: none;
}
.user-menu a:hover { color: var(--accent); text-decoration: none; }
.user-menu a.danger { color: var(--bad-bright); }
.user-menu a.danger:hover { color: var(--bad); }
.user-menu a .ico {
    width: 18px;
    text-align: center;
    margin: 0;
    color: var(--mid);
    font-family: ui-monospace, "SF Mono", monospace;
}
.user-menu[hidden] { display: none; }

@media (min-width: 720px) {
    .topbar { padding: 12px 18px; }
    .topbar .logo img { height: 44px; }
}

/* ======================================================================
   BADGES
   ====================================================================== */

.badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    background: var(--bg-3);
    color: var(--mid);
    border: 1px solid var(--line);
    white-space: nowrap;
    line-height: 1.4;
    text-transform: uppercase;
}
.badge-lan   { background: #064e3b; color: #d1fae5; border-color: #065f46; }
.badge-near  { background: #1e40af; color: #dbeafe; border-color: #1d4ed8; }
.badge-far   { background: #92400e; color: #fef3c7; border-color: #b45309; }
.badge-off   { background: #7f1d1d; color: #fee2e2; border-color: #991b1b; }
.badge-nofix { background: #4b5563; color: #e5e7eb; border-color: #6b7280; }
.badge-admin { background: var(--accent); color: white; border-color: var(--accent); }
/* ---- real-time traffic lights ----
   Two directional arrows like network activity LEDs:
     green ▲ = uplink   (pulses when we SEND over the WebSocket)
     red   ▼ = downlink (pulses when we RECEIVE over the WebSocket)
   Both sit dim by default. realtime.js restarts the .tx / .rx
   animation on every packet, so each pulse is a distinct flash even
   under rapid traffic. */
.conn-lights {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    padding: 2px 5px;
    border-radius: 999px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    line-height: 1;
}
.conn-arrow {
    font-size: 11px;
    line-height: 1;
}
/* dim resting state */
.conn-arrow.conn-up   { color: #1f4d33; }   /* dim green */
.conn-arrow.conn-down { color: #5a2626; }   /* dim red   */
/* socket open — resting arrows a touch brighter */
.conn-lights.live .conn-arrow.conn-up   { color: #2f7d4f; }
.conn-lights.live .conn-arrow.conn-down { color: #8a3a3a; }

/* A single pulse = bright flash, then fade back to the resting colour.
   The class is re-applied per packet (realtime.js forces a reflow),
   so the animation replays each time and the light visibly blinks. */
.conn-lights.tx .conn-arrow.conn-up   { animation: conn-pulse-up   0.5s ease-out; }
.conn-lights.rx .conn-arrow.conn-down { animation: conn-pulse-down 0.5s ease-out; }

@keyframes conn-pulse-up {
    0%   { color: #86efac; text-shadow: 0 0 9px rgba(74,222,128,1); }
    100% { color: #2f7d4f; text-shadow: 0 0 0   rgba(74,222,128,0); }
}
@keyframes conn-pulse-down {
    0%   { color: #fca5a5; text-shadow: 0 0 9px rgba(248,113,113,1); }
    100% { color: #8a3a3a; text-shadow: 0 0 0   rgba(248,113,113,0); }
}

/* ======================================================================
   BOTTOM NAV
   ====================================================================== */

.bottomnav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    display: grid;
    grid-template-columns: repeat(4, 1fr);   /* default; overridden below */
    background: rgba(34, 37, 44, 0.92);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
    z-index: 50;
}
.bottomnav.nav-cols-2 { grid-template-columns: repeat(2, 1fr); }
.bottomnav.nav-cols-3 { grid-template-columns: repeat(3, 1fr); }
.bottomnav.nav-cols-4 { grid-template-columns: repeat(4, 1fr); }
.bottomnav a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    text-align: center;
    padding: 10px 4px 8px;
    color: var(--dim);
    font-size: 11px;
    letter-spacing: 0.03em;
    text-decoration: none;
    border-top: 2px solid transparent;
    transition: color 0.15s, background 0.15s;
}
.bottomnav a:hover { color: var(--mid); text-decoration: none; }
.bottomnav a.on {
    color: var(--fg);
    border-top-color: var(--accent);
    background: linear-gradient(180deg, rgba(59,130,246,0.10), transparent 60%);
}
.bottomnav a .glyph {
    font-size: 22px;
    line-height: 1;
    font-family: ui-monospace, "SF Mono", monospace;
}
.bottomnav a.on .glyph { transform: scale(1.05); }
.bottomnav a .lbl { font-weight: 500; }

/* Generic icon glyph used inside .btn — keeps it from inheriting bold/sized text */
.btn .ico {
    margin-right: 6px;
    font-family: ui-monospace, "SF Mono", monospace;
    font-weight: normal;
}
.btn.icon-only .ico { margin-right: 0; }
.btn.icon-only { padding: 8px 10px; min-width: 36px; }

/* ======================================================================
   GENERIC LAYOUT
   ====================================================================== */

main { padding: 14px; max-width: 1100px; margin: 0 auto; }
h1, h2, h3 { font-weight: 600; }
h2 { margin: 0 0 4px; font-size: 22px; }
h3 { margin: 24px 0 8px; font-size: 16px; color: var(--mid); }

.crumbs { font-size: 13px; color: var(--mid); margin-bottom: 18px; }
.crumbs a { color: var(--accent); }

/* activity page — event counter on the left, CSV export icon on the right */
.audit-summary {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 18px;
}
.audit-summary .crumbs { margin-bottom: 0; }
.audit-summary .export-csv {
    margin-left: auto;
    font-size: 13px;
    color: var(--accent);
    white-space: nowrap;
}
.audit-summary .export-csv:hover { text-decoration: underline; }

/* active search criteria — chips shown below the filter form */
.filter-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 12px 0 4px;
}
.filter-chips-label {
    font-size: 12px;
    color: var(--mid);
    margin-right: 2px;
}
/* a chip is a link — clicking it removes that one filter */
.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 8px 3px 4px;
    border-radius: 999px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    font-size: 12px;
    color: var(--fg);
    text-decoration: none;
    cursor: pointer;
    transition: border-color 0.12s, background 0.12s;
}
.filter-chip:hover {
    border-color: var(--bad);
    background: var(--bg-2);
}
.filter-chip .fc-key {
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--accent);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.filter-chip .fc-x {
    font-size: 10px;
    color: var(--dim);
}
.filter-chip:hover .fc-x { color: var(--bad-bright); }

/* ---- admin section tab strip ----
   Sits at the top of every admin page; horizontally scrollable on
   mobile so all sections stay reachable on a phone. */
.adminnav {
    display: flex;
    gap: 4px;
    margin: 4px 0 18px;
    padding-bottom: 6px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border-bottom: 1px solid var(--line);
}
.adminnav a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--mid);
    text-decoration: none;
    white-space: nowrap;
    border: 1px solid transparent;
}
.adminnav a:hover {
    color: var(--fg);
    background: var(--bg-3);
    text-decoration: none;
}
.adminnav a.on {
    color: var(--fg);
    background: var(--bg-3);
    border-color: var(--accent);
}
.adminnav .adminnav-glyph {
    font-family: ui-monospace, "SF Mono", monospace;
    font-size: 14px;
}

/* ---- audit search / filter bar ---- */
.audit-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    margin: 4px 0 12px;
}
.audit-filter input[type=text] {
    flex: 1 1 220px;
    min-width: 160px;
}
.audit-filter input[type=date],
.audit-filter select {
    flex: 0 0 auto;
    width: auto;
    min-width: 110px;
}
.audit-filter .btn { flex: 0 0 auto; }

.banner {
    border-radius: 8px;
    padding: 12px 14px;
    margin-bottom: 12px;
    font-size: 14px;
}
.banner-info { background: #1e3a5f; color: #dbeafe; border: 1px solid #2563eb; }
.banner-warn { background: #78350f; color: #fef3c7; border: 1px solid #d97706; }
.banner-bad  { background: #7f1d1d; color: #fee2e2; border: 1px solid #dc2626; }
.banner-ok   { background: #064e3b; color: #d1fae5; border: 1px solid #10b981; }

/* GPS-off warning — sticky banner at the very top of the page. Shown
   only when the user has denied / disabled location and is not on the
   trusted LAN. Self-dismiss per page session. */
.gps-off-banner {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 100;                  /* above the topbar (z-index 50) */
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    font-size: 13px;
    font-weight: 600;
    background: #78350f;
    color: #fef3c7;
    border-bottom: 1px solid #d97706;
    box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.gps-off-banner-text { flex: 1; line-height: 1.4; }
.gps-off-banner-x {
    flex: none;
    background: transparent;
    border: 1px solid rgba(254,243,199,0.4);
    color: #fef3c7;
    border-radius: 6px;
    padding: 2px 10px;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}
.gps-off-banner-x:hover { background: rgba(254,243,199,0.12); }
.gps-off-banner[hidden] { display: none; }
/* shove the page content down when the banner is up so it doesn't
   cover the topbar */
body.gps-off-banner-up { padding-top: 44px; }

/* ======================================================================
   FORM CONTROLS — note: NO global "input { width: 100% }"; that broke
   every checkbox. Only block-level controls get full width.
   ====================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 16px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: var(--bg-3);
    color: var(--fg);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
    text-decoration: none;
}
.btn:hover { background: var(--bg-2); text-decoration: none; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: white; }
.btn-primary:hover { background: #2563eb; }
.btn-danger  { background: var(--bad); border-color: var(--bad); color: white; }
.btn-small { padding: 6px 10px; font-size: 12px; }
.btn-block { display: flex; width: 100%; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:disabled:hover { background: var(--bg-3); }

/* ---- hold-to-confirm button (login, etc.) ----
   A full-width button the user must press and hold. A left-to-right
   fill shows progress; AMI.holdButton() drives the .hold-fill width. */
.hold-btn {
    position: relative;
    width: 100%;
    padding: 16px 18px;
    border: 1px solid var(--accent);
    border-radius: 10px;
    background: var(--bg-3);
    color: var(--fg);
    font: inherit;
    font-size: 16px;
    font-weight: 700;
    cursor: pointer;
    overflow: hidden;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.08s ease;
}
.hold-btn:active { transform: scale(0.99); }
.hold-btn.disabled { opacity: 0.55; cursor: default; }
.hold-btn .hold-fill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 0%;
    background: var(--accent);
    z-index: 0;
}
.hold-btn .hold-label {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.hold-btn.holding { color: #fff; }
.hold-btn.holding .ico { color: #fff; }

input[type=text], input[type=email], input[type=tel], input[type=password],
input[type=number], input[type=search], input[type=url], input[type=date],
select, textarea {
    display: block;
    width: 100%;
    padding: 10px 12px;
    background: var(--bg-3);
    color: var(--fg);
    border: 1px solid var(--line);
    border-radius: 6px;
    font-size: 14px;
    font-family: inherit;
}
textarea { min-height: 80px; resize: vertical; }

/* Date inputs: keep the native control on a dark background, and make
   the calendar-picker glyph visible against it (it ships black). */
input[type=date] {
    color-scheme: dark;
    line-height: normal;
}
input[type=date]::-webkit-calendar-picker-indicator {
    filter: invert(0.8);
    cursor: pointer;
}

/* Override Chrome's autofill yellow tint that breaks dark-theme contrast. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 30px var(--bg-3) inset !important;
    -webkit-text-fill-color: var(--fg) !important;
    caret-color: var(--fg);
    transition: background-color 0s 600000s, color 0s 600000s;
}

input[type=radio] {
    /* keep the native size; never let "width: 100%" leak in */
    width: 18px;
    height: 18px;
    margin: 0;
    accent-color: var(--accent);
    vertical-align: middle;
}

/* All checkboxes render as switches. appearance:none + a sliding knob
   means every <input type=checkbox> in the app becomes a toggle with
   no per-page markup changes. */
input[type=checkbox] {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    flex: none;
    width: 34px;
    height: 19px;
    margin: 0;
    padding: 0;
    border-radius: 999px;
    /* off = red, on = blue (see :checked below) */
    background: var(--bad);
    border: 1px solid var(--bad);
    cursor: pointer;
    vertical-align: middle;
    transition: background .15s ease, border-color .15s ease;
}
input[type=checkbox]::before {
    content: "";
    position: absolute;
    top: 1px;
    left: 1px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: #fff;
    transition: transform .15s ease, background .15s ease;
}
input[type=checkbox]:checked {
    background: var(--accent);
    border-color: var(--accent);
}
input[type=checkbox]:checked::before {
    transform: translateX(15px);
    background: #fff;
}
input[type=checkbox]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

label.field { display: block; margin-bottom: 12px; }
label.field > span { display: block; color: var(--mid); font-size: 13px; margin-bottom: 4px; }

/* Inline checkbox/radio with label */
label.check {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 6px 0;
    font-size: 14px;
    cursor: pointer;
    color: var(--fg);
    user-select: none;
}
label.check input { flex: none; }

/* ======================================================================
   DOOR TILES — icon-led
   A big door-type icon carries recognition; the name is small below it.
   Interaction is device-chosen: .dtile-click (desktop, single click) or
   .dtile-slide (touch, slide a fill across the tile to activate).
   Colour = state: green = locked/safe, red = unlocked, blue = gate.
   ====================================================================== */

/* The door grid is multi-column on wide screens, one column on a
   phone. The tile itself is the SAME bar layout everywhere — only the
   number of columns changes. */
.door-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 12px;
    margin-top: 12px;
}

/* ---- door tile: a horizontal BAR (icon panel left, text right) ----
   Same look on desktop and mobile. */
.dtile {
    --t1: #20a558;            /* tile gradient top    */
    --t2: #137a40;            /* tile gradient bottom */
    --accent-soft: rgba(255,255,255,0.16);
    appearance: none; -webkit-appearance: none;
    font: inherit; color: white;
    cursor: pointer;
    position: relative;
    border: none;
    border-radius: 12px;
    overflow: hidden;
    isolation: isolate;
    /* flex column holding the name+status, vertically centred,
       left-padded to clear the absolute icon panel */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    padding: 12px 14px 12px 78px;
    min-height: 64px;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    background:
        radial-gradient(120% 100% at 0% 0%, var(--accent-soft), transparent 60%),
        linear-gradient(165deg, var(--t1), var(--t2));
    box-shadow:
        0 5px 14px rgba(0,0,0,0.34),
        0 2px 0 rgba(0,0,0,0.4),
        inset 0 1px 0 rgba(255,255,255,0.16);
    transition: transform 0.09s ease, filter 0.15s ease;
}
.dtile:hover  { filter: brightness(1.06); }
.dtile:active { transform: translateY(1px) scale(0.98); }

/* ---- icon panel: pinned to the full left side of the bar ---- */
.dtile-badge {
    position: absolute;
    z-index: 2;
    left: 0; top: 0; bottom: 0;
    width: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.32);
    border-radius: 12px 0 0 12px;
    padding: 12px;
}
.dtile-icon {
    width: 100%; height: 100%;
    max-height: 44px;
    fill: none;
    stroke: white;
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
}

/* ---- labels — name over status, left-aligned ---- */
.dtile .door-name {
    z-index: 2;
    max-width: 100%;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.15;
    text-align: left;
    letter-spacing: 0.01em;
    text-shadow: 0 1px 2px rgba(0,0,0,0.35);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dtile .door-sub {
    z-index: 2;
    margin-top: 1px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    opacity: 0.65;
}
.dtile .door-glyph {
    position: absolute;
    z-index: 2;
    top: 8px; right: 10px;
    font-size: 12px;
    opacity: 0.6;
    line-height: 1;
    font-family: ui-monospace, "SF Mono", monospace;
}

/* ---- state colours ----
   Every tile colors by its live relay state — state-off uses the
   inactive color, state-on the active color. Momentary devices follow
   the same rule (guardian briefly reports them on). Per-device colors
   arrive as --door-active / --door-inactive (set by doors.js from the
   admin Colors fields); the hard-coded values are the fallback when a
   device has none. --t2 is a darker shade so the gradient keeps depth. */
.dtile.state-off {                                    /* inactive state */
    --t1: var(--door-inactive, #20a558);
    --t2: color-mix(in srgb, var(--t1) 70%, #000);
}
.dtile.state-on  {                                    /* active state   */
    --t1: var(--door-active, #ef4444);
    --t2: color-mix(in srgb, var(--t1) 70%, #000);
}

/* ---- denied ---- */
.dtile.denied {
    --t1: #4b5563; --t2: #374151;
    color: #d1d5db;
    cursor: not-allowed;
    box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.06);
}
.dtile.denied:hover  { filter: none; }
.dtile.denied:active { transform: none; }
.dtile.denied .dtile-icon { stroke: #9ca3af; }
.dtile.denied .door-sub   { color: #fca5a5; opacity: 1; }

/* ---- SLIDE mode (touch): a fill follows the finger across the tile ----
   .dtile-fill grows left→right as the user drags; JS sets its width. */
.dtile-fill {
    position: absolute;
    z-index: 1;                 /* above the gradient, below icon+text */
    left: 0; top: 0; bottom: 0;
    width: 0%;
    background: rgba(255,255,255,0.85);
    pointer-events: none;
}
/* while sliding, hint the gesture and dim the resting tile slightly */
.dtile-slide { touch-action: pan-y; }   /* let vertical scroll pass through */
.dtile-slide.sliding { cursor: grabbing; }

/* slide completed — brief confirming flash */
.dtile.slide-complete { animation: dtile-fired 0.6s ease-out; }
@keyframes dtile-fired {
    0%   { box-shadow: 0 0 0 0 rgba(74,222,128,0),   0 5px 14px rgba(0,0,0,0.34); }
    35%  { box-shadow: 0 0 0 7px rgba(74,222,128,0.6), 0 5px 14px rgba(0,0,0,0.34); }
    100% { box-shadow: 0 0 0 0 rgba(74,222,128,0),   0 5px 14px rgba(0,0,0,0.34); }
}

/* ---- in-flight feedback ---- */
.dtile.changing { animation: dtile-changing 1s ease-in-out infinite; }
@keyframes dtile-changing {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* ---- swipe-to-activate hint (touch only; JS unhides it) ---- */
.swipe-hint {
    margin: 8px 0 4px;
    font-size: 13px;
    font-weight: 600;
    color: var(--accent);
    letter-spacing: 0.02em;
}

/* On a phone the door grid drops to a single column of bars. The bar
   tile itself is the same — defined once in the base rules above. */
@media (max-width: 720px) {
    .door-grid {
        grid-template-columns: 1fr;
        gap: 10px;
    }
}

/* ======================================================================
   DRIVING VIEW
   Big icon tiles. On mobile the grid fills the screen — every door
   tile sized so they all fit with no scrolling.
   ====================================================================== */

/* Desktop driving — the same bar tiles, just larger, in a multi-column
   grid. (Mobile driving fills the viewport — overridden below.) */
.driving {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 16px;
    max-width: 1100px;
    margin: 12px auto 0;
}
.driving .dtile {
    min-height: 110px;
    border-radius: 18px;
    padding-left: 108px;          /* wider icon panel */
}
.driving .dtile .dtile-badge {
    width: 92px;
    border-radius: 18px 0 0 18px;
}
.driving .dtile .dtile-icon { max-height: 60px; }
.driving .dtile .door-name  { font-size: 22px; }
.driving .dtile .door-sub   { font-size: 11px; }
.driving .dtile .door-glyph { font-size: 14px; top: 12px; right: 14px; }

/* Driving on mobile: same bar tiles, but the grid fills the viewport
   exactly — no scroll, no dead space. driving-main is sized to the gap
   between the top bar and the bottom nav; the tiles divide that space
   into equal-height rows. */
@media (max-width: 720px) {
    /* the driving page does NOT need the global bottom-nav padding —
       driving-main reserves the nav space itself. Without this the
       page gets that padding AND the reserved gap = phantom scroll. */
    body.page-driving { padding-bottom: 0; }

    .driving-main {
        /* exact gap: viewport − top bar (52px) − bottom nav */
        height: calc(100dvh - 52px - 64px - env(safe-area-inset-bottom));
        display: flex;
        flex-direction: column;
        padding: 14px;
        overflow: hidden;                 /* belt-and-braces: never scroll */
    }
    .driving {
        flex: 1;
        grid-template-columns: 1fr;       /* one column */
        grid-auto-rows: 1fr;              /* equal rows fill the height */
        gap: 10px;
        margin: 8px 0 0;
        min-height: 0;
    }
    /* the tile is already a bar (base rules) — here it just grows to
       fill its grid row instead of the base 64px min-height */
    .driving .dtile {
        min-height: 0;
        padding-left: 96px;
    }
    .driving .dtile .dtile-badge { width: 80px; }
    .driving .dtile .dtile-icon  { max-height: 52px; }
}

/* ======================================================================
   LIGHTS — bulb grid + advanced controls
   ====================================================================== */

.light-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: 10px;
    margin-top: 12px;
}

@keyframes spin { to { transform: rotate(360deg); } }
/* a light is a card: name on top, then explicit On / Off buttons */
.light-tile {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    border-radius: 14px;
    border: 1px solid var(--line);
    background: var(--bg-2);
}
.light-tile .light-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--fg);
    text-align: center;
}
/* On (top) / Off (bottom) — one connected segmented control: shared
   border between them, no gap. Each button rounds its own OUTER corners
   (no overflow:hidden, so a lit button's glow isn't clipped). */
.light-btns { display: grid; }
.light-on, .light-off {
    appearance: none; -webkit-appearance: none;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 10px;
    border: 1px solid var(--line);
    background: var(--bg-3);
    color: var(--mid);
    transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.light-on  { border-radius: 9px 9px 0 0; }   /* top corners    */
.light-off {
    border-radius: 0 0 9px 9px;              /* bottom corners */
    border-top: none;                        /* share the divider line */
}
/* the button matching the bulb's last-known state is highlighted —
   a visual hint only; both buttons always send absolute commands.
   The highlighted half is raised so its full border sits cleanly on
   top of the neighbour's edge. */
.light-tile.is-on  .light-on,
.light-tile.is-off .light-off { position: relative; z-index: 1; }
.light-tile.is-on  .light-on {
    background: linear-gradient(165deg, #b45309, #92400e);
    color: #fff7ed;
    border-color: #f59e0b;
    box-shadow: 0 0 14px rgba(245,158,11,0.25);
}
.light-tile.is-off .light-off {
    background: var(--bg-2);
    color: var(--fg);
    border-color: var(--mid);
    border-top: 1px solid var(--mid);   /* own the full boundary when lit */
}
.light-tile.busy { animation: door-changing 1s ease-in-out infinite; }
/* state-unknown / state-failed get NO dimming — the On/Off buttons are
   fully usable the moment the card renders; the (unreliable) state
   fetch only adds a highlight hint when it succeeds. */

/* mobile — one tile per row: name on the left, the On/Off switch
   becomes a left/right pair on the right. */
@media (max-width: 600px) {
    .light-grid { grid-template-columns: 1fr; }
    .light-tile {
        flex-direction: row;
        align-items: center;
        gap: 12px;
    }
    .light-tile .light-name {
        flex: 1;
        min-width: 0;
        text-align: left;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .light-btns {
        flex: none;
        grid-auto-flow: column;          /* On | Off, side by side */
        grid-auto-columns: 1fr;
        width: 168px;
    }
    /* corners round on the OUTER sides; the shared divider is vertical */
    .light-on  { border-radius: 9px 0 0 9px; }
    .light-off {
        border-radius: 0 9px 9px 0;
        border-top: 1px solid var(--line);   /* restore — no longer shared vertically */
        border-left: none;                    /* On/Off now share a vertical divider */
    }
    /* when Off is selected it owns its full boundary — the shared edge
       in the row layout is the LEFT side, so restore that border */
    .light-tile.is-off .light-off {
        border-top: 1px solid var(--mid);
        border-left: 1px solid var(--mid);
    }
}

/* title between the bulb tiles and the colour-profile panel */
.adv-section-title {
    margin: 32px 0 10px;
    font-size: 16px;
}
.light-advanced {
    margin-top: 0;             /* the section title provides the gap */
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 16px;
}

/* slider value readout next to its field label */
.range-readout {
    color: var(--accent);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* colour-mode tab strip — full width; a single sliding pill is the
   highlight, and it can be dragged along the strip to switch modes */
.mode-tabs {
    position: relative;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    padding: 4px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: 9px;
}
.mode-pill {
    position: absolute;
    top: 4px;
    left: 0;
    height: calc(100% - 8px);
    background: var(--accent);
    border-radius: 6px;
    cursor: grab;
    touch-action: none;
    transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1),
                width 0.24s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 1;
}
.mode-tabs.dragging .mode-pill { cursor: grabbing; }
.mode-tab {
    appearance: none; -webkit-appearance: none;
    position: relative;
    z-index: 2;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 8px 6px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--mid);
    white-space: nowrap;
    transition: color 0.12s;
}
.mode-tab:hover { color: var(--fg); }
.mode-tab.on { color: #fff; }

/* mobile — the 5 tabs don't fit a phone width as equal columns, so the
   strip scrolls horizontally; tabs size to their text. The drag-pill
   still works (offsetLeft/Width are relative to the scrolling box). */
@media (max-width: 600px) {
    .mode-tabs {
        grid-auto-columns: max-content;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .mode-tabs::-webkit-scrollbar { display: none; }
    .mode-tab { padding: 8px 12px; }
}

/* per-mode body — a sliding carousel of panels. The viewport height
   is set by JS to the active panel, so the card shrinks to fit. */
.mode-viewport {
    overflow: hidden;
    margin: 12px 0;
    transition: height 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.mode-track {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 100%;   /* each panel = one full viewport width */
    width: 100%;
    align-items: start;
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.mode-panel {
    display: grid;
    gap: 12px;
    align-content: start;
}
.mode-note { font-size: 13px; margin: 0; }

/* bulb selector — multi-select chips; the Apply button targets these */
.bulb-select-field { margin-bottom: 16px; }
/* it's a div.field (not label.field), so its label span needs spacing */
.bulb-select-field > span {
    display: block;
    color: var(--mid);
    font-size: 13px;
    margin-bottom: 6px;
}
.bulb-chips {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(108px, 1fr));
    gap: 6px;
}
.bulb-chip {
    appearance: none; -webkit-appearance: none;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    padding: 7px 8px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--bg-3);
    color: var(--mid);
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.bulb-chip:hover { color: var(--fg); border-color: var(--mid); }
.bulb-chip.on {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

/* action bar — brightness on the left, Apply on the right, divider above */
.adv-actions {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}
.adv-actions .bright-field { flex: 1; min-width: 0; }
.adv-actions .adv-apply { flex: none; }

/* sliders — a pill-in-a-bar control. The .slider wrapper is the bar;
   --fill (0..100%) is the filled width set by JS. The native range
   input sits on top, transparent, as the drag target. The value is
   printed inside the bar via .slider-val. */
.slider {
    --fill: 0%;
    position: relative;
    height: 38px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: var(--bg-3);
    overflow: hidden;
}
/* the accent fill up to the current value */
.slider::before {
    content: "";
    position: absolute;
    top: 0; bottom: 0; left: 0;
    width: var(--fill);
    background: var(--accent);
    transition: width 0.05s linear;
}
/* the value — plain white text centred in the bar, with a dark outline
   + drop shadow so it stays legible over any part of the gradient
   without a background chip hiding the bar */
.slider-val {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 13px;
    font-weight: 700;
    color: #fff;
    font-variant-numeric: tabular-nums;
    /* faux stroke (4-way) + soft shadow for separation */
    text-shadow:
         0  1px 0 rgba(0,0,0,0.9),
         0 -1px 0 rgba(0,0,0,0.9),
         1px 0  0 rgba(0,0,0,0.9),
        -1px 0  0 rgba(0,0,0,0.9),
         0  2px 5px rgba(0,0,0,0.7);
    pointer-events: none;
}
/* the native range — full-bleed, invisible, just the interaction layer */
.slider input[type=range] {
    -webkit-appearance: none;
    appearance: none;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    background: transparent;
    cursor: pointer;
}
/* slider handle — white with a dark border, plus a light outer ring
   and strong shadow so it stands out on light AND dark parts of the
   gradient bars. */
.slider input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 20px;
    height: 30px;
    border-radius: 6px;
    background: #fff;
    border: 2px solid #16181d;
    box-shadow:
        0 0 0 1.5px rgba(255,255,255,0.95),
        0 2px 6px rgba(0,0,0,0.65);
}
.slider input[type=range]::-moz-range-thumb {
    width: 20px;
    height: 30px;
    border-radius: 6px;
    background: #fff;
    border: 2px solid #16181d;
    box-shadow:
        0 0 0 1.5px rgba(255,255,255,0.95),
        0 2px 6px rgba(0,0,0,0.65);
}
.slider input[type=range]::-moz-range-track { background: transparent; }

/* brightness — the bar shows its full dark→light gradient edge-to-edge;
   the knob marks the level, no dimming scrim. */
.slider--bright {
    background: linear-gradient(to right, #2b2b2b, #fde68a, #fffbeb);
}
.slider--bright::before { display: none; }

/* warm / cold / temperature sliders — the bar shows its full colour
   gradient edge-to-edge (the gradient IS the range of tones); the knob
   just marks the chosen point, no dimming fill. */
.slider--warm {
    background: linear-gradient(to right, #5a3410, #ffae6b, #ffe8d0);
}
.slider--cold {
    background: linear-gradient(to right, #1d3a5c, #bcd9ff, #f4f9ff);
}
.slider--temp {
    background: linear-gradient(to right,
        #ff9d49, #ffb86b, #ffd9a8, #fff4e8, #f4f6ff, #d6e6ff);
}
/* no fill scrim — the whole bar stays coloured */
.slider--warm::before,
.slider--cold::before,
.slider--temp::before { display: none; }

/* RGB picker — a large preview swatch with the colour input layered
   over it; the chosen hex is shown inside */
.rgb-picker {
    position: relative;
    height: 84px;
    border-radius: 10px;
    border: 1px solid var(--line);
    overflow: hidden;
}
.rgb-preview {
    position: absolute;
    inset: 0;
}
.rgb-picker input[type=color] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    border: none;
    background: transparent;
    opacity: 0;          /* invisible — just the click target */
    cursor: pointer;
}
.rgb-hex {
    position: absolute;
    left: 50%;
    bottom: 10px;
    transform: translateX(-50%);
    padding: 4px 11px;
    border-radius: 999px;
    background: rgba(17, 19, 23, 0.92);
    border: 1px solid rgba(255, 255, 255, 0.5);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    font-family: ui-monospace, "SF Mono", monospace;
    letter-spacing: 0.04em;
    pointer-events: none;
}

/* combined colour-output preview swatch (cold/warm result) */
.color-preview {
    height: 44px;
    border-radius: 8px;
    border: 1px solid var(--line);
}

/* animation-speed slider — a darker-blue ripple continuously sweeps
   left→right through the blue fill. --sweep (one cycle's duration) is
   set by JS from the value, so the ripple speeds up as the slider
   rises. A repeating gradient translated by one tile loops seamlessly. */
.slider--speed {
    --sweep: 1.2s;
}
.slider--speed::before {
    background:
        repeating-linear-gradient(110deg,
            rgba(20, 52, 120, 0)    0px,
            rgba(20, 52, 120, 0.75) 28px,
            rgba(20, 52, 120, 0)    56px),
        var(--accent);
    background-size: 56px 100%, 100% 100%;
    animation: speed-ripple var(--sweep) linear infinite;
}
@keyframes speed-ripple {
    from { background-position: 0    0, 0 0; }
    to   { background-position: 56px 0, 0 0; }
}
@media (prefers-reduced-motion: reduce) {
    .slider--speed::before { animation: none; }
}

/* hidden — kept so other pages' input[type=color] is unaffected */
.light-advanced input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
.light-advanced input[type=color]::-webkit-color-swatch { border: none; border-radius: 5px; }
.light-advanced input[type=color]::-moz-color-swatch { border: none; border-radius: 5px; }

/* scene picker — an even grid of equal-width selectable chips */
.scene-chips {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
    gap: 6px;
}
.scene-chip {
    appearance: none; -webkit-appearance: none;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    padding: 6px 8px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--bg-3);
    color: var(--mid);
    text-align: center;
    text-transform: capitalize;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.scene-chip:hover { color: var(--fg); border-color: var(--mid); }
.scene-chip.on {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

/* inactive users — collapsed dropdown at the bottom of the users page */
.inactive-users {
    margin-top: 24px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 0 16px;
}
.inactive-users summary {
    cursor: pointer;
    padding: 14px 0;
    font-weight: 600;
    list-style: none;
    color: var(--mid);
}
.inactive-users summary::-webkit-details-marker { display: none; }
.inactive-users summary::before { content: '▸ '; color: var(--mid); }
.inactive-users[open] summary::before { content: '▾ '; }
.inactive-users > .user-cards { padding-bottom: 16px; }
.user-card-inactive .user-card-head { align-items: center; }

/* archived doors — collapsed dropdown on the doors admin page */
.archived-doors {
    margin-top: 24px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 0 16px;
}
.archived-doors summary {
    cursor: pointer;
    padding: 14px 0;
    font-weight: 600;
    list-style: none;
    color: var(--mid);
}
.archived-doors summary::-webkit-details-marker { display: none; }
.archived-doors summary::before { content: '▸ '; color: var(--mid); }
.archived-doors[open] summary::before { content: '▾ '; }
.archived-doors > .door-cards { padding-bottom: 16px; }
/* slim archived card — just the name + the Hide-from-users toggle */
.door-card-archived .door-card-bar { align-items: center; }
.door-card-archived .flag-pill { margin-left: auto; }
/* when the guardian badge is present it carries the auto-margin, so the
   toggle sits tight against the badge rather than splitting the gap */
.door-card-archived .db-guardian + .flag-pill { margin-left: 8px; }

/* ======================================================================
   TABLES — admin views
   Wrapped in .table-scroll so they don't break the viewport on mobile.
   ====================================================================== */

.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    margin: 12px 0;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--bg-2);
}
table.data {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
table.data th,
table.data td {
    padding: 10px 12px;
    text-align: left;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
    vertical-align: middle;
}
table.data th {
    color: var(--mid);
    font-weight: 500;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: var(--bg-2);
    position: sticky;
    top: 0;
}
table.data tr:last-child td { border-bottom: none; }
table.data tbody tr:hover td { background: var(--bg-3); }
table.data td.actions { text-align: right; }
table.data td.wrap { white-space: normal; }
table.data td.mono {
    font-family: ui-monospace, "SF Mono", monospace;
    font-size: 12px;
    color: var(--mid);
}

/* ======================================================================
   ADMIN — user editor (NOT a wide flat table)
   Each user is a card; edit affordances live inline.
   ====================================================================== */

/* "Add a user" — a single inline row of fields + button */
.add-user-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 12px;
    background: var(--bg-2);
    border: 1px solid var(--accent);
    border-radius: 8px;
}
.add-user-row .add-user-label {
    font-weight: 600;
    font-size: 13px;
    white-space: nowrap;
}
.add-user-row input {
    flex: 1 1 130px;
    min-width: 110px;
    width: auto;
    padding: 7px 10px;
    font-size: 13px;
}
.add-user-row .btn { flex: none; }

/* ---- My account — a calm, personal settings page (not an admin grid).
   A profile hero, then plainly-titled sections with generous spacing.
   Width gives the 5-column activity table room without a scroll. */
.account { max-width: 820px; }

/* profile hero — avatar circle + name + email */
.acct-hero {
    display: flex;
    align-items: center;
    gap: 16px;
    margin: 4px 0 28px;
}
.acct-avatar {
    flex: none;
    width: 64px;
    height: 64px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    font-weight: 700;
    color: #fff;
    background: linear-gradient(150deg, var(--accent), #1d4ed8);
}
.acct-hero-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.acct-hero-name { font-size: 22px; font-weight: 700; color: var(--fg); }
.acct-hero-email { font-size: 14px; color: var(--mid); }

/* a titled section — friendly heading, not an uppercase admin chip */
.acct-block {
    padding: 22px 0;
    border-top: 1px solid var(--line);
}
.acct-block-head { margin-bottom: 16px; }
.acct-block-head h3 {
    margin: 0 0 4px;
    font-size: 17px;
    font-weight: 700;
}
.acct-block-head p {
    margin: 0;
    font-size: 13px;
    color: var(--mid);
    line-height: 1.5;
}
.acct-note {
    font-size: 13px;
    color: var(--mid);
    line-height: 1.5;
}
/* action row at the foot of a section */
.acct-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 16px;
}
.acct-actions--start { justify-content: flex-start; }

/* device layout editor — two drag-sortable lists side by side */
.layout-cols {
    display: grid;
    grid-template-columns: 1fr;
    gap: 14px;
}
@media (min-width: 560px) {
    .layout-cols { grid-template-columns: 1fr 1fr; }
}
.layout-col-head {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 8px;
}
.layout-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 56px;
    padding: 8px;
    border: 1px dashed var(--line);
    border-radius: 10px;
    background: var(--bg-2);
}
.layout-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 10px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--bg-3);
    font-size: 13px;
    font-weight: 600;
    user-select: none;
}
.layout-item.dragging {
    opacity: 0.85;
    border-color: var(--accent);
    box-shadow: 0 4px 14px rgba(0,0,0,0.4);
}
/* the grip is the drag handle — `touch-action: none` stops the browser
   stealing the touch for page scrolling, so a finger-drag reorders. */
.layout-grip {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    margin: -9px 0 -9px -10px;
    padding: 9px 8px;
    color: var(--dim);
    font-size: 16px;
    line-height: 1;
    cursor: grab;
    touch-action: none;
}
.layout-grip:active { cursor: grabbing; }
.layout-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "+ Device" row — sits at the foot of the driving list, aligned to
   the right. The link reveals the picker select in place. */
.layout-add {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 7px 10px;
}
.layout-add-link {
    font-size: 13px;
    font-weight: 600;
    color: var(--accent);
    cursor: pointer;
}
.layout-add-link:hover { text-decoration: underline; }
.layout-add-pick { flex: 1; min-width: 0; }
/* the base `select { display:block }` rule overrides the HTML `hidden`
   attribute — restore hidden so the picker is collapsed until clicked */
.layout-add-pick[hidden],
.layout-add-link[hidden] { display: none; }
/* ✕ to remove a device from the driving list */
.layout-remove {
    flex: none;
    width: 20px;
    height: 20px;
    padding: 0;
    line-height: 1;
    font-size: 11px;
    border-radius: 5px;
    cursor: pointer;
    color: var(--bad-bright);
    background: transparent;
    border: 1px solid var(--line);
}
.layout-remove:hover { background: var(--bad); border-color: var(--bad); color: #fff; }

.user-cards { display: grid; gap: 8px; margin-top: 12px; }
.user-card {
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 10px 12px;
}
.user-card.pending { border-color: #d97706; }
/* single ordered info bar across the top of the card */
.user-card-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 8px;
    font-size: 11px;
    color: var(--mid);
}
.user-card-bar .ub-name {
    font-weight: 600;
    font-size: 14px;
    color: var(--fg);
}
.user-card-bar .ub-link { color: var(--accent); }
.user-card-bar .ub-link:hover { text-decoration: underline; }
.user-card-bar .ub-sep {
    width: 1px;
    align-self: stretch;
    min-height: 12px;
    background: var(--line);
}
/* Edit button sits at the far right of the bar; the NO-DEVICE badge,
   when present, sits just before it */
.user-card-bar .edit-user { margin-left: auto; }
.user-card-bar .ub-badge ~ .edit-user { margin-left: 8px; }
.user-card-bar .ub-badge { margin-left: auto; }

/* inline profile editor — revealed by the bar's Edit button */
.user-edit {
    margin-top: 10px;
    padding: 12px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: 8px;
    display: grid;
    gap: 10px;
}

.user-card-body {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--line);
    display: grid;
    grid-template-columns: 1fr;
    gap: 10px;
}

/* two-column split inside an admin card (users: Devices|Doors,
   doors: Details|Behavior). Stacks on narrow; a divider rule sits
   between the split and the section below it. */
.card-split {
    display: grid;
    grid-template-columns: 1fr;
    gap: 10px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--line);
}
@media (min-width: 560px) {
    .card-split {
        grid-template-columns: 1fr 1fr;
        gap: 14px;
    }
    /* vertical rule between the Devices and Doors columns */
    .card-split .card-section:first-child {
        padding-right: 14px;
        border-right: 1px solid var(--line);
    }
}

/* a labelled block inside a user card (Access, Doors, Devices…) */
.card-section { display: grid; gap: 6px; align-content: start; }
.section-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mid);
}
/* colored dot before each section label, color set per-section below */
.section-label::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--section-color, var(--dim));
    flex: none;
}
.section-label--devices  { --section-color: var(--ok-bright); }
.section-label--doors    { --section-color: var(--accent); }
.section-label--access   { --section-color: var(--warn); }
.section-label--details  { --section-color: var(--accent); }
.section-label--behavior { --section-color: var(--warn); }
.section-label--colors   { --section-color: var(--bad-bright); }
.section-label--icon     { --section-color: var(--ok-bright); }

/* door grants — even-column grid so pills line up. Wide columns give
   the switch + a full door name room (one per row in the half col). */
.door-pills {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 6px;
}
/* access flags sit inline with the Save button on one row */
.user-card-flags {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}
/* small spacer separating Active from the other three flags */
.user-card-flags .flag-gap { width: 3px; flex: none; }
.door-pill,
.flag-pill {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 8px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: 6px;
    font-size: 12px;
    cursor: pointer;
    user-select: none;
}
.door-pill span,
.flag-pill span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* switches inside pills — slightly smaller to fit the compact pill */
.door-pill input[type=checkbox],
.flag-pill input[type=checkbox] { width: 30px; height: 17px; }
.door-pill input[type=checkbox]::before,
.flag-pill input[type=checkbox]::before { width: 13px; height: 13px; }
.door-pill input[type=checkbox]:checked::before,
.flag-pill input[type=checkbox]:checked::before { transform: translateX(13px); }
/* an "on" pill stays neutral — the switch itself (blue when checked)
   carries the on-state; a faintly lighter bg is the only hint, so a
   user with many grants isn't a wall of blue. */
.door-pill.on,
.flag-pill.on { background: var(--bg-2); }

/* ---- device (passkey) list under a user ----
   Two columns of vertical device cards so full names fit on their
   own line. */
.device-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: 6px;
}
/* two columns only when there's room — one per row on a phone, so the
   label + Remove button never get squeezed past the viewport edge */
@media (min-width: 560px) {
    .device-list { grid-template-columns: repeat(2, 1fr); }
}
.device-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
        "dot  label btn"
        "meta meta  meta";
    align-items: center;
    gap: 2px 8px;
    padding: 7px 8px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: 6px;
    font-size: 12px;
}
.device-row .device-dot {
    grid-area: dot;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--ok-bright);
    flex: none;
}
.device-row .device-label {
    grid-area: label;
    font-weight: 600;
}
.device-row .device-meta {
    grid-area: meta;
    color: var(--dim);
    font-size: 11px;
}
/* the Remove button sits in the "btn" cell, inline with the name */
.device-row > .btn { grid-area: btn; white-space: nowrap; }
/* small icon-only button, inline with the device name */
.device-btn {
    grid-area: btn;
    width: 20px;
    height: 20px;
    padding: 0;
    line-height: 1;
    font-size: 11px;
    border-radius: 5px;
    cursor: pointer;
    color: var(--bad-bright);
    background: transparent;
    border: 1px solid var(--line);
}
.device-btn:hover {
    background: var(--bad);
    border-color: var(--bad);
    color: #fff;
}
.device-btn-enable { color: var(--ok-bright); }
.device-btn-enable:hover {
    background: var(--ok);
    border-color: var(--ok);
    color: #fff;
}
.device-row.rejected {
    opacity: 0.7;
    background: var(--bg-2);
}
.device-row.rejected .device-dot { background: var(--dim); }
.device-row.rejected .device-label { text-decoration: line-through; }

/* ---- enrolment panel (QR + 6-digit code) ---- */
.enroll-panel {
    position: relative;
    display: flex;
    gap: 16px;
    align-items: center;
    flex-wrap: wrap;
    padding: 14px;
    margin: 4px 0 10px;
    background: var(--bg);
    border: 1px dashed var(--accent);
    border-radius: 10px;
}
.enroll-close {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 24px;
    height: 24px;
    padding: 0;
    line-height: 1;
    font-size: 13px;
    color: var(--mid);
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: 6px;
    cursor: pointer;
}
.enroll-close:hover {
    color: var(--fg);
    border-color: var(--bad);
    background: var(--bg-2);
}
.enroll-qr img {
    width: 160px; height: 160px;
    display: block;
    border-radius: 6px;
    background: #fff;
}
.enroll-code-wrap { min-width: 160px; }
.enroll-hint   { font-size: 12px; }
.enroll-code {
    font-family: ui-monospace, "SF Mono", monospace;
    font-size: 30px;
    font-weight: 700;
    letter-spacing: 0.12em;
    margin: 4px 0;
}
.enroll-actions { margin: 6px 0 4px; }
.enroll-expiry { font-size: 11px; }

/* ---- add-a-device modal (self-service, from the user menu) ---- */
.enroll-modal {
    position: fixed;
    inset: 0;
    z-index: 80;
    background: rgba(0,0,0,0.6);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.enroll-modal[hidden] { display: none; }
.enroll-modal-box {
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 20px;
    max-width: 360px;
    width: 100%;
    text-align: center;
}
.enroll-modal-box .spread { margin-bottom: 6px; }
.enroll-modal-box .modal-x {
    background: none;
    border: none;
    color: var(--mid);
    font-size: 18px;
    cursor: pointer;
    line-height: 1;
}
.enroll-modal-qr {
    margin: 12px auto;
}
.enroll-modal-qr img {
    width: 200px; height: 200px;
    background: #fff;
    border-radius: 8px;
    display: block;
    margin: 0 auto;
}
/* "out of keys" state of the add-device modal */
.enroll-full-icon {
    font-size: 40px;
    line-height: 1;
    margin: 8px 0 4px;
}
.enroll-full-lead {
    font-size: 15px;
    font-weight: 700;
    color: var(--fg);
    margin: 0 0 8px;
}

/* ======================================================================
   ADMIN — door editor as cards (mobile-friendly)
   ====================================================================== */

.door-cards { display: grid; gap: 8px; }
.door-card {
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 10px 12px;
}
.door-card.disabled { opacity: 0.55; }

/* single ordered info bar across the top of the door card,
   with the Save button at the far right */
.door-card-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 8px;
    font-size: 11px;
    color: var(--mid);
}
.door-card-bar .save { flex: none; }
.door-card-bar .db-name {
    font-weight: 600;
    font-size: 14px;
    color: var(--fg);
}
.door-card-bar .db-id {
    font-family: ui-monospace, "SF Mono", monospace;
    font-size: 11px;
    color: var(--dim);
}
.door-card-bar .db-sync {
    margin-left: auto;
    margin-right: 14px;
    color: var(--dim);
}
/* "guardian hidden" badge sits just left of the synced text — push the
   badge right instead of db-sync, so the two travel together */
.door-card-bar .db-guardian {
    margin-left: auto;
    margin-right: 8px;
}
.door-card-bar .db-guardian + .db-sync { margin-left: 0; }

.door-card-body {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--line);
}
/* three columns: Details | Behavior | Icon. Stacks on narrow.
   Columns stretch to equal height (align-items:stretch) so the short
   Details column doesn't look stunted; content stays top-aligned.
   The block fills the full card width so its edges line up with the
   header bar — even padding all around the card. */
.door-split {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    align-items: stretch;
}
@media (min-width: 720px) {
    /* zero column gap — the gutters come entirely from the symmetric
       padding below, so the divider rule is centered in each gutter
       and content is inset evenly on both sides. The Icon column is
       sized to fit its fixed 6-wide swatch grid exactly (no dead space
       on its right); Details/Behavior split the remaining width. */
    .door-split {
        grid-template-columns: 1fr 1fr 269px;
        gap: 0;
    }
    .door-split .card-section:not(:first-child) { padding-left: 16px; }
    .door-split .card-section:not(:last-child) {
        padding-right: 16px;
        border-right: 1px solid var(--line);
    }
}
/* each column fills its (equal) height with content packed at the top */
.door-split .card-section { align-content: start; }
/* compact form fields inside the door card sections */
.door-card .field { margin-bottom: 0; }
.door-card .field > span { font-size: 11px; margin-bottom: 3px; }
.door-card .field input,
.door-card .field select { padding: 7px 10px; font-size: 13px; }

/* door behavior flags — toggle pills, one per row */
.door-flags { display: grid; gap: 6px; margin-top: 2px; }
.door-flags .flag-pill { justify-content: flex-start; }
/* …two side by side (Driving mode / Hide from users) */
.door-flags-row { grid-template-columns: 1fr 1fr; }

/* Colors subsection — the section label sits mid-section, so it needs
   a touch of top space to read as its own block. Two pickers side by side. */
.section-label--colors { margin-top: 4px; }
.color-row {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 8px;
    align-items: end;
}
/* tiny swap button between the two color pickers. The 2px margin-bottom
   leaves it 1px below an exact box-match — an optical nudge so the small
   dark button reads as centred against the big bright colour swatches. */
.color-swap {
    align-self: end;
    margin-bottom: 2px;
    width: 24px;
    height: 32px;
    padding: 0;
    font-size: 13px;
    line-height: 1;
    color: var(--mid);
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: 6px;
    cursor: pointer;
}
.color-swap:hover {
    color: var(--fg);
    border-color: var(--accent);
}
/* color picker — the chosen color fills the control edge-to-edge,
   stripping the browser's default white swatch padding/border */
.color-field input[type=color] {
    width: 100%;
    height: 32px;
    padding: 0;
    cursor: pointer;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--bg-3);
    overflow: hidden;
}
.color-field input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
.color-field input[type=color]::-webkit-color-swatch {
    border: none;
    border-radius: 5px;
}
.color-field input[type=color]::-moz-color-swatch {
    border: none;
    border-radius: 5px;
}


/* ---- admin icon picker — fixed 6-column swatch grid (6 x 4),
   compact swatches capped so they don't stretch with the column ---- */
.icon-picker {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 38px));
    gap: 5px;
}
.icon-swatch {
    appearance: none; -webkit-appearance: none;
    aspect-ratio: 1;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--bg-3);
    cursor: pointer;
    display: grid; place-items: center;
    padding: 7px;
    transition: border-color 0.12s, background 0.12s;
}
.icon-swatch:hover { border-color: var(--mid); }
.icon-swatch.on {
    border-color: var(--accent);
    background: #1e3a5f;
}
.icon-swatch svg {
    width: 100%; height: 100%;
    fill: none;
    stroke: var(--mid);
    stroke-width: 3.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.icon-swatch.on svg { stroke: #dbeafe; }

/* ======================================================================
   TOASTS
   ====================================================================== */

#toasts {
    position: fixed;
    top: 64px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100;
    display: flex;
    flex-direction: column;
    gap: 6px;
    pointer-events: none;
}
.toast {
    background: var(--bg-2);
    border: 1px solid var(--line);
    color: var(--fg);
    padding: 10px 16px;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.4);
    font-size: 14px;
    max-width: 90vw;
    animation: toast-in 0.2s ease-out;
}
.toast.ok  { border-color: var(--ok); }
.toast.bad { border-color: var(--bad); }
@keyframes toast-in {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ======================================================================
   LOGIN / REGISTER CARD
   ====================================================================== */

.card {
    max-width: 440px;
    margin: 40px auto;
    padding: 24px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 12px;
}
.card h2 { margin-top: 0; }

/* logo above the login / register card (these pages have no nav bar) */
.auth-logo {
    display: block;
    max-width: 440px;
    margin: 48px auto 0;
    text-align: center;
}
.auth-logo img { height: 72px; }
/* tighten the gap between the logo and the card below it */
.auth-logo + .card { margin-top: 18px; }

/* ======================================================================
   CAMERAS
   ====================================================================== */

/* One camera at a time. The list and the viewer share ONE framed
   panel — list on the left (a divider, no separate border), viewer
   on the right. On mobile the list drops below the viewer. */
.cameras { max-width: 1100px; }
.cam-layout {
    display: grid;
    grid-template-columns: 1fr;
    margin-top: 14px;
    border: 1px solid var(--line);
    border-radius: 12px;
    overflow: hidden;
    background: var(--bg-2);
}
@media (min-width: 720px) {
    /* list (left) | viewer (right) */
    .cam-layout { grid-template-columns: 220px 1fr; }
}

/* ---- viewer ----
   The frame fills the full panel height (the list column sets it), with
   a black background; the snapshot is centred in it, so any extra space
   reads as intentional letterboxing rather than an empty grey block. */
.cam-viewer { display: flex; }
.cam-viewer-frame {
    position: relative;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 240px;
    background: #000;
}
#cam-view-img {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}
/* camera name — across the TOP of the viewer */
.cam-viewer-top {
    position: absolute;
    left: 0; right: 0; top: 0;
    padding: 10px 12px;
    background: linear-gradient(rgba(0,0,0,0.8), transparent);
    pointer-events: none;
}
.cam-viewer-name { font-size: 15px; font-weight: 700; color: #fff; }
/* timestamp + controls — across the bottom */
.cam-viewer-bar {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: linear-gradient(transparent, rgba(0,0,0,0.8));
}
.cam-viewer-meta {
    flex: 1;
    font-size: 12px;
    color: rgba(255,255,255,0.7);
    font-variant-numeric: tabular-nums;
}
.cam-viewer-btn {
    flex: none;
    width: 34px;
    height: 34px;
    border-radius: 8px;
    border: 1px solid rgba(255,255,255,0.25);
    background: rgba(0,0,0,0.45);
    color: #fff;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}
.cam-viewer-btn:hover { background: rgba(0,0,0,0.75); }
/* loading spinner over the viewer */
.cam-viewer-spin {
    position: absolute;
    top: 50%; left: 50%;
    width: 38px; height: 38px;
    margin: -19px;
    border: 3px solid rgba(255,255,255,0.25);
    border-top-color: #fff;
    border-radius: 50%;
    opacity: 0;
    pointer-events: none;
}
.cam-viewer-loading .cam-viewer-spin {
    opacity: 1;
    animation: spin 0.8s linear infinite;
}
.cam-viewer-failed #cam-view-img { opacity: 0.3; }
.cam-viewer-failed .cam-viewer-meta { color: var(--bad-bright); }

/* ---- camera list — left column inside the shared frame ---- */
.cam-list {
    display: flex;
    flex-direction: column;
    padding: 6px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--line) transparent;
    /* scroll-affordance: a fade at the top/bottom edges that shows ONLY
       when there's more list to scroll to. The `cover` layers are
       pinned to the scroll container and hide the fade at the extremes;
       the `fade` layers are pinned to the viewport and stay put. Pure
       CSS — works regardless of how the OS draws its scrollbar. */
    background:
        /* top cover — hides the top fade when scrolled to the top */
        linear-gradient(var(--bg-2), transparent) center top / 100% 20px no-repeat local,
        /* bottom cover — hides the bottom fade when at the end */
        linear-gradient(transparent, var(--bg-2)) center bottom / 100% 20px no-repeat local,
        /* the fades themselves — pinned to the viewport */
        linear-gradient(var(--bg-2), transparent) center top / 100% 16px no-repeat,
        linear-gradient(transparent, var(--bg-2)) center bottom / 100% 16px no-repeat,
        var(--bg-2);
    /* mobile: list sits BELOW the viewer, divider on top */
    order: 2;
    border-top: 1px solid var(--line);
    max-height: 240px;
}
.cam-list::-webkit-scrollbar { width: 8px; }
.cam-list::-webkit-scrollbar-thumb {
    background: var(--line);
    border-radius: 4px;
    border: 2px solid var(--bg-2);   /* inset so it reads as slim */
}
.cam-list::-webkit-scrollbar-thumb:hover { background: var(--mid); }
.cam-list::-webkit-scrollbar-track { background: transparent; }
@media (min-width: 720px) {
    /* desktop: list on the LEFT, divider on its right edge */
    .cam-list {
        order: 0;
        border-top: none;
        border-right: 1px solid var(--line);
        max-height: 64vh;
    }
}
.cam-list-group { display: flex; flex-direction: column; gap: 2px; }
/* divider between the core cameras and the rest */
.cam-list-sep {
    height: 0;
    margin: 8px 4px;
    border: none;
    border-top: 1px solid var(--line);
}
.cam-list-item {
    appearance: none; -webkit-appearance: none;
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 9px 10px;
    border: 1px solid transparent;
    border-radius: 7px;
    background: transparent;
    color: var(--mid);
    font-size: 13px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.cam-list-item:hover { background: var(--bg-3); color: var(--fg); }
.cam-list-item.on {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}
.cam-list-dot {
    flex: none;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--dim);
}
.cam-list-item.on .cam-list-dot { background: #fff; }
.cam-list-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ======================================================================
   HELPERS
   ====================================================================== */

.hidden { display: none !important; }
.muted { color: var(--mid); }
.dim   { color: var(--dim); }
.right { text-align: right; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.spacer { flex: 1; }

/* The .row class was previously forcing flex:1 on every child, which
   crammed labels into thin columns. Switched to a sane gap-based flex
   that lets each child size to content. Use .row-equal for the old
   behaviour explicitly. */
.row {
    display: flex;
    gap: 12px;
    align-items: center;
    flex-wrap: wrap;
}
.row-equal > * { flex: 1; min-width: 0; }

.grid-2 {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
}
@media (min-width: 720px) {
    .grid-2 { grid-template-columns: 1fr 1fr; }
}

/* Bigger touch targets on small screens */
@media (max-width: 600px) {
    .btn  { padding: 12px 16px; font-size: 15px; }
}

.ok-pill, .deny-pill, .err-pill {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.ok-pill   { background: #064e3b; color: #d1fae5; }
.deny-pill { background: #78350f; color: #fef3c7; }
.err-pill  { background: #7f1d1d; color: #fee2e2; }
