/*
 * PaperSense — Component recipes
 * ===============================
 * reference/lumora_source_prompt.md "Shared component recipes" + per-section
 * specs (verbatim values) for PillButton, Eyebrow, TagChip, AnimatedLink,
 * Header, clock chip, Menu button, NavMenu, RequestModal geometry (->
 * Search modal, C-10). KPI tile / flag chip / skeleton /
 * freshness badge / form controls / data table / empty & error states are
 * PaperSense additions (DESIGN_CONVERSION_MAP §5) with no Lumora source —
 * their look is DERIVED from the token system (radii, hairlines, weights,
 * the Lumora control style) rather than copied from a spec.
 *
 * All hover motion driven by the JS spring (js/core/hover.js) is left to
 * JS — this file only sets the static layout/appearance those elements
 * animate from. Simple non-spring CSS :hover rules are gated to
 * `(hover: hover) and (pointer: fine)` throughout (ARCHITECTURE §3), same
 * as the JS hover springs, so nothing sticks on touch.
 */

/* =======================================================================
 * PillButton
 * ===================================================================== */
.pill-button {
  display: inline-block;
}
.pill-button__pill {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  border-radius: var(--radius-pill);
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  padding: 0.875rem 1.75rem; /* py-3.5 px-7 — no-arrow */
}
.pill-button--with-arrow .pill-button__pill {
  padding-block: 0.375rem;
  padding-inline: 1.5rem 0.375rem; /* py-1.5 pl-6 pr-1.5 */
}
.pill-button--dark .pill-button__pill {
  background: var(--ink);
  color: var(--on-ink);
}
.pill-button--light .pill-button__pill {
  background: var(--surface);
  color: var(--foreground);
}
.pill-button--outline .pill-button__pill {
  border: 1px solid var(--line);
  background: transparent;
  color: var(--foreground);
}
.pill-button__badge {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  font-size: 1rem;
  flex-shrink: 0;
}
.pill-button--dark .pill-button__badge {
  background: var(--on-ink);
  color: var(--ink);
}
.pill-button--light .pill-button__badge,
.pill-button--outline .pill-button__badge {
  background: var(--ink);
  color: var(--on-ink);
}
/* The plain "→" of pillButton({arrow: 'inline'}) (the Landing hero's CTA): inline-block, so `.flip-rtl` can mirror it. */
.pill-button__arrow {
  display: inline-block;
}
/* "danger" tone (new, v2 — components/confirm-dialog.js's destructive Confirm). */
.pill-button--danger .pill-button__pill {
  background: var(--down);
  color: var(--on-ink);
}
.pill-button--danger .pill-button__badge {
  background: var(--on-ink);
  color: var(--down);
}

/* =======================================================================
 * Eyebrow
 * ===================================================================== */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  color: var(--foreground-a70); /* dark tone (default — on light surfaces) */
}
.eyebrow__dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--radius-pill);
  background: var(--foreground-a50);
  flex-shrink: 0;
}
.eyebrow--light {
  color: var(--on-ink-a70);
}
.eyebrow--light .eyebrow__dot {
  background: var(--on-ink-a60);
}
.eyebrow--bordered {
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 0.375rem 1rem;
}

/* =======================================================================
 * TagChip
 * ===================================================================== */
.tag-chip {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  color: var(--foreground);
}
.tag-chip--light {
  border-color: var(--on-ink-a25);
  color: var(--on-ink);
}

/* =======================================================================
 * AnimatedLink — hover motion (translateX/opacity) is JS-driven; this is
 * just the structural wrapper.
 * ===================================================================== */
.animated-link {
  display: inline-flex;
  align-items: center;
}
.animated-link__label {
  display: inline-block;
  will-change: transform, opacity;
}

/* =======================================================================
 * BackLink — "← Back to Rankings" at the top of a detail page (components/back-link.js).
 * A quiet text link above the title, so it never competes with it. The padding gives the focus ring room and the
 * negative margin puts the arrow on the page's edge. The target is 32-36px tall (the App's rem is 14-16px) and
 * 44px on a phone. Its text is --foreground-a70: 6.9:1 on white.
 * ===================================================================== */
.back-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: fit-content;
  max-width: 100%;
  min-height: 2.25rem;
  margin-block-end: 0.5rem; /* clear of the title: the focus ring reaches 4px past the link */
  margin-inline-start: -0.5rem;
  padding-inline: 0.5rem;
  border-radius: var(--radius-control);
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  line-height: 1.25;
  color: var(--foreground-a70);
}
@media (max-width: 639.98px) {
  .back-link {
    min-height: 44px;
  }
}
.back-link__arrow {
  display: inline-block;
  flex-shrink: 0;
  transition: translate 0.15s var(--ease-out-cubic); /* `translate`, not `transform`: .flip-rtl owns the transform */
}
@media (hover: hover) and (pointer: fine) {
  .back-link:hover {
    background: var(--surface);
    color: var(--foreground);
  }
  .back-link:hover .back-link__arrow {
    translate: -0.1875rem 0;
  }
  [dir='rtl'] .back-link:hover .back-link__arrow {
    translate: 0.1875rem 0; /* the arrow is mirrored, and so is its nudge */
  }
}

/* =======================================================================
 * Header — variant "overlay" (Landing, spec verbatim) / "app" (a solid nav
 * row, about 68px, stuck to the top; the freshness line under it scrolls
 * away — v3 decision #27, ARCHITECTURE §14.1/§14.7).
 * Brand lockup sizes are px, not rem: the Landing's rem follows the adaptive
 * grid (11–16px), and the designer's targets are exact (review §1).
 * ===================================================================== */
.site-header {
  z-index: var(--z-header);
}
.site-header--overlay {
  position: absolute;
  inset-inline: 0;
  top: 0;
}
/* The App's header group and its mount point (.site-header-host, in app.html) generate no box, so
   the sticky nav row's containing block is the whole page. A sticky element can't leave its
   parent's box: the v2 header sat in a header-high #app-header and never actually stuck. */
.site-header-host,
.site-header-group {
  display: contents;
}
.site-header--app {
  position: sticky;
  top: 0;
  background: var(--background);
  border-bottom: 1px solid var(--line);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1.25rem; /* combines with .shell's own padding-inline */
}
@media (min-width: 640px) {
  .site-header__inner {
    padding-block: 2rem;
  }
}
/* App: a fixed-height row instead of rem padding: 60px, 68px from 1024px (the hairline included). */
.site-header--app .site-header__inner {
  padding-block: 0;
  min-height: calc(var(--app-header-h) - 1px);
}

/* Brand lockup: mark + wordmark (Onest 600, -0.015em). App 24/19 (20 from 1024px); Landing 26/20,
   28/21 from 1024px, 32/24 from 1280px. */
.site-header__brand {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  font-weight: var(--weight-semibold);
}
.site-header__brand-mark {
  /* Spec: header brand LogoMark is #b15f2c (--accent) in both variants —
     only the loader and NavMenu marks use the lighter --accent-from. */
  display: inline-flex;
  line-height: 0;
  font-size: 24px;
  color: var(--accent);
}
.site-header__brand-name {
  font-size: 19px;
  line-height: 1.15;
  letter-spacing: -0.015em;
  white-space: nowrap;
}
@media (min-width: 1024px) {
  .site-header--app .site-header__brand-name {
    font-size: 20px;
  }
}
.site-header--overlay .site-header__brand-mark {
  font-size: 26px;
}
.site-header--overlay .site-header__brand-name {
  font-size: 20px;
}
@media (min-width: 1024px) {
  .site-header--overlay .site-header__brand {
    gap: 9px;
  }
  .site-header--overlay .site-header__brand-mark {
    font-size: 28px;
  }
  .site-header--overlay .site-header__brand-name {
    font-size: 21px;
  }
}
@media (min-width: 1280px) {
  .site-header--overlay .site-header__brand {
    gap: 10px;
  }
  .site-header--overlay .site-header__brand-mark {
    font-size: 32px;
  }
  .site-header--overlay .site-header__brand-name {
    font-size: 24px;
  }
}

/* Nav: labels never wrap. Below 1280px the gap tightens and the clock chip hides (the Menu shows
   the clock); the inline nav itself shows from 1024px, below that the Menu is the only nav. */
.site-header__nav {
  display: none;
}
@media (min-width: 1024px) {
  .site-header__nav {
    display: flex;
  }
}
.site-header__nav-list {
  display: flex;
  gap: 1.25rem;
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
}
@media (min-width: 1280px) {
  .site-header__nav-list {
    gap: 2rem;
  }
}
/* The Landing's header follows the approved concept where the App mockups differ from it: nav 15px with a 30px gap
   (the mockups' App header: 14px / 22px), the chip padded 8px 14px, and the freshness line pulled up against the
   nav row (the concept sets its text at y = 96 of a 100px row; the App's line hangs under its own bordered row).
   The line moves visually only (`top`), so the header keeps the height the hero measures its clearance from. */
.site-header--overlay .site-header__nav-list {
  font-size: 0.9375rem;
}
@media (min-width: 1280px) {
  .site-header--overlay .site-header__nav-list {
    gap: 1.875rem;
  }
}
/* Labels are full ink at rest, as in the concept (the App keeps the Lumora 80%; its mockups show 70%). The spring in
   header.js writes no opacity for this variant, so nothing overrides this. Hover and keyboard focus stay visible: the
   2px lift of the spring, plus a thin underline (keyboard focus also has the global 2px focus ring). The underline is
   switched on and off as a line, never faded from a transparent colour: forced-colors mode repaints
   `text-decoration-color`, which would leave every label permanently underlined. */
.site-header--overlay .site-header__nav-item {
  opacity: 1;
}
.site-header--overlay .site-header__nav-item:focus-visible {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.375rem;
}
@media (hover: hover) and (pointer: fine) {
  .site-header--overlay .site-header__nav-item:hover {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.375rem;
  }
}
.site-header--overlay .clock-chip {
  padding: 0.5rem 0.875rem;
}
@media (min-width: 1024px) {
  .site-header--overlay .site-header__freshness {
    position: relative;
    top: -0.8rem;
  }
}
.site-header__nav-item {
  display: inline-block;
  padding-block: 0.25rem 0.375rem;
  white-space: nowrap;
  opacity: 0.8; /* rest state; the hover spring (js/core/hover.js) lifts it to 1 */
}
/* The page you are on: full strength and an underline (aria-current is set by header.js and, on
   every route change, by js/app.js). `!important`: the hover spring writes inline opacity. */
.site-header__nav-item[aria-current='page'] {
  opacity: 1 !important;
  box-shadow: inset 0 -2px 0 var(--foreground);
}
@media (forced-colors: active) {
  /* forced colors drop box-shadow: keep the marker */
  .site-header__nav-item[aria-current='page'] {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.375rem;
  }
}
.site-header__nav-caret {
  font-size: 0.75rem;
  opacity: 0.6;
  margin-inline-start: 0.25rem;
}
.site-header__actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}
/* Where the chip shows (from 1280px) it sits a header gap (28px) from the Menu button, as in the concept and the
   mockups (`.hdr{gap:28px}` between every item); below that the Menu button is alone and the gap is moot. */
@media (min-width: 1280px) {
  .site-header__actions {
    gap: 1.75rem;
  }
}

/* Clock chip — market clock (C-09). Chip styling verbatim; content is 3
 * segments ("NYSE · status · time ET") instead of the source's label/time/
 * date, per the conversion. Shown from 1280px only (ARCHITECTURE §14.1). */
/* A pill with a 10% ink hairline and word-spaced segments: the concept and the App mockups both draw it so
   ("border-radius: 999px", `border: 1px solid rgba(17,17,17,.1)`); it was the Lumora 14px box with a 12px gap. */
.clock-chip {
  display: none;
  align-items: center;
  gap: 0.1875rem; /* 3px: the spaces around a "·" in the concept's plain-text chip */
  border: 1px solid var(--foreground-a10);
  background: rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: var(--radius-pill);
  padding: 0.5rem 0.75rem;
  font-size: var(--text-xs);
  /* Plain text in one tone and one weight, as in the concept and the mockups (`.chip{color:var(--fa60)}`): the status
     and the time used to be darker and medium-weight. 60% ink is the floor for small text (BUG-020) and passes AA:
     4.8:1 on the chip over the Landing's paper, 4.9:1 on white — no lighter-passing token was needed. */
  color: var(--foreground-a60);
  white-space: nowrap;
}
@media (min-width: 1280px) {
  .clock-chip {
    display: flex;
  }
}
.clock-chip__time {
  min-width: 3.5rem; /* the minute ticking over never moves the chip */
  font-variant-numeric: tabular-nums;
}

/* Language toggle "EN | עב" (components/lang-toggle.js): two buttons in one pill with the same 10% ink hairline as the chip and the Menu
   button; the current language is filled. It mirrors by itself in a right-to-left page (a flex row). */
.lang-toggle {
  display: inline-flex;
  align-items: stretch;
  overflow: hidden;
  border: 1px solid var(--foreground-a10);
  border-radius: var(--radius-pill);
  background: var(--on-ink-a40);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.lang-toggle__option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.125rem;
  min-height: 1.875rem;
  padding-inline: 0.625rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
  color: var(--foreground-a60);
}
.lang-toggle__option[aria-pressed='true'] {
  background: var(--ink);
  color: var(--on-ink);
}
.lang-toggle__option:focus-visible {
  outline-offset: -2px;
}
@media (hover: hover) and (pointer: fine) {
  .lang-toggle__option[aria-pressed='false']:hover {
    color: var(--foreground);
    background: var(--foreground-a10);
  }
}
@media (forced-colors: active) {
  .lang-toggle__option[aria-pressed='true'] {
    text-decoration: underline; /* forced colors drop the fill: keep the marker */
  }
}

/* Menu button: a pill with the same 10% ink hairline as the chip (concept and mockups agree). */
.menu-button {
  border: 1px solid var(--foreground-a10);
  background: rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: var(--radius-pill);
}
@media (hover: hover) and (pointer: fine) {
  .menu-button:hover {
    background: rgba(255, 255, 255, 0.7);
  }
}
.menu-button__inner {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.5rem 1rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold); /* 600 / +0.06em: the concept's and the mockups' MENU */
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.menu-button__icon {
  font-size: 0.875rem;
}
.menu-button__label {
  display: none;
}
@media (min-width: 640px) {
  .menu-button__label {
    display: inline;
  }
}

/* =======================================================================
 * NavMenu overlay
 * ===================================================================== */
.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-nav-overlay);
  display: flex;
  flex-direction: column;
  overflow-y: auto; /* a short window scrolls instead of cutting the list off */
  background: var(--ink);
  color: var(--on-ink);
  opacity: 0;
}
.nav-overlay.is-visible {
  opacity: 1;
}
.nav-overlay__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 1.25rem;
}
@media (min-width: 640px) {
  .nav-overlay__top {
    padding-block: 1.5rem;
  }
}
/* Menu lockup: 24 / 20 / gap 8 on every screen (designer review §1), px like the header's. */
.nav-overlay__brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: var(--weight-semibold);
}
.nav-overlay__brand-mark {
  display: inline-flex;
  line-height: 0;
  font-size: 24px;
  color: var(--accent-from);
}
.nav-overlay__brand-name {
  font-size: 20px;
  line-height: 1.15;
  letter-spacing: -0.015em;
  white-space: nowrap;
}
.nav-overlay__close {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--on-ink-a15);
  border-radius: var(--radius-control);
  padding: 0.5rem 1rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--on-ink-a70);
}
@media (hover: hover) and (pointer: fine) {
  .nav-overlay__close:hover {
    border-color: var(--on-ink-a40);
    color: var(--on-ink);
  }
}
.nav-overlay__nav {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.nav-overlay__list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.nav-overlay__item {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
  padding-block: 0.5rem;
  text-align: start;
  font-size: 2.25rem;
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  transition: transform 0.5s ease-out, opacity 0.5s ease-out;
  transform: translateY(1rem);
  opacity: 0;
}
@media (min-width: 640px) {
  .nav-overlay__item {
    font-size: 3.75rem;
  }
}
/* Seven items at 60px need ~820px of height; below that they scale down so the list stays on screen. */
@media (min-width: 640px) and (max-height: 820px) {
  .nav-overlay__item {
    font-size: clamp(2.25rem, 6.5vh, 3.75rem);
  }
}
.nav-overlay__item.is-visible {
  transform: translateY(0);
  opacity: 1;
}
.nav-overlay__index {
  font-size: 1rem;
  font-weight: var(--weight-regular);
  color: var(--on-ink-a60); /* a30 was 2.61:1 on --ink (QA-A-17) */
  transition: color 0.3s;
}
.nav-overlay__label {
  color: var(--on-ink-a70);
  transition: color 0.3s;
}
/* The page you are on (aria-current, set when the Menu opens). */
.nav-overlay__item[aria-current='page'] .nav-overlay__label {
  color: var(--on-ink);
}
.nav-overlay__item[aria-current='page'] .nav-overlay__index {
  color: var(--accent-from);
}
@media (hover: hover) and (pointer: fine) {
  .nav-overlay__item:hover .nav-overlay__index {
    color: var(--accent-from);
  }
  .nav-overlay__item:hover .nav-overlay__label {
    color: var(--on-ink);
  }
}
.nav-overlay__bottom {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  border-top: 1px solid var(--on-ink-a10);
  padding: 1.5rem 1.25rem;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.025em;
  color: var(--on-ink-a60); /* the market clock is small text: a45 was 4.4:1 on --ink */
}
@media (min-width: 640px) {
  .nav-overlay__bottom {
    flex-direction: row;
    justify-content: space-between;
    padding-inline: 2rem;
  }
}
.nav-overlay__cta {
  color: var(--on-ink-a70);
  transition: color 0.2s;
}
@media (hover: hover) and (pointer: fine) {
  .nav-overlay__cta:hover {
    color: var(--on-ink);
    text-decoration: underline;
  }
}

/* =======================================================================
 * Search modal (RequestModal geometry — DESIGN_CONVERSION_MAP C-10).
 * Content = search input + results list instead of the source's form.
 * ===================================================================== */
.search-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 1rem;
  background: rgba(17, 17, 17, 0.3);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
@media (min-width: 640px) {
  .search-modal-backdrop {
    align-items: center;
  }
}
.search-modal {
  position: relative;
  width: 100%;
  max-width: 32rem;
  overflow: hidden;
  border-radius: var(--radius-card);
  background: var(--background);
  padding: 1.5rem;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  outline: 1px solid var(--line); /* ring 1px #e6e5e2 */
  outline-offset: -1px;
}
@media (min-width: 640px) {
  .search-modal {
    padding: 2rem;
  }
}
.search-modal__close {
  position: absolute;
  inset-inline-end: 1rem;
  top: 1rem;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--foreground-a60);
}
@media (hover: hover) and (pointer: fine) {
  .search-modal__close:hover {
    background: var(--surface-2);
    color: var(--foreground);
  }
}
.search-modal__heading {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  color: var(--foreground-a60);
}
.search-modal__heading-dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
  flex-shrink: 0;
}
.search-modal__field {
  position: relative;
  display: flex;
  align-items: center;
}
.search-modal__icon {
  position: absolute;
  inset-inline-start: 1rem;
  color: var(--foreground-a45);
  font-size: 1rem;
  pointer-events: none;
}
.search-modal__input {
  width: 100%;
  border: 1px solid var(--line);
  background: var(--surface-a50);
  border-radius: var(--radius-control);
  padding: 0.75rem 1rem;
  padding-inline-start: 2.5rem;
  font-size: 0.875rem;
  font-family: inherit;
  color: var(--foreground);
  outline: none;
  transition: border-color 0.2s, background 0.2s;
}
.search-modal__input:focus {
  border-color: var(--foreground-a30);
  background: var(--background);
}
/* a ticker or a company name typed in a right-to-left page (BUG-157): the direction comes from the first strong letter, so "Apple Inc." keeps its stop at the end */
.search-modal__input {
  unicode-bidi: plaintext;
}
.search-modal__results {
  margin-top: 1rem;
  max-height: 20rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.search-modal__result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border-radius: var(--radius-control);
  padding: 0.625rem 0.75rem;
  font-size: 0.875rem;
  color: var(--foreground);
  text-align: start;
  width: 100%;
}
.search-modal__result.is-active {
  background: var(--surface);
}
@media (hover: hover) and (pointer: fine) {
  .search-modal__result:hover {
    background: var(--surface);
  }
}
.search-modal__result-main {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}
.search-modal__result-ticker {
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.search-modal__result-name {
  color: var(--foreground-a60); /* a55 was 4.04:1 on the active row (QA-A-17) */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-modal__result-meta {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-xs);
  color: var(--foreground-a60); /* the result tags: a45 was 2.96:1 on the active row (QA-A-17) */
}
.search-modal__result-type {
  padding: 0.0625rem 0.375rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--foreground-a60); /* a55 was 4.19:1 (QA-A-17) */
}
.search-modal__empty,
.search-modal__hint {
  padding: 2rem 1rem;
  text-align: center;
  font-size: 0.875rem;
  color: var(--foreground-a60);
}

/* =======================================================================
 * KPI tile (new — DESIGN_CONVERSION_MAP §5)
 * ===================================================================== */
.kpi-tile {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.25rem;
  border-radius: var(--radius-card-sm);
  background: var(--surface);
}
.kpi-tile__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--foreground-a60);
}
.kpi-tile__value {
  font-size: 1.875rem;
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
}
.kpi-tile__delta {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.8125rem;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}
.kpi-tile__delta--up {
  color: var(--up-text);
}
.kpi-tile__delta--down {
  color: var(--down-text);
}
.kpi-tile__delta--neutral {
  color: var(--neutral-text);
}

/* =======================================================================
 * Flag chip (new — data-quality flags, e.g. ANNUAL_FALLBACK)
 * ===================================================================== */
.flag-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border-radius: var(--radius-pill);
  padding: 0.25rem 0.625rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  border: 1px solid transparent;
  white-space: nowrap;
}
.flag-chip--neutral {
  background: var(--surface);
  color: var(--neutral-text);
  border-color: var(--line);
}
.flag-chip--warn {
  background: color-mix(in srgb, var(--warn) 14%, white);
  color: var(--warn-text);
}
.flag-chip--info {
  background: color-mix(in srgb, var(--info) 14%, white);
  color: var(--info-text);
}
.flag-chip--up {
  background: color-mix(in srgb, var(--up) 14%, white);
  color: var(--up-text);
}
.flag-chip--down {
  background: color-mix(in srgb, var(--down) 14%, white);
  color: var(--down-text);
}

/* =======================================================================
 * Skeleton (new). Shimmer explicitly disabled under reduced motion —
 * base.css's verbatim `*{animation:none!important}` rule only targets
 * elements, not ::after pseudo-elements, so the shimmer needs its own
 * reduced-motion rule to actually stop.
 * ===================================================================== */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border-radius: var(--radius-control);
  color: transparent;
}
.skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--surface-2), transparent);
  transform: translateX(-100%);
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
  100% {
    transform: translateX(100%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton::after {
    animation: none;
    display: none;
  }
}
.skeleton--text {
  height: 1em;
  border-radius: 0.25rem;
}
.skeleton--block {
  width: 100%;
  height: 100%;
}

/* Ghosts (QA-A-08): the loading stand-in for content that has not arrived yet. Unlike a skeleton block, a ghost lives
   INSIDE the real element it stands for (built from that element's own classes: same box, font, line box), so the
   page has its loaded height from the first paint and nothing moves when the data lands. Only the paint is defined
   here; the builders are views/indices/ghost.js and views/stock/ghost.js. Under reduced motion base.css stops the
   animation and the gradient stays as a still grey. */
.ghost,
.ghost-text,
.ghost-fill {
  background: linear-gradient(90deg, var(--surface) 25%, var(--surface-2) 50%, var(--surface) 75%);
  background-size: 200% 100%;
  animation: ghost-shimmer 1.5s ease-in-out infinite;
  color: transparent;
}
/* a bar inside a real element: drawn at 0.8em, so it never grows that element's line box */
.ghost {
  display: inline-block;
  height: 0.8em;
  vertical-align: middle;
  border-radius: 0.25rem;
}
/* transparent text that wraps like the text it stands for, one shimmer block per line */
.ghost-text {
  border-radius: 0.25rem;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
/* a box that keeps its own padding, font and border but shows no ink (a badge, a pill link) */
.ghost-fill {
  border-color: transparent;
}
/* forced colors paint `color: transparent` as real text and drop the shimmer, so the stand-in copy would read as
   content: hide the ink but keep the box, so the page still holds its loaded height (lead, 2026-09-30) */
@media (forced-colors: active) {
  .ghost,
  .ghost-text,
  .ghost-fill {
    visibility: hidden;
  }
}
@keyframes ghost-shimmer {
  100% {
    background-position: -200% 0;
  }
}

/* =======================================================================
 * Form controls — "the Lumora control style" (from the RequestModal form
 * fields: hairline border, translucent surface fill, focus -> solid).
 * ===================================================================== */
.field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.field__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.025em;
  color: var(--foreground-a60); /* a50 was 3.54:1 on the Plan forms (QA-A-17) */
}
.control {
  width: 100%;
  border: 1px solid var(--line);
  background: var(--surface-a50);
  border-radius: var(--radius-control);
  padding: 0.75rem 1rem;
  font-size: 0.875rem;
  font-family: inherit;
  color: var(--foreground);
  outline: none;
  transition: border-color 0.2s, background 0.2s;
}
.control:focus {
  border-color: var(--foreground-a30);
  background: var(--background);
}
/* QA-03: `.control`'s own `outline: none` ties base.css's global
   `:focus-visible` rule on specificity and, loading later, wins — silently
   killing the keyboard focus ring on every text/select/textarea input
   site-wide. Re-enable it explicitly, same treatment as the toggle-switch's
   QA-02 fix above. */
.control:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
textarea.control {
  resize: none;
}
.control[type='number'] {
  font-variant-numeric: tabular-nums;
}

/* Select */
.select-control {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 100%;
}
.select-control select.control {
  appearance: none;
  padding-inline-end: 2.5rem;
}
.select-control__icon {
  position: absolute;
  inset-inline-end: 0.875rem;
  pointer-events: none;
  color: var(--foreground-a45);
  font-size: 0.875rem;
}

/* Segmented control */
.segmented-control {
  display: inline-flex;
  /* BUG-009 / BUG-021: wrap instead of widening the page when the options don't fit (8 price
     ranges at 375px). Was patched per view (stock, screener, dashboard) until a new view missed it. */
  flex-wrap: wrap;
  max-width: 100%;
  padding: 0.25rem;
  background: var(--surface);
  border-radius: var(--radius-control);
  gap: 0.25rem;
}
.segmented-control__option {
  padding: 0.5rem 1rem;
  border-radius: calc(var(--radius-control) - 0.25rem);
  font-size: 0.8125rem;
  font-weight: var(--weight-medium);
  color: var(--foreground-a60);
}
.segmented-control__option.is-active {
  background: var(--background);
  color: var(--foreground);
  box-shadow: 0 1px 2px rgba(10, 10, 10, 0.08);
}

/* Price panel — the range control + chart + loading/error status shared by the Stock page
   (js/views/stock/price-panel.js) and the Indices detail page (js/views/indices/price-panel.js).
   It lives here, once, because a component rendered by view X is styled by X's CSS or components.css
   (BUG-031): the rules used to sit in css/views/stock.css only, so #/indices/:id opened in a fresh tab lost
   its layout until a Stock page had been visited (QA-B-06). */
.price-panel {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.price-panel__status {
  position: relative;
  width: 100%;
  height: 20rem;
}
/* :not([hidden]) — the `hidden` attribute (both price panels toggle it to swap the chart/skeleton/error
   state) must win over this rule's own `display`, or the container stays a visible empty flex box whenever
   it is "hidden" (BUG-008). */
.price-panel__status:not([hidden]) {
  display: flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 640px) {
  .price-panel__status {
    height: 16rem;
  }
}
/* "Last session: Sep 29, 2026" under the 1D chart when it shows an earlier session (QA-B-22): a quiet footnote, half
   the panel's gap from the chart (the size and the colour come from .panel__note). */
.price-panel__note {
  margin-block-start: -0.5rem;
}

/* Toggle switch */
.toggle-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 2.5rem;
  height: 1.5rem;
  border-radius: var(--radius-pill);
  background: var(--line);
  flex-shrink: 0;
  transition: background 0.2s ease;
}
.toggle-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.toggle-switch__thumb {
  position: absolute;
  inset-inline-start: 0.1875rem;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--radius-pill);
  background: var(--background);
  transition: transform 0.2s ease;
  box-shadow: 0 1px 2px rgba(10, 10, 10, 0.25);
}
.toggle-switch input:checked ~ .toggle-switch__thumb {
  transform: translateX(1rem);
}
[dir='rtl'] .toggle-switch input:checked ~ .toggle-switch__thumb {
  transform: translateX(-1rem);
}
.toggle-switch:has(input:checked),
.toggle-switch.is-checked {
  background: var(--ink);
}
/* QA-02: visible keyboard focus on the switch (the real input is transparent on top of it) */
.toggle-switch:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Checkbox */
.checkbox-control {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  color: var(--foreground);
}
.checkbox-control input[type='checkbox'] {
  appearance: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 0.375rem;
  background: var(--background);
  display: grid;
  place-content: center;
  flex-shrink: 0;
  color: var(--on-ink);
}
.checkbox-control input[type='checkbox']::before {
  content: '';
  width: 0.625rem;
  height: 0.625rem;
  transform: scale(0);
  transition: transform 0.12s ease;
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
  background: currentColor;
}
.checkbox-control input[type='checkbox']:checked {
  background: var(--ink);
  border-color: var(--ink);
}
.checkbox-control input[type='checkbox']:checked::before {
  transform: scale(1);
}

/* =======================================================================
 * Data table (new) — sticky header, hairline rows, hover fill, numeric
 * columns tabular-nums + end-aligned via logical properties (RTL-safe).
 * ===================================================================== */
.data-table-wrapper {
  overflow-x: auto;
}
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}
.data-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--background);
  text-align: start;
  font-weight: var(--weight-medium);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.025em;
  color: var(--foreground-a60);
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.data-table td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--line);
  color: var(--foreground);
}
.data-table tbody tr {
  transition: background 0.15s;
}
@media (hover: hover) and (pointer: fine) {
  .data-table tbody tr:hover {
    background: var(--surface);
  }
}
.data-table tbody tr.is-selected {
  background: var(--surface);
}
.data-table tbody tr:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.data-table th[data-align='end'],
.data-table td[data-align='end'],
.data-table .data-table__cell--numeric {
  text-align: end;
  font-variant-numeric: tabular-nums;
}
.data-table__sort-button {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  color: inherit;
  cursor: pointer;
}
.data-table__sort-icon {
  font-size: var(--text-2xs);
  opacity: 0.5;
}
.data-table th[aria-sort='ascending'] .data-table__sort-icon,
.data-table th[aria-sort='descending'] .data-table__sort-icon {
  opacity: 1;
  color: var(--accent);
}

/* =======================================================================
 * Empty & error states (new)
 * ===================================================================== */
.empty-state,
.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 3rem 1.5rem;
  text-align: center;
  border-radius: var(--radius-card-sm);
  background: var(--surface);
}
.empty-state__icon,
.error-state__icon {
  font-size: 1.75rem;
  color: var(--foreground-a40);
}
.error-state__icon {
  color: var(--down-text);
}
.empty-state__title,
.error-state__title {
  font-size: 1rem;
  font-weight: var(--weight-medium);
  color: var(--foreground);
}
.empty-state__body,
.error-state__body {
  max-width: 28rem;
  font-size: 0.875rem;
  color: var(--foreground-a60);
}

/* =======================================================================
 * Floating panel base (new, v2) — shared chrome for info-popover.js's
 * panel, quality-slots.js's tooltip and ui.js flagChip's help tooltip.
 * `position:fixed` top/left are set at runtime by core/popover.js
 * (floatNear); this only sets the card look and the open/closed fade.
 * No animation under reduced motion: base.css's sitewide
 * `*{transition:none!important}` already covers a plain element transition.
 * ===================================================================== */
.info-popover__panel,
.quality-slots__tooltip,
.flag-chip__tooltip,
.heatmap-cell__tooltip {
  position: fixed;
  z-index: var(--z-popover);
  width: max-content;
  max-width: 20rem;
  border-radius: var(--radius-card-sm);
  border: 1px solid var(--line);
  background: var(--background);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  padding: 0.75rem 0.875rem;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--foreground);
  /* Text reset (QA-B-04): a panel is a DOM child of whatever it explains — a filter label (uppercase, nowrap,
     letter-spaced), a panel title, a table header — and a `position: fixed` box still inherits text styles.
     Every property a host commonly sets is restored, so the panel reads the same inside any label. */
  font-weight: var(--weight-regular);
  font-style: normal;
  letter-spacing: normal;
  text-transform: none;
  text-align: start;
  white-space: normal;
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.info-popover__panel.is-open,
.quality-slots__tooltip.is-open,
.flag-chip__tooltip.is-open,
.heatmap-cell__tooltip.is-open {
  opacity: 1;
  transform: translateY(0);
}
.info-popover__panel[data-side='top'],
.quality-slots__tooltip[data-side='top'],
.flag-chip__tooltip[data-side='top'],
.heatmap-cell__tooltip[data-side='top'] {
  transform-origin: bottom;
}
.heatmap-cell__tooltip-title {
  font-weight: var(--weight-semibold);
}
.heatmap-cell__tooltip p + p {
  margin-block-start: 0.25rem;
  color: var(--foreground-a70);
}

/* =======================================================================
 * Info popover (new, v2) — COPY_DECK §0.6's What/Why/How template.
 * ===================================================================== */
.info-popover {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
}
.info-popover__trigger {
  position: relative;
  isolation: isolate; /* keeps the hover disc (::before, z-index -1) behind the glyph */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Hit area of at least 24 x 24 px (QA-B-19, WCAG 2.5.8) — in px, because the App's root font-size shrinks to 14px;
     never below the old 1.25rem either (the Landing's root grows on very large screens).
     The negative margin gives back what the bigger box adds, so the layout footprint stays the old 1.25rem square,
     and the disc drawn on hover (::before) stays 1.25rem too: no visual size change. */
  --info-hit: max(24px, 1.25rem);
  width: var(--info-hit);
  height: var(--info-hit);
  margin: calc((1.25rem - var(--info-hit)) / 2);
  border-radius: var(--radius-pill);
  font-size: 0.8125rem;
  line-height: 1;
  color: var(--foreground-a60); /* a45 was 3:1 on white — the ⓘ is a control, keep it readable (BUG-020 rule) */
}
.info-popover__trigger::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: calc((var(--info-hit) - 1.25rem) / 2);
  border-radius: var(--radius-pill);
}
@media (hover: hover) and (pointer: fine) {
  .info-popover__trigger:hover {
    color: var(--foreground);
  }
  .info-popover__trigger:hover::before {
    background: var(--surface);
  }
}
.info-popover__line + .info-popover__line {
  margin-top: 0.5rem;
}
.info-popover__label {
  display: block;
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--foreground-a60); /* a45 was 3.03:1 (QA-A-17) */
  margin-bottom: 0.125rem;
}
.info-popover__text {
  display: block;
}

/* =======================================================================
 * Quality slots (new, v2) — ARCHITECTURE §13.3's 8-check indicator.
 * ===================================================================== */
.quality-slots {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.quality-slots__dots {
  display: inline-flex;
  align-items: center;
  gap: 0.1875rem;
}
.quality-slots__dot {
  display: inline-block;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
}
.quality-slots--sm .quality-slots__dot {
  width: 0.5rem;
  height: 0.5rem;
}
.quality-slots--md .quality-slots__dot {
  width: 0.75rem;
  height: 0.75rem;
}
.quality-slots__dot.is-pass {
  background: var(--up);
}
.quality-slots__dot.is-fail {
  background: var(--down);
}
.quality-slots__dot.is-na {
  background: transparent;
  border: 1.5px solid var(--neutral);
}
.quality-slots--fallback {
  font-size: 0.8125rem;
  color: var(--foreground-a70);
}
.quality-slots__summary {
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--foreground-a70);
}
.quality-slots--md .quality-slots__summary {
  font-size: 0.875rem;
}
.quality-slots__tooltip {
  max-width: 22rem;
}
.quality-slots__tooltip-line + .quality-slots__tooltip-line {
  margin-top: 0.375rem;
}
.quality-slots:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-control);
}

/* =======================================================================
 * Flag-chip tooltip (new, v2) — ui.js flagChip's optional help text.
 * ===================================================================== */
.flag-chip-wrap {
  display: contents; /* transparent for flex/grid layout — the chip itself still lays out as a single item */
}
.flag-chip[aria-describedby] {
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}

/* =======================================================================
 * Tier badge (new, v2) — COPY_DECK §1/§3.2.
 * ===================================================================== */
.tier-badge {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-pill);
  padding: 0.25rem 0.75rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
.tier-badge--up {
  background: color-mix(in srgb, var(--up) 14%, white);
  color: var(--up-text);
}
.tier-badge--info {
  background: color-mix(in srgb, var(--info) 14%, white);
  color: var(--info-text);
}
.tier-badge--neutral {
  background: var(--surface);
  color: var(--neutral-text);
}

/* =======================================================================
 * Freshness line (new, v2) — mounted under the header on every screen.
 * v3: in the App it sits under the sticky nav row and scrolls with the page
 * (the nav row's own hairline separates them); below 640px it is one line,
 * the short form "{date} close · Up to date ⓘ" (components/freshness.js).
 * ===================================================================== */
.freshness {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 0.5rem;
  row-gap: 0.25rem;
  /* the loaded height (the ⓘ trigger sets it): the row keeps its place until /api/health answers */
  min-height: 2.25rem;
  padding-block: 0.5rem;
  font-size: var(--text-xs);
  color: var(--foreground-a60); /* a55 is under AA 4.5:1 on white and on --paper */
}
.freshness--compact {
  flex-wrap: nowrap;
}
.freshness--compact .freshness__line {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.freshness__status-wrap {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  flex-shrink: 0;
  white-space: nowrap;
  font-weight: var(--weight-medium);
  color: var(--foreground-a70);
}
/* The status reads in its tone colour, as in the approved concept and mockups (the dot is never the only cue: the
   words say it too). The -text variants clear 4.5:1 on white and on --paper. */
.freshness__status-wrap--up {
  color: var(--up-text);
}
.freshness__status-wrap--warn {
  color: var(--warn-text);
}
.freshness__status-wrap--down {
  color: var(--down-text);
}
.freshness__status-wrap[hidden] {
  display: none;
}
/* the popover's text carries "line\nexplanation" in the short form */
.freshness .info-popover__text {
  white-space: pre-line;
}
.freshness__dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--radius-pill);
  background: var(--neutral);
  flex-shrink: 0;
}
.freshness__dot--up {
  background: var(--up);
}
.freshness__dot--warn {
  background: var(--warn);
}
.freshness__dot--down {
  background: var(--down);
}
.freshness__info {
  display: inline-flex;
  align-items: center;
}

/* =======================================================================
 * Confirm dialog (new, v2) — same visual language as .search-modal
 * (geometry/backdrop/shadow), content replaced with a title/body/actions
 * alertdialog. Enter/exit spring is JS (components/confirm-dialog.js), same
 * {260,30} as the search modal.
 * ===================================================================== */
.confirm-dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 1rem;
  background: rgba(17, 17, 17, 0.3);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
@media (min-width: 640px) {
  .confirm-dialog-backdrop {
    align-items: center;
  }
}
.confirm-dialog {
  position: relative;
  width: 100%;
  max-width: 28rem;
  border-radius: var(--radius-card);
  background: var(--background);
  padding: 1.5rem;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  outline: 1px solid var(--line);
  outline-offset: -1px;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
@media (min-width: 640px) {
  .confirm-dialog {
    padding: 2rem;
  }
}
.confirm-dialog__title {
  font-size: 1.25rem;
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  color: var(--foreground);
}
.confirm-dialog__body {
  font-size: 0.9375rem;
  color: var(--foreground-a70);
  line-height: 1.5;
}
.confirm-dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 0.5rem;
  flex-wrap: wrap;
}

/* =======================================================================
 * Toast (new, v2) — components/toast.js. Tone is a dot, never the whole
 * fill (ARCHITECTURE §8 + keeps the pre-validated --ink/--on-ink contrast
 * pairing instead of an unvalidated white-on-`--up` combination).
 * ===================================================================== */
.toast-region {
  position: fixed;
  inset-inline: 0;
  bottom: 1.25rem;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding-inline: 1rem;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  max-width: 26rem;
  width: max-content;
  border-radius: var(--radius-control);
  background: var(--ink);
  color: var(--on-ink);
  padding: 0.75rem 1.25rem;
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.toast__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--on-ink-a60);
  flex-shrink: 0;
}
/* A toast with one action (the installed app's update toast, components/toast.js, js/core/pwa.js): the text, then a text button on the inline end. */
.toast--with-action {
  gap: 0.75rem;
}
.toast__text {
  min-width: 0;
}
.toast__action {
  flex-shrink: 0;
  margin-inline-start: 0.25rem;
  padding: 0.375rem 0.75rem;
  border: 1px solid var(--on-ink-a40);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--on-ink);
  font: inherit;
  font-weight: var(--weight-semibold);
  cursor: pointer;
}
.toast__action:hover {
  background: var(--on-ink-a15);
}
.toast__action:focus-visible {
  outline: 2px solid var(--on-ink);
  outline-offset: 2px;
}
.toast--success .toast__dot {
  background: var(--up);
}
.toast--danger .toast__dot {
  background: var(--down);
}

/* =======================================================================
 * Offline notice (v4 Release B, js/core/pwa.js): a calm card over the bottom of the page when the installed app opens with no connection or with the computer that runs
 * PaperSense switched off. It says what is missing and that nothing of the portfolio is stored on the device. It is FIXED, never part of the page's flow: it shows up after the
 * page has been drawn, and a bar in the flow pushed everything below it down when it did (BUG-163, a layout shift of 0.05). "Close" puts it away.
 * ===================================================================== */
.offline-notice {
  position: fixed;
  inset-inline: 0.75rem;
  inset-block-end: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  z-index: var(--z-popover);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.25rem;
  max-inline-size: 38rem;
  padding: 0.875rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-card-sm);
  background: var(--surface);
  color: var(--foreground);
  box-shadow: 0 12px 32px -14px rgba(0, 0, 0, 0.3);
}
.offline-notice__text {
  flex: 1 1 16rem;
  min-inline-size: 0;
}
.offline-notice__title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: var(--weight-semibold);
}
.offline-notice__body {
  max-inline-size: 46rem;
  margin: 0.125rem 0 0;
  font-size: 0.875rem;
  color: var(--foreground-a70);
}
.offline-notice__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.75rem;
}
.offline-notice__close {
  min-block-size: 2.75rem;
  padding-inline: 0.5rem;
  border: 0;
  background: none;
  color: var(--foreground);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.offline-notice__close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* the Portfolio's phone layout has its own bar fixed at the bottom (css/views/portfolio.css): the card stands above it */
@media (max-width: 639.98px) {
  html.is-newsprint .offline-notice {
    inset-block-end: calc(4rem + env(safe-area-inset-bottom, 0px));
  }
}

/* ---------------------------------------------------------------------
 * Indices verdict badge (shared: Indices overview + detail, Dashboard widget)
 * (moved out of indices.css: that file loads only on #/indices, so this rendered unstyled elsewhere — Dvir, 2026-09-27)
 * ------------------------------------------------------------------- */
.indices-verdict {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-pill);
  padding: 0.25rem 0.75rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
.indices-verdict.tone-up {
  background: color-mix(in srgb, var(--up) 14%, white);
  color: var(--up-text);
}
.indices-verdict.tone-neutral {
  background: var(--surface);
  color: var(--neutral-text);
}
.indices-verdict.tone-warn {
  background: color-mix(in srgb, var(--warn) 14%, white);
  color: var(--warn-text);
}
.indices-verdict--unknown {
  background: var(--surface);
  color: var(--foreground-a60);
}

/* =======================================================================
 * Account menu (the header's, v4 Release B — js/components/account-menu.js)
 * Password mode only: in loopback mode the slot is `hidden` and empty. A round avatar with the handle's first letter (the handle itself from 1280px,
 * where the clock chip also shows), a menu panel under it, and a Sign in link for a visitor who is not signed in. Logical properties only; the panel
 * opens from the end edge in both directions. Lives here, not in a view's CSS, because both the Landing and the App render the header (BUG-031).
 * ===================================================================== */
.acct {
  position: relative;
  display: flex;
  align-items: center;
}
.acct[hidden],
.acct__panel[hidden] {
  display: none;
}
.acct__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-block-size: 2.25rem;
  padding: 0.1875rem;
  border: 1px solid var(--foreground-a10);
  border-radius: var(--radius-pill);
  background: var(--on-ink-a40);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}
@media (hover: hover) and (pointer: fine) {
  .acct__btn:hover {
    background: var(--on-ink-a70);
  }
}
.acct__avatar {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--on-ink);
  font-size: 0.8125rem;
  font-weight: var(--weight-semibold);
  line-height: 1;
}
.acct__name,
.acct__caret,
.acct__signin-text {
  display: none;
}
/* The handle (and "Sign in") are written out only where the header has room: the App from 1280px, the Landing's larger header from 1600px. Below that the
   button is the round avatar, or a person for a visitor who is not signed in: the Landing's overlay header was 8px too wide at 1440 with a "Sign in" word. */
@media (min-width: 1280px) {
  .site-header--app .acct__name {
    display: inline;
    max-inline-size: 9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .site-header--app .acct__caret {
    display: inline;
    font-size: 0.6875rem;
    opacity: 0.6;
  }
  .site-header--app .acct__btn {
    padding-inline-end: 0.875rem;
  }
  .site-header--app .acct__signin-text {
    display: inline;
  }
  .site-header--app .acct__signin {
    padding-inline: 0.75rem 1rem;
  }
}
@media (min-width: 1600px) {
  .site-header--overlay .acct__name {
    display: inline;
    max-inline-size: 9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .site-header--overlay .acct__btn {
    padding-inline-end: 0.875rem;
  }
  .site-header--overlay .acct__signin-text {
    display: inline;
  }
  .site-header--overlay .acct__signin {
    padding-inline: 0.75rem 1rem;
  }
}
.acct__panel {
  position: absolute;
  inset-block-start: calc(100% + 0.5rem);
  inset-inline-end: 0;
  z-index: var(--z-popover);
  min-inline-size: 15rem;
  padding: 0.375rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-card-sm);
  background: var(--background);
  color: var(--foreground);
  box-shadow: 0 20px 40px -16px color-mix(in srgb, var(--ink) 35%, transparent);
  text-align: start;
}
.acct__who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.125rem 0.5rem;
  margin-block-end: 0.25rem;
  padding: 0.625rem 0.75rem;
  border-block-end: 1px solid var(--line);
  font-size: var(--text-xs);
  color: var(--foreground-a70);
}
.acct__who b {
  font-size: 0.875rem;
  font-weight: var(--weight-semibold);
  color: var(--foreground);
}
.acct__role {
  margin-inline-start: auto;
  font-weight: var(--weight-medium);
}
.acct__item {
  display: flex;
  align-items: center;
  inline-size: 100%;
  min-block-size: 2.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-control);
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  color: var(--foreground);
  text-align: start;
}
.acct__item:focus-visible {
  outline-offset: -2px;
}
@media (hover: hover) and (pointer: fine) {
  .acct__item:hover {
    background: var(--surface);
  }
}
.acct__item:focus {
  background: var(--surface);
}
.acct__sep {
  block-size: 1px;
  margin-block: 0.25rem;
  background: var(--line);
}
.acct__signin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-block-size: 2.25rem;
  min-inline-size: 2.25rem;
  padding-inline: 0;
  border: 1px solid var(--foreground-a10);
  border-radius: var(--radius-pill);
  background: var(--on-ink-a40);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {
  .acct__signin:hover {
    background: var(--on-ink-a70);
  }
}
.acct__icon {
  inline-size: 1.125rem;
  block-size: 1.125rem;
}

/* A header that carries the account control is a little tighter than it was, so the control fits where the header was already full (the class is set only
   while the control is on screen: loopback mode keeps every spacing as it was): the Landing's overlay header at 1024 and 1280 and below 414px, the App
   below 360px. js/components/header.js toggles `.site-header--acct`. */
@media (min-width: 1280px) {
  .site-header--acct .site-header__actions {
    gap: 1.25rem;
  }
}
/* 1280 to 1359: a classic scrollbar takes 15px of the window, and the Landing's header with the account control then needed up to 14px more than it had (English, signed in) */
@media (min-width: 1280px) and (max-width: 1359.98px) {
  .site-header--acct .site-header__actions {
    gap: 1rem;
  }
  .site-header--acct .site-header__nav-list {
    gap: 0.75rem;
  }
  .site-header--acct .site-header__inner {
    column-gap: 1rem;
  }
}
@media (min-width: 1024px) and (max-width: 1279.98px) {
  .site-header--acct .site-header__inner {
    column-gap: 1rem;
  }
  .site-header--acct .site-header__nav-list {
    gap: 0.75rem;
  }
}
@media (max-width: 1023.98px) {
  .site-header--acct .site-header__inner {
    column-gap: 0.75rem;
  }
  .site-header--acct .site-header__actions {
    gap: 0.5rem;
  }
}
@media (max-width: 359.98px) {
  .site-header--acct .site-header__inner {
    padding-inline: 0.75rem;
  }
  .site-header--acct .lang-toggle__option {
    min-width: 1.75rem;
    padding-inline: 0.4375rem;
  }
}
