/* NetMagen Vision portal — /blog-assets/portal.css?v=<filemtime>
   Standalone on purpose, same reasoning as admin.css: importing blog.css
   would pull the whole public stylesheet into a dense data UI.

   ADAPTED FROM admin.css originally; REDESIGNED 2026-09-02 (owner request:
   "stunning on all screens"). What changed and why:
     - the content column is CAPPED (--content-max) and centered — before
       this, tiles/tables/forms stretched to 2,500px on wide monitors;
     - IBM Plex now actually loads (self-hosted woff2 subsets under
       fonts/ — latin + hebrew + cyrillic; before this the declared family
       silently fell back to Segoe UI on every machine without Plex);
     - the sidebar gained icons and an app-bar mode ≤900px (horizontal,
       scrollable, no wrapping), the topbar is sticky, tiles/cards/tables
       got a consistent radius/spacing/shadow scale, and the chart grew a
       real axis + tooltip vocabulary (.chart-grid/.chart-tip).
   The COMPONENT VOCABULARY IS UNCHANGED — every pre-redesign class keeps
   its name and contract (portal.js and the templates depend on them);
   only presentation moved. Logical properties throughout (he is RTL). */

/* ── self-hosted IBM Plex (unicode-range subsets, fontsource files) ──── */

@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-display: swap; font-weight: 400;
  src: url('fonts/ibm-plex-sans-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-display: swap; font-weight: 600;
  src: url('fonts/ibm-plex-sans-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-display: swap; font-weight: 700;
  src: url('fonts/ibm-plex-sans-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-display: swap; font-weight: 400;
  src: url('fonts/ibm-plex-sans-cyrillic-400-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-display: swap; font-weight: 600;
  src: url('fonts/ibm-plex-sans-cyrillic-600-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-display: swap; font-weight: 700;
  src: url('fonts/ibm-plex-sans-cyrillic-700-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face {
  font-family: 'IBM Plex Sans Hebrew'; font-style: normal; font-display: swap; font-weight: 400;
  src: url('fonts/ibm-plex-sans-hebrew-hebrew-400-normal.woff2') format('woff2');
  unicode-range: U+0307-0308,U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F;
}
@font-face {
  font-family: 'IBM Plex Sans Hebrew'; font-style: normal; font-display: swap; font-weight: 600;
  src: url('fonts/ibm-plex-sans-hebrew-hebrew-600-normal.woff2') format('woff2');
  unicode-range: U+0307-0308,U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F;
}
@font-face {
  font-family: 'IBM Plex Sans Hebrew'; font-style: normal; font-display: swap; font-weight: 700;
  src: url('fonts/ibm-plex-sans-hebrew-hebrew-700-normal.woff2') format('woff2');
  unicode-range: U+0307-0308,U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-display: swap; font-weight: 400;
  src: url('fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-display: swap; font-weight: 600;
  src: url('fonts/ibm-plex-mono-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* ── tokens ──────────────────────────────────────────────────────────── */

:root {
  --paper:      #F4F8F8;
  --card:       #FFFFFF;
  --field:      #FBFDFD;
  --tint:       #E9F3F2;
  --tint-brand: #E3F3F1;
  --line:       #D9E6E4;
  --line-soft:  #E4EEEC;
  --ink:        #10262A;
  --ink-2:      #4E6E6A;
  --brand:        #0E7C97;
  --brand-strong: #0B5F79;
  --accent:       #0E7C72;
  --navy:       #0F2A2E;
  --navy-2:     #0C2226;
  --on-navy:    #FFFFFF;
  --on-navy-2:  #A9C6C2;
  --line-navy:  #1F4448;
  --ok:         #1D7A4C;
  --alert:      #C43D3D;
  --amber:      #9A6700;
  /* Archive timeline charts: two blues for motion, two reds for audio, on a
     neutral footage base so nothing blends into the page (owner request 2026-09-15). */
  --tl-footage:         #CBD5DA;
  --tl-footage-charted: #E6EBEE;
  --tl-motion:          #1D4F91;
  --tl-motion-fine:     #8EC3E6;
  --tl-audio-loud:      #C1272D;
  --tl-audio-low:       #F3A6A6;
  --tl-playhead:        #0F2A2E;
  --ease:       cubic-bezier(0.22, 1, 0.36, 1);
  --dur-fast:   180ms;
  --font-sans:  'IBM Plex Sans', 'IBM Plex Sans Hebrew', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono:  'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Menlo, monospace;
  --radius:     14px;
  --radius-s:   9px;
  --shadow-1:    0 1px 2px rgb(16 38 42 / 0.05), 0 1px 1px rgb(16 38 42 / 0.03);
  --shadow-lift: 0 14px 34px -14px rgb(16 38 42 / 0.22);

  --gap-1: 4px;  --gap-2: 8px;  --gap-3: 14px;
  --gap-4: 20px; --gap-5: 30px; --gap-6: 46px;
  --sidebar: 252px;
  --content-max: 1520px;
  --gutter: clamp(16px, 2.4vw, 40px);
}

* { box-sizing: border-box; }

/* Focus- and scrollIntoView-driven scrolling stops BELOW the sticky chrome
   (topbar + a sticky toolbar on desktop, the app bar on phones) instead of
   parking the focused control underneath it. portal.js measures the stack
   into --sticky-h (initStickyOffsets); 0 until it runs. */
html { scroll-padding-block-start: var(--sticky-h, 0px); }

[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.55 var(--font-sans);
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
}

a { color: var(--brand); }
a:hover { color: var(--brand-strong); }

code, .mono, pre { font-family: var(--font-mono); font-size: 0.92em; }

.skip {
  position: absolute; inset-inline-start: -9999px; top: 0; z-index: 100;
  background: var(--navy); color: var(--on-navy); padding: var(--gap-2) var(--gap-3);
}
.skip:focus { inset-inline-start: 0; }

.muted { color: var(--ink-2); }
.small { font-size: 13px; }
.spacer { flex: 1 1 auto; }
.inline { display: inline-flex; gap: var(--gap-2); align-items: center; margin: 0; }

/* ── impersonation banner ───────────────────────────────────────────── */

.imp-banner {
  background: #FDF4E3; color: #6B4700; border-block-end: 1px solid var(--amber);
  padding: var(--gap-2) var(--gap-4); text-align: center; font-size: 13px; font-weight: 600;
  position: relative; z-index: 60;
}

/* ── shell ───────────────────────────────────────────────────────────── */

.shell { display: grid; grid-template-columns: var(--sidebar) minmax(0, 1fr); min-height: 100vh; }

.sidebar {
  background: linear-gradient(180deg, var(--navy) 0%, var(--navy-2) 100%);
  color: var(--on-navy);
  display: flex; flex-direction: column; gap: var(--gap-4);
  padding: var(--gap-4) var(--gap-3);
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.brand { display: flex; gap: 10px; align-items: center; font-weight: 700; letter-spacing: -0.01em; padding-inline: 6px; }
.brand-mark {
  display: grid; place-items: center; inline-size: 34px; block-size: 34px; flex: 0 0 auto;
  background: linear-gradient(135deg, var(--brand) 0%, var(--accent) 100%);
  border-radius: 10px; font-size: 13px; color: #fff;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.22);
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text small { font-weight: 400; font-size: 11px; color: var(--on-navy-2); letter-spacing: 0.04em; }

.nav { display: flex; flex-direction: column; gap: 3px; }
.nav-item {
  display: flex; align-items: center; gap: 10px;
  color: var(--on-navy-2); text-decoration: none; padding: 9px 12px;
  border-radius: 10px; font-size: 14px; position: relative;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav-item:hover { background: #14383D; color: var(--on-navy); }
.nav-item.is-active { background: var(--brand); color: #fff; font-weight: 600; box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.16); }
.nav-ico { display: inline-grid; place-items: center; inline-size: 18px; block-size: 18px; flex: 0 0 auto; opacity: 0.9; }
.nav-ico svg { inline-size: 18px; block-size: 18px; display: block; }
.nav-txt { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sidebar-foot { margin-block-start: auto; padding-inline: 6px; }
.env {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font: 600 11px/1.6 var(--font-mono); letter-spacing: 0.08em;
  background: var(--line-navy); color: var(--on-navy-2);
}
.env-dev { background: var(--amber); color: #fff; }
.env-prod { background: var(--ok); color: #fff; }

.main-col { display: flex; flex-direction: column; min-inline-size: 0; }

/* Sticky topbar: full-bleed bar, centered inner column (same cap as the
   content) so the title tracks the content edge on every viewport. */
.topbar {
  position: sticky; top: 0; z-index: 40;
  border-block-end: 1px solid var(--line); background: var(--card);
}
.topbar-inner {
  display: flex; gap: var(--gap-3); align-items: center; justify-content: space-between;
  max-inline-size: var(--content-max); margin-inline: auto; inline-size: 100%;
  padding: 13px var(--gutter); min-inline-size: 0;
}
.topbar h1 { margin: 0; font-size: 21px; letter-spacing: -0.02em; line-height: 1.25; }
.subtitle { margin: 1px 0 0; color: var(--ink-2); font-size: 13px; }
.topbar-title { min-inline-size: 0; }
.topbar-right { display: flex; gap: var(--gap-3); align-items: center; min-inline-size: 0; }
.who { font-size: 13px; color: var(--ink-2); min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── sub-users (spec 2026-09-03) ─────────────────────────────────────── */

/* The member context chip beside the signed-in address: "Viewing Alice's account". */
.member-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px;
  background: var(--tint-brand); color: var(--brand-strong); font-size: 12px; font-weight: 600;
  min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Owner-only chrome inside shared templates is hidden for a member (the server refuses it anyway). */
body.is-member .owner-only { display: none; }

/* Permission / group checkbox grids (the group form, the invite + edit dialogs). */
.cap-grid { display: grid; gap: var(--gap-2); grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
.cap-grid .inline, .camera-list .inline, .sched-days .inline { align-items: flex-start; font-size: 13px; }
.cap-grid input, .camera-list input, .sched-days input { margin-block-start: 2px; }
.camera-list { display: grid; gap: var(--gap-2); grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); }
.sched-days { display: flex; gap: var(--gap-3); flex-wrap: wrap; }
[data-schedule-fields].is-off { opacity: 0.45; pointer-events: none; }
.camera-list.is-off { opacity: 0.45; pointer-events: none; }

/* A member's access state on the overview and in the members table. */
.acc-state { vertical-align: middle; }
.plain-list { margin: 0; padding-inline-start: 1.2em; display: grid; gap: 4px; }

/* The copied invitation link: read-only, LTR, monospace so it can be selected whole. */
.invite-url { font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; }

.members-table td, .activity-table td, .groups-table td { vertical-align: top; }
.members-table .row-actions { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
.members-table .sub { display: block; font-size: 12px; color: var(--ink-2); margin-block-start: 2px; }
.activity-table .what { max-inline-size: 48ch; }

/* ── locale switcher (site LocaleSwitcher pattern: same path, new prefix) ── */

.locales { display: flex; gap: 2px; }
.locale {
  padding: 4px 8px; border-radius: var(--radius-s); font-size: 12px; font-weight: 600;
  color: var(--ink-2); text-decoration: none;
}
.locale:hover { background: var(--tint); color: var(--ink); }
.locale[aria-current='true'] { background: var(--brand); color: #fff; }
.bare-locales { display: flex; justify-content: end; }

.content {
  max-inline-size: var(--content-max); margin-inline: auto; inline-size: 100%;
  padding: var(--gap-4) var(--gutter) var(--gap-6);
  display: grid; gap: var(--gap-4); align-content: start;
}

/* Grid items default to `min-width: auto` -- a content-based minimum that a
   wide child (the 7-column sessions table, a nested minmax() grid, a long
   unbroken string) can inflate far past the viewport, pushing the whole
   column out of view instead of scrolling inside its own .table-wrap.
   Measured live 2026-09-02: on a 375px phone the Security page's .cols
   refused to shrink below 624px. Every direct child of the content grid
   must be allowed to shrink. */
.content > * { min-inline-size: 0; }

/* A page region that is a plain STACK of blocks (title row, filters bar,
   grid or table, pager, empty state) gets one block gap between them. A bare
   `.region` is display:block, so they sat flush: 0px between the Persons
   filters bar and its first row of cards (owner report 2026-09-20), and the
   same measured on Review, Plates, Activity, Users and Groups. Named one by
   one because the other regions own their layout (live, archive, status, and
   the credit/usage grid further down).
   A flex COLUMN rather than a grid because of Persons' position:sticky
   toolbar: a flex item's containing block is the whole container in every
   engine, while a grid item's is its own grid area -- one row tall here.
   Chromium keeps such a bar stuck anyway (probed 2026-09-20, the only engine
   installed on this box); the column is the choice that does not lean on that.
   Two things a gap makes visible: the empty state is a <p> whose UA margin
   does not collapse in a flex container and would stack on the gap; and a
   list container with no rows yet (Persons' grid, Review's list) is 0px tall
   but still an item, so the empty state under it sat TWO gaps down (40px,
   measured on the live Review page). */
.region:is([data-region="persons"], [data-region="review"], [data-region="plates"], [data-region="activity"], [data-region="members"], [data-region="groups"]) {
  display: flex; flex-direction: column; gap: var(--gap-4);
}
.region:is([data-region="persons"], [data-region="review"], [data-region="plates"], [data-region="activity"], [data-region="members"], [data-region="groups"]) > .empty { margin: 0; }
.region:is([data-region="persons"], [data-region="review"], [data-region="plates"], [data-region="activity"], [data-region="members"], [data-region="groups"]) > [data-rows]:empty { display: none; }

/* ── flashes ─────────────────────────────────────────────────────────── */

.flashes {
  display: grid; gap: var(--gap-2);
  max-inline-size: var(--content-max); margin: var(--gap-3) auto 0; inline-size: 100%;
  padding-inline: var(--gutter);
}
.flash { padding: var(--gap-3) var(--gap-4); border-radius: var(--radius); font-size: 14px; border: 1px solid; }
.flash-ok { background: #ECF7F1; border-color: var(--ok); color: #105534; }
.flash-error { background: #FCEDED; border-color: var(--alert); color: #7A1F1F; }
.flash-warn { background: #FDF4E3; border-color: var(--amber); color: #6B4700; }

/* ── cards ───────────────────────────────────────────────────────────── */

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--gap-4); display: grid; gap: var(--gap-3); align-content: start;
  box-shadow: var(--shadow-1); min-inline-size: 0;
}
.card-emphasis { border-inline-start: 4px solid var(--brand); }
.card-title { margin: 0; font-size: 15px; letter-spacing: -0.01em; display: flex; gap: var(--gap-2); align-items: center; }
.card-hint { margin: 0; color: var(--ink-2); font-size: 13px; max-inline-size: 68ch; }

/* Constrain in-card forms so a password field never spans a monitor. */
.card > form.stack { max-inline-size: 520px; }
.stack > .btn { justify-self: start; }

/* min(Xpx, 100%) throughout, never a bare minmax(Xpx, 1fr): a bare fixed
   minimum makes the TRACK wider than a narrow container instead of
   collapsing to one full-width column (same 2026-09-02 measurement as the
   .content > * rule above). */
.cols { display: grid; gap: var(--gap-4); grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); min-inline-size: 0; }

/* ── badges, chips ───────────────────────────────────────────────────── */

.badge, .chip {
  display: inline-block; border-radius: 999px; padding: 1px 8px;
  font: 600 11px/1.7 var(--font-mono); letter-spacing: 0.06em; white-space: nowrap;
}
.chip-active, .chip-done { background: #E4F3EB; color: #105534; }
.chip-pending { background: var(--tint); color: var(--ink-2); }
.chip-disabled { background: #FCEDED; color: #7A1F1F; }

/* ── buttons ─────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border-radius: var(--radius-s); border: 1px solid transparent; cursor: pointer;
  font: 600 13px/1 var(--font-sans); padding: 10px 15px; text-decoration: none;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--brand); color: #fff; box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.18); }
.btn-primary:hover:not(:disabled) { background: var(--brand-strong); color: #fff; }
.btn-ghost { background: var(--card); border-color: var(--line); color: var(--ink); }
.btn-ghost:hover:not(:disabled) { background: var(--tint); color: var(--ink); }
.btn-ghost.is-on { background: var(--tint-brand); border-color: var(--brand); color: var(--brand-strong); }
.btn-danger { background: var(--alert); color: #fff; }
.btn-danger:hover:not(:disabled) { background: #A63030; color: #fff; }
.btn-sm { padding: 6px 11px; font-size: 12px; }

.toolbar { display: flex; gap: var(--gap-2); align-items: center; flex-wrap: wrap; }

/* Link-shaped button (the sessions table's location cell): a real button,
   dressed as a link -- keyboard/AT get button semantics for free. */
.link-btn {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--brand);
  text-decoration: underline; text-underline-offset: 2px; text-align: start;
}
.link-btn:hover { color: var(--brand-strong); }

/* ── forms ───────────────────────────────────────────────────────────── */

.stack { display: grid; gap: var(--gap-3); }
.field { display: grid; gap: 4px; }
.label { font-size: 12px; font-weight: 600; color: var(--ink-2); display: flex; gap: 6px; align-items: baseline; }
.hint { font-size: 12px; color: var(--ink-2); }
/* The sub-user limit reached (users.max_members): amber, not red -- a full
   account is a state the owner acts on, not a mistake they made. */
.hint-limit { color: var(--amber); font-weight: 600; }
.field-error { font-size: 12px; color: var(--alert); font-weight: 600; }
.has-error input, .has-error select { border-color: var(--alert); }

input[type=text], input[type=email], input[type=tel], input[type=password],
input[type=search], input[type=number], input[type=datetime-local], input[type=date], input[type=time], select {
  font: inherit; font-size: 14px; color: var(--ink); background: var(--field);
  border: 1px solid var(--line); border-radius: var(--radius-s); padding: 9px 11px;
  inline-size: 100%; min-inline-size: 0;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
/* Date and time fields (owner report, 2026-09-17). The rule above gives them
   the box every other field has; Chromium still wraps their digits in an inner
   field 2 px taller than a text line and draws the picker glyph in solid black,
   so a date filter stood taller and darker than the fields beside it. The
   engines that do not know these pseudo-elements drop the rules, harmlessly. */
input:is([type=date], [type=datetime-local], [type=time])::-webkit-datetime-edit-fields-wrapper { padding-block: 0; }
input:is([type=date], [type=datetime-local], [type=time])::-webkit-calendar-picker-indicator { cursor: pointer; opacity: 0.6; }
input:is([type=date], [type=datetime-local], [type=time]):hover::-webkit-calendar-picker-indicator { opacity: 0.9; }
input:hover, select:hover { border-color: #C4D7D4; }
input:focus-visible, select:focus-visible, .btn:focus-visible, a:focus-visible,
.link-btn:focus-visible, summary:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

.filters {
  display: flex; gap: var(--gap-3) 18px; align-items: end; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--gap-3) var(--gap-4); box-shadow: var(--shadow-1);
}
.filters .field { min-inline-size: 150px; }

.region-state { font-size: 12px; color: var(--ink-2); }
.region-state.is-error { color: var(--alert); font-weight: 600; }

/* Usage range controls: preset chips + from/to + points. */
.usage-filters { align-items: end; }
.preset-chips { display: flex; gap: 6px; align-self: end; padding-block-end: 1px; }
.usage-filters .field { min-inline-size: 0; }
.usage-filters input[type=datetime-local] { inline-size: auto; }

/* ── tables ──────────────────────────────────────────────────────────── */

.table-wrap {
  overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); box-shadow: var(--shadow-1);
}
.table { inline-size: 100%; border-collapse: collapse; font-size: 13px; }
.table th, .table td { padding: 11px 14px; text-align: start; border-block-end: 1px solid var(--line-soft); vertical-align: top; }
.table thead th {
  position: sticky; top: 0; background: #EEF6F5; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--ink-2); z-index: 1; white-space: nowrap;
}
.table tbody tr:last-child td { border-block-end: 0; }
.table tbody tr:hover { background: #F4FAF9; }
.actions-col { white-space: nowrap; text-align: end; }
.actions-col .btn { margin-inline-end: 4px; }

.pager { display: flex; gap: var(--gap-3); align-items: center; justify-content: center; font-size: 13px; }
.empty {
  text-align: center; color: var(--ink-2); padding: var(--gap-5);
  border: 1px dashed var(--line); border-radius: var(--radius); background: var(--card);
}

/* ── auth screens: sign-in / sign-up / forgot / reset ──────────── */
/* Redesigned 2026-09-02. These four screens used to be a 430px .bare-card
   alone in the middle of the viewport; on a wide monitor that is mostly
   background. The split puts the product's name and what it does beside the
   form at the moment someone is deciding to sign up, and collapses to a slim
   header <=900px so a phone loses nothing. Logical properties throughout --
   Hebrew mirrors with no extra rules. .bare-card is untouched and still
   serves /locked, /delete-account and /credit. */

body.auth { min-block-size: 100vh; background: var(--paper); }

.auth-split {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  min-block-size: 100vh;
}

.auth-aside {
  display: flex; flex-direction: column; justify-content: space-between; gap: var(--gap-5);
  padding: clamp(28px, 4vw, 56px);
  background: linear-gradient(150deg, var(--brand-strong) 0%, var(--brand) 46%, var(--accent) 100%);
  color: #fff;
}
.auth-brand { display: flex; gap: var(--gap-2); align-items: center; font-weight: 700; font-size: 17px; }
.auth-brand-text small { font-weight: 400; opacity: 0.78; margin-inline-start: 4px; }
/* The mark sits ON the gradient here, so it needs its own contrast rather
   than the gradient it carries everywhere else. */
.auth-aside .brand-mark { background: rgb(255 255 255 / 0.17); box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.22); }

.auth-headline {
  margin: 0 0 var(--gap-4) 0;
  font-size: clamp(24px, 2.4vw, 33px); line-height: 1.22; font-weight: 700; letter-spacing: -0.02em;
  max-inline-size: 19ch;
}
.auth-points { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--gap-3); max-inline-size: 42ch; }
.auth-points li { position: relative; padding-inline-start: 26px; font-size: 15px; line-height: 1.5; opacity: 0.93; }
.auth-points li::before {
  content: '✓'; position: absolute; inset-inline-start: 0; top: 0;
  font-weight: 700; opacity: 0.85;
}

.auth-aside .locale { color: #fff; opacity: 0.82; }
.auth-aside .locale:hover { opacity: 1; color: #fff; }
.auth-aside .locale[aria-current='true'] { background: rgb(255 255 255 / 0.2); opacity: 1; color: #fff; }

.auth-main { display: grid; place-items: center; padding: clamp(28px, 4vw, 56px) clamp(16px, 4vw, 48px); }
.auth-card { inline-size: min(430px, 100%); display: grid; gap: var(--gap-3); }
.auth-card h1 { margin: 0; font-size: 25px; line-height: 1.2; letter-spacing: -0.02em; }
.auth-lede { margin: -4px 0 var(--gap-2) 0; color: var(--ink-2); font-size: 14px; line-height: 1.5; }

/* First + last name share a row; everything else stays full width. The pair
   stacks below 420px, where two half-width fields stop being typable. */
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-3); }
@media (max-width: 420px) { .field-pair { grid-template-columns: 1fr; } }

.auth-alt { margin: 0; font-size: 13px; color: var(--ink-2); }
.auth-alt a { font-weight: 600; }

@media (max-width: 900px) {
  .auth-split { grid-template-columns: 1fr; min-block-size: 0; }
  .auth-aside {
    flex-direction: row; align-items: center; justify-content: space-between; gap: var(--gap-3);
    padding: var(--gap-3) clamp(16px, 4vw, 28px);
  }
  /* The pitch is the panel's whole reason to exist on desktop, and the first
     thing to go on a phone: a sign-in form must not open below the fold. */
  .auth-pitch { display: none; }
  .auth-main { padding: var(--gap-4) clamp(16px, 4vw, 28px) var(--gap-5); }
}


/* ── bare pages (login / lockout / error) ────────────────────────────── */

body.bare { display: grid; place-items: center; min-block-size: 100vh; padding: var(--gap-4); background: var(--paper); }
.bare-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: var(--gap-5); inline-size: min(430px, 100%); display: grid; gap: var(--gap-3);
  box-shadow: var(--shadow-lift);
}
.bare-card h1 { margin: 0; font-size: 20px; letter-spacing: -0.02em; }
.bare-brand { display: flex; gap: var(--gap-2); align-items: center; font-weight: 700; }
.bare-brand small { font-weight: 400; color: var(--ink-2); }

/* The adaptive login captcha (Blog\LoginCaptcha) -- admin.css's rule verbatim,
   including WHY the wrapper is constrained as well as scaled: transform changes
   paint only, so without `inline-size: 100%` + `overflow: hidden` the widget's
   304px layout width widens .bare-card and the sign-in page scrolls sideways on
   a phone. See admin.css for the measurements. */
.captcha-field {
  display: grid; justify-items: start;
  inline-size: 100%; min-block-size: 78px; overflow: hidden;
}
.captcha-field .g-recaptcha { transform-origin: 0 0; }
@media (max-width: 400px) {
  .captcha-field { min-block-size: 0; block-size: 67px; }
  .captcha-field .g-recaptcha { transform: scale(0.85); }
}
@media (max-width: 340px) {
  .captcha-field { block-size: 57px; }
  .captcha-field .g-recaptcha { transform: scale(0.72); }
}

.field-inline { gap: 2px; }
.num { text-align: end; font-variant-numeric: tabular-nums; }

/* ── icon / star buttons (persons grid + detail header) ─────────────── */

.icon-btn {
  display: inline-grid; place-items: center; inline-size: 26px; block-size: 26px;
  border: 1px solid transparent; border-radius: var(--radius-s); background: transparent;
  color: var(--ink-2); cursor: pointer; font-size: 13px; line-height: 1;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.icon-btn:hover { background: var(--tint); color: var(--ink); }

.star-btn {
  display: inline-grid; place-items: center; inline-size: 30px; block-size: 30px;
  border: 1px solid var(--line); border-radius: var(--radius-s); background: var(--card);
  color: var(--line); cursor: pointer; font-size: 15px; line-height: 1;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.star-btn:hover { color: var(--amber); border-color: var(--amber); }
.star-btn.is-active { color: var(--amber); border-color: var(--amber); background: #FDF4E3; }

/* ── persons grid (redesigned 2026-09-03: the cover is the control surface) ── */
/* Every fixed-position control sits on the square cover -- select at the
   top-start corner, watch at the top-end corner, status chip at the
   bottom-start over a scrim -- and the body below holds text only. Before
   this, name, chip and star shared one non-wrapping row: the Russian/French
   chip alone is ~130px, so on a 230px card the name shrank to a few letters
   and on a 160px phone card the star was pushed out and clipped. Logical
   properties throughout: Hebrew mirrors the corners by itself. */

.persons-grid { display: grid; gap: var(--gap-4); grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr)); }

.person-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; display: flex; flex-direction: column;
  box-shadow: var(--shadow-1);
  transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.person-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
/* Selected: a solid brand ring (the 1px border + a 2px shadow ring = a 3px
   border that costs no layout), a soft halo outside it, and a brand tint on
   the body -- the owner asked for selected cards to stand out from their
   neighbours at a glance. Rings are shadows so the grid never shifts; the
   6px halo still fits inside the 14px phone gap. */
.person-card.is-selected { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand), 0 0 0 6px var(--tint-brand), var(--shadow-1); }
.person-card.is-selected:hover { box-shadow: 0 0 0 2px var(--brand), 0 0 0 6px var(--tint-brand), var(--shadow-lift); }
.person-card.is-selected .person-card-body { background: var(--tint-brand); }

.person-cover { position: relative; overflow: hidden; background: var(--tint); }
.cover-link { display: block; }
.cover {
  inline-size: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; background: var(--tint);
  transition: transform 600ms var(--ease);
}
.person-card:hover .cover { transform: scale(1.04); }
/* No photo yet: a silhouette drawn from the palette -- no text, nothing to translate. */
.cover-blank, .sel-thumb-blank {
  background:
    radial-gradient(circle at 50% 36%, var(--line) 0 17%, transparent 17.5%),
    radial-gradient(ellipse at 50% 100%, var(--line) 0 36%, transparent 36.5%),
    var(--tint);
}
/* Bottom scrim so the chip reads on any photo; below the controls (z 1 vs 2). */
.person-cover::after {
  content: ''; position: absolute; inset-inline: 0; inset-block-end: 0; block-size: 42%; z-index: 1;
  background: linear-gradient(180deg, rgb(16 38 42 / 0) 0%, rgb(16 38 42 / 0.46) 100%);
  pointer-events: none;
}

.person-select {
  position: absolute; inset-block-start: 8px; inset-inline-start: 8px; z-index: 2;
  inline-size: 28px; block-size: 28px; cursor: pointer;
}
/* The real checkbox: transparent but full-size and painted ABOVE the circle
   that draws it, so a pointer hits the control itself (not a label proxy)
   and it stays focusable and in the accessibility tree. */
.person-select-input {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1;
}
.person-select-box {
  position: absolute; inset: 0; border-radius: 50%;
  background: rgb(255 255 255 / 0.88); border: 1.5px solid rgb(16 38 42 / 0.3);
  box-shadow: 0 1px 3px rgb(16 38 42 / 0.28);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
/* The tick is centred geometrically (so the circle can grow for touch below)
   and physical (left/top): a check mark is never mirrored in RTL. */
.person-select-box::after {
  content: ''; position: absolute; left: 50%; top: 50%; inline-size: 6px; block-size: 11px;
  border: solid #fff; border-width: 0 2.2px 2.2px 0;
  transform: translate(-50%, -60%) rotate(45deg); opacity: 0;
}
.person-select:hover .person-select-box { border-color: var(--brand); transform: scale(1.06); }
.person-select-input:checked + .person-select-box { background: var(--brand); border-color: var(--brand); }
.person-select-input:checked + .person-select-box::after { opacity: 1; }
.person-select-input:focus-visible + .person-select-box { outline: 2px solid var(--brand); outline-offset: 2px; }

.cover-star {
  position: absolute; inset-block-start: 8px; inset-inline-end: 8px; z-index: 2;
  inline-size: 28px; block-size: 28px; border-radius: 50%; font-size: 14px;
  background: rgb(255 255 255 / 0.88); border-color: transparent; color: rgb(16 38 42 / 0.38);
  box-shadow: 0 1px 3px rgb(16 38 42 / 0.28);
}
.cover-star:hover { background: #fff; border-color: transparent; color: var(--amber); }
.cover-star.is-active { background: var(--amber); border-color: var(--amber); color: #fff; }
.cover-star.is-active:hover { background: #7E5400; border-color: #7E5400; }

.chip-cover {
  position: absolute; inset-inline-start: 10px; inset-block-end: 10px; z-index: 2;
  box-shadow: 0 1px 2px rgb(16 38 42 / 0.3);
}

.person-card-body { padding: 11px 14px 13px; display: grid; gap: 4px; }
.person-card-label { display: flex; gap: 4px; align-items: center; min-inline-size: 0; }
.person-card-name {
  flex: 1 1 auto; min-inline-size: 0;
  font-weight: 600; font-size: 14px; color: var(--ink); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.person-card-name:hover { color: var(--brand-strong); }
.person-card-label .icon-btn { flex: 0 0 auto; opacity: 0.55; }
.person-card:hover .icon-btn, .person-card:focus-within .icon-btn { opacity: 1; }
.person-card-input {
  font: inherit; font-size: 14px; color: var(--ink); background: var(--card);
  border: 1px solid var(--brand); border-radius: var(--radius-s); padding: 4px 6px; min-inline-size: 0; flex: 1 1 auto;
}
.person-card-meta { display: flex; flex-wrap: wrap; gap: 1px 10px; align-items: baseline; font-size: 12px; color: var(--ink-2); }
.person-card-count { font-weight: 600; }
.person-card-range { font-family: var(--font-mono); font-size: 11px; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ── persons toolbar: filters + the selection actions at their inline-end ── */
/* Sticky under the topbar on desktop (portal.js measures the bar into
   --topbar-h: its height depends on the sign-out button and it wraps on
   phones), so Merge stays reachable while scrolling a long grid. ≤900px the
   topbar is not sticky either; there the actions float instead (below). */
.persons-toolbar { position: sticky; inset-block-start: var(--topbar-h, 62px); z-index: 30; }
.filters-actions { display: flex; align-items: center; gap: var(--gap-2); margin-inline-start: auto; flex-wrap: wrap; }
.select-all { font-size: 13px; color: var(--ink-2); cursor: pointer; user-select: none; }
.select-all input { inline-size: 16px; block-size: 16px; margin: 0; accent-color: var(--brand); cursor: pointer; }
.select-all input:disabled { cursor: default; }
.sel-pill {
  display: inline-flex; align-items: center; gap: 2px;
  padding-block: 3px; padding-inline: 10px 3px; border-radius: 999px;
  background: var(--tint-brand); color: var(--brand-strong); font-size: 12px; font-weight: 600; white-space: nowrap;
}
.sel-clear {
  display: grid; place-items: center; inline-size: 20px; block-size: 20px; border: 0; border-radius: 50%;
  background: transparent; color: inherit; cursor: pointer; font: inherit; font-size: 16px; line-height: 1;
}
.sel-clear:hover { background: rgb(14 124 151 / 0.16); }
.sel-clear:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* ── selection dialogs (persons grid): merge = radio rows, delete = plain rows ── */
.sel-modal { max-inline-size: min(600px, 94vw); }
.sel-list { display: grid; gap: 6px; max-block-size: min(46vh, 420px); overflow-y: auto; padding: 2px; }
.sel-row {
  display: grid; grid-template-columns: auto 44px minmax(0, 1fr) auto; gap: 12px; align-items: center;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--card); cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.sel-row.no-lead { grid-template-columns: 44px minmax(0, 1fr) auto; cursor: default; }
.sel-row:not(.no-lead):hover { background: var(--tint); }
.sel-row.is-keep { border-color: var(--brand); background: var(--tint-brand); box-shadow: 0 0 0 1px var(--brand); }
.sel-row input[type=radio] { margin: 0; inline-size: 16px; block-size: 16px; accent-color: var(--brand); cursor: pointer; }
.sel-thumb { inline-size: 44px; block-size: 44px; border-radius: 10px; object-fit: cover; display: block; background: var(--tint); }
.sel-row-body { display: grid; gap: 3px; min-inline-size: 0; }
.sel-row-name { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sel-row-meta { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.sel-row-star { color: var(--amber); font-size: 13px; line-height: 1; }
.sel-row-state { font-size: 12px; font-weight: 600; white-space: nowrap; color: var(--brand-strong); }
.sel-row.is-wait .sel-row-state { color: var(--ink-2); }
.sel-row.is-done { opacity: 0.55; }
.sel-row.is-done .sel-row-state { color: var(--ok); }
.sel-row.is-failed { border-color: var(--alert); }
.sel-row.is-failed .sel-row-state { color: var(--alert); }
.sel-summary { margin: 0; font-size: 13px; color: var(--ink-2); }
.sel-summary strong { color: var(--brand-strong); }
.sel-summary-danger { color: var(--alert); font-weight: 600; }

/* ── person detail header ───────────────────────────────────────────── */

.person-header-top { display: flex; gap: var(--gap-3); align-items: center; flex-wrap: wrap; }
.person-header-label { display: flex; gap: 6px; align-items: center; }
.person-header-name { margin: 0; font-size: 22px; letter-spacing: -0.02em; }

/* ── chart card ──────────────────────────────────────────────────────── */

.chart-card { min-inline-size: 0; }
.card-title-row { display: flex; gap: var(--gap-3); align-items: center; justify-content: space-between; flex-wrap: wrap; }

.chart-host { position: relative; }
.bar-chart { display: block; inline-size: 100%; block-size: auto; }
.chart-bar { fill: var(--brand); }
.chart-bar:hover { fill: var(--brand-strong); }
.chart-axis { stroke: var(--line); stroke-width: 1; }
.chart-grid { stroke: var(--line-soft); stroke-width: 1; }
.chart-axis-label { fill: var(--ink-2); font: 10px var(--font-mono); }

.chart-tip {
  position: absolute; z-index: 5; pointer-events: none;
  background: var(--navy); color: #fff;
  font: 600 12px/1.45 var(--font-sans); padding: 6px 10px; border-radius: 8px;
  box-shadow: var(--shadow-lift); white-space: nowrap;
  transform: translate(-50%, calc(-100% - 8px));
  opacity: 0; transition: opacity 120ms var(--ease);
}
.chart-tip.is-on { opacity: 1; }
.chart-tip .tip-when { display: block; font-weight: 400; font-size: 11px; color: var(--on-navy-2); direction: ltr; }

/* ── face gallery (person detail, feeds the Split dialog) ──────────────── */

.face-gallery { display: grid; gap: var(--gap-3); grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.face-tile {
  position: relative; display: block; border: 1px solid var(--line); border-radius: var(--radius-s);
  overflow: hidden; cursor: pointer; background: var(--tint);
}
.face-tile img { inline-size: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; }
.face-quality {
  position: absolute; inset-inline-start: 4px; inset-block-end: 4px; background: rgb(16 38 42 / 0.72);
  color: #fff; padding: 1px 5px; border-radius: 999px; font-size: 10px;
}
.face-tile .face-check {
  position: absolute; inset-block-start: 6px; inset-inline-end: 6px; inline-size: 18px; block-size: 18px; margin: 0;
}

.sighting-thumb { inline-size: 48px; block-size: 48px; object-fit: cover; border-radius: var(--radius-s); display: block; }

/* ── dialogs (native <dialog>, admin.css's .modal vocabulary) ─────────── */

.modal {
  border: 1px solid var(--line); border-radius: var(--radius); padding: var(--gap-4);
  max-inline-size: min(560px, 92vw); box-shadow: var(--shadow-lift); color: var(--ink); background: var(--card);
}
.modal::backdrop { background: rgb(15 42 46 / 0.45); }
.modal-actions { display: flex; gap: var(--gap-2); justify-content: flex-end; }

.picker-list { display: grid; gap: 2px; max-block-size: 220px; overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius-s); }
.picker-row {
  display: flex; justify-content: space-between; gap: var(--gap-2); align-items: center;
  padding: 8px 10px; border: none; background: var(--card); text-align: start; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--ink); border-block-end: 1px solid var(--line);
}
.picker-row:last-child { border-block-end: none; }
.picker-row:hover { background: var(--tint); }
.picker-row.is-selected { background: var(--tint-brand); outline: 2px solid var(--brand); outline-offset: -2px; }

/* The IP-location dialog (Security page, 2026-09-02). */
/* Grid layout ONLY while open: a bare `.ip-modal{display:grid}` would
   override the UA's `dialog:not([open]){display:none}` (author styles beat
   UA styles at any specificity) and leak the closed dialog into the page
   flow -- caught live 2026-09-02. */
.ip-modal { max-inline-size: min(640px, 94vw); }
.ip-modal[open] { display: grid; gap: var(--gap-3); }
.ip-meta { margin: 0; color: var(--ink-2); font-size: 12px; text-align: start; }
.ip-grid {
  display: grid; grid-template-columns: minmax(130px, auto) 1fr;
  gap: 7px 18px; margin: 0; font-size: 13px;
}
.ip-grid dt { color: var(--ink-2); font-weight: 600; }
.ip-grid dd { margin: 0; min-inline-size: 0; overflow-wrap: anywhere; }
.ip-grid dd.ltr { direction: ltr; text-align: end; unicode-bidi: isolate; text-align: start; }
.ip-flags { display: flex; gap: 6px; flex-wrap: wrap; }
.ip-raw summary { cursor: pointer; font-size: 12px; font-weight: 600; color: var(--ink-2); }
.ip-raw summary:hover { color: var(--ink); }
.ip-raw-pre {
  max-block-size: 240px; overflow: auto; background: var(--tint);
  border: 1px solid var(--line); border-radius: var(--radius-s);
  padding: 10px 12px; font-size: 11.5px; margin: 8px 0 0; text-align: start;
}

/* ── toasts ──────────────────────────────────────────────────────────── */

.toast-host {
  position: fixed; inset-block-end: var(--gap-4); inset-inline-end: var(--gap-4); z-index: 1000;
  display: grid; gap: var(--gap-2); max-inline-size: min(360px, 90vw);
}
.toast {
  padding: var(--gap-3) var(--gap-4); border-radius: var(--radius-s); font-size: 13px; font-weight: 600;
  box-shadow: var(--shadow-lift); border: 1px solid;
  animation: toast-in var(--dur-fast) var(--ease);
}
.toast-ok { background: #ECF7F1; border-color: var(--ok); color: #105534; }
.toast-error { background: #FCEDED; border-color: var(--alert); color: #7A1F1F; }

@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ── review queue (task 14) ─────────────────────────────────────────── */

.kbd-legend { margin: 0; }
.kbd-legend kbd {
  display: inline-block; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--line);
  background: var(--card); font: 600 11px var(--font-mono); color: var(--ink-2);
}

.review-list { display: grid; gap: var(--gap-3); }
.review-card {
  display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: var(--gap-4);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--gap-3); align-items: start; box-shadow: var(--shadow-1);
}
.review-card.is-active { border-color: var(--brand); box-shadow: 0 0 0 2px var(--tint-brand); }
/* The checkbox adds a THIRD child; without its own column the fixed
   160px/1fr grid would put the crop where the checkbox belongs and wrap the
   body onto a second row. Only pending rows are selectable, hence a
   modifier rather than a change to .review-card itself. */
.review-card.has-select { grid-template-columns: auto 160px minmax(0, 1fr); }
.review-select-wrap { display: flex; align-items: start; justify-content: center; padding-block-start: 2px; }
.review-select { inline-size: 18px; block-size: 18px; cursor: pointer; }
.review-bulk { display: flex; gap: var(--gap-3); align-items: center; flex-wrap: wrap; }
.review-crop-wrap {
  inline-size: 160px; block-size: 160px; border-radius: var(--radius-s); overflow: hidden; background: var(--tint);
}
.review-crop { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.review-crop-blank { inline-size: 100%; block-size: 100%; }
.review-body { display: grid; gap: 6px; align-content: start; }
.review-candidate { margin: 0; display: flex; gap: var(--gap-2); align-items: baseline; flex-wrap: wrap; }
.review-candidate-label { font-weight: 600; color: var(--brand-strong); text-decoration: none; }
.review-candidate-label:hover { color: var(--brand); }
.review-meta { margin: 0; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.review-card-footer { margin-block-start: 4px; }

/* ── plates table: expandable sightings sub-row (task 14) ─────────────── */

.plate-subrow > td { background: var(--tint); padding: var(--gap-3); }
.sub-table { inline-size: 100%; border-collapse: collapse; font-size: 12px; }
.sub-table th, .sub-table td { padding: 6px 8px; text-align: start; border-block-end: 1px solid var(--line); }
.sub-table thead th { color: var(--ink-2); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; }
.sub-table tbody tr:last-child td { border-block-end: 0; }

/* ── usage dashboard tiles (task 14; accent hairline 2026-09-02) ──────── */

.tiles { display: grid; gap: var(--gap-3); grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); }
.tile {
  position: relative; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 17px var(--gap-4) 15px; display: grid; gap: 4px; align-content: start;
  box-shadow: var(--shadow-1);
}
.tile::before {
  content: ''; position: absolute; inset-block-start: 0; inset-inline: 0; block-size: 3px;
  background: linear-gradient(90deg, var(--brand), var(--accent));
}
.tile-label { font-size: 11px; font-weight: 600; color: var(--ink-2); text-transform: uppercase; letter-spacing: 0.07em; }
.tile-value { font-size: 30px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; line-height: 1.15; }

/* ── credit screen (redesigned 2026-09-20, owner request: "stunning and
   pixel perfect") ─────────────────────────────────────────────────────────
   Three bands: the BALANCE HERO, then the remote-access PLAN card beside the
   token SHOP, then the payment HISTORY.

   What was measured on the live page before this, and is fixed here:
     - `.region` is display:block, so the heading, the tiles and the three
       cards touched each other (0px between them). Usage had the same
       defect and shares the fix below (owner's call, same day);
     - at 390px the plan card's nowrap chip pushed the card's content 17px
       past its own edge, the Subscribe button with it;
     - the pitch lines stood 67px apart (paragraph margins on top of the
       .stack gap); the Subscribe button stretched to 1,077px;
     - six packs wrapped 5 + 1, and the pack without a bonus chip put its
       Buy button on a different line from its neighbours.

   LAYOUT IS CHOSEN BY CONTAINER, NOT VIEWPORT: the sidebar takes a different
   share of every screen (252px, 218px, none), so the region names its own
   width (`credit`) and the shop card names its (`shop`). Narrow-first: with
   no container-query support everything stays one tidy column.
   Logical properties throughout (he is RTL). */

.region[data-region="credit"], .region[data-region="usage"] { display: grid; gap: var(--gap-4); }
/* A grid item's auto minimum is content-based: without this a wide table
   inflates the track instead of scrolling inside its .table-wrap (the
   2026-09-02 measurement recorded at `.content > *`). */
.region[data-region="credit"] > *, .region[data-region="usage"] > * { min-inline-size: 0; }
.region[data-region="credit"] { container: credit / inline-size; }

/* Balance hero: the screen's one bold element, on the dashboard hero's own
   gradient + dot texture. Every small text on it is solid white -- an opacity
   step measured under 4.5:1 across the middle of the gradient. */
.credit-hero {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-4) var(--gap-5); align-items: center;
  padding: clamp(20px, 2.6vw, 34px) clamp(18px, 2.8vw, 38px);
  border-radius: var(--radius);
  background: linear-gradient(150deg, var(--brand-strong) 0%, var(--brand) 46%, var(--accent) 100%);
  color: #fff; box-shadow: var(--shadow-lift);
}
.credit-hero::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(rgb(255 255 255 / 0.14) 1px, transparent 1.4px);
  background-size: 22px 22px; opacity: 0.35;
}
.credit-hero > * { position: relative; z-index: 1; min-inline-size: 0; }
.credit-hero-main { display: grid; gap: 6px; }
.credit-hero-head { display: flex; flex-wrap: wrap; gap: 6px var(--gap-3); align-items: center; }
.credit-hero-label { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: 0; line-height: 1.4; }
.credit-hero-value {
  margin: 0; font-size: clamp(38px, 4.6vw, 58px); font-weight: 700; letter-spacing: -0.03em;
  line-height: 1.05; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.credit-hero-sub { margin: 0; font-size: 14px; line-height: 1.5; }
.credit-hero .region-state { font-size: 12px; color: #fff; }
.credit-hero .region-state.is-error { background: #fff; color: #7A1F1F; padding: 2px 10px; border-radius: 11px; }
/* The 30-day figure sits in a darker well, never a lighter glass: white on a
   lightened gradient fell to 4.2:1, on the well it is 6.4:1. */
.credit-hero-side {
  display: grid; gap: 2px; padding: 14px 16px 12px; border-radius: 12px;
  background: rgb(6 40 50 / 0.22); box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.14);
}
.credit-hero-figure { margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; font-variant-numeric: tabular-nums; }
/* 30 bars, one per day, oldest first. LTR on purpose: time runs left to right
   on every chart in this portal whatever the page direction (barChart's SVG). */
.credit-spark { display: flex; align-items: flex-end; gap: 3px; block-size: 40px; margin-block-start: 8px; direction: ltr; }
.credit-spark i { flex: 1 1 0; min-inline-size: 0; min-block-size: 2px; border-radius: 2px 2px 0 0; background: rgb(255 255 255 / 0.72); }
.credit-spark i.is-zero { background: rgb(255 255 255 / 0.34); }

/* plan | shop */
.credit-cols { display: grid; gap: var(--gap-4); grid-template-columns: minmax(0, 1fr); }
.credit-cols > * { min-inline-size: 0; }

/* Remote-access plan card. A flex column so the status + action pair can sit
   on the card's floor when the shop beside it is the taller one. */
.credit-plan { display: flex; flex-direction: column; }
.plan-head { display: flex; flex-wrap: wrap; gap: 8px var(--gap-2); align-items: center; }
/* The chip carries a whole timestamp ("Active until 2030-06-12 03:13 UTC+3"):
   it may wrap on a narrow card instead of pushing the card open, and an 11px
   radius still reads as a pill on one line. */
.plan-chip { white-space: normal; border-radius: 11px; padding-block: 2px; line-height: 1.5; }
.plan-price { margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; font-variant-numeric: tabular-nums; }
.plan-points { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; font-size: 14px; line-height: 1.45; }
.plan-points li { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 10px; align-items: start; }
.plan-check { display: grid; place-items: center; inline-size: 20px; block-size: 20px; border-radius: 50%; background: var(--tint-brand); color: var(--brand); }
.plan-check svg { inline-size: 12px; block-size: 12px; stroke-width: 2.6; display: block; }
.plan-status {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: start;
  margin-block-start: auto; padding: 11px 13px; border-radius: var(--radius-s);
  background: var(--field); border: 1px solid var(--line-soft);
}
.plan-status-dot { inline-size: 8px; block-size: 8px; border-radius: 50%; margin-block-start: 5px; background: var(--ink-2); opacity: 0.5; }
.plan-status[data-relay-status='online'] .plan-status-dot,
.plan-status[data-relay-status='legacy_up'] .plan-status-dot { background: var(--ok); box-shadow: 0 0 0 3px #E4F3EB; opacity: 1; }
.plan-status[data-relay-status='preparing'] .plan-status-dot,
.plan-status[data-relay-status='offline'] .plan-status-dot { background: var(--amber); box-shadow: 0 0 0 3px #FDF4E3; opacity: 1; }
.plan-status[data-relay-status='legacy_down'] .plan-status-dot { background: var(--alert); box-shadow: 0 0 0 3px #FCEDED; opacity: 1; }
.plan-status-body { margin: 0; display: grid; gap: 1px; min-inline-size: 0; }
.plan-status-title { font-size: 12px; font-weight: 600; color: var(--ink-2); }
.plan-status-text { font-size: 14px; font-weight: 600; line-height: 1.4; }
.plan-status-meta { font-size: 12px; color: var(--ink-2); }
.plan-foot { display: grid; gap: var(--gap-2); justify-items: start; }
.plan-foot > p { margin: 0; }
.plan-cta { inline-size: 100%; padding-block: 12px; font-size: 14px; }
.plan-note { font-size: 13px; color: var(--ink-2); }
.plan-card-line { display: flex; gap: 8px; align-items: center; font-size: 13px; color: var(--ink-2); }
.plan-card-line svg { inline-size: 18px; block-size: 18px; flex: 0 0 auto; }

/* Token shop. Six packs only ever lay out 1 x 6, 2 x 3 or 3 x 2 -- an
   auto-fit track count gave 5 + 1 on a desktop. Under 400px of card they are
   ROWS (price | what it buys | Buy): six stacked cards were 1,500px of phone. */
.credit-shop { container: shop / inline-size; }
.pkg-grid { display: grid; gap: var(--gap-3); grid-template-columns: minmax(0, 1fr); margin-block-start: var(--gap-2); }
.pkg-card {
  position: relative; display: flex; align-items: center; gap: 10px; min-inline-size: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 13px 14px;
  transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.pkg-body {
  flex: 1 1 auto; min-inline-size: 0;
  display: grid; grid-template-columns: 60px minmax(0, 1fr); gap: 2px 10px; align-items: center; justify-items: start;
}
.pkg-price { grid-row: 1 / span 3; margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.pkg-tokens, .pkg-recognitions, .pkg-bonus { grid-column: 2; }
.pkg-tokens { margin: 0; font-size: 14px; font-weight: 600; line-height: 1.35; }
.pkg-recognitions { margin: 0; font-size: 13px; color: var(--ink-2); line-height: 1.4; }
.pkg-bonus { margin-block-start: 4px; }
.pkg-buy { flex: 0 0 auto; min-block-size: 40px; min-inline-size: 60px; padding-inline: 12px; }
/* The whole pack is the Buy link's target (a row on a phone is a list item);
   the link keeps its own focus ring. No link (purchases paused) -> no lift. */
.pkg-buy::after { content: ''; position: absolute; inset: 0; border-radius: inherit; }
.pkg-card:has(.pkg-buy):hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: #BCD9D5; }
/* One filled button per block: the best-value pack's. The rest are outlined
   in the brand colour and fill on hover (two classes + :hover outranks
   .btn-ghost:hover:not(:disabled)). */
.pkg-card .pkg-buy.btn-ghost { color: var(--brand-strong); border-color: #BCD9D5; }
.pkg-card .pkg-buy.btn-ghost:hover { background: var(--brand); border-color: var(--brand); color: #fff; }
/* Kept out of the rule above on purpose: a selector list is not forgiving, so
   an engine without :has() would drop both. */
.pkg-card:has(.pkg-buy):hover .pkg-buy.btn-ghost { background: var(--brand); border-color: var(--brand); color: #fff; }
/* As a row, the badge on its top edge needs a little more head room over the first text line. */
.pkg-card-best { background: #F2FAF9; border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); padding-block-start: 17px; }
.pkg-card-best:has(.pkg-buy):hover { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand), var(--shadow-lift); }
.pkg-best-badge { position: absolute; inset-block-start: -10px; inset-inline-start: 13px; background: var(--brand); color: #fff; z-index: 1; }
.pkg-buy-line { margin: var(--gap-2) 0 0; font-size: 14px; }

@container shop (min-width: 400px) {
  .pkg-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Cards: equal height per row (grid stretch), the button on the floor of
     each -- so a pack with no bonus chip lines up with its neighbours. */
  .pkg-card, .pkg-card-best { flex-direction: column; align-items: stretch; gap: var(--gap-3); padding: 16px 16px 14px; }
  .pkg-body { grid-template-columns: minmax(0, 1fr); gap: 2px; align-content: start; }
  .pkg-price { grid-row: auto; font-size: 30px; margin-block-end: 4px; }
  .pkg-tokens, .pkg-recognitions, .pkg-bonus { grid-column: auto; }
  .pkg-tokens { font-size: 15px; }
  .pkg-buy { inline-size: 100%; }
  .pkg-best-badge { inset-inline-start: 15px; }
}
@container shop (min-width: 600px) {
  .pkg-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Payment history: a table inside a card needs no second shadow. The two
   alignment rules are spelled with .table in front because `.table td`
   (0,1,1) outranks a bare `.num` / `.actions-col` -- measured here: amounts
   and invoice buttons sat at the START of their columns. One row height, so
   a row with an invoice button is not 5px taller than one without. */
.credit-history { container: history / inline-size; }
.credit-history .table-wrap { box-shadow: none; }
.credit-history .table td { vertical-align: middle; block-size: 50px; }
.credit-history .table .num, .credit-history .table .actions-col { text-align: end; }
.credit-history .actions-col .btn { margin-inline-end: 0; }
/* Under 560px of card the five columns do not fit: the table scrolled
   sideways with its Item column crushed to four lines (measured at 390px).
   Each payment becomes two lines instead -- what and how much, then when,
   its state and the invoice. The template carries explicit ARIA table roles
   because a display change drops the native ones in some engines; the header
   row stays in the accessibility tree, only clipped from view. */
@container history (max-width: 559.98px) {
  .credit-history .table, .credit-history .table tbody { display: block; }
  .credit-history .table thead {
    position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .credit-history .table tbody tr {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 7px 10px; align-items: center;
    grid-template-areas: 'item item amount' 'date status invoice';
    padding: 12px 14px; border-block-end: 1px solid var(--line-soft);
  }
  .credit-history .table tbody tr:last-child { border-block-end: 0; }
  .credit-history .table tbody td { display: block; padding: 0; border: 0; block-size: auto; min-inline-size: 0; }
  .credit-history .table .history-item { grid-area: item; font-weight: 600; }
  .credit-history .table .num { grid-area: amount; font-weight: 700; align-self: start; }
  .credit-history .table .history-date { grid-area: date; color: var(--ink-2); font-size: 12px; }
  /* "Partiellement remboursé" beside a date and an invoice button overflowed a 320px card by 14px: here the chip may wrap. */
  .credit-history .table .history-status { grid-area: status; white-space: normal; }
  .credit-history .table .history-status .chip { white-space: normal; border-radius: 11px; }
  .credit-history .table .actions-col { grid-area: invoice; }
}
.history-date { white-space: nowrap; font-variant-numeric: tabular-nums; }
.history-status { white-space: nowrap; }
.credit-empty { margin: 0; display: grid; justify-items: center; gap: 10px; border: 0; background: var(--field); padding: var(--gap-5) var(--gap-4); }
.credit-empty-ico { display: grid; place-items: center; inline-size: 40px; block-size: 40px; border-radius: 50%; background: var(--tint-brand); color: var(--brand); }
.credit-empty-ico svg { inline-size: 20px; block-size: 20px; display: block; }

/* From 680px of region: the hero splits balance | 30-day figure, and the plan
   card -- still alone on its row -- lays out in two columns so it is never a
   wide card holding three short lines. */
@container credit (min-width: 680px) {
  .credit-hero { grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); }
}
@container credit (min-width: 680px) and (max-width: 1039.98px) {
  .credit-plan {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: var(--gap-3) var(--gap-5);
    grid-template-areas: 'head points' 'price points' 'foot status'; align-items: start;
  }
  .plan-head { grid-area: head; }
  .plan-price { grid-area: price; }
  .plan-points { grid-area: points; }
  .plan-status { grid-area: status; margin-block-start: 0; }
  .plan-foot { grid-area: foot; }
  .plan-cta { inline-size: auto; min-inline-size: 220px; }
}
/* From 1040px of region: plan beside shop. The floor is what keeps three
   pack columns at >= 210px, where "≈ 11,000 face recognitions" fits one line. */
@container credit (min-width: 1040px) {
  /* The plan column stops at 360px: wider, its three lines stop wrapping, the
     card gets shorter and the space it is stretched over (the shop's height)
     opens into a hole. Its own rhythm is a step looser here for the same reason. */
  .credit-cols { grid-template-columns: clamp(320px, 30%, 360px) minmax(0, 1fr); }
  .credit-plan { gap: 18px; }
  .plan-price { font-size: 28px; }
  /* The two cards share one height (grid stretch) but the plan's own varies
     by 140px with the account's state. Whichever card is the shorter one
     absorbs the difference where it cannot be seen as a hole: the plan in
     the rhythm of its checklist, the shop in the height of its pack rows. */
  .plan-points { gap: 12px; flex: 1 1 auto; align-content: space-evenly; }
  .plan-status { margin-block-start: 0; }
  .credit-shop { grid-template-rows: auto auto 1fr; }
  .credit-shop .pkg-grid { grid-auto-rows: 1fr; }
}

/* ── checkout (BlueSnap Hosted Payment Fields, spec 2026-09-03) ──────── */

.checkout-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1.7fr); align-items: start; }
.checkout-summary { position: sticky; inset-block-start: calc(var(--gap-4) + 56px); }
.sum { margin: 0; display: grid; gap: 8px; }
.sum-row { display: flex; justify-content: space-between; gap: var(--gap-3); font-size: 14px; }
.sum-row dt { color: var(--ink-2); }
.sum-row dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; direction: ltr; }
.sum-total { border-block-start: 1px solid var(--line); padding-block-start: 8px; font-size: 17px; }
.sum-total dt { color: var(--ink); font-weight: 600; }
.card.checkout-form { max-inline-size: none; }
.checkout-form input[readonly] { background: var(--tint); color: var(--ink-2); }
/* The SDK injects one iframe per placeholder and sizes it to the box; an
   explicit height is what keeps the three fields aligned with our inputs. */
.bs-field {
  block-size: 42px; border: 1px solid var(--line); border-radius: var(--radius-s); padding: 0 12px;
  background: var(--card); direction: ltr; overflow: hidden;
}
.bs-field iframe { inline-size: 100%; block-size: 100%; border: 0; display: block; }
/* Trust strip under the Pay button + the lock on the "Card details" heading.
   Token colours only; the icons are the nav's hand-authored 24-unit stroke
   glyphs. The row wraps on narrow screens and mirrors in RTL by itself. */
.card-title-secure svg { inline-size: 18px; block-size: 18px; flex: 0 0 auto; color: var(--brand); }
.trust { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--gap-2) var(--gap-4); }
.trust-item { display: flex; align-items: center; gap: 8px; flex: 1 1 150px; min-inline-size: 0; font-size: 12px; color: var(--ink-2); }
.trust-ico {
  flex: 0 0 auto; inline-size: 28px; block-size: 28px; border-radius: 50%;
  display: grid; place-items: center; background: var(--tint-brand); color: var(--brand);
}
.trust-ico svg { inline-size: 16px; block-size: 16px; display: block; }

@media (max-width: 900px) {
  .checkout-cols { grid-template-columns: 1fr; }
  .checkout-summary { position: static; }
}

/* ── responsive: wide desks first, then tablets, then phones ─────────── */

@media (min-width: 1900px) {
  :root { --content-max: 1680px; }
}

@media (max-width: 1200px) {
  :root { --sidebar: 218px; }
  .tile-value { font-size: 26px; }
}

/* Tablet and down: the sidebar becomes a sticky APP BAR -- brand at the
   inline start, nav as one horizontally scrollable row of pills (never
   wrapping into a two-line jumble), env pill hidden (it earns no space
   here). The topbar stops being sticky so the two bars never stack. */
@media (max-width: 900px) {
  .shell { display: block; }
  .sidebar {
    position: sticky; top: 0; z-index: 50; height: auto; overflow: visible;
    flex-direction: row; align-items: center; gap: var(--gap-3);
    padding: 9px var(--gap-3);
  }
  .brand { flex: 0 0 auto; padding-inline: 0; }
  .brand-text { display: none; }
  .nav {
    flex-direction: row; flex-wrap: nowrap; gap: 4px; min-inline-size: 0;
    overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
    padding-block: 2px;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav-item { white-space: nowrap; padding: 8px 11px; flex: 0 0 auto; }
  .sidebar-foot { display: none; }
  .topbar { position: static; }
  .review-card, .review-card.has-select { grid-template-columns: 1fr; }
  .review-crop-wrap { inline-size: 100%; block-size: 200px; }
  .toast-host { inset-inline-end: var(--gap-3); inset-inline-start: var(--gap-3); max-inline-size: none; }
  /* Persons: the filters bar scrolls away with the page here, so while a
     selection exists its actions float as a navy sheet above the grid --
     the merge button never disappears mid-selection. Toasts lift above it. */
  .persons-toolbar { position: static; }
  /* A two-row sheet: select-all + counter on top, the two actions full-width
     below (four controls no longer fit one row at 390px). Grid columns flow
     inline-start → end, so Hebrew mirrors on its own. */
  .filters-actions.has-selection {
    position: fixed; inset-inline: 14px; inset-block-end: 14px; z-index: 45; margin: 0;
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px 8px; align-items: center;
    padding: 10px 12px; padding-inline-start: 14px; border-radius: var(--radius);
    background: var(--navy); color: var(--on-navy); box-shadow: var(--shadow-lift);
  }
  .filters-actions.has-selection .select-all { color: var(--on-navy); }
  .filters-actions.has-selection .sel-pill { justify-self: end; background: rgb(255 255 255 / 0.16); color: #fff; }
  .filters-actions.has-selection .sel-clear:hover { background: rgb(255 255 255 / 0.22); }
  .filters-actions.has-selection .btn { inline-size: 100%; }
  body.has-floating-actions .toast-host { inset-block-end: 118px; }
  /* Room under the last row so the floating sheet never hides a card's meta. */
  body.has-floating-actions .content { padding-block-end: 136px; }
  /* Touch targets: the cover controls grow from 28px to 32px. */
  .person-select, .cover-star { inline-size: 32px; block-size: 32px; }
}

@media (max-width: 640px) {
  :root { --gutter: 14px; }
  .topbar-inner { flex-wrap: wrap; gap: 6px var(--gap-2); padding-block: 10px; }
  .topbar h1 { font-size: 19px; }
  .topbar-right { inline-size: 100%; justify-content: space-between; flex-wrap: wrap; gap: 6px var(--gap-2); }
  .who { max-inline-size: 60vw; }
  .content { padding-block: var(--gap-3) var(--gap-6); gap: var(--gap-3); }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tile { padding: 14px 15px 12px; }
  .tile-value { font-size: 23px; }
  .card { padding: var(--gap-3); }
  .filters { padding: var(--gap-3); gap: var(--gap-2) var(--gap-3); }
  .filters .field { flex: 1 1 140px; min-inline-size: 0; }
  .usage-filters input[type=datetime-local] { inline-size: 100%; }
  /* One row per field: sharing a row, a date-time field shrank to its 140 px basis and cut the time off (2026-09-17). */
  .usage-filters .field { flex-basis: 100%; }
  .preset-chips { flex-wrap: wrap; }
  .modal { padding: var(--gap-3); }
  .ip-grid { grid-template-columns: 1fr; gap: 2px; }
  .ip-grid dt { margin-block-start: 8px; }
  .ip-grid dt:first-child { margin-block-start: 0; }
  .persons-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: var(--gap-3); }
}

@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .person-card, .pkg-card, .btn, .chart-tip, .cover, .person-select-box, .sel-row { transition: none; }
  .person-card:hover, .pkg-card:hover, .person-card:hover .cover, .person-select:hover .person-select-box { transform: none; }
  /* Its own rule: the credit screen's lift is keyed on :has(), which outranks `.pkg-card:hover` and would void the list above on an engine without it. */
  .pkg-card:has(.pkg-buy):hover { transform: none; }
}

/* -- Google sign-in (login/register bare cards) -----------------------------
   The anchor is a plain .btn.btn-ghost -- the OAuth flow is a full-page
   redirect, so no script and no CSP change ride along with it. */
.or-sep {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--gap-2); color: var(--ink-2); font-size: 12px;
}
.or-sep::before, .or-sep::after { content: ''; block-size: 1px; background: var(--line); }
.btn-google { gap: 10px; }
.btn-google svg { flex: 0 0 auto; }

/* ── Download page (NVR release contract, /{l}/vision/app/download) ──────
   The bare card widened for a page that is mostly prose: hero with the
   Windows button, then checksum, the Linux hero (only once there is a .deb
   release), requirements, the SmartScreen steps, the release notes and the
   freemium pitch. Logical properties; Hebrew mirrors. */
.bare-card:has(.download) { inline-size: min(760px, 100%); }
.download { display: grid; gap: var(--gap-2); }
.download h1 { font-size: 26px; }
.download h2 { margin: var(--gap-3) 0 0; font-size: 17px; letter-spacing: -0.01em; }
.download .lede { margin: 0; color: var(--ink-2); font-size: 15px; }
.download-hero {
  display: grid; gap: var(--gap-2); justify-items: start;
  padding: var(--gap-4); border: 1px solid var(--line); border-radius: 12px; background: var(--paper);
}
.btn-download { font-size: 18px; padding: 14px 28px; }
.download-meta { margin: 0; display: flex; flex-wrap: wrap; gap: var(--gap-2); color: var(--ink-2); }
.download .sha {
  inline-size: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--paper); color: var(--ink);
}
/* The apt line under the Linux button: white on the hero's --paper, so it
   reads as something to copy rather than prose. Wraps on a phone. */
.download .cmd {
  display: block; inline-size: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--ink);
  overflow-wrap: anywhere;
}
.download .steps { margin: 0; padding-inline-start: 1.4em; display: grid; gap: 6px; }
.download .notes h2 + p, .download .notes h3 { margin-block-start: var(--gap-1); }
.download .notes h3 { font-size: 15px; margin-block-end: 4px; }
.download .notes h4 { font-size: 14px; margin: var(--gap-1) 0 4px; }
.download .notes p { margin: 0 0 var(--gap-1); }
.download .notes ul { margin: 0 0 var(--gap-1); padding-inline-start: 1.3em; display: grid; gap: 4px; }
.download .notes code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.92em; padding: 0 4px; border-radius: 4px; background: var(--paper); }

/* ── Owner dashboard / welcome page (spec 2026-09-04 owner-dashboard) ────
   Two modes share the `dash-` prefix: the WELCOME page (hero on the auth
   panel's gradient, the numbered getting-started steps, the feature cards)
   and the DASHBOARD shell (status tiles with a state dot, the attention
   card, the two recent lists, the apps strip). Everything reuses the
   existing .card/.tile/.chip/.btn vocabulary and only adds what has no
   equivalent. Logical properties throughout (he is RTL). */

.btn-lg { padding: 13px 22px; font-size: 14px; }
.chip-amber { background: #FDF4E3; color: #6B4700; }
.chip-glass { background: rgb(255 255 255 / 0.18); color: #fff; }

/* ── Recorder connection card (browser relay access, spec 2026-09-13 §4.5) ── */
.relay-card { display: grid; gap: var(--gap-2); }
.relay-status { margin: 0; font-size: 14px; }
.relay-status.is-ok { color: var(--ok); font-weight: 600; }
.relay-status.is-warn { color: #6B4700; }
.relay-status.is-bad { color: var(--alert); }
.relay-origin { margin: 0; font-size: 12px; color: var(--ink-2); overflow-wrap: anywhere; }
.relay-actions { display: flex; gap: var(--gap-2); align-items: center; flex-wrap: wrap; }
.relay-result { font-size: 13px; color: var(--ink-2); }
.relay-result.is-error { color: var(--alert); font-weight: 600; }

/* hero */
.dash-hero {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); gap: var(--gap-5); align-items: center;
  padding: clamp(24px, 3.2vw, 44px);
  border-radius: var(--radius);
  background: linear-gradient(150deg, var(--brand-strong) 0%, var(--brand) 46%, var(--accent) 100%);
  color: #fff; box-shadow: var(--shadow-lift);
}
.dash-hero::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(rgb(255 255 255 / 0.14) 1px, transparent 1.4px);
  background-size: 22px 22px; opacity: 0.35;
}
.dash-hero > * { position: relative; z-index: 1; }
.dash-hero-copy { display: grid; gap: var(--gap-3); min-inline-size: 0; }
.dash-eyebrow { margin: 0; font: 600 11px/1.6 var(--font-mono); letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.8; }
.dash-hero-title { margin: 0; font-size: clamp(26px, 2.6vw, 36px); line-height: 1.15; letter-spacing: -0.02em; font-weight: 700; }
.dash-hero-lede { margin: 0; font-size: 15px; line-height: 1.55; max-inline-size: 58ch; opacity: 0.94; }
.dash-hero-actions { display: flex; flex-wrap: wrap; gap: var(--gap-3) var(--gap-4); align-items: start; margin-block-start: var(--gap-1); }
.dash-hero-action { display: grid; gap: 6px; justify-items: start; }
.dash-hero-meta { font-size: 12px; opacity: 0.88; max-inline-size: 34ch; line-height: 1.45; }
.dash-hero .btn-primary { background: #fff; color: var(--brand-strong); box-shadow: 0 8px 22px -10px rgb(0 0 0 / 0.5); }
.dash-hero .btn-primary:hover:not(:disabled) { background: #EAF6F5; color: var(--brand-strong); }
.dash-btn-glass { background: rgb(255 255 255 / 0.14); border-color: rgb(255 255 255 / 0.4); color: #fff; }
.dash-btn-glass:hover:not(:disabled) { background: rgb(255 255 255 / 0.24); color: #fff; }
.dash-soon-chip { font-size: 12px; padding: 5px 12px; }

/* hero art: a four-camera wall with a REC pulse and a floating recognition pill */
.dash-hero-art { position: relative; min-inline-size: 0; }
.dash-art-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; transform: rotate(-3deg); }
.dash-art-cam {
  position: relative; aspect-ratio: 16 / 10; border-radius: 9px;
  background: linear-gradient(160deg, #1C3B41 0%, #0F2A2E 60%, #0A1F22 100%);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.12), 0 10px 24px -12px rgb(0 0 0 / 0.6);
  overflow: hidden;
}
.dash-art-cam::after {
  content: ''; position: absolute; inset: auto 0 0 0; block-size: 42%;
  background: linear-gradient(180deg, transparent, rgb(14 124 151 / 0.35));
}
.dash-art-cam i {
  position: absolute; inset-block-start: 8px; inset-inline-end: 8px;
  inline-size: 7px; block-size: 7px; border-radius: 50%; background: #FF5A5A;
  box-shadow: 0 0 0 0 rgb(255 90 90 / 0.6); animation: dash-rec 2.2s ease-out infinite;
}
.dash-art-cam:nth-child(2) i { animation-delay: 0.6s; }
.dash-art-cam:nth-child(3) i { animation-delay: 1.1s; }
.dash-art-cam:nth-child(4) i { animation-delay: 1.6s; }
@keyframes dash-rec { 0% { box-shadow: 0 0 0 0 rgb(255 90 90 / 0.55); } 70% { box-shadow: 0 0 0 8px rgb(255 90 90 / 0); } 100% { box-shadow: 0 0 0 0 rgb(255 90 90 / 0); } }
.dash-art-pill {
  position: absolute; inset-block-end: -6px; inset-inline-start: 8%;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 999px;
  background: #fff; color: var(--ink); font-size: 12px; font-weight: 600;
  box-shadow: 0 10px 26px -10px rgb(0 0 0 / 0.55);
}
.dash-art-pill svg { inline-size: 16px; block-size: 16px; color: var(--brand); }

/* getting-started steps */
.dash-steps { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--gap-3); }
.dash-step {
  display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: var(--gap-3); align-items: start;
  padding: var(--gap-3); border: 1px solid var(--line-soft); border-radius: var(--radius-s); background: var(--field);
}
.dash-step.is-done { border-color: #CFE8DA; background: #F4FBF7; }
.dash-step-num {
  display: grid; place-items: center; inline-size: 34px; block-size: 34px; border-radius: 50%;
  font: 700 14px/1 var(--font-mono); background: var(--tint); color: var(--ink-2);
}
.dash-step.is-done .dash-step-num { background: var(--ok); color: #fff; }
.dash-step.is-wait .dash-step-num { background: #FDF4E3; color: #6B4700; }
.dash-step-num svg { inline-size: 18px; block-size: 18px; }
.dash-step-body { display: grid; gap: 5px; min-inline-size: 0; }
.dash-step-title { margin: 0; font-size: 15px; letter-spacing: -0.01em; }
.dash-step-text { margin: 0; font-size: 13px; color: var(--ink-2); max-inline-size: 70ch; line-height: 1.5; }
.dash-step-state { margin: 4px 0 0; display: flex; flex-wrap: wrap; gap: var(--gap-2) var(--gap-3); align-items: center; }

/* what the account unlocks */
.dash-unlocks { display: grid; gap: var(--gap-3); }
.dash-section-title { margin: 0; font-size: 15px; letter-spacing: -0.01em; }
.dash-feature-grid { display: grid; gap: var(--gap-3); grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.dash-feature { gap: var(--gap-2); }
.dash-feature-ico {
  display: grid; place-items: center; inline-size: 38px; block-size: 38px; border-radius: 10px;
  background: var(--tint-brand); color: var(--brand);
}
.dash-feature-ico svg { inline-size: 20px; block-size: 20px; }
.dash-feature-title { margin: 0; font-size: 14px; }
.dash-feature-text { margin: 0; font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.dash-feature-link { margin: 0; font-size: 13px; font-weight: 600; }
.dash-privacy { margin: 0; display: flex; gap: var(--gap-2); align-items: start; font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.dash-privacy-ico { flex: 0 0 auto; color: var(--brand); }
.dash-privacy-ico svg { inline-size: 18px; block-size: 18px; }

/* dashboard shell */
.dash { display: grid; gap: var(--gap-4); }
.dash-toolbar { display: flex; gap: var(--gap-3); align-items: center; flex-wrap: wrap; }
.dash-tile { text-decoration: none; color: inherit; }
a.dash-tile { transition: box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease); }
a.dash-tile:hover { box-shadow: var(--shadow-lift); transform: translateY(-1px); color: inherit; }
.dash-tile-text { font-size: 22px; }
.dash-tile-sub { font-size: 12px; color: var(--ink-2); min-block-size: 1.2em; }
.dash-dot {
  display: inline-block; inline-size: 8px; block-size: 8px; border-radius: 50%;
  margin-inline-end: 7px; vertical-align: 1px; background: var(--line);
}
.dash-tile[data-state='ok'] .dash-dot { background: var(--ok); box-shadow: 0 0 0 3px #E4F3EB; }
.dash-tile[data-state='warn'] .dash-dot { background: var(--amber); box-shadow: 0 0 0 3px #FDF4E3; }
.dash-tile[data-state='alert'] .dash-dot { background: var(--alert); box-shadow: 0 0 0 3px #FCEDED; }
.dash-tile[data-state='off'] .dash-dot { background: var(--ink-2); opacity: 0.5; }
.dash-tile[data-state='ok']::before { background: var(--ok); }
.dash-tile[data-state='warn']::before { background: var(--amber); }
.dash-tile[data-state='alert']::before { background: var(--alert); }

.dash-attention-list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--gap-2); }
.dash-attn {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--gap-3); align-items: center;
  padding: 10px 12px; border-radius: var(--radius-s); background: var(--tint); font-size: 13px;
}
.dash-attn-warn { background: #FDF4E3; color: #6B4700; }
.dash-attn-alert { background: #FCEDED; color: #7A1F1F; }
.dash-attn-ico { display: grid; place-items: center; inline-size: 26px; block-size: 26px; border-radius: 50%; background: rgb(255 255 255 / 0.7); font-weight: 700; font-size: 13px; }
.dash-clear { margin: 0; display: flex; gap: var(--gap-2); align-items: center; color: var(--ok); font-weight: 600; font-size: 14px; }
.dash-clear-ico { display: grid; place-items: center; inline-size: 26px; block-size: 26px; border-radius: 50%; background: #E4F3EB; }
.dash-clear-ico svg { inline-size: 16px; block-size: 16px; }

.dash-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.dash-row {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: var(--gap-3); align-items: center;
  padding: 6px 8px; margin: 0 -8px; border-radius: var(--radius-s);
  color: inherit; text-decoration: none; transition: background var(--dur-fast) var(--ease);
}
a.dash-row:hover { background: var(--tint); color: inherit; }
.dash-thumb { inline-size: 44px; block-size: 44px; border-radius: 9px; object-fit: cover; background: var(--tint); display: block; }
.dash-thumb-blank { display: grid; place-items: center; color: var(--ink-2); }
.dash-thumb-blank svg { inline-size: 20px; block-size: 20px; }
.dash-thumb-plate { font: 700 12px/1 var(--font-mono); letter-spacing: 0.04em; color: var(--ink); background: var(--tint); border: 1px solid var(--line); }
.dash-row-body { display: grid; gap: 3px; min-inline-size: 0; }
.dash-row-name { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 14px; font-weight: 600; }
.dash-row-meta { font-size: 12px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-row-time { font: 12px/1.3 var(--font-mono); color: var(--ink-2); text-align: end; white-space: nowrap; }
.dash-star { color: var(--amber); font-size: 13px; }

.dash-apps { grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: var(--gap-4); }
/* icon | body on the first row, the action under the body on the second --
   the same shape at every width, so a wide chip never squeezes the copy. */
.dash-app { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px var(--gap-3); align-items: start; min-inline-size: 0; }
.dash-app-ico { display: grid; place-items: center; inline-size: 36px; block-size: 36px; border-radius: 10px; background: var(--tint-brand); color: var(--brand); grid-row: span 2; }
.dash-app-ico svg { inline-size: 20px; block-size: 20px; }
.dash-app-body { display: grid; gap: 2px; min-inline-size: 0; }
.dash-app-body strong { font-size: 14px; }
.dash-app > .btn, .dash-app > .chip { grid-column: 2; justify-self: start; }

@media (max-width: 1200px) {
  .dash-hero { grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.8fr); }
}
@media (max-width: 900px) {
  .dash-hero { grid-template-columns: 1fr; }
  .dash-hero-art { display: none; }
}
@media (max-width: 640px) {
  .dash-hero { padding: var(--gap-4); gap: var(--gap-3); }
  .dash-hero-title { font-size: 24px; }
  .dash-hero-actions { gap: var(--gap-3); }
  .dash-hero-action, .dash-hero-action .btn { inline-size: 100%; }
  .dash-tile-text { font-size: 18px; }
  .dash-step { grid-template-columns: 30px minmax(0, 1fr); gap: var(--gap-2); padding: var(--gap-2) var(--gap-3); }
  .dash-step-num { inline-size: 30px; block-size: 30px; font-size: 13px; }
  .dash-row { grid-template-columns: 40px minmax(0, 1fr); }
  .dash-row-time { grid-column: 2; text-align: start; }
  .dash-thumb { inline-size: 40px; block-size: 40px; }
}
@media (prefers-reduced-motion: reduce) {
  .dash-art-cam i { animation: none; }
  a.dash-tile, a.dash-row { transition: none; }
  a.dash-tile:hover { transform: none; }
}

/* ── Live page (portal NVR control, Part 2) ──────────────────────────────
   Desktop-first (P2-D1): a wide auto-fit grid of 16:9 poster tiles that
   collapses to ONE column on a phone. min(Xpx, 100%) as everywhere else in
   this file -- a bare fixed minimum makes the TRACK wider than a narrow
   container instead of collapsing. */
.live { display: grid; gap: var(--gap-4); min-inline-size: 0; }
.live-toolbar {
  display: flex; gap: var(--gap-3) var(--gap-4); align-items: end; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--gap-3) var(--gap-4); box-shadow: var(--shadow-1);
}
.live-toolbar .field-inline { display: grid; }
.live-toolbar select { inline-size: auto; min-inline-size: 9rem; }
.live-slots { font: 600 12px/1.6 var(--font-mono); color: var(--ink-2); margin-inline-start: auto; }

.live-grid {
  display: grid; gap: var(--gap-4); min-inline-size: 0;
  grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
}
.live-card {
  display: grid; gap: var(--gap-2); min-inline-size: 0; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--gap-2); box-shadow: var(--shadow-1);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.live-card:hover, .live-card:focus-within { border-color: var(--brand); box-shadow: var(--shadow-lift); }
/* The card and the tile are buttons by role, so they need the same visible
   ring every real control gets -- the shared rule above only lists tags. */
.live-card:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.live-frame {
  position: relative; display: grid; place-items: center;
  aspect-ratio: 16 / 9; max-inline-size: 100%; overflow: hidden;
  border-radius: var(--radius-s); background: var(--navy);
}
.live-shot { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.live-note { position: absolute; margin: 0; color: var(--on-navy-2); font-size: 12px; }
.live-retry { position: absolute; }
.live-meta { display: flex; gap: var(--gap-2); align-items: center; justify-content: space-between; min-inline-size: 0; }
.live-name { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-inline-size: 0; }
.live-pill { flex: 0 0 auto; }

@media (max-width: 640px) {
  .live-grid { grid-template-columns: 1fr; }
  .live-slots { margin-inline-start: 0; }
  .live-toolbar { padding: var(--gap-3); }
}

/* ── the player overlay (Live single view; plan 4 reuses .player-frame) ── */
.player-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: grid; grid-template-rows: 1fr auto; gap: var(--gap-3);
  padding: var(--gutter); background: rgb(12 34 38 / 0.92);
}
.player-frame {
  position: relative; display: grid; place-items: center; min-block-size: 0;
  background: #000; border-radius: var(--radius); overflow: hidden;
}
.player-frame video {
  inline-size: 100%; block-size: 100%; max-block-size: 100%;
  object-fit: contain; display: block; background: #000;
}
.player-state {
  position: absolute; padding: 8px 14px; border-radius: 999px;
  background: rgb(0 0 0 / 0.55); color: #fff; font-size: 13px; font-weight: 600;
  max-inline-size: min(42ch, 90%); text-align: center;
}
.player-state:empty, .player-state[hidden] { display: none; }
.player-state.is-error { background: var(--alert); }
.player-controls {
  display: flex; gap: var(--gap-2); align-items: center; flex-wrap: wrap;
  max-inline-size: var(--content-max); inline-size: 100%; margin-inline: auto;
}
.player-title {
  color: #fff; font-size: 15px; font-weight: 600; margin-inline-end: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-inline-size: 0;
}
.player-controls .btn-ghost {
  background: rgb(255 255 255 / 0.14); border-color: rgb(255 255 255 / 0.38); color: #fff;
}
.player-controls .btn-ghost:hover:not(:disabled) { background: rgb(255 255 255 / 0.26); color: #fff; }

@media (max-width: 640px) {
  .player-overlay { padding: var(--gap-2); }
  .player-title { inline-size: 100%; margin-inline-end: 0; }
}

/* ── multiview board (2x2, spec §4) ──────────────────────────────────────
   Four tiles are the WHOLE live cap, so the board never grows past 2x2. On a
   phone it becomes one column -- the phone experience is the iOS app's job
   (P2-D1), but the page must still be usable. */
.multiview {
  position: fixed; inset: 0; z-index: 55;
  display: grid; grid-template-rows: auto 1fr; gap: var(--gap-3);
  padding: var(--gutter); background: rgb(12 34 38 / 0.94);
}
.multiview-bar {
  display: flex; gap: var(--gap-3); align-items: center; justify-content: space-between; flex-wrap: wrap;
  color: #fff; font: 600 12px/1.6 var(--font-mono);
  max-inline-size: var(--content-max); inline-size: 100%; margin-inline: auto;
}
.multiview-bar .btn-ghost {
  background: rgb(255 255 255 / 0.14); border-color: rgb(255 255 255 / 0.38); color: #fff;
  font-family: var(--font-sans);
}
.multiview-board {
  display: grid; gap: var(--gap-3); min-block-size: 0; min-inline-size: 0;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  max-inline-size: var(--content-max); inline-size: 100%; margin-inline: auto;
}
.multiview-tile { display: grid; grid-template-rows: 1fr auto; gap: 6px; min-block-size: 0; min-inline-size: 0; cursor: pointer; }
.multiview-tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.multiview-tile .player-frame { min-block-size: 0; }
.multiview-label {
  color: #fff; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* One enlarged tile takes the whole board; the rest step out of the flow
   rather than reflow it (their sessions keep running). */
.multiview-board.has-enlarged .multiview-tile { display: none; }
/* tile layouts by count, shared by the live board and the archive tile grid */
.multiview-board.layout-1, .archive-tiles.layout-1 { grid-template-columns: minmax(0, 1fr); }
.multiview-board.layout-2, .archive-tiles.layout-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.multiview-board.layout-3, .multiview-board.layout-4, .archive-tiles.layout-3, .archive-tiles.layout-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.multiview-board.layout-5, .multiview-board.layout-6, .archive-tiles.layout-5, .archive-tiles.layout-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.multiview-board.layout-7, .multiview-board.layout-8, .archive-tiles.layout-7, .archive-tiles.layout-8 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* A phone stacks two per row whatever the count -- EXCEPT a single camera,
   which is "the single view of today" (spec 2026-09-15 section 4.4) and must keep the
   whole column. The exemption is spelled into this selector rather than
   undone by a later rule, because `.archive-tiles.layout-1` above ties on
   specificity and would lose to it on source order alone (whole-branch
   review finding 1: the archive player painted at half width on every
   phone). The board needs no `:not()` here -- the 900 px block further
   down, which matches a phone too, carries its own `.layout-1` override. */
@media (max-width: 36rem) { .multiview-board[class*="layout-"], .archive-tiles[class*="layout-"]:not(.layout-1) { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.multiview-board.has-enlarged,
.multiview-board.has-enlarged[class*="layout-"] { grid-template-columns: minmax(0, 1fr); }
.archive-tiles { display: grid; gap: var(--gap-2); min-inline-size: 0; }
.archive-tiles .player-frame { position: relative; }
.archive-tile-name { position: absolute; inset-block-start: var(--gap-1); inset-inline-start: var(--gap-1); display: inline-flex; align-items: center; gap: 6px; background: rgb(15 42 46 / 0.78); color: #fff; font-size: 12px; padding: 2px 8px; border-radius: 999px; }
/* the author `display` above outranks the UA sheet's [hidden] rule, so say it here: the badge ships hidden and is hidden again on a one-camera board */
.archive-tile-name[hidden] { display: none; }
.archive-tile.is-lead .archive-tile-name::before { content: "★"; }
/* a FOLLOWER's badge carries the make-lead control; the lead's has none */
/* 24 px minimum target (whole-branch review finding 11): it sits on a video
   surface where a miss double-clicks into full screen. inline-flex centring
   rather than a taller line-height, so the pill keeps its shape and the glyph
   stays in the middle of it. */
.archive-tile-lead-btn { appearance: none; -webkit-appearance: none; border: 0; margin: 0; padding: 0 6px; border-radius: 999px; background: rgb(255 255 255 / 0.16); color: #fff; font: inherit; font-size: 12px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; min-block-size: 24px; min-inline-size: 24px; }
.archive-tile-lead-btn:hover { background: rgb(255 255 255 / 0.34); }
.archive-tile-lead-btn:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.archive-tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.live-picker { position: relative; }
.live-picker-list { position: absolute; inset-block-start: 100%; inset-inline-start: auto; inset-inline-end: 0; z-index: 70; min-inline-size: min(16rem, calc(100vw - 2rem)); max-inline-size: calc(100vw - 2rem); max-block-size: 60vh; overflow: auto; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-s); box-shadow: var(--shadow-lift); padding: var(--gap-2); display: grid; gap: var(--gap-1);
  /* the list is a child of .multiview-bar, whose white mono text is for the dark board: on this white card the names take the page colour and font (owner screenshot 2026-09-16: names invisible) */
  color: var(--ink); font: 15px/1.55 var(--font-sans); }
.live-picker-list label, .archive-cameras label { display: flex; align-items: center; gap: var(--gap-2); cursor: pointer; }
.archive-cameras { display: grid; gap: var(--gap-1); }
.archive-cameras .archive-cameras-title { display: flex; justify-content: space-between; font-weight: 600; }
.archive-cameras .is-lead { color: var(--amber); font-size: 13px; }
.multiview-board.has-enlarged .multiview-tile.is-enlarged { display: grid; }

.live-viewers {
  display: grid; gap: var(--gap-1); padding: var(--gap-3) var(--gap-4);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}
.live-viewers-title { margin: 0; font-size: 13px; letter-spacing: -0.01em; }
.live-viewer { margin: 0; font-size: 13px; color: var(--ink-2); }

@media (max-width: 900px) {
  .multiview-board, .multiview-board[class*="layout-"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .multiview-board.layout-1, .multiview-board.has-enlarged[class*="layout-"] { grid-template-columns: minmax(0, 1fr); }
  .multiview { padding: var(--gap-2); overflow-y: auto; }
}

/* ── Archive page (Part 2 plan 4): side column + main, month calendar ── */

.region.archive {
  display: grid; grid-template-columns: 300px minmax(0, 1fr);
  gap: var(--gap-4); align-items: start;
}
.archive-side { display: flex; flex-direction: column; gap: var(--gap-3); min-inline-size: 0; }
.archive-main { position: relative; display: flex; flex-direction: column; gap: var(--gap-3); min-inline-size: 0; }

.archive-calendar {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--gap-3); box-shadow: var(--shadow-1);
}
.cal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--gap-2); margin-block-end: var(--gap-2); }
.cal-title { font-weight: 600; }
.cal-nav {
  inline-size: 28px; block-size: 28px; border: 1px solid var(--line); background: var(--field);
  color: var(--ink); border-radius: var(--radius-s); cursor: pointer; font: inherit; font-size: 16px; line-height: 1;
}
.cal-nav:hover { background: var(--tint); }
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.cal-dow { text-align: center; font-size: 11px; color: var(--ink-2); padding-block: 2px; }
.cal-pad { display: block; }
.cal-day {
  border: 1px solid transparent; background: transparent; color: var(--ink-2);
  border-radius: var(--radius-s); padding-block: 6px; font: inherit; font-size: 13px; cursor: default;
}
.cal-day.has-footage { color: var(--ink); background: var(--tint); border-color: var(--line-soft); font-weight: 600; cursor: pointer; }
.cal-day.has-footage:hover { border-color: var(--brand); }
.cal-day.is-today { outline: 1px dashed var(--ink-2); outline-offset: -2px; }
.cal-day.is-active { background: var(--brand); border-color: var(--brand); color: #fff; }

.archive-timeline {
  display: block; inline-size: 100%; block-size: 96px;
  border: 0; padding: 0; margin: 0;
  background: var(--card); border-radius: var(--radius-s);
  touch-action: none; cursor: crosshair;
}

/* the strip: the canvas plus the lane-name pills laid over it (spec 2026-09-16 §4.3).
   The template gives it dir="ltr" because the axis is left-to-right in every
   locale; a pill sits at the LEFT edge for the same reason, and
   unicode-bidi: plaintext lets a Hebrew name read correctly inside it. */
.archive-strip { position: relative; min-inline-size: 0; }
.archive-lanes { position: absolute; inset: 0; pointer-events: none; }
.archive-lane-name {
  position: absolute; left: 6px; max-inline-size: 40%;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  background: rgb(15 42 46 / 0.78); color: #fff; font-size: 12px; line-height: 1;
  padding: 3px 8px; border-radius: 999px; unicode-bidi: plaintext;
}
.archive-lane-name.is-lead::before { content: "★ "; }
.archive-lane-name.is-failed { background: var(--alert); }
.archive-lane-note { margin-inline-start: 6px; opacity: 0.85; }

/* the precision slider above the timeline and the day scrollbar below it */
.archive-zoom { display: flex; align-items: center; gap: var(--gap-2); }
.archive-zoom-label { font-size: 12px; color: var(--ink-2); white-space: nowrap; }
.archive-zoom-range { flex: 1; min-inline-size: 0; margin: 0; accent-color: var(--brand); }
.archive-scroll { position: relative; block-size: 14px; margin-block-start: calc(-1 * var(--gap-2));
  border-radius: 7px; background: var(--tint); touch-action: none; cursor: pointer; }
.archive-scroll:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.archive-scroll-thumb { position: absolute; inset-block: 2px; inset-inline-start: 0; inline-size: 20%; min-inline-size: 18px;
  border-radius: 5px; background: var(--brand); cursor: grab; transition: background var(--dur-fast) var(--ease); }
.archive-scroll-thumb:hover, .archive-scroll.is-dragging .archive-scroll-thumb { background: var(--brand-strong); }
.archive-scroll.is-dragging .archive-scroll-thumb { cursor: grabbing; }
.region.is-disconnected .archive-zoom, .region.is-disconnected .archive-scroll { display: none; }

.archive-main .player-frame {
  position: relative; background: #000; border-radius: var(--radius);
  overflow: hidden; aspect-ratio: 16 / 9; inline-size: 100%;
}
.archive-main .player-frame video { inline-size: 100%; block-size: 100%; display: block; object-fit: contain; background: #000; }
/* full viewport without the Fullscreen API (iOS Safari, a refusing embed): the frame takes the screen itself */
.archive-main .player-frame.is-fullviewport { position: fixed; inset: 0; z-index: 80; inline-size: 100vw; block-size: 100vh; aspect-ratio: auto; border-radius: 0; }
.archive-main .player-frame:fullscreen { border-radius: 0; }
.archive-main .player-state {
  position: absolute; inset-block-end: var(--gap-2); inset-inline-start: var(--gap-2);
  background: rgb(15 42 46 / 0.78); color: #fff; font-size: 12px;
  padding: 4px 10px; border-radius: 999px; max-inline-size: 80%;
}
.archive-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2); }
.archive-clock { font-family: var(--font-mono); font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.archive-speed { display: inline-flex; gap: 2px; border: 1px solid var(--line); border-radius: var(--radius-s); padding: 2px; }
.archive-speed button {
  border: 0; background: transparent; color: var(--ink-2); font: inherit; font-size: 13px;
  padding: 2px 8px; border-radius: calc(var(--radius-s) - 2px); cursor: pointer;
}
.archive-speed button.is-active { background: var(--brand); color: #fff; font-weight: 600; }
/* the rates several cameras cannot have (spec 2026-09-15 §4.2): dead to look
   at, but a REAL button -- a disabled one shows no tooltip and leaves the
   accessibility tree, so the sentence explaining the cap would reach nobody */
.archive-speed [aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
.archive-keys { margin: 0; }

.archive-transcripts {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--gap-3); box-shadow: var(--shadow-1);
  max-block-size: 340px; overflow-y: auto;
}
.transcripts-title { margin: 0 0 var(--gap-2); font-size: 14px; }
.transcripts-rows { display: flex; flex-direction: column; gap: 2px; }
.transcript-row {
  display: grid; grid-template-columns: 62px minmax(0, 1fr); gap: var(--gap-2);
  text-align: start; border: 0; background: transparent; color: var(--ink);
  font: inherit; font-size: 13px; padding: 4px 6px; border-radius: var(--radius-s); cursor: pointer;
}
.transcript-row:hover { background: var(--tint); }
.transcript-row.is-current { background: var(--tint-brand); font-weight: 600; }
.transcript-time { color: var(--ink-2); font-size: 12px; }
.transcript-text { min-inline-size: 0; overflow-wrap: anywhere; }

.archive-clip { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2); }
.archive-clip [data-clip-range] { font-family: var(--font-mono); font-size: 13px; font-variant-numeric: tabular-nums; }
.archive-clip progress { inline-size: 160px; block-size: 8px; }
.clip-status { font-size: 12px; color: var(--ink-2); min-inline-size: 0; overflow-wrap: anywhere; }
.archive-controls .btn.is-active { background: var(--brand); border-color: var(--brand); color: #fff; }

@media (max-width: 900px) {
  .region.archive { grid-template-columns: minmax(0, 1fr); }
  .archive-transcripts { max-block-size: 220px; }
}
@media (max-width: 640px) {
  /* P2-D1: desktop browsers first -- the phone keeps the page usable, but
     the keyboard legend is meaningless there and the controls need room. */
  .archive-keys { display: none; }
  .archive-controls { gap: var(--gap-1); }
  .archive-controls .btn { padding-inline: var(--gap-2); }
  .archive-clip progress { inline-size: 110px; }
  .cal-day { padding-block: 8px; }
}

/* ── Status page + the global recording banner (spec 2026-09-13 portal video §6) ── */

/* One strip under the header on EVERY authenticated page while the recorder
   is paused. Solid red rather than the tinted .imp-banner treatment: this is
   a state the customer must act on, not a context note. #A63030 (btn-danger's
   hover) instead of --alert so 13px white text clears 4.5:1. */
.recording-banner {
  background: #A63030; color: #fff;
  padding: var(--gap-2) var(--gutter); text-align: center;
  font-size: 13px; font-weight: 600;
}

.region.status { display: grid; gap: var(--gap-4); }
.status-recorder, .status-disk, .status-viewers { display: grid; gap: var(--gap-2); }
.status-actions { display: flex; gap: var(--gap-3); align-items: center; flex-wrap: wrap; }

/* A snapshot the recorder is no longer confirming: dimmed, never hidden. */
.status-recorder.is-stale, .status-cameras.is-stale, .status-disk.is-stale { opacity: 0.62; }

.status-meta { margin: 0; font-size: 13px; color: var(--ink-2); }
.status-slots { margin: 0; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.status-alert { margin: 0; font-size: 13px; font-weight: 600; color: var(--alert); }
.status-alert.is-warn { color: var(--amber); }

.status-gauge { block-size: 10px; border-radius: 999px; background: var(--tint); overflow: hidden; }
.status-gauge-fill {
  display: block; block-size: 100%; border-radius: 999px; background: var(--ok);
  transition: inline-size var(--dur-fast) var(--ease);
}
.status-gauge-fill.is-ok { background: var(--ok); }
.status-gauge-fill.is-warn { background: var(--amber); }
.status-gauge-fill.is-alert { background: var(--alert); }

/* min-inline-size: 0 on the items, min() in the track: a grid item's
   automatic minimum is its CONTENT, which is what makes a long camera name
   push the page sideways on a phone. */
.status-cameras { display: grid; gap: var(--gap-3); grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
.status-cameras-title { grid-column: 1 / -1; margin: 0; }
.status-cameras .empty { grid-column: 1 / -1; }
.status-cam { display: grid; gap: var(--gap-2); padding: var(--gap-3) var(--gap-4); min-inline-size: 0; }
.status-cam-head { display: flex; gap: var(--gap-2); align-items: center; justify-content: space-between; min-inline-size: 0; }
.status-cam-name { font-weight: 600; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.status-cam-badges { display: flex; gap: 6px; flex-wrap: wrap; }

.status-viewers-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 6px; }
.status-viewer { display: flex; gap: var(--gap-2); align-items: center; flex-wrap: wrap; font-size: 13px; min-inline-size: 0; }
.status-viewer-label { font-weight: 600; }
.status-viewer-cam { color: var(--ink-2); min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.status-viewer-since { color: var(--ink-2); font-variant-numeric: tabular-nums; margin-inline-start: auto; }

@media (max-width: 640px) {
  .status-viewer-since { margin-inline-start: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .status-gauge-fill { transition: none; }
}

/* ── "connecting" card: the minute after a sign-in, with a 60 s ring countdown ── */
.connecting { max-inline-size: 42rem; margin-block: var(--gap-3) var(--gap-4); display: flex; align-items: center; gap: var(--gap-4); }
.connecting-ring { position: relative; flex: 0 0 auto; inline-size: 120px; block-size: 120px; }
.connecting-halo { position: absolute; inset: 6px; border-radius: 50%; background: var(--brand); opacity: 0.12;
  animation: connecting-halo 2.4s var(--ease) infinite; }
.connecting-svg { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; transform: rotate(-90deg); }
.connecting-track { fill: none; stroke: var(--tint); stroke-width: 8; }
.connecting-progress { fill: none; stroke: var(--brand); stroke-width: 8; stroke-linecap: round;
  stroke-dasharray: 326.73; stroke-dashoffset: 0; transition: stroke-dashoffset 1s linear; }
.connecting.is-late .connecting-progress { stroke: var(--accent); animation: connecting-spin 1.6s linear infinite; stroke-dasharray: 60 266.73; transition: none; }
.connecting-seconds { position: absolute; inset: 0; display: grid; place-items: center; font-family: var(--font-mono);
  font-size: 34px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.connecting.is-late .connecting-seconds { font-size: 26px; color: var(--accent); }
.connecting-text { min-inline-size: 0; }
.connecting-text .card-title { margin-block-end: var(--gap-2); }
.connecting-lead { color: var(--ink-2); margin-block: 0 var(--gap-2); }
.connecting-note { font-weight: 600; margin-block: 0; }
.connecting-late { color: var(--accent); font-weight: 600; margin-block: var(--gap-2) 0; }
.region.is-connecting .live-toolbar, .region.is-connecting .live-grid, .region.is-connecting .live-viewers,
.region.is-connecting .archive-main .player-frame, .region.is-connecting .archive-tiles, .region.is-connecting .archive-controls, .region.is-connecting .archive-zoom,
.region.is-connecting .archive-strip, .region.is-connecting .archive-timeline, .region.is-connecting .archive-scroll, .region.is-connecting .archive-clip, .region.is-connecting .archive-keys { display: none; }
@keyframes connecting-halo { 0%, 100% { transform: scale(0.92); opacity: 0.10; } 50% { transform: scale(1.08); opacity: 0.22; } }
@keyframes connecting-spin { to { stroke-dashoffset: -326.73; } }
@media (max-width: 36rem) { .connecting { flex-direction: column; text-align: center; } }
@media (prefers-reduced-motion: reduce) { .connecting-halo { animation: none; } .connecting-progress { transition: none; } .connecting.is-late .connecting-progress { animation: none; stroke-dasharray: 326.73; } }

/* ── "recorder not connected" panel (live.php / archive.php ship it hidden;
   portal.js shows it and marks the region is-disconnected) ─────────────── */
.recorder-setup { max-inline-size: 42rem; margin-block: var(--gap-3) var(--gap-4); }
.recorder-setup .card-title { margin-block-end: var(--gap-2); }
.recorder-setup-update { color: var(--alert); font-weight: 600; margin-block: 0 var(--gap-3); }
.recorder-setup-reason { color: var(--ink); font-weight: 600; margin-block: 0 var(--gap-3); }
.recorder-setup-lead { color: var(--ink-2); margin-block: 0 var(--gap-3); }
.recorder-setup-steps { margin: 0 0 var(--gap-3); padding-inline-start: 1.4em; display: grid; gap: var(--gap-2); }
.recorder-setup-steps li { padding-inline-start: 0.2em; }
.recorder-setup-actions { display: flex; flex-wrap: wrap; gap: var(--gap-2); margin-block: 0 var(--gap-2); }
.recorder-setup-wait, .recorder-setup-member { color: var(--ink-2); margin-block: 0; }
.recorder-setup-subscribe { margin-block-start: var(--gap-3); padding: var(--gap-3); border-radius: var(--radius-s); background: var(--tint); }
.recorder-setup-subscribe p { margin-block: 0 var(--gap-2); }
.recorder-setup-ios { margin-block-start: var(--gap-4); padding-block-start: var(--gap-3); border-block-start: 1px solid var(--line); }
.recorder-setup-ios h3 { font-size: 1rem; margin-block: 0 var(--gap-1); }
.recorder-setup-ios p { margin-block: 0 var(--gap-2); color: var(--ink-2); }
.region.is-disconnected .live-toolbar, .region.is-disconnected .live-grid, .region.is-disconnected .live-viewers,
.region.is-disconnected .archive-main .player-frame, .region.is-disconnected .archive-tiles, .region.is-disconnected .archive-controls,
.region.is-disconnected .archive-strip, .region.is-disconnected .archive-timeline, .region.is-disconnected .archive-clip, .region.is-disconnected .archive-keys { display: none; }
