/* ══ v1.7.557 (batch 214) — THE GLASS FOUNDATION ═══════════════════════════
   The owner, 18 Oct: "I like the dashboard mock"; "make sure we use glass UI
   everywhere". This sheet is the mock's system, as tokens the shell owns:

     · the faces, vendored (static/vendor/fonts — OFL; THIRD-PARTY-NOTICES.md)
     · the tokens, both themes, on the shell's own theme switch
       (html[data-theme]) — every name is NEW; no token portal.css defines is
       re-valued here (--glass-* are extended, not forked: the bezel's outer
       shell sits on --glass-border like every other glass surface);
       since v1.7.560 (batch 217) the colour tokens are ALIASES of the
       shell's (the mesh of 214 is gone; the 1.7.234 wallpaper is the field)
     · the double-bezel card (.rx-card > .in, radius 22 / 16), the eyebrow,
       the label voice, the pill CTA with its nested arrow, the motion curve
       cubic-bezier(.32,.72,0,1); prefers-reduced-motion is respected
     · focus mode's header pill (.rx-focus), drawn on any portal page

   Loaded ONLY by a page that opts in or shows the focus pill
   (templates/_rx_glass_head.html), so no other page's rendering moves in
   this release.

   v1.7.561 (batch 218) — ONE TYPEFACE SET EVERYWHERE (fonts B). The owner,
   18 Oct: one design language — "fonts B = Geist (text) · Plus Jakarta Sans
   (headings) · Geist Mono (numbers, codes)". This sheet is now THE place the
   faces and the shell's font tokens are defined (once, below), and every
   shell loads it: the owner hub (base.html, every page), the portal shell's
   glass pages and its CM / H&S / supplier shells (portal_base.html), the
   public error and receipt pages, the scan board and the site supplier
   page. The AM desk never loads it (its pages stay byte-identical). The
   company branding font stays on registrant-facing pages only, served
   vendored (templates/_rx_brand_font.html). No page asks Google for a face. */

/* ── the faces ─────────────────────────────────────────────────────────── */
@font-face { font-family: "Geist"; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/vendor/fonts/geist-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: "Geist"; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/vendor/fonts/geist-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: "Geist"; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/vendor/fonts/geist-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/vendor/fonts/geist-mono-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/vendor/fonts/geist-mono-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/vendor/fonts/plus-jakarta-sans-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/vendor/fonts/plus-jakarta-sans-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/vendor/fonts/plus-jakarta-sans-latin-700-normal.woff2') format('woff2'); }

/* ── the tokens ─────────────────────────────────────────────────────────
   v1.7.560 (batch 217) — THE PREVIOUS PALETTE. The owner, 18 Oct: "we had a
   nice theme, colourful and serious — bring it back". The skin's colour
   tokens are no longer colours of their own: each is an ALIAS of the shell's
   token (portal.css; base.css on the owner hub — the second name in a var()
   is the owner shell's), so a glass page paints in exactly the 1.7.556
   palette, follows the shell's theme switch and its colourful / serious
   version, and no shell token is re-valued here. Declared once on :root —
   a var() resolves on the element, so the dark values come from the shell's
   own html[data-theme="dark"] block. Only the chart series --s1…--s7 keep
   values of their own (data colours, validated for both themes). */
:root {
  --rx-bg: var(--bg);
  --shell: var(--glass-nav, var(--surface)); --core: var(--glass, var(--surface));
  --core-2: var(--glass-inset, var(--surface-2)); --core-solid: var(--solid, var(--surface));
  --hair: var(--hairline, var(--border)); --hair-strong: var(--hairline-hi, var(--border-strong));
  --hi: var(--glass-hi, transparent);
  --rx-fg: var(--text); --rx-muted: var(--text-muted, var(--muted)); --rx-faint: var(--text-muted, var(--muted));
  --rx-accent: var(--accent); --rx-accent-ink: var(--on-primary, var(--on-accent)); --rx-accent-deep: var(--accent-text);
  --rx-accent-soft: var(--accent-soft, var(--focus-ring));
  --rx-good: var(--ok-fill); --rx-warn: var(--warn-fill); --rx-bad: var(--danger-fill);
  --rx-good-ink: var(--ok-text); --rx-warn-ink: var(--warn-text); --rx-bad-ink: var(--danger-text);
  /* the ink ON the accent is the shell's --on-primary (the portal's equals its --on-accent); the owner hub's
     accent is graphite, whose ink is --on-primary (white 7.4:1), not the cyan's --on-accent (2.5:1) */
  /* a field's edge (WCAG 1.4.11, >= 3:1 on page, core and bed, both themes, both versions, both
     hosts): no shell BORDER token clears 3:1 on the light field (the strongest, --glass-border-hi, is
     1.75:1; --glass-border-mid 1.6:1), so the edge is the shell's muted INK at 80% — still the shell's
     colour, no new one: portal light 4.76:1, serious light 4.40, dark 5.87, owner hub light 3.23 */
  --rx-edge: color-mix(in srgb, var(--text-muted, var(--muted)) 80%, transparent); --rx-scrim: var(--scrim, rgb(16 24 40 / .45));
  --s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; --s4: #eda100; --s5: #e87ba4; --s6: #7c6fd6; --s7: #8a919e;
  --rx-sans: "Geist", "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --rx-disp: "Plus Jakarta Sans", "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --rx-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --rx-ease: cubic-bezier(.32, .72, 0, 1);
  --rx-r: 22px; --rx-pad: 6px;
  --rx-shadow: 0 30px 80px -48px rgb(17 24 39 / .35);
}
/* ── fonts B: the shell's font tokens, DEFINED ONCE (v1.7.561, batch 218) ──
   The shells' own sheets (portal.css, base.css) and the owner hub's inline
   style come BEFORE this sheet, so these win on order alone; a registrant-
   facing page re-points --font / --font-ui / --font-display to the company's
   branding face after this sheet (_rx_brand_font.html) — --font-mono stays. */
:root {
  --font-ui: var(--rx-sans); --font-display: var(--rx-disp); --font-mono: var(--rx-mono);
  --font: var(--rx-sans);
}
/* ── the type contract (shared, zero specificity — a page's own rule wins) ──
   headings in the display face; the big ones (h1, KPI numbers — 24 px and
   up) tracked -.02em; body text 0; uppercase labels .14–.18em (each label
   rule below carries its own); numbers tabular in tables, KPIs and codes. */
:where(body) { letter-spacing: 0; }
:where(h1, h2, h3) { font-family: var(--font-display); }
:where(h1, .rx-num, .stat-card .val, .kpi-val, .kpi .v) { letter-spacing: -.02em; }
:where(table, .rx-num, .rx-k, .stat-card .val, .kpi-val, .kpi .v, code, kbd, samp, .mono) {
  font-variant-numeric: tabular-nums; }
:where(code, kbd, samp, pre, .mono) { font-family: var(--font-mono); }
/* ── dark: the data colours, the card's shadow, and the status MARKS ───
   A mark (a dot, a bar, a border, a flag) aliases the shell's FILL on the
   light theme; on dark the shell's fills are deepened for white ink on a
   button (ok 2.5:1, danger 2.2:1 against the dark field), so a mark there
   aliases the shell's INK of the same family — still the shell's colour. */
html[data-theme="dark"] {
  --rx-good: var(--ok-text); --rx-warn: var(--warn-text); --rx-bad: var(--danger-text);
  --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500; --s5: #d55181; --s6: #8b80e8; --s7: #6b7280;
  --rx-shadow: 0 30px 80px -40px rgb(0 0 0 / .6);
}

/* ── the opted-in page: the shell's own field, the faces, text ─────────
   v1.7.560 (batch 217): no mesh. The body keeps the shell's background and
   the 1.7.234 wallpaper and motes (portal_base's .rx-wallpaper) paint under
   the cards on a glass page as on every other page. */
html[data-rx-skin="glass"] body .portal-content {
  font-family: var(--rx-sans); color: var(--rx-fg); -webkit-font-smoothing: antialiased;
}
/* ── the full-width rule (v1.7.561, batch 218) ─────────────────────────────
   The owner, 18 Oct: "the page is very centred instead of full screen". A
   page's top-level wrapper fills the content area beside the rail, at every
   width; only running text inside a card may cap (~70ch). Several pages
   capped the shell's own `.content` (760–1200 px, some centred), among them
   a partial the AM desk shares (_badges_event_body.html) — so the rule is
   here, once, for every glass page, and the desk keeps its own layout.
   (The public form is a centred single column on purpose: a form on a
   phone. It does not wear the skin.) */
html[data-rx-skin="glass"] :is(.main, .portal-main, .portal-content) > .content {
  max-width: none; margin-left: 0; margin-right: 0; }   /* auto margins in the shell's flex column shrink it to fit */

/* ── type voices ────────────────────────────────────────────────────────── */
.rx-eyebrow { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--hair-strong);
  border-radius: 999px; padding: 4px 10px; font: 500 10px/1 var(--rx-mono); letter-spacing: .18em;
  text-transform: uppercase; color: var(--rx-muted); }
.rx-eyebrow i { width: 6px; height: 6px; border-radius: 50%; background: var(--rx-accent);
  box-shadow: 0 0 10px var(--rx-accent); }
.rx-label { font: 500 10px/1.2 var(--rx-mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--rx-muted); }
.rx-num { font: 500 1.9rem/1 var(--rx-disp); letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.rx-sub { color: var(--rx-muted); font-size: .8rem; }
.rx-k { font-family: var(--rx-mono); font-variant-numeric: tabular-nums; }

/* ── the double-bezel card: an outer glass shell, an inner core ──────────
   `.rx-card` already names the shell's flat content card (portal.css); the
   bezel is the `.rx-card` that HAS an `.in` — the flat card is untouched. */
.rx-card:has(> .in) { background: var(--shell); border: 1px solid var(--hair); border-radius: var(--rx-r);
  padding: var(--rx-pad); min-width: 0; box-shadow: var(--rx-shadow); }
.rx-card > .in { background: var(--core); border-radius: calc(var(--rx-r) - var(--rx-pad)); padding: 18px 20px;
  height: 100%; box-sizing: border-box; min-width: 0; border: 1px solid var(--hair);
  box-shadow: inset 0 1px 1px var(--hi);
  -webkit-backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2); }
@keyframes rx-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
/* ── the rise-in, on every glass page (v1.7.561, batch 218) ───────────────
   The dashboard's card entry, shared: a card fades up 18 px on the motion
   curve, staggered by its place among its siblings (six steps, then flat).
   Where: the bezel and flat .rx-card on any page that loads this sheet (the
   AM desk never does), and the shell's own cards (.card / .section /
   .stat-card) on a glass page. Only the OUTERMOST card rises (a card inside
   a card would rise twice). Never on a card that holds something measured
   on load — a canvas (the plan, the rulers), the Konva stage (.bd-konva),
   an iframe (the form editor's frame) — nor on the public form (it does not
   wear the skin and has no .rx-card). `backwards` fill: the card holds its
   from-state during its delay and keeps NOTHING once risen, so no
   stacking context or transform outlives the entry (a fixed dialog inside a
   card stays fixed to the viewport). A page restored from the back/forward
   cache is shown as it was left — the browser does not replay a finished
   animation — so there is no script here; reduced motion turns it off. */
/* v1.7.563 (batch 220) — THE TOP-LEVEL GLASS SURFACES: ONE LIST. The owner, 19 Oct: People and Access &
   badges had no rise-in — 218 rose the shells' .card / .section / .stat-card, but People builds its blocks
   from .acc-card / .zone-card / .scanner-card / .reg-token-card / .reg-add-card / .as-panel and its list
   panels (.rx-wait, the .rx-tiles strip, the list's .tbl-wrap), Access & badges from .ev-card, and none of
   them was in the list — nor in the surface rule (they painted flat). The SAME list is written in the rise
   rule (and its outermost-only guard), the surface rule and its :hover, the no-rise / lite rule, the nested-
   frost rule and the reduced-motion rule; THE LISTS MUST MATCH (a CSS custom selector does not exist yet —
   tests/test_ui_theme_3.py TheOneListOfSurfaces563 parses this sheet and asserts they are equal). The
   surface rule alone adds the flat .rx-card (.rx-card:not(:has(> .in)); the rise rule names .rx-card
   unscoped). The guards stay: only the OUTERMOST surface rises; never one that holds a canvas / iframe /
   Konva stage (219's zone-map stage, the badge designer, the plan canvas); none under reduced motion, after
   a POST (rx-no-rise) or on a weak device (rx-lite). */
:is(.rx-card, :where(html[data-rx-skin="glass"]) :is(.card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap)):not(:is(.rx-card, .card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap) *):not(:has(canvas, iframe, .bd-konva, .konvajs-content)) {
  animation: rx-rise .8s var(--rx-ease) backwards; animation-delay: var(--rx-stagger, 0s); }
.rx-bezel,
:where(html[data-rx-skin="glass"]) :is(.card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap, .rx-card:not(:has(> .in))) {
  border: var(--rx-pad) solid transparent; border-radius: var(--rx-r);
  background: linear-gradient(var(--core), var(--core)) padding-box,
              linear-gradient(var(--shell), var(--shell)) border-box;
  box-shadow: 0 0 0 1px var(--hair), inset 0 0 0 1px var(--hair), inset 0 1px 1px var(--hi), var(--rx-shadow);
  -webkit-backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2);
  transition: transform .6s var(--rx-ease), box-shadow .6s var(--rx-ease);
}
:where(html[data-rx-skin="glass"]) :is(.card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap, .rx-card:not(:has(> .in))):hover {
  transform: none; border-color: transparent;
  box-shadow: 0 0 0 1px var(--hair-strong), inset 0 0 0 1px var(--hair), inset 0 1px 1px var(--hi), var(--rx-shadow); }
/* v1.7.563 (batch 220) — THE LOST STAGGER: 218 set `animation-delay` here, at :where() specificity, and the
   rise rule's `animation` shorthand (far higher specificity) reset it to 0 — every card rose at once. The
   steps now set --rx-stagger, and the rise rule reads it with an `animation-delay` AFTER its shorthand.
   Only a rising card reads it (the outermost-only guard means no rising card has a card ancestor to inherit
   it from); the no-rise / lite / reduced-motion rules (`animation: none !important`) still win. */
:where(.rx-card, .card, .section, .stat-card):nth-child(2) { --rx-stagger: .05s; }
:where(.rx-card, .card, .section, .stat-card):nth-child(3) { --rx-stagger: .1s; }
:where(.rx-card, .card, .section, .stat-card):nth-child(4) { --rx-stagger: .15s; }
:where(.rx-card, .card, .section, .stat-card):nth-child(5) { --rx-stagger: .2s; }
:where(.rx-card, .card, .section, .stat-card):nth-child(n+6) { --rx-stagger: .25s; }
/* v1.7.563 (batch 220): no rise-in on a page that ANSWERS a form POST or carries a flash (the owner's
   "flash after Save" on Company branding — the whole page came back invisible and faded up over ~0.9 s),
   nor under rx-lite (weak devices). Both classes are set in the head include before the first paint
   (_rx_glass_head.html); the first GET of a page still rises. */
html:is(.rx-no-rise, .rx-lite) :is(.rx-card, .card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap) { animation: none !important; }

/* v1.7.563 (batch 220) — THE BLUR BUDGET (docs/UI_CONTRACT.md "Weak devices"): backdrop-filter only on
   top-level glass surfaces (cards, the rail, the bars, dialogs). A card inside a card is flat — a nested
   frost blurs the parent's already-blurred core for nothing — and a chip or a button never wears one. */
:is(.rx-card > .in, .rx-glass, .card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap)
  :is(.rx-glass, .rx-card > .in, .rx-card:not(:has(> .in)), .card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap),
html :is(.rx-focus-pill, .mob-menu-btn, .rx-mob-btn) {
  -webkit-backdrop-filter: none; backdrop-filter: none; }
html :is(.mob-menu-btn, .rx-mob-btn) { background-color: var(--solid, var(--surface)); }
/* hotfix 220d (v1.7.568) — THE SURFACES THAT ARE LISTS: a SECOND list, the ones the surface rule above must not
   blur (docs/UI_CONTRACT.md "Weak devices": a container that scrolls with the page or can hold more than ~20 rows
   never carries a backdrop-filter). The owner, on live 1.7.566, People → Show all (423 rows): "the GPU is still
   being loaded and entries fall behind as empty area takes time to load" — the list's .tbl-wrap was ONE frosted
   surface 30,546 × 2,705 px, re-blurred on every scroll frame. They stay in the ONE surface list (the bezel, the
   radius, the shadow, the rise-in — TheOneListOfSurfaces563 unchanged) and here trade the frost for the solid
   colour the frost composites to (--core-solid → --solid), the shell ring kept; People's own sheet paints the wrap
   and the strip on --solid too (rx-people-tools.css). Checked and left as they are: the H&S supplier list (its shell
   does not wear the glass skin — its .rx-card was never frosted); a fixed-height panel clipped to the viewport (the
   Live board's feeds, ≤ 40 rows, overflow hidden; the Access-sheets panel, one line per zone) keeps its frost: its
   blurred area never grows with its rows. */
:where(html[data-rx-skin="glass"]) :is(.rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap) {
  background: linear-gradient(var(--core-solid), var(--core-solid)) padding-box,
              linear-gradient(var(--shell), var(--shell)) border-box;
  -webkit-backdrop-filter: none; backdrop-filter: none; }
/* AMENDED v1.7.566 (hotfix 220c) — the owner on live: "the card is not transparent". The person window (and the
   add-person window, and the Access-sheets dialog) is the GLASS — its own blur and glass background, like every
   other top-level surface — and its SCRIM is a plain dim with no backdrop-filter: still one blur, never two
   stacked. (Was: the scrim blurred and the window flat on --solid.) `:root` outranks the glass skin's scrim rule
   (§7) and the shells' own `.modal-scrim` / `.as-scrim` blur. */
:root :is(.modal-scrim:has(> .pw), .as-scrim:has(> .as-dlg)) {
  -webkit-backdrop-filter: none; backdrop-filter: none; }
/* batch 235 (v1.7.581, SWEEP-235 B1): the window's blur never reached the page. The scrim's entrance fade
   (`scrim-in 160ms … both`) kept its opacity animation applied for as long as the window was open; an element with
   an applied opacity effect is a BACKDROP ROOT, so the window's backdrop-filter saw only the scrim's own dim — the
   People list behind read through the 72 % window sharp and unblurred (measured: Ada Sample's window over the list).
   The fade still plays; once it has played it lets go (fill backwards), and the window blurs the page. */
:root .modal-scrim.open:has(> .pw) { animation-fill-mode: backwards; }
html .modal-scrim > .pw {
  background: var(--glass, var(--core));
  -webkit-backdrop-filter: blur(var(--blur, 24px)) saturate(var(--sat, 1.2));
  backdrop-filter: blur(var(--blur, 24px)) saturate(var(--sat, 1.2)); }

/* ── the pill CTA with its nested arrow, and the ghost ──────────────────── */
.rx-cta { display: inline-flex; align-items: center; gap: 12px; background: var(--rx-accent);
  color: var(--rx-accent-ink); border-radius: 999px; padding: 7px 7px 7px 18px; border: 0;
  font: 500 .86rem var(--rx-sans); text-decoration: none; cursor: pointer;
  transition: transform .5s var(--rx-ease); }
.rx-cta:hover { transform: translateY(-1px); color: var(--rx-accent-ink); text-decoration: none; }
.rx-cta:active { transform: scale(.98); }
/* v1.7.560: the arrow's bed LIGHTENS the shell's accent (its ink is dark on cyan) — a darkening bed took the
   arrow to 4.06:1 on the light theme */
.rx-cta i { width: 28px; height: 28px; border-radius: 50%; background: rgb(255 255 255 / .24); display: grid;
  place-items: center; font-style: normal; transition: transform .5s var(--rx-ease); }
.rx-cta:hover i { transform: translate(2px, -1px); }
/* v1.7.563 (batch 220): Serious light's accent (the grey-blue) carries WHITE ink, so the lightening bed took the arrow
   to 3.05:1 (measured, the dashboard's "Live board ↗"); there the bed darkens instead — 6.4:1. Colourful and
   Serious dark keep the lightening bed (their ink is dark / they measure ≥ 4.5). */
html[data-rx-version="serious"][data-theme="light"] .rx-cta i { background: rgb(0 0 0 / .18); }
.rx-ghost { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--hair-strong);
  color: var(--rx-fg); border-radius: 999px; padding: 9px 16px; font: 500 .84rem var(--rx-sans);
  background: var(--core-2); text-decoration: none; cursor: pointer;
  transition: border-color .5s var(--rx-ease), transform .5s var(--rx-ease); }
.rx-ghost:hover { border-color: var(--rx-accent); color: var(--rx-fg); text-decoration: none; transform: translateY(-1px); }

/* ── focus mode's pill (any portal page; legacy tokens as the fallback) ── */
.rx-focus { display: flex; justify-content: flex-end; align-items: center; gap: 6px;
  padding: 10px max(16px, env(safe-area-inset-right, 0px)) 0 16px; position: relative; z-index: 30; }
/* v1.7.560 (batch 217): folded into the top bar — no padding, no row of its own */
.rx-focus.rx-focus-inbar { padding: 0; flex: 0 1 auto; min-width: 0; }
.rx-focus-inbar .rx-focus-dd { min-width: 0; max-width: 100%; }
/* on a phone the shell gives the title the whole first row (portal.css, v1.7.225); with the pill in the
   bar the title shares it instead — it ellipsises rather than breaking per word — and the pill drops its
   "Focused" key (the summary's aria-label still says it), so the bar keeps its row count */
@media (max-width: 768px) {
  .portal-main :is(.portal-topbar, .topbar):has(> .rx-focus-inbar) > h1 {
    flex: 1 1 0; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .rx-focus-inbar .rx-focus-pill { max-width: min(48vw, 260px); }
  .rx-focus-inbar .rx-focus-k { display: none; }
}
/* under 640 the phone shell (portal.css v1.7.399) stacks the bar — the title's row, then the actions strip
   that rxPhoneShell() moves every other child into; the pill rides at the right of the TITLE's row (the
   sticky bar is its containing block, so the strip's scroll clip does not cut it) and the title keeps room */
@media (max-width: 640px) {
  .portal-main :is(.portal-topbar, .topbar) .rx-focus-inbar {
    position: absolute; top: calc(6px + env(safe-area-inset-top, 0px)); right: 14px; }
  .portal-main :is(.portal-topbar, .topbar):has(.rx-focus-inbar) > h1 { padding-right: calc(min(40vw, 180px) + 44px); }
  .rx-focus-inbar .rx-focus-pill { max-width: min(40vw, 180px); padding: 4px 10px 4px 8px; }
  .rx-focus-inbar .rx-focus-x button { width: 28px; height: 28px; }
}
.rx-focus-dd { position: relative; }
.rx-focus-dd > summary { list-style: none; }
.rx-focus-dd > summary::-webkit-details-marker { display: none; }
.rx-focus-pill { display: inline-flex; align-items: center; gap: 8px; max-width: min(60vw, 420px);
  border: 1px solid var(--rx-accent); border-radius: 999px; padding: 5px 12px 5px 10px; cursor: pointer;
  background: var(--glass, var(--core)); color: var(--text, var(--rx-fg)); font: 500 12px/1.2 var(--rx-sans);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
.rx-focus-pill svg { width: 14px; height: 14px; flex: none; color: var(--text-muted, var(--rx-muted)); }
.rx-focus-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--rx-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--rx-accent) 22%, transparent); }
.rx-focus-k { font: 500 10px/1 var(--rx-mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-muted, var(--rx-muted)); }
.rx-focus-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.rx-focus-menu { position: absolute; right: 0; top: calc(100% + 6px); min-width: 240px; max-width: 86vw;
  max-height: 60vh; overflow: auto; padding: 6px; border-radius: 14px; border: 1px solid var(--glass-border, var(--hair-strong));
  background: var(--solid, var(--core)); box-shadow: 0 18px 50px -20px rgb(0 0 0 / .45); }
.rx-focus-cap { margin: 4px 8px 6px; font-size: 11.5px; color: var(--text-muted, var(--rx-muted)); }
.rx-focus-opt { margin: 0; }
.rx-focus-opt button { display: block; width: 100%; text-align: left; border: 0; background: none; cursor: pointer;
  padding: 9px 10px; border-radius: 10px; font: 500 13px/1.3 var(--rx-sans); color: var(--text, var(--rx-fg));
  min-height: 40px; }
.rx-focus-opt button:hover, .rx-focus-opt button:focus-visible { background: var(--row-hover-hi, var(--core-2)); }
.rx-focus-on { color: var(--text-muted, var(--rx-muted)); font-weight: 400; }
.rx-focus-x { margin: 0; }
.rx-focus-x button { width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 13px; line-height: 1;
  border: 1px solid var(--glass-border, var(--hair-strong)); background: var(--glass, var(--core));
  color: var(--text, var(--rx-fg)); }
.rx-focus-x button:hover { border-color: var(--rx-accent); }

@media (prefers-reduced-motion: reduce) {
  .rx-card, .rx-card:has(> .in), :where(html[data-rx-skin="glass"]) :is(.card, .section, .stat-card, .acc-card, .ev-card, .zone-card, .scanner-card, .reg-token-card, .reg-add-card, .as-panel, .rx-wait, .rx-tiles, .rx-pp-host .tbl-wrap) { animation: none !important; }
  .rx-cta, .rx-cta i, .rx-ghost { transition: none; }
}

/* ══ v1.7.559 (batch 216) — THE GLASS PASS, part 1 ═══════════════════════
   The owner, 18 Oct: "make sure we use glass UI everywhere". Every portal
   page and the owner hub opt in (`{% set rx_skin = 'glass' %}` in the page
   template; base.html carries the same opt-in as portal_base.html). This
   section is the foundation APPLIED to the shells' existing components —
   no markup, id, name, href or text moves; only how the shell's own
   components look on an opted-in page.

   How it stays out of everyone else's way:
     · every rule below is scoped by `html[data-rx-skin="glass"]`, so a page
       that loads this sheet only for the focus pill is unchanged;
     · the scope sits inside `:where()`, so each rule has exactly the
       specificity of the shell rule it restyles and wins on order alone
       (this sheet comes after portal.css / base.css): a page's own
       `<style>` (which comes later still) keeps its layout, and a more
       specific shell rule (the search box's reset, 309's) keeps winning.
       The four surfaces a page used to paint for itself — the dialog, its
       scrim, the drawer and the flash — are scoped WITHOUT `:where()`, so
       the shared look wins over a page's own copy (no page-local radius
       or colour on them); they set only paint, never size or position;
     · (216 re-pointed the legacy colour tokens here; v1.7.560 removed that —
       the shell's palette applies; only the faces and radii are re-pointed.)

   The shared classes (one each, used everywhere the component occurs; the
   shell's own class names are their aliases on a skinned page):
     .rx-bezel   the double bezel on ONE element (the shell's cards and
                 sections cannot gain an inner <div> without a markup change)
     .rx-field   a form field          .rx-modal   a dialog
     .rx-backdrop a dialog's scrim     .rx-drawer  a side drawer
     .rx-step    a wizard step         .rx-toast   a flash / notice
     .rx-tabs    a tab bar (+ .rx-tab) .rx-seg     a segmented control
     .rx-chip    a chip / badge        .rx-empty-g an empty state
     .rx-pager   pagination            .rx-table   the hairline table */

/* ── 1. the shells' VOICES on a skinned <html> (fonts and radii only) ─────
   v1.7.560 (batch 217): the colour re-point of 216 is gone — the shell's
   palette applies on a glass page exactly as on 1.7.556 (the skin's tokens
   alias the shell's, above). What stays is what the owner approved: the
   faces (Geist / Geist Mono / Plus Jakarta Sans) and the card radii. */
/* v1.7.561 (batch 218): the four font tokens left this block — they are defined ONCE, on :root above,
   for every shell that loads this sheet; what stays here is the glass card radii */
html[data-rx-skin="glass"][data-theme] {
  --r-xl: var(--rx-r); --r-lg: 18px; --r-md: 14px;
}
:where(html[data-rx-skin="glass"]) body { font-family: var(--rx-sans); color: var(--rx-fg);
  -webkit-font-smoothing: antialiased; }
:where(html[data-rx-skin="glass"]) :is(h1, h2, h3) { font-family: var(--rx-disp); }

/* ── 2. the chrome: the owner hub's rail and topbars, the portal's topbar ─ */
:where(html[data-rx-skin="glass"]) .sidebar {
  -webkit-backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2); }
:where(html[data-rx-skin="glass"]) :is(.topbar, .portal-topbar) {
  background: var(--shell); border-bottom: 1px solid var(--hair);
  -webkit-backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2); }
:where(html[data-rx-skin="glass"]) :is(.topbar, .portal-topbar) h1 {
  font-family: var(--rx-disp); letter-spacing: -.015em; color: var(--rx-fg); }
:where(html[data-rx-skin="glass"]) :is(.page-hdr, .page-head) h1 {
  font-family: var(--rx-disp); letter-spacing: -.02em; color: var(--rx-fg); }

/* ── 3. the double bezel on one element ────────────────────────────────
   The outer shell is the transparent 6px border showing --shell; the inner
   core is the padding box; the two hairlines are the ring and the inset. */
/* the double bezel on one element (the outer shell is the transparent 6px border showing --shell; the inner
   core is the padding box; the two hairlines are the ring and the inset) is defined in section 1, right
   after the rise-in, on THE TOP-LEVEL GLASS SURFACES list (v1.7.563, batch 220) */
/* v1.7.563 (batch 220): People's zone / scanner / registration-link cards carry a pre-glass dark fix in
   their template (`[data-theme="dark"] .zone-card … { background / border-color … !important }`) that would
   paint the 6 px bezel border solid; on the glass skin the bezel is their surface in both themes */
html[data-rx-skin="glass"][data-theme="dark"] :is(.zone-card, .scanner-card, .reg-token-card) {
  background: linear-gradient(var(--core), var(--core)) padding-box,
              linear-gradient(var(--shell), var(--shell)) border-box !important;
  border-color: transparent !important; }
:where(html[data-rx-skin="glass"]) .section-head { background: none; border-bottom: 1px solid var(--hair); }
:where(html[data-rx-skin="glass"]) .section-head h2 {
  font: 600 .95rem/1.2 var(--rx-disp); letter-spacing: -.01em; text-transform: none; color: var(--rx-fg); }
:where(html[data-rx-skin="glass"]) .section-title {
  font-family: var(--rx-disp); letter-spacing: -.01em; color: var(--rx-fg); }
:where(html[data-rx-skin="glass"]) .stat-card .val { font-family: var(--rx-disp); letter-spacing: -.03em; }
:where(html[data-rx-skin="glass"]) :is(.stat-card .lbl, .detail-label) {
  font: 500 10px/1.3 var(--rx-mono); letter-spacing: .16em; text-transform: uppercase; color: var(--rx-muted); }

/* ── 4. the pill CTA and the ghost, as the shell's buttons ──────────────── */
:where(html[data-rx-skin="glass"]) :is(.btn, .btn-icon) {
  border-radius: 999px; font-family: var(--rx-sans); font-weight: 500;
  transition: transform .5s var(--rx-ease), border-color .5s var(--rx-ease),
              background-color .5s var(--rx-ease), color .5s var(--rx-ease); }
:where(html[data-rx-skin="glass"]) .btn:hover { transform: translateY(-1px); }
:where(html[data-rx-skin="glass"]) .btn:active { transform: scale(.98); }
:where(html[data-rx-skin="glass"]) .btn { box-shadow: none; }
/* the neutral button only: a coloured variant (primary, danger, success, accent, ghost) keeps its fill */
:where(html[data-rx-skin="glass"]) :is(.btn, .btn-secondary, .btn-outline):where(:not(.btn-primary, .btn-danger, .btn-success, .btn-accent, .btn-ghost)) {
  background-color: var(--core-2); border: 1px solid var(--hair-strong); color: var(--rx-fg); }
:where(html[data-rx-skin="glass"]) :is(.btn, .btn-secondary, .btn-outline, .btn-icon):where(:not(.btn-primary, .btn-danger, .btn-success, .btn-accent, .btn-ghost)):hover {
  border-color: var(--rx-accent); color: var(--rx-fg); }
:where(html[data-rx-skin="glass"]) .btn-primary,
:where(html[data-rx-skin="glass"]) .btn-primary:hover {
  background-color: var(--rx-accent); color: var(--rx-accent-ink); border-color: transparent; }
:where(html[data-rx-skin="glass"]) .btn-ghost { background-color: transparent; border-color: transparent; color: var(--rx-muted); }
:where(html[data-rx-skin="glass"]) .btn-ghost:hover { background-color: var(--core-2); color: var(--rx-fg); }
:where(html[data-rx-skin="glass"]) .btn-accent { background-color: transparent; color: var(--rx-accent-deep); border-color: var(--rx-accent); }
:where(html[data-rx-skin="glass"]) .btn-danger,
:where(html[data-rx-skin="glass"]) .btn-danger:hover { border-color: transparent; }
:where(html[data-rx-skin="glass"]) .btn-icon { background-color: var(--core-2); border: 1px solid var(--hair-strong); }
:where(html[data-rx-skin="glass"]) :is(.btn, .btn-icon, .rx-cta, .rx-ghost):focus-visible {
  outline: 2px solid var(--rx-accent); outline-offset: 2px; }

/* ── 5. the form field ──────────────────────────────────────────────────── */
.rx-field,
:where(html[data-rx-skin="glass"]) :is(input[type="text"], input[type="email"], input[type="password"],
  input[type="number"], input[type="date"], input[type="tel"], input[type="url"], input[type="time"],
  input[type="datetime-local"], input[type="search"], textarea, select) {
  background-color: var(--core-2); border: 1px solid var(--rx-edge); border-radius: 12px; color: var(--rx-fg);
  font-family: var(--rx-sans);
  transition: border-color .4s var(--rx-ease), box-shadow .4s var(--rx-ease); }
.rx-field:focus,
:where(html[data-rx-skin="glass"]) :is(input, textarea, select):focus {
  outline: none; border-color: var(--rx-accent); box-shadow: 0 0 0 3px var(--rx-accent-soft); }
:where(html[data-rx-skin="glass"]) :is(input, textarea)::placeholder { color: var(--rx-faint); opacity: 1; }
:where(html[data-rx-skin="glass"]) :is(input[type="checkbox"], input[type="radio"], progress, input[type="range"]) {
  accent-color: var(--rx-accent); }
:where(html[data-rx-skin="glass"]) .form-group > label:not(:has(input, select, textarea)) {
  font: 500 10px/1.3 var(--rx-mono); letter-spacing: .14em; text-transform: uppercase; color: var(--rx-muted); }

/* ── 6. the hairline table (scrolls inside its card) ───────────────────── */
.rx-table th,
:where(html[data-rx-skin="glass"]) th {
  background: none; border-bottom: 1px solid var(--hair); color: var(--rx-muted);
  font: 500 10px/1.3 var(--rx-mono); letter-spacing: .14em; text-transform: uppercase; }
.rx-table td,
:where(html[data-rx-skin="glass"]) td { border-bottom-color: var(--hair); }
.rx-table tr:hover td,
:where(html[data-rx-skin="glass"]) tr:hover td { background-color: var(--core-2); }
:where(html[data-rx-skin="glass"]) :is(.table-scroll, .table-wrap, .events-table-wrap, .td-table-wrap, .tm-table-wrap) {
  overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; max-width: 100%; }

/* ── 7. the dialog, its scrim, and the drawer ──────────────────────────── */
.rx-modal,
html[data-rx-skin="glass"] :is(.modal-box, .rxp-modal-box, .qr-modal-box, .zone-modal-box, .fe-dlg, .as-dlg,
  .tpl-dlg, .cc-dlg) {
  border: 1px solid var(--hair-strong); border-radius: var(--rx-r); background-color: var(--core);
  box-shadow: inset 0 1px 1px var(--hi), 0 0 0 var(--rx-pad) var(--shell), var(--rx-shadow);
  -webkit-backdrop-filter: blur(24px) saturate(1.2); backdrop-filter: blur(24px) saturate(1.2); color: var(--rx-fg); }
.rx-backdrop,
html[data-rx-skin="glass"] :is(.modal-scrim, .modal-bg, .modal-overlay, .drawer-scrim) {
  background-color: var(--rx-scrim); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.rx-drawer,
html[data-rx-skin="glass"] :is(.drawer, .rx-help-panel) {
  background-color: var(--core); border-left: 1px solid var(--hair-strong);
  box-shadow: var(--rx-shadow);
  -webkit-backdrop-filter: blur(24px) saturate(1.2); backdrop-filter: blur(24px) saturate(1.2); }

/* ── 8. the wizard step, the toast, the tab bar, the segmented control ─── */
.rx-step,
:where(html[data-rx-skin="glass"]) .wiz-step { border-radius: 999px; background-color: var(--hair-strong); }
.rx-step.active, .rx-step.complete,
:where(html[data-rx-skin="glass"]) :is(.wiz-step.active, .wiz-step.complete) { background-color: var(--rx-accent); }
.rx-toast,
html[data-rx-skin="glass"] :is(.alert, .flash, .alert-banner) {
  border-radius: 14px; border-top: 1px solid var(--hair); border-right: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair); box-shadow: inset 0 1px 1px var(--hi);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
.rx-tabs,
:where(html[data-rx-skin="glass"]) :is(.rx-tabbar, .pw-tabs, .ev-tabs, .export-tabs, .filter-tabs, .tab-bar) {
  border-color: var(--hair); }
.rx-tab,
:where(html[data-rx-skin="glass"]) :is(.rx-tab, .pw-tab, .ev-tab, .export-tab, .excel-tab) {
  font-family: var(--rx-sans); border-radius: 999px; }
:where(html[data-rx-skin="glass"]) :is(.rx-tab, .pw-tab, .ev-tab, .export-tab, .excel-tab):is(.active, [aria-selected="true"], [aria-current="page"]) {
  color: var(--rx-fg); }
.rx-seg,
:where(html[data-rx-skin="glass"]) :is(.seg, .rx-ap-seg) {
  background: var(--core-2); border: 1px solid var(--hair); border-radius: 999px; }
.rx-seg > :is(a, button),
:where(html[data-rx-skin="glass"]) :is(.seg, .rx-ap-seg) > :is(a, button) { border-radius: 999px; }
.rx-seg > .active,
:where(html[data-rx-skin="glass"]) :is(.seg, .rx-ap-seg) > :is(a, button).active {
  background: var(--core); color: var(--rx-fg); box-shadow: 0 0 0 1px var(--hair-strong); }

/* ── 9. chips, empty states, pagination ────────────────────────────────── */
.rx-chip,
:where(html[data-rx-skin="glass"]) :is(.badge, .chip) {
  border-radius: 999px; font-family: var(--rx-mono); font-weight: 500; letter-spacing: .08em; }
:where(html[data-rx-skin="glass"]) .status-pill { font-family: var(--rx-sans); font-weight: 500; }
.rx-empty-g,
:where(html[data-rx-skin="glass"]) :is(.rx-empty, .empty-state, .as-empty, .ev-empty, .al-empty, .acc-empty, .gb-empty) {
  border: 1px dashed var(--hair-strong); border-radius: 18px; background-color: var(--core-2); color: var(--rx-muted); }
.rx-pager > :is(a, button, span),
:where(html[data-rx-skin="glass"]) :is(.pagination, .rxp-pager, .al-pager) > :is(a, button),
:where(html[data-rx-skin="glass"]) .page-btn {
  border-radius: 999px; font-family: var(--rx-mono); font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  :where(html[data-rx-skin="glass"]) :is(.btn, .btn-icon, .card, .section, .stat-card, input, textarea, select) {
    transition: none; }
  :where(html[data-rx-skin="glass"]) .btn:hover { transform: none; }
}
/* v1.7.563 (batch 220 sweep): the People filter's count badge sat on --accent-text (the INK colour) with
   --on-accent ink — 2.60:1 on Colourful light (dark ink on the deep cyan ink colour). It takes the accent bed its ink is made for
   (--accent + --on-accent, the designed pair in all four looks). */
html[data-rx-skin="glass"] .rxf-badge { background: var(--accent); }   /* outranks am-people.css, which loads after */

/* batch 224 (v1.7.570) — THE OWNER'S SWEEP, the shared rules: one defect seen on several glass pages is fixed once here
   (docs/UI_CONTRACT.md; the AM desk does not load this sheet). Look only — no control moves in the DOM or the tab order.
   1. A page header's actions pair up on the phone: when they wrapped (Build Schedule), the last control sat ALONE on
      its own line. Two equal columns; an odd last one spans the row on purpose (a deliberate full-width control, not a
      stray). Only rows of three or more. (The H&S desk, CM and supplier shells, which load this sheet without the skin
      attribute, carry the same rules in their own sheets.)
   2. The badge-template card: "Open designer →" takes the card's first line, "Duplicate" and "Delete" share the second
      ("Delete" wrapped alone under the other two in the 276-px card).
   3. "What we can provide": the chips, the "Add…" field and Save are one height (chips 46, field 40, Save 28 px).
 */
@media (max-width: 640px) {
  :where(html[data-rx-skin="glass"]) .page-hdr > .rx-actions:has(> :nth-child(3)) {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
  :where(html[data-rx-skin="glass"]) .page-hdr > .rx-actions:has(> :nth-child(3)) > * {
    min-width: 0; max-width: none; justify-content: center; }
  :where(html[data-rx-skin="glass"]) .page-hdr > .rx-actions:has(> :nth-child(3)) > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}
:where(html[data-rx-skin="glass"]) .tpl-card > .tpl-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
:where(html[data-rx-skin="glass"]) .tpl-card > .tpl-actions > :first-child { grid-column: 1 / -1; justify-content: center; }
:where(html[data-rx-skin="glass"]) .tpl-card > .tpl-actions > .dup-pick > summary.btn, :where(html[data-rx-skin="glass"]) .tpl-card > .tpl-actions > form > .btn { width: 100%; justify-content: center; box-sizing: border-box; }
:where(html[data-rx-skin="glass"]) .rx-prov-ed-chips > .rx-prov-ed-chip, :where(html[data-rx-skin="glass"]) .rx-prov-ed-chips > input[name="new_label"], :where(html[data-rx-skin="glass"]) .rx-prov-ed-chips > .btn {
  box-sizing: border-box; min-height: var(--ctl-h, 40px); }
:where(html[data-rx-skin="glass"]) .rx-prov-ed-chips > .rx-prov-ed-chip > input[type="text"] { min-height: 0; height: auto; }
/* batch 225 (v1.7.571, SWEEP-225 W2): inside a provision chip, the name field and its ✕ are at least 24 px tall and the
   ✕ 24 wide on touch widths (21 / 23 x 21 px; the chip itself is 40) — the shared _provision partial untouched */
@media (max-width: 1024px) {
  :where(html[data-rx-skin="glass"]) .rx-prov-ed-chips > .rx-prov-ed-chip > input[type="text"] { min-height: 24px; }
  :where(html[data-rx-skin="glass"]) .rx-prov-ed-chips > .rx-prov-ed-chip > .rx-prov-ed-x { min-width: 24px; min-height: 24px; }
}
