/*
 * Application stylesheet — minimal overrides
 * Most styling is handled by Tailwind CSS (app/assets/tailwind/application.css)
 * This file keeps only: fonts, animations, and JS-driven component styles
 */

@font-face {
  font-family: "Geist";
  src: url("/assets/GeistVF-5371dc46.woff") format("woff");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono";
  src: url("/assets/GeistMonoVF-ce04edea.woff") format("woff");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ── Brand components ("The Technical Ledger") ─────── */

.card {
  background: var(--color-paper);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
  box-shadow: inset 0 0 0 1px rgb(191 200 199 / 0.16);
}

/* --- Status card family (monitoring per-item cards) ---
   Each state gets a soft gradient wash (not a flat fill) that reads from
   across the room, plus a plain hairline border. Priority mirrors
   MonitoringController/_status_card.html.erb's existing card_classes order:
   unverified > challenge-gated > passing[+warning] > failing.
   Scoped under .status-card: card-oat is also used, unrelated to this
   family, as a plain flat-fill panel on 10+ other views (Configure,
   Containers, Admin, etc.) — the compound selector keeps this wash+border
   treatment from leaking into those. */

.status-card.card-pass {
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-success-soft) 70%, var(--color-paper)), var(--color-paper) 62%);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
}

.status-card.card-caution {
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-caution-soft) 85%, var(--color-paper)), var(--color-paper) 62%);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
}

.status-card.card-oat {
  /* Gold-soft is a saturated brand gold, not a pale tint like the other
     -soft tokens — blend at a lower ratio so this wash reads comparably
     pale to card-pass/card-caution/card-info/card-error. */
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-gold-soft) 45%, var(--color-paper)), var(--color-paper) 62%);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
}

.status-card.card-error {
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-danger-soft) 85%, var(--color-paper)), var(--color-paper) 62%);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
}

.status-card.card-info {
  background: linear-gradient(180deg, color-mix(in srgb, #eef4f8 85%, var(--color-paper)), var(--color-paper) 62%);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
}

/* Plain flat-fill panel used by Configure/Containers/Admin/etc. — pre-existing,
   unrelated to the monitoring status-card family above. */
.card-oat {
  background: var(--color-oat);
  border-radius: var(--radius-xl);
  padding: 1.4rem 1.5rem;
  box-shadow: none;
}

.card-quiet {
  background: transparent;
  border: 1px dashed var(--color-line);
  border-radius: var(--radius-xl);
}

/* "Error Details" monospace excerpt inside the Current Issue callout. Neutral,
   not danger-tinted — the callout's border/icon/heading already carry the
   color signal, so the nested code block stays quiet like any other excerpt. */
.codebox-error { background: var(--color-oat); box-shadow: inset 0 0 0 1px rgb(24 28 28 / 0.08); }
.codebox-error .code-text { color: var(--color-ink-soft); }

/* Service logo backdrop: a plain white circle so any logo holds up against
   every wash color above (some logos have transparent backgrounds). */
.status-icon-tile {
  width: 58px; height: 58px; border-radius: 50%;
  background: var(--color-paper);
  box-shadow: 0 1px 2px rgb(24 28 28 / 0.06), 0 3px 10px rgb(24 28 28 / 0.08);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}

/* Shared bare hit-area for the pass/never-seen/challenge-gated/failing status
   icons — no box, no border, just consistent sizing/alignment. */
.status-badge {
  width: 42px; height: 42px;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}

/* The one deliberately louder badge: a filled, glowing chip so an actual
   validation warning can't be missed among the other (bare) status icons. */
.warn-badge {
  width: 40px; height: 40px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--color-caution) 20%, transparent);
  color: var(--color-caution-ink);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-caution) 16%, transparent),
              0 2px 8px color-mix(in srgb, var(--color-caution) 30%, transparent);
  cursor: default;
  flex-shrink: 0;
}

.warn-badge:focus-visible {
  outline: 2px solid var(--color-caution);
  outline-offset: 2px;
}

/* Tracking ID next to the service/pathway name — metadata, not headline. */
.track-id {
  font-family: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, monospace;
  font-weight: 500;
  font-size: 0.78rem;
  color: var(--color-ink-muted);
  font-variant-numeric: tabular-nums;
  margin-left: 0.15rem;
}

/* Shared callout family — Never Seen / Challenge-Gated / Current Issue /
   Validation Warning all use this: the "Quiet Ledger" treatment (design
   direction B) — plain paper background, a thin all-around border in a
   muted variant tint, an icon+heading row, then body content. The card
   itself washes by status (direction C); the callout box deliberately
   stays quiet so it doesn't double up the color signal. Replaces the old
   .panel-info/.panel-error. */
.callout {
  background: var(--color-paper);
  border-radius: 8px;
  padding: 0.75rem 0.9rem;
  margin-top: 0.75rem;
}
.callout-head {
  display: flex; align-items: center; gap: 0.45rem;
  font-weight: 600; font-size: 0.8rem; margin-bottom: 0.3rem;
}
.callout-head svg { width: 15px; height: 15px; flex-shrink: 0; }
.callout-body { font-size: 0.75rem; line-height: 1.5; color: var(--color-ink-muted); }

.callout--info    { border: 1px solid color-mix(in srgb, #3a5a6e 45%, var(--color-paper)); color: #3a5a6e; }
.callout--gold    { border: 1px solid color-mix(in srgb, var(--color-gold-deep) 45%, var(--color-paper)); color: var(--color-gold-ink); }
.callout--danger  { border: 1px solid color-mix(in srgb, var(--color-danger) 45%, var(--color-paper)); color: var(--color-danger); }
.callout--caution { border: 1px solid color-mix(in srgb, var(--color-caution) 45%, var(--color-paper)); color: var(--color-caution-ink); }

.card-floating {
  box-shadow: inset 0 0 0 1px rgb(191 200 199 / 0.16), 0 10px 30px rgb(5 66 64 / 0.08);
}

.ghost-rule-t {
  box-shadow: inset 0 1px 0 rgb(92 107 101 / 0.15);
}

.ghost-rule-b {
  box-shadow: inset 0 -1px 0 rgb(92 107 101 / 0.15);
}

.darkpanel {
  background:
    linear-gradient(rgb(233 240 239 / 0.05) 1px, transparent 1px) 0 0 / 28px 28px,
    linear-gradient(90deg, rgb(233 240 239 / 0.05) 1px, transparent 1px) 0 0 / 28px 28px,
    linear-gradient(160deg, #0a3634, #0f3f3d 55%, #073230);
  color: #eff3f0;
}

.darkpanel-quiet {
  background:
    linear-gradient(rgb(233 240 239 / 0.05) 1px, transparent 1px) 0 0 / 28px 28px,
    linear-gradient(90deg, rgb(233 240 239 / 0.05) 1px, transparent 1px) 0 0 / 28px 28px,
    var(--color-pine-cta);
  color: #eff3f0;
}

.darkpanel-topbar {
  background:
    linear-gradient(rgb(233 240 239 / 0.06) 1px, transparent 1px) 0 0 / 28px 28px,
    linear-gradient(90deg, rgb(233 240 239 / 0.06) 1px, transparent 1px) 0 0 / 28px 28px,
    var(--color-pine);
  color: #eff3f0;
}

/* Buttons — full rules in docs/design/buttons.md. Read it before changing a
   variant or adding one. The short version: btn-pine = primary (one per group),
   btn-ghost = secondary, btn-danger = destructive, btn-gold = GATED ONLY,
   btn-ghost-on-pine = secondary on a dark surface. */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.85rem;
  padding: 0.45rem 0.9rem;
  border-radius: var(--radius-btn);
  white-space: nowrap;
  cursor: pointer;
  border: 0;
  text-decoration: none;
  transition: background-color 150ms, color 150ms;
}

.btn:focus-visible {
  outline: 2px solid var(--color-gold-hover);
  outline-offset: 2px;
}

/* Page-hero actions only — the single action a landing or empty-state page
   exists to prompt. Expect roughly five of these in the whole app. Radius does
   NOT change with size: a 44px pill next to a 31px rounded-rect reads as two
   unrelated components rather than two sizes of one. */
.btn-lg {
  font-size: 0.95rem;
  padding: 0.8rem 1.3rem;
}

/* Deprecated 2026-08-05. `.btn` now carries what `.btn-sm` used to; this alias
   keeps the ~59 existing call sites rendering correctly. Don't add new ones —
   script/design_lint.rb warns on them. Delete once the last one is gone. */
.btn-sm { }

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* Icons inside buttons are Material Symbols spans — never emoji or unicode
   glyphs in the label string. See docs/design/icons.md. */
.btn .material-symbols-outlined {
  font-size: 1.1em;
  line-height: 1;
}

/* Stretches a .btn to fill its container without baking a layout utility
   (w-full) into the button's own class string — see docs/design/buttons.md
   §2 rule 5. Apply the wrapper class to the container, never to .btn itself.
   Currently used by the tests/show suite cards (tests/show.html.erb). */
.btn-fill-container {
  display: block;
  width: 100%;
}
.btn-fill-container .btn {
  width: 100%;
  justify-content: center;
}

.btn-gold {
  background: var(--color-gold-soft);
  color: var(--color-gold-ink);
}
.btn-gold:hover {
  background: var(--color-gold-hover);
}

.btn-pine {
  background: linear-gradient(135deg, var(--color-pine), var(--color-pine-soft));
  color: #ffffff;
}
.btn-pine:hover {
  background: var(--color-pine-cta);
}

.btn-ghost {
  background: var(--color-greige);
  color: var(--color-pine);
}
.btn-ghost:hover {
  background: var(--color-wash);
}

.btn-ghost-on-pine {
  background: rgb(255 255 255 / 0.12);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #ffffff;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
  transition: background-color 150ms, color 150ms, transform 150ms, box-shadow 150ms;
}
.btn-ghost-on-pine:hover {
  background: rgb(255 255 255 / 0.2);
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.3), 0 8px 20px rgb(3 24 23 / 0.4);
}

/* The primary treatment on a dark/pine surface — what btn-pine is on light.
   Solid white lifts off the pine; the ring keeps its edge from dissolving
   into the gradient. See docs/design/buttons.md. */
.btn-white-on-pine {
  background: #ffffff;
  color: var(--color-pine);
  box-shadow: 0 6px 18px rgb(3 24 23 / 0.45), 0 0 0 1px rgb(255 255 255 / 0.5);
  transition: background-color 150ms, color 150ms, transform 150ms, box-shadow 150ms;
}
.btn-white-on-pine:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 26px rgb(3 24 23 / 0.6), 0 0 0 1px rgb(255 255 255 / 0.75);
}

@media (prefers-reduced-motion: reduce) {
  .btn-white-on-pine:hover,
  .btn-ghost-on-pine:hover {
    transform: none;
  }
}

.btn-danger {
  background: var(--color-danger-soft);
  color: var(--color-danger);
}
.btn-danger:hover {
  background: var(--color-danger);
  color: #ffffff;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 0.66rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.34rem 0.7rem;
  border-radius: var(--radius-full);
  white-space: nowrap;
}

.chip-op {
  background: var(--color-pine);
  color: #ffffff;
}

.chip-ver {
  background: var(--color-success-soft);
  color: var(--color-success);
}

.chip-warn {
  background: rgb(253 198 86 / 0.45);
  color: var(--color-gold-ink);
}

.chip-dgr {
  background: var(--color-danger-soft);
  color: var(--color-danger);
}

.chip-mute {
  background: var(--color-wash);
  color: var(--color-ink-muted);
}

/* Notification-bell count badge. The glyph carries the severity; the badge
   follows. Badges sit at 75% opacity deliberately — the pine reads through, so
   the chip sits *in* the topbar rather than pasted onto it. This is the
   sanctioned carve-out to the Brand Guide's no-pill rule: machine state only.
   Keyframes live under ── Animations ── below. */
.bell-count {
  position: absolute;
  top: -1px;
  right: -1px;
  min-width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 0.625rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.bell-count--info {
  background: rgb(255 255 255 / 0.22);
  color: #ffffff;
}

.bell-count--warning {
  background: rgb(245 158 11 / 0.75);
  color: var(--color-amber-ink);
}

.bell-count--critical {
  background: rgb(193 59 35 / 0.75);
  color: #ffffff;
  animation: bell-pulse 1.8s ease-out infinite;
}

.wm {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-headline);
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1;
  white-space: nowrap;
  color: var(--color-pine);
}
.wm .is {
  font-weight: 400;
  margin: 0 0.28em;
  color: var(--color-ink-muted);
}
.wm .on {
  display: inline-flex;
  align-items: center;
  color: var(--color-gold-hover);
}
.wm .dial {
  width: 1em;
  height: 1em;
  margin-right: 0.02em;
}
.wm-rev {
  color: #ffffff;
}
.wm-rev .is {
  color: rgb(255 255 255 / 0.62);
}
.wm-rev .on {
  color: var(--color-gold-soft);
}

/* Ad Spend Protected hero (Executive Overview) — full-width gold-on-pine
   treatment, ported from the approved design-exploration mockup. Tailwind
   utility classes on the ERB handle layout/spacing; this covers the
   gradient background, the noise texture, the gradient-clipped headline
   figure, and the trend-chart SVG that utilities alone can't express. */
.hero-spend-protected {
  background: linear-gradient(160deg, var(--color-pine) 0%, #06302e 70%, #052624 100%);
  border-radius: var(--radius-xl);
  color: var(--color-wash);
  position: relative;
  overflow: hidden;
}

.hero-spend-protected::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgb(255 255 255 / 0.05) 1px, transparent 1px);
  background-size: 16px 16px;
  opacity: 0.5;
  pointer-events: none;
}

.hero-spend-protected .figure {
  font-family: var(--font-headline);
  font-weight: 800;
  letter-spacing: -0.03em;
  font-size: clamp(2.8rem, 7vw, 5rem);
  line-height: 0.95;
  background: linear-gradient(180deg, #fff, var(--color-gold-soft) 140%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Top rail — row 1 of the hero card. Eyebrow left, sync + actions right,
   ghost rule beneath. Ghost rules, never 1px borders, on dark. */
.hero-rail {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 18px;
  box-shadow: inset 0 -1px 0 rgb(255 255 255 / 0.1);
  margin-bottom: 26px;
}

.hero-rail-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 18px;
}

.hero-rail-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-gold);
  box-shadow: 0 0 0 4px rgb(244 190 78 / 0.18);
  flex: none;
}

.hero-rail-divider {
  width: 1px;
  height: 34px;
  background: rgb(255 255 255 / 0.12);
  flex: none;
}

/* Below ~1100px the rail wraps: eyebrow keeps line 1, the sync + actions
   group drops to line 2 and left-aligns rather than hanging off the right. */
@media (max-width: 1100px) {
  .hero-rail-actions {
    width: 100%;
    justify-content: flex-start;
  }
}

/* Letter-grade badge. Color pair comes from ApplicationHelper#grade_band_classes
   (the .prs--* pass-rate ramp), so a letter grade and a pass-rate bar can never
   disagree. Never gold — gold means gated. */
.grade-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-headline);
  font-weight: 800;
  font-size: 0.625rem;
  line-height: 1;
  letter-spacing: 0.02em;
  padding: 4px 6px;
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
}

.trend-chart {
  width: 100%;
  height: 140px;
  overflow: visible; /* dots get a soft glow that can extend past the path bounds */
}

.trend-chart-fill {
  fill: url(#trend-chart-fill-gradient);
  stroke: none;
}

.trend-chart-line {
  fill: none;
  stroke: var(--color-gold-soft);
  stroke-width: 2.5;
  stroke-linecap: round;
}

.trend-chart-dot {
  fill: var(--color-gold-soft);
  cursor: pointer;
}

.trend-chart-dot--now {
  fill: #fff;
  filter: drop-shadow(0 0 6px var(--color-gold));
}

/* ── Animations ─────────────────────────────────────── */

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.loading-dots {
  display: inline-flex;
  animation: pulse 1.5s ease-in-out infinite;
}

/* Marching-dot spinner: a single filled dot orbits a faint track ring.
   Sized in em so it scales with surrounding text; colour follows currentColor. */
@keyframes sync-spinner-rotate {
  to { transform: rotate(360deg); }
}

.sync-spinner {
  display: inline-block;
  position: relative;
  width: 1.1em;
  height: 1.1em;
  vertical-align: -0.2em;
  animation: sync-spinner-rotate 0.9s linear infinite;
}

.sync-spinner::after {
  /* faint static track ring */
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 0.12em solid currentColor;
  opacity: 0.18;
}

.sync-spinner::before {
  /* the marching dot, pinned to the top of the ring and carried around by the rotation */
  content: "";
  position: absolute;
  top: -0.05em;
  left: 50%;
  width: 0.34em;
  height: 0.34em;
  margin-left: -0.17em;
  border-radius: 50%;
  background: currentColor;
}

@media (prefers-reduced-motion: reduce) {
  .sync-spinner { animation-duration: 2.4s; }
}

/* Sync progress bar. Fills smoothly CLIENT-SIDE via a keyframe so it's immune to
   the poller replacing the section's innerHTML (a server-computed width can't
   animate across element swaps) and looks smooth regardless of how fast the sync
   actually is. It eases toward ~90% (never reaching 100% while indeterminate);
   the Stimulus controller adds .is-complete on success to finish it to 100%, then
   .is-fading to gracefully dissolve before the reload — instead of blinking away. */
@keyframes sync-progress-fill {
  from { width: 6%; }
  to   { width: 90%; }
}

.sync-progress-track {
  transition: opacity 300ms ease-out;
}

.sync-progress-bar {
  width: 6%;
  animation: sync-progress-fill 5s cubic-bezier(0.15, 0.75, 0.25, 1) forwards;
}

.sync-progress-track.is-complete .sync-progress-bar {
  width: 100%;
  animation: none;
  transition: width 260ms ease-out;
}

.sync-progress-track.is-fading {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .sync-progress-bar { animation: none; width: 90%; }
  .sync-progress-track.is-complete .sync-progress-bar { transition: none; }
  .sync-progress-track { transition: none; }
}

/* Notification bell, critical state only. Long rest, brief tick —
   instrumentation, not an app trying to feel alive. */
@keyframes bell-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgb(193 59 35 / 0.55); }
  70%      { box-shadow: 0 0 0 9px rgb(193 59 35 / 0); }
}

@keyframes bell-swing {
  0%, 86%, 100% { transform: rotate(0); }
  90%           { transform: rotate(9deg); }
  94%           { transform: rotate(-7deg); }
}

.bell-swing {
  animation: bell-swing 4s ease-in-out infinite;
  transform-origin: 50% 15%;
}

@media (prefers-reduced-motion: reduce) {
  .bell-swing,
  .bell-count--critical {
    animation: none;
  }
}

/* ── Pathway Step Cards (JS-driven, needs stable classes) ── */

.pathway-card-expanded {
  /* Marker class for JS querySelector — styled via Tailwind in template */
}

.steps-row {
  display: flex;
  gap: 0.6rem;
  overflow-x: auto;
  padding-bottom: 0.5rem;
}

.step-card {
  flex-shrink: 0;
  width: 200px;
  border: 1px solid #cfcabb;
  border-radius: 8px;
  padding: 0.5rem 0.6rem;
  background: #eceee3;
  position: relative;
}

.step-card-handle {
  cursor: grab;
  font-size: 0.8rem;
  color: #5c6b65;
  text-align: center;
  letter-spacing: 0.15em;
  line-height: 1;
  margin-bottom: 0.2rem;
  user-select: none;
}

.step-card-handle:active {
  cursor: grabbing;
}

.step-card--dragging {
  opacity: 0.4;
}

.step-card--dragover {
  border-color: #054240;
  border-style: dashed;
}

.step-card--dropped {
  border-color: #054240;
  box-shadow: 0 0 0 3px rgba(5, 66, 64, 0.2);
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.step-card:not(.step-card--dropped) {
  transition: border-color 0.5s ease 2.5s, box-shadow 0.5s ease 2.5s;
}

.step-card-title {
  font-size: 0.65rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #054240;
  margin-bottom: 0.35rem;
}

.step-card-role {
  display: inline-block;
  margin-left: 0.3rem;
  padding: 0.05rem 0.3rem;
  border-radius: 999px;
  background: rgb(92 107 101 / 0.12);
  color: #5c6b65;
  font-size: 0.52rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
}

.step-card-remove {
  position: absolute;
  top: 0.3rem;
  right: 0.4rem;
  background: none;
  border: none;
  color: #c13b23;
  font-size: 0.75rem;
  font-weight: 800;
  cursor: pointer;
  padding: 0;
  line-height: 1;
}

/* Suggestion chip carries full trigger names too — keep it inside the card
   and let the name wrap rather than widen the column. */
.step-suggestion-chip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  min-width: 0;
  max-width: 100%;
  font-size: 0.62rem;
  margin-bottom: 0.3rem;
}

.step-suggestion-chip code {
  overflow-wrap: anywhere;
}

/* Linked-trigger rows: the select must never dictate card width — a bare
   <select> sizes to its longest <option> (trigger names run long) and punches
   out of the fixed-width step card. Constrain it and clip with ellipsis; the
   dropdown itself still shows full option names when opened. */
.linked-trigger-rows {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.linked-trigger-row {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
}

.linked-trigger-select {
  flex: 1;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  padding: 0.28rem 0.4rem;
  border: 1px solid #cfcabb;
  border-radius: 7px;
  background: #ffffff;
  font-size: 0.68rem;
  color: #181c1c;
}

/* Saved links render as a static name, not a dropdown — the select only
   appears in the "+ add another trigger" flow. */
.linked-trigger-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  padding: 0.28rem 0.4rem;
  border: 1px solid transparent; /* aligns baseline with select rows */
  font-size: 0.68rem;
  color: #181c1c;
}

.linked-trigger-edit {
  display: inline-flex;
  align-items: center;
  color: #5c6b65;
  text-decoration: none;
  line-height: 1;
}

.linked-trigger-edit:hover {
  color: #054240;
}

.linked-trigger-edit .material-symbols-outlined {
  font-size: 0.85rem;
}

/* ── Pathway step view (monitoring cards) ─────────── */
/* Vertical step timeline: status icon column with a connecting rail, label
   beside it. Statuses color both the icon and their half of the rail. */

.pathway-step-track {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pathway-step {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.22rem 0;
}

.pathway-step-icon {
  display: inline-flex;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}

/* Connecting rail between icons, colored by the lower step's status. */
.pathway-step + .pathway-step::before {
  content: "";
  position: absolute;
  left: 8px;
  top: -0.55rem;
  height: 1.1rem;
  width: 2px;
  background: currentColor;
  opacity: 0.45;
}

.pathway-step-label {
  font-size: 0.72rem;
  color: #181c1c;
  min-width: 0;
  overflow-wrap: anywhere;
}

.pathway-step--passed { color: var(--color-pine, #054240); }
.pathway-step--failed { color: var(--color-danger, #c13b23); }
.pathway-step--gated { color: var(--color-gold-ink, #5d4200); }
.pathway-step--not_reached { color: var(--color-ink-muted, #5c6b65); }

.pathway-step--failed .pathway-step-label { color: var(--color-danger, #c13b23); font-weight: 600; }
.pathway-step--not_reached .pathway-step-label { color: var(--color-ink-muted, #5c6b65); }

/* ── URL List (JS-driven, triggers + pathways) ────── */

.url-list-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.25rem;
}

.url-list-input {
  flex: 1;
  padding: 0.28rem 0.5rem;
  border: 1px solid #cfcabb;
  border-radius: 7px;
  background: #ffffff;
  font-size: 0.68rem;
  color: #181c1c;
  font-family: "Geist Mono", ui-monospace, monospace;
  min-width: 0;
}

.url-list-remove {
  color: #c13b23;
  font-weight: 800;
  font-size: 0.72rem;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

.url-list-add {
  font-size: 0.7rem;
  color: #054240;
  font-weight: 800;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

/* ── Assertion Rows (step test builder) ───────────── */

.step-action-section {
  border-top: 1px solid #cfcabb;
  padding-top: 0.3rem;
  margin-top: 0.3rem;
}

.step-assertions {
  border-top: 1px solid #cfcabb;
  padding-top: 0.3rem;
  margin-top: 0.3rem;
}

.assertion-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin-bottom: 0.35rem;
  position: relative;
}

.assertion-row select,
.assertion-row input {
  font-size: 0.68rem;
  padding: 0.22rem 0.35rem;
  width: 100%;
}

.assertion-remove {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  background: none;
  border: none;
  color: #c13b23;
  font-weight: 800;
  font-size: 0.7rem;
  cursor: pointer;
  padding: 0 0.15rem;
  line-height: 1;
}

.assertion-add {
  font-size: 0.65rem;
  color: #054240;
  font-weight: 800;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  margin-top: 0.15rem;
}

.step-json-area {
  width: 100%;
  min-height: 5rem;
  font-family: "Geist Mono", ui-monospace, monospace;
  font-size: 0.65rem;
  padding: 0.3rem;
  border: 1px solid #cfcabb;
  border-radius: 6px;
  background: #ffffff;
  color: #181c1c;
  resize: vertical;
}

.step-insert-template {
  font-size: 0.6rem;
  color: #054240;
  font-weight: 700;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  margin-top: 0.15rem;
}

/* ── Tooltip ──────────────────────────────────────── */

.tooltip {
  position: absolute;
  z-index: 100;
  max-width: 300px;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  background: #181c1c;
  color: #eff3f0;
  font-size: 0.75rem;
  line-height: 1.4;
  box-shadow: 0 4px 12px rgb(5 66 64 / 0.14);
  pointer-events: none;
}

.tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #181c1c;
}

/* ── Lightbox (test run artifacts) ────────────────── */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  background: rgb(5 66 64 / 0.45);
  backdrop-filter: blur(6px);
}

.lightbox[hidden] {
  display: none;
}

.lightbox-panel {
  width: min(95vw, 1400px);
  height: min(95vh, 900px);
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding: 1.25rem;
  border-radius: 1rem;
  background: #ffffff;
  box-shadow: 0 24px 64px rgb(5 66 64 / 0.18);
}

.lightbox-toolbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid #cfcabb;
}

.lightbox-meta {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.lightbox-title {
  font-size: 1.05rem;
  font-weight: 800;
  color: #181c1c;
}

.lightbox-description {
  font-size: 0.875rem;
  color: #404848;
}

.lightbox-page {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.lightbox-page-label {
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #5c6b65;
}

.lightbox-page-link {
  color: #054240;
  font-size: 0.875rem;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lightbox-page-link:hover {
  text-decoration: underline;
}

.lightbox-actions {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

.lightbox-frame {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  padding: 1rem;
  border-radius: 0.75rem;
  background: #eceee3;
}

.lightbox-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 0.5rem;
}

.lightbox-html {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 0.5rem;
  background: #ffffff;
}

/* ── Responsive ───────────────────────────────────── */

@media (max-width: 980px) {
  .steps-row { flex-direction: column; }
  .step-card { width: 100%; }
}

/* Responsive data tables: below the sm breakpoint a wide table would either
   scroll sideways (janky) or clip its right columns. Add `table-stack` to a
   <table> and `data-label="Header"` to each <td>, and on phones each row
   becomes a compact card with the column header shown inline before its value.
   The first cell reads as the row's title (no label). Cells left empty (e.g. a
   trailing action column with nothing in it) collapse away. */
@media (max-width: 639px) {
  table.table-stack, table.table-stack tbody { display: block; width: 100%; }
  table.table-stack thead { display: none; }
  table.table-stack tr { display: block; padding: 10px 2px; }
  table.table-stack tr + tr { border-top: 1px solid rgba(17, 24, 39, 0.08); }
  table.table-stack td {
    display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
    padding: 3px 0 !important; border: 0 !important; text-align: right;
  }
  table.table-stack td::before {
    content: attr(data-label); text-align: left; white-space: nowrap; margin-right: auto;
    font-weight: 700; text-transform: uppercase; font-size: 10px; letter-spacing: .06em;
    color: #6b7280;
  }
  /* A cell with an empty data-label (a trailing action column) shows just its
     value; a cell with no content at all collapses. */
  table.table-stack td:empty { display: none; }
}

/* Text inputs below 16px make iOS Safari zoom the page in when they're
   focused, which is jarring and often leaves the field off-centre. Our form
   fields use Tailwind's text-sm (14px). Lift them to 16px only under the `sm`
   breakpoint (640px) so mobile avoids the zoom while desktop keeps its
   compact styling. Excludes checkbox/radio, which aren't text entry. */
@media (max-width: 639px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]):not([type="color"]),
  select,
  textarea {
    font-size: 16px;
  }
}

/* ── Status grid (uptime day-bars) ────────────────── */

.status-grid-body {
  display: flex;
  gap: 0.5rem;
}

.status-grid--lg .status-grid-body {
  height: 288px; /* 24 hourly cells @ ~11px + 1px gaps, plus the day-label row below */
}

.status-grid--sm .status-grid-body {
  height: 84px; /* seamless 12-cell bar — the coarser bucket count is what keeps this legible this short */
}

.status-grid-days {
  flex: 1;
  min-width: 0;
  display: flex;
  gap: 3px;
}

.status-grid-day {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The animated bar stack. DOM order is top-to-bottom (latest hour first);
   animation-delay comes from an inline style set by the ERB partial. */
.status-grid .daycol {
  flex: 1;
  display: flex;
  flex-direction: column;
  transform-origin: bottom;
}

/* Grow-up wave is the big "Last 7 days" hero chart only — the small
   per-item grid embedded in every service/trigger/pathway status card
   used to replay it too, competing with that card's own anchor-flash
   glow (see anchor_highlight_controller.js) every time it re-rendered. */
.status-grid--lg .daycol {
  gap: 1px;
  animation: status-grid-grow 0.95s cubic-bezier(0.45, 0.05, 0.3, 1) both;
}

.status-grid--sm .daycol {
  gap: 0; /* deliberate: small cards are a seamless bar, no hourly separation */
}

.status-grid .cell {
  flex: 1;
  min-height: 0;
}

.status-grid .daycol .cell:first-child {
  border-radius: 3px 3px 0 0;
}

.status-grid .daycol .cell:last-child {
  border-radius: 0 0 3px 3px;
}

.status-grid .cell:hover {
  outline: 1px solid rgb(24 28 28 / 0.4);
  outline-offset: -1px;
  filter: brightness(1.1);
  cursor: crosshair;
}

/* Pass-rate bands — colors come from the --color-prs-* theme tokens
   (app/assets/tailwind/application.css), the single CSS-side source of
   truth. Still keep in sync with PassRateScale::BANDS (app/models/pass_rate_scale.rb),
   which duplicates these same 6 values in Ruby — see the project roadmap
   for the follow-up to unify the two. */
.prs--nodata { background: var(--color-prs-nodata); }
.prs--0      { background: var(--color-prs-0); }    /* 0% passed */
.prs--49     { background: var(--color-prs-49); }    /* 1-49% */
.prs--79     { background: var(--color-prs-79); }    /* 50-79% */
.prs--89     { background: var(--color-prs-89); }    /* 80-89% */
.prs--99     { background: var(--color-prs-99); }    /* 90-99% */
.prs--100    { background: var(--color-prs-100); }   /* 100% passed */

/* Hour axis (big chart): 12 labels, each spanning a 2-hour band */
.status-grid-axis {
  display: flex;
  flex-direction: column;
  padding-bottom: 22px; /* keeps axis aligned with bars above the label row */
}

.status-grid-axis span {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  font-size: 0.625rem;
  line-height: 1;
  color: var(--color-ink-muted);
  font-variant-numeric: tabular-nums;
}

/* Endpoint hints (small card) */
.status-grid-hints {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding-bottom: 22px;
  font-size: 0.625rem;
  line-height: 1;
  color: var(--color-ink-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.status-grid-daylabel {
  height: 18px;
  margin-top: 4px;
  text-align: center;
  font-size: 0.6875rem;
  line-height: 18px;
  color: var(--color-ink-muted);
}

/* Grow-up wave */
@keyframes status-grid-grow {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}

/* Gold top-cell spark — big chart only. The delay (column delay + grow
   duration) is set inline by the ERB partial so it fires as the column lands. */
.status-grid--lg .cell--spark {
  position: relative;
  z-index: 1; /* let the glow paint over neighboring columns */
  animation: status-grid-spark 0.6s ease-out 1 both;
}

@keyframes status-grid-spark {
  0%   { box-shadow: 0 0 0 0 rgb(250 204 21 / 0); }
  40%  { box-shadow: 0 0 12px 3px rgb(250 204 21 / 0.9); }
  100% { box-shadow: 0 0 0 0 rgb(250 204 21 / 0); }
}

/* Cursor-following tooltip (status-grid controller) */
.status-grid-tooltip {
  position: fixed;
  z-index: 1000;
  padding: 0.4rem 0.65rem;
  border-radius: 8px;
  background: #181c1c;
  color: #eff3f0;
  font-size: 0.75rem;
  line-height: 1.45;
  white-space: nowrap;
  box-shadow: 0 4px 12px rgb(5 66 64 / 0.2);
  pointer-events: none;
}

.status-grid-tooltip-stat {
  font-size: 0.6875rem;
  color: #b8c2bd;
}

@media (prefers-reduced-motion: reduce) {
  .status-grid--lg .daycol,
  .status-grid--lg .cell--spark {
    animation: none;
  }
}

/* ── Tracking volatility grid (monitoring/_day_bars_rail) ────────────── */
/* Severity-colored, not the pass-rate-shaded .status-grid above — 3 flat
   tones (this app's existing severity vocabulary: danger/caution/muted-teal)
   instead of PassRateScale's 6-band gradient, since this grid answers "did a
   critical target fail" rather than "how much of the hour passed". Same
   stacked-column structure as .status-grid (7 day columns, cells top-to-
   bottom = latest-to-earliest hour in the DOM, per the view's own .reverse),
   without that grid's grow-in animation or spark — a plain rail summary,
   not the page's hero visual. */
.volatility-grid {
  display: flex;
  gap: 3px;
  height: 168px; /* 24 hourly cells @ 6px + 1px gaps */
}

.volatility-day {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.volatility-cell {
  flex: 1;
  min-height: 0;
}

.volatility-day .volatility-cell:first-child {
  border-radius: 3px 3px 0 0;
}

.volatility-day .volatility-cell:last-child {
  border-radius: 0 0 3px 3px;
}

.volatility-cell--none         { background: var(--color-prs-100); }
.volatility-cell--non-critical { background: var(--color-caution); }
.volatility-cell--critical     { background: var(--color-danger); }
.volatility-cell--no-data      { background: var(--color-prs-nodata); }

/* A light races around a card's own border once an anchor link scrolls it
   into view — see anchor_highlight_controller.js. Originally monitoring-only
   (notification bell/toast, Pause Spend list, all `.status-card`); also
   drives the "View Trigger/Service/Pathway Configuration" links on a failed
   test result (test_runs/show.html.erb), which land on a plain `.card` on
   the configure pages, so both card classes are covered here. Adapted from a
   reference SVG+JS "border trace" technique
   (ai-ops/local-library/glowing-border-trace-button) into pure CSS: a masked
   conic-gradient ring instead of a separate SVG shape, so it automatically
   follows the card's own border-radius (no measuring needed) and needs no JS
   beyond toggling the class we already have. @property registers
   --anchor-trace-angle as an animatable angle — plain custom properties
   can't interpolate, they'd just jump. */
@property --anchor-trace-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@keyframes anchor-trace {
  from { --anchor-trace-angle: 0deg; }
  to   { --anchor-trace-angle: 1080deg; } /* 3 laps over 3s reads as "racing", not a slow crawl */
}

.status-card.anchor-flash,
.card.anchor-flash {
  position: relative;
}

.status-card.anchor-flash::before,
.card.anchor-flash::before {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: inherit;
  padding: 3px;
  background: conic-gradient(
    from var(--anchor-trace-angle),
    transparent 0deg,
    transparent 260deg,
    rgba(244, 190, 78, 0.15) 300deg,
    rgba(240, 169, 58, 0.95) 344deg,
    #fdc656 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: anchor-trace 3s linear;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .status-card.anchor-flash::before,
  .card.anchor-flash::before { animation: none; }
}

/* ── Spend Call (monitoring header redesign) ──────── */

.spend-banner {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 16px 18px;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
.spend-banner--pause   { background: var(--color-danger); }
.spend-banner--risk    { background: var(--color-spend-risk); }
.spend-banner--safe    { background: var(--color-pine); }
.spend-banner--no-data { background: var(--color-ink-muted); }

.spend-banner-icon { width: 20px; height: 20px; flex: none; margin-top: 2px; color: rgb(255 255 255 / 0.85); }
.spend-banner-title {
  font-weight: 800;
  font-size: 14.5px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #fff;
  margin-bottom: 3px;
}
.spend-banner-sub { font-size: 12.5px; line-height: 1.5; color: rgb(255 255 255 / 0.85); }
.spend-banner-sub b { color: #fff; font-weight: 700; }

.spend-body { padding: 16px 18px 18px; }

.cc-list { display: flex; flex-direction: column; gap: 15px; }
.cc-row {
  display: flex; gap: 10px; align-items: flex-start;
  border-radius: 8px; text-decoration: none; color: inherit;
  transition: background-color 0.15s ease;
}
.cc-row:hover { background: var(--color-wash); }
.cc-icon {
  width: 28px; height: 28px; border-radius: 8px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 11px;
}
.cc-icon--pathway, .cc-icon--trigger { background: var(--color-danger-soft); color: var(--color-danger); }
.cc-icon--service { background: var(--color-danger-soft); }
.cc-main { min-width: 0; flex: 1; }
.cc-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 3px; }
.cc-name { font-size: 13.5px; font-weight: 600; color: var(--color-ink); }

/* Shared small "kind" tag — used by the mixed critical-components list
   (.cc-row) and the severity-card rows (.severity-row), so the two near-
   identical badges added alongside it aren't independently maintained. */
.tag-kind {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--color-ink-muted); background: var(--color-oat); padding: 2px 6px; border-radius: 4px;
}
.cc-state { font-size: 12px; font-weight: 600; color: var(--color-danger); line-height: 1.45; }
.cc-primary {
  display: inline-block; margin-left: 4px;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--color-danger); background: var(--color-danger-soft); padding: 2px 6px; border-radius: 4px;
}

.pw-steps { display: flex; gap: 4px; margin: 5px 0; }
.pwstep { flex: 1; height: 5px; border-radius: 2px; background: var(--color-success); }
.pwstep.bad { background: var(--color-danger); }
.pwstep.gated { background: var(--color-caution); }
.pwstep.not-reached { background: var(--color-line); }

.severity-card { display: flex; flex-direction: column; gap: 4px; }
.severity-card-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; }
.severity-tag {
  font-weight: 700; font-size: 12px; letter-spacing: 0.07em; text-transform: uppercase;
  padding: 3px 9px 4px; border-radius: 5px;
}
.severity-tag--fix { background: var(--color-caution-soft); color: var(--color-caution); }
.severity-tag--qol { background: var(--color-oat); color: var(--color-ink-muted); }
.severity-tag--stop { background: var(--color-danger-soft); color: var(--color-danger); }
.severity-frame { font-size: 13px; color: var(--color-ink-muted); margin: 2px 0 14px; max-width: 560px; }
.severity-rows { display: flex; flex-direction: column; }
.severity-row { display: grid; grid-template-columns: 3px 1fr; gap: 14px; padding: 12px 0; border-top: 1px solid var(--color-line); }
.severity-rows .severity-row:first-child { border-top: none; padding-top: 0; }
.severity-row-stripe { border-radius: 2px; background: var(--color-caution); }
.severity-card--qol .severity-row-stripe { background: var(--color-ink-muted); }
.severity-card--stop .severity-row-stripe { background: var(--color-danger); }
.severity-row-stripe--stop { background: var(--color-danger); }
.severity-row-stripe--qol { background: var(--color-ink-muted); }
.severity-row-name { font-weight: 600; font-size: 14.5px; color: var(--color-ink); }
/* .tag-kind supplies the badge look; this just adds row-context spacing. */
.severity-row-kind { margin-left: 8px; }
.severity-row-detail { font-size: 13px; color: var(--color-ink-soft); line-height: 1.5; margin-top: 3px; }

/* ── Section Nav (hover rail) ─────────────────────── */
/* Shared sticky in-page jump nav (Monitoring + Executive Overview). The
   <aside> keeps a fixed 56px layout width so <main> never reflows; this
   <nav> is absolutely positioned inside it and grows over the page content
   on hover, instead of pushing it. The active (scroll-spy) row and
   whichever row is hovered share one treatment — both mean "you're headed
   here." */

.section-nav {
  position: absolute;
  top: 0;
  left: 0;
  width: 56px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px;
  background: var(--color-oat);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  overflow: hidden;
  transition: width 220ms ease;
}

.section-nav:hover,
.section-nav:focus-within {
  width: 208px;
}

.section-nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px;
  border-radius: 8px;
  text-decoration: none;
  color: var(--color-ink-muted);
  white-space: nowrap;
  transition: background-color 150ms ease, color 150ms ease;
}

.section-nav-icon {
  flex-shrink: 0;
  font-size: 19px;
  color: inherit;
}

.section-nav-label {
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0;
  transition: opacity 150ms ease 50ms;
}

.section-nav:hover .section-nav-label,
.section-nav:focus-within .section-nav-label {
  opacity: 1;
}

.section-nav-link:hover,
.section-nav-link.is-active {
  background: var(--color-pine);
  color: var(--color-gold-soft);
}

@media (prefers-reduced-motion: reduce) {
  .section-nav,
  .section-nav-label,
  .section-nav-link {
    transition: none;
  }
}

/* ── Incident ribbon (monitoring hero) ────────────── */

/* A 30-day track per incident row, each bar drawn at its true duration.
   Geometry (left/width percentages) is computed in IncidentRibbonChart and
   arrives as inline styles — the only thing this file can't express, since it
   is per-incident data, not design.

   Severity tone is a single custom property set on the row, so the bar, the
   uncertainty band and the still-open state label all read from one source
   and can't drift apart. The three values are the app's settled severity
   vocabulary, the same one `.severity-row-stripe*` above already paints. */
.ribbon-row {
  --ribbon-tone: var(--color-ink-muted);
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.ribbon-row--stop   { --ribbon-tone: var(--color-danger); }
.ribbon-row--review { --ribbon-tone: var(--color-caution); }
.ribbon-row--qol    { --ribbon-tone: var(--color-ink-muted); }

/* A plain color swatch reading --ribbon-tone, for the rail legend
   (_ribbon_legend.html.erb) — nothing else needs a bare, unpositioned tone
   bar, so this stays separate from .ribbon-bar rather than overloading it
   with an optional-position mode. Same height as the legend's own .ribbon-bar
   fill swatches (Design "FILL — is it over") so the two rows of the legend
   read as one consistent set, not two different swatch styles. */
.ribbon-legend-swatch {
  display: block;
  width: 100%;
  height: 11px;
  border-radius: 2px;
  background: var(--ribbon-tone);
}

/* clip-path, not `overflow: hidden`: the bar's 1.5px min-width and the tick's
   2px are pixel floors laid over percentages, so a span detected at the
   window's right edge would paint a few pixels past the track. Clipping has
   to bound the track horizontally *without* cutting the tick's deliberate
   vertical overhang (below), which is what `overflow: hidden` would do —
   a negative vertical inset keeps that overhang and still clips the ends. */
.ribbon-track {
  position: relative;
  height: 12px;
  border-radius: 3px;
  background: var(--color-wash);
  clip-path: inset(-5px 0);
}

/* min-width is the honest floor for a short outage: a one-hour incident is
   0.14% of a 30-day track, i.e. sub-pixel, and dropping it below a pixel
   would hide a real incident entirely. True for one isolated bar, but a
   FIXED floor summed over many segments inflates the total the row's own
   percent-of-window-broken label states — confirmed live on the real
   Checkout Purchase row (109 segments): the old 4px floor painted 56.2% of
   the track against a labeled 43%. 1.5px bounds that worst case to roughly
   a third of the old distortion while segments stay visibly non-zero-width;
   it does not eliminate the effect (no fixed floor can, at high enough
   segment density), so a very-high-count row is still a legitimate
   candidate for a segment-count-aware floor if this recurs. */
.ribbon-bar {
  position: absolute;
  top: 0;
  bottom: 0;
  min-width: 1.5px;
  border-radius: 3px;
  background: var(--ribbon-tone);
}

/* Still open: no right-hand cap (the bar runs into the window's edge rather
   than ending) plus a stripe, so "open" reads at the level of the individual
   segment — which is what distinguishes the current break from the historical
   ones inside a recurrence row's bar. */
.ribbon-bar--open {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  background: repeating-linear-gradient(
    115deg,
    var(--ribbon-tone) 0 5px,
    color-mix(in srgb, var(--ribbon-tone) 45%, var(--color-paper)) 5px 10px
  );
}

/* The catch gap — last known-good check to the check that caught the break.
   Lighter because the break happened somewhere inside it, not at its edge. */
.ribbon-band {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 3px 0 0 3px;
  background: color-mix(in srgb, var(--ribbon-tone) 26%, var(--color-wash));
}

/* The moment a check caught it. Overhangs the track so it reads as a mark on
   the timeline rather than a slice of the bar. The paper halo is load-bearing,
   not decoration: on a quality-of-life row the tone is ink-muted, close enough
   to the tick's ink that a min-width bar and a tick would otherwise be the
   same small dark mark. */
.ribbon-tick {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  border-radius: 1px;
  background: var(--color-ink);
  box-shadow: 0 0 0 1px var(--color-paper);
}

/* Design §4a's "expandable to the full list": a collapsed simultaneity group
   names one target in its label and counts the rest, so the rest live here.
   A plain <details>, so the row costs no JS and announces itself. */
.ribbon-members > summary {
  list-style: none;
  cursor: pointer;
}

.ribbon-members > summary::-webkit-details-marker { display: none; }

.ribbon-members > summary:focus-visible {
  outline: 2px solid var(--color-pine);
  outline-offset: 2px;
  border-radius: var(--radius);
}

.ribbon-members-caret {
  font-size: 1rem;
  vertical-align: -0.2em;
  color: var(--color-ink-muted);
  transition: transform 0.15s ease;
}

.ribbon-members[open] .ribbon-members-caret { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .ribbon-members-caret { transition: none; }
}

.ribbon-members-list {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  margin-top: 0.375rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-lg);
  background: var(--color-wash);
  font-size: var(--text-tiny);
  color: var(--color-ink-soft);
}

.ribbon-row-state {
  font-size: var(--text-tiny);
  font-weight: 600;
  color: var(--color-ink-muted);
}

.ribbon-row-state--open {
  font-weight: 700;
  color: var(--ribbon-tone);
}

.ribbon-axis {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.625rem;
}

.ribbon-axis span {
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: var(--tracking-micro);
  text-transform: uppercase;
  color: var(--color-ink-muted);
}

/* Zero incidents in the window is the product's best outcome, so it gets a
   real panel rather than an empty track (Design §"Edge Cases"). */
.ribbon-empty {
  padding: 1rem 1.125rem;
  border-radius: var(--radius-lg);
  background: var(--color-wash);
}

.ribbon-overflow {
  margin-top: 0.75rem;
  font-size: var(--text-tiny);
  color: var(--color-ink-muted);
}

/* Narrow viewport (Design §"Edge Cases"): 4a exists because a 30-day track is
   too cramped even at the *desktop* promoted-column width — on a phone the
   whole track is ~300px, where every incident shorter than a day collapses
   onto the bar's 4px floor and the axis labels collide. So below the
   breakpoint the track, its axis and the half of the caption that explains
   them are dropped outright, and each row becomes a line of text: name,
   how long it was down, and how it ended. `.ribbon-duration` is the piece
   that only exists in this mode — above the breakpoint the bar already says
   it, and printing it twice would be noise.

   639px is the app's settled phone breakpoint (see `.table-stack` above),
   which resolves the same problem the same way: below it, a wide horizontal
   structure becomes a stack of labelled text. */
.ribbon-duration { display: none; }

@media (max-width: 639px) {
  .ribbon-track,
  .ribbon-axis,
  .ribbon-caption-bars { display: none; }

  .ribbon-duration {
    display: inline;
    font-size: var(--text-tiny);
    font-weight: 600;
    color: var(--color-ink-soft);
  }
}
