/* Bina Board — Super Admin (PortalAdmin) portal theme.
   Logical properties throughout so this survives an RTL pass unchanged
   (02-AGENT-DESIGN.md §4) — `ur` and `ar` render right-to-left via
   `dir="rtl"` on <html> (templates/layouts/base.html), driven by
   core.middleware.UserLanguageMiddleware. */

*, *::before, *::after { box-sizing: border-box; }

/* App-wide robustness for the `hidden` attribute. Several progressive-
   enhancement scripts (static/js/widgets.js, ticket_live.js, ticket_form.js,
   expense_form.js, push.js, resident_dashboard.js) show/hide elements with
   `el.hidden = true/false`, relying on the UA rule `[hidden] { display: none }`.
   But a component that also declares `display` in an author rule (e.g.
   `.confirm-backdrop { display: flex }`, `.dropzone-body`/`.dropzone-file`)
   BEATS that UA rule, so `hidden` no longer hides it — a `position: fixed`
   overlay then keeps intercepting every click on the page (the vendor-detail
   "page freeze", issue #292), and inline widgets show stale/empty state.
   Making this an `!important` author rule guarantees `hidden` always wins,
   regardless of any current or future component's own `display`. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--color-text);
  background: var(--color-surface);
  -webkit-font-smoothing: antialiased;
}

/* `ur`/`ar` (02-AGENT-DESIGN.md §3): IBM Plex Sans has no Arabic-script
   glyphs, so without this every Urdu/Arabic string silently falls through
   to the browser/OS default font instead of the brand typeface. */
[dir="rtl"] body {
  font-family: "IBM Plex Sans Arabic", var(--font-sans);
}

a { color: inherit; }

h1, h2, h3, p { margin: 0; }

button, input, select { font-family: inherit; font-size: inherit; }

:focus-visible {
  outline: 2px solid var(--color-amber);
  outline-offset: 2px;
}

/* Subtle scrollbar polish, shared by every scrollable surface. */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--color-hairline);
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: #C7CBD2; }

/* ---------- Auth shell (login) ---------- */

.auth-shell {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--space-6);
  background:
    radial-gradient(circle at 15% 20%, rgba(79, 70, 229, 0.14), transparent 42%),
    linear-gradient(160deg, var(--color-ink-900), var(--color-ink) 65%);
}

.auth-card {
  width: 100%;
  max-width: 400px;
  background: var(--color-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-8) var(--space-6);
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-end: var(--space-6);
}

.auth-brand-mark {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  background: var(--color-ink);
  color: #A5B4FC; /* light indigo — the solid accent fails contrast on ink */
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: var(--text-md);
}

.auth-brand-logo {
  width: 36px;
  height: 36px;
  object-fit: contain;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: #fff;
  padding: 2px;
}

/* Project logo shown next to the Bina Board mark on a project subdomain's
   sign-in page (templates/login.html) — bordered so it reads as a
   distinct, second brand mark rather than a replacement for the first. */
.auth-brand-logo-project {
  border: 1px solid var(--color-border, #e2e2e2);
}

.auth-brand-name {
  font-weight: 600;
  font-size: var(--text-lg);
  color: var(--color-ink-900);
}

.auth-brand-sub {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.auth-title {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--color-ink-900);
  margin-block-end: var(--space-1);
}

.auth-subtitle {
  color: var(--color-text-muted);
  margin-block-end: var(--space-6);
}

/* ---------- Form primitives ---------- */

.field {
  margin-block-end: var(--space-4);
}

.label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-900);
  margin-block-end: var(--space-2);
}

.input {
  width: 100%;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  background: var(--color-card);
  color: var(--color-text);
  transition: background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast);
}

/* A multi-line control keeps comfortable padding rather than being squeezed to
   the single-row min-height. */
textarea.input { min-height: 96px; padding: var(--space-3); }

.input:focus {
  background: var(--color-card);
  border-color: var(--color-amber);
  box-shadow: var(--focus-ring);
  outline: none;
}

.field-required {
  color: var(--color-terracotta);
}

.field-error {
  color: var(--color-danger);
  font-size: var(--text-sm);
  margin-block-start: var(--space-1);
}

.field-help {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-block-start: var(--space-1);
}

/* A boolean (checkbox/switch) row inside a form. Replaces the ad-hoc
   inline-styled `<label style="display:flex; …">` pattern so every boolean
   field aligns identically across the app (issue #316):
   <label class="check-field">{{ form.all_day }} {{ form.all_day.label }}</label> */
.check-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-end: var(--space-3);
  font-size: var(--text-base);
  color: var(--color-text);
  cursor: pointer;
}

/* Inside a `.field` wrapper the wrapper already owns the vertical rhythm. */
.field > .check-field { margin-block-end: 0; }

/* A boolean sharing a `.field-grid` row with a regular input: bottom-align
   it and give it the input's 40px control height so the pair reads level. */
.field-align-end { display: flex; align-items: flex-end; }
.field-align-end .check-field { min-block-size: 40px; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: none;
  border-radius: var(--radius-md);
  /* A firm min-height keeps every button on a toolbar row the same height
     whether or not it carries a leading icon, and gives a comfortable
     touch target on the AZ/UAE mobile web (issue #316). */
  min-height: 40px;
  padding: var(--space-2) var(--space-5);
  font-weight: 600;
  font-size: var(--text-base);
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--motion-fast), box-shadow var(--motion-fast), border-color var(--motion-fast), transform var(--motion-fast);
  text-decoration: none;
}

.btn:active { transform: translateY(1px); }

/* Leading/trailing icons inside any button size to the text automatically. */
.btn svg { width: 16px; height: 16px; flex: none; }

/* Size variants (issue #316). Base `.btn` is the default/medium; these give a
   compact action for dense toolbars and a prominent one for hero CTAs, both
   reusing the same min-height rhythm as inputs so controls line up. */
.btn-sm {
  min-height: 32px;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}
.btn-sm svg { width: 14px; height: 14px; }

.btn-lg {
  min-height: 48px;
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-md);
}

/* `pointer-events: none` rather than only dimming — a disabled `<button>`
   already ignores clicks natively, but a link-styled `.btn` (`<a>`) with
   `aria-disabled="true"` has no such native behavior and would otherwise
   still navigate/hover on click. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

.btn.is-loading {
  cursor: progress;
  opacity: 0.75;
}

.btn.is-loading::after {
  content: "";
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-inline-end-color: transparent;
  animation: btn-spin 0.6s linear infinite;
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* Primary CTA — a soft top-lit ink gradient + inset highlight so the main
   action reads as a tactile, pressable control rather than a flat block
   (issue #316). */
.btn-primary {
  background: linear-gradient(180deg, var(--color-ink-700), var(--color-ink));
  color: #fff;
  width: 100%;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), var(--shadow-sm);
}

.btn-primary:hover {
  background: linear-gradient(180deg, #3B3E46, var(--color-ink-700));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), var(--shadow-md);
}

/* Secondary — a solid white bordered button (was transparent, which read as
   a label rather than a control against the neutral canvas). */
.btn-secondary {
  background: var(--color-card);
  color: var(--color-ink);
  border: 1px solid var(--color-hairline);
  box-shadow: var(--shadow-sm);
}

.btn-secondary:hover {
  background: var(--color-surface);
  border-color: #C7CBD2;
  box-shadow: var(--shadow-md);
}

/* Ghost — a quiet tertiary action (Cancel, Back) that only surfaces on
   hover, completing the primary/secondary/ghost/danger hierarchy. */
.btn-ghost {
  background: transparent;
  color: var(--color-text-muted);
}

.btn-ghost:hover {
  background: color-mix(in srgb, var(--color-ink) 6%, transparent);
  color: var(--color-ink);
}

/* Icon-only square button (toolbar utilities) — pairs with the 40px
   input/button rhythm. Always give it an aria-label. */
.btn-icon {
  width: 40px;
  min-height: 40px;
  padding: 0;
  flex: none;
}
.btn-icon svg { width: 18px; height: 18px; }

.input:hover { border-color: #C7CBD2; }

.input[type="file"] {
  padding-block: calc(var(--space-2) + 2px);
}

.input[type="file"]::file-selector-button {
  margin-inline-end: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--color-ink);
  color: #fff;
  font-weight: 600;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--motion-fast);
}

.input[type="file"]::file-selector-button:hover { background: var(--color-ink-700); }

/* Native <select> arrow — fallback for the instant before Choices.js
   upgrades it (and for no-JS). Choices.js hides the original element
   once it initializes. */
select.input {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 16px;
  padding-inline-end: var(--space-8);
}

/* ---------- Custom checkboxes, radios & switches (issue #316) ----------
   Native checkboxes/radios rendered as tiny OS-default controls that
   clashed with the rest of the design system. `appearance: none` +
   hand-drawn states brings them into the same ink/indigo family as
   `.input`/`.btn`, across every Django-rendered form and inline filter —
   no template changes needed. The marketing pages' hidden menu-toggle
   checkboxes (`.hp-menu-toggle`/`.mkt-nav-toggle`) are visually hidden and
   unaffected. RTL-safe: logical properties throughout. */
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  inline-size: 18px;
  block-size: 18px;
  margin: 0;
  flex: none;
  vertical-align: text-bottom;
  border: 1.5px solid #C7CBD2;
  border-radius: var(--radius-sm);
  background: var(--color-card);
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast);
}

input[type="radio"] { border-radius: 50%; }

input[type="checkbox"]:hover,
input[type="radio"]:hover { border-color: var(--color-amber); }

input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: none;
  border-color: var(--color-amber);
  box-shadow: var(--focus-ring);
}

input[type="checkbox"]:checked {
  border-color: var(--color-ink);
  background-color: var(--color-ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 12px;
}

input[type="radio"]:checked {
  border-color: var(--color-ink);
  background: var(--color-ink);
  box-shadow: inset 0 0 0 3.5px var(--color-card);
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Toggle-switch variant — opt in with `class="switch"` on a boolean
   checkbox that flips a setting/mode (rather than selects an item from a
   list). The input itself is the track; the thumb is a pseudo-element, so
   it stays a plain, submittable checkbox with no extra markup. */
input[type="checkbox"].switch {
  position: relative;
  inline-size: 38px;
  block-size: 22px;
  border-radius: 999px;
  background: var(--color-hairline);
  border-color: var(--color-hairline);
}

input[type="checkbox"].switch::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 2px;
  inline-size: 15px;
  block-size: 15px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.25);
  transform: translateY(-50%);
  transition: inset-inline-start var(--motion-fast);
}

input[type="checkbox"].switch:checked {
  background: var(--color-success);
  border-color: var(--color-success);
  background-image: none;
}

input[type="checkbox"].switch:checked::before { inset-inline-start: 18px; }

/* ---------- Alerts / messages ---------- */

.alert {
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-4);
  font-size: var(--text-sm);
}

.alert-error {
  background: var(--color-danger-surface);
  color: var(--color-danger);
}

.alert-success {
  background: var(--color-success-surface);
  color: var(--color-success);
}

.alert-info {
  background: color-mix(in srgb, var(--color-amber) 15%, transparent);
  color: var(--color-amber-600);
}

/* Modern dismissible flash/toast messages (templates/partials/messages.html).
   Builds on the base .alert color variants above WITHOUT redeclaring any of
   them — .alert-rich only adds the icon/body/dismiss layout, a leading accent
   (currentColor, so it auto-matches each variant) and an entrance animation,
   so the inline form-error .alert usages elsewhere stay exactly as they were.
   RTL-safe: logical properties throughout. */
.alert-warning {
  background: var(--color-warning-surface);
  color: var(--color-warning);
}

.alert-rich {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  border-inline-start: 3px solid currentColor;
  animation: alert-in var(--motion-fast) ease-out both;
}

.alert-icon {
  flex: 0 0 auto;
  inline-size: 1.15rem;
  block-size: 1.15rem;
  margin-block-start: 1px;
}

.alert-icon svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}

.alert-body {
  flex: 1 1 auto;
  min-inline-size: 0;
  align-self: center;
}

.alert-dismiss {
  flex: 0 0 auto;
  display: none; /* revealed only once JS enhances — never a dead control without JS */
  align-items: center;
  justify-content: center;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  opacity: 0.7;
  cursor: pointer;
  transition: opacity var(--motion-fast), background var(--motion-fast);
}

.alert.is-enhanced .alert-dismiss {
  display: inline-flex;
}

.alert-dismiss:hover {
  opacity: 1;
  background: color-mix(in srgb, currentColor 14%, transparent);
}

.alert-dismiss svg {
  inline-size: 1rem;
  block-size: 1rem;
}

.alert.is-dismissing {
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--motion-fast), transform var(--motion-fast);
}

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

/* ---------- App shell (dashboard) ---------- */

.app-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  height: 100vh;
}

.sidebar {
  background: var(--color-card);
  color: var(--color-text);
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow-y: auto;
  border-inline-end: 1px solid var(--color-hairline);
}

.sidebar .auth-brand { margin-block-end: var(--space-8); }
.sidebar .auth-brand-name { color: var(--color-ink-900); }
.sidebar .auth-brand-sub { color: var(--color-text-muted); }

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--nav-fg);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  margin-block-end: var(--space-1);
  border-inline-start: 2px solid transparent;
  transition: background var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
}

.nav-item:hover {
  background: var(--nav-hover-surface);
  color: var(--nav-fg-hover);
}

.nav-item-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: inherit;
  opacity: 0.9;
}

.nav-item.is-active {
  background: var(--nav-active-surface);
  color: var(--nav-active-fg);
  font-weight: 600;
  border-inline-start-color: var(--color-amber);
}

.nav-item.is-active .nav-item-icon { opacity: 1; color: var(--color-amber); }

/* Sidebar navigation group labels (issue #316): the Union rail lists 20+
   routes — a flat list that long is unscannable for a new user. Small
   uppercase group labels (Overview / Property / Finance / Community /
   Operations / Administration) break it into recognizable sections,
   mirroring the reference design's grouped rail. RTL-safe (logical
   padding). */
.nav-group-label {
  margin-block: var(--space-5) var(--space-2);
  padding-inline: var(--space-3);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  user-select: none;
}

.sidebar nav > .nav-group-label:first-child { margin-block-start: 0; }

.sidebar-footer {
  margin-block-start: auto;
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-hairline);
}

.sidebar-user {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text);
  margin-block-end: var(--space-3);
}

.sidebar-user-avatar {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-amber);
  color: #fff;
  font-weight: 600;
  font-size: var(--text-xs);
}

.sidebar-user-name { min-width: 0; overflow-wrap: anywhere; }

.sidebar .btn-secondary {
  gap: var(--space-2);
}

.main {
  padding: var(--space-8);
  /* Widened from 1100px (issue #316): this is a table-heavy admin product —
     the reference design runs a wide content canvas, and the extra room
     stops bill/ledger/roster tables forcing their own horizontal scroll on
     a normal desktop. */
  max-width: 1280px;
  width: 100%;
  margin-inline: auto;
  height: 100vh;
  overflow-y: auto;
  background: var(--color-surface);
}

/* Page header row: a hairline under every page's title/actions separates
   the header zone from the content below, so each page opens with the same
   structured band the reference design uses (issue #316). `.dashboard-hero`
   supplies its own panel treatment on top of this. */
.topbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-block-end: var(--space-6);
  padding-block-end: var(--space-5);
  border-block-end: 1px solid var(--color-hairline);
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* Desktop-only utility row above `.topbar` (notification bell + signed-in
   user) — the four sidebar-based portals already surface both in the
   sidebar itself (nav item / footer), so this hides below the 860px
   breakpoint where the sidebar becomes an off-canvas drawer rather than
   duplicating them a third time next to the hamburger button. */
.app-header {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-4);
  margin-block-end: var(--space-5);
}

.app-header-bell {
  position: relative;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--color-glass-border);
  background: var(--color-glass-surface);
  color: var(--color-text-muted);
  text-decoration: none;
  transition: background var(--motion-fast), color var(--motion-fast);
}

.app-header-bell:hover { background: var(--color-surface); color: var(--color-ink-900); }
.app-header-bell svg { width: 20px; height: 20px; }

.app-header-bell-dot {
  position: absolute;
  inset-block-start: 7px;
  inset-inline-end: 7px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-danger);
  border: 2px solid var(--color-surface);
}

.app-header-user {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-inline-start: var(--space-4);
  border-inline-start: 1px solid var(--color-hairline);
}

.app-header-user-avatar {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-amber) 18%, transparent);
  color: var(--color-amber-600);
  font-weight: 600;
  font-size: var(--text-sm);
}

.app-header-user-name {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-900);
  line-height: 1.3;
}

.app-header-user-sub {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

@media (max-width: 860px) {
  .app-header { display: none; }
}

.page-title {
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--color-ink-900);
}

.page-subtitle {
  color: var(--color-text-muted);
  margin-block-start: var(--space-1);
}

/* Standalone heading above a group of cards that isn't itself inside a
   `.card` (e.g. the union dashboard's "Action Required" widget row) —
   same weight/color as `.card-title` so it reads as part of the same
   type scale. */
.section-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-ink-900);
  margin-block-end: var(--space-4);
}

/* Two-column dashboard layout (union dashboard: Action Required + Contract
   on the left, Need help on the right) — collapses to one column at the
   same breakpoint the sidebar goes off-canvas at, so it never fights the
   mobile drawer for space. */
.dashboard-columns {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-6);
  align-items: start;
}

/* ---------- Stat cards ---------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-block-end: var(--space-8);
}

.stat-card {
  background: var(--color-card);
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  backdrop-filter: blur(var(--blur-glass));
  -webkit-backdrop-filter: blur(var(--blur-glass));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-4) var(--space-6);
  /* A `.stat-card` is often an `<a>` link to its detail list (dashboard
     Blocks/Bills, resident Tickets). Base it here so those call sites no
     longer each carry an inline `text-decoration:none` — a plain `<div>`
     stat card has nothing to un-underline, so this is harmless there. */
  text-decoration: none;
  transition: box-shadow var(--motion-base), transform var(--motion-base);
  /* Icon + label share the header row (icon start, label end, per the
     reference design); every other direct child — value, progress bar,
     field-help, footer — spans the full card below it, via the catch-all
     rule after `.stat-card-icon` below. This works across every existing
     `.stat-card` call site with no template changes, since it re-places
     children by grid position rather than relying on source order. */
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: var(--space-3);
  /* Registers the card as a size container so `.stat-card-value`'s `cqi`
     clamp can step the headline down on narrow cards. Safe here: every
     `.stat-card` sits in a `.stat-grid`, whose tracks size the card
     regardless of content, so inline-size containment changes nothing
     about layout. */
  container-type: inline-size;
  /* Cards in a `.stat-grid` row stretch to the tallest sibling; without
     this, grid distributes that extra height between the header and value
     rows differently per card, so values sit at ragged vertical positions
     across a row (most visible on the aging report's 7-card band). Packing
     rows to the start keeps every card's label/value at the same height. */
  align-content: start;
}

.stat-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.stat-card-label {
  grid-column: 2;
  grid-row: 1;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  /* Start-aligned beside the icon so the label and the value below share a
     single alignment axis — an end-aligned label over a start-aligned value
     gives each card two competing edges and reads as misaligned. */
  text-align: start;
  line-height: 1.35;
}

.stat-card > :not(.stat-card-icon):not(.stat-card-label) {
  grid-column: 1 / -1;
}

.stat-card-value {
  /* Sans, not mono: `.table .num` money columns already render figures in
     the sans face with `tabular-nums`, so a mono card value sat over a sans
     table of the same numbers on the same page (aging report, ledgers) and
     read as typewriter output. Sans digits are also ~20% narrower, which is
     what lets a grouped 5-figure amount plus its currency suffix fit a
     minimum-width (200px) grid card on one line at `--text-2xl`. */
  font-family: var(--font-sans);
  font-size: var(--text-2xl);
  /* Size-to-fit on narrow cards: when a `.stat-grid` row squeezes cards
     near their 200px minimum, step the headline down so a grouped 6-figure
     amount still fits on one line. `cqi` resolves against the card itself
     (`container-type` on `.stat-card`); browsers without container queries
     drop this declaration and keep the `--text-2xl` above — the
     `.stat-card-currency` wrap fallback still guarantees nothing clips. */
  font-size: clamp(var(--text-lg), 11cqi, var(--text-2xl));
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--color-ink-900);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  margin-block-start: var(--space-3);
}

/* A currency code / unit / "of N" suffix following a `.stat-card-value`
   number reads as one unit at the same size and weight as the number
   itself otherwise (e.g. "1320.00 AZN") — this keeps the amount the
   visually dominant part and the unit a quieter annotation next to it,
   the same relationship a real receipt/statement uses. */
.stat-card-currency {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
  font-variant-numeric: normal;
  /* Symmetric inline margin: in RTL the amount's digits keep LTR order, so
     the suffix's visually-adjacent side is its logical END — a start-only
     margin left the code touching the digits under `dir="rtl"`. */
  margin-inline: 0.25em;
  /* A currency code must never break internally ("AZ / N"); if the card is
     genuinely too narrow the whole code wraps as one word instead. */
  white-space: nowrap;
  /* Atomic inline: the templates put no whitespace between the amount and
     this span, so as a plain inline there was no soft-wrap opportunity and
     an over-long amount + suffix CLIPPED at the card edge instead
     (`overflow: hidden` on the card is required by its accent bar). Line
     breaks are allowed around an atomic inline even with no whitespace, so
     the suffix wraps below the amount on a genuinely narrow card — while
     keeping normal inline bidi ordering under `dir="rtl"`, which a flex
     layout here would have flipped. */
  display: inline-block;
}

/* Per-metric icon badge on a `.stat-card` — one metric type per dashboard
   (Blocks, Bills, Open tickets, …), each already visually distinct in the
   surrounding template copy but, without this, indistinguishable at a
   glance from one another: every card was just a muted label over a big
   number. `--stat-accent` defaults to the ink brand color; the `.is-*`
   modifiers below reuse the same semantic tokens `.badge`/`.alert` already
   use elsewhere (success/danger/amber) so a card's color carries the same
   meaning it would on a status badge. */
.stat-card-icon {
  grid-column: 1;
  grid-row: 1;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--stat-accent, var(--color-ink)) 14%, transparent);
  color: var(--stat-accent, var(--color-ink));
}

.stat-card-icon svg { width: 18px; height: 18px; }

.stat-card-icon.is-amber { --stat-accent: var(--color-amber-600); }
.stat-card-icon.is-success { --stat-accent: var(--color-success); }
.stat-card-icon.is-danger { --stat-accent: var(--color-danger); }
.stat-card-icon.is-terracotta { --stat-accent: var(--color-terracotta); }

/* Bordered footer inside a `.stat-card` for a secondary line + a
   `.btn-inline` call-to-action (e.g. the union dashboard's Bills card:
   "Total due" plus a "Review all bills →" link), separating it from the
   headline value above without needing its own card. */
.stat-card-footer {
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-hairline);
}

/* ---------- Action-required widget row ----------
   A distinct, centered treatment for "queue depth" metrics (payment
   claims/tickets/invoices awaiting the Union Admin) — a circular icon
   above a large count, separate from the `.stat-card` grid above so it
   reads as its own "needs your attention" group rather than one more
   generic metric. */
.action-required-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4);
}

.action-required-card {
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  backdrop-filter: blur(var(--blur-glass));
  -webkit-backdrop-filter: blur(var(--blur-glass));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  text-decoration: none;
  transition: box-shadow var(--motion-base), transform var(--motion-base), border-color var(--motion-base);
}

.action-required-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--stat-accent, var(--color-ink)) 40%, var(--color-glass-border));
}

.action-required-icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-block-end: var(--space-3);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-text-muted);
}

.action-required-icon svg { width: 20px; height: 20px; }

.action-required-icon.is-danger { --stat-accent: var(--color-danger); background: var(--color-danger-surface); color: var(--color-danger); }

.action-required-count {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: 600;
  color: var(--color-ink-900);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.action-required-label {
  margin-block-start: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------- Card / table ---------- */

.card {
  background: var(--color-card);
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  backdrop-filter: blur(var(--blur-glass));
  -webkit-backdrop-filter: blur(var(--blur-glass));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
  transition: box-shadow var(--motion-base), transform var(--motion-base);
}

.card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

/* A hairline under every card header separates "what this card is" from its
   content, so a page of cards reads as structured panels rather than loose
   text over boxes (issue #316). `.table-card > .card-header` keeps its own
   padded variant. */
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--color-hairline);
  gap: var(--space-4);
  flex-wrap: wrap;
}

.card-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-ink-900);
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

/* Column headers app-wide use the quiet uppercase "band" treatment the
   Bills/Aging tables pioneered (issue #276) — a light tinted strip of small
   letter-spaced labels, per the reference design. Promoted from
   `.bills-table` to every `.table` so all list pages read as one system
   (issue #316). */
.table th {
  text-align: start;
  color: var(--color-text-muted);
  font-weight: 600;
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: var(--space-3);
  background: color-mix(in srgb, var(--color-ink-900) 4%, var(--color-card));
  border-block-end: 1px solid var(--color-hairline);
}

.table td {
  padding: var(--space-3);
  border-block-end: 1px solid var(--color-hairline);
  vertical-align: middle;
}

.table tr:last-child td { border-block-end: none; }

.table tbody tr { transition: background var(--motion-fast); }
.table tbody tr:nth-child(even) { background: rgba(17, 24, 39, 0.022); }
.table tbody tr:hover { background: var(--color-surface); }

/* A table row's own "click to view detail" link (e.g. a block/apartment
   name, an invoice number) — distinct from `.btn-inline`'s in-row actions
   (Edit, Approve, …), so excluded here rather than reusing that class.
   Without this it fell through to the bare `a { color: inherit; }` above
   with the browser's default underline, the same weak, easy-to-miss
   affordance the `.btn-inline` fix addressed for row actions. Semibold +
   no underline reads as "this is the clickable record", with the
   underline reappearing on hover/focus as the interaction cue. */
.table td a:not(.btn-inline):not(.btn) {
  font-weight: 600;
  text-decoration: none;
}

.table td a:not(.btn-inline):not(.btn):hover,
.table td a:not(.btn-inline):not(.btn):focus-visible {
  color: var(--color-amber-600);
  text-decoration: underline;
}

/* Whole-row click affordance (static/js/rowlink.js, issue #316). A row in a
   `.table--clickable` table is clickable anywhere, so it gets a pointer cursor,
   a clear hover/focus highlight and a trailing chevron cue — the block/apartment
   name is no longer the only, easy-to-miss, hover-to-discover click target. */
.table--clickable tbody tr.is-rowlink { cursor: pointer; }
.table--clickable tbody tr.is-rowlink:hover,
.table--clickable tbody tr.is-rowlink:focus-visible {
  background: var(--nav-active-surface);
  outline: none;
}
.table--clickable tbody tr.is-rowlink:focus-visible {
  box-shadow: inset 0 0 0 2px var(--color-amber);
}
/* Trailing chevron cell: muted at rest, accent + nudged on row hover. */
.row-chevron {
  color: var(--color-text-muted);
  text-align: end;
  width: 1.5rem;
}
.row-chevron svg {
  width: 16px;
  height: 16px;
  transition: transform var(--motion-fast), color var(--motion-fast);
}
.table--clickable tbody tr.is-rowlink:hover .row-chevron { color: var(--color-amber-600); }
.table--clickable tbody tr.is-rowlink:hover .row-chevron svg { transform: translateX(2px); }
[dir="rtl"] .table--clickable tbody tr.is-rowlink:hover .row-chevron svg { transform: translateX(-2px) scaleX(-1); }
[dir="rtl"] .row-chevron svg { transform: scaleX(-1); }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Status dot on lifecycle/semantic badges (issue #316) — a small
   currentColor bullet in front of the label, so a status reads at a glance
   from color + shape rather than text alone (and stays distinguishable for
   color-blind users via the label). Matches the reference design's
   "•Secure" status treatment. Content/one-off badges (categories,
   priorities) deliberately don't get a dot. */
.badge-active::before,
.badge-suspended::before,
.badge-inactive::before,
.badge-danger::before,
.badge-warning::before,
.badge-success::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.badge-active { background: var(--color-success-surface); color: var(--color-success); }
.badge-suspended { background: var(--color-danger-surface); color: var(--color-danger); }
.badge-inactive { background: var(--color-hairline); color: var(--color-text-muted); }

/* Semantic color variants for one-off status/priority badges (ticket
   status, bulletin category, payment-claim flags, …) that don't map onto
   the active/suspended/inactive lifecycle names above — pulled out of the
   many identical `style="background: color-mix(...); color: ...;"` inline
   attributes those badges used to carry, into three reusable classes. */
.badge-danger { background: color-mix(in srgb, var(--color-danger) 15%, transparent); color: var(--color-danger); }
.badge-warning { background: color-mix(in srgb, var(--color-amber) 20%, transparent); color: var(--color-amber-600); }
.badge-success { background: var(--color-success-surface); color: var(--color-success); }

/* Small unread-count bubble on a nav link (resident portal's Marketplace
   sidebar item, the general marketplace header's Messages item, the inbox
   row itself) — not a `.badge` variant since those are uppercase status
   labels, and this is a number in a circle. */
.badge-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--color-danger);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.nav-item-badge { margin-inline-start: auto; }

/* ---------- Marketplace ---------- */

.marketplace-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-5);
  margin-block-end: var(--space-8);
}

.marketplace-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}

.marketplace-card-media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--color-hairline);
}

.marketplace-card-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.marketplace-card-media-placeholder {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  color: var(--color-text-muted);
}

.marketplace-card-media-placeholder svg { width: 32px; height: 32px; opacity: 0.5; }

.marketplace-card-price {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-2);
  background: rgba(14, 15, 18, 0.78);
  color: #fff;
  font-weight: 600;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.marketplace-card-status {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
}

.marketplace-card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
}

.marketplace-card-meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.marketplace-card-title {
  font-weight: 600;
  color: var(--color-ink-900);
}

.marketplace-card-footer {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-block-start: auto;
  padding-block-start: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

.pill-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-end: var(--space-4);
}

.pill-filter {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: 999px;
  background: var(--color-card);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  transition: background var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
}

.pill-filter:hover { border-color: var(--color-ink); }

.pill-filter.is-active {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: #fff;
}

.marketplace-photo-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-end: var(--space-3);
}

.marketplace-photo-item {
  position: relative;
  display: block;
  width: 96px;
  height: 96px;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-hairline);
}

.marketplace-photo-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.marketplace-photo-remove {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 2px 4px;
  background: rgba(14, 15, 18, 0.72);
  color: #fff;
  font-size: 0.6875rem;
}

.marketplace-inbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-block-end: 1px solid var(--color-hairline);
  text-decoration: none;
  color: inherit;
}

.marketplace-inbox-row:last-child { border-block-end: none; }

.marketplace-inbox-row-thumb {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-md);
  object-fit: cover;
  background: var(--color-hairline);
}

.marketplace-inbox-row-body { flex: 1; min-width: 0; }

.marketplace-inbox-row-title {
  font-weight: 500;
  color: var(--color-ink-900);
}

.marketplace-inbox-row.is-unread .marketplace-inbox-row-title { font-weight: 700; }

.marketplace-inbox-row-meta {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* ---------- Avatars & comment threads (ticket + bulletin comments) ----------
   Both comment threads used to be a bare paragraph of muted meta text over
   the comment body, with only a colored left border (ticket comments) or
   nothing at all (bulletin comments) to separate one from the next. This
   gives every comment author a small initials avatar and puts every comment
   in one shared, consistently-spaced `.comment-item` shell so the two
   threads read as the same component instead of two different ad hoc
   layouts. */

.avatar-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-ink) 14%, transparent);
  color: var(--color-ink);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
}

.comment-item {
  display: flex;
  gap: var(--space-3);
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--color-hairline);
}

.comment-item:last-child { border-block-end: none; }

.comment-item-body { flex: 1; min-width: 0; }

.comment-item-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  margin-block-end: var(--space-1);
}

.comment-item-author { font-weight: 600; color: var(--color-ink-900); }

.comment-item.is-internal .avatar-badge { background: color-mix(in srgb, var(--color-amber) 20%, transparent); color: var(--color-amber-600); }
.comment-item.is-hidden .avatar-badge { background: var(--color-hairline); color: var(--color-text-muted); }
.comment-item.is-hidden .comment-item-body { opacity: 0.7; }

/* ---------- Progress bar (bill detail: paid vs. total) ---------- */

.progress-bar {
  margin-block-start: var(--space-2);
}

.progress-bar-track {
  width: 100%;
  height: 6px;
  border-radius: 999px;
  background: var(--color-hairline);
  overflow: hidden;
}

.progress-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--color-success);
  transition: width var(--motion-base);
}

.progress-bar-fill.is-danger { background: var(--color-danger); }
.progress-bar-fill.is-warning { background: var(--color-amber-600); }

.progress-bar-label {
  margin-block-start: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* ---------- Bulletin cards (resident board) ----------
   Replaced a plain `.card` (title line + a muted meta paragraph) with a
   left-accent bar matching the bulletin's category color (same palette as
   `partials/bulletin_category_badge.html`) and a matching icon badge, so
   the board reads at a glance the way the category badge already does. */

.bulletin-card {
  display: flex;
  gap: var(--space-4);
  text-decoration: none;
  color: inherit;
  border-inline-start: 4px solid var(--bulletin-accent, var(--color-ink));
}

.bulletin-card-body { flex: 1; min-width: 0; }

.bulletin-card-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-ink-900);
}

.bulletin-card-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

.bulletin-card-excerpt {
  color: var(--color-text);
  margin: 0;
}

.bulletin-card.is-urgent { --bulletin-accent: var(--color-danger); }
.bulletin-card.is-financial { --bulletin-accent: var(--color-amber-600); }
.bulletin-card.is-maintenance { --bulletin-accent: var(--color-text-muted); }
.bulletin-card.is-event,
.bulletin-card.is-notice { --bulletin-accent: var(--color-success); }

.bulletin-card-icon {
  flex: none;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bulletin-accent, var(--color-ink)) 14%, transparent);
  color: var(--bulletin-accent, var(--color-ink));
}

.bulletin-card-icon svg { width: 18px; height: 18px; }

/* Left-border accent on a table row (union ticket list: flags an open,
   SLA-breached ticket without a whole extra column). */
.row-accent-danger td:first-child { box-shadow: inset 3px 0 0 var(--color-danger); }

/* ---------- Live-update highlight (tickets/ticket_live.js) ---------- */

@keyframes ticket-live-in {
  from { opacity: 0; transform: translateY(-4px); background-color: color-mix(in srgb, var(--color-success) 18%, transparent); }
  to { opacity: 1; transform: translateY(0); background-color: transparent; }
}

.ticket-comment-live-in {
  animation: ticket-live-in 1.6s ease-out;
}

.table-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  justify-content: flex-end;
}

/* A compact, bordered "chip" button for the in-row/in-card actions
   (Edit, Reset password, Approve, Mark paid, pagination, …) that used to
   render as plain underlined text — those read as afterthoughts rather
   than actionable controls, especially once a row has more than one.
   Kept as its own class (rather than reusing `.btn-secondary`) because it
   needs to stay small enough to sit several-to-a-row inside a table cell
   or card footer. */
.btn-inline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 30px;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  color: var(--color-ink);
  font: inherit;
  font-weight: 600;
  font-size: var(--text-sm);
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast), color var(--motion-fast);
}

.btn-inline svg { width: 14px; height: 14px; flex: none; }

.btn-inline:hover {
  background: var(--color-surface);
  border-color: #C7CBD2;
  color: var(--color-ink-700);
  box-shadow: var(--shadow-sm);
}

.btn-inline:active { transform: translateY(1px); }

.btn-inline.is-danger {
  color: var(--color-danger);
  border-color: color-mix(in srgb, var(--color-danger) 35%, var(--color-hairline));
}

.btn-inline.is-danger:hover {
  background: var(--color-danger-surface);
  border-color: var(--color-danger);
}

/* ---------- Sub-navigation (tab bar, e.g. union/partials/billing_subnav.html) ----------
   A segmented tab bar for switching between sibling views of the same
   section (Bills / Bill types / Monthly values / …), distinct from
   `.nav-item` (top-level sidebar routes) and `.btn-inline` (inline text
   actions inside a table row) — neither reads as a "you are here" control
   on its own. */

.subnav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-block-end: var(--space-5);
  padding-block-end: var(--space-2);
  border-block-end: 1px solid var(--color-hairline);
}

.subnav-item {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background var(--motion-fast), color var(--motion-fast);
}

.subnav-item:hover { background: var(--color-glass-surface); color: var(--color-ink-900); }

.subnav-item.is-active {
  background: var(--color-ink);
  color: #fff;
}

.empty-state {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  color: var(--color-text-muted);
  font-size: var(--text-md);
}

/* Generic "nothing here yet" icon, added once here rather than per
   template — every `.empty-state` across the app (68+ call sites) was a
   plain line of muted text with no visual anchor. A pseudo-element keeps
   every call site a one-line `<div class="empty-state">…</div>` with no
   template changes. */
.empty-state::before {
  content: "";
  display: block;
  width: 44px;
  height: 44px;
  margin: 0 auto var(--space-4);
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--color-ink) 8%, transparent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 12h-6l-2 3h-4l-2-3H2'%3E%3C/path%3E%3Cpath d='M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11Z'%3E%3C/path%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 20px;
}

/* ---------- One-time credentials panel ---------- */

.credentials-box {
  background: var(--color-ink-900);
  color: #fff;
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-block-end: var(--space-6);
}

.credentials-box .field-help { color: rgba(255, 255, 255, 0.6); }
.credentials-box .card-title { color: #fff; }
/* Accent on the dark panel uses the light end of the indigo family —
   the solid accent (#4F46E5) sits at ~2.3:1 against near-black and fails
   WCAG AA; indigo-300 keeps the brand hue and passes. */
.credentials-box .btn-inline {
  background: transparent;
  border-color: rgba(165, 180, 252, 0.5);
  color: #A5B4FC;
}
.credentials-box .btn-inline:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: #A5B4FC;
  color: #fff;
}

.credentials-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-block-end: 1px solid rgba(255, 255, 255, 0.12);
}

.credentials-row:last-child { border-block-end: none; }

.credentials-key {
  color: rgba(255, 255, 255, 0.6);
  font-size: var(--text-sm);
}

.credentials-value {
  font-family: var(--font-mono);
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* ---------- Copy-to-clipboard button (progressive enhancement) ----------
   JS-injected by widgets.js next to any [data-copy] element, so it is never a
   dead control without JS. RTL-safe (logical margin). */
.copy-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-inline-start: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  vertical-align: middle;
  transition: background var(--motion-fast), border-color var(--motion-fast), color var(--motion-fast);
}

.copy-btn:hover {
  border-color: var(--color-ink);
  color: var(--color-ink);
}

.copy-btn svg { width: 14px; height: 14px; flex-shrink: 0; }

.copy-btn.is-copied {
  border-color: var(--color-success);
  color: var(--color-success);
}

/* Dark-surface variant on the one-time credentials box, matching its
   .btn-inline treatment. */
.credentials-box .copy-btn {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.24);
  color: rgba(255, 255, 255, 0.7);
}
.credentials-box .copy-btn:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: #A5B4FC;
  color: #fff;
}
.credentials-box .copy-btn.is-copied {
  border-color: var(--color-success);
  color: var(--color-success);
}

/* ---------- Form sections / contact grid ---------- */

.form-section {
  background: var(--color-card);
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  backdrop-filter: blur(var(--blur-glass));
  -webkit-backdrop-filter: blur(var(--blur-glass));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
  margin-block-end: var(--space-5);
}

.form-section-header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-block-end: var(--space-5);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--color-hairline);
}

.form-section-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-amber) 14%, transparent);
  color: var(--color-amber-600);
}

.form-section-icon svg { width: 17px; height: 17px; }

.form-section-title {
  font-size: var(--text-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.form-section-header .form-section-title {
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--color-ink-900);
  text-transform: none;
  letter-spacing: normal;
}

.form-section-desc {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-block-start: 2px;
}

.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0 var(--space-6);
}

.form-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding-block: var(--space-4);
  margin-block-start: var(--space-2);
  background: linear-gradient(to top, var(--color-surface) 65%, transparent);
}

/* In-card variant: an action row that belongs to a detail-page card section
   rather than closing a full-page form — leads from the start edge (RTL-safe),
   never sticks, and drops the page-canvas gradient that looked wrong over a
   white card. Replaces the ad-hoc `style="justify-content:flex-start"`
   overrides (issue #316). */
.form-actions--start {
  position: static;
  justify-content: flex-start;
  flex-wrap: wrap;
  padding-block: 0;
  margin-block-start: var(--space-4);
  background: none;
}

/* Data-entry forms cap at a readable line length instead of stretching
   every input across the whole 1280px canvas — an edge-to-edge text input
   is hard to scan and reads unprofessional (issue #316). Scoped to a
   `.form-section` (and its sibling actions bar) sitting directly inside a
   form, so full-width panels elsewhere are untouched. Logical property, so
   the cap tracks the start edge in RTL too. */
form > .form-section,
form > .form-actions {
  max-inline-size: 800px;
}

/* An inline action form (reject-with-reason, status change, comment
   moderation): its controls sit on one wrapping row, bottom-aligned to the
   shared 40px control rhythm so a select + button always line up. Replaces
   the ad-hoc `style="display:flex; …; align-items:flex-end"` forms
   (issue #316). */
.form-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}

.form-inline .field {
  margin: 0;
  flex: 1 1 200px;
  max-inline-size: 320px;
}

/* A bare control dropped straight into the row (no `.field` wrapper — e.g.
   a table cell's apartment picker) gets the same disciplined sizing. */
.form-inline > .input {
  width: auto;
  flex: 1 1 180px;
  max-inline-size: 320px;
}

.form-inline .btn { flex: 0 0 auto; }

.project-logo-thumb {
  width: 28px;
  height: 28px;
  object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  border: 1px solid var(--color-hairline);
  vertical-align: middle;
  margin-inline-end: var(--space-2);
}

/* ---------- Capacity meters (allowed vs actual, live) ---------- */

.meter-row {
  padding: var(--space-4) 0;
  border-block-end: 1px solid var(--color-hairline);
}

.meter-row:last-child { border-block-end: none; }

.meter-labels {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-block-end: var(--space-2);
  font-size: var(--text-sm);
}

.meter-labels .meter-name {
  font-weight: 600;
  color: var(--color-ink-900);
}

.meter-labels .meter-value {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.meter-track {
  height: 8px;
  border-radius: 999px;
  background: var(--color-hairline);
  overflow: hidden;
}

.meter-fill {
  height: 100%;
  background: var(--color-amber);
  border-radius: 999px;
}

.meter-fill.is-over-capacity { background: var(--color-danger); }

/* ---------- Quick-nav (long forms) ---------- */

.form-quicknav {
  position: sticky;
  inset-block-start: 0;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-block: var(--space-2);
  margin-block-end: var(--space-4);
  background: linear-gradient(to bottom, var(--color-surface) 75%, transparent);
}

.form-quicknav a {
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  border: 1px solid var(--color-hairline);
  background: var(--color-glass-surface);
  backdrop-filter: blur(var(--blur-glass));
  -webkit-backdrop-filter: blur(var(--blur-glass));
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  scroll-margin-block-start: var(--space-4);
  transition: border-color var(--motion-fast), color var(--motion-fast);
}

.form-quicknav a:hover {
  border-color: var(--color-amber);
  color: var(--color-ink-900);
}

/* Step numbers on each form-section, in document order — no JS needed. */
#project-form { counter-reset: form-section; }

#project-form .form-section { counter-increment: form-section; }

#project-form .form-section-icon {
  position: relative;
}

#project-form .form-section-icon::after {
  content: counter(form-section);
  position: absolute;
  inset-block-start: -6px;
  inset-inline-end: -6px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-ink);
  color: #fff;
  font-size: 10px;
  line-height: 16px;
  text-align: center;
  font-weight: 700;
}

/* ---------- Native date input polish ---------- */

input[type="date"].input {
  position: relative;
}

input[type="date"].input::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.55;
  padding: 2px;
  border-radius: var(--radius-sm);
  transition: opacity var(--motion-fast), background var(--motion-fast);
}

input[type="date"].input::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
  background: rgba(17, 24, 39, 0.07);
}

/* ---------- Mobile navigation ----------
   Below 860px the sidebar becomes an off-canvas drawer instead of stacking
   in the document flow above the page content — a fixed-height nav with a
   dozen-plus links (e.g. the Union Admin portal) would otherwise push every
   page's actual content below the fold on a phone. `.mobile-topbar`/
   `.sidebar-toggle`/`.sidebar-backdrop` are only ever rendered by the four
   sidebar-based layouts (`saportal`, `unionportal`, `residentportal`,
   `vendorportal`) — `generalmarketplace`'s header has no sidebar to toggle.
   Toggling is handled by static/js/widgets.js. */

.mobile-topbar {
  display: none;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-ink-900);
  color: #fff;
  position: sticky;
  inset-block-start: 0;
  z-index: 30;
}

.mobile-topbar-brand {
  font-weight: 600;
  font-size: var(--text-md);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-toggle {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: var(--radius-md);
  background: var(--color-card);
  border: 1px solid var(--color-hairline);
  color: var(--color-text);
  cursor: pointer;
  transition: background var(--motion-fast);
}

.sidebar-toggle:hover { background: var(--color-surface); }
.sidebar-toggle svg { width: 20px; height: 20px; }

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(14, 15, 18, 0.5);
  z-index: 40;
  opacity: 0;
  transition: opacity var(--motion-base);
}

.sidebar-backdrop.is-open {
  display: block;
  opacity: 1;
}

@media (max-width: 860px) {
  .app-shell { grid-template-columns: 1fr; height: auto; }
  .mobile-topbar { display: flex; }
  .sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    width: 280px;
    max-width: 82vw;
    transform: translateX(-100%);
    transition: transform var(--motion-base);
    z-index: 50;
    box-shadow: var(--shadow-lg);
  }
  [dir="rtl"] .sidebar { transform: translateX(100%); }
  .sidebar.is-open { transform: translateX(0); }
  .main { padding: var(--space-4); height: auto; overflow-y: visible; }

  /* Wide tables (bills, ledgers, employee/vendor lists, …) would otherwise
     force the whole page to scroll horizontally on a phone; scoping the
     scroll to the `.card` that contains the table keeps the rest of the
     page (title, filters, actions) fixed in place. */
  .card { overflow-x: auto; }

  .dashboard-columns { grid-template-columns: 1fr; }
  .table { min-width: 420px; }
}

@media (max-width: 480px) {
  .auth-shell { padding: var(--space-4); }
  .auth-card { padding: var(--space-6) var(--space-4); }
  .page-title { font-size: var(--text-xl); }
}

/* ---------- Language switcher ---------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.language-switcher { margin-block-end: var(--space-3); }

.language-select-submit { margin-block-start: var(--space-2); width: 100%; }

.auth-language-switcher {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-hairline);
}

/* `background-position: right`/`padding-inline-end` above (select.input)
   don't have a widely-supported logical equivalent for background-position,
   so the dropdown arrow needs an explicit flip under RTL. */
[dir="rtl"] select.input {
  background-position: left var(--space-3) center;
  padding-inline-start: var(--space-8);
  padding-inline-end: var(--space-3);
}

/* ---------- Custom select widget (static/js/widgets.js) ----------
   Progressively enhances every `select.input` (project form's Country/
   Currency/Language fields, the language switcher) into an accessible
   listbox combobox that matches the glass theme instead of the bare OS
   dropdown. The underlying <select> stays in the DOM (hidden) so it keeps
   driving form submission unchanged; without JS the plain `select.input`
   styling above is what renders. */

.select-widget {
  position: relative;
  width: 100%;
}

.select-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  background: var(--color-card);
  color: var(--color-text);
  font-family: inherit;
  font-size: inherit;
  text-align: start;
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast);
}

.select-trigger:hover { border-color: #C7CBD2; }

/* Matches `.input:focus` exactly (accent border + indigo `--focus-ring`) so a
   native input and an enhanced select read as the same control family — was a
   leftover teal-glass ring from before the 2026 redesign (issue #316). */
.select-widget.is-open .select-trigger,
.select-trigger:focus-visible {
  background: var(--color-card);
  border-color: var(--color-amber);
  box-shadow: var(--focus-ring);
  outline: none;
}

.select-trigger-icon {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--color-text-muted);
}

.select-trigger-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}

.select-trigger-chevron {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--color-text-muted);
  transition: transform var(--motion-fast);
}

.select-widget.is-open .select-trigger-chevron { transform: rotate(180deg); }

/* `position: fixed` with its `top`/`left`/`width` computed in JS
   (static/js/widgets.js `positionListbox`) rather than `absolute` inside
   `.select-widget` — the listbox is reparented to `document.body` on open
   specifically so it escapes any ancestor `.card`'s stacking context
   (`.card`'s `backdrop-filter`, and `transform` on `:hover`, each start a
   new stacking context) and any ancestor's `overflow`/`overflow-y: auto`
   clipping (`.main`, `.select-listbox` itself). Without this, a listbox
   opened near the bottom of one `.card` rendered behind a later sibling
   `.card` instead of on top of it. */
.select-listbox {
  position: fixed;
  z-index: 20;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  max-height: 260px;
  overflow-y: auto;
  background: var(--color-card);
  border: 1px solid var(--color-glass-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  animation: select-listbox-in var(--motion-fast) ease-out;
}

.select-listbox.drop-up {
  animation: select-listbox-in-up var(--motion-fast) ease-out;
}

@keyframes select-listbox-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes select-listbox-in-up {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

.select-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}

.select-option.is-active { background: var(--color-surface); }

.select-option.is-selected {
  color: var(--color-ink);
  font-weight: 600;
}

.select-option-check {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--color-ink);
  visibility: hidden;
}

.select-option.is-selected .select-option-check { visibility: visible; }

/* Dark variant for the select widget when it sits in the dark sidebar
   (Country/Currency/Language on the project form never appear there, but
   the language switcher — templates/partials/language_switcher.html —
   does, via templates/layouts/portal.html and union_portal.html). */
.sidebar .select-trigger {
  background-color: var(--color-surface);
  border-color: var(--color-hairline);
  color: var(--color-text);
}

.sidebar .select-trigger:hover { border-color: var(--color-ink); }

.sidebar .select-trigger-icon, .sidebar .select-trigger-chevron {
  color: var(--color-text-muted);
}

/* ---------- File upload dropzone (static/js/widgets.js) ---------- */

.dropzone {
  position: relative;
  border: 1.5px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  transition: border-color var(--motion-fast), background var(--motion-fast);
}

.dropzone:hover, .dropzone.is-dragover, .dropzone:focus-within {
  border-color: var(--color-amber);
  background: var(--color-card);
}

.dropzone.is-dragover { box-shadow: var(--focus-ring); }

.dropzone-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: none;
  opacity: 0;
  cursor: pointer;
}

.dropzone.has-file .dropzone-input { pointer-events: none; }

.dropzone-body {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  pointer-events: none;
}

.dropzone-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-amber) 14%, transparent);
  color: var(--color-amber-600);
}

.dropzone-icon svg { width: 17px; height: 17px; }

.dropzone-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.dropzone-title { font-size: var(--text-sm); font-weight: 600; color: var(--color-ink-900); }

.dropzone-hint { font-size: var(--text-xs); color: var(--color-text-muted); }

.dropzone-file {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
}

.dropzone-file-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  background: var(--color-success-surface);
  color: var(--color-success);
}

.dropzone-file-icon svg { width: 17px; height: 17px; }

.dropzone-file-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }

.dropzone-file-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dropzone-file-size { font-size: var(--text-xs); color: var(--color-text-muted); }

.dropzone-remove {
  position: relative;
  z-index: 2;
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast);
}

.dropzone-remove:hover { background: var(--color-danger-surface); color: var(--color-danger); }

.dropzone-remove svg { width: 14px; height: 14px; }

/* ---------- Facility month calendar ---------- */

.calendar-table { table-layout: fixed; }
.calendar-table th { text-align: center; }

.calendar-day {
  vertical-align: top;
  padding: 0 !important;
  height: 64px;
  border: 1px solid var(--color-hairline);
}

.calendar-day a {
  display: flex;
  flex-direction: column;
  gap: 2px;
  height: 100%;
  padding: var(--space-2);
  color: inherit;
  text-decoration: none;
}

.calendar-day a:hover { background: var(--color-surface); }

.calendar-day.is-outside { opacity: 0.4; }

.calendar-day.is-today .calendar-day-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--color-ink);
  color: #fff;
}

.calendar-day.is-full { background: color-mix(in srgb, var(--color-danger) 8%, transparent); }

.calendar-day-number { font-weight: 600; font-size: var(--text-sm); }
.calendar-day-count { font-size: var(--text-xs); color: var(--color-text-muted); }
.calendar-day-pending { font-size: var(--text-xs); color: var(--color-amber-600); }

/* ---------- Community calendar (month grid) ----------
   Separate from `.calendar-day` above: this grid lists several entry
   chips per day (not one link filling the whole cell), so it needs its
   own padding/height and a per-type accent color instead of reusing the
   uppercase `.badge` pill (built for status tags, not a 6-way event
   legend). */

.cal-day-cell {
  vertical-align: top;
  padding: var(--space-2) !important;
  height: auto;
  border: 1px solid var(--color-hairline);
}

.cal-day-cell.is-outside { opacity: 0.4; }

.cal-day-cell.is-today { background: color-mix(in srgb, var(--color-ink) 5%, transparent); }

.cal-day-cell.is-today .calendar-day-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--color-ink);
  color: #fff;
}

.cal-entries {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-block-start: var(--space-1);
}

.cal-type-event { --cal-accent: var(--color-success); }
.cal-type-maintenance { --cal-accent: var(--color-amber-600); }
.cal-type-incident { --cal-accent: var(--color-danger); }
.cal-type-ticket_visit { --cal-accent: var(--color-ink); }
.cal-type-amenity_blackout { --cal-accent: var(--color-terracotta); }
.cal-type-poll_deadline { --cal-accent: var(--color-text-muted); }
.cal-type-asset_maintenance { --cal-accent: var(--color-amber-600); }

.cal-entry {
  display: block;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  border-inline-start: 3px solid var(--cal-accent, var(--color-text-muted));
  background: color-mix(in srgb, var(--cal-accent, var(--color-text-muted)) 14%, transparent);
  color: var(--cal-accent, var(--color-text-muted));
  font-size: var(--text-xs);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-entry:hover { filter: brightness(0.92); }

.cal-entry-more {
  padding-inline-start: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.cal-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-end: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.cal-legend-item { display: inline-flex; align-items: center; gap: 6px; }

.cal-legend-dot {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: var(--cal-accent);
  flex: none;
}

/* ---------- Knowledge base ---------- */

.kb-quicknav { position: sticky; inset-block-start: 0; margin-block-end: var(--space-6); }

.kb-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  margin-block-end: var(--space-6);
}

.kb-topic { padding: 0; overflow: hidden; }

.kb-topic-summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-6);
}
.kb-topic-summary::-webkit-details-marker { display: none; }

.kb-topic-summary::after {
  content: "+";
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  color: var(--color-text-muted);
  font-size: var(--text-md);
}
.kb-topic[open] .kb-topic-summary::after { content: "\2212"; }

.kb-topic-body { padding: 0 var(--space-6) var(--space-6); }
.kb-topic-body p { line-height: 1.6; color: var(--color-text); }
.kb-topic-body p + p,
.kb-topic-body ol,
.kb-topic-body ul { margin-block-start: var(--space-3); }
.kb-topic-body ol + p,
.kb-topic-body ul + p { margin-block-start: var(--space-3); }
.kb-topic-body ol,
.kb-topic-body ul {
  padding-inline-start: var(--space-6);
  color: var(--color-text);
  line-height: 1.6;
}
.kb-topic-body li + li { margin-block-start: var(--space-2); }

.kb-faq { display: flex; flex-direction: column; }

/* Native <details>/<summary> instead of a JS accordion — same
   progressively-enhanced convention as static/js/widgets.js, works with
   JS disabled and needs no extra markup for open/closed state. */
.kb-faq-item {
  padding-block: var(--space-4);
  border-block-end: 1px solid var(--color-hairline);
}
.kb-faq-item:first-child { padding-block-start: 0; }
.kb-faq-item:last-child { border-block-end: none; padding-block-end: 0; }

.kb-faq-item summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  font-weight: 600;
  color: var(--color-ink-900);
  font-size: var(--text-md);
}
.kb-faq-item summary::-webkit-details-marker { display: none; }

.kb-faq-item summary::after {
  content: "+";
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  color: var(--color-text-muted);
  font-size: var(--text-md);
}
.kb-faq-item[open] summary::after { content: "\2212"; }

.kb-faq-item p {
  margin-block-start: var(--space-3);
  color: var(--color-text-muted);
  line-height: 1.6;
}


/* ── Public "general" marketing site (pages app, issue #214) ──────────────
   Landing page + About/Careers/Contact/Press/Advertise + Privacy/Terms/
   Cookies. These are the only public, signed-out pages, so they get their
   own self-contained chrome (`.mkt-*`) rather than reusing the portal's
   sidebar-oriented `.app-shell`/`.topbar`/`.nav-item` classes, which are
   built for a dark sidebar and rendered broken here. Uses the shared
   design tokens throughout. */
.mkt {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
}

/* Header */
.mkt-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: saturate(1.4) blur(var(--blur-glass));
  border-block-end: 1px solid var(--color-hairline);
}
.mkt-header-inner {
  max-width: 1140px;
  margin-inline: auto;
  padding: var(--space-3) var(--space-6);
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.mkt-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--color-ink);
  font-weight: 700;
  flex-shrink: 0;
}
.mkt-brand-logo { height: 28px; width: auto; display: block; }
.mkt-brand-name { font-size: var(--text-lg); letter-spacing: -0.01em; }
.mkt-nav {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.mkt-nav-link {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--text-base);
  font-weight: 500;
  transition: color var(--motion-fast), background var(--motion-fast);
}
.mkt-nav-link:hover { color: var(--color-ink); background: rgba(17, 24, 39, 0.045); }
.mkt-nav-link.is-active { color: var(--color-ink); font-weight: 600; }
.mkt-nav-cta {
  margin-inline-start: var(--space-2);
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-md);
  background: var(--color-ink);
  color: #fff;
  text-decoration: none;
  font-size: var(--text-base);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
  transition: background var(--motion-fast), box-shadow var(--motion-fast);
}
.mkt-nav-cta:hover { background: var(--color-ink-700); box-shadow: var(--shadow-md); }

/* Country badge next to the brand on a country subdomain (az./pk./ae.). */
.mkt-brand-country {
  margin-inline-start: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill, 999px);
  background: var(--color-info-surface);
  color: var(--color-info);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* Compact language switcher inside the marketing header. */
.mkt-nav-lang { display: inline-flex; align-items: center; margin-inline-start: var(--space-2); }
.mkt-nav-lang .language-switcher { margin: 0; }
.mkt-nav-lang .language-select { min-width: 8.5rem; }

/* Mobile nav toggle — pure CSS checkbox, no JS. */
.mkt-nav-toggle { display: none; }
.mkt-nav-burger { display: none; }

/* Shared buttons on hero/CTA sections. */
.mkt-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: var(--text-md);
  text-decoration: none;
  transition: background var(--motion-fast), box-shadow var(--motion-fast), border-color var(--motion-fast), transform var(--motion-fast);
}
.mkt-btn:active { transform: translateY(1px); }
.mkt-btn-primary { background: var(--color-ink); color: #fff; box-shadow: var(--shadow-sm); }
.mkt-btn-primary:hover { background: var(--color-ink-700); box-shadow: var(--shadow-md); }
.mkt-btn-ghost { background: var(--color-card); color: var(--color-ink); border: 1px solid var(--color-hairline); }
.mkt-btn-ghost:hover { border-color: #C7CBD2; box-shadow: var(--shadow-sm); }

.mkt-main { flex: 1; }
.mkt-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-amber-600);
  margin-block-end: var(--space-3);
}

/* Landing hero */
.mkt-hero {
  background:
    radial-gradient(1200px 480px at 78% -10%, rgba(79, 70, 229, 0.12), transparent 60%),
    linear-gradient(180deg, #fff, var(--color-surface));
  border-block-end: 1px solid var(--color-hairline);
}
.mkt-hero-inner {
  max-width: 1140px;
  margin-inline: auto;
  padding: var(--space-12) var(--space-6);
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: var(--space-12);
  align-items: center;
}
.mkt-hero-title {
  font-size: var(--text-3xl);
  line-height: 1.12;
  letter-spacing: -0.02em;
  font-weight: 700;
  color: var(--color-ink-900);
  margin-block-end: var(--space-4);
}
.mkt-hero-lede {
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--color-text-muted);
  margin-block-end: var(--space-6);
  max-width: 46ch;
}
.mkt-hero-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.mkt-hero-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-block-start: var(--space-6);
}
.mkt-hero-badges span {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  padding-inline-start: var(--space-4);
  position: relative;
}
.mkt-hero-badges span::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-success);
  transform: translateY(-50%);
}
/* Decorative dashboard-preview card */
.mkt-hero-card {
  background: var(--color-card);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-5);
}
.mkt-hero-card-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--color-hairline);
  font-size: var(--text-base);
  color: var(--color-text-muted);
}
.mkt-hero-card-row strong { color: var(--color-text); font-weight: 600; }
.mkt-hero-card-row .mkt-pos { color: var(--color-success); }
.mkt-hero-card-bar {
  margin-block-start: var(--space-4);
  height: 8px;
  border-radius: 999px;
  background: var(--color-surface);
  overflow: hidden;
}
.mkt-hero-card-bar span { display: block; height: 100%; background: linear-gradient(90deg, var(--color-amber), var(--color-amber-600)); }
.mkt-hero-card-note { margin-block-start: var(--space-3); font-size: var(--text-sm); color: var(--color-text-muted); }

/* Feature grid */
.mkt-section { max-width: 1140px; margin-inline: auto; padding: var(--space-12) var(--space-6); }
.mkt-section-head { max-width: 620px; margin-block-end: var(--space-8); }
.mkt-section-title {
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--color-ink-900);
  margin-block-end: var(--space-3);
}
.mkt-section-sub { font-size: var(--text-md); line-height: 1.6; color: var(--color-text-muted); }
.mkt-features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-5);
}
.mkt-feature {
  background: var(--color-card);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  transition: box-shadow var(--motion-fast), transform var(--motion-fast), border-color var(--motion-fast);
}
.mkt-feature:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); border-color: #C7CBD2; }
.mkt-feature h3 { font-size: var(--text-lg); font-weight: 600; color: var(--color-ink); margin-block-end: var(--space-2); }
.mkt-feature p { font-size: var(--text-base); line-height: 1.6; color: var(--color-text-muted); }

/* Stat band */
.mkt-stats { background: var(--color-ink-900); color: #fff; }
.mkt-stats-inner {
  max-width: 1140px;
  margin-inline: auto;
  padding: var(--space-12) var(--space-6);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-8);
}
.mkt-stat { text-align: center; }
/* Light indigo on the dark stat band — the solid accent fails contrast
   against near-black (see .credentials-box note). */
.mkt-stat-value { display: block; font-size: var(--text-3xl); font-weight: 700; color: #A5B4FC; }
.mkt-stat-label { display: block; margin-block-start: var(--space-2); font-size: var(--text-sm); color: rgba(255, 255, 255, 0.72); }

/* Closing CTA */
.mkt-cta { max-width: 1140px; margin-inline: auto; padding: var(--space-12) var(--space-6); }
.mkt-cta-inner {
  background:
    radial-gradient(700px 300px at 100% 0%, rgba(99, 102, 241, 0.22), transparent 65%),
    linear-gradient(180deg, var(--color-ink), var(--color-ink-900));
  border-radius: var(--radius-lg);
  padding: var(--space-12) var(--space-8);
  text-align: center;
  color: #fff;
  box-shadow: var(--shadow-lg);
}
.mkt-cta-title { font-size: var(--text-2xl); font-weight: 700; margin-block-end: var(--space-3); }
.mkt-cta-sub { color: rgba(255, 255, 255, 0.78); margin-block-end: var(--space-6); }
.mkt-cta .mkt-hero-actions { justify-content: center; }
.mkt-cta .mkt-btn-ghost { background: rgba(255, 255, 255, 0.08); color: #fff; border-color: rgba(255, 255, 255, 0.24); }
.mkt-cta .mkt-btn-ghost:hover { background: rgba(255, 255, 255, 0.16); }

/* Content pages (About/Careers/Contact/… — centered reading column) */
.mkt-page { max-width: 780px; margin-inline: auto; padding: var(--space-12) var(--space-6); }
.mkt-page-hero { margin-block-end: var(--space-8); }
.mkt-page-title {
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--color-ink-900);
  margin-block-end: var(--space-3);
}
.mkt-page-lede { font-size: var(--text-lg); color: var(--color-text-muted); line-height: 1.6; }
.mkt-prose h2 { font-size: var(--text-xl); font-weight: 600; color: var(--color-ink); margin-block: var(--space-8) var(--space-3); }
.mkt-prose h3 { font-size: var(--text-md); font-weight: 600; color: var(--color-ink); margin-block: var(--space-6) var(--space-2); }
.mkt-prose p { color: var(--color-text); line-height: 1.75; margin-block-end: var(--space-4); }
.mkt-prose ul, .mkt-prose ol { margin-block-end: var(--space-4); padding-inline-start: var(--space-6); color: var(--color-text); line-height: 1.75; }
.mkt-prose li { margin-block-end: var(--space-2); }
.mkt-prose a { color: var(--color-ink-700); text-decoration: underline; text-underline-offset: 2px; }
.mkt-prose a:hover { color: var(--color-amber-600); }
.mkt-updated { font-size: var(--text-sm); color: var(--color-text-muted); margin-block-end: var(--space-6); }

/* Reusable stat/contact blocks inside content pages. */
.marketing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-block: var(--space-4) var(--space-6);
}
.marketing-stat {
  padding: var(--space-5);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-card);
}
.marketing-stat-value { font-size: var(--text-2xl); font-weight: 700; display: block; color: var(--color-ink); }
.marketing-stat-label { color: var(--color-text-muted); font-size: var(--text-sm); }
.marketing-contact-card {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-5);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-card);
  margin-block: var(--space-4) var(--space-6);
}

/* Footer */
.mkt-footer { background: var(--color-card); border-block-start: 1px solid var(--color-hairline); }
.mkt-footer-inner {
  max-width: 1140px;
  margin-inline: auto;
  padding: var(--space-12) var(--space-6) var(--space-8);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8) var(--space-12);
  justify-content: space-between;
}
.mkt-footer-brand { max-width: 320px; }
.mkt-footer-brand .mkt-brand { margin-block-end: var(--space-3); }
.mkt-footer-tagline { font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.6; }
.mkt-footer-cols { display: flex; flex-wrap: wrap; gap: var(--space-8) var(--space-12); }
.mkt-footer-heading {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin-block-end: var(--space-3);
}
.mkt-footer-col a {
  display: block;
  color: var(--color-text);
  text-decoration: none;
  margin-block-end: var(--space-2);
  font-size: var(--text-base);
}
.mkt-footer-col a:hover { color: var(--color-amber-600); }
.mkt-footer-legal {
  max-width: 1140px;
  margin-inline: auto;
  padding: var(--space-5) var(--space-6);
  border-block-start: 1px solid var(--color-hairline);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

@media (max-width: 820px) {
  .mkt-nav {
    position: absolute;
    top: 100%;
    inset-inline: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--color-card);
    border-block-end: 1px solid var(--color-hairline);
    padding: var(--space-3);
    box-shadow: var(--shadow-md);
    display: none;
  }
  .mkt-nav-toggle:checked ~ .mkt-nav { display: flex; }
  .mkt-nav-link { padding: var(--space-3); }
  .mkt-nav-cta { margin-inline-start: 0; margin-block-start: var(--space-2); text-align: center; }
  .mkt-nav-burger {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    margin-inline-start: auto;
    width: 40px;
    height: 40px;
    padding: var(--space-2);
    border-radius: var(--radius-sm);
    cursor: pointer;
  }
  .mkt-nav-burger span { display: block; height: 2px; background: var(--color-ink); border-radius: 2px; }
  .mkt-header-inner { position: relative; }
  .mkt-hero-inner { grid-template-columns: 1fr; gap: var(--space-8); padding-block: var(--space-8); }
  .mkt-hero-card { order: -1; }
  .mkt-hero-title { font-size: var(--text-2xl); }
}

/* ============================================================
   Phase 1 (issue #248) — Global UX foundation
   Additive polish + accessibility that cascades to every page.
   Nothing here overrides existing component geometry; it only
   layers reduced-motion safety, richer focus, and reusable
   utilities for later phases.
   ============================================================ */

/* Native controls/scrollbars render with correct light intent. */
:root { color-scheme: light; }

/* Smooth in-page scrolling (quick-nav anchors) + quieter mobile taps. */
html { scroll-behavior: smooth; }
body { -webkit-tap-highlight-color: transparent; }

/* Branded text selection — amber wash, ink text. */
::selection {
  background: color-mix(in srgb, var(--color-amber) 30%, transparent);
  color: var(--color-ink-900);
}

/* Refined focus ring on interactive non-input elements, using the new
   `--focus-ring` token. Same specificity (0,1,0) as the base
   `:focus-visible` rule at the top of this file — later in the cascade,
   so these elements get the soft ring while inputs/selects (`.input:focus`,
   `.select-trigger:focus-visible`, both higher-specificity) keep their own
   existing focus treatment untouched. */
:where(a, button, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* Radius for the ring at ZERO specificity (`:where()` throughout), so any
   component's own border-radius (`.btn` = --radius-md, `.nav-item` =
   --radius-sm, …) always wins; only bare, radius-less links/buttons pick
   this up so their ring has softened corners. */
:where(a, button, [tabindex]):where(:focus-visible) {
  border-radius: var(--radius-sm);
}

/* Primary-CTA micro-interaction — the same subtle lift cards already do
   on hover, so the main action feels equally responsive. `.btn-primary`
   already transitions `transform` via `.btn`'s transition list. */
.btn-primary:hover { transform: translateY(-1px); }
.btn-primary:active { transform: translateY(0); }

/* Card hover elevation now uses the dedicated `--shadow-hover` token —
   equal specificity to (and after) the existing `:hover` rules, so it only
   strengthens the hover shadow; geometry/transform unchanged. */
.card:hover,
.stat-card:hover,
.action-required-card:hover {
  box-shadow: var(--shadow-hover);
}

/* Structured empty-state sub-elements — the `.empty-state` base (earlier in
   this file, with its generic `::before` icon) is deliberately NOT
   redeclared here, so its 68+ existing call sites are untouched. Later
   phases opt into richer empty states by adding these children. */
.empty-state-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-text-muted);
  display: grid;
  place-items: center;
  margin-inline: auto;
  margin-block-end: var(--space-3);
}

.empty-state-icon svg { width: 22px; height: 22px; }

.empty-state-title {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-ink-900);
  max-width: 360px;
  margin-inline: auto;
}

.empty-state-desc {
  color: var(--color-text-muted);
  max-width: 360px;
  margin-inline: auto;
  margin-block-start: var(--space-2);
}

/* An explicit icon slot replaces the base rule's generic `::before` icon
   (which would otherwise render twice); current call sites have no
   `.empty-state-icon`, so nothing changes today. */
.empty-state:has(.empty-state-icon)::before { display: none; }

/* Reusable page toolbar — one flex row for a page's filters/actions;
   `.toolbar-spacer` pushes trailing actions to the end (flex, so RTL-safe). */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-block-end: var(--space-5);
}

.toolbar-spacer { flex: 1; }

/* Skeleton-loading placeholder for future async/loading states. The shimmer
   direction is purely decorative, so a physical translateX is safe under RTL. */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--color-hairline);
  border-radius: var(--radius-sm);
}

.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--color-card) 55%, transparent),
    transparent
  );
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
}

@keyframes skeleton-shimmer {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* Danger button variant for destructive primary actions (remove, terminate,
   reject) — later phases adopt it; complements `.btn-inline.is-danger`. */
.btn-danger {
  background: var(--color-danger);
  color: #fff;
  box-shadow: var(--shadow-sm);
}

.btn-danger:hover {
  background: color-mix(in srgb, var(--color-danger) 85%, #000);
  box-shadow: var(--shadow-md);
}

/* Outline-danger variant of the secondary button — a destructive action
   that shouldn't shout like the solid `.btn-danger` (Disable, Archive). */
.btn-secondary.is-danger {
  color: var(--color-danger);
  border-color: color-mix(in srgb, var(--color-danger) 35%, var(--color-hairline));
}

.btn-secondary.is-danger:hover {
  background: var(--color-danger-surface);
  border-color: var(--color-danger);
}

/* Auto-width button utility (issue #248). `.btn-primary` is full-width by
   default (`width: 100%`, for a form's main submit); a toolbar/inline action
   or empty-state CTA that should size to its own label opted out with an
   ad-hoc `style="width:auto;"` on ~250 call sites. This tokenizes that into a
   single reusable modifier. Declared after `.btn-primary` so equal-specificity
   source order wins — a `.btn.btn-primary.btn-auto` renders auto-width. */
.btn-auto { width: auto; }

/* ==========================================================================
   Phase 2 (issue #248) — Union Admin portal component layer.

   A cohesive set of modern, reusable widgets that adopt the Phase 1 tokens
   and build on the existing `.card`/`.table`/`.subnav`/`.stat-card` system
   without redeclaring any of it — every rule below is a NEW class, so no
   existing geometry/aesthetic changes and there's no regression surface.
   Logical properties throughout (RTL-safe). Adopted first by the Union
   Admin list/detail/dashboard pages; available to every portal after.
   ========================================================================== */

/* --- Responsive table wrapper -------------------------------------------
   Wrap any `.table` so it scrolls horizontally on a narrow screen instead
   of overflowing the card / breaking the layout — the one genuinely missing
   piece in the existing table system. Rounded to match the card it sits in,
   with a subtle scroll cue. Safe to wrap every table with. */
.table-wrap {
  inline-size: 100%;
  overflow-x: auto;
  border-radius: var(--radius-md);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

/* 560px min so a narrow-content table still forces its OWN scroll region on
   a phone (Phase 3, Resident & Vendor lists) rather than cramping; on any
   container wider than that the table's own `width:100%` fills it, so this
   is a no-op on desktop and non-regressing for the Union Admin tables. */
.table-wrap .table { min-inline-size: 560px; }

/* Optional sticky header for long scrolling tables. */
.table.is-sticky thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  background: color-mix(in srgb, var(--color-ink-900) 4%, var(--color-card));
  box-shadow: inset 0 -1px 0 var(--color-hairline);
}

/* Right-aligned numeric column helper (amounts, counts). */
.table .num { text-align: end; font-variant-numeric: tabular-nums; }

/* --- Filter bar ---------------------------------------------------------
   A compact, surface-backed inline filter row for a list page's GET form —
   lighter than wrapping each list's filters in a full `.form-section`
   panel. Fields sit inline and wrap gracefully; on mobile they stack. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-5);
  background: var(--color-glass-surface);
  border: 1px solid var(--color-glass-border);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(var(--blur-glass));
  -webkit-backdrop-filter: blur(var(--blur-glass));
}

.filter-bar-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-inline-size: 160px;
  max-inline-size: 240px;
  flex: 0 1 200px;
}

.filter-bar-field .label { margin-block-end: 0; }

/* Filter labels are quiet uppercase micro-labels, visually distinct from a
   data-entry form's labels, so a filter bar reads as one crisp toolbar
   rather than a loose scattering of form fields (issue #316). */
.filter-bar .label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* A field that should absorb leftover space (a free-text search box) opts
   back into growing; everything else keeps a disciplined, even width so a
   bar with only one or two controls doesn't stretch them absurdly wide. */
.filter-bar .filter-grow {
  flex: 1 1 240px;
  max-inline-size: 380px;
}

.filter-bar .btn { flex: 0 0 auto; }
.filter-bar-spacer { flex: 1 1 auto; }

@media (max-width: 640px) {
  .filter-bar-field,
  .filter-bar .field,
  .filter-bar .filter-grow { flex-basis: 100%; max-inline-size: none; }
}

/* --- Search field (input with a leading icon) --------------------------- */
.search-field { position: relative; display: block; }

.search-field .search-field-icon {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: var(--space-3);
  transform: translateY(-50%);
  inline-size: 16px;
  block-size: 16px;
  color: var(--color-text-muted);
  pointer-events: none;
}

.search-field .input { padding-inline-start: calc(var(--space-6) + var(--space-2)); }

/* --- Filter chips -------------------------------------------------------
   Pill toggles for quick, one-tap list filtering (status/category), an
   alternative to a `<select>` when the option set is small. Anchor-based
   so they work with no JS (each links to a filtered querystring). */
.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-end: var(--space-5);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  border: 1px solid var(--color-hairline);
  background: var(--color-card);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  line-height: 1.6;
  transition: background var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
}

.chip:hover { border-color: var(--color-ink); color: var(--color-ink-900); }

.chip.is-active {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: #fff;
}

.chip .chip-count {
  font-size: var(--text-xs);
  padding: 0 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-ink) 8%, transparent);
  color: inherit;
}

.chip.is-active .chip-count { background: rgba(255, 255, 255, 0.22); }

/* --- Segmented control --------------------------------------------------
   A grouped in-page toggle (e.g. list-view vs. calendar-view). Distinct
   from `.subnav`, which navigates between pages; this switches a mode. */
.seg {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-ink) 6%, transparent);
  border: 1px solid var(--color-glass-border);
}

.seg-item {
  padding: var(--space-1) var(--space-4);
  border-radius: calc(var(--radius-md) - 3px);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: background var(--motion-fast), color var(--motion-fast);
}

.seg-item:hover { color: var(--color-ink-900); }

.seg-item.is-active {
  background: var(--color-card);
  color: var(--color-ink-900);
  box-shadow: var(--shadow-sm);
}

/* --- Stat-card trend delta ----------------------------------------------
   Small up/down change indicator under a `.stat-card-value`. Complements
   the existing `.stat-card-icon` accent modifiers. */
.stat-card-trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-block-start: var(--space-1);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
}

.stat-card-trend svg { inline-size: 13px; block-size: 13px; }
.stat-card-trend.is-up { color: var(--color-success); }
.stat-card-trend.is-down { color: var(--color-danger); }

/* --- Detail description list --------------------------------------------
   A responsive key/value list for detail pages, an alternative to a
   `.field-grid` when the values are short label→value pairs stacked in
   rows with hairline separators. */
.data-list { display: flex; flex-direction: column; }

.data-list-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--color-hairline);
}

.data-list-row:last-child { border-block-end: none; }

.data-list-row dt {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.data-list-row dd {
  margin: 0;
  text-align: end;
  font-weight: 600;
  color: var(--color-ink-900);
}

/* Grouped secondary sub-values (e.g. a contact's phone/email under a name)
   stack under the primary value instead of running inline. */
.data-list-row dd .field-help {
  display: block;
  font-weight: 400;
}

/* --- Table loading skeleton ---------------------------------------------
   Rows of shimmer bars for an async/slow list, reusing the Phase 1
   `.skeleton` shimmer. Purely opt-in markup. */
.table-skeleton { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-2) 0; }
.table-skeleton .skeleton { height: 16px; }
.table-skeleton .skeleton:nth-child(3n) { inline-size: 60%; }
.table-skeleton .skeleton:nth-child(3n+1) { inline-size: 85%; }

/* =======================================================================
   Phase 4 (issue #248) — Super Admin + general-marketplace widgets.
   Every rule below is a NEW class/modifier that builds on the existing
   design system without redeclaring any of it, so no existing geometry or
   aesthetic changes and there's no regression surface. Logical properties
   throughout (RTL-safe).
   ======================================================================= */

/* Compact `.stat-card-value` modifier — for a stat card showing a short
   text value (a date, a country/currency/language label) rather than a big
   count, so those pages no longer need an inline `font-size` override. */
.stat-card-value.is-sm {
  font-size: var(--text-lg);
  line-height: 1.3;
  letter-spacing: -0.01em;
}

/* Detail-page `.stat-card-value` modifier — the smaller `--text-md` size a
   record's meta cards use for a text value (a ticket's reporter/location,
   an event's start/end, a vendor's tax id), replacing the per-card inline
   `font-size: var(--text-md)` those detail pages previously repeated. The
   `.is-danger` variant tints a value (e.g. a breached SLA due date) red
   without an inline `color` override. */
.stat-card-value.is-md {
  font-size: var(--text-md);
  line-height: 1.3;
}
.stat-card-value.is-danger { color: var(--color-danger); }
/* Matching label tint for a "needs attention" metric (outstanding money,
   SLA breaches) — replaces the inline `style="color: …"` those stat cards
   carried. */
.stat-card-label.is-danger { color: var(--color-danger); }

/* --- Detail photo strip -------------------------------------------------
   A wrapping row of fixed-size thumbnails for a record's photos (a
   marketplace listing's gallery), each opening full-size. Replaces the
   per-call-site inline styling those strips used before. */
.detail-media {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-end: var(--space-4);
}

.detail-media a {
  display: block;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-border);
  transition: box-shadow var(--motion-fast), transform var(--motion-fast);
}

.detail-media a:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }

.detail-media img {
  display: block;
  inline-size: 140px;
  block-size: 140px;
  object-fit: cover;
}

/* --- Price lead ---------------------------------------------------------
   The prominent price line on a listing detail page. */
.price-lead {
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--color-ink-900);
  margin-block-end: var(--space-2);
}

/* --- Message thread (chat bubbles) -------------------------------------
   A modern chat layout for a buyer↔seller conversation: the current
   user's own messages align to the end in an inked bubble, the other
   party's to the start in a surface bubble. Logical properties keep it
   correct under RTL. Purely opt-in markup, no existing thread changes. */
.msg-thread {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-block-end: var(--space-5);
}

.msg { display: flex; flex-direction: column; max-inline-size: 78%; }
.msg.is-own { align-self: flex-end; align-items: flex-end; }

.msg-bubble {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--color-card);
  border: 1px solid var(--color-hairline);
  color: var(--color-ink-900);
  line-height: 1.5;
}

.msg-bubble p { margin: 0; }

.msg.is-own .msg-bubble {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: #fff;
}

.msg-meta {
  margin-block-start: 3px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* =======================================================================
   Phase 5 (issue #248) — wider adoption of the Phase 2 filter widgets.
   The shared date-range partial (`partials/date_range_fields.html`) emits
   plain `.field` rows, so a list-page GET form that includes it couldn't
   fully adopt `.filter-bar` in Phase 2. These rules normalize a `.field`
   ONLY when it sits inside a `.filter-bar`, so those forms can now use the
   compact glass filter bar too — the global `.field` used on every real
   form elsewhere is untouched (new nesting context, no redeclaration), so
   there's no regression surface. Logical properties throughout (RTL-safe).
   ======================================================================= */
.filter-bar .field {
  margin: 0;
  min-inline-size: 160px;
  flex: 0 1 200px;
  max-inline-size: 240px;
}

.filter-bar .field .label { margin-block-end: var(--space-1); }

/* An inline help line (e.g. the custom-range hint) spans the full bar and
   drops its top margin so it hugs the fields above it. */
.filter-bar .field-help {
  flex-basis: 100%;
  margin-block-start: 0;
}

/* Back-to-top floating action button (Phase 18) — a modern long-page
   affordance. Rendered hidden in every layout (templates/layouts/base.html)
   and only ever revealed by JS (widgets.js `enhanceScrollTop`) once it can
   action the scroll, so it is never a dead control without JS (same
   progressive-enhancement convention as `.alert-dismiss`). RTL-safe: it
   anchors to `inset-inline-end`, flipping to the start edge for ur/ar. */
.scroll-top {
  position: fixed;
  inset-block-end: var(--space-6);
  inset-inline-end: var(--space-6);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  border: 1px solid var(--color-glass-border);
  border-radius: 999px;
  background: var(--color-glass-surface);
  -webkit-backdrop-filter: blur(var(--blur-glass));
  backdrop-filter: blur(var(--blur-glass));
  color: var(--color-ink);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  /* Hidden until scrolled past the threshold — off-screen + non-interactive. */
  opacity: 0;
  transform: translateY(var(--space-4));
  pointer-events: none;
  transition: opacity var(--motion-base) ease, transform var(--motion-base) ease,
    box-shadow var(--motion-fast) ease;
}

.scroll-top.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.scroll-top:hover {
  box-shadow: var(--shadow-hover);
  transform: translateY(-2px);
}

.scroll-top svg {
  inline-size: 20px;
  block-size: 20px;
}

/* Spacing utilities — tokenized replacements for the residual ad-hoc inline
   margin-block overrides on cards/sections/rows (same "single source of truth"
   treatment Phases 15/17 gave stat-cards and button widths). Values are the
   existing --space-* tokens verbatim, so there is no visual change. Logical
   properties (block-start/block-end) keep them RTL-safe. */
.mbs-1 { margin-block-start: var(--space-1); }
.mbs-2 { margin-block-start: var(--space-2); }
.mbs-3 { margin-block-start: var(--space-3); }
.mbs-4 { margin-block-start: var(--space-4); }
.mbs-5 { margin-block-start: var(--space-5); }
.mbs-6 { margin-block-start: var(--space-6); }
.mbe-1 { margin-block-end: var(--space-1); }
.mbe-2 { margin-block-end: var(--space-2); }
.mbe-3 { margin-block-end: var(--space-3); }
.mbe-4 { margin-block-end: var(--space-4); }
.mbe-5 { margin-block-end: var(--space-5); }
.mbe-6 { margin-block-end: var(--space-6); }

/* Password show/hide toggle (Phase 22) — progressive enhancement.
   widgets.js wraps each password <input class="input"> in .password-field and
   injects the .password-toggle button, so it is never a dead control without JS.
   RTL-safe: the button sits at inset-inline-end and the input reserves matching
   padding-inline-end (flips to the start edge for ur/ar). */
.password-field {
  position: relative;
  display: block;
}
.password-field .input {
  /* Reserve room for the toggle at the inline-end edge. */
  padding-inline-end: calc(var(--space-6) + var(--space-4));
}
.password-toggle {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: var(--space-2);
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--space-6);
  block-size: var(--space-6);
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color var(--motion-fast), background var(--motion-fast);
}
.password-toggle:hover {
  color: var(--color-ink);
  background: color-mix(in srgb, var(--color-ink) 8%, transparent);
}
.password-toggle svg {
  inline-size: 18px;
  block-size: 18px;
}
/* Show the "eye" by default; swap to "eye-off" once the password is revealed. */
.password-toggle .icon-hide { display: none; }
.password-toggle.is-visible .icon-show { display: none; }
.password-toggle.is-visible .icon-hide { display: inline-flex; }

/* ---------- Character counter (Phase 23, progressive enhancement) ----------
   enhanceCharCounters() in widgets.js injects a .char-counter after any
   input/textarea.input[maxlength], revealed only as the value nears the limit
   so short entries stay uncluttered. Never present without JS. RTL-safe. */
.char-counter {
  margin-block-start: var(--space-1);
  font-size: var(--text-xs);
  line-height: 1.2;
  color: var(--color-text-muted);
  text-align: end;
  font-variant-numeric: tabular-nums;
  transition: color var(--motion-fast);
}
.char-counter.is-near { color: var(--color-warning); }
.char-counter.is-full { color: var(--color-danger); font-weight: 600; }

/* ---------- Confirmation dialog (Phase 24, progressive enhancement) ----------
   enhanceConfirmForms() in widgets.js intercepts the submit of any form that
   carries a `data-confirm` message and shows this modern, focus-trapped modal
   in place of the browser's jarring native confirm(). Without JS the inline
   `onsubmit="return confirm(...)"` still runs, so it is never a dead control.
   Injected at the end of <body>, so it escapes any ancestor stacking context.
   RTL-safe (logical properties throughout). */
.confirm-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: rgba(14, 15, 18, 0.5);
  opacity: 0;
  transition: opacity var(--motion-base);
}
/* The modal ships hidden (widgets.js sets `backdrop.hidden = true` until a
   confirm form is submitted). The `display: flex` above is an author rule and
   would otherwise override the UA `[hidden] { display: none }`, leaving an
   invisible (`opacity: 0`) but full-viewport `position: fixed; inset: 0`
   overlay that silently swallows every click on the page — so hidden must
   explicitly win. */
.confirm-backdrop[hidden] { display: none; }
.confirm-backdrop.is-open { opacity: 1; }

.confirm-dialog {
  inline-size: min(30rem, 100%);
  max-block-size: calc(100vh - 2 * var(--space-6));
  overflow-y: auto;
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  background: var(--color-card);
  box-shadow: var(--shadow-lg);
  transform: translateY(8px) scale(0.98);
  opacity: 0;
  transition: transform var(--motion-base), opacity var(--motion-base);
}
.confirm-backdrop.is-open .confirm-dialog { transform: none; opacity: 1; }

.confirm-dialog-head {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}
.confirm-dialog-icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--radius-full, 999px);
  background: var(--color-danger-surface);
  color: var(--color-danger);
}
.confirm-dialog-icon svg { inline-size: 22px; block-size: 22px; }
.confirm-dialog-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-ink-900);
}
.confirm-dialog-msg {
  margin-block: var(--space-2) 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}
.confirm-dialog-actions {
  display: flex;
  gap: var(--space-3);
  justify-content: flex-end;
  flex-wrap: wrap;
  margin-block-start: var(--space-6);
}

/* ── Print stylesheet ────────────────────────────────────────────────────
   A finance/community app is printed constantly — bills, receipts, monthly
   statements, ledgers, ticket/booking records. Without this, Ctrl+P renders
   the dark sidebar gradient, glass-blur cards, nav chrome, the back-to-top
   FAB, filter bars and every action button, wasting ink and mangling the
   page. This collapses the app to a clean, single-column, black-on-white
   document. Purely additive: it only takes effect at print time, so there's
   no screen regression surface. Two opt-in utilities (`.no-print` /
   `.print-only`) let templates fine-tune later without touching this block. */
@media print {
  /* Unwrap the two-column app shell into a single flowing document. */
  .app-shell { display: block; height: auto; }
  .main {
    height: auto;
    overflow: visible;
    max-width: none;
    inline-size: 100%;
    margin-inline: 0;
    padding: 0;
    background: none;
  }
  body {
    background: #fff;
    color: #000;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* Hide app chrome and every interactive-only control — none of it belongs
     on paper. */
  .sidebar,
  .mobile-topbar,
  .sidebar-backdrop,
  .app-header,
  .subnav,
  .scroll-top,
  .filter-bar,
  .toolbar,
  .pill-filter-row,
  .chip-group,
  .seg,
  .btn,
  .copy-btn,
  .password-toggle,
  .char-counter,
  .alert-dismiss,
  .confirm-backdrop,
  form[method="get"],
  .no-print {
    display: none !important;
  }

  /* Opt-in content that should appear only on paper (e.g. a print header). */
  .print-only { display: revert !important; }

  /* Flatten glass cards to plain bordered blocks that don't split across a
     page. */
  .card,
  .stat-card,
  .data-list,
  .table {
    box-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    background: #fff !important;
    break-inside: avoid;
  }
  .card,
  .stat-card {
    border: 1px solid #ccc !important;
    transform: none !important;
    margin-block-end: var(--space-4);
  }

  /* Let a wrapped table print in full instead of clipping to its scroll box. */
  .table-wrap { overflow: visible !important; }
  .table-wrap .table { min-inline-size: 0 !important; }
  .table th,
  .table td { border-color: #ccc !important; }
  thead { display: table-header-group; }   /* repeat headers across pages */
  tr, .stat-card, .msg { break-inside: avoid; }

  /* Links read as plain text on paper. */
  a { color: inherit; text-decoration: none; }

  /* Keep semantic colors (paid/overdue badges, alert tints) legible. */
  .badge,
  .alert,
  .alert-rich,
  .status-dot { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  @page { margin: 1.5cm; }
}

/* ============================================================
   Portal dashboard redesign (issue #276)
   A professional/modern uplift for the Union Admin, Resident/Owner
   and Vendor dashboards. Deliberately additive — it enhances the
   shared `.stat-card`/`.card`/`.action-required-card` components
   rather than any one template, so all three portals (and every
   other page that reuses them) gain a consistent, more polished
   feel with no per-template markup churn.
   ============================================================ */

/* ---- Hero header band ----
   Replaces the flat `.topbar` heading on a dashboard with a branded,
   gradient panel so the page opens with a clear, designed focal point
   instead of two lines of text floating on the background wash. Applied
   as an extra class alongside `.topbar`, so its flex layout is kept. */
.dashboard-hero {
  position: relative;
  overflow: hidden;
  align-items: center;
  padding: var(--space-6) var(--space-8);
  margin-block-end: var(--space-8);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-hairline);
  background: var(--color-card);
  box-shadow: var(--shadow-sm);
}

/* Thin brand accent down the leading edge — a small, deliberate detail that
   reads as "designed" rather than a plain panel. */
.dashboard-hero::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 4px;
  background: var(--color-amber);
}

.dashboard-hero .page-title { color: var(--color-ink-900); letter-spacing: -0.01em; }
.dashboard-hero .page-subtitle { color: var(--color-text-muted); }

/* ---- Stat card: colored accent + material icon badge ----
   Color-code each metric card by lifting the accent its icon already
   carries (`.is-amber`/`.is-success`/… on `.stat-card-icon`) up to the
   card itself via `:has()`, then paint a thin accent bar across the top.
   No template change: the accent classes already exist on the icons. */
.stat-card { position: relative; overflow: hidden; }
.stat-card:has(.stat-card-icon.is-amber) { --stat-accent: var(--color-amber-600); }
.stat-card:has(.stat-card-icon.is-success) { --stat-accent: var(--color-success); }
.stat-card:has(.stat-card-icon.is-danger) { --stat-accent: var(--color-danger); }
.stat-card:has(.stat-card-icon.is-terracotta) { --stat-accent: var(--color-terracotta); }

.stat-card::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 3px;
  background: var(--stat-accent, var(--color-ink));
  opacity: 0.9;
}

/* Give the icon badges a soft gradient fill + inset highlight so they
   read as a raised material chip rather than a flat tinted square. */
.stat-card-icon {
  background:
    linear-gradient(150deg,
      color-mix(in srgb, var(--stat-accent, var(--color-ink)) 24%, transparent),
      color-mix(in srgb, var(--stat-accent, var(--color-ink)) 10%, transparent));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 2px color-mix(in srgb, var(--stat-accent, var(--color-ink)) 24%, transparent);
}

.action-required-icon.is-danger {
  background:
    linear-gradient(150deg,
      color-mix(in srgb, var(--color-danger) 22%, #fff),
      color-mix(in srgb, var(--color-danger) 10%, #fff));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
}

/* ---- Interactive vs. static affordance ----
   The base rules lift every `.card`/`.stat-card` on hover, including
   non-clickable content cards — which reads as a cheap, undirected
   animation. Restrict the translate to genuinely interactive cards
   (anchor stat/content cards and the action-required queue tiles);
   a static card keeps only the shadow response. */
.card:hover,
.stat-card:hover { transform: none; }

a.card:hover,
a.stat-card:hover,
.action-required-card:hover { transform: translateY(-2px); }

/* ---- Section heading tick ----
   A short accent tick before a standalone `.section-title` groups the
   cards beneath it and echoes the stat-card accent language above. */
.section-title {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.section-title::before {
  content: "";
  width: 4px;
  height: 1.1em;
  border-radius: 2px;
  background: linear-gradient(var(--color-amber), var(--color-amber-600));
}

/* ============================================================
   Union Admin — Bills page redesign (issue #276)
   A full, professional/modern uplift of `/u/<slug>/bills/`, built on
   the shared design system (`.dashboard-hero`, `.stat-card`, `.filter-bar`,
   `.table`) so it stays consistent with the redesigned dashboards. Scoped
   to `.bills-view`/`.filter-panel`/`.btn-hero` so no other page shifts.
   ============================================================ */

/* Hero action button — the primary call-to-action on a page hero. The hero
   is now a light panel, so this is the solid near-black primary button (same
   language as `.btn-primary`). */
.btn-hero {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: #fff;
  background: var(--color-ink);
  border: 1px solid var(--color-ink);
}
.btn-hero svg { width: 16px; height: 16px; }
.btn-hero:hover {
  color: #fff;
  background: var(--color-ink-700);
  border-color: var(--color-ink-700);
  box-shadow: var(--shadow-md);
}

/* Resident & Vendor portal heroes keep their existing `.btn-primary` /
   `.btn-secondary` action markup rather than being rewritten to `.btn-hero`.
   With the light hero panel they already read correctly, so no glass override
   is needed anymore — they render as the normal primary/secondary buttons. */
/* The unread-count chip on a hero action (e.g. Marketplace → Messages)
   needs to stay legible against the glass button. */
.dashboard-hero .btn .badge-count {
  background: rgba(255, 255, 255, 0.9);
  color: var(--color-ink-900);
}

/* Filter bar promoted to a titled, solid-surface "panel": a full-width
   header row labels the controls so the bar reads as a deliberate filter
   card rather than a loose strip of inputs. */
.filter-panel { background: var(--color-card); box-shadow: var(--shadow-sm); }
.filter-panel-head {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-end: var(--space-1);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.filter-panel-head svg { width: 15px; height: 15px; color: var(--color-amber-600); }

/* A group of inline checkbox toggles inside a `.filter-panel` (e.g. the
   calendar month's entry-type filters) — one wrapping ROW of checks sitting
   on the same 40px control rhythm as the inputs beside it. The explicit
   `flex-direction: row` matters: `.filter-bar-field` sets `column`, which
   used to stack these checks into a tall misaligned pile (issue #316). */
.filter-checks {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: center;
  min-block-size: 40px;
  max-inline-size: none;
  flex: 1 1 auto;
}
.filter-check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  white-space: nowrap;
  cursor: pointer;
}

/* The results table gets a card wrapper with no inner padding so the table
   bleeds edge-to-edge inside its rounded surface. */
.table-card { padding: 0; overflow: hidden; }

/* Numeric columns (Total / Outstanding): right-aligned, tabular figures so
   the money lines up column-wise instead of drifting. */
.bills-table th.num,
.bills-table td.num {
  text-align: end;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bills-table td.num { font-weight: 600; color: var(--color-ink-900); }
.bills-table td.num.is-owed { color: var(--color-danger); }
/* The currency code de-emphasized next to the amount it annotates. */
.bills-table .ccy {
  font-weight: 500;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.bills-table td.num.is-owed .ccy { color: color-mix(in srgb, var(--color-danger) 70%, var(--color-text-muted)); }
/* Money received into the union ledger — a positive, success-tinted amount,
   the counterpart to `.is-owed`'s danger tint for money paid out. */
.bills-table td.num.is-in { color: var(--color-success); }
.bills-table td.num.is-in .ccy { color: color-mix(in srgb, var(--color-success) 70%, var(--color-text-muted)); }

/* Column headers as quiet uppercase labels — a common "professional table"
   convention that reads as more considered than plain sentence-case. */
.bills-table thead th {
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The apartment link leads each row: give it weight so it reads as the row's
   primary handle. */
.bills-table .unit-link { font-weight: 600; }

/* ============================================================
   Union Admin — Aging report page redesign (issue #276)
   Reuses the Bills page language (`.bills-view`/`.dashboard-hero`/
   `.filter-panel`/`.table-card`/`.bills-table`) so the two collections
   screens read as one family. Only the aging-specific extras live here,
   scoped to `.aging-view`/`.aging-table`.
   ============================================================ */

/* Every per-bucket cell is a money column, so lean on `.bills-table`'s
   tabular-num alignment; an empty bucket ("—") is de-emphasized so the eye
   tracks the columns that actually carry a balance. */
.aging-table .cell-empty { color: var(--color-border); }

/* The oldest, most-overdue buckets (61-90 / 90+) are the ones a collections
   follow-up cares about most — tint their column headers to match the
   terracotta/danger accent their stat cards already carry, so the table and
   the summary cards speak the same visual language. */
.aging-table thead th.is-warn-col { color: var(--color-terracotta); }
.aging-table thead th.is-danger-col { color: var(--color-danger); }

/* A quiet tinted header band on the ledger/aging/bills tables, so a long
   money table reads as a structured statement rather than a flat grid. */
.table-card .bills-table thead th {
  background: color-mix(in srgb, var(--color-ink-900) 4%, var(--color-card));
  border-block-end: 1px solid var(--color-hairline);
}

/* ============================================================
   Union Admin — Payment claims queue redesign (issue #276)
   A verification-queue card reads as a self-contained "review this"
   task: apartment + amount headline, a flag row, a quiet definition
   grid, and a clearly separated action footer (Confirm vs. Reject).
   Scoped to `.claim-*` so nothing else shifts.
   ============================================================ */
.claim-list { display: flex; flex-direction: column; gap: var(--space-4); }
.claim-card { padding: 0; overflow: hidden; }

/* Headline row: unit + period lead, the claimed amount is the focal figure. */
.claim-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-block-end: 1px solid var(--color-hairline);
}
.claim-unit { font-size: var(--text-base); font-weight: 700; }
.claim-sub { margin-block-start: 2px; font-size: var(--text-sm); color: var(--color-text-muted); }
.claim-amount {
  font-size: var(--text-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--color-ink-900);
}
.claim-amount .ccy { font-size: var(--text-xs); font-weight: 500; color: var(--color-text-muted); }

/* A danger leading edge draws the eye to a claim that's been waiting too long. */
.claim-card.is-aging { box-shadow: inset 3px 0 0 var(--color-danger), var(--shadow-sm); }

.claim-flags { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-3) var(--space-5) 0; }

/* Definition grid for the claim's details — quiet uppercase term, value
   beneath — instead of a loose field grid, so several claims scan cleanly. */
.claim-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4) var(--space-5);
  margin: 0;
  padding: var(--space-4) var(--space-5);
}
.claim-meta dt {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: 2px;
}
.claim-meta dd { margin: 0; font-size: var(--text-sm); color: var(--color-ink-900); }

/* Action footer: a tinted band separating "decide" from "read", with the
   reject controls inline and the two verbs clearly distinct. */
.claim-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-5);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border-block-start: 1px solid var(--color-hairline);
}
.claim-reject { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3); }
.claim-reject .field { margin: 0; }
.claim-confirm { display: flex; align-items: flex-end; }

/* ============================================================
   Union Admin redesign — Phase 1: app-shell chrome (issue #276)
   A professional/modern uplift of the frame every Union Admin page
   renders inside — the sidebar navigation, brand block, user chip,
   desktop app-header and mobile topbar. Pure CSS refinements to the
   existing shell classes (defined earlier in this file), so nothing
   in any of the 77 page templates changes and all portals that share
   this shell (Resident/Vendor) inherit the same uplift in lockstep.
   ============================================================ */

/* Slightly wider rail + a thin, self-colored scrollbar so the long
   nav list scrolls without a heavy default gutter interrupting the
   dark gradient. */
.app-shell { grid-template-columns: var(--sidebar-width) 1fr; }
/* The rail is a LIGHT surface since the 2026 redesign (issue #316) — the
   scrollbar/divider/user-chip values below were written for the old dark
   gradient (white rgba tints) and rendered literally invisible on white. */
.sidebar {
  padding-block: var(--space-5);
  scrollbar-width: thin;
  scrollbar-color: var(--color-hairline) transparent;
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb {
  background: var(--color-hairline);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.sidebar::-webkit-scrollbar-thumb:hover { background: #C7CBD2; background-clip: padding-box; }

/* Brand reads as a proper header: hairline divider + tightened rhythm. */
.sidebar .auth-brand {
  margin-block-end: var(--space-5);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--color-hairline);
}

/* Nav items become rounder, quieter chips; the whole rail shares one
   consistent rest/hover/active language driven by the Phase 1 tokens.
   The active item is a soft amber chip with an inset ring + a rounded
   leading marker (replacing the flat 2px border-inline-start) so it
   reads as a deliberately "selected" pill rather than a tinted box. */
.nav-item {
  position: relative;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--nav-fg);
  border-inline-start: 0;
  transition: background var(--motion-fast), color var(--motion-fast), box-shadow var(--motion-fast);
}
.nav-item:hover { background: var(--nav-hover-surface); color: var(--nav-fg-hover); }
.nav-item-icon { opacity: 0.72; transition: opacity var(--motion-fast); }
.nav-item:hover .nav-item-icon { opacity: 1; }

.nav-item.is-active {
  background: var(--nav-active-surface);
  color: var(--nav-active-fg);
  box-shadow: var(--nav-active-glow);
}
.nav-item.is-active .nav-item-icon { opacity: 1; }
/* Rounded leading marker for the active item — a solid accent tick (the
   old amber→terracotta gradient resolves to a jarring indigo→red under the
   redesigned tokens). */
.nav-item.is-active::before {
  content: "";
  position: absolute;
  inset-block: 22%;
  inset-inline-start: -6px;
  width: 3px;
  border-radius: 99px;
  background: var(--color-amber);
}

/* Notification count pill on a nav item — solid accent chip with white
   text (the old ink-on-accent pairing was tuned for the dark rail and
   fails contrast on the indigo accent). */
.nav-item-badge {
  min-width: 18px;
  height: 18px;
  padding-inline: 5px;
  display: inline-grid;
  place-items: center;
  border-radius: 99px;
  background: var(--color-amber);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
}

/* Footer user promoted from a loose row to a contained chip so the
   signed-in identity reads as a deliberate account block. */
.sidebar-user {
  margin-block-end: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-hairline);
}
.sidebar-user-avatar {
  width: 30px;
  height: 30px;
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--color-amber) 26%, transparent),
    color-mix(in srgb, var(--color-amber) 12%, transparent));
  color: var(--color-amber-600);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4);
}

/* Desktop utility header: quiet sticky bar so the bell + user stay in
   reach while a long list scrolls, with a hairline that separates it
   from the page wash below. */
.app-header {
  position: sticky;
  inset-block-start: calc(-1 * var(--space-8));
  z-index: 5;
  margin-block: calc(-1 * var(--space-8)) var(--space-6);
  margin-inline: calc(-1 * var(--space-8));
  padding: var(--space-4) var(--space-8);
  background: color-mix(in srgb, var(--color-surface) 82%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-block-end: 1px solid var(--color-hairline);
}

/* Mobile topbar: a neutral near-black band (the old gradient faded into a
   leftover teal `#061a1f` from before the redesign) with a soft shadow so
   it reads as a raised bar when scrolled. */
.mobile-topbar {
  background: linear-gradient(180deg, var(--color-ink), var(--color-ink-900));
  box-shadow: var(--shadow-sm);
}

/* ============================================================
   Union Admin redesign — Phase 2: Blocks & Apartments (issue #276)
   Brings the `block_*`/`apartment_*`/`residency_*` pages into the same
   professional/modern language the dashboards + Bills/Aging pages already
   use: the branded `.dashboard-hero` header, glass `.btn-hero` actions,
   `.table-card`-wrapped tables with the quiet uppercase `.bills-table`
   column treatment, and stat-card accents — all shared, global classes,
   so this phase adds almost no new CSS. The one genuinely new bit is a
   link that has to read on the dark hero band.
   ============================================================ */

/* A link inside the hero subtitle (e.g. the parent-block link on an
   apartment) — the light `.dashboard-hero .page-subtitle` colour would
   otherwise swallow a default-tinted anchor. Underline on hover keeps the
   affordance without a heavy always-on rule. */
.dashboard-hero .hero-link {
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  border-block-end: 1px solid rgba(255, 255, 255, 0.35);
}
.dashboard-hero .hero-link:hover { border-block-end-color: #fff; }

/* A `.table-card` that holds a `.card-header` (block/apartment detail) needs
   the header padded back in, since `.table-card` zeroes the card padding so
   the table can bleed edge-to-edge. */
.table-card > .card-header {
  padding: var(--space-5) var(--space-6);
  margin-block-end: 0;
  border-block-end: 1px solid var(--color-hairline);
}

/* ---------- Skip-to-content link (issue #316) ----------
   WCAG 2.4.1 bypass-blocks: the first tab stop on every page jumps a
   keyboard/screen-reader user straight past the sidebar's 20+ links to
   the page content. Visually hidden until focused, then a floating ink
   pill at the top start edge. RTL-safe (logical inset). */
.skip-link {
  position: fixed;
  inset-block-start: var(--space-3);
  inset-inline-start: var(--space-3);
  z-index: 100;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-ink);
  color: #fff;
  font-weight: 600;
  font-size: var(--text-sm);
  text-decoration: none;
  box-shadow: var(--shadow-lg);
  /* Hidden but focusable (not display:none, which would drop it from the
     tab order). */
  transform: translateY(calc(-100% - var(--space-6)));
  transition: transform var(--motion-fast);
}

.skip-link:focus-visible {
  transform: translateY(0);
  outline: none;
  box-shadow: var(--focus-ring), var(--shadow-lg);
}

/* ---------- Breadcrumbs (issue #316) ----------
   A quiet "where am I" trail above a detail page's header band, so a new
   user landing deep in the hierarchy (Blocks → Block A → Apt 12) can both
   orient and jump back up without hunting the sidebar — part of the same
   "difficult to understand for new users" complaint the clickable rows
   fixed. The separator is a direction-neutral "/" drawn by CSS, so the
   trail needs no per-item markup and reads correctly under RTL. */
.breadcrumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-block-end: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.breadcrumbs a {
  color: var(--color-text-muted);
  font-weight: 500;
  text-decoration: none;
  padding: 2px var(--space-1);
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast), background var(--motion-fast);
}

.breadcrumbs a:hover,
.breadcrumbs a:focus-visible {
  color: var(--color-ink-900);
  background: color-mix(in srgb, var(--color-ink) 6%, transparent);
  text-decoration: underline;
}

.breadcrumbs a + a::before,
.breadcrumbs a + span::before {
  content: "/";
  margin-inline-end: var(--space-1);
  color: var(--color-hairline);
  font-weight: 400;
}

.breadcrumbs [aria-current="page"] {
  display: inline-block; /* ellipsis needs a block container */
  color: var(--color-ink-900);
  font-weight: 600;
  padding: 2px var(--space-1);
  /* A long record name (ticket title, listing title) must not blow up the
     trail — clamp it and let the page title below carry the full text. */
  max-inline-size: 32ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.breadcrumbs:not(:has(a)) { display: none; } /* never render an orphan trail */

/* ---------- Numbered pagination (issue #316) ----------
   Rendered by `{% pagination page_obj %}` (core/templatetags/
   pagination_tags.py + partials/pagination.html) on every paginated list:
   previous/next controls around an elided page strip (1 … 4 5 6 … 12),
   replacing the bare Previous/Next text links. 32px square page targets,
   the current page a solid ink pill. RTL-safe: logical properties, and
   the direction chevrons mirror under `dir="rtl"`. */
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-4);
}

.pagination-pages {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-inline: var(--space-2);
}

.pagination-page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 32px;
  block-size: 32px;
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: background var(--motion-fast), color var(--motion-fast);
}

a.pagination-page:hover {
  background: var(--color-surface);
  color: var(--color-ink-900);
}

.pagination-page.is-current {
  background: var(--color-ink);
  color: #fff;
}

.pagination-ellipsis {
  min-inline-size: 24px;
  text-align: center;
  color: var(--color-text-muted);
  user-select: none;
}

.pagination-nav {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: 32px;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast);
}

.pagination-nav svg { inline-size: 14px; block-size: 14px; flex: none; }

a.pagination-nav:hover {
  background: var(--color-surface);
  border-color: #C7CBD2;
  box-shadow: var(--shadow-sm);
}

/* Disabled edge (first/last page): visible but clearly inert, so the two
   controls don't jump around as they appear/disappear between pages. */
span.pagination-nav[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The chevrons encode direction, so they must mirror for ar/ur. */
[dir="rtl"] .pagination-nav svg { transform: scaleX(-1); }

@media (max-width: 640px) {
  /* Keep prev/next reachable on a phone; drop their labels to icons. */
  .pagination-nav span { display: none; }
  .pagination-nav { min-inline-size: 32px; justify-content: center; }
}

/* ============================================================
   Issue #316 follow-up — detail-page inline forms & phantom classes
   A sweep of templates was still using class names that no CSS rule
   defined (agenda-list, amount-col, text-muted, …) or ad-hoc inline
   style="…" layout, so those pages rendered misaligned. Each class
   below is the shared replacement.
   ============================================================ */

/* A meeting's agenda: a numbered list of items with hairline dividers,
   matching `.comment-item`'s rhythm (union + resident meeting detail). */
.agenda-list {
  margin: 0;
  padding-inline-start: var(--space-5);
}

.agenda-list > li {
  padding-block: var(--space-4);
  border-block-end: 1px solid var(--color-hairline);
}

.agenda-list > li:last-child { border-block-end: none; padding-block-end: 0; }
.agenda-list > li:first-child { padding-block-start: 0; }

/* A `.table-actions` cluster that belongs under an item's own text (an
   agenda item's Edit/Remove) leads from the start edge instead of the
   trailing one — the same distinction `.form-actions--start` draws. */
.table-actions--start { justify-content: flex-start; }

/* A boolean toggle sitting inside an inline action form keeps the shared
   40px control rhythm instead of drifting above the row's baseline. */
.form-inline .check-field {
  margin-block-end: 0;
  min-block-size: 40px;
  flex: 0 1 auto;
}

/* A compact input for in-row forms (e.g. the residency-end date picker in
   a table's actions cell) — sized to sit beside `.btn-inline` chips. */
.input--compact {
  width: auto;
  padding: var(--space-1) var(--space-2);
  min-block-size: 30px;
  font-size: var(--text-sm);
}

/* A bare section heading between stacked queue cards (payment claims,
   vendor invoices, expense approvals): the `.card-header` type/hairline
   treatment without a card's own padding around it. */
.card-header--plain {
  padding: 0;
  margin-block-end: var(--space-3);
}

/* Numeric money columns: right-aligned tabular figures. Promoted from
   `.bills-table th.num` to every `.table` so the accounting/journal/report
   tables line up the same way the bills ledger does. */
.table th.num,
.table td.num {
  text-align: end;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Quiet secondary text (used on ledger sub-lines). */
.text-muted { color: var(--color-text-muted); }

/* A full-width button (sidebar sign-out / push toggle) — replaces the
   per-layout style="width:100%" overrides. */
.btn-block { width: 100%; }

/* A notification-center card: timestamp + mark-as-read action on one
   baseline-aligned footer row; unread cards flagged by an accent border. */
.notification-card { margin-block-end: var(--space-3); }
.notification-card.is-unread { border-color: var(--color-amber-600); }

.notification-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* CRITICAL a11y: honor prefers-reduced-motion — collapse every animation/
   transition (0.001ms rather than 0 so animationend/transitionend events
   still fire for any JS that waits on them) and cancel the hover lifts.
   Must stay the LAST rule in this file. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .card:hover, .stat-card:hover, .action-required-card:hover,
  a.card:hover, a.stat-card:hover,
  .btn-primary:hover, .btn:active, .scroll-top:hover { transform: none !important; }
  /* Keep the visible state's resting transform so the button doesn't sit
     shifted; only its motion is removed by the global collapse above. */
  .scroll-top.is-visible { transform: none !important; }
}
