/* ops-page.css — shared styling for /ops and /security.

   Two standalone admin dashboards. Colour tokens follow the site's
   existing --c-* variables so the pages inherit the header, nav and dark
   toggle for free; chart-specific roles are defined here.

   THEMING: the site is toggle-driven ([data-theme="dark"] on <html>),
   NOT prefers-color-scheme — matching style.css rather than introducing a
   second, contradictory mechanism on two pages.

   CHART PALETTE: the eight categorical slots below are a validated set.
   Run against this file's real surfaces (#FFFFFF light, #1E293B dark):
     light — worst adjacent CVD dE 9.1, normal-vision dE 19.6, ALL PASS
     dark  — worst adjacent CVD dE 8.4, normal-vision dE 19.3, ALL PASS
   Three light slots and one dark slot sit under 3:1 against the surface,
   which obliges RELIEF: every chart ships a legend with direct labels and
   a per-panel table view, so identity is never carried by colour alone.
   Re-run the validator before changing any hex. */

/* Hide the body until the auth guard resolves — same mechanism as /admin. */
html[data-fp-gating] body { visibility: hidden; }

:root {
  /* Chart surfaces + ink */
  --ops-surface:   #FFFFFF;
  --ops-grid:      #E2E8F0;
  --ops-axis:      #94A3B8;
  --ops-ink:       #1E293B;
  --ops-ink-muted: #64748B;

  /* Status — fixed, never themed. Always paired with an icon + label,
     because warning/serious are deliberately sub-3:1 on a light surface. */
  --ops-good:     #0ca30c;
  --ops-warning:  #fab219;
  --ops-serious:  #ec835a;
  --ops-critical: #d03b3b;

  /* Categorical series, light steps */
  --ops-s1: #2a78d6;  --ops-s2: #eb6834;  --ops-s3: #1baf7a;  --ops-s4: #eda100;
  --ops-s5: #e87ba4;  --ops-s6: #008300;  --ops-s7: #4a3aa7;  --ops-s8: #e34948;
}

[data-theme="dark"] {
  --ops-surface:   #1E293B;
  --ops-grid:      #334155;
  --ops-axis:      #94A3B8;
  --ops-ink:       #E2E8F0;
  --ops-ink-muted: #94A3B8;
  /* Same eight hues, re-stepped for the dark surface — not an auto-flip. */
  --ops-s1: #3987e5;  --ops-s2: #d95926;  --ops-s3: #199e70;  --ops-s4: #c98500;
  --ops-s5: #d55181;  --ops-s6: #008300;  --ops-s7: #9085e9;  --ops-s8: #e66767;
}

/* ── Shell ─────────────────────────────────────────────────────────── */
.ops-shell { max-width: 1180px; margin: 0 auto; padding: 24px 20px 64px; }

.ops-h {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: 12px; margin-bottom: 6px;
}
.ops-h h1 { margin: 0; font-size: 1.5rem; }
.ops-sub { font-size: .86rem; color: var(--c-muted); margin: 0 0 20px; }

.ops-toolbar {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-bottom: 20px;
}
.ops-range { display: flex; gap: 4px; }
.ops-range button {
  padding: 5px 12px; font-size: .82rem; cursor: pointer;
  border: 1.5px solid var(--c-border); border-radius: 6px;
  background: var(--c-bg); color: var(--c-text);
}
.ops-range button[aria-pressed="true"] {
  background: var(--c-green); border-color: var(--c-green); color: #fff;
}
.ops-range button:focus-visible { outline: 2px solid var(--c-green); outline-offset: 2px; }

/* ── Status tiles ──────────────────────────────────────────────────── */
.ops-tiles {
  display: grid; gap: 12px; margin-bottom: 26px;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
}
.ops-tile {
  border: 1.5px solid var(--c-border); border-radius: 10px;
  padding: 12px 14px; background: var(--c-bg-alt);
  /* The status colour is a 3px rail, not the tile's whole background:
     a wash of red across a card is unreadable and shouts at a glance
     even when the value is what matters. */
  border-left-width: 4px;
}
.ops-tile--green    { border-left-color: var(--ops-good); }
.ops-tile--amber    { border-left-color: var(--ops-warning); }
.ops-tile--red      { border-left-color: var(--ops-critical); }
.ops-tile--unknown  { border-left-color: var(--c-subtle); }

.ops-tile__label {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--c-muted); margin-bottom: 4px;
}
.ops-tile__value {
  font-size: 1.5rem; font-weight: 650; line-height: 1.1; color: var(--c-text);
}
.ops-tile__unit { font-size: .82rem; font-weight: 400; color: var(--c-muted); }
.ops-tile__detail {
  font-size: .78rem; color: var(--c-muted); margin-top: 5px; line-height: 1.4;
}
/* Shown only when a check stopped being written. Deliberately loud
   relative to the detail line: the tile's own status rail will still be
   green from the last good run, so this text is the ONLY thing telling a
   reader the number above it is history rather than news. */
.ops-tile__stale {
  font-size: .72rem; font-weight: 650; margin-top: 6px;
  text-transform: uppercase; letter-spacing: .03em;
  /* Uses the SAME light/dark pair as .ops-pill--amber rather than the raw
     --ops-serious ramp step: #ec835a is a chart-fill colour and lands at
     roughly 2.8:1 on the white surface, which is unreadable at .72rem.
     The amber pair is already the file's validated small-text warning. */
  color: var(--c-amber-text);
}
[data-theme="dark"] .ops-tile__stale { color: var(--ops-warning); }
/* Per-tile history. Collapsed by default like the metric panels, but for
   a different reason: this one costs a DynamoDB query rather than a
   CloudWatch read, so it is collapsed for legibility, not for money. */
.ops-tile__hist { margin-top: 8px; }
.ops-tile__hist > summary {
  cursor: pointer; font-size: .72rem; font-weight: 600;
  letter-spacing: .03em; text-transform: uppercase; color: var(--c-muted);
  list-style: none;
}
.ops-tile__hist > summary::-webkit-details-marker { display: none; }
.ops-tile__hist > summary::before { content: '▸ '; }
.ops-tile__hist[open] > summary::before { content: '▾ '; }
.ops-tile__hist > summary:hover { color: var(--ops-ink); }
.ops-tile__hist > summary:focus-visible {
  outline: 2px solid var(--ops-s1); outline-offset: 2px;
}
/* uPlot sizes to its container, so the box needs a real height or the
   chart renders 0px tall and reads as "loading forever". */
.ops-tile__chart { margin-top: 6px; min-height: 140px; }
.ops-tile__histnote {
  font-size: .72rem; margin-top: 6px; line-height: 1.4;
}

/* Icon + label, so status never rides on colour alone (CVD, print,
   forced-colors, and greyscale screenshots all lose the rail). */
.ops-pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: .72rem; font-weight: 600; letter-spacing: .03em;
  text-transform: uppercase;
}
.ops-pill--green   { color: #006300; }
.ops-pill--amber   { color: var(--c-amber-text); }
.ops-pill--red     { color: var(--ops-critical); }
.ops-pill--unknown { color: var(--c-muted); }
[data-theme="dark"] .ops-pill--green { color: var(--ops-good); }
[data-theme="dark"] .ops-pill--amber { color: var(--ops-warning); }

/* ── Expandable panels ─────────────────────────────────────────────── */
/* Collapsed by default and fetched on expand. This is the UX the brief
   asked for AND the cost control: a collapsed panel issues no CloudWatch
   read at all. */
.ops-panel {
  border: 1.5px solid var(--c-border); border-radius: 10px;
  margin-bottom: 12px; background: var(--c-bg-alt); overflow: hidden;
}
.ops-panel > summary {
  cursor: pointer; padding: 13px 16px; font-weight: 600;
  display: flex; align-items: center; gap: 10px; list-style: none;
}
.ops-panel > summary::-webkit-details-marker { display: none; }
.ops-panel > summary::before {
  content: '▸'; font-size: .8rem; color: var(--c-muted);
  transition: transform .15s ease;
}
.ops-panel[open] > summary::before { transform: rotate(90deg); }
.ops-panel > summary:hover { background: rgba(0, 0, 0, .03); }
[data-theme="dark"] .ops-panel > summary:hover { background: rgba(255, 255, 255, .04); }
.ops-panel > summary:focus-visible { outline: 2px solid var(--c-green); outline-offset: -2px; }
.ops-panel__note { font-weight: 400; font-size: .78rem; color: var(--c-muted); margin-left: auto; }
.ops-panel__body { padding: 4px 16px 18px; }

/* ── Charts ────────────────────────────────────────────────────────── */
.ops-chart { width: 100%; min-height: 240px; }
/* Wide content scrolls inside its own container so the page body never
   scrolls sideways on a phone. */
.ops-chart-scroll { overflow-x: auto; }

.ops-legend {
  display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 10px 0 4px;
  font-size: .8rem;
}
.ops-legend__item { display: inline-flex; align-items: center; gap: 6px; color: var(--c-text); }
.ops-legend__swatch {
  width: 11px; height: 11px; border-radius: 3px; flex: none;
  /* 2px surface ring so adjacent swatches never merge into one block. */
  box-shadow: 0 0 0 2px var(--ops-surface);
}
.ops-legend__val { color: var(--c-muted); font-variant-numeric: tabular-nums; }

/* ── Tables (the relief mechanism for sub-3:1 series) ──────────────── */
.ops-table-wrap { overflow-x: auto; }
.ops-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.ops-table th, .ops-table td {
  text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--c-border);
  white-space: nowrap;
}
.ops-table th { font-weight: 600; color: var(--c-muted); font-size: .76rem;
                text-transform: uppercase; letter-spacing: .04em; }
.ops-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.ops-table tbody tr:hover { background: rgba(0, 0, 0, .025); }
[data-theme="dark"] .ops-table tbody tr:hover { background: rgba(255, 255, 255, .03); }

/* ── Chart chrome injected via innerHTML ───────────────────────────── */
/* The site's CSP is `style-src 'self'` with no 'unsafe-inline', so an
   HTML-parsed style attribute is REFUSED. Geometry therefore lives here as
   real classes, and only the two value-derived properties (series colour,
   bar width) travel as `data-fp-style`, which window.fpApplyStyles()
   promotes to a style property after render — the same mechanism admin.js
   uses. Getting this wrong renders the bars and swatches invisible, and
   the legend is the contrast relief the palette validator requires. */
.ops-tip__dot {
  display: inline-block; width: 9px; height: 9px; border-radius: 2px;
  margin-right: 6px; box-shadow: 0 0 0 2px var(--c-bg);
}
.ops-tip__when { color: var(--ops-ink-muted); margin-bottom: 3px; }

.ops-bar__label { width: 34%; }
.ops-bar__track { width: 50%; }
.ops-bar__fill {
  display: block; height: 11px;
  /* 4px rounded data-end, anchored to the baseline at the left. */
  border-radius: 0 4px 4px 0;
}

.ops-toggle-table {
  margin-top: 10px; font-size: .78rem; background: none; border: none;
  color: var(--c-green); cursor: pointer; padding: 2px 0; text-decoration: underline;
}
.ops-toggle-table:focus-visible { outline: 2px solid var(--c-green); outline-offset: 2px; }

/* ── Security-specific ─────────────────────────────────────────────── */
.sec-banner {
  border-radius: 10px; padding: 14px 16px; margin-bottom: 20px;
  border: 1.5px solid var(--c-border); background: var(--c-bg-alt);
  border-left-width: 4px; border-left-color: var(--ops-good);
}
.sec-banner--warn     { border-left-color: var(--ops-warning); }
.sec-banner--critical { border-left-color: var(--ops-critical); }
.sec-banner h2 { margin: 0 0 4px; font-size: 1rem; }
.sec-banner p  { margin: 0; font-size: .86rem; color: var(--c-muted); }
.sec-banner ul { margin: 8px 0 0; padding-left: 18px; font-size: .85rem; }

.sec-feed { max-height: 460px; overflow-y: auto; }
.sec-ev {
  display: grid; grid-template-columns: 92px 150px 1fr; gap: 10px;
  padding: 7px 10px; border-bottom: 1px solid var(--c-border);
  font-size: .82rem; align-items: baseline;
}
.sec-ev__time { color: var(--c-muted); font-variant-numeric: tabular-nums; }
.sec-ev__kind { font-weight: 600; }
.sec-ev__meta { color: var(--c-muted); word-break: break-word; }
.sec-ev--critical .sec-ev__kind { color: var(--ops-critical); }
.sec-ev--warn     .sec-ev__kind { color: var(--c-amber-text); }
[data-theme="dark"] .sec-ev--warn .sec-ev__kind { color: var(--ops-warning); }

.sec-trace-form { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.sec-trace-form input {
  flex: 1 1 240px; padding: 7px 10px; font-size: .86rem;
  border: 1.5px solid var(--c-border); border-radius: 6px;
  background: var(--c-bg); color: var(--c-text);
}
.sec-trace-form button {
  padding: 7px 16px; font-size: .86rem; cursor: pointer; border-radius: 6px;
  border: 1.5px solid var(--c-green); background: var(--c-green); color: #fff;
}
.sec-trace-form button:disabled { opacity: .55; cursor: default; }

.ops-ip-btn {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--c-green); text-decoration: underline; font: inherit;
  font-variant-numeric: tabular-nums;
}

.ops-empty { color: var(--c-muted); font-size: .86rem; padding: 14px 0; }
.ops-err {
  color: var(--ops-critical); font-size: .85rem; padding: 10px 0;
}

@media (max-width: 620px) {
  .sec-ev { grid-template-columns: 76px 1fr; }
  .sec-ev__meta { grid-column: 1 / -1; }
}
