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

/* =============================================================================
   8b. Buy Candidates weekly Heikin Ashi chart — .buy-candidate-chart
   Compact per-candidate weekly HA chart on the Buy Candidates cockpit. The
   candle colors are token-driven in JS (buy-candidates-chart.js reads
   --status-healthy / --status-risk) so green/red carry the same status meaning
   as everywhere else. This rule owns only the responsive chart region.
   Cross-device: fixed legible height that shrinks on small screens; the chart
   canvas sizes to this box and never overflows the card.
   ============================================================================= */
.buy-candidate-chart {
  position: relative;
  width: 100%;
  height: 180px;
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.buy-candidate-chart__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--text-muted);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  text-align: center;
  padding: var(--space-2);
}

/* Slightly shorter on phones so the readiness card stays scannable
   (Cross-Device policy — reflow, don't clip). */
@media (max-width: 768px) {
  .buy-candidate-chart {
    height: 150px;
  }
}


/* =============================================================================
   Buy Candidates cockpit — Daily verdict, Regime gate banner, Exposure meter
   (buy-candidates-discipline-redesign, Component C8 — R3 / R8 / R27 / R18).

   Shared, token-driven components: every value comes from tokens.css and every
   status colour is bound to a semantic status token via a meaning modifier
   (produced by ui_semantics.buy_posture_status). No hardcoded colours, no
   page-local tokens, no inline shared styles (UI Consistency Policy §1–4).
   Responsive by construction (flex-wrap + in-flow stacking) so the cockpit
   reflows without horizontal overflow on phone/tablet (Cross-Device Policy).
   ============================================================================= */

/* ── Daily one-line verdict (R8) — a single calm strip above everything ───── */
.smd-verdict {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-left: 3px solid var(--status-neutral);
  border-radius: var(--radius-md);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  color: var(--text-primary);
}
.smd-verdict__icon { font-size: 1.05em; line-height: 1; color: var(--text-muted); }
.smd-verdict__text { font-weight: var(--fw-medium); }
.smd-verdict__count { font-weight: var(--fw-bold); }
.smd-verdict__cta {
  display: inline-flex;
  align-items: center;
  min-height: 44px;                 /* ≥44px touch target (Req 19.4) */
  padding: var(--space-1) var(--space-3);
  margin-left: auto;
  color: var(--status-info);
  text-decoration: none;
  font-weight: var(--fw-semibold);
}
.smd-verdict__cta:hover,
.smd-verdict__cta:focus { text-decoration: underline; }
/* Meaning accents — reuse the semantic tokens, never a new colour. */
.smd-verdict--healthy { border-left-color: var(--status-healthy); }
.smd-verdict--caution { border-left-color: var(--status-caution); }
.smd-verdict--risk    { border-left-color: var(--status-risk); }
.smd-verdict--neutral { border-left-color: var(--status-neutral); }
.smd-verdict--info    { border-left-color: var(--status-info); }

/* ── Regime gate banner (R3) — full-width, colour-coded from Buy_Posture ──── */
.smd-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-left: 4px solid var(--status-neutral);
  border-radius: var(--radius-lg);
  font-family: var(--font-ui);
  color: var(--text-primary);
}
.smd-banner__icon {
  flex: 0 0 auto;
  font-size: var(--fs-xl);
  line-height: 1;
  color: var(--status-neutral);
}
.smd-banner__body { flex: 1 1 260px; min-width: 0; }
.smd-banner__title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
}
.smd-banner__text {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}
.smd-banner__unlock {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
/* Meaning modifiers — soft tint + accent bind to the semantic status tokens
   (BUY-posture: Healthy=entries OK, Caution=watch-only, Risk=no buys). */
.smd-banner--healthy {
  border-left-color: var(--status-healthy);
  background: linear-gradient(0deg, var(--status-healthy-soft), transparent 70%);
}
.smd-banner--healthy .smd-banner__icon { color: var(--status-healthy); }
.smd-banner--caution {
  border-left-color: var(--status-caution);
  background: linear-gradient(0deg, var(--status-caution-soft), transparent 70%);
}
.smd-banner--caution .smd-banner__icon { color: var(--status-caution); }
.smd-banner--risk {
  border-left-color: var(--status-risk);
  background: linear-gradient(0deg, var(--status-risk-soft), transparent 70%);
}
.smd-banner--risk .smd-banner__icon { color: var(--status-risk); }
.smd-banner--neutral { border-left-color: var(--status-neutral); }
.smd-banner--info {
  border-left-color: var(--status-info);
  background: linear-gradient(0deg, var(--status-info-soft), transparent 70%);
}
.smd-banner--info .smd-banner__icon { color: var(--status-info); }

/* ── Subsector/theme exposure meter (R27) — visibility only, no cap ───────── */
.smd-meter { display: flex; flex-direction: column; gap: var(--space-2); }
.smd-meter__row {
  display: grid;
  grid-template-columns: minmax(80px, 30%) 1fr auto;
  align-items: center;
  gap: var(--space-3);
}
.smd-meter__label {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.smd-meter__track {
  height: 10px;
  background: var(--bg-surface-2);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.smd-meter__bar {
  height: 100%;
  min-width: 2px;
  background: var(--status-info);   /* visibility-only accent, no status meaning */
  border-radius: var(--radius-sm);
}
.smd-meter__count {
  font-family: var(--font-ticker);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  min-width: 1.5em;
  text-align: right;
}

/* Narrow screens: let the verdict CTA wrap under the text instead of pushing
   the strip wider, and keep the meter rows legible (Cross-Device Policy). */
@media (max-width: 480px) {
  .smd-verdict__cta { margin-left: 0; }
  .smd-meter__label { white-space: normal; }
}

/* ── Cash-override gate (C6, R6) — weak-tape cash dimming + deliberate override.
   In Cautious/Defensive regime each candidate card is visually dimmed and its
   cash component reads "$0 — watch only" until the owner makes a conscious
   Cash_Override choice, which un-dims ONLY that card for the session. The
   override never touches the margin/leverage lock (R6.6 / R7.4) — this is a
   purely presentational gate. Opacity is a token-free primitive (allowed). */
.buy-candidate-row--dimmed { opacity: 0.55; }
.buy-candidate-row--dimmed:focus-within { opacity: 1; }  /* keyboard legibility */

.smd-cash-gate {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--status-caution-soft);
  border: 1px solid var(--status-caution);
  border-radius: var(--radius-sm);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
}
.smd-cash-gate__label {
  font-weight: var(--fw-semibold);
  color: var(--status-caution);
}
.smd-cash-gate__value {
  font-family: var(--font-ticker);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
/* The deliberate override / re-dim action — a real form submit, ≥44×44px. */
.smd-cash-gate__action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 44px;                 /* ≥44px touch target (Req 19.4) */
  padding: var(--space-1) var(--space-3);
  margin-left: auto;
  background: transparent;
  border: 1px solid var(--status-caution);
  border-radius: var(--radius-sm);
  color: var(--status-caution);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.smd-cash-gate__action:hover,
.smd-cash-gate__action:focus {
  background: var(--status-caution-soft);
  outline: var(--focus-ring);
}
/* When overridden, the strip reads as info (deliberate choice active), not a
   warning — same token layer, different meaning modifier. */
.smd-cash-gate--overridden {
  background: var(--status-info-soft);
  border-color: var(--status-info);
}
.smd-cash-gate--overridden .smd-cash-gate__label { color: var(--status-info); }
.smd-cash-gate--overridden .smd-cash-gate__action {
  border-color: var(--status-info);
  color: var(--status-info);
}
.smd-cash-gate--overridden .smd-cash-gate__action:hover,
.smd-cash-gate--overridden .smd-cash-gate__action:focus {
  background: var(--status-info-soft);
}
@media (max-width: 480px) {
  .smd-cash-gate__action { margin-left: 0; }
}

/* ── Entry-Ready vs Watching split (R5) + entry-discipline card (R9) ──────── */
/* A status group wraps the Entry-Ready or Watching rows within one instrument
   group. Its accent binds to the shared semantic tokens via the meaning
   modifier the classifier supplies (healthy = Entry-Ready, info = Watching) —
   never a page-local colour. */
.buy-status-group {
  border-left: 3px solid var(--status-neutral);
  padding-left: var(--space-3);
}
.buy-status-group--healthy { border-left-color: var(--status-healthy); }
.buy-status-group--info    { border-left-color: var(--status-info); }
.buy-status-group--caution { border-left-color: var(--status-caution); }
.buy-status-group--risk    { border-left-color: var(--status-risk); }
.buy-status-group--neutral { border-left-color: var(--status-neutral); }
.buy-status-group__heading {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
}
.buy-status-group--healthy .buy-status-group__cue { color: var(--status-healthy); }
.buy-status-group--info .buy-status-group__cue    { color: var(--status-info); }
.buy-status-group__count {
  font-family: var(--font-ticker);
  font-variant-numeric: tabular-nums;
  background: var(--bg-surface-2);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-2);
  color: var(--text-primary);
}
.buy-status-group__hint {
  font-weight: var(--fw-regular);
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-muted);
}

/* The entry-discipline panel inside the readiness card (entry sequence, cash +
   locked-margin split, staggered starter, entry-window nudge). */
.buy-entry-discipline {
  background: var(--bg-surface-2);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
}
.buy-candidate-row__unmet { color: var(--status-info); }

/* Entry-sequence ladder — ordered chips; the current stage is highlighted. */
.buy-entry-ladder { margin: 0; }
.buy-entry-ladder__step {
  font-size: var(--fs-xs, 0.75rem);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-3, var(--bg-surface-2));
  color: var(--text-muted);
  border: 1px solid transparent;
}
.buy-entry-ladder__step.is-current {
  background: var(--status-healthy-soft);
  color: var(--status-healthy);
  border-color: var(--status-healthy);
  font-weight: var(--fw-semibold);
}

/* Cross-device: keep the split groups and ladder legible on small screens. */
@media (max-width: 480px) {
  .buy-status-group { padding-left: var(--space-2); }
  .buy-entry-ladder__step { font-size: 0.72rem; }
}