/* ==========================================================================
   GATE PASS — Components
   Gate Pass components: tight typography, restrained shadows,
   8-12px radius (no pill-heavy chrome), 8px spacing grid.
   ========================================================================== */

/* ============================================================
   TYPOGRAPHY HELPERS
   ============================================================ */
.section-title {
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}
.section-subtitle {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.text-muted { color: var(--text-muted); font-size: var(--text-sm); }
/* Muted micro text for table meta cells (dates, counts) — replaces the
   repeated inline style="color:var(--text-muted);font-size:12px". */
.cell-meta { color: var(--text-muted); font-size: var(--text-xs); }
.eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-muted);
}

/* ============================================================
   BUTTONS — accent (black primary), ghost, danger-ghost
   Monochrome system: "accent" is black, not gold. See tokens.css.
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 18px;
  height: 44px;
  border-radius: var(--radius-lg);
  font-weight: var(--weight-semibold);
  font-size: var(--text-base);
  letter-spacing: -0.005em;
  transition:
    background var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast),
    opacity var(--duration-fast),
    transform var(--duration-fast);
  white-space: nowrap;
  border: 1px solid transparent;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Tactile press feedback — a clear squish on tap (the old 0.5px nudge was
   invisible, especially on touch). */
.btn:active:not(:disabled) { transform: scale(0.96); }
/* Keyboard focus ring — buttons had none (only inputs did), so tab focus
   was invisible. Matches the input/search/toggle focus treatment. */
.btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Accent = the one primary CTA token. v89 unified btn--primary into this.
   Both used to resolve to black; merging removes the visual-token confusion
   between "primary" (navy) and "accent" (gold) that the original spec set
   up but never delivered on. */
.btn--accent {
  background: var(--color-accent-500);
  color: var(--accent-on);
  /* Neutral shadow — the button is black (gray-1000); the old gold-tinted
     shadow was vestigial from an abandoned gold-accent direction. */
  box-shadow: 0 1px 2px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.12);
}
.btn--accent:hover:not(:disabled) {
  background: var(--color-accent-600);
  box-shadow: 0 2px 6px rgba(0,0,0,0.22), inset 0 1px 0 rgba(255,255,255,0.12);
}
/* Press: black can't darken, so it lightens a touch (visible flash) and the
   shadow flips inset to read as pushed-in. Combined with the .btn scale. */
.btn--accent:active:not(:disabled) {
  background: var(--gray-700);
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.4);
}

.btn--ghost {
  background: var(--surface-raised);
  color: var(--text-secondary);
  border-color: var(--surface-border);
  box-shadow: var(--shadow-xs);
}
.btn--ghost:hover:not(:disabled) {
  background: var(--surface-sunken);
  color: var(--text-primary);
  border-color: var(--surface-border-strong);
}

/* Solid-red .btn--danger removed (C4): destructive actions use one
   convention — .btn--danger-ghost (red outline) — gated by type-to-confirm
   modals. Reintroduce only with a deliberate reason. */

.btn--danger-ghost {
  background: transparent;
  color: var(--color-danger);
  border-color: var(--color-danger-border);
}
.btn--danger-ghost:hover:not(:disabled) {
  background: var(--color-danger-bg);
}

.btn--sm { height: 36px; padding: 0 14px; font-size: var(--text-sm); border-radius: var(--radius-md); }
.btn--lg { height: 52px; padding: 0 24px; font-size: var(--text-md); }

/* Soft floating card — white on the near-white page, lifted by a gentle
   shadow instead of a hairline border (v3 redesign). */
.card {
  background: var(--surface-raised);
  border: 0;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: 20px;
}

/* ==========================================================================
   LANDING — public front door (logged-out root)
   ========================================================================== */
.landing { max-width: 960px; margin: 0 auto; padding: 20px 20px 40px; min-height: 100dvh; display: flex; flex-direction: column; }
.landing__nav { display: flex; align-items: center; justify-content: space-between; padding: 8px 0 24px; }
.landing__brand { display: flex; align-items: center; gap: 10px; font-weight: var(--weight-semibold); font-size: var(--text-lg); }
.landing__brand .brand-mark { width: 34px; height: 34px; display: inline-flex; }
.landing__wordmark { letter-spacing: -0.01em; }
.landing__hero { padding: 40px 0 32px; text-align: center; max-width: 620px; margin: 0 auto; }
.landing__eyebrow { display: inline-block; font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); background: var(--surface-sunken); padding: 6px 14px; border-radius: var(--radius-full); margin-bottom: 20px; }
.landing__title { font-size: 44px; line-height: 1.05; letter-spacing: -0.03em; font-weight: var(--weight-bold); margin: 0 0 16px; }
.landing__sub { font-size: var(--text-lg); color: var(--text-secondary); line-height: 1.6; margin: 0 auto 28px; max-width: 520px; }
.landing__cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.landing__cta .btn { min-width: 160px; }
.landing__features { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 16px; margin-top: 24px; }
.landing__feature { background: var(--surface-raised); border-radius: var(--radius-xl); box-shadow: var(--shadow-card); padding: 24px; text-align: left; }
.landing__feature-ico { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: var(--radius-lg); background: var(--surface-sunken); color: var(--text-primary); margin-bottom: 14px; }
.landing__feature h3 { font-size: var(--text-lg); margin: 0 0 6px; font-weight: var(--weight-semibold); }
.landing__feature p { font-size: var(--text-sm); color: var(--text-secondary); line-height: 1.6; margin: 0; }
.landing__footer { margin-top: auto; padding-top: 32px; display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: var(--text-sm); color: var(--text-muted); }
.landing__footer-links a { color: var(--text-secondary); }
.landing__footer-links span { margin: 0 8px; color: var(--text-muted); }

/* How it works — numbered steps on a soft gray band. */
.landing__how { margin-top: 32px; background: var(--surface-sunken); border-radius: var(--radius-2xl); padding: 44px 28px; text-align: center; }
.landing__section-title { font-size: 28px; font-weight: var(--weight-bold); letter-spacing: -0.02em; margin: 0 0 8px; }
.landing__section-sub { font-size: var(--text-base); color: var(--text-secondary); margin: 0 auto 28px; max-width: 420px; }
.landing__steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; text-align: left; }
.landing__step-num { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: var(--radius-full); background: var(--gray-1000); color: #fff; font-family: var(--font-mono); font-size: var(--text-sm); font-weight: var(--weight-semibold); margin-bottom: 12px; }
.landing__step h3 { font-size: var(--text-lg); margin: 0 0 6px; font-weight: var(--weight-semibold); }
.landing__step p { font-size: var(--text-sm); color: var(--text-secondary); line-height: 1.6; margin: 0; }

/* Final CTA — black card. */
.landing__final { margin-top: 32px; background: var(--gray-1000); border-radius: var(--radius-2xl); padding: 48px 28px; text-align: center; }
.landing__final h2 { color: #fff; font-size: 26px; font-weight: var(--weight-bold); letter-spacing: -0.02em; margin: 0 0 12px; }
.landing__final p { color: rgba(255,255,255,0.7); font-size: var(--text-base); line-height: 1.6; margin: 0 auto 24px; max-width: 440px; }
.landing__final-btn { background: #fff; color: var(--gray-1000); border: 0; min-width: 180px; }
.landing__final-btn:hover:not(:disabled) { background: rgba(255,255,255,0.9); }

@media (max-width: 720px) {
  .landing__title { font-size: 34px; }
  .landing__sub { font-size: var(--text-base); }
  .landing__features { grid-template-columns: 1fr; }
  .landing__steps { grid-template-columns: 1fr; gap: 28px; }
  .landing__hero { padding: 24px 0; }
  .landing__cta .btn { flex: 1; min-width: 0; }
}

/* ------------------------------------------------------------
   Landing animations. Settle-once, monochrome-quiet:
   - hero cascades in on load ("sorted." underline sweeps last)
   - [data-reveal] sections rise in on scroll (JS adds .is-in,
     --rd staggers siblings)
   - step numbers pop with a spring once their card reveals
   - feature cards lift on hover (desktop)
   Everything lives inside a no-preference media query, so
   reduced-motion users get a fully static, always-visible page.
   ------------------------------------------------------------ */
.landing__accent { position: relative; white-space: nowrap; }

@media (prefers-reduced-motion: no-preference) {
  /* Hero entrance cascade */
  .landing__nav,
  .landing__hero > * {
    opacity: 0;
    animation: land-rise 0.65s var(--ease-out) forwards;
  }
  .landing__nav                    { animation-delay: 0s; }
  .landing__hero > .landing__eyebrow { animation-delay: 0.08s; }
  .landing__hero > .landing__title   { animation-delay: 0.18s; }
  .landing__hero > .landing__sub     { animation-delay: 0.3s; }
  .landing__hero > .landing__cta     { animation-delay: 0.42s; }
  @keyframes land-rise {
    from { opacity: 0; transform: translateY(18px); }
    to   { opacity: 1; transform: none; }
  }

  /* "sorted." underline sweep — lands after the title settles. */
  .landing__accent::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0.02em;
    height: 0.09em;
    background: currentColor;
    border-radius: 2px;
    transform: scaleX(0);
    transform-origin: left;
    animation: land-underline 0.5s var(--ease-out) 0.75s forwards;
  }
  @keyframes land-underline { to { transform: scaleX(1); } }

  /* Scroll reveals */
  [data-reveal] {
    opacity: 0;
    transform: translateY(26px);
    transition:
      opacity 0.6s var(--ease-out),
      transform 0.6s var(--ease-out);
    transition-delay: var(--rd, 0s);
  }
  [data-reveal].is-in { opacity: 1; transform: none; }

  /* Step numbers spring in once their card is revealed. */
  .landing__step[data-reveal] .landing__step-num {
    transform: scale(0.4);
    opacity: 0;
  }
  .landing__step[data-reveal].is-in .landing__step-num {
    animation: land-num-pop 0.55s var(--ease-spring) forwards;
    animation-delay: calc(var(--rd, 0s) + 0.25s);
  }
  @keyframes land-num-pop {
    from { transform: scale(0.4); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
  }

  /* Hover life (desktop) */
  .landing__feature {
    transition:
      transform var(--duration-slow) var(--ease-out),
      box-shadow var(--duration-slow) var(--ease-out);
  }
  .landing__feature:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-card-hover);
  }
  .landing__feature-ico { transition: transform var(--duration-slow) var(--ease-spring); }
  .landing__feature:hover .landing__feature-ico { transform: scale(1.1); }
}

/* Neutral informational box (e.g. the Report / Delete sheet blurbs). Gray
   rounded panel, no color — matches the Figma. */
.note-box {
  background: var(--surface-sunken);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  font-size: var(--text-base);
  color: var(--text-secondary);
  line-height: 1.6;
}

/* ============================================================
   TABLES — Linear/Stripe-grade
   ============================================================ */
.table-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-bottom: none;
  border-top-left-radius: var(--radius-xl);
  border-top-right-radius: var(--radius-xl);
}
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.table th {
  text-align: left;
  padding: 10px 16px;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--surface-border);
  white-space: nowrap;
}
.table td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--surface-border);
  color: var(--text-primary);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr { transition: background var(--duration-fast); }
.table tbody tr:hover td { background: var(--surface-sunken); }

.row--suspended td { opacity: 0.45; }
.row--suspended td:last-child { opacity: 1; }

/* Search input inside table toolbar */
.search-input {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  padding: 0 14px;
  height: 48px;
  /* White field with a hairline border on the gray page (matches the Figma
     and the resident search). Focus wraps it in a black border. */
  border: 1px solid var(--surface-border);
  background: var(--surface-raised);
  border-radius: var(--radius-lg);
}
.search-input:focus-within { border-color: var(--accent); box-shadow: none; }
.search-input svg { color: var(--text-muted); flex-shrink: 0; }
.search-input input {
  /* appearance:none kills the native type="search" chrome (the inset inner
     border/box that showed on mobile). No wrapper border either now. */
  -webkit-appearance: none;
  appearance: none;
  border: none; outline: none; box-shadow: none; background: transparent;
  font-size: var(--text-sm); flex: 1; padding: 0;
  color: var(--text-primary);
}
.search-input input::-webkit-search-decoration,
.search-input input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Labeled inline date filter ("From [date]") — gives the bare date input a
   visible label so it isn't a mystery empty box. */
.filter-date {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.filter-date__label { flex-shrink: 0; font-weight: var(--weight-medium); }

/* ============================================================
   EMPTY STATES
   ============================================================ */
.empty {
  padding: 48px 24px;
  text-align: center;
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.empty--error {
  color: var(--color-danger);
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: var(--radius-md);
  padding: 16px;
}

/* ------------------------------------------------------------
   Rich empty-state. Use when an empty list deserves an action,
   not just a one-line label. Pattern: icon → headline →
   one-line subhead → primary CTA (optional).

     <div class="empty-state">
       <div class="empty-state__icon">SVG</div>
       <h3 class="empty-state__title">No visitors yet</h3>
       <p class="empty-state__sub">Register the people you'll invite over.</p>
       <button class="btn btn--accent">Add visitor</button>
     </div>
   ------------------------------------------------------------ */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 56px 24px;
  gap: 8px;
  color: var(--text-secondary);
}
.empty-state__icon {
  width: 56px;
  height: 56px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface-sunken);
  color: var(--text-muted);
  margin-bottom: 4px;
}
.empty-state__icon svg { width: 28px; height: 28px; }
.empty-state__icon i { font-size: 28px; }
.empty-state__title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  letter-spacing: -0.018em;
}
.empty-state__sub {
  margin: 0;
  max-width: 38ch;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}
.empty-state > .btn { margin-top: 8px; }

/* ------------------------------------------------------------
   Retryable error state. Same shape as .empty-state but with a
   "Try again" action (wired by Screen.paintError). Shown when a
   data load throws instead of leaving a dead-end error line.
   ------------------------------------------------------------ */
.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 48px 24px;
  gap: 6px;
  color: var(--text-secondary);
}
.error-state__icon {
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface-sunken);
  color: var(--text-secondary);
  margin-bottom: 4px;
}
.error-state__title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  letter-spacing: -0.018em;
}
.error-state__sub {
  margin: 0;
  max-width: 34ch;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}
.error-state > .btn { margin-top: 12px; }

/* ------------------------------------------------------------
   Skeleton loaders. Grey, shimmering placeholders shaped like
   the content that's about to load (see screen.js skeleton()).
   The shimmer respects prefers-reduced-motion.
   ------------------------------------------------------------ */
.skeleton { display: flex; flex-direction: column; gap: 8px; }
.sk-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 6px; }
.sk-rows { display: flex; flex-direction: column; gap: 8px; }
.sk-card {
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-xl);
  padding: 16px;
}
.sk-row {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-xl);
  padding: 14px;
}
.sk-row__body { flex: 1; display: flex; flex-direction: column; gap: 9px; }

.sk {
  position: relative;
  overflow: hidden;
  background: var(--surface-border);
  border-radius: 6px;
}
.sk-line { height: 12px; width: 60%; }
.sk-line--sm { height: 10px; width: 40%; }
.sk-num { height: 26px; width: 45%; margin-top: 12px; }
.sk-avatar { width: 40px; height: 40px; border-radius: 50%; flex: none; }
.sk-pill { width: 56px; height: 22px; border-radius: var(--radius-full); flex: none; }
.sk-chart { height: 150px; border-radius: var(--radius-lg); }
.sk-stat-value { display: inline-block; width: 44px; height: 22px; vertical-align: middle; }

/* Spinning outline icon (e.g. "Verifying…"). */
.ico-spin { display: inline-flex; animation: ico-spin 0.8s linear infinite; }
@keyframes ico-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ico-spin { animation-duration: 1.6s; } }

.sk::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
  animation: sk-shimmer 1.4s ease-in-out infinite;
}
@keyframes sk-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .sk::after { animation: none; }
}

/* ============================================================
   FORM FIELDS
   ============================================================ */
.field { display: flex; flex-direction: column; gap: 6px; }

.field__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.field__input {
  height: 48px;
  padding: 0 16px;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  color: var(--text-primary);
  font-size: var(--text-base);
  transition:
    border-color var(--duration-fast),
    box-shadow var(--duration-fast),
    background var(--duration-fast);
}
.field__input::placeholder { color: var(--text-muted); }

/* Selects: replace the native arrow (which hugs the border) with our outline
   chevron inset 16px — same breathing room as the Visitors trigger caret. */
select.field__input {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23525252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  padding-right: 46px;
}
/* Date/time pickers: Chrome sizes these to their content, so they collapse to
   half a field and hug the left while every other control fills the row. Force
   them to fill (skipping the inline toolbar filters, which sit in a row), and
   swap the browser's picker glyph for the SAME chevron the selects use — 18px,
   stroke-width 2, inset 16px — so the arrow matches exactly. Restyling the
   indicator (rather than hiding it) keeps the picker tappable. */
input[type="date"].field__input:not(.field__input--inline),
input[type="time"].field__input:not(.field__input--inline),
input[type="datetime-local"].field__input:not(.field__input--inline) {
  /* Same as select.field__input: strip the browser's own control decoration,
     or Chrome draws ITS arrow next to ours and you get two chevrons. */
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  padding-right: 46px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23525252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
}
/* Hide the browser's own picker glyph so only our chevron shows — tapping the
   field still opens the native picker on phones, which is where this is used. */
input[type="date"].field__input:not(.field__input--inline)::-webkit-calendar-picker-indicator,
input[type="time"].field__input:not(.field__input--inline)::-webkit-calendar-picker-indicator,
input[type="datetime-local"].field__input:not(.field__input--inline)::-webkit-calendar-picker-indicator {
  opacity: 0;
}
/* Fields contrast with their surface: white on the gray page (default above);
   gray-filled inside white surfaces (sheets, the login card, onboarding). */
.modal .field__input,
.auth .field__input,
.onboarding .field__input,
.card .field__input {
  /* background-COLOR, not the shorthand: selects carry their chevron as a
     background-image, and the shorthand would erase it. */
  background-color: var(--surface-sunken);
  border-color: transparent;
}
/* Radios + checkboxes use the brand near-black, not the browser default. */
input[type="radio"],
input[type="checkbox"] { accent-color: var(--text-primary); }

/* Resident dashboard "Notify security" — set apart at the bottom as a calm,
   always-available safety action (not competing with the primary CTA). */
.dash-notify-wrap { margin-top: 8px; padding-top: 16px; border-top: 1px solid var(--surface-border); }

/* ===== Resident mobile redesign (shared across resident screens) ===== */
.rd-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rd-hi { margin: 0; font-size: var(--text-xl); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-tight); }
.rd-loc { display: flex; align-items: center; gap: 4px; margin: 4px 0 0; font-size: var(--text-xs); color: var(--text-secondary); }

.rd-stats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.rd-stat { background: var(--surface-raised); border-radius: var(--radius-xl); padding: var(--space-5); box-shadow: var(--shadow-card); }
.rd-stat__label { display: block; font-size: var(--text-xs); color: var(--text-secondary); }
.rd-stat__value { display: block; font-size: var(--text-2xl); font-weight: var(--weight-semibold); margin-top: 4px; }
.rd-stat__hint { display: block; font-size: var(--text-2xs); color: var(--text-muted); margin-top: 2px; }

.rd-section-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.rd-link { font-size: var(--text-xs); color: var(--text-secondary); background: none; border: 0; padding: 0; cursor: pointer; }
.rd-link:hover { color: var(--text-primary); }

.rd-list { display: flex; flex-direction: column; }
.rd-item { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--surface-border); }
.rd-item:last-child { border-bottom: 0; }
.rd-av { width: 36px; height: 36px; border-radius: var(--radius-full); background: var(--surface-sunken); color: var(--text-secondary); display: flex; align-items: center; justify-content: center; font-size: var(--text-xs); font-weight: var(--weight-medium); flex: none; }
.rd-item__main { flex: 1; min-width: 0; }
.rd-item__name { font-size: var(--text-sm); font-weight: var(--weight-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rd-item__sub { font-size: var(--text-2xs); color: var(--text-muted); }
.rd-item__meta { font-size: var(--text-2xs); color: var(--text-muted); flex: none; }
.rd-item--tappable { cursor: pointer; }
.rd-item--tappable:hover { background: var(--surface-sunken); }
.rd-chevron { color: var(--text-muted); flex: none; }

.rd-tag { font-size: var(--text-2xs); padding: 3px 8px; border-radius: var(--radius-full); background: var(--surface-sunken); color: var(--text-secondary); flex: none; }
.rd-tag--delivery { background: var(--color-info-bg); color: var(--color-info); }

/* round add button + search field (visitors) */
.rd-add { width: 44px; height: 44px; border-radius: var(--radius-full); background: var(--accent); color: var(--accent-on); border: 0; display: flex; align-items: center; justify-content: center; cursor: pointer; flex: none; transition: background var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out); }
.rd-add:hover { background: var(--accent-hover); }
.rd-add:active { transform: scale(0.9); background: var(--gray-700); }
/* Transparent border at rest so focus can wrap the WHOLE field in a black
   border with no layout shift — matches .search-input. */
.rd-search { display: flex; align-items: center; gap: 8px; background: var(--surface-raised); border: 1px solid var(--surface-border); border-radius: var(--radius-lg); padding: 0 14px; min-height: 48px; color: var(--text-muted); }
.rd-search:focus-within { border-color: var(--accent); box-shadow: none; }
.rd-search input {
  /* appearance:none kills the native type="search" chrome (the inset inner
     border/glow that showed on mobile when tapped). */
  -webkit-appearance: none; appearance: none;
  border: 0; outline: none; box-shadow: none; background: transparent;
  flex: 1; height: 38px; font-size: var(--text-sm); color: var(--text-primary);
}
.rd-search input::-webkit-search-decoration,
.rd-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.rd-icon-btn { border: 0; background: transparent; color: var(--text-muted); padding: 6px; cursor: pointer; border-radius: var(--radius-sm); flex: none; }
.rd-icon-btn:hover { color: var(--color-danger); background: var(--surface-sunken); }
.rd-avatar-lg { width: 64px; height: 64px; border-radius: var(--radius-full); background: var(--surface-sunken); color: var(--text-secondary); display: flex; align-items: center; justify-content: center; font-size: var(--text-xl); font-weight: var(--weight-medium); margin: 0 auto 8px; }

/* admin user cards (mobile-first, replaces the users table) */
.user-card { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--surface-border); }
.user-card:last-child { border-bottom: 0; }
.user-card.is-suspended { opacity: 0.55; }
.user-card .rd-item__main { flex: 1; min-width: 0; }
.user-card--tappable { cursor: pointer; }
.user-card--tappable:hover { background: var(--surface-sunken); }
.user-card--tappable .rd-chevron { display: inline-flex; color: var(--text-muted); transform: rotate(-90deg); flex: none; }

/* Household card on the resident Profile (code to share + add member). */
/* Profile slots collapse when empty so a gap-based stack doesn't leave a void
   (e.g. install hidden, or a member's household rendered lower down). */
#household-slot:empty,
#household-slot-bottom:empty,
#install-slot:empty { display: none; }
.hh-code {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
  padding: 12px 14px;
}
.hh-code__key { display: inline-flex; color: var(--text-secondary); flex: none; }
.hh-code__main { flex: 1; min-width: 0; }
.hh-code__label { font-size: var(--text-2xs); color: var(--text-secondary); }
.hh-code__value { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: var(--weight-semibold); letter-spacing: 3px; color: var(--text-primary); }
.hh-code__btn {
  width: 34px; height: 34px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted);
}
.hh-code__btn:hover { background: var(--surface-raised); color: var(--text-primary); }

/* Super-admin: users grouped under their estate (collapsible accordion). */
.estate-group {
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-xl);
  /* No overflow clip: a kebab menu on a row inside a search-result section must
     be able to escape the card. (The old accordion needed the clip; it's gone.)
     Inner content is padded, so the rounded corners still read cleanly. */
  margin-bottom: 10px;
}
.estate-head {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-primary);
}
button.estate-head:hover { background: var(--surface-sunken); }
.estate-group.is-open button.estate-head { background: var(--surface-sunken); }
.estate-head__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  color: var(--text-primary);
}
.estate-group.is-open .estate-head__icon { background: var(--surface-raised); }
.estate-head__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.estate-head__name {
  font-size: var(--text-base); font-weight: var(--weight-semibold);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.estate-head__sub { font-size: var(--text-xs); color: var(--text-secondary); }
.estate-head__chev { flex: none; color: var(--text-muted); display: inline-flex; transition: transform var(--duration-base) var(--ease-out); }
.estate-group.is-open .estate-head__chev { transform: rotate(180deg); }
.estate-body { padding: 0 16px 8px; border-top: 1px solid var(--surface-border); }
@media (prefers-reduced-motion: reduce) { .estate-head__chev { transition: none; } }

/* Drill-in card: the estate row is now a button that opens a scoped inner
   screen rather than an accordion. Carries the .estate-head flex layout since
   it IS the tappable control. */
.estate-group--card {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 14px 16px; text-align: left; cursor: pointer;
  color: var(--text-primary);
  transition: background var(--duration-fast);
}
.estate-group--card:hover,
.estate-group--card:active { background: var(--surface-sunken); }
.estate-head__chev--go { transition: none; } /* points right; never rotates */
.role-pill { font-size: var(--text-2xs); padding: 3px 9px; border-radius: var(--radius-full); text-transform: capitalize; flex: none; }
.role-pill--resident { background: var(--surface-sunken); color: var(--text-secondary); }
.role-pill--security { background: var(--color-info-bg); color: var(--color-info); }
.role-pill--admin { background: transparent; color: var(--text-primary); border: 1px solid var(--surface-border-strong); }
.pending-zone { background: var(--color-warning-bg); border-color: var(--color-warning-border); }

/* audit log cards (replaces the logs table) */
/* Audit log — timeline of entries: a rail with a per-action dot + connecting
   line, and a soft card beside it (matches the Figma). */
.log-list { display: flex; flex-direction: column; }
.log-entry { display: flex; gap: 12px; padding-bottom: 14px; }
.log-entry__rail { position: relative; flex: none; width: 34px; display: flex; justify-content: center; }
.log-entry:not(:last-child) .log-entry__rail::after {
  content: ""; position: absolute; left: 50%; top: 36px; bottom: -14px;
  width: 2px; transform: translateX(-50%); background: var(--surface-border);
}
.log-entry__dot {
  width: 34px; height: 34px; border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center; z-index: 1;
  background: var(--surface-raised); border: 1px solid var(--surface-border);
  color: var(--text-secondary);
}
.log-entry__dot .ico { width: 17px; height: 17px; }
.log-entry__dot--allow { background: var(--gray-1000); border-color: var(--gray-1000); color: #fff; }
/* Denials were emitting this class with no rule behind it, so a refused scan
   looked identical to a neutral entry in the audit log — the one row an admin
   is scanning the list for. Filled like the allow dot so it reads at a glance. */
.log-entry__dot--deny { background: var(--color-deny); border-color: var(--color-deny); color: #fff; }
.log-entry__card {
  flex: 1; min-width: 0;
  background: var(--surface-raised); border: 1px solid var(--surface-border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card); padding: 14px;
}
.log-entry__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.log-entry__action { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--text-primary); word-break: break-all; }
.log-entry__by { font-size: var(--text-xs); color: var(--text-muted); margin-top: 2px; }
.log-entry__ctx { margin-top: 10px; background: var(--surface-sunken); border-radius: var(--radius-md); padding: 10px 12px; font-size: var(--text-xs); color: var(--text-secondary); line-height: 1.5; }
.log-entry__meta { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-top: 10px; font-size: var(--text-2xs); color: var(--text-muted); font-family: var(--font-mono); }

/* Audit log toolbar — search stacked over a row of filter pills, no box. */
.log-toolbar { flex-direction: column; align-items: stretch; gap: 12px; padding: 0; background: transparent; border: 0; }
.log-filters { display: flex; flex-wrap: wrap; gap: 8px; }
.log-filters .field__input--inline,
.log-filters .filter-date {
  height: 40px; min-height: 40px; width: auto;
  border-radius: var(--radius-full);
  border: 1px solid var(--surface-border);
  background-color: var(--surface-raised);
  padding: 0 14px; font-size: var(--text-sm); color: var(--text-primary);
}
/* Compact filter pills: keep the select chevron clear of the text. */
.log-filters select.field__input--inline {
  padding-right: 36px;
  background-position: right 12px center;
}
.log-filters .filter-date { display: inline-flex; align-items: center; gap: 6px; }
.log-filters .filter-date .field__input--inline { height: auto; min-height: 0; border: 0; padding: 0; background: transparent; border-radius: 0; }
.log-filters .filter-date__label { font-size: var(--text-xs); color: var(--text-muted); text-transform: none; font-weight: var(--weight-medium); }

/* Passes list — compact tappable rows; the full ticket (QR, share, revoke)
   opens as a sheet on tap. */
.rd-list-label {
  margin: 4px 0 8px;
  font-size: var(--text-2xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.pass-rows { display: flex; flex-direction: column; gap: 8px; }
.pass-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-lg);
  padding: 13px 14px;
  cursor: pointer;
  transition: transform var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.pass-row:active:not(.pass-row--past) { transform: scale(0.98); background: var(--surface-sunken); }
.pass-row__chip {
  width: 40px; height: 40px;
  border-radius: var(--radius-md);
  background: var(--gray-1000);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  flex: none;
}
.pass-row__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pass-row__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pass-row__sub { font-size: var(--text-2xs); color: var(--text-secondary); font-family: var(--font-mono); }
.pass-row__sub--plain { font-family: var(--font-sans); }
.pass-row__chev { flex: none; color: var(--text-muted); display: inline-flex; transform: rotate(-90deg); }
.pass-row--past { opacity: 0.75; cursor: default; }

/* Segmented toggle (Passes / Events) — one control, two options. */
.segmented { display: flex; gap: 4px; background: var(--surface-sunken); border-radius: var(--radius-lg); padding: 3px; }
.segmented__opt {
  flex: 1; border: none; background: none; cursor: pointer;
  padding: 7px 0; border-radius: var(--radius-md);
  font-size: var(--text-sm); color: var(--text-secondary);
  transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.segmented__opt.is-active { background: var(--gray-1000); color: #fff; font-weight: var(--weight-medium); }

/* "Says they paid" — a suspended resident's CLAIM, awaiting the admin's check
   against their bank. Never implies GatePass confirmed a payment. */
.paid-chip {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 5px; padding: 2px 8px;
  background: var(--color-success-bg); color: var(--color-success);
  border-radius: var(--radius-full);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  /* The row's middle column is narrow (badge + chevron + kebab sit beside it),
     so without this the label wraps to two lines and bursts out of the card. */
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.paid-chip .ico { flex-shrink: 0; }

/* Suspended screen: why, where to pay, and the reported-payment state. */
.susp-reason {
  background: var(--color-warning-bg);
  border-radius: var(--radius-lg);
  padding: 10px 12px;
  text-align: left;
}
.susp-reason__label {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-warning);
}
.susp-reason__text { font-size: var(--text-sm); color: var(--color-warning); margin-top: 2px; }

.susp-pay {
  background: var(--surface-sunken);
  border-radius: var(--radius-lg);
  padding: 12px;
  text-align: left;
  margin-top: 10px;
}
.susp-pay__label {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary);
  margin-bottom: 8px;
}
.susp-pay__rows { display: flex; flex-direction: column; gap: 7px; font-size: var(--text-sm); }
.susp-pay__rows > div { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.susp-pay__rows > div > span:first-child { color: var(--text-secondary); }
.susp-pay__acct {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--text-base);
  font-weight: var(--weight-semibold); color: var(--text-primary);
}

.susp-claimed {
  display: flex; align-items: flex-start; gap: 9px; text-align: left;
  background: var(--color-success-bg);
  border-radius: var(--radius-lg);
  padding: 11px 12px;
  margin-top: 10px;
}
.susp-claimed__icon { color: var(--color-success); flex-shrink: 0; margin-top: 1px; }
.susp-claimed__title { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-success); }
.susp-claimed__sub { font-size: var(--text-xs); color: var(--color-success); margin-top: 2px; }

/* ---------------------------------------------------------------------------
   Settings rows (Profile). The Profile is a MENU, not a form: each row shows
   its current value and opens a sheet. Keeps the page short however much we
   add, and every future setting is one more row rather than more scroll.
   --------------------------------------------------------------------------- */
.set-page { padding-bottom: 32px; }

/* Avatar stacked above the name, both centred — the identity block reads as a
   header rather than a list row, and matches the previous Profile design. */
.set-head {
  display: flex; flex-direction: column; align-items: center;
  gap: 12px; padding: 8px 2px 4px; text-align: center;
}
.set-head__name { font-size: var(--text-lg); font-weight: var(--weight-semibold); }
.set-head__sub {
  font-size: var(--text-sm); color: var(--text-secondary);
  margin-top: 2px; text-transform: capitalize;
}

.set-label {
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin: 18px 4px 7px;
}

.set-group {
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.set-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 13px 14px;
  background: none; border: 0; text-align: left;
  border-bottom: 1px solid var(--surface-border);
  cursor: pointer; color: inherit; font: inherit;
}
.set-row:last-child { border-bottom: 0; }
.set-row:active { background: var(--surface-sunken); }
/* Read-only rows (email, estate, iOS install hint): nothing to open. */
.set-row--locked { cursor: default; }
.set-row--locked:active { background: none; }

.set-row__icon { color: var(--text-secondary); display: flex; flex-shrink: 0; }
.set-row__main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.set-row__label { font-size: var(--text-base); }
.set-row__value {
  font-size: var(--text-xs); color: var(--text-secondary);
  /* Up to two lines, then ellipsis. A single clamped line looked tidy on a
     wide screen but destroyed the content on a narrow one: a real estate name
     plus its address needs ~565px, so a 320px phone showed the name and threw
     the whole address away. Two lines keeps the row bounded while letting
     small screens breathe; wide screens still fit on one line naturally. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.4;
}
.set-row__value--muted { color: var(--text-muted); }
.set-row__chev {
  color: var(--text-muted); flex-shrink: 0; display: flex;
  transform: rotate(-90deg); /* the shared chevron points down; a row points right */
}
.set-row__lock { color: var(--text-muted); flex-shrink: 0; display: flex; }

.set-footer { display: flex; flex-direction: column; gap: 6px; margin-top: 22px; }
.set-danger {
  background: none; border: 0; cursor: pointer;
  color: var(--color-danger);
  font-size: var(--text-sm);
  /* Min 44px tall: the iOS minimum touch target. At 10px padding this came out
     40px, small enough to fat-finger next to Sign out. */
  min-height: 44px; padding: 10px;
}

/* A household MEMBER's read-only context: not a setting, so no row, no
   chevron — just a quiet note at the very bottom of the page. */
.set-note {
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--surface-sunken);
  border-radius: var(--radius-lg);
  padding: 12px 13px;
  margin-top: 18px;
}
.set-note__icon { color: var(--text-muted); flex-shrink: 0; display: flex; margin-top: 1px; }
.set-note p {
  margin: 0; font-size: var(--text-xs);
  color: var(--text-secondary); line-height: 1.55;
}

/* Expandable stack trace on the super-admin Errors screen. */
.err-stack summary { cursor: pointer; font-size: var(--text-2xs); color: var(--text-secondary); margin-top: 6px; }
.err-stack pre {
  white-space: pre-wrap; word-break: break-word;
  font-size: var(--text-2xs); color: var(--text-secondary);
  background: var(--surface-sunken); padding: 8px;
  border-radius: var(--radius-md); margin-top: 6px; overflow-x: auto;
}

/* One consistent modal footer everywhere: full-width, stacked action button(s)
   so the primary action is centred and positioning matches across all modals.
   (The canonical .danger-zone component lives further down and is shared by the
   profile self-delete and the edit-org modal.) */
.modal__body .row-end { flex-direction: column; align-items: stretch; }
.modal__body .row-end .btn { width: 100%; }

/* Read-only / disabled fields must LOOK non-editable so users don't try to
   change them (e.g. the org-owned Estate fields on the profile screen). */
.field__input:disabled,
.field__input[readonly] {
  background-color: var(--gray-50);
  border-color: var(--surface-border);
  color: var(--text-secondary);
  -webkit-text-fill-color: var(--text-secondary); /* iOS keeps disabled text legible */
  cursor: not-allowed;
  opacity: 1;
}
textarea.field__input {
  height: auto;
  padding: 10px 12px;
  resize: vertical;
  min-height: 80px;
  line-height: 1.5;
}
.field__input:focus-visible,
.field__input:focus {
  outline: none;
  background: var(--surface-raised);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0,0,0,0.06);
}
.field__input[aria-invalid="true"] {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 3px rgba(239,68,68,0.1);
}
.field__input--inline {
  height: 32px;
  width: auto;
  min-width: 128px;
  font-size: var(--text-sm);
  text-transform: capitalize;
}
.field__error { font-size: var(--text-xs); color: var(--color-danger); }
.field__hint  { font-size: var(--text-xs); color: var(--text-muted); }

/* Mobile overrides moved to the END of this file so they reliably
   win the cascade against the base rules. See the bottom of the file. */

/* ============================================================
   BADGES — 8px radius (NO full pills per spec)
   ============================================================ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.01em;
  text-transform: capitalize;
  white-space: nowrap;
  border: 1px solid transparent;
  line-height: 1.3;
}
.badge::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
/* Status badge colors (neutral chip + colored dot) live in the dot-only
   block further down — these variants are intentionally not filled here.
   See ".badge--active" etc. in the v2 polish section. */

.badge--role::before { display: none; }
.badge--role {
  text-transform: none;
  padding: 4px 12px;
  background: var(--surface-sunken);
  color: var(--text-secondary);
  border-color: transparent;
}

/* ============================================================
   MODAL
   ============================================================ */
/* v3 — bottom sheet on mobile: dim backdrop, panel slides up from the bottom
   edge with rounded top corners. Recentered into a dialog on desktop below. */
.modal-backdrop {
  position: fixed; inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: end center;
  padding: 0;
  background: rgba(0,0,0,0.45);
  backdrop-filter: blur(3px);
  animation: fade-in var(--duration-base) var(--ease-out);
}
.modal {
  width: 100%; max-width: 560px;
  /* Cap to viewport so tall content (e.g. visitor pass with QR) gets an
     INTERNAL scrollbar instead of scrolling the page and burying the X. */
  max-height: 92dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: 0;
  border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
  box-shadow: var(--shadow-xl);
  outline: none;
  overflow: hidden;
  padding-bottom: env(safe-area-inset-bottom, 0);
  animation: sheet-up var(--duration-slow) var(--ease-out);
}
@keyframes sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
@media (min-width: 1024px) {
  .modal-backdrop { place-items: center; padding: 24px; }
  .modal {
    border-radius: var(--radius-2xl);
    max-height: calc(100dvh - 48px);
    padding-bottom: 0;
    animation: fade-in var(--duration-base) var(--ease-out);
  }
}

/* Centered dialog variant — floats in the middle instead of sliding up as a
   bottom sheet. Used for short confirmations (sign out, delete account,
   report an issue). Overrides the mobile bottom-sheet defaults above. */
.modal-backdrop--center { place-items: center; padding: 24px; }
/* A fixed 24px inset costs 48px of a 320px screen, squeezing form inputs to
   200px. Claw some back on the smallest phones. */
@media (max-width: 380px) {
  .modal-backdrop--center { padding: 14px; }
}
.modal--center {
  max-width: 440px;
  border-radius: var(--radius-2xl);
  max-height: calc(100dvh - 48px);
  padding-bottom: 0;
  animation: modal-pop var(--duration-base) var(--ease-out);
}
@keyframes modal-pop {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .modal--center { animation: fade-in var(--duration-base) var(--ease-out); }
}
.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--surface-border);
  flex-shrink: 0; /* never collapse — close button must stay reachable */
  background: var(--surface-raised);
}
.modal__title { font-size: var(--text-md); font-weight: var(--weight-semibold); }
.modal__close {
  width: 36px; height: 36px; /* 44pt safe area minus chrome — easy tap target */
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  font-size: var(--text-xl);
  color: var(--text-muted);
  line-height: 1;
  flex-shrink: 0;
}
.modal__close:hover { background: var(--surface-sunken); color: var(--text-primary); }
.modal__body {
  padding: 20px;
  /* Scroll the body within the modal, keep the header pinned. */
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* ============================================================
   KEBAB MENU — row-level overflow menu used in admin Users to
   collapse Suspend / Archive / Delete onto a single button.
   ============================================================ */
.kebab { position: relative; display: inline-block; }
.kebab__trigger {
  background: transparent;
  border: 1px solid transparent;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  cursor: pointer;
}
.kebab__trigger:hover {
  background: var(--surface-sunken);
  color: var(--text-primary);
  border-color: var(--surface-border);
}
.kebab__menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  min-width: 180px;
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 4px;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kebab__item {
  text-align: left;
  background: transparent;
  border: none;
  padding: 8px 12px;
  font-size: var(--text-sm);
  color: var(--text-primary);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.kebab__item:hover { background: var(--surface-sunken); }
.kebab__item--danger { color: #b91c1c; }
.kebab__item--danger:hover { background: #fef2f2; }

/* Archived row: muted background + struck-through name (sprint 2 polish). */
.row--archived { opacity: 0.55; }
.row--archived td strong { text-decoration: line-through; }

/* Danger zone — used in edit-org modal (and future settings panels) to
   visually quarantine destructive actions from the regular form footer.
   Pattern borrowed from GitHub / Vercel settings: subtle red border,
   small "Danger zone" label, the action button sits to the right of an
   explanation block. */
.danger-zone {
  margin-top: 20px;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-xl);
  background: var(--surface-raised);
}
.danger-zone__label {
  padding: 10px 16px;
  border-bottom: 1px solid var(--surface-border);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: transparent;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
.danger-zone__body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  padding: 16px;
}
.danger-zone__body strong {
  display: block;
  color: var(--text-primary);
  font-size: var(--text-sm);
  margin-bottom: 2px;
}
.danger-zone__body p {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.4;
}
@media (max-width: 480px) {
  .danger-zone__body { flex-direction: column; align-items: stretch; }
  .danger-zone__body button { width: 100%; }
}

/* ============================================================
   ALERTS — semantic info / warning / danger blocks.
   Replaces the inline-styled .auth__error misuse for warnings
   and destructive context. Keep .auth__error for actual recoverable
   form errors only (red).
   ============================================================ */
.alert {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-size: var(--text-sm);
  line-height: 1.5;
}
.alert strong { font-weight: var(--weight-semibold); }
.alert--warning {
  background: #fff8e1;
  border-color: #ffe7a3;
  color: #7a5b00;
}
.alert--danger {
  background: #fff0f0;
  border-color: #fca5a5;
  color: #7a1d1d;
}

/* ============================================================
   TOASTS
   ============================================================ */
/* Top-center banner stack. Slides down from under the status bar; clears the
   notch via safe-area inset. Sits above the nav and modals. */
.toast-root {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  left: 50%;
  transform: translateX(-50%);
  z-index: calc(var(--z-modal) + 10);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: max-content;
  max-width: min(420px, calc(100vw - 24px));
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 13px 15px;
  border-radius: 14px;
  background: var(--gray-900);
  color: #fff;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: 1.35;
  cursor: pointer;
  pointer-events: auto;
  animation: toast-in var(--duration-base) var(--ease-out);
}
.toast__icon {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  background: rgba(255, 255, 255, 0.12);
  color: #e5e5e5;
}
.toast__msg { min-width: 0; }
.toast--success .toast__icon { background: rgba(52, 211, 153, 0.16); color: #34d399; }
.toast--error   .toast__icon { background: rgba(248, 113, 113, 0.16); color: #f87171; }

.toast--leaving { opacity: 0; transform: translateY(-8px); transition: opacity var(--duration-base), transform var(--duration-base) var(--ease-out); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(-16px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .toast--leaving { transform: none; }
}

/* ============================================================
   PANEL (analytics + section containers)
   ============================================================ */
.panel {
  padding: 20px;
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-xl);
}
.panel__title {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}
/* ============================================================
   USER AREA (header)
   ============================================================ */
.user-area {
  display: flex; align-items: center; gap: 10px;
}

.user-area__role {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-transform: capitalize;
  padding: 3px 8px;
  border-radius: var(--radius-sm);
  color: var(--brand);
  background: var(--brand-light);
  border: 1px solid rgba(24,36,66,0.12);
}
.user-area__email {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-area__logout {
  height: 34px;
  padding: 0 14px;
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  transition: all var(--duration-fast);
  white-space: nowrap;   /* never wrap "Sign out" to two lines */
  flex-shrink: 0;
}
.user-area__logout:hover {
  background: var(--surface-sunken);
  border-color: var(--surface-border);
  color: var(--text-primary);
}

/* ============================================================
   LAYOUT HELPERS
   ============================================================ */
.form { width: 100%; }
.stack      { display: flex; flex-direction: column; gap: 16px; }
.stack-lg   { display: flex; flex-direction: column; gap: 32px; }
.row        { display: flex; gap: 12px; flex-wrap: wrap; }
.row-between{ justify-content: space-between; }
.row-center { align-items: center; }
.row-end    { justify-content: flex-end; }
.flex-1     { flex: 1 1 0%; }

/* ============================================================
   MOBILE OVERRIDES — at end of file so they win the cascade.
   Apple HIG: 44pt touch targets. iOS Safari: 16px+ inputs to avoid
   the focus-zoom that wrecks form UX.
   ============================================================ */
@media (max-width: 767px) {
  /* iOS focus-zoom guard: every input must be ≥16px to avoid auto-zoom */
  .field__input,
  textarea.field__input,
  .search-input input { font-size: 16px; }
  .field__input--code { font-size: 22px; }

  /* Touch targets */
  .btn            { height: 44px; padding: 0 18px; }
  .btn--sm        { height: 40px; padding: 0 14px; font-size: var(--text-sm); }
  .btn--lg        { height: 48px; padding: 0 20px; }
  .user-area__logout { height: 40px; padding: 0 14px; }

  /* Touch targets: everything tappable hits the 44px floor on mobile
     (DESIGN.md §1.5 / Apple HIG). These were 32–40px — under the floor on
     the two most-tapped controls (kebab menu + inline role select). */
  .field__input--inline { height: 44px; }
  .kebab__trigger { width: 44px; height: 44px; }
}

/* ==========================================================================
   MOBILE BOTTOM TAB BAR (v2 mobile-first nav — replaces hamburger drawer)
   ========================================================================== */
/* v3 — floating capsule. Centered, hugs its content, lifts off the page.
   The active tab morphs into a black pill and its label liquid-expands; the
   whole capsule breathes as labels open/close for the fluid feel. */
.bottom-nav {
  display: none; /* desktop hides it; mobile @media below shows it */
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: var(--z-bottom-nav);
  width: max-content;
  max-width: calc(100% - 20px);
  background: var(--surface-raised);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-nav);
  padding: 6px;
  height: auto;
  gap: 4px;
  align-items: center;
  justify-content: center;
}
.bottom-nav__item {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 14px;
  background: transparent;
  border: 0;
  text-decoration: none;
  color: var(--text-muted);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  border-radius: var(--radius-full);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--duration-base) var(--ease-out),
    color var(--duration-base) var(--ease-out);
}
.bottom-nav__item:hover { color: var(--text-secondary); text-decoration: none; }
.bottom-nav__item[aria-current="page"] {
  background: var(--gray-1000);
  color: var(--accent-on);
}
.bottom-nav__label {
  display: inline-block;
  max-width: 0;
  margin-left: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--text-sm);
  line-height: 1;
  transition:
    max-width var(--duration-nav) var(--ease-fluid),
    margin-left var(--duration-nav) var(--ease-fluid),
    opacity var(--duration-base) var(--ease-out);
}
.bottom-nav__item[aria-current="page"] .bottom-nav__label {
  max-width: 120px;
  margin-left: 8px;
  opacity: 1;
}

/* Outline nav glyphs (SVG) — sized per context; stroke inherits link color
   (white on the active bottom-nav pill, gray otherwise). */
.ico { flex: none; }
.nav-glyph { width: 20px; height: 20px; flex: none; display: block; }
.app-nav__link .nav-glyph { opacity: 0.75; transition: opacity var(--duration-fast); }
.app-nav__link:hover .nav-glyph,
.app-nav__link[aria-current="page"] .nav-glyph { opacity: 1; }
.bottom-nav__item .nav-glyph { width: 24px; height: 24px; }

/* When the tab bar is visible, give the main scroll area headroom so the
   last row isn't covered by the bar (and account for the safe area). */
@media (max-width: 1023px) {
  .bottom-nav { display: flex; }
  .app-main {
    padding-bottom: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 0px) + 16px) !important;
  }
  /* The actual scroll container is .container (it holds #screen-root). The
     padding on .app-main sits OUTSIDE that scroll area, so the last element
     could still slip under the fixed bar. Put headroom on the scroller too. */
  .container {
    padding-bottom: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 0px) + 24px);
  }
  /* Hide the hamburger button — tab bar is the primary nav now. The "More"
     tab still triggers it programmatically for any overflow items. */
  .app-header__menu-btn { display: none !important; }
}

/* ==========================================================================
   v2 MOBILE POLISH — fixes from real-device screenshots
   ========================================================================== */

@media (max-width: 1023px) {
  /* Show a compact brand mark + current screen title on the left. The
     bottom bar highlights tabs, but OVERFLOW screens (Organizations, Audit
     Logs, Profile…) only light up "More" — so the title is the only thing
     telling the user which screen they're on. Slim, single-line, truncates. */
  .app-header__mark {
    display: inline-flex;
    align-items: center;
    width: 26px; height: 26px;
    flex-shrink: 0;
    color: var(--text-primary);
  }
  .app-header__mark svg { width: 26px; height: 26px; }
  .app-header__title {
    display: block;
    font-size: 16px;
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  .app-header { padding-left: var(--space-4); gap: 8px; }

  /* "Sign out" was reading as plain text — give it a real button affordance. */
  .user-area__logout {
    border: 1px solid var(--surface-border);
    background: var(--surface-raised);
    color: var(--text-primary);
    border-radius: var(--radius-md);
    font-weight: var(--weight-medium);
    font-size: 13px;
  }
  .user-area__logout:hover { background: var(--surface-sunken); }

  /* Page-header rows ("Welcome back…" + CTA, "Passes" + Issue button, etc.)
     should ALWAYS stack on mobile — current behavior wraps inconsistently
     based on heading length, producing left- vs right-aligned CTAs.
     Targeting `.row-between` inside a section is broad but the only place
     it lives is at the top of each screen. */
  section > .row.row-between {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  section > .row.row-between .btn { align-self: flex-start; }
}

/* ==========================================================================
   v3 ADMIN-SCREEN POLISH (from admin screenshots)
   ========================================================================== */
@media (max-width: 1023px) {

  /* Admin Dashboard's page-header uses `<div class="stack">` wrapper (not
     `<section>`), so the section> selector didn't catch it. Match both. */
  .stack > .row.row-between,
  section > .row.row-between {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  .stack > .row.row-between .row,
  section > .row.row-between .row {
    flex-wrap: wrap;
    gap: var(--space-2);
  }
  .stack > .row.row-between .btn,
  section > .row.row-between .btn { flex: 1 1 auto; justify-content: center; }

  /* Stat-card grid: ensure 2-up on phones. The auto-fill min was being
     overridden somewhere — force explicit 2-column grid up to 640px. */
  .grid-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
  }

  /* Admin Users role <select> — compact inline control, but still on the
     44px touch floor (it's a primary tappable control on this screen). */
  .table .field__input--inline,
  .table select.field__input {
    height: 44px;
    padding: 0 28px 0 10px;
    font-size: 13px;
    min-width: 96px;
    width: auto;
  }
}

/* ==========================================================================
   v4 ADMIN POLISH (from screenshots: stretched icons, overflowing filters,
   rainbow stat icons, vibrant badges)
   ========================================================================== */

/* Force every SVG inside a button to keep its declared size — the v29
   `flex: 1 1 auto` made unsized SVGs stretch to fill the flex track,
   producing the giant Open-scanner QR pattern. */
.btn svg { flex: 0 0 auto; width: 16px; height: 16px; display: inline-block; }
.btn--sm svg { width: 14px; height: 14px; }
.btn--lg svg { width: 18px; height: 18px; }

/* Audit log ALLOW/DENY badges: keep the dot, drop the loud bg colors.
   Color survives in the dot + text — no fill. */
.badge--active,
.badge--revoked,
.badge--expired,
.badge--created,
.badge--pending {
  background: var(--surface-raised);
  border-color: var(--surface-border);
}
.badge--active  { color: var(--text-primary); }
.badge--active::before  { background: #16a34a; }
.badge--revoked { color: var(--text-primary); }
.badge--revoked::before { background: #dc2626; }
.badge--scanned { color: var(--text-muted); }
.badge--scanned::before { background: var(--gray-400); }
.badge--expired { color: var(--text-muted); }
.badge--expired::before { background: var(--gray-500); }
.badge--created { color: var(--text-primary); }
.badge--created::before { background: var(--blue-500); }
.badge--pending { color: var(--text-secondary); }
.badge--pending::before { background: var(--gray-500); }

/* Audit log filter toolbar: stack on mobile so it doesn't horizontally
   scroll. Search goes full-width, selects share row 2. */
@media (max-width: 767px) {
  /* Generic fallback: any inline field/select in a row should shrink */
  .row select.field__input { min-width: 0; }
}

/* The logs screen's toolbar is a plain .table-toolbar. */
@media (max-width: 767px) {
  .table-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }
  .table-toolbar .search-input { flex: 1 1 100%; }
  .table-toolbar select.field__input { flex: 1 1 calc(50% - 4px); min-width: 0; }
  /* "From" / "To" share a row — each takes half, label + compact picker. */
  .table-toolbar .filter-date { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .table-toolbar .filter-date .field__input--inline { flex: 1 1 auto; width: auto; min-width: 0; }
}

/* ==========================================================================
   v5: TABLES BECOME CARDS ON MOBILE
   The Passes / Visitors tables had 5 columns that overflowed horizontally
   on a 375px viewport — actions (Show pass / Revoke) lived offscreen,
   requiring a swipe to reach. On mobile each row now renders as a stacked
   card with visitor + code/status on one line and actions on their own row.
   ========================================================================== */
@media (max-width: 767px) {
  .table {
    display: block;
    border-collapse: separate;
    border-spacing: 0;
  }
  .table thead { display: none; }   /* column headers don't make sense once stacked */
  .table tbody { display: flex; flex-direction: column; gap: 10px; }
  .table tr {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px;
    background: var(--surface-raised);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-lg);
  }
  .table tr:hover td { background: transparent; }
  .table td {
    display: block;
    padding: 0;
    border-bottom: 0;
  }
  /* In card mode `.table td` (0,1,1) was overriding `.hide-mobile` (0,1,0),
     so hidden columns leaked back in as stacked cells — including empty
     "—" placeholders (e.g. a blank visitor Purpose, empty audit Decision/
     Reason). Keep them hidden, and hide explicitly-empty cells too. */
  .table td.hide-mobile,
  /* First td (visitor name) is the headline */
  .table td:first-child {
    font-size: 15px;
    font-weight: var(--weight-semibold);
  }
  /* Inline pass code + status badge: render side-by-side via flex on td#2-3 */
  .table td:nth-child(2),
  .table td:nth-child(3) {
    display: inline-flex;
    margin-right: 8px;
    vertical-align: middle;
  }
}

/* Pass countdown sub-label under the status badge */
.pass-countdown {
  margin-top: 4px;
  font-size: var(--text-2xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

/* ==========================================================================
   L4: SAFE-AREA INSETS on more surfaces (landscape iPhone, edge displays)
   ========================================================================== */
.app-header {
  padding-left:  max(var(--space-4), env(safe-area-inset-left, 0px));
  padding-right: max(var(--space-4), env(safe-area-inset-right, 0px));
  padding-top:   env(safe-area-inset-top, 0px);
}
.app-main {
  padding-left:  env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}
.modal {
  padding-left:  max(16px, env(safe-area-inset-left, 0px));
  padding-right: max(16px, env(safe-area-inset-right, 0px));
}

/* Forgot-password "if an account exists…" info card */
.auth__info {
  display: flex;
  gap: 8px;
  padding: 12px 14px;
  background: var(--surface-sunken);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.5;
}

/* ==========================================================================
   Password show/hide toggle
   ========================================================================== */
.pw-toggle { position: relative; display: block; }
/* The input is nested inside .pw-toggle, so it loses .field's flex-stretch —
   force it back to full width so it matches the other fields and the eye
   button sits inside it. */
.pw-toggle .field__input { width: 100%; }
.pw-toggle__btn {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: var(--radius-sm);
  -webkit-tap-highlight-color: transparent;
}
.pw-toggle__btn:hover { color: var(--text-primary); background: var(--surface-sunken); }
.pw-toggle__btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}
.pw-toggle__btn svg { width: 18px; height: 18px; display: block; }
.pw-toggle__btn i { font-size: 16px; }
.auth__code {
  display: block;
  margin: 0;
  padding: 12px 14px;
  background: var(--surface-sunken);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-primary);
  white-space: pre-wrap;
  word-break: break-all;
  overflow-x: auto;
}

/* Header user-area: org name pill (multi-tenancy v49+) */
.user-area__org {
  font-size: 12px;
  color: var(--text-secondary);
  font-weight: var(--weight-medium);
  padding: 6px 14px;
  background: var(--surface-sunken);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-area__org:empty { display: none; }
@media (max-width: 767px) {
  .user-area__org { max-width: 110px; font-size: var(--text-2xs); padding: 3px 8px; }
}
/* On mobile the header now carries a brand mark + screen title on the left,
   so the right side must stay slim. The role chip is redundant (the app is
   already role-scoped, and super-admin has the yellow banner) — hide it and
   keep just the org pill + Sign out. */
@media (max-width: 1023px) {
  .user-area { gap: 8px; }
  .user-area__role { display: none; }
}

/* Unified brand logo — single source: assets/icons/icon-192.png. The mark is
   self-contained (dark rounded square with the key), so the container stays
   transparent with no padding — no white frame around it. */
.brand-img { width: 100%; height: 100%; object-fit: contain; display: block; }
.brand-mark { background: transparent !important; box-shadow: none !important; overflow: hidden; padding: 0; }

/* Field requirement markers. Optional fields carry a muted "(optional)" at all
   times. Required fields carry a "Required" marker that is HIDDEN until the
   user tries to submit with the field empty (see requiredHints.js): the form
   reads clean, and the red nudge only appears when it's actually useful, then
   clears as they type. */
.field__req {
  display: none;                 /* revealed only on a flagged field, below */
  font-size: var(--text-2xs);
  font-weight: var(--weight-medium);
  color: var(--color-danger);
  margin-left: 4px;
  vertical-align: 1px;
}
.field-invalid .field__req { display: inline; }
.field.field-invalid .field__input {
  border-color: var(--color-danger);
}
.field.field-invalid .field__input:focus {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 3px var(--color-danger-bg);
}
