/* ============================================================
   VoiceInsights Africa — Shared Component Primitives (Milestone A)
   Only components confirmed absent from style.css: alerts, tabs,
   modal/dialog, pagination, stepper, timeline, dropdown, a
   filter/search bar, and semantic evidence/decision/recommendation
   card variants. Buttons, cards, badges, tables, forms and
   skeleton loaders already exist in style.css and are reused as-is.
   Load this AFTER style.css and tokens.css.
   ============================================================ */

/* ---------- Alert ---------- */
.alert {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  font-size: .88rem; line-height: 1.5;
}
.alert-icon { flex: none; font-size: 1.1rem; line-height: 1; }
.alert-title { font-weight: 700; margin-bottom: .2em; }
.alert-info    { border-color: rgba(79,164,144,.35); background: rgba(79,164,144,.08); }
.alert-success { border-color: rgba(111,191,139,.35); background: var(--success-bg); color: var(--success); }
.alert-warn    { border-color: rgba(228,194,58,.35); background: var(--warn-bg); color: var(--warn); }
.alert-danger  { border-color: rgba(217,99,74,.35); background: var(--danger-bg); color: var(--danger); }

/* ---------- Tabs ---------- */
.tabs { display: flex; gap: var(--space-2); overflow-x: auto; border-bottom: 1px solid var(--border); padding-bottom: 0; }
.tab {
  flex: none; padding: var(--space-3) var(--space-4); border: none; background: transparent;
  color: var(--text-dim); font-weight: 700; font-size: .85rem; cursor: pointer;
  border-bottom: 2px solid transparent; transition: color var(--motion-base), border-color var(--motion-base);
}
.tab:hover { color: var(--text-muted); }
/* Wave 2: the shipped Workspace tab strip marks its selection with .is-active,
   so the canonical selected style answers to both names rather than forcing a
   JS rename that would risk the behaviour this wave is not allowed to change. */
/* The LABEL takes --accent-text (3.09:1 -> 4.88:1 on a light surface); the
   underline keeps the brand marigold, because a 2px rule is not text and has
   no ratio to meet. Selection is therefore still marked twice — by weight and
   by the rule — so it never rests on colour alone. */
.tab.active,
.tab.is-active { color: var(--accent-text); border-bottom-color: var(--accent); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* ---------- Modal / Dialog ---------- */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.55); display: none;
  align-items: center; justify-content: center; padding: var(--space-5); z-index: 200;
}
.modal-overlay.open { display: flex; }
.modal-dialog {
  width: min(560px, 100%); max-height: 88vh; overflow: auto;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius);
  padding: var(--space-6); box-shadow: var(--elevation-3);
}
.modal-dialog h2 { margin-top: 0; }
.modal-close { position: absolute; top: var(--space-4); right: var(--space-4); background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: 1.1rem; }

/* ---------- Pagination ---------- */
.pagination { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.pagination button {
  min-width: 2.2rem; height: 2.2rem; padding: 0 .6rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: transparent; color: var(--text-muted);
  cursor: pointer; font-size: .82rem; font-weight: 700;
}
.pagination button.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.pagination button:disabled { opacity: .35; cursor: not-allowed; }

/* ---------- Stepper ---------- */
.stepper { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.step { display: flex; align-items: center; gap: var(--space-2); color: var(--text-dim); font-size: .82rem; font-weight: 700; }
.step-index {
  width: 1.6rem; height: 1.6rem; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); font-size: .76rem;
}
.step.done .step-index { background: var(--success-bg); color: var(--success); border-color: transparent; }
.step.current .step-index { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.step.current { color: var(--text); }
.step-connector { width: 1.5rem; height: 1px; background: var(--border-strong); }

/* ---------- Timeline ---------- */
.timeline { position: relative; padding-left: var(--space-5); border-left: 2px solid var(--border-strong); }
.timeline-item { position: relative; margin-bottom: var(--space-5); }
.timeline-item::before {
  content: ""; position: absolute; left: calc(-1 * var(--space-5) - 5px); top: .3rem;
  width: 10px; height: 10px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px var(--bg);
}
.timeline-date { font-size: .74rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em; font-weight: 700; }
.timeline-title { font-weight: 700; margin: .2em 0; }

/* ---------- Dropdown ---------- */
.dropdown { position: relative; display: inline-block; }
.dropdown-menu {
  position: absolute; top: calc(100% + 6px); right: 0; min-width: 200px;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius);
  box-shadow: var(--elevation-2); padding: var(--space-2); display: none; z-index: 60;
}
.dropdown-menu.open { display: block; }
.dropdown-item {
  display: block; width: 100%; text-align: left; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  background: none; border: none; color: var(--text); font-size: .85rem; cursor: pointer;
}
.dropdown-item:hover { background: var(--surface-2); }

/* ---------- Filter / search bar ---------- */
.filter-bar { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; margin-bottom: var(--space-5); }
.search-field { position: relative; flex: 1 1 240px; min-width: 0; }
.search-field input {
  width: 100%; padding: .6rem .9rem .6rem 2.1rem; border-radius: 999px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text); font-size: .85rem;
}
.search-field input:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); }
.search-field::before {
  content: "🔎"; position: absolute; left: .8rem; top: 50%; transform: translateY(-50%); font-size: .8rem; opacity: .6; pointer-events: none;
}

/* ---------- Semantic card variants (thin wrappers on .card) ---------- */
.evidence-card, .decision-card, .recommendation-card {
  border-radius: var(--radius); border: 1px solid var(--border); background: var(--surface);
  padding: var(--space-4); border-left-width: 4px; border-left-style: solid;
}
.evidence-card       { border-left-color: var(--accent-2); }
.decision-card        { border-left-color: var(--accent); }
.recommendation-card  { border-left-color: var(--success); }
.evidence-card b, .decision-card b, .recommendation-card b { display: block; margin-bottom: .3em; }

@media (max-width: 640px) {
  .tabs { gap: var(--space-1); }
  .modal-dialog { padding: var(--space-4); }
  .filter-bar { flex-direction: column; align-items: stretch; }
  .filter-bar .search-field { flex: 0 0 auto; width: 100%; }
}

/* ---------- Product foundation ---------- */
.visually-hidden {
  position: absolute !important; width: 1px !important; height: 1px !important;
  padding: 0 !important; margin: -1px !important; overflow: hidden !important;
  clip: rect(0 0 0 0) !important; clip-path: inset(50%) !important;
  white-space: nowrap !important; border: 0 !important;
}
.section-header { max-width: 720px; margin-bottom: var(--space-6); }
.section-header.centered { margin-inline: auto; text-align: center; }
.section-header h2 { margin: .45rem 0 .75rem; font-size: var(--text-2xl); }
.section-header p { font-size: var(--text-lg); line-height: 1.65; }

.capability-card, .data-product-card, .trust-card, .industry-card, .kpi-card, .dashboard-panel, .chart-panel {
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  background: var(--surface-base); padding: var(--space-5); box-shadow: var(--elevation-1);
}
.capability-card h3, .data-product-card h3, .trust-card h3, .industry-card h3 { margin: .65rem 0 .45rem; font-size: 1.05rem; }
.component-icon { width: 2.35rem; height: 2.35rem; display: inline-flex; align-items: center; justify-content: center; border-radius: 12px; background: rgba(228,162,58,.12); color: var(--accent); }
.component-icon svg { width: var(--icon-md); height: var(--icon-md); }

.format-list { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: var(--space-4); }
.format-badge { display: inline-flex; align-items: center; min-height: 1.75rem; padding: .25rem .55rem; border: 1px solid var(--border-subtle); border-radius: 999px; color: var(--text-secondary); font-size: var(--text-xs); font-weight: 700; }
.format-badge.planned { border-style: dashed; opacity: .72; }
.format-disclosure { margin-top: var(--space-3); color: var(--text-secondary); }
.format-disclosure summary { width: fit-content; cursor: pointer; color: var(--text-secondary); font-size: var(--text-xs); font-weight: 750; }
.format-disclosure summary:hover { color: var(--text-primary); }
.format-disclosure[open] summary { color: var(--accent); }
.format-disclosure .format-list { margin-top: var(--space-2); }
.download-action { display: inline-flex; align-items: center; gap: .45rem; margin-top: var(--space-4); color: var(--accent-2); font-weight: 700; font-size: var(--text-sm); }

.state-panel { border: 1px dashed var(--border-interactive); border-radius: var(--radius-lg); padding: var(--space-6); text-align: center; background: var(--surface-raised); }
.state-panel h3 { margin: .75rem 0 .35rem; }
.state-panel p { max-width: 48ch; margin-inline: auto; }
.state-loading { position: relative; overflow: hidden; }
.state-loading::after { content: ''; position: absolute; inset: 0; background: linear-gradient(90deg,transparent,rgba(255,255,255,.06),transparent); transform: translateX(-100%); animation: vi-shimmer 1.6s infinite; }
@keyframes vi-shimmer { to { transform: translateX(100%); } }

.workspace-preview { border: 1px solid var(--border-interactive); border-radius: var(--radius-xl); overflow: hidden; background: var(--surface-base); box-shadow: var(--elevation-3); }
.workspace-preview-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .85rem 1rem; border-bottom: 1px solid var(--border-subtle); background: var(--surface-raised); }
.workspace-preview-body { display: grid; grid-template-columns: 180px 1fr; min-height: 440px; }
.workspace-preview-nav { padding: 1rem; border-right: 1px solid var(--border-subtle); }
.workspace-preview-nav span { display: block; padding: .55rem .65rem; border-radius: 8px; color: var(--text-tertiary); font-size: var(--text-xs); font-weight: 700; }
.workspace-preview-nav span.active { color: var(--text-primary); background: rgba(228,162,58,.12); }
.workspace-preview-main { padding: var(--space-5); min-width: 0; }
.workspace-kpis { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: .75rem; }
.kpi-value { display: block; font-family: var(--font-display); font-size: 1.55rem; color: var(--text-primary); }
.kpi-label { color: var(--text-tertiary); font-size: var(--text-xs); }
.preview-chart { display: flex; align-items: end; gap: .45rem; height: 150px; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border-subtle); }
.preview-chart span { flex: 1; min-width: 8px; height: var(--bar); border-radius: 6px 6px 2px 2px; background: linear-gradient(180deg,var(--accent),rgba(228,162,58,.22)); }

.breadcrumb-list { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; margin: 0 0 .25rem; padding: 0; list-style: none; color: var(--text-tertiary); font-size: var(--text-xs); }
.breadcrumb-list li:not(:last-child)::after { content: '/'; margin-left: .35rem; color: var(--border-interactive); }

@media (max-width: 860px) {
  .workspace-preview-body { grid-template-columns: 1fr; }
  .workspace-preview-nav { display: flex; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--border-subtle); }
  .workspace-preview-nav span { white-space: nowrap; }
  .workspace-kpis { grid-template-columns: repeat(2,minmax(0,1fr)); }
}
@media (max-width: 480px) {
  .workspace-preview-main { padding: var(--space-4); }
  .workspace-kpis { grid-template-columns: 1fr 1fr; }
}

/* ============================================================
   WAVE 3 — RESPONSIVE, FOCUS AND MOTION PRIMITIVES

   Added to the CANONICAL component layer rather than to pages, so a single
   definition serves every shell surface and Wave 3's guarantees cannot drift
   page by page. Everything below is token-based, which is what lets the theme
   switch centrally instead of per-component.
   ============================================================ */

/* ---------- Focus visibility ----------
   :focus-visible only, so a mouse click does not paint a ring while a keyboard
   user still gets one. The outline is never removed without a replacement -
   that is the failure mode this exists to prevent. */
.tab:focus-visible,
.modal-close:focus-visible,
.dropdown-item:focus-visible,
.search-field input:focus-visible,
.nav-item:focus-visible,
.skip-link:focus-visible,
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ---------- Table responsiveness ----------
   A scroll CONTAINER rather than shrinking columns: a table squeezed until the
   text wraps to one character per line is technically responsive and
   practically unreadable. Row actions stay reachable because the row keeps its
   width and the viewport moves. */
.table-scroll {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.table-scroll > table { min-width: 40rem; }

/* ---------- Dialog responsiveness ---------- */
.modal-dialog {
  max-width: min(48rem, calc(100vw - var(--space-5) * 2));
  max-height: calc(100vh - var(--space-6) * 2);
  overflow-y: auto;
}

/* ---------- Touch targets ----------
   Applied to the controls a thumb actually reaches on a phone. Dense desktop
   tables are deliberately excluded: enlarging every cell control there breaks
   the layout it is meant to help. */
@media (pointer: coarse) {
  .tab,
  .modal-close,
  .nav-item,
  .mobile-nav-toggle,
  #menu-toggle,
  .ew-copilot-launch,
  #via-launcher { min-height: 44px; min-width: 44px; }
}

/* ---------- Card and filter reflow ---------- */
@media (max-width: 720px) {
  .filter-bar { flex-wrap: wrap; }
  .filter-bar > * { flex: 1 1 100%; }
  .workspace-kpis { grid-template-columns: 1fr; }
  .tabs { gap: var(--space-1); }
}

/* ---------- Reduced motion ----------
   Motion is shortened rather than deleted: a transition of ~0 keeps state
   changes legible without animating them at anyone who asked us not to. */
@media (prefers-reduced-motion: reduce) {
  .tab,
  .modal-overlay,
  .modal-dialog,
  .dropdown-menu,
  .nav-item { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* ============================================================
   THE CROSS-CUTTING SOLUTION FAMILY
   ------------------------------------------------------------
   Gender, Protection & Safeguarding is a THEME applied across the
   four operational families, not a fifth product alongside them. The
   styling has to carry that distinction, because a card that looks
   exactly like its four neighbours reads as a fifth peer — and a
   buyer who reads it as a peer has been told a module exists that
   does not.

   Two devices do the whole job: the card spans the grid rather than
   sitting in a column, and it carries an accent edge the four
   operational cards do not. Nothing else changes, so it still
   obviously belongs to the same set.
   ============================================================ */
.solution-card-thematic {
  grid-column: 1 / -1;
  border-left: 3px solid var(--accent-2);
}
.solution-thematic-note {
  margin: .4rem 0 .9rem;
  font-size: .8rem;
  line-height: 1.65;
  color: var(--text-muted);
}

/* THE SAFETY BOUNDARY.
   Given real weight — its own ground and border — because it is the
   one paragraph on this page that a reader must not skim. It is
   deliberately NOT styled as a warning: this is an accurate statement
   of scope, and dressing it in alarm colours would read as a caveat
   we were forced into rather than a boundary we chose. */
.solution-boundary {
  margin: 1rem 0 0;
  padding: .8rem .9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs, 6px);
  background: var(--surface-2, rgba(0,0,0,.03));
  font-size: .78rem;
  line-height: 1.7;
  color: var(--text-muted);
}
.solution-boundary strong { color: var(--text); }

@media (max-width: 720px) {
  .solution-card-thematic { grid-column: auto; }
}

/* ---------- Sector chips (moved here in BLOCK 1.1) -----------------------
   THE SAME DEFECT, NINE MORE PAGES. These rules lived in wave2-public.css,
   whose own comment (preserved below) records finding `.sector-chip-list`
   shipping as an unstyled <ul> on /industries.html and fixing it there.

   universal-sector-page.js and sector-governed-panel.js render that identical
   component on nine more pages — government, energy, mining, tourism, labour,
   environment, digital-transformation, private-sector and the sector template —
   and not one of them loads wave2-public.css. Measured on the deployed Preview
   at /sectors/government: three chip lists, 24 items in the first, computed
   `display:block; list-style:disc; padding-left:40px`, chips transparent with
   zero padding. A browser-default bullet list, on a page whose every other
   section is a designed card.

   The fix is the FILE, not the rules. A component rendered by two page
   families cannot live in one family's stylesheet: fixing it for industries.html
   only was what left the other nine broken, and copying the rules into
   sector-framework.css would have set up the next divergence. components.css is
   loaded by both families, which is the whole reason it exists.

   Below is the original block, verbatim, including its account of how the
   defect was found — a static read and a local render both miss it, because
   the list is built from an API call the local harness cannot make.
   ------------------------------------------------------------------------ */
/* ---------- The canonical sector index -----------------------------------
   The 55-node estate listing on /industries.html is rendered by
   universal-sector-page.js as `.sector-chip-list` / `.sector-chip`. Those
   classes had exactly one rule anywhere in the repository — a border, in
   sector-identity.css — and industries.html does not load that file. So
   the whole index shipped as an unstyled <ul>: bullet points, default
   margins, and bare inline links.

   axe found it as a target-size failure on 24 nodes at every viewport,
   and only against the DEPLOYED origin, because the list is built from an
   API call that cannot succeed on the local harness. It is a good example
   of a defect that a static read and a local render both miss.

   Styling it here rather than loading sector-identity.css is deliberate:
   that file is a THEME, keyed on --sector-primary, and industries.html is
   the estate index rather than one sector's page. It needs geometry, not
   a sector's colour. */

.sector-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
}
/* Two kinds of chip come out of the same renderer: a plain label, and a
   label wrapped in a link. Both need to be at least 24px tall on both
   axes; the link one additionally needs the whole chip to be the target,
   not just the text inside it.

   The first pass expressed that with four overlapping rules, one of
   which — resetting the anchor's padding before `:has()` put it back —
   was dead in any browser that supports `:has()` and load-bearing in one
   that does not. Two states written as three.

   This is the same result with the padding owned in exactly one place
   per case. Without `:has()` support the chip keeps its own padding and
   the anchor adds its own: a slightly roomier chip, still well over the
   24px minimum, which is the right direction to degrade in. */
.sector-chip {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: .35rem .7rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--surface);
  font-size: var(--text-sm);
  color: var(--text);
}
.sector-chip:has(> a) { padding: 0; }
.sector-chip > a {
  display: flex;
  align-items: center;
  min-height: 34px;
  padding: .35rem .7rem;
  color: inherit;
  text-decoration: none;
}
.sector-chip:hover { border-color: var(--accent); }
.sector-chip > a:hover { text-decoration: underline; }

/* ============================================================================
   ROLE WORKSPACE TITLE
   ----------------------------------------------------------------------------
   The one heading that answers "which workspace am I in?". It is inserted by
   the shell on a role home that has no heading of its own — four of the twelve
   had none, so on a phone, where the browser tab is not visible, the screen
   never said which workspace it was.

   Sized to read as the page's primary heading without competing with the
   content beneath it; a role home is a place to work, not a title card.
   ========================================================================== */
.vi-workspace-title {
  margin: 0 0 1rem;
  font-size: clamp(1.25rem, 1rem + 1.2vw, 1.75rem);
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text, #14181f);
}

/* ============================================================================
   KNOWLEDGE HUB KPI
   ----------------------------------------------------------------------------
   The summary renderer called a metric() that lived in a module this page does
   not load, so the panel showed "Knowledge summary unavailable" after the API
   had already answered 200. The helper is declared on the page; this is the
   matching presentation, kept in the shared stylesheet the page already loads
   rather than in a fourth inline <style> block.
   ========================================================================== */
.k-metric {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  padding: .85rem 1rem;
  border: 1px solid var(--border, #e4e7ec);
  border-radius: 10px;
  background: var(--surface, #fff);
}
.k-metric strong { font-size: 1.4rem; line-height: 1.1; font-weight: 650; }
.k-metric span { font-size: .78rem; color: var(--text-dim, #667085); }
