/* =========================================================
   916scanner — Status Page (redesign / test)
   Aligned with main site design language:
   - Helvetica Neue / Arial typeface
   - #0a0a0f bg, translucent-white glass surfaces
   - Tailwind-blue accents (#3b82f6 / #60a5fa)
   - 10px / 999px border radii
   ========================================================= */

:root {
    --bg:           #11141d;
    --bg-2:         #161a24;
    --surface:      rgba(255, 255, 255, 0.04);
    --surface-2:    rgba(255, 255, 255, 0.07);
    --surface-3:    rgba(255, 255, 255, 0.10);
    --line:         rgba(255, 255, 255, 0.08);
    --line-2:       rgba(255, 255, 255, 0.16);

    --text:         #ffffff;
    --text-mute:    rgba(255, 255, 255, 0.65);
    --text-dim:     rgba(255, 255, 255, 0.42);

    --accent:       #3b82f6;
    --accent-soft:  #60a5fa;
    --accent-light: #93c5fd;

    --ok:           #22c55e;
    --ok-soft:      rgba(34, 197, 94, 0.15);
    --elev:         #f59e0b;
    --elev-soft:    rgba(245, 158, 11, 0.16);
    --off:          #ef4444;
    --off-soft:     rgba(239, 68, 68, 0.16);

    --shadow-1:     0 1px 0 rgba(255,255,255,0.03), 0 6px 18px rgba(0,0,0,0.45);
    --shadow-2:     0 1px 0 rgba(255,255,255,0.04), 0 16px 48px rgba(0,0,0,0.55);

    --radius:       10px;
    --radius-sm:    10px;
    --maxw:         1180px;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
    font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
    font-feature-settings: "tnum", "ss01";
    /* No font-smoothing properties on purpose. Inter at 400 with grayscale
       smoothing reads visibly thinner than subpixel-rendered text on macOS,
       particularly for UI labels. Letting macOS use its default subpixel
       anti-aliasing keeps strokes at a more natural weight. */
    line-height: 1.45;
}

body {
    min-height: 100vh;
}

a { color: var(--accent-soft); text-decoration: none; }
a:hover { color: var(--accent-light); text-decoration: underline; }

::selection { background: rgba(59, 130, 246, 0.35); }

/* ---- Live progress bar (fetch indicator) ---- */
.liveBar {
    position: fixed;
    top: 0; left: 0;
    height: 2px;
    width: 0%;
    background: linear-gradient(90deg, transparent, var(--accent) 50%, var(--accent-light) 90%, transparent);
    z-index: 100;
    transition: width 0.6s cubic-bezier(.2,.7,.2,1), opacity 0.4s linear;
    opacity: 0;
    pointer-events: none;
}
.liveBar.is-active { opacity: 1; }

/* ---- Top bar ---- */
.topbar {
    position: sticky;
    top: 0;
    z-index: 50;
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    background: rgba(17, 20, 29, 0.72);
    border-bottom: 1px solid var(--line);
}
.topbar__inner {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 12px 24px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
}
/* The brand container is now a non-link wrapper. Only the logo inside is
   clickable (links to /). The "STATUS" label sits next to it as plain
   text — clicking it does nothing, which matches its informational role. */
.topbar__brand {
    display: flex;
    align-items: center;
    gap: 12px;
}
.topbar__logoLink {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: inherit;
    line-height: 0;
}
.topbar__logoLink:hover { text-decoration: none; }
.topbar__logoLink:hover .topbar__logo { opacity: 1; }
.topbar__logo {
    height: 22px;
    width: auto;
    display: block;
    opacity: 0.95;
    transition: opacity 0.15s ease;
}
.topbar__label {
    font-size: 11px;
    color: rgba(255, 255, 255, 0.55);
    text-transform: uppercase;
    letter-spacing: 0.22em;
    font-weight: 500;
    padding-left: 12px;
    border-left: 1px solid var(--line-2);
    line-height: 1;
}
.topbar__right {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12px;
    color: var(--text-mute);
}
.topbar__pulse { display: inline-flex; }
.pulseDot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--ok);
    animation: pulseDot 1.6s ease-out infinite;
}
/* One-shot expanding ring per cycle. End state has no ring; the iteration
   snaps back instantly to the start so there's no contracting animation. */
@keyframes pulseDot {
    0%   { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5); }
    100% { box-shadow: 0 0 0 6px rgba(34, 197, 94, 0); }
}
.topbar__live {
    color: var(--ok);
    font-weight: 700;
    font-size: 11px;
    letter-spacing: 0.18em;
}

/* The topbar LIVE indicator reflects whether the page is updating, not the
   health of the feeds. It stays green regardless of system state — feed
   health is communicated by the hero status icon and headline. */

/* ---- Container ---- */
.container {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 32px 24px 80px;
}

/* =========================================================
   HERO — clarity-first: status icon + plain headline,
   listener count, then 7-day inline stats.
   ========================================================= */
.hero {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    /* Two-layer background: a soft accent-blue radial glow anchored at the
       top-left corner (near the status icon) over the flat white-tint base.
       Stays neutral with respect to state — no green/amber/red wash, since
       the icon already carries the state color. The glow fades to zero by
       ~60% so it reads as ambient depth, not an alert. */
    background:
        radial-gradient(circle at 0% 0%,
            rgba(59, 130, 246, 0.14),
            rgba(59, 130, 246, 0)),
        rgba(255, 255, 255, 0.03);
    padding: 28px 32px 24px;
    box-shadow: 0 1px 24px rgba(0, 0, 0, 0.35);
}

/* Status row — large, can't-miss headline */
.hero__status {
    display: flex;
    align-items: center;
    gap: 14px;
    position: relative;
    z-index: 1;
}
.hero__statusIcon {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
}
/* Only the icon glyph picks up the state color — the hero itself stays
   neutral so the card doesn't take on a green / amber / red wash. */
.hero__statusGlyph { display: none; }
.hero[data-state="normal"]   .hero__statusGlyph--normal   { display: inline; color: var(--ok); }
.hero[data-state="elevated"] .hero__statusGlyph--elevated { display: inline; color: var(--elev); }
.hero[data-state="degraded"] .hero__statusGlyph--degraded { display: inline; color: var(--off); }

.hero__statusText {
    font-size: 22px;
    font-weight: 700;
    color: var(--text);
    margin: 0;
    letter-spacing: -0.01em;
    line-height: 1.2;
}

/* Two-column body — primary count on the left, system summary facts on
   the right. The facts panel uses the otherwise-dead space without
   adding a chart. */
.hero__body {
    margin-top: 22px;
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(220px, auto);
    gap: 40px;
    align-items: end;
}

.hero__primary {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

/* System summary facts — vertical stack of label-on-top, value-below
   pairs. Border-left divider separates the column visually from the
   primary count. */
.hero__facts {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 18px;
    padding-left: 28px;
    border-left: 1px solid var(--line);
}
.hero__fact {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.hero__factLabel {
    font-size: 10px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-weight: 600;
    line-height: 1;
}
.hero__factValue {
    font-size: 16px;
    color: var(--text);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
}
.hero__count {
    font-size: clamp(48px, 8vw, 76px);
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 0.95;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
/* Direction-aware count bumps. Increase = upward bounce + brief color
   brighten (reads as "good news, more listeners"). Decrease = small
   downward bounce, no color change. Same rule applies to per-row counts
   below; magnitudes differ to match each element's visual weight. */
.hero__count.is-bumping-up   { animation: heroBumpUp   0.55s cubic-bezier(.2,.8,.2,1); }
.hero__count.is-bumping-down { animation: heroBumpDown 0.55s cubic-bezier(.2,.8,.2,1); }
@keyframes heroBumpUp {
    0%   { transform: translateY(0); color: var(--text); }
    25%  { transform: translateY(-4px); color: var(--accent-light); }
    100% { transform: translateY(0); color: var(--text); }
}
@keyframes heroBumpDown {
    0%   { transform: translateY(0); }
    35%  { transform: translateY(2px); }
    100% { transform: translateY(0); }
}
.hero__countLabel {
    font-size: 14px;
    color: var(--text-mute);
    line-height: 1.4;
    font-weight: 500;
}

/* ---- Outage banner ---- */
.outage {
    margin-top: 20px;
    display: flex;
    align-items: center;
    gap: 14px;
    background: rgba(239, 68, 68, 0.08);
    border: 1px solid rgba(239, 68, 68, 0.30);
    border-radius: var(--radius);
    padding: 14px 18px;
}
.outage.hidden { display: none; }
.outage__pulse {
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--off);
    flex-shrink: 0;
    animation: outagePulse 1.6s ease-out infinite;
}
/* Outward-only pulse — ring grows and fades, then resets instantly. */
@keyframes outagePulse {
    0%   { box-shadow: 0 0 0 0   rgba(239, 68, 68, 0.6); }
    100% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
}
.outage__body { flex: 1; min-width: 0; }
.outage__title {
    font-weight: 600;
    color: #ffd9d9;
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.outage__sub {
    font-size: 12px;
    color: var(--text-mute);
    margin-top: 2px;
}
.outage__jump {
    font-size: 12px;
    font-weight: 600;
    color: var(--accent-soft);
    white-space: nowrap;
    padding: 7px 14px;
    border-radius: 999px;
    border: 1px solid rgba(96, 165, 250, 0.30);
    background: rgba(96, 165, 250, 0.08);
    display: inline-flex;
    align-items: center;
    gap: 8px;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.outage__jump:hover {
    background: rgba(96, 165, 250, 0.16);
    border-color: rgba(96, 165, 250, 0.55);
    color: var(--accent-light);
    text-decoration: none;
    transform: translateX(1px);
}
.outage__jump i {
    font-size: 11px;
    transition: transform 0.15s ease;
}
.outage__jump:hover i {
    transform: translateX(2px);
}

/* ---- Section shell ---- */
.section {
    margin-top: 36px;
}
.section__header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 14px;
    padding-bottom: 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--line);
}
.section__titleGroup {
    display: flex;
    align-items: baseline;
    gap: 14px;
    flex-wrap: wrap;
}
.section__title {
    margin: 0;
    font-size: 18px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
}
.section__count {
    font-size: 12px;
    color: var(--text-mute);
    font-variant-numeric: tabular-nums;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 3px 10px;
}
.section__hint {
    font-size: 12px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-weight: 600;
}

/* ---- Legend ---- */
.legend {
    display: flex;
    gap: 14px;
    align-items: center;
    flex-wrap: wrap;
}
.legend__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--text-mute);
    text-transform: uppercase;
    letter-spacing: 0.10em;
    font-weight: 600;
}
.legend__swatch {
    width: 8px; height: 8px;
    border-radius: 2px;
}
.legend__swatch--ok   { background: var(--ok); }
.legend__swatch--elev { background: var(--elev); }
.legend__swatch--off  { background: var(--off); }

/* ---- Controls ---- */
.controls {
    display: flex;
    gap: 10px;
    align-items: center;
}
/* Filter (segmented) and sort (select) controls share visual size so the
   row reads as one toolbar. Both target ~30px outer height: pill border +
   8px vertical padding + 12px text. */
.seg {
    display: inline-flex;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 2px;
    line-height: 1;
}
.seg__btn {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-mute);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    padding: 6px 14px;
    border-radius: 999px;
    cursor: pointer;
    letter-spacing: 0.04em;
    line-height: 1.25;
    transition: background 0.15s ease, color 0.15s ease;
}
.seg__btn[aria-pressed="true"] {
    background: var(--surface-2);
    color: var(--text);
    box-shadow: 0 0 0 1px var(--line-2);
}
.seg__btn:hover { color: var(--text); }

.select {
    appearance: none;
    -webkit-appearance: none;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 8px 30px 8px 14px;
    font-size: 12px;
    font-weight: 500;
    font-family: inherit;
    line-height: 1.25;
    cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 5l3 3 3-3'/></svg>");
    background-repeat: no-repeat;
    background-position: right 10px center;
}
.select:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
/* No iOS override — modern Safari (15.4+) respects `appearance: none` on
   selects, so our custom SVG chevron renders correctly. The previous
   override forced native menulist styling, which dropped a default
   black caret on top of the dark pill. */

/* =========================================================
   FEED ROWS
   ========================================================= */
.rows {
    display: flex;
    flex-direction: column;
    /* No flex gap — gap is applied via .row margin-bottom so hidden rows
       (max-height: 0) can collapse the spacing too. With flex gap, every
       hidden row would still inject 6px between siblings, leaving a tall
       empty column below the last visible row when the filter hides many. */
}
.rows .row { margin-bottom: 6px; }
.rows .row:last-child { margin-bottom: 0; }
.rows__empty {
    padding: 28px;
    text-align: center;
    color: var(--text-mute);
    font-size: 13px;
    border: 1px dashed var(--line);
    border-radius: var(--radius);
}
.rows__empty.hidden { display: none; }

.row {
    position: relative;
    display: grid;
    grid-template-columns: minmax(220px, 1.4fr) minmax(220px, 2.2fr) 130px;
    align-items: center;
    gap: 18px;
    padding: 14px 18px;
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    max-height: 220px;
    margin-top: 0;
    transition:
        transform 0.18s ease,
        border-color 0.18s ease,
        background 0.18s ease,
        box-shadow 0.18s ease,
        max-height 0.40s cubic-bezier(.4,0,.2,1),
        opacity 0.30s cubic-bezier(.4,0,.2,1),
        padding 0.35s cubic-bezier(.4,0,.2,1),
        margin 0.35s cubic-bezier(.4,0,.2,1),
        border-width 0.30s cubic-bezier(.4,0,.2,1);
    animation: rowFadeIn 0.5s cubic-bezier(.2,.7,.2,1) backwards;
    animation-delay: var(--rowDelay, 0ms);
}
/* When a row is FLIPping into a new sort position, its inline transition
   overrides .row's shorthand transition for the FLIP duration. Reduced
   motion users get neither — the @media query below disables both the
   FLIP transform animation and the show/hide collapse. */
@media (prefers-reduced-motion: reduce) {
    .row { transition: none !important; }
}
@keyframes rowFadeIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* Feed rows aren't clickable, so no hover affordance — a color shift
   would falsely suggest interactivity. */
.row[data-state="elev"] {
    border-color: rgba(245, 158, 11, 0.35);
    background: rgba(245, 158, 11, 0.06);
}
.row[data-state="off"] {
    opacity: 0.72;
}
/* Hidden rows collapse via max-height + opacity rather than display:none
   so the transition is animatable on both enter and exit. The row stays
   in the DOM with a 0px max-height; overflow:hidden on .row clips the
   collapsing content so it doesn't bleed out during the animation. */
.row.is-hidden {
    max-height: 0;
    opacity: 0;
    margin-top: 0;
    margin-bottom: 0;
    padding-top: 0;
    padding-bottom: 0;
    border-top-width: 0;
    border-bottom-width: 0;
    pointer-events: none;
}

.row__head {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}
.row__statusDot {
    width: 9px; height: 9px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--ok);
    box-shadow: 0 0 0 3px var(--ok-soft);
}
.row[data-state="elev"] .row__statusDot {
    background: var(--elev);
    box-shadow: 0 0 0 3px var(--elev-soft);
    animation: rowDotPulse 1.6s ease-in-out infinite;
}
.row[data-state="off"] .row__statusDot {
    background: var(--off);
    box-shadow: 0 0 0 3px var(--off-soft);
}
@keyframes rowDotPulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.25); }
}
.row__name {
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.row__spark {
    height: 32px;
    display: flex;
    align-items: flex-end;
    gap: 1.5px;
    padding: 2px 0;
}
.row__spark .bar {
    flex: 1;
    min-width: 1px;
    background: var(--accent-soft);
    border-radius: 2px 2px 0 0;
    /* Per-bar staggered transitions. --bar-i is set in JS as
       (count-1)-idx, so the rightmost (most recent) bar gets delay 0 and
       the wave ripples back through history. With 84 buckets at 6ms each
       the last bar starts ~500ms after the first; combined with the 0.7s
       height transition itself the full sweep takes about 1.2s. Color
       transition kept fast (0.35s) so state flips read as immediate even
       though heights are gliding. */
    transition:
        opacity    0.15s ease,
        height     0.7s  cubic-bezier(.2,.85,.3,1) calc(var(--bar-i, 0) * 6ms),
        background 0.35s ease                       calc(var(--bar-i, 0) * 6ms);
    cursor: crosshair;
}
@media (prefers-reduced-motion: reduce) {
    .row__spark .bar { transition: none; }
}
.row__spark .bar:hover { opacity: 0.7; }
.row__spark .bar--elev { background: var(--elev); }
.row__spark .bar--off  { background: var(--off); }
.row__spark .bar--empty {
    background: rgba(255,255,255,0.08);
    height: 2px !important;
    cursor: default;
}

.row__metric {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    line-height: 1.2;
}
.row__count {
    font-size: 24px;
    font-weight: 700;
    color: var(--accent-soft);
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    display: inline-block;
}
.row__count--elev { color: var(--elev); }
.row__count--off  { color: var(--text-dim); }

/* Per-row bumps: same direction-aware pattern as the hero, scaled down
   so individual rows don't compete with the headline number. */
.row__count.is-bumping-up   { animation: rowBumpUp   0.45s cubic-bezier(.2,.8,.2,1); }
.row__count.is-bumping-down { animation: rowBumpDown 0.45s cubic-bezier(.2,.8,.2,1); }
@keyframes rowBumpUp {
    0%   { transform: translateY(0); color: var(--accent-soft); }
    25%  { transform: translateY(-3px); color: var(--accent-light); }
    100% { transform: translateY(0); color: var(--accent-soft); }
}
@keyframes rowBumpDown {
    0%   { transform: translateY(0); }
    35%  { transform: translateY(1.5px); }
    100% { transform: translateY(0); }
}
/* Elevated rows already use amber; bump-up flash brightens to a paler
   amber instead of the default accent-light. */
.row__count--elev.is-bumping-up {
    animation: rowBumpUpElev 0.45s cubic-bezier(.2,.8,.2,1);
}
@keyframes rowBumpUpElev {
    0%   { transform: translateY(0); color: var(--elev); }
    25%  { transform: translateY(-3px); color: #fcd34d; }
    100% { transform: translateY(0); color: var(--elev); }
}
.row__countLabel {
    font-size: 11px;
    color: var(--text-mute);
    margin-top: 4px;
}

/* =========================================================
   TIMELINE
   ========================================================= */
.timeline {
    position: relative;
    /* Reserved left zone for absolute timestamps (~84px), then the rail
       gutter (16px), then content. Timestamps sit to the left of the rail
       so each entry's "when" is anchored on the chronological axis. */
    padding-left: 110px;
}
.timeline::before {
    content: "";
    position: absolute;
    left: 89px;
    top: 0;
    bottom: 0;
    width: 2px;
    background: linear-gradient(180deg,
        transparent 0%,
        var(--line) 6%,
        var(--line) 90%,
        transparent 100%);
}
.tl__day {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--text-dim);
    font-weight: 700;
    margin: 18px 0 10px -110px;
    padding-left: 110px;
    position: relative;
}
.tl__day:first-child { margin-top: 0; }
.tl__day::before {
    content: "";
    position: absolute;
    left: 90px;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--bg);
    border: 1px solid var(--line-2);
}

/* Frameless timeline entries — no card chrome. The rail + dot is the
   primary visual structure; entries separate via vertical rhythm and a
   hairline divider, like a true activity log. */
.tl__entry {
    position: relative;
    padding: 18px 0;
    background: transparent;
    border: 0;
    border-radius: 0;
    margin-bottom: 0;
}
/* Timeline entries aren't clickable — no hover state to avoid implying
   interactivity. */
.tl__node {
    position: absolute;
    /* Center on the rail (12px from .timeline's outer left edge).
       In .tl__entry padding-box coords that's 12 - 32 = -20 (timeline
       padding-left only — entries no longer have a border). Vertical
       position aligns the dot's center with the title baseline. */
    left: -20px;
    top: 22px;
    transform: translateX(-50%);
    width: 12px; height: 12px;
    border-radius: 50%;
    background: var(--ok);
    box-shadow: 0 0 0 4px var(--bg);
    z-index: 1;
}
/* Outage in progress — both 'current' (fully offline) and 'partial' (some
   feeds restored, some still down) are ongoing service degradation, so
   both get the red pulsing node. Amber is reserved for elevated activity. */
.tl__entry[data-state="current"] .tl__node,
.tl__entry[data-state="partial"] .tl__node {
    background: var(--off);
    animation: tlNodePulse 1.6s ease-out infinite;
}
.tl__entry[data-state="resolved"] .tl__node { background: var(--ok); }
.tl__entry[data-state="elevated"] .tl__node { background: var(--elev); }
/* Ongoing elevated incidents get the same radiating pulse pattern as
   active outages, in amber to match the elevated state color. Resolved
   elevated entries stay static — only ones still in progress pulse. */
.tl__entry[data-state="elevated"].is-ongoing .tl__node {
    animation: tlNodePulseElev 1.6s ease-out infinite;
}
/* Outward-only pulse for consistency with the topbar live indicator and
   the outage-banner dot — the inner 4px bg ring stays constant (it masks
   the rail behind the dot), the outer red ring grows and fades, then
   resets instantly for the next iteration. */
@keyframes tlNodePulse {
    0%   { box-shadow: 0 0 0 4px var(--bg), 0 0 0 4px rgba(239, 68, 68, 0.45); }
    100% { box-shadow: 0 0 0 4px var(--bg), 0 0 0 10px rgba(239, 68, 68, 0); }
}
@keyframes tlNodePulseElev {
    0%   { box-shadow: 0 0 0 4px var(--bg), 0 0 0 4px rgba(245, 158, 11, 0.45); }
    100% { box-shadow: 0 0 0 4px var(--bg), 0 0 0 10px rgba(245, 158, 11, 0); }
}

.tl__head {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.tl__titleStack {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}
.tl__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    margin: 0;
    line-height: 1.3;
}
/* Timestamp sits to the LEFT of the rail, in the timeline's reserved
   left zone. Right-aligned so the time/relative text "points" toward
   the rail and its corresponding dot. */
.tl__time {
    position: absolute;
    left: -110px;
    top: 22px;
    /* Width sized so the right edge of the timestamp sits ~20px left of
       the rail, matching the rail-to-content gap on the other side
       (rail at outer-left 90, content at 110 → 20px right gap; timestamp
       ends at 70 → 20px left gap, mirrored). */
    width: 70px;
    text-align: right;
    font-size: 12px;
    color: var(--text-mute);
    font-variant-numeric: tabular-nums;
    line-height: 1.3;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
}
.tl__rel {
    font-size: 10px;
    color: var(--text-dim);
    text-transform: lowercase;
}
.tl__summary {
    font-size: 12.5px;
    color: var(--text-mute);
    line-height: 1.3;
}
.tl__statusLine {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    font-size: 11px;
    color: var(--text-mute);
    background: rgba(255,255,255,0.04);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 4px 10px;
}
.tl__statusLine[data-state="current"],
.tl__statusLine[data-state="partial"]  { color: #ffd0d0; border-color: rgba(239, 68, 68, 0.3); }
.tl__statusLine[data-state="resolved"] { color: #b8f3d6; border-color: rgba(34, 197, 94, 0.3); }
.tl__statusLine[data-state="elevated"] { color: #ffe1ad; border-color: rgba(245, 158, 11, 0.3); }
.tl__statusLine em { font-style: normal; }
.tl__statusLine strong {
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.tl__details {
    margin-top: 14px;
    padding-top: 0;
    border-top: 0;
}
/* Feed name on top, duration as a sub-line below — gives long names like
   "Regional Transit Police Department" the full row width without crowding
   the duration on the right, and reads naturally as a labeled item. */
/* Feed name on top, duration as a sub-line below. Recovered/offline
   items match the muted green/red used by the status pill above so the
   colors don't clash with the brighter ok/off variables. */
.tl__detailItem {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    font-size: 12px;
    color: var(--text);
    padding: 5px 0;
}
/* Recovered: feed name in muted-gray (matches "N feeds" summary text);
   the "restored after Xm" sub-line stays muted green so the recovered
   state still reads visually. Offline items stay muted red. */
.tl__detailItem.recovered { color: var(--text-mute); }
.tl__detailItem.recovered .tl__detailDur { color: #7fdca8; opacity: 0.7; }
.tl__detailItem.offline   { color: #f29a9a; }
.tl__detailDur {
    color: inherit;
    opacity: 0.7;
    font-variant-numeric: tabular-nums;
    font-size: 11px;
}
.tl__toggle { margin-top: 8px; }
.tl__toggle summary {
    list-style: none;
    cursor: pointer;
    user-select: none;
    font-size: 11px;
    color: var(--text-dim);
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.tl__toggle summary::-webkit-details-marker { display: none; }
.tl__toggle summary::after {
    content: "▾";
    font-size: 9px;
    transition: transform 0.15s ease;
}
.tl__toggle[open] summary::after { transform: rotate(-180deg); }

.timeline__loading {
    padding: 28px;
    color: var(--text-mute);
    font-size: 13px;
    display: flex;
    align-items: center;
    gap: 12px;
    justify-content: center;
}
.dots {
    display: inline-flex;
    gap: 4px;
}
.dots span {
    width: 5px; height: 5px;
    border-radius: 50%;
    background: var(--text-mute);
    /* Use the neutral opacity-pulse (same as topbar's loading dots) — the
       'pulseDot' animation has a green ring tied to the LIVE indicator
       and shouldn't bleed into a generic loading affordance. */
    animation: pulseDotMute 1.2s ease-in-out infinite;
}
.dots span:nth-child(2) { animation-delay: 0.15s; }
.dots span:nth-child(3) { animation-delay: 0.3s; }

.timeline__empty {
    padding: 32px 24px;
    text-align: center;
    color: var(--text-mute);
    font-size: 13.5px;
    border: 1px dashed var(--line);
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
.timeline__emptyIcon {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--ok-soft);
    color: var(--ok);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.timeline__emptyTitle {
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
}
.timeline__emptyHint {
    color: var(--text-dim);
    font-size: 12px;
}

/* ---- Sparkline tooltip ---- */
.sparkTip {
    position: fixed;
    background: rgba(15, 16, 24, 0.96);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    color: var(--text);
    border: 1px solid var(--line-2);
    border-radius: 8px;
    padding: 8px 10px;
    font-size: 11px;
    box-shadow: var(--shadow-2);
    pointer-events: none;
    opacity: 0;
    transform: translateY(2px);
    transition: opacity 0.12s ease, transform 0.12s ease;
    z-index: 1000;
    white-space: nowrap;
    line-height: 1.4;
    font-variant-numeric: tabular-nums;
}
.sparkTip.is-visible {
    opacity: 1;
    transform: translateY(0);
}
.sparkTip strong {
    color: var(--text);
    display: block;
    font-size: 11px;
    font-weight: 600;
    margin-bottom: 2px;
}
.sparkTip em {
    color: var(--text-mute);
    font-style: normal;
}

/* ---- Footer ---- */
.footer {
    margin-top: 56px;
    padding-top: 24px;
    border-top: 1px solid var(--line);
    color: var(--text-dim);
    font-size: 12px;
    display: flex;
    justify-content: center;
    gap: 28px;
    flex-wrap: wrap;
    align-items: center;
}
.footer a { color: var(--text-mute); }

/* =========================================================
   RESPONSIVE
   ========================================================= */

@media (max-width: 980px) {
    .row {
        grid-template-columns: 1fr 110px;
        grid-template-areas:
            "head   metric"
            "spark  spark";
        gap: 10px 14px;
        padding: 14px;
    }
    .row__head    { grid-area: head; }
    .row__metric  { grid-area: metric; }
    .row__spark   { grid-area: spark; }
}

@media (max-width: 860px) {
    /* Below ~860px the 2-col hero starts cramping — stack the facts
       under the count, with a top hairline divider instead of left. */
    .hero__body {
        grid-template-columns: minmax(0, 1fr);
        gap: 22px;
        align-items: stretch;
    }
    .hero__facts {
        border-left: 0;
        padding-left: 0;
        border-top: 1px solid var(--line);
        padding-top: 16px;
        flex-direction: row;
        flex-wrap: wrap;
        gap: 14px 28px;
    }
    .hero__fact { flex: 1 1 130px; }
}

@media (max-width: 720px) {
    .container { padding: 24px 16px 60px; }
    .topbar__inner { padding: 10px 16px; }

    .hero { padding: 22px 20px 20px; }
    .hero__statusText { font-size: 18px; }
    .hero__statusIcon { width: 28px; height: 28px; }
    .hero__count { font-size: clamp(40px, 12vw, 56px); }
    .hero__countLabel { font-size: 13px; }

    .section__header {
        flex-direction: column;
        align-items: flex-start;
    }
    .controls { width: 100%; justify-content: space-between; }
    .legend { gap: 10px; }
    .legend__item { font-size: 10px; }

    .row__count { font-size: 20px; }
    .row__name { font-size: 14px; }

    /* Below 720px, the reserved left zone for timestamps eats too much
       horizontal space. Collapse back to a tight rail and put the
       timestamp inline above the title. */
    .timeline { padding-left: 28px; }
    .timeline::before { left: 7px; }
    .tl__day { margin-left: -28px; padding-left: 28px; }
    .tl__day::before { left: 8px; }
    .tl__node { left: -20px; }
    .tl__time {
        position: static;
        width: auto;
        align-items: flex-start;
        text-align: left;
        flex-direction: row;
        gap: 6px;
        order: -1;
        margin-bottom: 2px;
    }
}

@media (max-width: 480px) {
    .hero { padding: 18px 16px 16px; }
    .hero__statusText { font-size: 16px; }
    .hero__primary { gap: 8px; }
}

/* =========================================================
   INITIAL LOADING SKELETON
   ========================================================= */
@keyframes skeletonShimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
/* Skeleton shimmer for the sparkline area only — avg/peak no longer
   exist as a data point. */
body[data-loading="full"] .row__spark {
    position: relative;
    overflow: hidden;
    border-radius: 4px;
}
body[data-loading="full"] .row__spark::after {
    content: "";
    position: absolute;
    top: 0; left: 0;
    height: 100%;
    width: 60%;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(96, 165, 250, 0.18) 50%,
        transparent 100%
    );
    animation: shimmerSweep 1.8s linear infinite;
    pointer-events: none;
}
@keyframes shimmerSweep {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(266.67%); }
}

.topbar__loading {
    display: none;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--text-mute);
}
body[data-loading="full"] .topbar__loading { display: inline-flex; }
/* While loading: hide both the green pulse dot and the LIVE label so the
   only thing in this slot is the "loading data" pip with its own neutral
   dots. The LIVE indicator only makes sense once the page actually has
   live data to show. */
body[data-loading="full"] .topbar__live,
body[data-loading="full"] .topbar__pulse { display: none; }
.topbar__loadingDot {
    width: 5px; height: 5px;
    border-radius: 50%;
    background: currentColor;
    animation: pulseDotMute 1.2s ease-in-out infinite;
}
.topbar__loadingDot:nth-child(2) { animation-delay: 0.15s; }
.topbar__loadingDot:nth-child(3) { animation-delay: 0.3s; }
@keyframes pulseDotMute {
    0%, 60%, 100% { opacity: 0.3; transform: scale(0.85); }
    30%           { opacity: 1;   transform: scale(1.1); }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}
