/*
 * Sanrad Hotspot Manager -- mobile-first stylesheet.
 *
 * Built for one-handed phone use first: 16px minimum input text (anything
 * smaller makes iOS Safari zoom on focus) and 44px minimum tap targets. The
 * desktop layout widens the same structure rather than being a separate design.
 *
 * Theming has three states, not two. An explicit choice stamps
 * data-theme="light|dark" on <html>; the default follows the browser and
 * stamps nothing, so only prefers-color-scheme separates light from dark
 * there. Every colour is a token defined on bare :root, redefined in the media
 * query (guarded so an explicit light choice beats a dark OS) and again under
 * [data-theme="dark"] so the toggle wins in both directions. Never write a
 * colour whose only definition lives inside a media or [data-theme] block --
 * it would not apply in the unstamped state.
 *
 * Brand blue #5b9bd5 is carried over from the payments portal.
 */

:root {
  --brand: #2d6ea3;
  --brand-bright: #5b9bd5;
  --brand-ink: #ffffff;
  --brand-tint: #e8f1f9;

  --ink: #16202b;
  --ink-soft: #55697c;
  --ink-faint: #7c8ea0;
  --line: #dae2ec;
  --line-strong: #bcc9d8;
  --surface: #ffffff;
  --surface-2: #eef2f7;
  --canvas: #f4f7fa;

  --ok: #1f7a4d;      --ok-tint: #e4f2ea;
  --warn: #9a6708;    --warn-tint: #f8efdd;
  --danger: #b23636;  --danger-tint: #f8e9e9;

  /* Glass surfaces. Opaque enough to read on their own where
     backdrop-filter is unsupported -- the blur is enhancement, not structure. */
  --glass: rgba(255, 255, 255, .82);
  --glass-edge: rgba(255, 255, 255, .65);
  --scrim: rgba(22, 32, 43, .34);
  /* The pinned strips. Split into colour and alpha because the alpha and the
     blur are tunable in System Settings, while the colour must stay per-theme
     -- that split is what lets a single setting apply correctly to both light
     and dark.
     clamp() is the last line of defence: the model refuses an unreadable value
     and so does the form, but a hand-edited row must not be able to render the
     navigation invisible either. */
  --glass-rgb: 255, 255, 255;
  --glass-bar-alpha: .42;
  --glass-blur: 20px;
  --glass-bar: rgba(var(--glass-rgb), clamp(.25, var(--glass-bar-alpha), 1));

  /* The copyright strip is fixed to the viewport, so it is out of flow and
     cannot push content down by itself. This height is what `body` reserves
     underneath -- change one and you must change the other, which is why it
     is a token rather than a number written twice. */
  /* The masthead's height. The nav bar sticks directly beneath it, so this
     has to be right or the two overlap or leave a gap. A sensible default is
     written here and corrected from the real rendered height by misc.js --
     which matters because a late web font can change it after first paint. */
  --header-h: 59px;
  /* The nav bar beneath it. Zero on public pages, which have no nav -- also
     kept in step from misc.js. */
  --nav-h: 0px;
  /* Everything pinned to the top, together. What content must clear. */
  --chrome-h: calc(var(--header-h) + var(--nav-h));

  --footer-h: 44px;
  /* Breathing room between the content and the pinned strips, top and bottom,
     so nothing ever sits hard against them. One value for both ends, so the
     page is symmetrically inset rather than accidentally lopsided. */
  --chrome-gap: 28px;
  --footer-gap: var(--chrome-gap);

  --radius: 10px;
  --radius-sm: 7px;
  --shadow: 0 1px 2px rgba(22,32,43,.06), 0 4px 14px rgba(22,32,43,.06);
  --shadow-lg: 0 10px 34px rgba(22,32,43,.18);

  --font: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --nav-h: 58px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --brand: #7db4e0;
    --brand-bright: #5b9bd5;
    --brand-ink: #0d141b;
    --brand-tint: #14293a;

    --ink: #e6edf5;
    --ink-soft: #a2b2c2;
    --ink-faint: #78899b;
    --line: #27333e;
    --line-strong: #3a4a59;
    --surface: #151d26;
    --surface-2: #1c2632;
    --canvas: #0e141a;

    --ok: #4cbb84;      --ok-tint: #10251a;
    --warn: #d9a63f;    --warn-tint: #2a2012;
    --danger: #e08585;  --danger-tint: #2b1717;

    --glass: rgba(24, 33, 43, .84);
    --glass-edge: rgba(255, 255, 255, .10);
    --scrim: rgba(4, 8, 12, .58);
    --glass-rgb: 18, 25, 33;

    --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 14px rgba(0,0,0,.32);
    --shadow-lg: 0 10px 34px rgba(0,0,0,.5);
  }
}

:root[data-theme="dark"] {
  --brand: #7db4e0;
  --brand-bright: #5b9bd5;
  --brand-ink: #0d141b;
  --brand-tint: #14293a;

  --ink: #e6edf5;
  --ink-soft: #a2b2c2;
  --ink-faint: #78899b;
  --line: #27333e;
  --line-strong: #3a4a59;
  --surface: #151d26;
  --surface-2: #1c2632;
  --canvas: #0e141a;

  --ok: #4cbb84;      --ok-tint: #10251a;
  --warn: #d9a63f;    --warn-tint: #2a2012;
  --danger: #e08585;  --danger-tint: #2b1717;

  --glass: rgba(24, 33, 43, .84);
  --glass-edge: rgba(255, 255, 255, .10);
  --scrim: rgba(4, 8, 12, .58);
  --glass-rgb: 18, 25, 33;

  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 14px rgba(0,0,0,.32);
  --shadow-lg: 0 10px 34px rgba(0,0,0,.5);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--canvas);
  -webkit-text-size-adjust: 100%;
  /* Clearance for the pinned strips at both ends. All three are fixed, so they
     are out of flow and cannot make room for themselves -- content would start
     underneath the masthead and end underneath the copyright strip without
     this. */
  padding-top: calc(var(--chrome-h) + var(--chrome-gap));
  padding-bottom: calc(var(--footer-h) + var(--footer-gap) + env(safe-area-inset-bottom));
}

/* Links.
   Without this, a plain <a> falls back to the browser default #0000EE, which
   is unreadable on the dark ground -- exactly what happened to the "sign in"
   and "terminal" links on the landing page. Components that carry their own
   colour (.btn, .tile, .nav a, .brand) are class-based and so still win. */
a { color: var(--brand); text-underline-offset: 2px; }
a:hover { color: var(--brand-bright); }
a:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }

/* ---------- layout ---------- */
/* Fixed rather than sticky: content passes beneath it, which is what the glass
   is for. Opaque by default so the bar is readable where backdrop-filter is
   unsupported; the frost is added only where the browser can actually blur. */
.app-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 31;
  display: flex; align-items: center; gap: 14px;
  padding: 10px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.app-header .brand {
  font-weight: 600; letter-spacing: -.01em; color: var(--ink);
  text-decoration: none; display: flex; align-items: center; gap: 9px;
}
/* The Sanrad wordmark, carried over from the payments portal and used exactly
   as supplied. It carries its own blue ground behind the white "Sa" and sets
   "nrad" in brand blue on transparency, so it reads on a light or a dark
   surface without a per-theme variant and without any filter. Do not recolour
   or key it -- the white is part of the mark, by design. */
.app-header .brand .logo {
  height: 20px; width: auto; display: block; flex: none;
}
/* Separates the company mark from the product name without a heavy rule. */
.app-header .brand .wordmark {
  padding-left: 10px; margin-left: 2px;
  border-left: 1px solid var(--line-strong);
  color: var(--ink-soft); font-weight: 500;
}
.app-header .spacer { margin-left: auto; }

/* The top inset is the gap below the sticky chrome; the bottom clearance for
   the fixed copyright strip lives on `body`, since a page may put content
   outside .page. */
.page { max-width: 1080px; margin: 0 auto; padding: 0 16px 12px; }
.page.narrow { max-width: 560px; }

/* Sticks directly under the masthead so the sections stay reachable however
   far a table scrolls. Opaque, and one below the header in the stack, so a
   sideways-scrolled nav never bleeds over it. */
.nav {
  position: fixed; top: var(--header-h); left: 0; right: 0; z-index: 30;
  display: flex; gap: 2px; overflow-x: auto; padding: 0 16px;
  background: var(--surface); border-bottom: 1px solid var(--line);
  scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }
.nav-links { display: flex; gap: 2px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.nav-links::-webkit-scrollbar { display: none; }
.nav a {
  padding: 11px 13px; font-size: .88rem; text-decoration: none;
  color: var(--ink-soft); white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--ink); }
.nav a.on { color: var(--brand); border-bottom-color: var(--brand); font-weight: 500; }
.nav-empty { padding: 11px 4px; font-size: .82rem; color: var(--ink-faint); white-space: nowrap; }

/* ---------- page menu ---------- */
/* Every page the user may open, so a short bookmark bar never hides anything. */
.page-menu { position: relative; flex: none; display: flex; align-items: center; }
.page-menu-btn {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: .86rem; font-weight: 500;
  padding: 7px 11px; margin-right: 10px;
  color: var(--ink); background: var(--surface-2);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  cursor: pointer; white-space: nowrap;
}
.page-menu-btn:hover { border-color: var(--brand); color: var(--brand); }
.page-menu-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.page-menu-btn svg {
  width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.page-menu-btn .chev { width: 13px; height: 13px; transition: transform .15s ease; }
.page-menu-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .page-menu-btn .chev { transition: none; } }

.page-menu-list {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 40;
  min-width: 230px; max-height: 70vh; overflow-y: auto;
  padding: 8px; border-radius: var(--radius);
  background: var(--glass); border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  box-shadow: var(--shadow-lg);
}
.page-menu-list[hidden] { display: none; }
.page-menu-group {
  margin: 10px 8px 4px; font-family: var(--mono); font-size: .64rem;
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint);
}
.page-menu-group:first-child { margin-top: 2px; }
.page-menu-list a {
  display: block; padding: 8px 10px; border-radius: var(--radius-sm);
  border-bottom: none; color: var(--ink); font-size: .88rem;
}
.page-menu-list a:hover { background: var(--surface-2); color: var(--brand); }
.page-menu-list a.on { color: var(--brand); font-weight: 600; background: var(--brand-tint); }
.page-menu-foot { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
.page-menu-foot a { font-size: .82rem; color: var(--ink-soft); }

/* ---------- bookmark editor ---------- */
.bookmark-list { list-style: none; margin: 0 0 6px; padding: 0; }
.bookmark-list li {
  display: grid; grid-template-columns: 18px 1fr auto; gap: 10px; align-items: center;
  padding: 9px 11px; margin-bottom: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); cursor: grab;
}
.bookmark-list li.dragging { opacity: .4; }
.bookmark-list li.drop-target { border-color: var(--brand); }
.bookmark-list .grip { color: var(--ink-faint); cursor: grab; }
.bookmark-list label { display: flex; align-items: center; gap: 9px; margin: 0; cursor: pointer; }
.bookmark-list .bm-label { font-size: .9rem; }
.bookmark-list .bm-group {
  font-family: var(--mono); font-size: .68rem; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .06em;
}

h1 { font-size: 1.45rem; font-weight: 600; letter-spacing: -.018em; margin: 0 0 4px; text-wrap: balance; }
h2 { font-size: 1.08rem; font-weight: 600; letter-spacing: -.01em; margin: 26px 0 10px; }
.lede { color: var(--ink-soft); font-size: .92rem; margin: 0 0 22px; max-width: 62ch; }

/* ---------- cards ---------- */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow);
  margin-bottom: 16px;
}
.card h2 { margin-top: 0; }
.grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }

.tile { display: block; text-decoration: none; color: inherit; }
.tile:hover { border-color: var(--brand-bright); }
.tile .t { font-weight: 600; margin-bottom: 3px; }
.tile .d { color: var(--ink-soft); font-size: .85rem; }

/* ---------- forms ---------- */
.field { margin-bottom: 15px; }
.field > label {
  display: block; font-size: .84rem; font-weight: 500;
  color: var(--ink-soft); margin-bottom: 5px;
}
input[type=text], input[type=password], input[type=email], input[type=number],
input[type=date], input[type=time], input[type=tel], select, textarea {
  width: 100%; font: inherit; font-size: 16px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); padding: 10px 12px; min-height: 44px;
}
textarea { min-height: 110px; resize: vertical; }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand);
}
.help { font-size: .8rem; color: var(--ink-faint); margin-top: 4px; }
.errors { color: var(--danger); font-size: .84rem; margin-top: 5px; }
.errorlist { list-style: none; padding: 0; margin: 5px 0 0; color: var(--danger); font-size: .84rem; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font: inherit; font-size: .92rem; font-weight: 500;
  min-height: 44px; padding: 0 16px; border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink);
  cursor: pointer; text-decoration: none;
}
.btn:hover { border-color: var(--brand); color: var(--brand); }
.btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.btn-primary { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.btn-primary:hover { background: var(--brand-bright); border-color: var(--brand-bright); color: var(--brand-ink); }
.btn-danger { border-color: var(--danger); color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: #fff; }
.btn-sm { min-height: 34px; padding: 0 10px; font-size: .83rem; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
.actions { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 18px; }

/* ---------- messages ---------- */
.alert {
  border-radius: var(--radius-sm); padding: 11px 14px; margin-bottom: 14px;
  font-size: .89rem; border: 1px solid;
}
.alert-success { background: var(--ok-tint); border-color: var(--ok); color: var(--ok); }
.alert-error   { background: var(--danger-tint); border-color: var(--danger); color: var(--danger); }
.alert-warning { background: var(--warn-tint); border-color: var(--warn); color: var(--warn); }
.alert-info    { background: var(--brand-tint); border-color: var(--brand); color: var(--brand); }

/* ---------- pills ---------- */
.pill {
  display: inline-block; font-size: .73rem; font-weight: 600;
  padding: 2px 8px; border-radius: 20px; border: 1px solid; white-space: nowrap;
}
.pill-ok { color: var(--ok); border-color: var(--ok); background: var(--ok-tint); }
.pill-warn { color: var(--warn); border-color: var(--warn); background: var(--warn-tint); }
.pill-danger { color: var(--danger); border-color: var(--danger); background: var(--danger-tint); }
.pill-muted { color: var(--ink-faint); border-color: var(--line-strong); background: var(--surface-2); }

/* ---------- tables ---------- */
.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
table.data { border-collapse: collapse; width: 100%; font-size: .87rem; }
table.data th, table.data td {
  text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
table.data thead th {
  background: var(--surface-2); font-size: .74rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft);
}
/* Anything the browser scrolls to -- a fragment link, a focused field pulled
   into view, a validation error -- must land clear of the pinned chrome rather
   than underneath it. Applied broadly rather than only to :target, because
   most of those jumps never set one. */
:target,
h1, h2, h3,
.card,
.field,
[id] {
  scroll-margin-top: calc(var(--chrome-h) + var(--chrome-gap));
}
html { scroll-padding-top: calc(var(--chrome-h) + var(--chrome-gap)); }
table.data thead th a { color: inherit; text-decoration: none; display: inline-flex; gap: 4px; }
table.data thead th a:hover { color: var(--brand); }
table.data tbody tr:hover { background: var(--surface-2); }
table.data tbody tr:last-child td { border-bottom: none; }
table.data td.num, table.data td.code { font-family: var(--mono); font-variant-numeric: tabular-nums; }
tr.filters td { padding: 5px 8px; background: var(--surface); }
tr.filters input, tr.filters select {
  min-height: 32px; font-size: .82rem; padding: 4px 7px; min-width: 100px;
}
.table-toolbar {
  display: flex; gap: 9px; align-items: center; flex-wrap: wrap; margin-bottom: 11px;
}
.table-toolbar .spacer { margin-left: auto; }
.empty { padding: 34px 18px; text-align: center; color: var(--ink-faint); font-size: .9rem; }
.pagination { display: flex; gap: 6px; align-items: center; margin-top: 13px; font-size: .85rem; }

/* Column picker: checkboxes plus drag handles. */
.col-picker { display: none; margin-bottom: 12px; }
.col-picker.open { display: block; }
.col-picker ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 7px; }
.col-picker li {
  display: flex; align-items: center; gap: 7px; font-size: .84rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 6px 10px; background: var(--surface); cursor: grab;
}
.col-picker li.dragging { opacity: .45; }
.col-picker li .grip { color: var(--ink-faint); cursor: grab; }

/* ---------- glass modal ---------- */
/* One dialog serves the whole application. Native alert/confirm/prompt are not
   used anywhere in this project: they cannot be themed, they block the page,
   and they read as though they came from somewhere other than the portal. */
/* Centred on the viewport.
   This only holds while the dialog is a child of <body>. backdrop-filter --
   which the masthead, nav and footer all carry -- establishes a containing
   block for fixed-position descendants, so a dialog nested inside one of those
   bars centres on the bar instead of the screen. Keep dialogs at body level;
   modal.js appends its own there for the same reason. */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 200;
  display: none; align-items: center; justify-content: center; padding: 18px;
  background: var(--scrim);
  -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
}
.modal-backdrop.open { display: flex; }
.modal-glass {
  width: 100%; max-width: 460px; max-height: 86vh; overflow-y: auto;
  background: var(--glass);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  backdrop-filter: blur(18px) saturate(150%);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 20px; color: var(--ink);
}
.modal-glass.wide { max-width: 720px; }
.modal-glass h2 { margin: 0 0 6px; font-size: 1.05rem; }
.modal-glass .modal-body { margin: 0 0 16px; color: var(--ink-soft); font-size: .89rem; }
.modal-actions { display: flex; gap: 8px; }
.modal-actions .btn { flex: 1; }
@media (prefers-reduced-motion: no-preference) {
  .modal-backdrop.open .modal-glass { animation: modal-in .14s ease-out; }
  @keyframes modal-in {
    from { opacity: 0; transform: translateY(6px) scale(.985); }
    to { opacity: 1; transform: none; }
  }
}

/* ---------- policy prose ---------- */
.policy {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 18px 20px;
  max-height: 52vh; overflow-y: auto; font-size: .92rem; line-height: 1.62;
}
.policy h1, .policy h2, .policy h3 { margin: 18px 0 8px; font-size: 1rem; }
.policy h1:first-child, .policy h2:first-child { margin-top: 0; }
.policy p, .policy li { color: var(--ink-soft); }
.policy ul, .policy ol { padding-left: 22px; }

/* ---------- stock meters ---------- */
.meter { height: 7px; border-radius: 4px; background: var(--surface-2); border: 1px solid var(--line); overflow: hidden; }
.meter i { display: block; height: 100%; background: var(--brand-bright); }
.meter.low i { background: var(--warn); }
.meter.empty i { background: var(--danger); }
.stock-row { display: grid; grid-template-columns: 1fr 2fr auto; gap: 12px; align-items: center; padding: 9px 0; border-bottom: 1px solid var(--line); }
.stock-row:last-child { border-bottom: none; }
.stock-row .n { font-family: var(--mono); font-size: .82rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* ---------- misc ---------- */
.mono { font-family: var(--mono); }
.muted { color: var(--ink-faint); }
.small { font-size: .84rem; }
.code-badge {
  font-family: var(--mono); font-size: 1.6rem; font-weight: 600; letter-spacing: .1em;
  background: var(--brand-tint); color: var(--brand);
  border: 1px solid var(--brand); border-radius: var(--radius);
  padding: 14px 20px; display: inline-block; margin: 8px 0;
}
.theme-toggle { display: flex; gap: 3px; border: 1px solid var(--line); border-radius: 20px; padding: 2px; }
.theme-toggle button {
  border: none; background: none; color: var(--ink-faint); cursor: pointer;
  font: inherit; font-size: .75rem; padding: 4px 9px; border-radius: 16px; min-height: 30px;
}
.theme-toggle button.on { background: var(--brand); color: var(--brand-ink); }
.perm-group { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px; margin-bottom: 11px; }
.perm-group h3 { margin: 0 0 8px; font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); }
.perm-group label { display: flex; gap: 8px; align-items: flex-start; font-size: .87rem; padding: 4px 0; }
.perm-group label.off { opacity: .45; }
.countdown { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* The wordmark is longer than the header has room for on a narrow phone, so it
   truncates rather than pushing the sign-out button off screen. The brand mark
   beside it keeps the portal identifiable when the text is clipped. */
.app-header .wordmark {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 46vw;
}
@media (min-width: 620px) { .app-header .wordmark { max-width: none; } }

/* ---------- notification centre ---------- */
/* Replaces the toast strip: a toast is gone in six seconds whether or not
   anyone read it, which is wrong for "the code was issued" or "that group
   could not be deleted". */
.bell {
  position: relative; flex: none;
  width: 38px; height: 38px; padding: 0;
  display: grid; place-items: center;
  background: none; border: 1px solid var(--line); border-radius: 50%;
  color: var(--ink-soft); cursor: pointer;
}
.bell:hover { color: var(--brand); border-color: var(--brand); }
.bell:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.bell svg {
  width: 19px; height: 19px; fill: none;
  stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.bell-count {
  position: absolute; top: -3px; right: -3px;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: grid; place-items: center;
  border-radius: 9px; background: var(--danger); color: #fff;
  font-family: var(--mono); font-size: .66rem; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
  border: 2px solid var(--surface);
}
.bell-count.is-zero { display: none; }

.notif-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
}
.notif-head h2 { margin: 0; flex: 1; }

.notif-list { list-style: none; margin: 0 0 14px; padding: 0; max-height: 46vh; overflow-y: auto; }
.notif-list-overlay { max-height: 40vh; margin-bottom: 16px; }

.notif {
  display: grid; grid-template-columns: 10px 1fr; gap: 10px;
  padding: 11px 4px; border-bottom: 1px solid var(--line);
}
.notif:last-child { border-bottom: none; }
.notif-dot {
  width: 8px; height: 8px; border-radius: 50%; margin-top: 6px;
  background: var(--ink-faint);
}
.notif-success .notif-dot { background: var(--ok); }
.notif-info    .notif-dot { background: var(--brand); }
.notif-warning .notif-dot { background: var(--warn); }
.notif-error   .notif-dot { background: var(--danger); }
.notif-body p { margin: 0 0 3px; font-size: .89rem; color: var(--ink); }
.notif-body time {
  font-family: var(--mono); font-size: .72rem; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
/* Unread carries weight as well as colour, so the distinction survives being
   printed, screenshotted, or read by someone who cannot separate the hues. */
.notif.is-unread .notif-body p { font-weight: 600; }
.notif.is-unread { background: var(--surface-2); border-radius: var(--radius-sm); }
.notif-empty { padding: 26px 4px; text-align: center; color: var(--ink-faint); font-size: .88rem; }

.notif-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.notif-actions form { flex: 1; }
.notif-actions .btn { width: 100%; }

/* The frost on all three pinned strips.
   Gated on @supports: without a working backdrop-filter a translucent bar just
   lets the page show through and the text becomes unreadable, so those
   browsers keep the opaque background set above. */
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .app-header, .nav, .app-footer {
    background: var(--glass-bar);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(165%);
    backdrop-filter: blur(var(--glass-blur)) saturate(165%);
  }
  /* A hairline highlight along the lower edge. At low opacity the border alone
     stops reading as an edge, and without one the strip and the page merge
     into each other as content scrolls past. */
  .app-header, .nav { box-shadow: 0 1px 0 rgba(var(--glass-rgb), .35); }
  .app-footer { box-shadow: 0 -1px 0 rgba(var(--glass-rgb), .35); }
}

/* ---------- footer ---------- */
/* Fixed to the viewport, not to the end of the document: the strip stays
   visible however far a page scrolls. Opaque rather than glass so content
   passing beneath it never shows through and muddies the text.

   z-index sits below the modal layer (200) so a dialog covers it, and below
   the sticky header (30) which it never meets anyway. */
.app-footer {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 25;
  padding: 13px 16px calc(13px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--surface);
  text-align: center;
}
.app-footer p {
  margin: 0; max-width: none;
  font-size: .78rem; line-height: 1.35; color: var(--ink-faint);
}

/* On a narrow phone the line wraps to two rows, so the strip is taller and the
   clearance above must grow with it. Measured, not guessed: the text is ~53
   characters and wraps below roughly 420px. */
@media (max-width: 420px) {
  :root { --footer-h: 62px; }
}


/* ---------- range sliders ---------- */
/* The generic input rule above gives every control a 44px box and a border,
   which turns a range input into a bordered strip with a track floating in it.
   These are for judgement calls you make by eye, so they get a real track. */
input[type=range] {
  -webkit-appearance: none; appearance: none;
  width: 100%; min-height: 0; height: 6px; padding: 0;
  margin: 14px 0; border: none; border-radius: 3px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--brand); border: 2px solid var(--surface);
  box-shadow: var(--shadow); cursor: grab;
}
input[type=range]::-moz-range-thumb {
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--brand); border: 2px solid var(--surface);
  box-shadow: var(--shadow); cursor: grab; border-color: var(--surface);
}
input[type=range]:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.field > label output {
  font-weight: 600; color: var(--brand); font-variant-numeric: tabular-nums;
}
