/* =============================================================================
   StocksMD — Shared Component Layer  (components.css)
   -----------------------------------------------------------------------------
   Shared, reusable component styles for the StocksMD Design_System.

   RULES (enforced by design, Req 3.1 / 3.3 / 1.4):
     - Every color / spacing / radius / typography value is expressed via
       var(--token), referencing the canonical tokens defined in tokens.css.
       No raw hex, no ad-hoc pixel colors. (Motion timings and elevation
       shadows are the only non-tokenized primitives, matching the design.)
     - This file houses ONLY shared components. Page-specific layout stays in
       styles.css / screener.css / landing.css.
     - Loaded by base.html AFTER tokens.css and BEFORE styles.css so every page
       (legacy, screener, landing) inherits one canonical component set.

   Component inventory (design "components.css" section):
     1. .smd-card                     — standard card
     2. KPI tile family (.smd-kpi /   — four-up metric tiles w/ semantic accent
        .portfolio-box)
     3. .zone-chip                    — health-zone chip (BUY / HOLD / SELL)
     4. .smd-badge                    — status pills (meaning -> color+icon+label)
     5. .smd-table                    — shared dense data table
     6. .score-filters / .score-btn / — chart score + timeframe controls
        .time-filters
     7. .ticker-chip                  — ticker symbol preview chip (--font-ticker)
     8. .smd-chart-tooltip            — shared chart tooltip
     9. .smd-empty / .smd-loading /   — the four shared state patterns
        .smd-error / .smd-stale

   Spec: .kiro/specs/ui-ux-consistency-polish  (Req 1.4, 3.1, 3.3, 6.1, 6.6, 8.4)
   ============================================================================= */

/* =============================================================================
   1. Standard card — .smd-card
   Replaces the divergent .card definitions in styles.css and screener overrides.
   ============================================================================= */
.smd-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  color: var(--text-primary);
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

.smd-card:hover {
  transform: translateY(-2px);
  border-color: var(--border-strong);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.smd-card__title {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 var(--space-2);
}

.smd-card__body {
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  color: var(--text-secondary);
}

/* =============================================================================
   2. KPI tile family — .smd-kpi (and the legacy .portfolio-box alias)
   Token-driven metric tiles with a semantic left-border accent, replacing the
   gradient boxes on Market / Screener. The accent is set via modifier classes
   that map onto the semantic status colors (never new colors).
   ============================================================================= */
.smd-kpi {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-left: 3px solid var(--status-neutral);   /* default accent */
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  color: var(--text-primary);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.smd-kpi:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.smd-kpi__label {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 0;
}

.smd-kpi__value {
  font-family: var(--font-ui);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.smd-kpi__meta {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

/* Semantic accent modifiers — left border + soft tint keyed to Status_Meaning */
.smd-kpi--healthy {
  border-left-color: var(--status-healthy);
  background: linear-gradient(0deg, var(--status-healthy-soft), transparent 60%);
}
.smd-kpi--caution {
  border-left-color: var(--status-caution);
  background: linear-gradient(0deg, var(--status-caution-soft), transparent 60%);
}
.smd-kpi--risk {
  border-left-color: var(--status-risk);
  background: linear-gradient(0deg, var(--status-risk-soft), transparent 60%);
}
.smd-kpi--neutral {
  border-left-color: var(--status-neutral);
}
.smd-kpi--info {
  border-left-color: var(--status-info);
  background: linear-gradient(0deg, var(--status-info-soft), transparent 60%);
}

/* =============================================================================
   3. Health-zone chip — .zone-chip  (promoted from screener-local to shared)
   Encoding + thresholds are owned by the ui_semantics zone() helper; this is
   purely the presentational chip. BUY = healthy, HOLD = caution, SELL = risk.
   ============================================================================= */
.zone-chip {
  display: inline-block;
  font-family: var(--font-ticker);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  line-height: 1.4;
}
.zone-chip--buy {
  background: var(--status-healthy-soft);
  color: var(--status-healthy);
  border-color: var(--status-healthy);
}
.zone-chip--hold {
  background: var(--status-caution-soft);
  color: var(--status-caution);
  border-color: var(--status-caution);
}
.zone-chip--sell {
  background: var(--status-risk-soft);
  color: var(--status-risk);
  border-color: var(--status-risk);
}

/* =============================================================================
   4. Status pills — .smd-badge  (one badge system, meaning -> color+icon+label)
   Replaces scattered bg-success / bg-danger / inline pills. Each modifier binds
   to exactly one Status_Meaning; the icon (Font Awesome) + label carry the
   meaning beyond color for accessibility (Req 7.1).
   ============================================================================= */
.smd-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.4;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.smd-badge .smd-badge__icon {
  font-size: 0.9em;
  line-height: 1;
}

.smd-badge--healthy {
  background: var(--status-healthy-soft);
  color: var(--status-healthy);
  border-color: var(--status-healthy);
}
.smd-badge--caution {
  background: var(--status-caution-soft);
  color: var(--status-caution);
  border-color: var(--status-caution);
}
.smd-badge--risk {
  background: var(--status-risk-soft);
  color: var(--status-risk);
  border-color: var(--status-risk);
}
.smd-badge--neutral {
  background: var(--status-neutral-soft);
  color: var(--status-neutral);
  border-color: var(--status-neutral);
}
.smd-badge--info {
  background: var(--status-info-soft);
  color: var(--status-info);
  border-color: var(--status-info);
}

/* Phrase variant — for a compliance-safe signal PHRASE rather than a one-word
   pill (e.g. the selling-criteria exit-layer labels "Momentum fading — trim
   candidate"). Drops the uppercase/nowrap of the base badge so the phrase stays
   legible and WRAPS instead of forcing horizontal overflow on small screens
   (Cross-Device Policy). Colour + icon still come from the shared meaning
   modifiers above — this variant only relaxes typography, no colour of its own. */
.smd-badge--phrase {
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  font-weight: var(--fw-medium);
  align-items: flex-start;
  text-align: left;
  max-width: 100%;
}

/* =============================================================================
   5. Shared dense data table — .smd-table
   Generalized from the screener .data-table into a reusable sticky-header dense
   table. Screener-local density tokens (--row-*) are replaced by the canonical
   spacing scale so any page can adopt it.
   ============================================================================= */
.smd-table-wrap {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  overflow: auto;
  contain: layout;
}

.smd-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--text-primary);
}

.smd-table thead {
  position: sticky;
  top: 0;
  z-index: 2;
}

.smd-table thead th {
  background: var(--bg-surface-2);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-default);
  white-space: nowrap;
}

.smd-table thead th a {
  color: inherit;
  text-decoration: none;
}
.smd-table thead th a:hover {
  color: var(--text-primary);
}

.smd-table tbody td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  white-space: nowrap;
  vertical-align: middle;
}

.smd-table tbody tr {
  transition: background 0.06s ease;
}
.smd-table tbody tr:hover {
  background: var(--bg-surface-2);
}
.smd-table tbody tr.is-selected {
  background: var(--bg-hover);
  outline: 1px solid var(--status-info);
  outline-offset: -1px;
}

.smd-table .col-num {
  font-family: var(--font-ticker);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.smd-table .col-symbol {
  font-family: var(--font-ticker);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
}
.smd-table .col-symbol a {
  color: var(--text-primary);
  text-decoration: none;
}
.smd-table .col-symbol a:hover {
  color: var(--status-info);
}

/* Name/label cell link — legible primary text that highlights on hover
   (replaces per-page .text-white usage). */
.smd-table .col-name a {
  color: var(--text-primary);
  text-decoration: none;
}
.smd-table .col-name a:hover {
  color: var(--status-info);
}

/* Directional value coloring (meaning-based, tabular) */
.smd-table .val-up { color: var(--status-healthy); }
.smd-table .val-down { color: var(--status-risk); }
.smd-table .val-flat { color: var(--text-muted); }

/* =============================================================================
   6. Chart controls — .score-filters / .score-btn / .time-filters
   Consolidated from Market's and Regime's inline <style> blocks. Score buttons
   carry their own accent color via a --pill-color custom property (set per
   button) so the single rule set styles every score/overlay pill without
   per-button hardcoded hex.
   ============================================================================= */
.score-filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}

.score-btn {
  --pill-color: var(--status-info);          /* default accent, override inline */
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 44px;                           /* ≥44px touch target (Req 5.3) */
  padding: var(--space-2) var(--space-4);
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--pill-color);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.score-btn:hover:not(:disabled):not([aria-disabled="true"]) {
  background: var(--border-subtle);
  transform: translateY(-2px);
}

.score-btn.active {
  border-color: currentColor;
  background: var(--border-default);
}

.score-btn:disabled,
.score-btn[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
}

/* The small colored dot inside a score/overlay pill inherits the pill accent */
.score-btn .mie-pill-dot,
.mie-pill-dot {
  display: inline-block;
  width: var(--space-2);
  height: var(--space-2);
  border-radius: 50%;
  background: var(--pill-color);
}

/* Timeframe buttons (1M / 3M / 1Y ...) — a compact segmented control */
.time-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.time-filters button {
  min-height: 44px;                           /* ≥44px touch target (Req 5.3) */
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.time-filters button:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

/* Active timeframe pill. Both the plain .active state and the
   .chart-tf__btn--active class (toggled by mie-chart.js / chart.js) resolve to
   the same shared treatment, so market/regime no longer need an inline
   green !important override. Uses --status-healthy to preserve the existing
   green active look verbatim (behavior preservation, Req 10.3). */
.time-filters button.active,
.time-filters .chart-tf__btn--active,
.chart-tf__btn--active {
  background: var(--status-healthy-soft);
  color: var(--status-healthy);
  border-color: var(--status-healthy);
  font-weight: var(--fw-semibold);
}

/* =============================================================================
   6b. Chart header — .chart-placeholder .chart-header
   Consolidated from Market's inline <style> (the Previous / Current / Change
   AO summary row above the S&P 500 chart). Token-driven; .positive / .negative
   map onto the semantic healthy / risk colors. Reflows to a single column on
   narrow screens using the shared mobile breakpoint (no per-page media query
   in the template).

   Scoped under .chart-placeholder (the Market chart wrapper) on purpose: the
   plain `.chart-header` class is ALSO used by legacy styles.css (line ~1105,
   loaded AFTER this file) and by chart.css (the BEM stock-detail header). The
   .chart-placeholder scope raises specificity so this consolidated rule wins on
   the Market page — preserving the original inline panel appearance verbatim
   (Req 3.5 / 10.3) — without touching styles.css (a later migration task) and
   without matching historical_view (.chart-shell) or Regime (no chart-header).
   ============================================================================= */
.chart-placeholder .chart-header {
  display: flex;
  justify-content: space-around;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--bg-surface-2);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-4);
}

.chart-placeholder .chart-header span {
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.chart-placeholder .chart-header strong {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  margin-left: var(--space-1);
}

.chart-placeholder .chart-header .positive { color: var(--status-healthy); }
.chart-placeholder .chart-header .negative { color: var(--status-risk); }

/* Reflow: stack the summary row on narrow screens (Cross-Device policy). */
@media (max-width: 768px) {
  .chart-placeholder .chart-header {
    flex-direction: column;
    gap: var(--space-2);
  }
}

/* =============================================================================
   7. Ticker preview chip — .ticker-chip  (Req 8.4)
   Monospace ticker token, used for Saved Lists previews and anywhere a symbol
   is shown as a chip. Uses --font-ticker so symbols align and read cleanly.
   ============================================================================= */
.ticker-chip {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-ticker);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: 0.03em;
  color: var(--text-primary);
  background: var(--bg-surface-2);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}

.ticker-chip:hover {
  border-color: var(--border-strong);
  color: var(--status-info);
}

/* Horizontal, wrapping row of ticker chips (Saved Filters preview, etc.).
   Wraps on narrow screens instead of forcing horizontal overflow
   (Cross-Device policy) and overrides the dense-table cell's nowrap. */
.ticker-chip-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  white-space: normal;
}

.ticker-chip-row__more {
  font-family: var(--font-ticker);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Token-driven icon-only action button with a ≥44×44px touch target
   (Req 5.3 / Cross-Device policy). Neutral by default; the --danger modifier
   is used for destructive actions such as delete. */
.smd-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--fs-md);
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

/* Shared disabled state so inactive reorder arrows read as inactive
   consistently (opacity/cursor are allowed non-token primitives). */
.smd-icon-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.smd-icon-btn:hover,
.smd-icon-btn:focus-visible {
  color: var(--text-primary);
  background: var(--bg-hover);
}

.smd-icon-btn--danger:hover,
.smd-icon-btn--danger:focus-visible {
  color: var(--status-risk);
  background: var(--status-risk-soft);
  border-color: var(--status-risk);
}

/* =============================================================================
   8. Shared chart tooltip — .smd-chart-tooltip
   One token-driven tooltip style applied to ApexCharts and Lightweight Charts
   tooltips so the hover surface matches the rest of the app.
   ============================================================================= */
.smd-chart-tooltip {
  background: var(--bg-surface-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}

.smd-chart-tooltip__label {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  margin-bottom: var(--space-1);
}

.smd-chart-tooltip__value {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
}

.smd-chart-tooltip__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* =============================================================================
   9. Shared state patterns — .smd-empty / .smd-loading / .smd-error / .smd-stale
   Each renders identically across pages (Req 6.6): a consistent icon + text +
   token color. The reference for Empty_State is Alert Center's existing empty
   state (icon + calm copy + optional CTA), generalized here.
   ============================================================================= */

/* Shared scaffold for the centered icon+text patterns */
.smd-empty,
.smd-loading,
.smd-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-4);
  font-family: var(--font-ui);
}

.smd-empty__icon,
.smd-loading__icon,
.smd-error__icon {
  font-size: var(--fs-2xl);
  line-height: 1;
}

.smd-empty__title,
.smd-loading__title,
.smd-error__title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  margin: 0;
}

.smd-empty__text,
.smd-loading__text,
.smd-error__text {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin: 0;
  max-width: 42ch;
}

/* Empty_State — calm, neutral */
.smd-empty {
  color: var(--text-muted);
}
.smd-empty__icon {
  color: var(--status-neutral);
}

/* Optional CTA button shared by Empty_State and Error_State (retry affordance).
   Token-driven so it matches the shell rather than pulling in Bootstrap btns. */
.smd-empty__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;                 /* touch target ≥ 44px (Req 5.3) */
  padding: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  color: var(--status-info);
  background: transparent;
  border: 1px solid var(--status-info);
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: background 0.15s ease, transform 0.15s ease;
}
.smd-empty__cta:hover,
.smd-empty__cta:focus-visible {
  background: var(--status-info-soft);
  color: var(--status-info);
}

/* Loading_State — informational, with a spinning indicator */
.smd-loading {
  color: var(--text-muted);
}
.smd-loading__icon {
  color: var(--status-info);
  animation: smd-spin 0.9s linear infinite;
}

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

/* Error_State — risk-toned but calm, with a retry affordance where applicable */
.smd-error {
  color: var(--text-secondary);
}
.smd-error__icon {
  color: var(--status-risk);
}

/* Stale_Data_State — the "data not current — last updated {date}" badge.
   Visual treatment only; freshness is decided by the existing
   stocks/utils.py::market_scores_freshness rule (Signal Consistency Policy —
   NO parallel freshness rule is introduced here). Caution-toned so a stale
   section is never shown in unqualified Healthy color (Req 6.7). */
.smd-stale {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--status-caution);
  background: var(--status-caution-soft);
  border: 1px solid var(--status-caution);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}

.smd-stale__icon {
  font-size: 0.9em;
  line-height: 1;
  color: var(--status-caution);
}

/* Stale ROW / CARD treatment (buy-candidate-trust-hardening P1d, row-level
   extension). When a per-stock row is frozen (the shared staleness predicate —
   Stock.updated_at behind the latest data cycle), its derived numbers (price +
   Health + CCI + RV + …) are recomputed from the same frozen HistoricalData and
   are equally suspect. This is the SHARED, token-driven treatment (no hardcoded
   color, no page-local badge) that de-emphasizes those numbers and adds a
   caution-toned edge alongside the shared `.smd-stale` "as of {date}" badge, so
   a frozen Health/indicator is never presented as current. Reused by the
   screener rows/cards and the Buy Candidates surface consistently. */
.stock-card--stale {
  border-left: 3px solid var(--status-caution);
}
/* Derived numbers on a stale card/row read as caution-muted, never as an
   unqualified current value. Opacity is a token-free primitive (allowed);
   colors stay token-driven. */
.stock-card--stale .card-stats,
.smd-table tr.is-stale td {
  opacity: 0.72;
}

/* =============================================================================
   10. App_Shell — sticky navbar + sticky footer  (base.html)
   Replaces the legacy absolute-positioned navbar and its 140px content offset
   with a single sticky navbar in normal document flow, plus a shared footer
   pinned to the bottom on short pages via a min-height:100vh flex column.
   Spec: ui-ux-consistency-polish  (Req 4.1, 4.2, 4.5, 4.6, 4.7, 5.3, 9.1-9.5)
   ============================================================================= */

/* Sticky-footer layout: body is a full-height flex column; the footer's
   margin-top:auto pushes it to the bottom without wrapping page content
   (preserves body > .container ancestry that the screener relies on). */
body.app-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ── Sticky navbar ───────────────────────────────────────────────────────── */
.app-navbar {
  position: sticky;
  top: 0;
  z-index: 1030;
  width: 100%;
  padding: var(--space-3) 0;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-default);
}

.app-navbar .navbar-brand {
  display: inline-flex;
  align-items: center;
  padding: 0;
  margin-right: var(--space-6);
}

.app-navbar .navbar-brand img {
  height: 34px;
  width: auto;
  max-width: 160px;
  display: block;
}

/* Nav links — token-driven, with ≥44×44px touch targets (Req 5.3). */
.app-navbar .navbar-nav .nav-link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
}

.app-navbar .navbar-nav .nav-link:hover,
.app-navbar .navbar-nav .nav-link:focus {
  color: var(--text-primary);
}

/* Active-page indication (Req 4.3): color + underline indicator. When
   active_nav matches no link, nothing is highlighted (Req 4.4). */
.app-navbar .navbar-nav .nav-link.is-active {
  color: var(--status-healthy);
  font-weight: var(--fw-semibold);
  box-shadow: inset 0 -2px 0 0 var(--status-healthy);
}

/* Toggler — ≥44×44px touch target for mobile collapse (Req 4.5, 5.3). */
.app-navbar .navbar-toggler {
  min-width: 44px;
  min-height: 44px;
  border-color: var(--border-default);
}

/* ── Shared footer ───────────────────────────────────────────────────────── */
.app-footer {
  margin-top: auto;               /* pin to the bottom on short pages          */
  background: var(--bg-surface);
  border-top: 1px solid var(--border-default);
  padding: var(--space-8) 0 var(--space-6);
  color: var(--text-muted);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
}

.app-footer__top {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: var(--space-5);
}

.app-footer__logo {
  height: 28px;
  width: auto;
  max-width: 150px;
  display: block;
}

.app-footer__tagline {
  margin: var(--space-2) 0 0;
  color: var(--text-secondary);
}

.app-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
}

.app-footer__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text-muted);
  text-decoration: none;
}

.app-footer__links a:hover,
.app-footer__links a:focus {
  color: var(--text-primary);
}

.app-footer__disclaimer {
  margin: 0 0 var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

.app-footer__copy {
  margin: 0;
  color: var(--text-disabled);
  font-size: var(--fs-xs);
}
