﻿/* WookiVerse Status.
   Chrome copied from the app itself (MainLayout.razor.css): a #f7f7f7 top bar with a #d6d5d5
   hairline, and #111/#222 as the dark brand tone from the docked sidebar. The navy-to-purple
   gradient belongs to the sign-in screen and is deliberately absent here.
   States are Bootstrap semantics and nothing else uses colour.
   Type: Tilt Neon and Fira Code are both already in the app's font import. */

@import url('https://fonts.googleapis.com/css2?family=Tilt+Neon&family=Fira+Code:wght@400;500&display=swap');

:root {
    /* App chrome */
    --bar: #f7f7f7;
    --bar-line: #d6d5d5;
    --dark: #212529;
    --darker: #111;

    /* Bootstrap semantics — states only */
    --success: #198754;
    --warning: #ffc107;
    --danger: #dc3545;
    /* The ramp's middle step. Not otherwise part of the palette - a day is the only thing on
       this page that is measured on a scale rather than being in a state. */
    --orange: #fd7e14;
    --secondary: #6c757d;

    --page: #eeeeee;
    --surface: #ffffff;
    --border: #dee2e6;
    --ink: #212529;
    --ink-soft: #6c757d;
    --track: #e9ecef;

    --radius: 6px;
    --mono: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
    --display: 'Tilt Neon', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

/* The page colour is painted by html, and body is explicitly transparent - Bootstrap's reboot
   would otherwise give body a white background of its own, and body's background paints over
   anything at a negative z-index. With the colour on the canvas instead, the corner mark can sit
   behind the content at z-index:-1 and still be seen, which is what lets .page stay free of a
   stacking context. */
html { background: var(--page); }

body {
    margin: 0;
    background: transparent;
    color: var(--ink);
    font-family: var(--display);
    font-size: 15px;
    line-height: 1.55;
}

/* --- Top bar: the app's brand header ------------------------------------------------------
   The app's brand block (NavMenu.razor) is white-on-dark: the sidebar's #111-to-#222 gradient
   under a rgba(0,0,0,.4) tint, carrying the blinking-eyes logo and Wooki<b>Verse</b>. Its
   #f7f7f7 .top-row only reads as chrome because that dark sidebar sits beside it; on a page
   with no sidebar the same grey is invisible. So the brand header IS the top bar here. */

.top-row {
    background-color: var(--darker);
    background-image:
        linear-gradient(rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)),
        linear-gradient(180deg, #111 0%, #222 75%);
    color: #fff;
    height: 3.5rem;
    position: sticky;
    top: 0;
    z-index: 6;
}

.top-row-inner {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; color: #fff; }
.brand img { width: 40px; height: auto; }

.wordmark { font-size: 20px; line-height: 1; }
.wordmark b { font-weight: 400; }

/* Burger — <details> so the menu needs no JavaScript */
.menu { position: relative; }

.menu summary {
    list-style: none;
    cursor: pointer;
    color: #fff;
    padding: 6px 4px 2px;
    border-radius: var(--radius);
}

.menu summary::-webkit-details-marker { display: none; }
.menu summary:hover { color: #c9c7d4; }

.menu ul {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    min-width: 220px;
    margin: 0;
    padding: 6px 0;
    list-style: none;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 6px 20px rgba(0, 0, 0, .1);
    z-index: 7;
}

.menu a { display: block; padding: 9px 18px; color: var(--ink); text-decoration: none; font-size: 14px; }
.menu a:hover, .menu a:focus-visible { background: var(--secondary); color: #fff; }
.menu a[aria-current="page"] { font-weight: 500; }
.menu .sep { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 6px; }
.menu .sep a { color: var(--ink-soft); font-family: var(--mono); font-size: 12px; }

.page { padding-top: 30px; padding-bottom: 64px; }

/* --- Banner ------------------------------------------------------------------------------ */

.banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px 18px;
    /* Bootstrap mb-3. The stamp used to carry this gap; it moved into the banner. */
    margin-bottom: 1rem;
    padding: 15px 20px;
    border-radius: var(--radius);
    color: #fff;
}

.banner-main { display: flex; flex-direction: column; gap: 3px; }
.banner-text { font-size: 21px; line-height: 1.25; }

/* The stamp lives in the banner now: the state and how fresh it is are one claim, and
   splitting them let a reader take the state on trust without seeing its age. */
.banner-stamp { font-family: var(--mono); font-size: 11px; opacity: .82; }

.banner-affected { display: flex; flex-wrap: wrap; gap: 6px; }
.banner-badge img { width: 22px; height: 22px; object-fit: contain; }

/* Translucent white rather than a semantic colour — a red badge on a red banner disappears. */
.banner-badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: .3em .75em .3em .45em;
    border-radius: .375rem;
    background: rgba(255, 255, 255, .22);
    font-size: 12px;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
}

.banner.up { background: var(--success); }
.banner.down { background: var(--danger); }
/* Amber, not red: something is broken, but not most of it. Sharing red with a major outage
   would leave the page no way to say the difference. */
.banner.partial { background: var(--warning); color: var(--dark); }
.banner.partial .banner-badge { background: rgba(0, 0, 0, .12); color: var(--dark); }
/* Degraded is the same amber as a partial outage and as .state.degraded on the rows below:
   part of the estate is unwell. With no rule at all it fell back to white text on nothing. */
.banner.degraded { background: var(--warning); color: var(--dark); }
.banner.degraded .banner-badge { background: rgba(0, 0, 0, .12); color: var(--dark); }
.banner.maintenance { background: var(--secondary); }
.banner.unknown { background: var(--secondary); }

/* Still used by the incidents and uptime pages. */
.stamp { color: var(--ink-soft); font-family: var(--mono); font-size: 12px; margin: 12px 0 28px; }

/* --- Packages (admin) -----------------------------------------------------------------------
   A list to scan, not a document to read, so the type gets out of the way and the alignment
   does the work. Tabular numerals because the versions are the column being compared. */
.table-scroll { overflow-x: auto; }

.packages { width: 100%; border-collapse: collapse; font-size: 13px; }
.packages th {
    text-align: left;
    padding: 6px 12px 8px 0;
    border-bottom: 1px solid var(--border);
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    font-weight: 500;
}
.packages td { padding: 7px 12px 7px 0; border-bottom: 1px solid var(--edge-soft, #eee); vertical-align: baseline; }
.packages tr:last-child td { border-bottom: 0; }

.pkg-id { font-weight: 500; }
.pkg-ver { font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pkg-where { color: var(--ink-soft); font-size: 12px; }

/* A transitive package is somebody else's choice, and upgrading it is a different job from
   upgrading one we asked for. */
.pkg-tag {
    margin-left: 8px;
    font-size: 10px;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ink-soft);
    border: 1px solid var(--border);
    border-radius: 2px;
    padding: 1px 5px;
}

/* A left edge rather than a filled row: a table of a hundred rows in three background colours
   is a stained-glass window, and the badge already says which is which. */
.pkg.bad { box-shadow: inset 3px 0 0 var(--danger); }
.pkg.warn { box-shadow: inset 3px 0 0 var(--warning); }
.pkg.bad .pkg-id, .pkg.warn .pkg-id { padding-left: 10px; }
.pkg.good .pkg-id, .pkg.held .pkg-id { padding-left: 10px; }

/* Held back has no edge at all - it is not a problem, it is a decision somebody wrote down. */
.count-held { color: var(--ink-soft); }

.count-bad { color: var(--danger); }
.count-warn { color: var(--warning); }
.count-good { color: var(--success); }

/* --- Changelog ------------------------------------------------------------------------------
   A list of small facts, so the type carries almost none of the work and the alignment carries
   all of it: the module chip in a fixed column, the sentence in the other. */
.release { margin-bottom: 12px; }
.release h2 {
    margin: 0 0 12px;
    padding: 0 0 10px;
    border-bottom: 1px solid var(--border);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* Inside the card's padding, not bled out to its edges - the backend's convention is
   card > card-body > table.datatable, with the striping stopping where the card body's padding
   starts, and this is the same thing wearing a <ul>. */
.changes { list-style: none; margin: 0; padding: 0; border-radius: 4px; overflow: hidden; }

/* TEXT 8, CHIPS 4 - as Bootstrap columns in the markup (col-md-8 / col-md-4), not as a grid
   declared here. Only the striping below is ours. */

/* Zebra striping, the same convention and the same grey as the backend's datatables
   (table.datatable tr:nth-child(even) { background-color: #f2f2f2 }), so somebody moving between
   the two products meets banding they already recognise. */
.change:nth-child(even) { background-color: #f2f2f2; }

.change-what { font-size: 14.5px; line-height: 1.55; }

/* d-flex flex-wrap gap-2 in the markup does the rest; the stacking below md is col-12 col-md-4. */

/* A tag, not a colour: feat and fix both mean "this got better", and colouring one of them green
   invites a reader to work out what the other one means. */
.change-tag {
    font-size: 11px;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--danger);
    border: 1px solid var(--danger);
    border-radius: 2px;
    padding: 1px 6px;
    white-space: nowrap;
}

@media (max-width: 640px) {
    .change { grid-template-columns: 1fr; gap: 4px; }
}

/* --- Components -------------------------------------------------------------------------- */

.card {
    /* display FIRST and explicitly, because Bootstrap also defines .card and its version sets
       display:flex; flex-direction:column. Ours predates it and everything inside these cards is
       laid out as blocks, so the collision is settled here rather than discovered on a public
       page. It is the only one of the eighteen shared class names where Bootstrap would change a
       property we had not thought about - the rest (.btn, .form-control, .modal, .dropdown-menu)
       are ones this page was hand-rolling under Bootstrap's own names anyway. */
    display: block;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 18px;
}

/* The platform-wide strip: full width above the per-component grid, so the first thing under
   the banner is one line about the whole estate rather than twelve about its parts. */
.overall { margin-bottom: 10px; overflow: visible; }
.overall .bars { height: 40px; }
.overall .bar { border-right-width: 2px; }

.components { display: grid; grid-template-columns: 1fr; gap: 10px; }
@media (min-width: 992px) { .components { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.component { overflow: visible; }
/* 40px square, matching the module icons in the app nav — not circles: these are pictograms,
   not avatars, and cropping them to a circle clips the artwork. */
.component-icon { width: 40px; height: 40px; object-fit: contain; }
.name { display: flex; align-items: center; gap: 9px; font-weight: 500; }

/* The name stacks: what it is, then what we call it. The icon stays beside both, so .name keeps
   its row and the words get a column of their own inside it. */
.name-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

/* What we call it internally, for whoever has to go and fix it. Grey, lighter and a size down,
   so the customer name is still the one doing the work on the line above it. */
.internal {
    color: var(--ink-soft);
    font-weight: 400;
    font-size: 11.5px;
    line-height: 1.25;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* A hairline rather than a literal "|": a pipe glyph sits on the text baseline and is drawn at
   whatever weight the font feels like, so it either vanishes or shouts. This is the same divider
   at any size. */
.subtitle-sep {
    width: 1px;
    align-self: stretch;
    background: var(--border);
    /* Zero-width content, so the rule has a height without occupying a character cell. */
    font-size: 0;
}
.subtitle-sep::before { content: " a0"; }

.component-head {
    display: flex;
    justify-content: space-between;
    /* Centred, not baseline: the 40px icon makes the name side tall, and a baseline-aligned
       badge hangs at the bottom of it. */
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

/* Bootstrap badges. Solid fill for a state we are asserting; a quiet outline for Unknown,
   because "we cannot see" is not a claim about the service and should not shout like one. */
.state {
    display: inline-block;
    padding: .35em .7em;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.2;
    border-radius: .375rem;
    white-space: nowrap;
    color: #fff;
}

.state.up { background: var(--success); }
.state.down { background: var(--danger); }
.state.maintenance { background: var(--secondary); }
.state.degraded { background: var(--warning); color: #000; }
.state.unknown { background: transparent; color: var(--ink-soft); box-shadow: inset 0 0 0 1px var(--border); }

/* --- What is wrong inside a component ------------------------------------------------------
   Only rendered when something IS wrong, so it can afford the vertical space. Quiet by design:
   the badge above is the alarm, this is the explanation, and an explanation that shouts as loudly
   as the alarm makes the reader work out which one to believe. */
.parts-lead {
    margin: -4px 0 8px;
    font-size: 13px;
    color: var(--ink-soft);
}

.parts {
    list-style: none;
    margin: 0 0 12px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.part {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    font-size: 13px;
    /* A rule per row rather than a border: at four or five rows a boxed table starts competing
       with the strip below it, which is the thing people actually come to read. */
    padding-bottom: 4px;
    border-bottom: 1px dashed var(--border);
}

.part:last-child { border-bottom: 0; }

/* A dot, not a coloured name: colouring the label makes the NAME look like the problem, and on
   a page where amber and red already mean something precise, a third use dilutes both. */
.part-name::before {
    content: '';
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-right: 8px;
    vertical-align: middle;
    background: var(--success);
}

.part.bad .part-name::before { background: var(--warning); }
.part-name { font-weight: 500; }
.part-note { color: var(--ink-soft); font-size: 12px; text-align: right; white-space: nowrap; }
.part.bad .part-note { color: var(--ink); }

.bars { display: flex; gap: 0; height: 32px; cursor: default; }

/* A popover, not a tooltip: a real element carrying a badge and figures, rather than the
   browser's title attribute, which is one string, slow to appear and unstyleable.
   Deliberately not focusable — 90 bars across 12 components would be 1,080 tab stops, and the
   same figures are in the legend below and day-by-day on the Uptime page. */
.bar { position: relative; }

.pop {
    display: none;
    position: absolute;
    bottom: calc(100% + 10px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 8;
    min-width: 320px;
    padding: 0;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 8px 24px rgba(0, 0, 0, .16);
    text-align: left;
    font-size: 12px;
    line-height: 1.45;
    cursor: default;
}

/* Popovers at either end would overflow the panel, so the outermost bars anchor to their own
   edge instead of centring. Driven by index at render time, so nothing has to be measured. */
.bar.pop-start .pop { left: 0; transform: none; }
.bar.pop-end .pop { left: auto; right: 0; transform: none; }

/* Two triangles: the border colour behind, the surface in front, so the arrow keeps the
   popover's outline instead of showing a seam where it meets the card. */
.pop::before, .pop::after {
    content: "";
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    border: 7px solid transparent;
}

.pop::before { top: 100%; border-top-color: var(--border); }
.pop::after { top: calc(100% - 1px); border-top-color: var(--surface); }

.pop-start .pop::before, .pop-start .pop::after { left: 16px; transform: none; }
.pop-end .pop::before, .pop-end .pop::after { left: auto; right: 16px; transform: none; }

/* Rows, not a stack: the day, what happened and how long it lasted belong on one line, and the
   platform strip then adds a line per component underneath in the same three columns. */
.pop-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 13px;
}

.pop-row + .pop-row { border-top: 1px solid var(--border); }
.pop-head { background: var(--bar); border-radius: var(--radius) var(--radius) 0 0; }
.pop-head { font-size: 13px; font-weight: 500; }
.pop-day { flex: 1; }
.pop-pct { font-family: var(--mono); font-size: 12px; font-weight: 400; color: var(--ink-soft); }

/* A component's own rows: the window stacked on the left, what it was on the right. */
.pop-window {
    flex: none;
    font-family: var(--mono);
    font-size: 11px;
    line-height: 1.35;
    color: var(--ink-soft);
    white-space: nowrap;
}

.pop-window b { display: block; font-size: 12px; font-weight: 500; color: var(--ink); }
.pop-what { margin-left: auto; text-align: right; font-size: 12px; }

/* Two lines: who and what above, when and how long below. */
.pop-stack { display: block; }
.pop-top { display: flex; align-items: center; gap: 9px; }
.pop-name { flex: 1; font-size: 13px; }
.pop-sub {
    display: block;
    margin-top: 4px;
    padding-left: 29px;
    font-family: var(--mono);
    font-size: 11px;
    color: var(--ink-soft);
}
.pop-when {
    display: flex;
    align-items: center;
    gap: 7px;
    flex: 1;
    font-family: var(--mono);
    font-size: 12px;
    white-space: nowrap;
}

.pop-icon { width: 20px; height: 20px; object-fit: contain; flex: none; }
.pop-len { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--ink-soft); white-space: nowrap; }

.pop-figure { font-family: var(--mono); }
.pop-note { color: var(--ink-soft); font-size: 12px; }
.pop .state { flex: none; }

.bar.has-pop:hover { box-shadow: inset 0 0 0 999px rgba(0, 0, 0, .18); }
.bar.has-pop:hover .pop { display: block; }

/* The popover escapes its row, so nothing on the path may clip it. */
.component, .bars { overflow: visible; }
.bar {
    flex: 1 1 0;
    min-width: 0;
    box-sizing: border-box;
    border-right: 1px solid var(--surface);
    background: var(--track);
    background-clip: padding-box;
}

.bar:last-child { border-right: 0; }
.bar.up { background: var(--success); }
.bar.down { background: var(--danger); }
/* Platform strip only: some components down, not all. Matches .state.degraded above. */
.bar.degraded { background: var(--warning); }
.bar.maintenance { background: var(--secondary); }
.bar.unknown { background: var(--track); }

/* --- The checks behind a component -----------------------------------------------------------
   A link on the card and a Bootstrap modal behind it. What is FAILING stays on the card, because
   during an incident "which part of it" is the question the reader has and a click is the wrong
   place to put the answer. */
.checks-failing { font-size: .82rem; color: var(--danger); }

/* A button, because it opens a modal and a thing that does something on click is a button - but
   it has to READ as the quiet text it sits beside. Everything a button brings with it is stripped:
   the chrome, the padding, the inherited-then-overridden font, and the link colouring it had while
   it was a btn-link. It competed with the state badge for the eye, on a page whose whole job is
   to make one thing obvious. */
.checks-link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.checks-link .fa-fw { font-size: .85em; opacity: .75; }
.checks-link:hover { color: var(--ink); }
.checks-link:hover .fa-fw { opacity: 1; }

/* --- Severity ramp (components.js DAY_COLOUR = 'ramp') -------------------------------------
   There are no rules here on purpose. Atlassian's ramp is a CONTINUOUS gradient - green through
   yellow and orange to red by how bad the day was - so a day's colour is a computed value rather
   than one of a handful of states, and it arrives as an inline background from rampColour().
   Spelling a gradient as classes would mean inventing a hundred of them.

   The state rules above are the fallback, and they matter: if the style never arrives, a bad day
   still paints its flat state colour instead of nothing, and an unpainted square is
   indistinguishable from "no data" - the one thing this page must never say about a day it
   measured. --orange exists for the same reason the other three do: css.test.js holds these
   variables against the constants in components.js so the two copies of the palette cannot
   drift. */

.legend {
    display: flex;
    justify-content: space-between;
    margin-top: 9px;
    color: var(--ink-soft);
    font-family: var(--mono);
    font-size: 11px;
}

/* --- Incidents --------------------------------------------------------------------------- */

h2 {
    font-size: 21px;
    font-weight: 400;
    margin: 30px 0 10px;
}

h2:first-child { margin-top: 0; }

.open { margin-bottom: 22px; }

/* An open incident on the summary. Coloured down the leading edge by severity so it reads as
   the live thing on the page, without competing with the banner above it. */
.live-incident {
    margin-bottom: 10px;
    border-left: 3px solid var(--secondary);
}

.live-incident.down { border-left-color: var(--danger); }
.live-incident.maintenance { border-left-color: var(--secondary); }

/* A correction is neither an outage nor planned work: nothing is broken for the reader and
   nothing has been fixed for them either - what changed is what this page was saying. Grey like
   maintenance, because both are "this is us, not you", and emphatically not red. */
.live-incident.monitoring { border-left-color: var(--secondary); }

/* A correction pointing the other way - our checks said green while the service was not - is an
   outage to the person reading it, whatever we file it as. Amber and red are for the reader; the
   kind is for us. */
.live-incident.degraded { border-left-color: var(--warning); }

.live-incident h3 { margin: 0 0 6px; font-size: 17px; font-weight: 500; }
.live-incident h3 a { color: var(--ink); text-decoration: none; }
.live-incident h3 a:hover { text-decoration: underline; }

.live-latest { margin: 0 0 6px; font-size: 14px; max-width: 80ch; }
.live-latest b { color: var(--dark); }

.live-when { margin: 0; font-family: var(--mono); font-size: 11px; color: var(--ink-soft); }
.live-when a { color: var(--ink-soft); }

.incident { margin-bottom: 12px; }
.incident h3 { font-size: 19px; font-weight: 500; margin: 0 0 3px; max-width: 60ch; color: var(--ink); }

.incident-when { margin: 0; color: var(--ink-soft); font-family: var(--mono); font-size: 11px; }

/* Chips ride the title row rather than sitting under it: they answer "what does this touch",
   which belongs beside the headline, not stacked between it and the story. */
.incident-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 16px;
    margin-bottom: 14px;
}

/* The order of an incident's updates IS the information, so the shape carries it: one rail
   down the side, a dot per update, newest at the top. */
.timeline { position: relative; margin: 0; padding: 2px 0 0 24px; list-style: none; }

.timeline::before {
    content: "";
    position: absolute;
    left: 5px;
    top: 8px;
    bottom: 8px;
    width: 2px;
    background: var(--border);
}

.update { position: relative; margin: 0 0 14px; font-size: 14px; max-width: 72ch; }
.update:last-child { margin-bottom: 2px; }

.update .dot {
    position: absolute;
    left: -24px;
    top: 6px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: inset 0 0 0 2px var(--secondary);
}

/* The dot's colour is the update's own state, so the rail reads as a progression: red while it
   is being worked out, amber once a fix is being watched, green when it is done. */
.update.bad .dot { background: var(--danger); box-shadow: inset 0 0 0 2px var(--danger); }
.update.watching .dot { background: var(--warning); box-shadow: inset 0 0 0 2px var(--warning); }
.update.good .dot { background: var(--success); box-shadow: inset 0 0 0 2px var(--success); }
.update.neutral .dot { background: var(--secondary); box-shadow: inset 0 0 0 2px var(--secondary); }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-left: auto; }

.chip {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    background: var(--secondary);
    border: 1px solid var(--secondary);
    border-radius: .375rem;
    padding: 6px 14px 6px 8px;
    font-size: 13px;
    color: #fff;
}

/* 24px, not the nav's 40px: the app uses 40 for icons in LIST ROWS, and a 40px icon inside an
   inline pill makes a 46px chip that outweighs the incident title it sits beside. */
.chip img { width: 24px; height: 24px; object-fit: contain; }

.update { margin: 8px 0; font-size: 14px; max-width: 72ch; }
.update b { color: var(--dark); }
.update time { border-bottom: 1px dotted var(--border); cursor: help; }
.update .at { display: block; margin-top: 3px; color: var(--ink-soft); font-family: var(--mono); font-size: 11px; }

/* --- Uptime calendar --------------------------------------------------------------------- */

/* LAYOUT IS BOOTSTRAP'S - .row / .col / .col-auto in the markup, which every route on this page
   already loads. Hand-rolling it here produced a flex row where `justify-content: space-between`
   and `margin-left: auto` competed for the same free space and neither won: the description sat
   399px inside a card that had 1260px of room for 743px of content, and the controls wrapped to a
   second line regardless. This rule keeps only what is not layout. */
.picker { margin-bottom: 20px; }

/* flex: 1 rather than leaving it to justify-content.
   The row was `justify-content: space-between` with `margin-left: auto` on the controls - two
   mechanisms competing for the same free space - and it laid out wrongly on both counts: the
   description sat 399px in from the left of a card that had 1260px of room for 743px of content,
   and the controls still wrapped onto a second line. Given a flex-basis it fills the row, sits
   where it starts, and the controls keep the end. */
.picker-desc { min-width: 0; margin: 0; max-width: 60ch; color: var(--ink-soft); font-size: 13px; }

/* The changelog's standfirst. .picker-desc is a caption under a control - 13px, 60ch - and this
   is the first thing a reader meets on the page, so it was arriving small and broken across three
   short lines. A modifier rather than a change to .picker-desc, which incidents and uptime use in
   its original role. */
.picker-desc.lede { font-size: 14.5px; line-height: 1.6; max-width: 88ch; margin-bottom: 16px; }
.picker-controls { display: flex; gap: 8px; justify-content: flex-end; }

/* --- The same dropdown as a form field ------------------------------------------------------
   A native <select> renders as whatever the operating system feels like - a grey slab on
   Windows, a different one on a Mac, a full-screen wheel on a phone - so a row of them looks
   like three design languages at once and none of them is this one. The app's own house style
   has no <select> in it for the same reason.

   The items are <button>s here rather than links, so they need the browser's button styling
   taken off before they can look like the list rows they are. */
/* Full width of the field, not of the longest word in it: a menu narrower than the control it
   drops from reads as a different control. */
/* THREE ROWS OF FOUR. Twelve months divide into four columns exactly; auto-fill packed as many
   240px columns as the viewport allowed, which on a wide monitor is five, leaving two stranded on
   a third row and a year that reads as a mistake rather than a shape. Stepped down by hand rather
   than left to auto-fill, so every width lands on a divisor of twelve. */
.months { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }

@media (max-width: 1180px) { .months { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 900px)  { .months { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 580px)  { .months { grid-template-columns: minmax(0, 1fr); } }
.month { overflow: visible; }

.month h3 { display: flex; justify-content: space-between; align-items: baseline; font-size: 15px; font-weight: 500; margin: 0 0 12px; }
.month h3 span { color: var(--ink-soft); font-family: var(--mono); font-size: 12px; font-weight: 400; }

.weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 3px;
    margin-bottom: 4px;
    color: var(--ink-soft);
    font-size: 11px;
    text-align: center;
}

.weekdays .weekend { color: #a9a9a9; }

.cells { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; overflow: visible; }

.cell {
    position: relative;
    aspect-ratio: 1;
    border-radius: 3px;
    background: var(--track);
    display: flex;
    align-items: center;
    justify-content: center;
    /* A square is not a paragraph. Without this the date inside it gives the whole calendar a
       text I-beam, which reads as "select me" over the one thing on the page nobody wants to
       select - and dragging across a month then highlights forty numbers. */
    cursor: default;
    user-select: none;
}

.cell-num { font-size: 11px; font-family: var(--mono); color: rgba(255, 255, 255, .92); }
.cell.unknown .cell-num { color: var(--ink-soft); }
.cell.blank { background: none; }
.cell.blank .cell-num { display: none; }

/* The plain arrow, deliberately. Pointer promises a click and clicking a day does nothing; help
   decorates the arrow for a reveal that is already obvious the moment it happens. The square just
   should not claim to be text, which the base .cell rule above settles for every day at once. */
.cell.has-pop:hover .pop { display: block; }
.cell.has-pop:hover { box-shadow: inset 0 0 0 999px rgba(0, 0, 0, .18); }
.month { overflow: visible; }
.cell.up { background: var(--success); }
.cell.down { background: var(--danger); }
.cell.maintenance { background: var(--secondary); }

/* Amber, like the bar and the badge. This was MISSING, so a day on which some services were down
   and others were not fell through to the bare .cell and rendered grey - the exact colour this
   page uses for "we have no record of that day". A day carrying five outages looked like a day
   nobody measured, with its own popover saying 83.64% underneath.

   It only ever showed on the ALL SERVICES view, which is why it lasted: a single component's day
   is up, down or maintenance, and 'degraded' is produced by combinedDays alone - some components
   down, not all. Picking one service showed red, amber and green perfectly. css.test.js now holds
   every state against every element that takes one. */
.cell.degraded { background: var(--warning); }
.cell.degraded .cell-num { color: rgba(0, 0, 0, .78); }

/* Announced, not yet happened. A lighter grey than maintenance itself, because a plan and a
   measurement should not look identical — one of them can still be cancelled. */
.cell.planned { background: #adb5bd; }
.cell.unknown { background: var(--track); }
.cell.future { background: none; box-shadow: inset 0 0 0 1px var(--track); }

.cell.future .cell-num { color: #bdbdbd; }

.empty { color: var(--ink-soft); }

/* --- Admin ---------------------------------------------------------------------------------
   Bootstrap's control shapes in the app's colours. A form select rather than the <details>
   dropdowns the public pages use: those are navigation and carry no value, and a form needs one
   submitted. Bootstrap's own answer to a dropdown inside a form is a styled <select> too. */

.admin-card { margin-bottom: 14px; }
.admin-card h2 { margin: 0 0 4px; padding: 0; border: 0; font-size: 20px; }

.notice { padding: 11px 14px; border-radius: var(--radius); background: #fff3cd; color: #664d03; }

p.hint { margin: 0 0 4px; }

/* Bootstrap draws the caret as a background image rather than a glyph, so it needs no icon font. */
textarea.form-control { max-width: 760px; resize: vertical; }

/* The service and module pickers are the SAME tag as an incident's module chips — .chip above,
   unchanged — because a picked service and a published one should not be two different objects.
   Only the unpicked state is new: the fill comes off and it reads as an outline.

   A grid of identical checkboxes made "which services are affected" something you had to read
   one row at a time. As tags the chosen set is one shape against another, legible at a glance.

   The checkbox is still there, still posting and still keyboard-reachable — clipped rather than
   display:none, which would take it out of the tab order and off the form entirely. */
.chips.chips-pick { margin-left: 0; max-width: 900px; }

.chip-pick { cursor: pointer; }

.chip-pick input {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.chip-pick input:not(:checked) + .chip {
    background: var(--surface);
    border-color: #ced4da;
    color: var(--ink);
}

.chip-pick:hover input:not(:checked) + .chip { background: var(--bar); }

/* The tag is the control, so the focus ring has to land on it rather than on a 1px input
   parked off in the corner. */
.chip-pick input:focus-visible + .chip {
    outline: 0;
    box-shadow: 0 0 0 .2rem rgba(108, 117, 125, .35);
}

/* Equal thirds. They were weighted 5:8:6 by how many chips each holds, which balanced the
   scrolling and made three boxes that never quite lined up - and lining up is what a row of
   three controls is for. */
.pickers { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 16px; align-items: start; }
.picker-col { min-width: 0; }

/* A real clip, and it has to beat the .chips overflow rule further down the file - see the note
   there. The cost is that a chip's description popover is clipped at the edge of this box rather
   than escaping it, which is the right trade inside a picker: the note is a nicety here, and the
   public incident page keeps the escaping kind. */
.chips-scroll {
    overflow: hidden auto;
    max-height: 280px;
    min-height: 120px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: #fcfcfc;
}

/* Always visible where the browser allows it, rather than appearing on hover: an overlay
   scrollbar on a box like this is the difference between "there is more" and "that is all". */
.chips-scroll { scrollbar-width: thin; }
.chips-scroll::-webkit-scrollbar { width: 10px; }
.chips-scroll::-webkit-scrollbar-thumb {
    background: #ccc;
    border: 3px solid #fcfcfc;
    border-radius: 5px;
}

/* One column below this: three columns of one chip each is a worse list than one column of
   three, and on a phone the whole thing scrolls anyway. */
@media (max-width: 1100px) {
    .pickers { grid-template-columns: 1fr; gap: 0; }
    .chips-scroll { max-height: 200px; }
}

.chips-dense .chip { gap: 7px; padding: 5px 11px 5px 7px; font-size: 12px; }
.chips-dense .chip img { width: 18px; height: 18px; }

/* Type, Status and what it marks its services as: the three facts that decide what an incident
   DOES, on one line, in the order you decide them. */
@media (max-width: 1000px) { .form-row-3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 700px) { .form-row, .form-row-3 { grid-template-columns: 1fr; } }

/* Two class selectors, so this beats both .form-control and textarea.form-control without
   reaching for !important. */
.planned-row { max-width: 100%; }

/* The preview is the real public block, so it needs the ground it sits on out there rather
   than the white of the form — otherwise a white card on a white card reads as one surface. */
.preview {
    padding: 12px;
    border-radius: var(--radius);
    background: var(--page);
}
.preview .incident { margin: 0; }
.preview .hint { display: block; }

/* --- Quality floor ------------------------------------------------------------------------ */

a:focus-visible, select:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, summary:focus-visible {
    outline: 2px solid var(--dark);
    outline-offset: 2px;
}

@media (max-width: 640px) {
    .container { padding-left: 1rem; padding-right: 1rem; }
    .wordmark { font-size: 17px; }
    .brand img { width: 26px; }
    .page { padding-top: 22px; padding-bottom: 48px; }
    .banner { font-size: 18px; padding: 15px 18px; }
    .months { flex-direction: column; }
    .component-head { flex-direction: column; align-items: flex-start; gap: 2px; }
}

@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
}

/* --- Planned maintenance ---------------------------------------------------------------------
   The announced window, which is a different fact from the window it actually ran in. Both are
   shown on a finished incident: "we said 22:00–02:00, it ran 22:04–03:10" is the honest record.
*/
.incident-plan { margin: 2px 0 0; font-size: 13px; color: var(--ink-soft); }

.upcoming-tag {
    display: inline-block;
    margin-left: 9px;
    padding: 2px 9px;
    border-radius: .375rem;
    background: var(--secondary);
    color: #fff;
    font-size: 11px;
    font-weight: 500;
    vertical-align: middle;
}

.live-incident.upcoming { border-left-color: #adb5bd; }

.planned-row { display: flex; align-items: center; gap: 10px; max-width: 620px; }
.planned-row .form-control { flex: 1 1 0; min-width: 0; }
.planned-sep { color: var(--ink-soft); font-size: 13px; }

/* --- Corrections and deletion ------------------------------------------------------------------
   Folded away because they belong to the morning after, not to running an incident. */
.update-fields { display: none; }

.preview-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }

/* Corrections happen where the words are READ, not in a list of boxes further down that has to
   be matched up by eye against the timeline above it. The affordances are attached by script,
   so the served markup stays identical to the public page's. */
.preview .update { position: relative; padding-right: 28px; }

.update-body.editable { border-bottom: 1px dashed transparent; cursor: text; }
.preview .update:hover .update-body.editable { border-bottom-color: var(--border); }
.update-body.editable:focus {
    outline: 0;
    border-bottom-color: var(--secondary);
    background: var(--bar);
}

.update-remove {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 20px;
    height: 20px;
    padding: 0;
    line-height: 1;
    font-size: 15px;
    color: var(--ink-soft);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
    cursor: pointer;
    opacity: 0;
}
.preview .update:hover .update-remove, .update-remove:focus-visible { opacity: 1; }
.update-remove:hover, .update-remove:focus-visible { color: var(--danger); border-color: var(--danger); }

/* The examples modal carries a dozen worked sentences rather than one question, so it needs the
   room a confirmation does not - and a neutral edge, because nothing here is dangerous. */
.templates { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }

/* A button, not a div with a handler: it is keyboard-reachable, it is announced as a control,
   and Enter works on it without a line of script. */
.template {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: 100%;
    text-align: left;
    padding: 11px 13px;
    font: inherit;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
}

.template:hover, .template:focus-visible { border-color: var(--ink-soft); background: #fbfbfb; }

.template-label { font-size: 11px; font-weight: 600; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; }
.template-title { font-weight: 500; }
.template-body { font-size: 13px; line-height: 1.5; }

/* Why this one works, which is the half of the answer an example on its own does not give. */
.template-why {
    margin-top: 4px;
    padding-top: 5px;
    border-top: 1px dashed var(--border);
    font-size: 12px;
    font-style: italic;
    color: var(--ink-soft);
}

/* The label and its button share a line: the button belongs to the field below, and floating it
   anywhere else makes it look like it belongs to the form. */
.label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }

/* Title, then the hint on its own line. Inline, a hint of any length wrapped inside a
   third-width column and pushed that column's box down past its neighbours'. */
/* Title and Description are one labelled pair, but they are still two controls — flush against
   each other they read as one box with a line through it. */
input.form-wide + textarea.form-wide { margin-top: 4px; }

/* The row Save is about to add. Dashed, because it is a promise rather than a record — it must
   not be mistakable for something already published. */
.update.pending {
    padding: 6px 10px 6px 8px;
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    background: var(--bar);
}
.update.pending .at { font-style: italic; }

/* The chip explains what a service IS when you hover it, and says so with the plain arrow. */
.chip-noted { position: relative; cursor: default; }

.chip-pop {
    position: absolute;
    bottom: calc(100% + 7px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    width: max-content;
    max-width: 260px;
    padding: 8px 11px;
    font-size: 12px;
    line-height: 1.45;
    text-align: left;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .13);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.chip-noted:hover .chip-pop,
.chip-pick input:focus-visible + .chip .chip-pop { opacity: 1; visibility: visible; }

/* Chip popovers escape their row - EXCEPT in the pickers, which are scroll boxes.
   This rule sits after .chips-scroll and matches at the same weight, so it was winning: the
   picker kept its 280px ceiling and then drew its chips straight through the bottom of the box
   and over the Preview underneath. A max-height with visible overflow is not a shorter box, it
   is a box that lies about where it ends.
   :not() rather than reordering, because the reason each rule exists should stay next to it. */
.chips:not(.chips-scroll) { overflow: visible; }

/* The key prompt. Narrow, because it asks one thing. */
.unlock { max-width: 460px; margin: 40px auto 0; }
.unlock .form-actions { justify-content: flex-start; }

/*
   The same bottom-right brand mark the staff backend carries, so the two read as one product.

   The backend sets z-index:-1 and gets away with it because nothing opaque sits between the mark
   and the viewport. The page colour is painted by html rather than by body, so the canvas is
   behind everything and a z-index:-1 fixed child still shows - which means the mark can go behind
   the content without anything else having to be lifted in front of it.

   pointer-events:none because a decorative mark that eats clicks on whatever it happens to
   overlap is a bug waiting to be reported as "the last row is not clickable".
*/
.corner-mark {
    position: fixed;
    right: 25px;
    bottom: 15px;
    z-index: -1;
    opacity: .25;
    pointer-events: none;
}

.corner-mark img { display: block; width: 256px; }

/* NOTHING HERE MAY CREATE A STACKING CONTEXT. .page used to be position:relative with z-index:1,
   to lift it over the corner mark. A Bootstrap modal is a descendant of .page and asks for
   z-index 1055; inside a stacking context pinned at 1, it was confined to 1, and .modal-backdrop -
   appended to body, at 1050 - painted over it. The modals opened, took focus and looked entirely
   correct, and every click inside one landed on the backdrop instead. The mark goes behind
   instead, which needs no lift at all. */

/* Below the first container breakpoint the page is narrow enough that a 256px mark crowds it. */
@media (max-width: 767px) { .corner-mark { display: none; } }

/* The vendor mark on an integrations check row. Sized to the cap height of the label beside it
   rather than to a round number, so a logo reads as part of the line instead of a picture the
   text is wrapped around. */
.part-icon {
    width: 18px;
    height: 18px;
    object-fit: contain;
    vertical-align: -3px;
    margin-right: 7px;
}

/* A row in an iconed list that has no icon of its own - see partsBlock. Same box, nothing in it,
   so the labels stay in one column. */
.part-icon-none { display: inline-block; }
