/*
 * 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);
  /* The dim behind a dialog. Deep enough that the page reads as *behind*
     something rather than merely tinted -- at a third opacity the content
     stayed legible and competed with the dialog for attention. */
  --scrim: rgba(14, 21, 30, .58);
  /* 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(2, 5, 9, .72);
    --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(2, 5, 9, .72);
  --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; }
/* Table screens take the whole viewport. A log with ten columns squeezed into
   a 1080px column scrolls sideways on a monitor with room to spare, and these
   pages exist to be scanned across. Only the side gutter is kept. */
.page.wide { max-width: none; padding-left: 24px; padding-right: 24px; }
@media (min-width: 1600px) { .page.wide { padding-left: 32px; padding-right: 32px; } }

/* 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; align-items: center; gap: 2px; padding: 0 16px;
  background: var(--surface); border-bottom: 1px solid var(--line);
  /* Deliberately NOT overflow-x: auto. The page menu hangs out of the bottom
     of this bar, and any overflow value other than visible on an ancestor
     clips it -- the dropdown opened but rendered invisible. Sideways scrolling
     for a long bookmark bar lives on .nav-links, which contains no popup. */
}
.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(9px); backdrop-filter: blur(9px);
}
.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 ---------- */
/* A popover hanging off the bell, top right -- not a centred dialog over a
   dimmed page. That treatment is reserved for confirmations, which must not be
   missed or dismissed by accident. A notification is something to glance at,
   so it neither dims nor blurs what is behind it. */
.notif-wrap { position: relative; flex: none; display: flex; align-items: center; }

.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-pop {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 45;
  width: 370px; max-width: calc(100vw - 24px);
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.notif-pop[hidden] { display: none; }
/* A small pointer back to the bell, so the popover reads as belonging to it
   rather than floating loose over the page. */
.notif-pop::before {
  content: ""; position: absolute; top: -7px; right: 13px;
  width: 12px; height: 12px; transform: rotate(45deg);
  background: var(--surface);
  border-left: 1px solid var(--line-strong);
  border-top: 1px solid var(--line-strong);
}
@media (prefers-reduced-motion: no-preference) {
  .notif-pop { animation: notif-in .13s ease-out; }
  @keyframes notif-in {
    from { opacity: 0; transform: translateY(-5px); }
    to { opacity: 1; transform: none; }
  }
}

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

.notif-list { list-style: none; margin: 0 0 12px; padding: 0; max-height: 52vh; overflow-y: auto; }
.notif {
  display: grid; grid-template-columns: 10px 1fr; gap: 10px;
  padding: 10px 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: .87rem; color: var(--ink); }
.notif-body time {
  font-family: var(--mono); font-size: .71rem; 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: 22px 4px; text-align: center; color: var(--ink-faint); font-size: .86rem; }

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

/* ---------- 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;
}

/* ---------- collapsible help ---------- */
/* Used on the 2FA enrolment screen for the optional icon instructions. Folded
   away by default: it is cosmetic, and the screen's real job is getting a code
   scanned. */
.icon-help {
  margin: 16px 0 4px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.icon-help > summary {
  cursor: pointer; font-size: .86rem; font-weight: 500; color: var(--brand);
  list-style: none;
}
.icon-help > summary::-webkit-details-marker { display: none; }
.icon-help > summary::before { content: "+ "; font-family: var(--mono); }
.icon-help[open] > summary::before { content: "− "; }
.icon-help > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.icon-help p, .icon-help ul { margin: 10px 0; }
.icon-help ul { padding-left: 20px; }
.icon-help li { margin-bottom: 4px; }


/* An amount and its unit read as one control, so they sit on one row with the
   unit only as wide as it needs to be. */
.split { display: flex; gap: 8px; align-items: stretch; }
.split > :first-child { flex: 1; min-width: 0; }
.split > select { flex: 0 0 auto; width: auto; min-width: 88px; }
