/* The console's whole stylesheet: both consoles, the download portal, and the
   three pages somebody reaches before they have a session.

   Two rules hold it together.

   **Every colour is a token.** Nothing below writes a hex, and no template
   writes one either — a colour in a template is a colour that has to be found
   again by hand, and the block at `:root` exists so that changing one is one
   edit. The tokens that look redundant are not: `--on-accent` is a *shape* on an
   accent fill and `--on-accent-ink` is *text* on one — they are two entries
   because a tick and a label do not owe the same ratio, and reading either as
   "the colour that goes on the accent" is how white text ends up on a button it
   cannot be read on; `--warn-dark` and `--danger-strong` are the one step darker
   that bold text and a pressed state need in order to stay readable.

   **Every text it draws meets WCAG AA.** Four colours are darker than the
   prototype's for that and no other reason, each by the smallest step that
   cleared — `apps/console/tests` measures every pair the console renders, from
   these values, so a colour adjusted later for how it looks fails there rather
   than in front of somebody who cannot read it. Several of those pairs clear by
   hundredths, which is what being moved the minimum distance means: the margin is
   deliberately not there, and the test is what stands in for it.

   **It carries what the console has and nothing else.** The prototype it is
   drawn from styles screens this product does not serve yet — activity
   timelines, level ladders, editable chip lists, a tool gallery. Those rules
   are not here. A stylesheet that describes pages nobody can reach is one
   nobody can tell is out of date. */

/* Served from this repository rather than from a font host. The console is the
   control plane of a security product: a request to a third party on every page
   load tells that third party who is administering what and from where, and it
   is one more service whose outage or compromise is ours. Two files, split the
   way the foundry splits them, so a page that never renders an accented
   character never fetches the second.

   `--sans` names a real fallback stack, so a blocked or failed fetch degrades
   to the reader's own interface face rather than to a serif nobody chose. */
@font-face{
  font-family:"Space Grotesk";font-style:normal;font-weight:300 700;font-display:swap;
  src:url("fonts/space-grotesk-latin.87c506d88b9f.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,
    U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Space Grotesk";font-style:normal;font-weight:300 700;font-display:swap;
  src:url("fonts/space-grotesk-latin-ext.0bb2ef2eda4e.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,
    U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

:root{
  --bg:#d7e4f3; --panel:#fff; --ink:#2d3748; --muted:#526173;
  --line:#bcccdf; --line-strong:#9fb4cd;
  --accent:#ff6b00; --accent-soft:#ffece0; --accent-line:#ffc49e;
  --accent-text:#b44c00; --accent-hover:#e05e00;
  --gray-soft:#e7eef7;
  /* Six entries because a destructive control has six jobs, and the last two are
     the armed state: a control that has been clicked once and will act on the
     next click. `--danger-armed` is a *fill*, one step darker than `--danger`:
     every other danger surface here is a tint of it, so an armed control is the
     only thing in this console that *is* red rather than sitting on red — which
     is the point, since it is the only state where the next click destroys
     something. `--on-danger-ink` is text on that fill, named the way
     `--on-accent-ink` is and for the same reason: what a label owes on a red
     fill is not what a border or a tint owes, and one token for both is how a
     colour nobody can read gets chosen. */
  --danger:#c0392b; --danger-dark:#7f1a10; --danger-line:#e6b3ad; --danger-soft:#fdeceb;
  --danger-strong:#fbdedb; --danger-armed:#a32b1f; --on-danger-ink:#fff;
  --ok:#2f7d32; --ok-line:#9cc79e; --ok-soft:#edf6ed;
  --warn:#996900; --warn-dark:#6b4a00; --warn-line:#e3c98f; --warn-soft:#fff7e6;
  --on-accent:#fff; --on-accent-ink:#361700; --placeholder:#6a7889;
  --shadow:0 1px 2px rgba(45,55,72,.06),0 12px 32px rgba(45,55,72,.08);
  /* The lift under the chosen level in the level control, which is what makes
     it a shape sitting in the bar rather than a colour painted across it. Its
     own entry and not `--shadow`: that one is a panel lifted off the page and
     is far too large for something 21px tall inside a 25px pill. */
  --shadow-pill:0 1px 2px rgba(45,55,72,.12);
  /* The page seen through the dim behind an open panel. Its own entry rather
     than a shadow: a shadow is cast by an element and this covers everything
     that is not one. The ink is `--ink`'s, so what is behind a panel goes the
     colour of the text rather than grey. */
  --backdrop:rgba(45,55,72,.45);
  /* The third level's mark, taken from the approved design exactly. The other
     three marks are the palette's own colours at the same values the design
     gives them — danger, accent, ok — and this one is a yellow that appears
     nowhere else, so it is the one that needs naming. It is a *shape* and
     never text, so it answers to no pair in the contrast table. */
  --level-3:#ffc933; --level-3-line:#d9a300;
  /* The accent has four entries because it does four jobs, and only three of
     them carry text.

     `--accent` is the brand and is **unchanged** at the prototype's `#ff6b00`:
     the mark, the focus ring, a border, a checked box. Those are not text and
     are not covered by the text ratio this palette was measured against.
     **They are not thereby exempt from everything** — the focus ring and the
     boundary of a control answer to a different rule, at 3:1, and they do not
     meet it: the ring is 2.86 on a panel and 2.21 on the page behind it, and a
     checked box is 2.86 against its own card. Every one of those numbers is the
     prototype's, unchanged, and moving them means moving the brand colour. It is
     recorded here so that nobody reads "not text" as "nothing to answer for".

     `--accent-text` is accent-coloured *text* — a link, the current navigation
     entry, an accent pill, the vendor chip. It is darker than the prototype's
     `#e05e00` because on a hovered table row that sat at 3.1 against the row's
     own background. **Its name is the enforcement**: it is not a darker accent
     to reach for when something needs a shade, it is the colour text takes.
     `--accent-hover` is the prototype's own `#e05e00`, kept exactly, in the one
     place that value was a *fill*: the primary button's hover. Splitting them is
     what lets the text darken without the button changing colour.

     `--on-accent-ink` is text on an accent fill and `--on-accent` is a shape on
     one — the square inside the mark, the tick in a checked box. Two entries and
     not one, because the ratio a label owes is not one a tick owes, and a single
     token would have been resolved in whichever direction the last person needed.

     The alternative to darkening the label was to darken the fill and keep the
     label white. Rejected: white needs `#c75300` to clear, and the hover under it
     would have had to go darker again — two browner oranges on the sign-in card,
     sixty pixels under a mark that has to stay `#ff6b00`. */
  --r:8px;
  /* The app bar's height, and the sticky sidebar's offset, and the height the
     layout subtracts — one number, because three places inferring it is how
     they came to disagree: they each said 43px while the bar measured 47. */
  --bar:47px;
  --mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;
  --sans:"Space Grotesk",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:14px;line-height:1.5;
  -webkit-font-smoothing:antialiased}
a{color:var(--accent-text);text-decoration:none}
a:hover{text-decoration:underline}

/* one focus ring for everything that takes focus, drawn outside the element so it
   reads the same on a white button, an orange one, and a table row link */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
.btn:focus-visible,.side a:focus-visible{outline-offset:3px}
h1,h2,h3,p,dl,dd,figure{margin:0}
.mono{font-family:var(--mono)}

/* ---------- app bar ---------- */
.appbar{display:flex;align-items:center;gap:12px;height:var(--bar);padding:0 18px;
  background:var(--panel);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:50}
.mark{width:24px;height:24px;border-radius:6px;background:var(--accent);flex:none;position:relative}
/* `--on-accent` is now only ever a shape on an accent fill — this square and
   the tick in a checked box. What the accent carries as *text* is
   `--on-accent-ink`. */
.mark::after{content:"";position:absolute;inset:6.5px;border-radius:2px;background:var(--on-accent)}
.brand{font-weight:600;font-size:14px;letter-spacing:-.01em;line-height:1.15}
.brand small{display:block;font-weight:400;font-size:10.5px;color:var(--muted);letter-spacing:0}
.spacer{flex:1}
.orgchip{display:flex;align-items:center;gap:7px;border:1px solid var(--line);background:var(--gray-soft);
  color:var(--ink);font-size:11.5px;font-weight:600;padding:4px 12px;border-radius:99px;flex:none}
.orgchip.vendor{background:var(--accent-soft);border-color:var(--accent-line);color:var(--accent-text)}

/* ---------- layout ---------- */
.layout{display:grid;grid-template-columns:212px minmax(0,1fr);align-items:start;position:relative;
  min-height:calc(100vh - var(--bar))}
/* the nav scrolls with the viewport, so its column is painted on the body itself:
   a sticky element cannot cover a page taller than the screen */
body.nav{background:linear-gradient(90deg,var(--panel) 0 212px,var(--line) 212px 213px,var(--bg) 213px)}
.side{position:sticky;top:var(--bar);max-height:calc(100vh - var(--bar));padding:12px 10px;
  overflow-y:auto}
.side-g{font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);font-weight:700;
  padding:12px 10px 5px}
.side-g:first-child{padding-top:2px}
.side a{display:flex;align-items:center;gap:9px;padding:6px 10px;border-radius:6px;color:var(--ink);
  font-size:12.5px;font-weight:500;text-decoration:none}
.side a:hover{background:var(--gray-soft)}
.side a.on{background:var(--accent-soft);color:var(--accent-text);font-weight:600}
.side a .i{color:var(--muted);flex:none}
/* The label and its icon are one colour, which is what the prototype drew.
   They stopped being one when the label darkened for contrast, and the icon
   follows it rather than staying bright — it is decoration, it answers to no
   ratio, and matching is the version nearer the approved design. */
.side a.on .i{color:var(--accent-text)}
.main{padding:18px 22px 44px;max-width:1220px}
.main.narrow{max-width:820px}

/* ---------- page head ---------- */
.crumb{display:flex;align-items:center;gap:5px;font-size:11.5px;color:var(--muted);margin-bottom:7px}
.crumb a{color:var(--muted);font-weight:600}
.crumb a:hover{color:var(--ink)}
.crumb .i{width:12px;height:12px}
.page{display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;margin-bottom:15px}
.page .t{min-width:0}
.h1{font-size:20px;font-weight:600;letter-spacing:-.02em;display:flex;align-items:center;gap:9px;
  flex-wrap:wrap}
.sub{color:var(--muted);font-size:12.5px;margin-top:3px}
.sub b{color:var(--ink);font-weight:600}
.page .acts{margin-left:auto;display:flex;align-items:center;gap:8px;flex:none}

/* ---------- panels ---------- */
.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);
  margin-bottom:14px;overflow:hidden}
.panel-h{display:flex;align-items:center;gap:10px;padding:10px 15px;border-bottom:1px solid var(--line)}
/* h1 as well as h2, because on the three pages that are a single card the
   panel's heading is the page's heading and there is no larger one above it */
.panel-h h1,.panel-h h2{font-size:13px;font-weight:600;letter-spacing:-.01em}
.panel-h .s{font-size:11.5px;color:var(--muted)}
.panel-b{padding:14px 15px}
.panel-b.tight{padding:0}
.panel-f{padding:9px 15px;border-top:1px solid var(--line);background:var(--gray-soft);
  font-size:11.5px;color:var(--muted);line-height:1.45}
.split{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:14px;align-items:start}
.split.wide{grid-template-columns:minmax(0,1fr) 380px}
.grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;align-items:start}
/* fields sit in a grid with no gap down the page: each already carries its own
   bottom margin, and a row gap on top of it reads as two separate forms */
.grid2.fields{gap:0 14px}

/* ---------- a panel that opens over the page ---------- */
/* One element in two worlds, which is what makes the panel work with a script
   and without one. The server renders `<dialog open>`; the browser lays that out
   as a block the width of its content with nothing behind it dimmed, which is
   what "a modal" was not. The script upgrades that same element with
   `showModal()`, which puts it in the top layer, dims the page through
   `::backdrop`, holds the tab order inside it and closes it on Escape.

   So both states are drawn here. `position:fixed` with `inset:0` and
   `margin:auto` is what centres it in the viewport, and it centres the
   no-script one too — a panel a browser running nothing draws as a card of a
   readable width, over the page rather than across it. What only `showModal()`
   can add is the dimming and the focus, and neither is something a stylesheet
   can stand in for.

   The padding is the browser's own and is taken off: the panel already carries
   a header, a body and a footer with their own. The shadow is `.panel`'s. */
dialog.panel{position:fixed;inset:0;margin:auto;padding:0;
  width:calc(100% - 32px);max-width:560px;max-height:calc(100vh - 64px);overflow:auto}
dialog.panel::backdrop{background:var(--backdrop)}

/* ---------- numbers with a gloss ---------- */
.strip{display:flex;flex-wrap:wrap;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden;margin-bottom:14px}
.stat{padding:12px 18px;border-right:1px solid var(--line);display:flex;align-items:baseline;gap:10px;
  flex:1;min-width:168px}
.stat:last-child{border-right:0}
.stat .n{font-family:var(--mono);font-size:23px;font-weight:700;line-height:1;letter-spacing:-.02em}
.stat .k{font-size:11px;color:var(--muted);line-height:1.3}
.stat .k b{display:block;color:var(--ink);font-weight:600;font-size:11.5px}
.stat.danger .n{color:var(--danger)}
.stat .n.zero{color:var(--muted)}
/* A strip drawn inside a panel already has the panel's surface; its own border,
   radius and shadow would read as a second card sitting on the first. */
.panel-b.nested-strip .strip,.panel-b.tight > .strip{
  border:0;border-radius:0;box-shadow:none;margin:0;background:transparent}

/* ---------- tables ---------- */
.tbl{width:100%;border-collapse:collapse;font-size:12.5px}
.tbl th{text-align:left;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
  font-weight:700;padding:8px 15px;background:var(--gray-soft);border-bottom:1px solid var(--line);white-space:nowrap}
/* A heading that sorts is a link, and it is the one link in this console drawn
   in the colour of the text around it rather than in the accent. A row of seven
   blue headings would read as seven things to go and look at; what these do is
   reorder what is already on screen. The underline on hover is what is left to
   say they can be clicked, and the arrow on the sorted one says which is. */
.tbl th a{display:inline-flex;align-items:center;gap:4px;color:inherit}
.tbl th .i{width:11px;height:11px}
.tbl td{padding:9px 15px;border-top:1px solid var(--line);vertical-align:middle}
.tbl tbody tr:first-child td{border-top:0}
.tbl.rows tbody tr:hover{background:var(--gray-soft)}
.tbl .r{text-align:right}
.tbl .num{font-family:var(--mono);text-align:right;font-weight:600}
.tbl .num.zero{color:var(--muted);font-weight:400}
.tbl .nm{font-weight:600;letter-spacing:-.01em}
.tbl .dim{color:var(--muted)}
.tbl td small{display:block;font-size:11px;color:var(--muted);font-weight:400;margin-top:1px}
.tbl .w{white-space:nowrap}
/* a row's own form has no layout of its own: it holds one button and would
   otherwise put it on a line below the cell it belongs in */
.tbl td form{display:inline}

/* ---------- a row that is one link ---------- */
/* The whole row is clickable and there is still exactly one link on it. The
   name's own anchor grows a box the size of the row, so a click anywhere in the
   row is a click on that anchor — which means the middle click, the modifier
   click and the context menu all do what they do on a link, and the keyboard
   still finds one stop per row rather than one per cell.

   `position:relative` on the row is what the box is measured against; without it
   the box is measured against the page and covers the document. The row is
   marked rather than every row in this console: a table whose cells are values
   to read, rather than a way through to a page, is left alone.

   What it costs is selecting text in the covered cells, which is the known price
   of this pattern and is why it is on the lists whose rows are a way through to
   a page — the fleet, the profiles and the activity list — rather than on the
   tables somebody reads values off. */
.tbl .linked{position:relative}
.tbl .linked .nm a::after{content:"";position:absolute;inset:0}
/* And the cell holding the row's own controls, lifted back out from under that
   box. The box is a positioned element, so it paints over everything after it
   that is not one — including a button in a later cell, which would look
   pressable and navigate instead. Positioning this cell puts it after the box in
   the same layer, which is what both the pointer and the paint follow.

   It is what makes a row carry an act as well as a destination. A *second link*
   still cannot go here: two anchors on one row is the case the box has no answer
   for. A form and a button are not a navigation and take nothing from the row's
   one destination. */
.tbl .linked .r{position:relative}
/* Two things standing together on the end of a row, drawn as one strip.

   The profiles list is why this exists: one of the two is a pill and the other
   is a button, and a pill is smaller type on thinner padding than a button is.
   Side by side in one cell they stood at different heights, and the label read
   as having been shrunk rather than as the state it names.

   **`stretch` is what settles that, and it settles it without a number.** The
   pill takes the height of the tallest item in the strip, which is the button
   beside it, so the two go on agreeing whatever either of them is later drawn
   with. A height written here instead would be a number measured off a screen,
   and it would stop being true the first time a button's padding moved — with
   nothing failing to say so. The label keeps its own type: it is a label and not
   a control, and matching a button by growing the words is how a label comes to
   look pressable.

   `justify-content` and not the cell's own `text-align`, which is not what a
   flex container lays its items out with. */
.tbl .r .pair{display:flex;align-items:stretch;justify-content:flex-end;gap:8px}

/* ---------- pills and tags ---------- */
.pill{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;padding:2px 8px;
  border-radius:99px;letter-spacing:.02em;white-space:nowrap;border:1px solid transparent}
.pill.ok{background:var(--ok-soft);border-color:var(--ok-line);color:var(--ok)}
.pill.warn{background:var(--warn-soft);border-color:var(--warn-line);color:var(--warn)}
.pill.danger{background:var(--danger-soft);border-color:var(--danger-line);color:var(--danger-dark)}
.pill.accent{background:var(--accent-soft);border-color:var(--accent-line);color:var(--accent-text)}
.pill.muted{background:var(--gray-soft);border-color:var(--line);color:var(--muted)}
.pill .d{width:6px;height:6px;border-radius:99px;background:currentColor;flex:none}
.tag{display:inline-flex;align-items:center;font-family:var(--mono);font-size:11px;background:var(--gray-soft);
  border:1px solid var(--line);border-radius:5px;padding:1px 6px;color:var(--muted);white-space:nowrap}

/* ---------- buttons ---------- */
/* `line-height` is declared rather than inherited, and that declaration is what
   makes a link drawn as a button and a real `<button>` the same height. A form
   control does not take the body's 1.5: the browser's own sheet sets
   `line-height:normal` on it, so the two came out a few pixels apart wherever
   they stand side by side — a panel's Revoke beside its Close, a Cancel beside
   a Create — with nothing in the markup to say why. `font-family`, `font-size`
   and `color` are on this rule for the same reason, and this is the fourth of
   them. */
.btn{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);background:var(--panel);
  color:var(--ink);padding:6px 13px;border-radius:var(--r);cursor:pointer;font-family:var(--sans);
  font-size:12.5px;font-weight:600;line-height:1.5;white-space:nowrap;text-decoration:none}
.btn:hover{border-color:var(--line-strong);background:var(--gray-soft);text-decoration:none}
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent-ink)}
.btn.primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}
.btn.danger{color:var(--danger-dark);border-color:var(--danger-line);background:var(--danger-soft)}
.btn.danger:hover{background:var(--danger-strong);border-color:var(--danger)}
.btn.sm{padding:4px 10px;font-size:11.5px}
/* A destructive control that has been clicked once and will act on the next
   click. It is a fill rather than a tint, because the idle state is already a
   tint of the same colour and the two read as one state: the label
   changing from "Delete" to "Confirm?" was the whole of what said the state had
   changed, and a reader who was not watching that word had nothing to read.

   After the two rules above, and the hover written out, because `.btn.danger`
   and `.btn.danger:hover` are as specific as these — the armed fill wins by
   being last, and without the second line hovering an armed control would put
   the idle tint back under the pointer. It applies to every weight and not only
   to `danger`: the machine page arms a plain control, and an armed control is
   about to destroy something whatever it is drawn as. */
.btn.armed{background:var(--danger-armed);border-color:var(--danger-armed);
  color:var(--on-danger-ink)}
.btn.armed:hover{background:var(--danger-dark);border-color:var(--danger-dark)}
.btn .i{flex:none}
/* the only control on a card that is as wide as the card: the last step of
   signing in, where there is nothing else to press */
.btn.wide{width:100%;justify-content:center}

/* ---------- forms ---------- */
.field{margin-bottom:13px}
/* A label and its control on opposite sides of a 1200px column is a pair the
   reader has to match up by eye. The prototype constrains the field rather than
   the page, so a list and the form under it can share one column. */
.panel-b .field{max-width:520px}
.grid2.fields .field{max-width:none}
/* Seats, and anything else counted: a number field as wide as a name field
   reads as a field that wants a sentence. */
input[type=number]{max-width:180px}
.field label{display:block;font-size:11px;font-weight:700;letter-spacing:.02em;margin-bottom:4px}
.field .hint{font-size:11.5px;color:var(--muted);line-height:1.45;margin-top:5px}
/* the password rules arrive from the framework as a list, and a list with the
   browser's own margins reads as a second paragraph rather than as a hint */
.field .hint ul{margin:3px 0 0;padding-left:15px}
/* What a form says when it was wrong. The prototype has no failing form, so
   this is the console's own addition — beside the field rather than at the top,
   because a message about one field at the top of four is a message the reader
   has to match up by hand. */
.field .hint.bad{color:var(--danger-dark);font-weight:600}
.field.bad input,.field.bad select{border-color:var(--danger-line)}
input,select{font-family:var(--sans);font-size:13px;color:var(--ink);background:var(--panel);
  border:1px solid var(--line);border-radius:6px;padding:8px 10px;outline:0;width:100%;min-width:0}
input.mono{font-family:var(--mono);font-size:12px}
/* Its own colour, not the border's. A placeholder is text and answers to the
   same ratio as text; `--line-strong` is a border and answers to none, and
   darkening it to fix this would have darkened every checkbox and every
   hovered button's edge to fix a colour in a field. */
input::placeholder{color:var(--placeholder)}
input:focus,select:focus{border-color:var(--accent-line);box-shadow:0 0 0 3px var(--accent-soft)}
/* The chevron is drawn from two borders rather than from an embedded image. An
   image would have to carry its own stroke colour, and a colour inside a data
   URI is the one colour on this page that `--muted` could not change. */
.sel{position:relative;display:block}
.sel select{appearance:none;padding-right:28px}
.sel::after{content:"";position:absolute;right:11px;top:calc(50% - 4px);width:6px;height:6px;
  border-right:1.6px solid var(--muted);border-bottom:1.6px solid var(--muted);
  transform:rotate(45deg);pointer-events:none}
.check{display:flex;align-items:flex-start;gap:9px;padding:9px 11px;border:1px solid var(--line);
  border-radius:var(--r);cursor:pointer;background:var(--panel);margin-bottom:7px}
.panel-b > .check:last-child{margin-bottom:0}
.check:hover{border-color:var(--line-strong)}
/* The prototype marks the chosen card from a script. Nothing here runs one, so
   the selector reads the radio itself — which is also the only version that is
   still right when somebody moves the choice with the keyboard. */
.check:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}
.check input{appearance:none;width:15px;height:15px;flex:none;border:1px solid var(--line-strong);
  border-radius:4px;background:var(--panel);cursor:pointer;position:relative;padding:0;margin-top:2px}
.check input[type=radio]{border-radius:99px}
.check input:checked{background:var(--accent);border-color:var(--accent)}
.check input[type=checkbox]:checked::after{content:"";position:absolute;left:6.5px;top:1px;width:4px;height:8px;
  border:solid var(--on-accent);border-width:0 2px 2px 0;transform:rotate(43deg)}
.check input[type=radio]:checked{background:var(--panel);border-width:5px}
/* the title and its explanation are written as either divs or spans; both stack */
.check .txt{flex:1;min-width:0}
.check .t{display:block;font-size:12.5px;font-weight:600;letter-spacing:-.01em}
.check .d{display:block;font-size:11.5px;color:var(--muted);line-height:1.45;margin-top:2px}
.check .d.bad{color:var(--danger-dark);font-weight:600}
/* the buttons end a form rather than sitting in a bar of their own */
.ends{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.ends .note{margin-right:auto;max-width:560px}
/* **Laid out whether or not it is showing**, which is the whole of why this is
   `visibility` and not `display`. The label sits before Apply in the row, so a
   label that took up no space until it appeared moved the button across the row
   the moment anything was touched — under the pointer that had just touched it.
   Reserving the space costs a fixed strip of empty row and moves nothing. */
.ends .dirty{visibility:hidden;margin-left:auto;font-size:11.5px;color:var(--warn-dark);font-weight:600}
/* Shown only after a choice disagrees with what the page was drawn with, and
   cleared the moment Apply is pressed — so a save in flight does not keep saying
   there is something left to do. The class is toggled by the console script. */
form.is-dirty .dirty{visibility:visible}
/* Except where a form is longer than a screen and repeats its control at the
   top. A panel carries its own bottom margin and none above it, so the gap
   between a control and the panel under it has to be written here — and it is
   the panel's own, so a row of buttons between two panels sits where a panel
   would. */
.ends + .panel{margin-top:14px}
/* The category page's Apply sits inside the form, and the protections table is
   the form's sibling — so the gap after Apply is the form's own bottom margin
   rather than `.ends + .panel`. */
form:has(.ladder){margin-bottom:14px}

/* ---------- explanation beside a control ---------- */
.note{font-size:11.5px;color:var(--muted);line-height:1.5}
.note.box{background:var(--gray-soft);border:1px solid var(--line);border-radius:var(--r);padding:10px 12px}
.note.danger{background:var(--danger-soft);border-color:var(--danger-line);color:var(--danger-dark)}
.note b{color:inherit;font-weight:700}
/* an alert is always about the form under it, so it carries the gap rather
   than the first field having to know an alert might appear above it */
.note.box[role=alert]{margin-bottom:13px}
.banner{display:flex;align-items:flex-start;gap:11px;border:1px solid var(--warn-line);background:var(--warn-soft);
  color:var(--warn);border-radius:var(--r);padding:11px 14px;margin-bottom:14px;font-size:12.5px;line-height:1.5}
.banner .i{flex:none;margin-top:1px}
.banner b{color:var(--warn-dark)}
.banner.danger{border-color:var(--danger-line);background:var(--danger-soft);color:var(--danger-dark)}
.banner.danger b{color:var(--danger-dark)}

/* ---------- a secret, shown once ---------- */
/* A joining link, an administrator link: a value that exists in readable form
   only in the answer to the request that made it, because only a digest is
   kept. So it is drawn as a value to be taken rather than as prose — its own
   surface, the monospace face, and broken across lines wherever it has to be,
   since a link that runs off the side of a panel is one somebody copies half
   of.

   There is no copy button in the markup. The script puts one here, in this
   element, and a browser running none leaves selectable text — which is what a
   button that needs a script to do anything would have degraded into anyway,
   except that it would still have been on screen, still have looked pressable,
   and still have done nothing. */
.secret{display:flex;align-items:flex-start;gap:8px;background:var(--gray-soft);
  border:1px solid var(--line);border-radius:var(--r);padding:9px 11px}
.secret code{flex:1;min-width:0;font-family:var(--mono);font-size:11.5px;
  line-height:1.45;word-break:break-all}
.secret .btn{flex:none}

/* ---------- facts ---------- */
.kv{display:grid;grid-template-columns:132px minmax(0,1fr);gap:8px 14px}
.kv dt{font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:700;padding-top:2px}
.kv dd{margin:0;font-size:12.5px}
.kv dd.mono{font-family:var(--mono);font-size:11.5px;word-break:break-all}
.kv.wide{grid-template-columns:176px minmax(0,1fr)}

/* ---------- lists inside panels ---------- */
.rows > *{border-top:1px solid var(--line)}
.rows > *:first-child{border-top:0}
.row{display:flex;align-items:center;gap:12px;padding:10px 15px}
.row .txt{flex:1;min-width:0}
.row .t{font-size:12.5px;font-weight:600;letter-spacing:-.01em}
.row .d{font-size:11.5px;color:var(--muted);line-height:1.4;margin-top:1px}

/* ---------- filters ---------- */
.filters{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 15px;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);margin-bottom:14px}
.filters select,.filters input{width:auto;font-size:12px;padding:5px 9px;font-weight:600}
.filters .sel{display:inline-block}
.filters .sel select{padding-right:26px}
/* Toggle menus for filters that take several values at once. A native
   multi-select needs a modifier key; a wall of chips owns the page when the
   destination list is long. A summary names the filter and how many are on;
   the options open underneath and Apply is still what commits. */
.menu{position:relative;display:inline-block}
.menu > summary{list-style:none;display:inline-flex;align-items:center;gap:6px;
  position:relative;font-size:12px;font-weight:600;padding:5px 26px 5px 9px;
  background:var(--panel);border:1px solid var(--line);border-radius:6px;cursor:pointer;
  line-height:1.3;color:var(--ink);user-select:none}
.menu > summary::-webkit-details-marker{display:none}
.menu > summary::after{content:"";position:absolute;right:10px;top:50%;
  width:6px;height:6px;border-right:1.6px solid var(--muted);border-bottom:1.6px solid var(--muted);
  transform:translateY(-50%) rotate(45deg);pointer-events:none}
.menu[open] > summary{border-color:var(--accent-line);box-shadow:0 0 0 3px var(--accent-soft)}
.menu .k{color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.02em;
  text-transform:uppercase}
.menu .v{color:var(--ink)}
.menu-b{position:absolute;z-index:5;top:calc(100% + 4px);left:0;min-width:220px;
  max-width:min(360px,80vw);max-height:280px;overflow:auto;padding:6px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--shadow)}
.menu-b .check{padding:6px 8px;margin:0;border-radius:6px;gap:8px}
.menu-b .check:hover{background:var(--gray-soft)}
.menu-b .dim{padding:8px;font-size:11.5px;color:var(--muted);line-height:1.4}

/* Starting level as four tiles, two to a row. */
.level-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.level-tile{display:flex;flex-direction:column;gap:3px;padding:10px 12px;
  position:relative;border:1px solid var(--line);border-radius:var(--r);background:var(--panel);
  cursor:pointer;min-width:0}
.level-tile:hover{border-color:var(--line-strong)}
.level-tile input{appearance:none;width:0;height:0;min-width:0;margin:0;padding:0;
  border:0;opacity:0;box-shadow:none;position:absolute}
.level-tile:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
/* Highlight follows the radio itself. A static `.on` class on the default
   tile would stay lit after another choice, so only `:has(input:checked)`. */
.level-tile:has(input:checked){border-color:var(--accent-line);
  background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent-line)}
.level-tile .t{font-size:12.5px;font-weight:600;letter-spacing:-.01em;color:var(--ink)}
.level-tile .d{font-size:11px;color:var(--muted);line-height:1.4}
.level-tile:has(input:checked) .t{color:var(--accent-text)}
/* wide enough that the longest placeholder we use is never clipped; the row wraps
   before the field shrinks past it */
.filters .search{position:relative;flex:1;min-width:240px;max-width:290px}
.filters .search input{width:100%;padding-left:29px;font-weight:400}
.filters .search .i{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:var(--muted)}
.filters .rt{margin-left:auto;font-size:11.5px;color:var(--muted)}

/* A row of small things that wraps: the values already on a list, each with the
   form that removes it. A grid would give every chip the width of the longest;
   these are words of very different lengths and the row reads as a list of them
   rather than as a table with one column. */
.inline{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:11px}
.inline form{display:flex;align-items:center;gap:5px}

/* ---------- the level control, the level map, and the ladder ---------- */
/* Three components, all from the prototype and all for one question: how much
   risk this profile carries in one kind of harm.

   The control is the prototype's `.levels`, and it is a **pill-shaped bar with
   the chosen level as a shape inside it** — a smaller pill, lifted off the bar,
   with the bar's own fill still showing around it. It is not a colour painted
   across the bar, which is what the version before this one drew: the bar was a
   form, `.tbl td form` set every form in a table cell to `display:inline` so
   that a one-button form does not take a line of its own, and that rule is more
   specific than this one — so the bar stopped being a flex container and became
   a strip of colour behind four buttons standing on top of it. The bar is a
   `<span>` now, and the four levels are one radio group inside the page's form.

   That is also what makes choosing a level cost nothing: `:has` reads the radio
   itself, so the mark follows the choice with no request and no script, and it
   is still right when the choice is moved with the keyboard. Committing the
   choice is the form's own button.

   The four tones are the four levels, least strict first, and they are the same
   four the map below uses. Each one is a text colour on its own tint, and each
   of the four is a pair `apps/console/tests` already measures as a pill — at the
   same threshold, and three of them on the same two tokens, so a tone that
   clears there clears here. */
.levels{display:inline-flex;background:var(--gray-soft);border:1px solid var(--line);
  border-radius:99px;padding:2px;flex:none;gap:2px;vertical-align:middle}
.levels .lvl{display:inline-flex;align-items:center;font-family:var(--sans);
  font-size:11px;font-weight:600;line-height:1.35;color:var(--muted);padding:4px 12px;
  border-radius:99px;cursor:pointer;white-space:nowrap}
.levels .lvl:hover{color:var(--ink)}
/* The radio is the control and the label is the shape, so the radio is drawn to
   nothing. Sized away rather than hidden: `display:none` takes it out of the tab
   order, and a level nobody can reach from the keyboard is a level nobody can
   reach. The focus ring goes on the label, because a ring around a box of no
   size is a dot beside the word rather than a mark on the thing being chosen. */
.levels .lvl input{appearance:none;width:0;height:0;min-width:0;margin:0;padding:0;
  border:0;opacity:0;box-shadow:none}
.levels .lvl:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
/* The lift is what puts the chosen level *in* the bar rather than over it: the
   tint says which one, and the shadow says it is a separate shape resting on the
   bar's own fill. Both are needed — a tint alone at these sizes reads as the bar
   having changed colour under one word. */
.levels .lvl:has(input:checked){box-shadow:var(--shadow-pill)}
.levels .lvl.l1:has(input:checked){background:var(--danger-soft);color:var(--danger-dark)}
.levels .lvl.l2:has(input:checked){background:var(--accent-soft);color:var(--accent-text)}
.levels .lvl.l3:has(input:checked){background:var(--warn-soft);color:var(--warn-dark)}
.levels .lvl.l4:has(input:checked){background:var(--ok-soft);color:var(--ok)}

/* Thirteen marks, one per kind of harm, in the Architecture's order. It is what
   lets four profiles be seen to disagree without opening one, and the order is
   what makes two rows comparable at a glance — so it is the schema's order and
   never a sort. */
.dots{display:inline-flex;gap:2px;align-items:center}
.dots i{width:6px;height:15px;border-radius:2px;display:block;
  background:var(--gray-soft);border:1px solid var(--line)}
.dots i.l1{background:var(--danger);border-color:var(--danger-dark)}
.dots i.l2{background:var(--accent);border-color:var(--accent-hover)}
.dots i.l3{background:var(--level-3);border-color:var(--level-3-line)}
.dots i.l4{background:var(--ok);border-color:var(--ok)}

/* One kind of harm, four rungs, one radio group. A rung is a `<label>` holding
   its own radio, so the whole row is the control: the pointer can land anywhere
   on it, the arrow keys move between the four, and a screen reader announces one
   of four rather than four separate things.

   The version before this drew the circle as a `<span>` and put a button at the
   end of each row. It looked exactly like this and the circle did nothing at
   all — there was no input under it and no label around it, so the one thing on
   the row that reads as a control was a picture of one. `cursor:pointer` on the
   whole row is the other half: a row that acts like a control has to look like
   it is worth clicking before it is clicked.

   **The tint follows the radio; `.on` names the level in force.** `.on` is
   rendered by the view and stays on the rung the profile holds; `:checked`
   follows the radio and paints the tint. They agree until somebody picks another
   rung, and after that the page is showing both — which is the whole difference
   between choosing and saving. */
.ladder{display:flex;flex-direction:column}
.step{display:flex;gap:12px;padding:12px 15px;border-top:1px solid var(--line);
  align-items:center;cursor:pointer}
.step:first-child{border-top:0}
.step.on{box-shadow:inset 3px 0 0 var(--accent)}
.step:has(input:checked){background:var(--accent-soft)}
/* Drawn to nothing and then drawn again, the way the checkbox cards are: the
   platform's own radio is a different shape on every platform, and this one has
   to be the same circle the rest of the console draws. Sized and visible rather
   than hidden, so the focus ring the stylesheet already puts on every control
   lands on the thing being chosen. */
.step .mk{appearance:none;width:16px;height:16px;flex:none;margin:0;padding:0;
  border:1px solid var(--line-strong);border-radius:99px;background:var(--panel);
  cursor:pointer}
.step .mk:checked{border:5px solid var(--accent)}
.step .t{font-size:13px;font-weight:600;letter-spacing:-.01em}
/* The count of rules a level turns on. Deliberately not a count of anything a
   rule *did*: every such figure on the prototype's page is a record, and records
   are a later milestone. */
.step .ev{margin-left:auto;text-align:right;flex:none;font-size:10.5px;
  color:var(--muted);line-height:1.3}
.step .ev b{display:block;font-family:var(--mono);font-size:15px;color:var(--ink);
  font-weight:700}

/* ---------- empty states ---------- */
.empty{padding:34px 20px;text-align:center;color:var(--muted)}
/* 28px, which is what the prototype writes as `.i.xl` on every one of these.
   Scoped here rather than made a class, because the icon tag renders one class
   and a size no template can ask for is a size no template will get. */
.empty .i{width:28px;height:28px;color:var(--line-strong);margin-bottom:9px}
.empty .t{font-size:13.5px;font-weight:600;color:var(--ink);letter-spacing:-.01em}
.empty .d{font-size:12px;line-height:1.5;margin:5px auto 0;max-width:420px}
.empty .btn{margin-top:13px}

/* ---------- the card somebody sees before they have a session ---------- */
.signin{min-height:100vh;display:grid;place-items:center;padding:24px}
.signin .card{width:100%;max-width:392px}
.signin .brandrow{display:flex;align-items:center;gap:11px;margin-bottom:18px;justify-content:center}
.signin .h1{margin-bottom:14px}
.signin .panel-b .note{display:block;margin-bottom:14px}
/* The card's rhythm is carried by the margin above rather than by a gap, so a
   value drawn between two notes needs the same one — without it the tinted box
   and the sentence under it touch. Inside the console the component sits in a
   `stack`, which is where that spacing comes from there. */
.signin .panel-b .secret{margin-bottom:14px}

/* ---------- misc ---------- */
.i{display:inline-flex;width:16px;height:16px;flex:none}
.i svg{width:100%;height:100%;display:block}
.dim{color:var(--muted)}
.stack{display:flex;flex-direction:column;gap:14px}

@media (max-width:1080px){
  .split,.split.wide{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:820px){
  .layout{grid-template-columns:minmax(0,1fr)}
  body.nav{background:var(--bg)}
  .side{background:var(--panel);position:static;max-height:none;border-right:0;
    border-bottom:1px solid var(--line);display:flex;gap:4px;overflow-x:auto;padding:8px 10px}
  .side-g{display:none}
  .side a{white-space:nowrap}
  .grid2,.grid2.fields{grid-template-columns:minmax(0,1fr)}
  .main{padding:14px 14px 36px}
  /* a table narrower than its columns is a table with a row of its own to
     scroll, rather than a page that scrolls sideways under the navigation */
  .panel-b.tight{overflow-x:auto}
}

/* A filter bar that is part of a panel rather than a strip above one — the
   machine page narrows its own activity in place. It keeps the padding and the
   layout and gives up the card: a bordered box inside a bordered box reads as
   something that floated loose. */
.panel .filters{border:0;border-bottom:1px solid var(--line);border-radius:0;
  box-shadow:none;margin-bottom:0;background:transparent}

/* How many other records said the same thing, beside the newest one's moment
   rather than in a column of its own: it qualifies the time, it is not a
   measurement standing on its own. */
.tbl .more{white-space:nowrap;color:var(--muted);font-variant-numeric:tabular-nums}

/* ---------- a list, and the two acts on it ---------- */
/* One panel per parameter, so the parameter's sentence is the panel's subtitle
   and each act is a single row rather than a labelled field of its own. */
.panel-h.lists h2{flex:none}
.panel-h.lists .s{flex:1;min-width:0;line-height:1.45}
.line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:11px}
.line:last-child{margin-bottom:0}
/* a field that takes one value is as wide as one value, not as wide as the row */
.line input{width:230px}
.line .sel{display:inline-block}
/* what a copy is worth belongs to the row, and under it */
.line .hint{flex-basis:100%;font-size:11.5px;color:var(--muted);line-height:1.45}
