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

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: #B9C7C8; }

/* ---------- 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(232, 161, 61, 0.12), transparent 40%),
    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: var(--color-amber);
  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%;
  padding: var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.5);
  color: var(--color-text);
  transition: background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast);
}

.input:focus {
  background: var(--color-card);
  border-color: var(--color-ink);
  box-shadow: 0 0 0 3px rgba(14, 58, 67, 0.12);
  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);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: none;
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-weight: 600;
  font-size: var(--text-base);
  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); }

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

.btn-primary {
  background: var(--color-ink);
  color: #fff;
  width: 100%;
  box-shadow: var(--shadow-sm);
}

.btn-primary:hover { background: var(--color-ink-700); box-shadow: var(--shadow-md); }

.btn-secondary {
  background: transparent;
  color: var(--color-ink);
  border: 1px solid var(--color-hairline);
}

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

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

.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='%235C7275' 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);
}

/* ---------- 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: linear-gradient(180deg, var(--color-ink-900), #061a1f 85%);
  color: #fff;
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow-y: auto;
  border-inline-end: 1px solid rgba(255, 255, 255, 0.06);
}

.sidebar .auth-brand { margin-block-end: var(--space-8); }
.sidebar .auth-brand-name { color: #fff; }
.sidebar .auth-brand-sub { color: rgba(255, 255, 255, 0.55); }

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: rgba(255, 255, 255, 0.68);
  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: rgba(255, 255, 255, 0.06);
  color: #fff;
}

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

.nav-item.is-active {
  background: rgba(232, 161, 61, 0.14);
  color: var(--color-amber);
  border-inline-start-color: var(--color-amber);
}

.nav-item.is-active .nav-item-icon { opacity: 1; }

.sidebar-footer {
  margin-block-start: auto;
  padding-block-start: var(--space-4);
  border-block-start: 1px solid rgba(255, 255, 255, 0.1);
}

.sidebar-user {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: rgba(255, 255, 255, 0.85);
  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: rgba(232, 161, 61, 0.16);
  color: var(--color-amber);
  font-weight: 600;
  font-size: var(--text-xs);
}

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

/* Dark variant so the Sign out button reads on the sidebar's dark gradient
   instead of rendering `.btn-secondary`'s ink-on-transparent styling
   near-invisible (same pattern as the `.select-trigger` dark variant
   below). */
.sidebar .btn-secondary {
  gap: var(--space-2);
  color: rgba(255, 255, 255, 0.85);
  border-color: rgba(255, 255, 255, 0.18);
}

.sidebar .btn-secondary:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
}

.main {
  padding: var(--space-8);
  max-width: 1100px;
  width: 100%;
  margin-inline: auto;
  height: 100vh;
  overflow-y: auto;
  background:
    radial-gradient(circle at 90% 0%, rgba(232, 161, 61, 0.06), transparent 45%),
    radial-gradient(circle at 0% 20%, rgba(14, 58, 67, 0.05), transparent 40%);
}

.topbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-block-end: var(--space-6);
  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;
  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);
}

.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);
  text-align: end;
}

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

.stat-card-value {
  font-family: var(--font-mono);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--color-ink-900);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  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-md);
  font-weight: 500;
  color: var(--color-text-muted);
  font-variant-numeric: normal;
  margin-inline-start: 0.25em;
}

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

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-4);
  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);
}

.table th {
  text-align: start;
  color: var(--color-text-muted);
  font-weight: 600;
  padding: var(--space-2) var(--space-3);
  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(14, 58, 67, 0.025); }
.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-ink-700);
  text-decoration: underline;
}

.badge {
  display: inline-flex;
  align-items: center;
  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;
}

.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(8, 32, 38, 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(8, 32, 38, 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;
  gap: var(--space-1);
  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;
  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:hover {
  background: var(--color-surface);
  border-color: #B9C7C8;
  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='%235C7275' 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; }
.credentials-box .btn-inline {
  background: transparent;
  border-color: rgba(232, 161, 61, 0.5);
  color: var(--color-amber);
}
.credentials-box .btn-inline:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: var(--color-amber);
  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: var(--color-amber);
  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: rgba(232, 161, 61, 0.14);
  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);
}

.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(14, 58, 67, 0.08);
}

/* ---------- 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: rgba(255, 255, 255, 0.1);
  color: #fff;
  cursor: pointer;
  transition: background var(--motion-fast);
}

.sidebar-toggle:hover { background: rgba(255, 255, 255, 0.18); }
.sidebar-toggle svg { width: 20px; height: 20px; }

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(8, 32, 38, 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%;
  padding: var(--space-3);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.5);
  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: #B9C7C8; }

.select-widget.is-open .select-trigger,
.select-trigger:focus-visible {
  background: var(--color-card);
  border-color: var(--color-ink);
  box-shadow: 0 0 0 3px rgba(14, 58, 67, 0.12);
  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: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.16);
  color: #fff;
}

.sidebar .select-trigger:hover { border-color: rgba(255, 255, 255, 0.32); }

.sidebar .select-trigger-icon, .sidebar .select-trigger-chevron {
  color: rgba(255, 255, 255, 0.55);
}

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

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

.dropzone:hover, .dropzone.is-dragover, .dropzone:focus-within {
  border-color: var(--color-ink);
  background: rgba(255, 255, 255, 0.6);
}

.dropzone.is-dragover { box-shadow: 0 0 0 3px rgba(14, 58, 67, 0.12); }

.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: rgba(232, 161, 61, 0.14);
  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-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(14, 58, 67, 0.05); }
.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: rgba(14, 58, 67, 0.08);
  color: var(--color-ink);
  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: #B9C7C8; 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(232, 161, 61, 0.16), 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: #D5DEDE; }
.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; }
.mkt-stat-value { display: block; font-size: var(--text-3xl); font-weight: 700; color: var(--color-amber); }
.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(232, 161, 61, 0.18), 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);
}

/* 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: 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;
  flex: 1 1 160px;
}

.filter-bar-field .label { margin-block-end: 0; }
.filter-bar .btn { flex: 0 0 auto; }
.filter-bar-spacer { flex: 1 1 auto; }

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

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

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

/* --- 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: 1 1 160px;
  max-inline-size: 220px;
}

.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(8, 32, 38, 0.5);
  opacity: 0;
  transition: opacity var(--motion-base);
}
.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-glass-border);
  background:
    radial-gradient(circle at 100% 0%, rgba(232, 161, 61, 0.20), transparent 55%),
    radial-gradient(circle at 0% 120%, rgba(181, 88, 58, 0.18), transparent 50%),
    linear-gradient(135deg, var(--color-ink-900), var(--color-ink-700));
  box-shadow: var(--shadow-md);
}

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

.dashboard-hero .page-title { color: #fff; letter-spacing: -0.01em; }
.dashboard-hero .page-subtitle { color: rgba(255, 255, 255, 0.72); }

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

/* ============================================================
   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 — a translucent "glass" button legible on the dark
   gradient hero band, replacing the light `.btn-secondary` that washed out. */
.btn-hero {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: #fff;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.28);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.btn-hero svg { width: 16px; height: 16px; }
.btn-hero:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.45);
}

/* Resident & Vendor portal heroes keep their existing `.btn-primary` /
   `.btn-secondary` action markup rather than being rewritten to `.btn-hero`
   like the Union pages were. Give any such button sitting directly inside a
   hero the same translucent glass treatment automatically, so it reads
   cleanly on the dark gradient without touching 39 templates. No Union hero
   contains these classes (they use `.btn-hero`), so this never regresses
   the Union portal. */
.dashboard-hero .btn-secondary,
.dashboard-hero .btn-primary {
  color: #fff;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.28);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.dashboard-hero .btn-primary {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.42);
}
.dashboard-hero .btn-secondary:hover,
.dashboard-hero .btn-primary:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.24);
  border-color: rgba(255, 255, 255, 0.5);
}
/* 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) — wraps as one flexible field. */
.filter-checks { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.filter-check { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); }

/* 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; }
.sidebar {
  padding-block: var(--space-5);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.16) transparent;
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.14);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.sidebar::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.24); 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 rgba(255, 255, 255, 0.08);
}

/* 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. */
.nav-item.is-active::before {
  content: "";
  position: absolute;
  inset-block: 22%;
  inset-inline-start: -6px;
  width: 3px;
  border-radius: 99px;
  background: linear-gradient(var(--color-amber), var(--color-terracotta));
}

/* Notification count pill on a nav item, tuned for the dark rail. */
.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: var(--color-ink-900);
  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: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
}
.sidebar-user-avatar {
  width: 30px;
  height: 30px;
  background: linear-gradient(150deg, rgba(232, 161, 61, 0.28), rgba(232, 161, 61, 0.12));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

/* 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 matches the sidebar's gradient instead of a flat block,
   and casts a soft shadow so it reads as a raised bar when scrolled. */
.mobile-topbar {
  background: linear-gradient(180deg, var(--color-ink-900), #061a1f);
  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);
}

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