/* environload — dark-only admin UI.
 *
 * Two typographic registers, one rule:
 *   monospace  = literal machine truth (keys, values, versions, hosts, tokens)
 *   sans       = us talking to you (prose, help, empty states, errors)
 * If it would be wrong to reproduce inexactly, it is set in monospace.
 *
 * Scope colour is a temperature ramp, not a set of labels. Precedence runs
 * cool+quiet -> warm+loud, so the nearest scope literally reads loudest.
 *
 * This file is the vocabulary every page shares: tokens, the reset, the shell,
 * and the components more than one view reaches for. What only one view uses
 * lives in pages.css, which the document loads second — so a rule there may
 * rely on sitting after everything here, and a rule here may not rely on
 * coming after anything there.
 */

:root {
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Code",
    "JetBrains Mono", Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* ink: a violet-leaning dark. not black — black reads as a terminal, and this
     is a vault. */
  --bg: #15141d;
  --surface: #1c1b27;
  --surface-2: #232231;
  --surface-3: #2b2a3c;
  --line: #322f47;
  --line-soft: #262435;

  --text: #e9e7f2;
  --text-dim: #a5a1bd;
  --text-faint: #6f6b8c;

  /* precedence ramp: global (coolest, quietest) -> environment (warmest, loudest) */
  --scope-global: #7d79a0;
  --scope-service: #63c0b6;
  --scope-environment: #f2a65f;

  --accent: #9d8cff;
  --accent-dim: #6d5fd0;
  --warn: #f2c46b;
  --danger: #ff7a7a;
  --ok: #63c08a;
  /* End-to-end values sit outside the scope ramp on purpose: precedence colour
     answers "which scope won", and this answers "who holds the key", which is a
     different axis entirely. */
  --sealed: #7fd4ff;

  --radius: 7px;
  --radius-sm: 4px;

  --shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 8px 24px -12px rgb(0 0 0 / 0.6);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  color-scheme: dark;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 13.5px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* prose register */
.prose,
p.note,
.empty p,
.help {
  font-family: var(--sans);
  color: var(--text-dim);
  line-height: 1.6;
}

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

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---------- shell ---------- */

.shell {
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr);
  min-height: 100vh;
}

.sidebar {
  border-right: 1px solid var(--line);
  background: var(--surface);
  padding: 18px 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 0 18px 16px;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 10px;
}
.brand b {
  font-weight: 700;
  letter-spacing: -0.02em;
  font-size: 15px;
}
.brand span {
  color: var(--text-faint);
  font-size: 11px;
}

.nav-label {
  padding: 14px 18px 4px;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.nav a {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 18px;
  color: var(--text-dim);
  border-left: 2px solid transparent;
}
.nav a:hover {
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.nav a[aria-current="page"] {
  color: var(--text);
  border-left-color: var(--accent);
  background: var(--surface-2);
}
.nav .env-link {
  padding-left: 32px;
  font-size: 12.5px;
}

/* Who you are signed in as, and the way out. margin-top:auto is what pins it to
   the bottom of the column however few services the nav above it lists. */
.sidebar-foot {
  margin-top: auto;
  padding: 16px 18px 0;
  border-top: 1px solid var(--line-soft);
}
.sidebar-foot .dim {
  font-size: 11px;
  margin-bottom: 6px;
}

.main {
  padding: 26px 32px 80px;
  max-width: 1180px;
  min-width: 0;
}

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 22px;
  flex-wrap: wrap;
}
.page-head h1 {
  margin: 0;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.page-head .sub {
  margin: 4px 0 0;
  color: var(--text-faint);
  font-size: 12px;
}

/* ---------- precedence meter ---------- */

/* Three segments, filled left to right by how strongly a scope binds. This is
   information, not decoration: more fill means a nearer scope. */
.meter {
  display: inline-flex;
  gap: 2px;
  vertical-align: middle;
  margin-right: 6px;
}
.meter i {
  width: 4px;
  height: 11px;
  border-radius: 1px;
  background: var(--line);
  display: block;
}
.scope-global .meter i:nth-child(1) {
  background: var(--scope-global);
}
.scope-service .meter i:nth-child(-n + 2) {
  background: var(--scope-service);
}
.scope-environment .meter i {
  background: var(--scope-environment);
}

/* The scope name that reads out the meter it follows, for the rows where the
   meter alone is not enough to say which scope is meant. */
.meter-label {
  margin-left: 6px;
  font-size: 11px;
}

.scope-tag {
  display: inline-flex;
  align-items: center;
  font-size: 11.5px;
  white-space: nowrap;
}
.scope-global {
  color: var(--scope-global);
}
.scope-service {
  color: var(--scope-service);
}
.scope-environment {
  color: var(--scope-environment);
}

/* ---------- tables ---------- */

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel + .panel {
  margin-top: 20px;
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface-2);
}
.panel-head h2 {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
/* The subtitle beside a panel's heading. */
.panel-head .dim {
  font-size: 11px;
}
/* The two shapes a panel's contents take: a body on its own, and one that
   follows a table or a list and needs the rule between them. These were nine
   copies of two paddings spread over six templates, so changing panel spacing
   meant editing every one of them from outside the file that defines it. */
.panel-body {
  padding: 14px 16px;
}
.panel-foot {
  padding: 14px 16px;
  border-top: 1px solid var(--line-soft);
}
/* Two forms in one panel body are separated by the combinator rather than by a
   flag threaded through the view model: the browser evaluates it against what
   was actually rendered, so it cannot disagree with which forms are there. */
.panel-body form + form {
  margin-top: 14px;
}
/* A warning about the whole panel, between its heading and its contents. Not a
   .panel-body: nothing follows it inside a box of its own, so it carries only
   the gap down to the table under it. */
.panel-note {
  padding: 12px 16px 0;
}
/* A panel whose body is nothing but links. */
.env-links {
  padding: 12px 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Prose closing out a panel: a footnote to the controls above it, so it steps
   down from the body text and hangs off whatever it follows. These were eight
   copies of the same two properties across five templates. */
.panel-body .help,
.panel-foot .help {
  margin: 10px 0 0;
  font-size: 12px;
}
/* Nothing above it to space away from. */
.panel-body > .help:first-child,
.panel-foot > .help:first-child {
  margin-top: 0;
}
/* A second note continues the first, so it sits nearer to it than it does to a
   form. */
.panel-body .help + .help,
.panel-foot .help + .help {
  margin-top: 8px;
}
/* Prose that introduces the control under it rather than footnoting the one
   above it, so it owns the gap down to that control. Sized by the register it
   is written in: .prose states the case, .help qualifies it. Ordered after the
   footnote rules above, which it has to beat at equal specificity. */
.prose.lead {
  margin: 0 0 14px;
  font-size: 13px;
}
.help.lead {
  margin: 0 0 12px;
  font-size: 12px;
}

.table-scroll {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
}
thead th {
  text-align: left;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 9px 16px;
  border-bottom: 1px solid var(--line-soft);
  white-space: nowrap;
}
tbody td {
  padding: 8px 16px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
tbody tr:last-child td {
  border-bottom: 0;
}
tbody tr:hover {
  background: var(--surface-2);
}

td.key {
  font-weight: 600;
  white-space: nowrap;
}
td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.dim {
  color: var(--text-faint);
}

/* Values are pre-wrapped: a value's own whitespace is part of it. Breaking uses
   `anywhere` rather than `break-all` so long unbroken strings (a URL, a key) wrap
   only when they have to, instead of one character per line in a narrow cell. */
.value {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  display: inline-block;
  max-width: 44ch;
  vertical-align: top;
}
.value.masked {
  color: var(--text-faint);
  letter-spacing: 0.12em;
}
.value.empty {
  color: var(--text-faint);
  font-style: italic;
}


/* ---------- badges ---------- */

.badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  border: 1px solid transparent;
  white-space: nowrap;
}
/* A badge that trails text on the same line, rather than standing on its own. */
.badge-inline {
  margin-left: 6px;
}
/* The mirror of .badge-inline: that spaces a badge off text, this spaces the
   sentence explaining a badge off the badge. */
.badge-note {
  margin-left: 6px;
  font-size: 11.5px;
}
/* A person's grants, as a row of chips. Revoking one is a POST, so that chip is
   wrapped in a form which has to lay out as though it were not there. */
.grants form {
  display: inline;
}
.grants .badge {
  margin-right: 5px;
}
.badge-secret {
  color: var(--accent);
  border-color: var(--accent-dim);
}
.badge-suppressed {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
.badge-drift {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
}
.badge-ok {
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 40%, transparent);
}
.badge-stale {
  color: var(--text-faint);
  border-color: var(--line);
}
.badge-protected {
  color: var(--scope-environment);
  border-color: color-mix(in srgb, var(--scope-environment) 45%, transparent);
}
/* An end-to-end value reads as a stronger claim than "secret", so it gets a
   filled badge rather than an outlined one: this is the only kind of value on
   the page that the server itself cannot open. */
.badge-sealed {
  color: var(--bg);
  background: var(--sealed);
  border-color: var(--sealed);
  font-weight: 600;
}

/* Restricted is a rung below sealed: the server reads it, people do not. So
   it borrows sealed's colour outlined rather than filled. */
.badge-restricted {
  color: var(--sealed);
  border-color: var(--sealed);
}
.badge-type {
  color: var(--text-faint);
  border-color: var(--line);
}

/* ---------- end-to-end values ---------- */

.sealed-cell {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* A warning that cannot be acted on from this page still has to be readable as
   a warning, so it keeps the rule and the colour without pretending to be a
   control. */
.warn-line {
  font-family: var(--sans);
  font-size: 12px;
  color: var(--warn);
  border-left: 2px solid color-mix(in srgb, var(--warn) 50%, transparent);
  padding-left: 8px;
  line-height: 1.5;
}
.warn-line .badge {
  margin-right: 4px;
}
/* A warning that has to be read before the thing under it is used keeps its
   distance from it. */
.warn-line + form {
  margin-top: 14px;
}
.warn-line + .actions {
  margin-top: 10px;
}


/* ---------- controls ---------- */

button,
.btn {
  font-family: var(--mono);
  font-size: 12.5px;
  padding: 5px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  transition: background 100ms ease, border-color 100ms ease;
}
button:hover,
.btn:hover {
  background: var(--surface-3);
  border-color: var(--accent-dim);
  text-decoration: none;
}
button.primary,
.btn.primary {
  background: var(--accent-dim);
  border-color: var(--accent-dim);
  color: #fff;
}
button.primary:hover,
.btn.primary:hover {
  background: var(--accent);
  border-color: var(--accent);
}
button.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-faint);
  padding: 2px 7px;
}
button.ghost:hover {
  color: var(--text);
  background: var(--surface-3);
  border-color: transparent;
}
button.danger {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
  background: transparent;
}
button.danger:hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-color: var(--danger);
}

/* The revoke control inside a grant badge. It has to sit in the badge without
   growing it, so it drops the button chrome entirely and is sized to the badge's
   own font rather than to the button scale. */
button.linkish {
  all: unset;
  cursor: pointer;
  margin-left: 5px;
  padding: 0 2px;
  color: var(--danger);
  font-size: 12px;
  line-height: 1;
  opacity: 0.65;
}
button.linkish:hover,
button.linkish:focus-visible {
  opacity: 1;
}
button.linkish:focus-visible {
  outline: 1px solid var(--danger);
  outline-offset: 1px;
}

input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
textarea,
select {
  font-family: var(--mono);
  font-size: 13px;
  width: 100%;
  padding: 6px 9px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
}
input:focus,
textarea:focus,
select:focus {
  border-color: var(--accent-dim);
  outline: none;
}
textarea {
  resize: vertical;
  min-height: 62px;
  white-space: pre;
}

/* The file input is the one control the browser draws itself. It cannot be
   restyled wholesale, so the wrapper is given the same frame as every other
   field and only the button part is repainted — a half-styled native widget
   reads as broken, while an unstyled one on a dark surface is unreadable.

   Capped rather than stretched: a file control is only ever as long as the name
   of one file, and run the width of a panel it reads as an empty bar. The cap
   belongs to the control, not to a class on the field wrapping it — there is no
   width at which a file input should fill a row. */
input[type="file"] {
  font-family: var(--mono);
  font-size: 12px;
  width: 100%;
  max-width: 340px;
  padding: 5px 9px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
}
input[type="file"]::file-selector-button {
  font-family: var(--mono);
  font-size: 12px;
  margin-right: 10px;
  padding: 4px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover {
  border-color: var(--accent-dim);
  color: var(--accent);
}
label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 4px;
}
.field {
  margin-bottom: 13px;
}
.check {
  display: flex;
  align-items: center;
  gap: 7px;
}
.check input {
  width: auto;
}
.check label {
  margin: 0;
  text-transform: none;
  letter-spacing: 0;
  font-size: 12.5px;
  color: var(--text-dim);
}

.form-row {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  flex-wrap: wrap;
}
.form-row .field {
  flex: 1 1 180px;
  margin-bottom: 0;
}
/* Fields whose answer has a known length, so they take a basis that fits it and
   stop rather than splitting the row evenly with the open-ended field beside
   them. Three sizes, deliberately — one rule per field meant the stylesheet
   knew what a "role" or a "confirm" was, and adding a form meant editing CSS
   to name its fields here. Ordered after the rule above, which they have to
   beat at equal specificity. */
.form-row .field-sm {
  flex: 0 1 140px;
}
.form-row .field-md {
  flex: 0 1 200px;
}
.form-row .field-lg {
  flex: 0 1 260px;
}
/* Controls that live inside a dense table row rather than in a form of their
   own. They size to their content so a rotation interval does not push the
   column out to the width of a URL field. */
.form-row .tiny {
  /* Wide enough for the largest interval the server accepts (3650) without the
     spinner clipping the last digit. */
  width: 7ch;
  padding: 4px 6px;
  text-align: right;
}
.form-row select {
  width: auto;
  padding: 4px 6px;
  font-size: 12px;
}
table .form-row {
  gap: 5px;
  flex-wrap: nowrap;
  align-items: center;
}

.actions {
  display: flex;
  gap: 7px;
  align-items: center;
  flex-wrap: wrap;
}

/* ---------- messages ---------- */

.flash {
  padding: 9px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  margin-bottom: 18px;
  font-family: var(--sans);
}
/* A command a flash tells you to run gets its own line, so it can be selected
   whole without catching the sentence around it. */
.flash code {
  display: block;
  margin-top: 6px;
}
.flash-error {
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--danger);
}
.flash-ok {
  border-color: color-mix(in srgb, var(--ok) 40%, transparent);
  background: color-mix(in srgb, var(--ok) 9%, transparent);
  color: var(--ok);
}
.flash-warn {
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 9%, transparent);
  color: var(--warn);
}

.empty {
  padding: 34px 20px;
  text-align: center;
}
.empty p {
  margin: 0 auto;
  max-width: 46ch;
}
.empty .actions {
  justify-content: center;
  margin-top: 14px;
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  .shell {
    grid-template-columns: 1fr;
  }
  .sidebar {
    position: static;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
  .main {
    padding: 20px 16px 60px;
  }
}

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