/* AllowID — the brand layer.
   ==========================================================================
   SOURCE OF TRUTH. This file lives at Global/shared/brand/brand.css and is
   COPIED into each portal's app/static/brand/ by sync.sh. Never edit a copy:
   the next sync overwrites it. Edit here, run sync.sh, commit all five.

   Why a copy and not a symlink or a package: each portal is its own Docker
   build context and its Dockerfile does `COPY app ./app` and nothing else. A
   file outside app/ is a file that is not in the image. Vendoring is what
   makes the deployment story stay boring.

   What is in here: the palette, the type, and the primitives every AllowID
   surface shares — wordmark, buttons, tags, banners, fields, focus. What is
   NOT in here: layout and density. Those belong to the portal, because a
   staff console read during an incident and a page a member of the public
   reads about their own life are not the same shape.

   Full rationale, contrast maths and usage rules: Global/docs/BRAND.md
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Typeface

   Inter, self-hosted. The four portals run `default-src 'none'` and make no
   external request of any kind, so a Google Fonts link is not an option and a
   CDN is not an option. The file is the latin subset of Inter Variable — one
   48 KB request covering every weight from 100 to 900, so asking for 650 or
   450 costs nothing extra.

   `font-display: swap` on purpose: the fallback stack below is metrically
   close enough that the reflow is small, and a page that renders nothing for
   300 ms is worse than a page that renders in the system face and settles.

   The CSP needs `font-src 'self'` for this to load at all. All four portals
   have it; a new surface that forgets it gets the fallback stack silently,
   which is the failure mode you want but is easy to miss — check the network
   panel, not your eyes.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("./fonts/inter-variable-latin.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+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
                 U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   2. Palette

   The five brand colours, verbatim from the guide. These never change and are
   never used directly by a portal stylesheet — they exist so the semantic
   tokens below have something to be defined in terms of, and so a designer
   reading this file can find the hex they were given.
   -------------------------------------------------------------------------- */

:root {
  --brand-navy:  #0D1B2A;   /* Deep Navy    */
  --brand-blue:  #1565FF;   /* Primary Blue */
  --brand-cyan:  #00C2FF;   /* Accent Cyan  */
  --brand-gray:  #F2F5F9;   /* Light Gray   */
  --brand-white: #FFFFFF;   /* White        */

  /* Decoration only. Never behind text — see BRAND.md §4. White on the cyan
     end of this ramp is 2.1:1, which is not a contrast ratio, it is a rumour. */
  --brand-gradient: linear-gradient(90deg, var(--brand-blue), var(--brand-cyan));

  /* Shape. The mark is a rounded square; everything else echoes it. */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;

  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;

  /* Density. Each portal overrides this one number; everything sized in `em`
     follows it. See BRAND.md §3 — compact 13.5px, standard 15px, comfortable
     17px. */
  --type-base: 15px;
}

/* --------------------------------------------------------------------------
   3. Semantic tokens — light

   The default. A surface declares nothing and gets this.

   Ink is Deep Navy, not black: the brand has a black-ish and it is #0D1B2A,
   and text set in it against Light Gray is 15.6:1, which leaves room to go
   quieter for secondary copy and still clear AA.
   -------------------------------------------------------------------------- */

:root,
:root[data-theme="light"] {
  color-scheme: light;

  --bg:          var(--brand-gray);
  --surface:     var(--brand-white);
  --surface-2:   #E9EEF5;
  --line:        #D6DEE8;
  --line-strong: #BFCBD9;

  --ink:         var(--brand-navy);
  --ink-muted:   #55637A;   /* 5.3:1 on white — AA for body text */
  --ink-faint:   #71809A;   /* 3.9:1 — labels and 14px+ only, never body */

  --accent:       var(--brand-blue);
  --accent-hover: #0D4FD6;
  --accent-ink:   var(--brand-white);   /* 4.8:1 on Primary Blue — AA */
  --accent-soft:  #E8F0FF;
  --accent-line:  #BFD4FF;

  /* Primary Blue AS TEXT, one step darker. This is not a second brand colour,
     it is the same colour corrected for a job it cannot quite do: #1565FF is
     4.43:1 on the Light Gray ground and 4.23:1 on the soft blue one, both of
     which fail AA for body text by a hair. #0D4FD6 is 6.2:1 and 5.9:1.

     So: --accent paints things (buttons, bars, borders, the focus ring, chart
     bars), --accent-text writes things (links, info tags, the heading of an
     info banner, the "ID" in the wordmark). On dark the distinction
     collapses — Accent Cyan is 8.4:1 as text — and the two are the same
     token. */
  --accent-text:  #0D4FD6;

  --ok:   #0E7C57;  --ok-bg:   #E4F3EC;  --ok-line:   #B4DCC8;
  --warn: #8A5B00;  --warn-bg: #FDF3E0;  --warn-line: #E8D3A4;
  --bad:  #B3261E;  --bad-bg:  #FDECEA;  --bad-line:  #F2C2BE;
  --crit: #7A1710;

  --focus: var(--brand-blue);

  /* The "ID" in the wordmark. Primary Blue per the guide — but as the text
     form of it, because that is what this is: two letters somebody reads. On
     a white header Primary Blue itself clears AA (4.84:1); in the footer of
     the user portal the same lockup sits on the Light Gray ground where it is
     4.43:1 and does not. One token that is right in both places beats two
     that are each right in one. */
  --wordmark-accent: var(--accent-text);

  --shadow-card: 0 1px 2px rgba(13, 27, 42, .05);
  --shadow-pop:  0 8px 24px rgba(13, 27, 42, .12);
}

/* --------------------------------------------------------------------------
   4. Semantic tokens — dark

   Deep Navy ground. Note the accent flips from Primary Blue to Accent Cyan:
   #1565FF on #0D1B2A is 2.7:1 and unreadable as text. Cyan is 8.1:1. This is
   the single most important line in the file — a dark surface that reaches
   for --brand-blue directly is a bug, which is why portals use --accent.
   -------------------------------------------------------------------------- */

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:          var(--brand-navy);
  --surface:     #132437;
  --surface-2:   #1B3047;
  --line:        #24394F;
  --line-strong: #33506D;

  --ink:         var(--brand-gray);
  --ink-muted:   #9FB0C4;   /* 8.0:1 on navy */
  --ink-faint:   #7D8FA3;   /* 4.8:1 — secondary only */

  --accent:       var(--brand-cyan);
  --accent-hover: #4DD4FF;
  --accent-ink:   #04202D;   /* navy-black on cyan, 8.1:1 */
  --accent-soft:  rgba(0, 194, 255, .14);
  --accent-line:  rgba(0, 194, 255, .35);
  --accent-text:  var(--accent);   /* cyan is 8.4:1 on navy — no correction needed */

  --ok:   #3DD68C;  --ok-bg:   rgba(61, 214, 140, .13); --ok-line:   rgba(61, 214, 140, .34);
  --warn: #F5A524;  --warn-bg: rgba(245, 165, 36, .13); --warn-line: rgba(245, 165, 36, .34);
  --bad:  #FF7A7F;  --bad-bg:  rgba(255, 122, 127, .13); --bad-line: rgba(255, 122, 127, .34);
  --crit: #FF4D4D;

  --focus: var(--brand-cyan);

  --wordmark-accent: var(--brand-cyan);

  --shadow-card: none;
  --shadow-pop:  0 10px 30px rgba(0, 0, 0, .45);
}

/* --------------------------------------------------------------------------
   5. Base
   -------------------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--type-base);
  line-height: 1.55;
  /* Inter's defaults leave capitals slightly loose at UI sizes. */
  font-feature-settings: "cv05" 1, "cv08" 1, "tnum" 0;
  -webkit-font-smoothing: antialiased;
}

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

/* One focus ring, everywhere, visible on both grounds. Never remove it —
   guide 9 §4 names keyboard and screen-reader use explicitly, and the staff
   portal is operated by people who do not touch the mouse. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

code, .mono { font-family: var(--font-mono); }

/* Tabular figures wherever numbers are compared down a column. */
.n, td.n, th.n, .stat .n, table.figures td { font-variant-numeric: tabular-nums; }

hr.rule { border: 0; border-top: 1px solid var(--line); margin: 1.5rem 0; }

.muted { color: var(--ink-muted); }
.scroll { overflow-x: auto; }
.hidden { display: none !important; }

/* --------------------------------------------------------------------------
   6. The mark and the wordmark

   Both, together, in that order — that is the lockup. `.brandmark` is the
   whole thing; drop it into a header and it is correct.

   The wordmark is set in the page's own text colour with "ID" in the accent,
   never in an image, so it inherits the theme, scales with zoom, and can be
   selected and read aloud.
   -------------------------------------------------------------------------- */

.brandmark {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}
.brandmark:hover { text-decoration: none; }

.brandmark img,
.brandmark svg {
  /* 1.8em keeps the mark in proportion to the wordmark at every density, and
     honours the guide's clear-space rule without a magic pixel value. */
  width: 1.8em;
  height: 1.8em;
  display: block;
  flex: none;
}

.wordmark {
  font-weight: 700;
  font-size: 1.15em;
  letter-spacing: -.018em;
  color: var(--ink);
}
.wordmark .id { color: var(--wordmark-accent); }

/* The surface's own name, sitting under or beside the wordmark. Quiet: the
   brand is the constant, this is the variable. */
.brandmark .surface-name {
  color: var(--ink-muted);
  font-weight: 400;
  font-size: .82em;
  letter-spacing: .01em;
}

/* --------------------------------------------------------------------------
   7. Buttons

   Primary is a SOLID accent, not the gradient. The gradient's cyan end cannot
   carry white text, and a button whose label is legible at one end and not
   the other is not a button. The gradient earns its keep as a 2px rule on the
   header and on focused cards — see .gradient-rule below.
   -------------------------------------------------------------------------- */

button, .button {
  font-family: inherit;
  font-size: 1em;
  font-weight: 600;
  line-height: 1.2;
  background: var(--accent);
  color: var(--accent-ink);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: .6em 1.05em;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background .14s ease, border-color .14s ease;
}
button:hover, .button:hover { background: var(--accent-hover); text-decoration: none; }

button.quiet, .button.quiet {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line-strong);
  font-weight: 500;
}
button.quiet:hover, .button.quiet:hover {
  background: var(--surface-2);
  border-color: var(--ink-faint);
}

button.danger, .button.danger, button.stop, .button.stop {
  background: var(--bad);
  color: var(--brand-white);
}
:root[data-theme="dark"] button.danger,
:root[data-theme="dark"] button.stop { color: #2A0507; }
button.danger:hover, button.stop:hover { filter: brightness(1.08); }

button[disabled], .button[disabled] { opacity: .45; cursor: not-allowed; }

.actions { display: flex; gap: .7rem; margin-top: 1rem; flex-wrap: wrap; align-items: center; }

/* --------------------------------------------------------------------------
   8. Fields
   -------------------------------------------------------------------------- */

input[type=text], input[type=email], input[type=password], input[type=tel],
input[type=number], input[type=search], select, textarea {
  font-family: inherit;
  font-size: 1em;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  padding: .5em .65em;
}
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
textarea { width: 100%; }

label {
  display: block;
  font-weight: 600;
  color: var(--ink-muted);
  margin: .8em 0 .25em;
}
label .hint {
  display: block;
  font-weight: 400;
  color: var(--ink-muted);
  margin-top: .1em;
}

form.inline { display: flex; gap: .6rem; align-items: flex-end; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   9. Tags and states

   Colour is never the only signal. Every one of these carries a word — that
   is a content rule, not a CSS rule, but it is written here because this is
   the file people copy from. See BRAND.md §6.
   -------------------------------------------------------------------------- */

.tag {
  display: inline-block;
  padding: .12em .5em;
  border-radius: var(--r-sm);
  font-size: .82em;
  font-weight: 500;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-muted);
  white-space: nowrap;
}
.tag.ok   { color: var(--ok);     background: var(--ok-bg);     border-color: var(--ok-line); }
.tag.warn { color: var(--warn);   background: var(--warn-bg);   border-color: var(--warn-line); }
.tag.bad  { color: var(--bad);    background: var(--bad-bg);    border-color: var(--bad-line); }
.tag.info { color: var(--accent-text); background: var(--accent-soft); border-color: var(--accent-line); }
.tag.crit { color: var(--brand-white); background: var(--crit); border-color: var(--crit); }

/* The pill form, for a status somebody reads rather than scans. */
.state {
  display: inline-block;
  padding: .2em .7em;
  border-radius: var(--r-pill);
  font-size: .88em;
  font-weight: 600;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-muted);
}
.state.on   { color: var(--ok);   background: var(--ok-bg);   border-color: var(--ok-line); }
.state.off  { color: var(--ink-muted); background: var(--surface-2); border-color: var(--line); }
.state.warn { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-line); }
.state.stop { color: var(--bad);  background: var(--bad-bg);  border-color: var(--bad-line); }

/* -------------------------------------------------------------------------
   10. Banners and notices

   Same thing at two densities: .banner for the working surfaces, .notice for
   the public one. Both take .ok / .warn / .bad — and .notice also takes
   .stop, which is what the user portal calls .bad, because "stop" is the word
   a person reads and "bad" is the word a developer types.

   `> b` and not `b`: these blocks lead with a bold heading, and the sentences
   underneath emphasise a name or a number inline. A bare descendant rule puts
   every one of those on its own line and shatters the sentence around it.
   ------------------------------------------------------------------------- */

.banner, .notice {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: .85em 1.05em;
  margin-bottom: 1.1rem;
  background: var(--surface-2);
}
.banner > b, .notice > b { display: block; margin-bottom: .25em; }

.banner.ok,   .notice.ok   { background: var(--ok-bg);     border-color: var(--ok-line); }
.banner.warn, .notice.warn { background: var(--warn-bg);   border-color: var(--warn-line); }
.banner.bad,  .notice.bad,
.notice.stop               { background: var(--bad-bg);    border-color: var(--bad-line); }
.banner.info, .notice.info { background: var(--accent-soft); border-color: var(--accent-line); }

/* On light, the tinted grounds are pale enough that body ink stays legible
   and the heading takes the semantic colour. On dark the tints are 13% alpha
   over navy, so ordinary ink is right and only the heading shifts. */
.banner.ok   > b, .notice.ok   > b { color: var(--ok); }
.banner.warn > b, .notice.warn > b { color: var(--warn); }
.banner.bad  > b, .notice.bad  > b,
.notice.stop > b                   { color: var(--bad); }
.banner.info > b, .notice.info > b { color: var(--accent-text); }

/* A pointer at something that lives on another AllowID surface. */
.elsewhere {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  padding: .75em .95em;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  margin: 1rem 0;
}

/* --------------------------------------------------------------------------
   11. Surfaces

   .panel is the working-surface container, .card the public one. They differ
   only in radius and padding, which the portals set.
   -------------------------------------------------------------------------- */

.panel, .card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
}
.card h2:first-child, .card h3:first-child,
.panel h2:first-child, .panel h3:first-child { margin-top: 0; }
.card p:last-child, .panel p:last-child { margin-bottom: 0; }

.grid { display: grid; gap: 1rem; }

/* The one place the gradient belongs: a hairline of brand across the top of a
   header or a hero. No text sits on it, so its contrast is nobody's problem. */
.gradient-rule { height: 2px; background: var(--brand-gradient); border: 0; margin: 0; }

/* --------------------------------------------------------------------------
   12. Tables
   -------------------------------------------------------------------------- */

table { width: 100%; border-collapse: collapse; }
th {
  text-align: left;
  font-weight: 600;
  font-size: .78em;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-muted);
  padding: .5em .65em;
  border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
}
td { padding: .55em .65em; border-bottom: 1px solid var(--line); vertical-align: top; }
tr:last-child td { border-bottom: none; }
td.n, th.n { text-align: right; font-family: var(--font-mono); }

/* --------------------------------------------------------------------------
   13. Odds and ends every surface turned out to need
   -------------------------------------------------------------------------- */

/* A QR code is always ink-on-white, whatever the theme. A cyan QR on navy is
   a QR that half the scanners in the world will refuse. */
.qr {
  background: var(--brand-white);
  padding: .7rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  width: 220px;
}
.qr svg, .qr img { width: 100%; height: auto; display: block; }

pre.body {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: .85em;
  white-space: pre-wrap;
  font-family: var(--font-mono);
  font-size: .88em;
  line-height: 1.5;
  overflow-x: auto;
}

.skip { position: absolute; left: -9999px; }
.skip:focus {
  position: fixed; left: 1rem; top: 1rem;
  background: var(--surface); color: var(--ink);
  padding: .6rem 1rem; border: 2px solid var(--accent);
  border-radius: var(--r-sm); z-index: 100;
}

/* Server-rendered SVG charts pick their colours up from CSS, so a chart is
   themed by the same tokens as everything around it. */
.chart { width: 100%; height: auto; }
.chart .axis { stroke: var(--line-strong); }
.chart .tick, .chart .bvalue { fill: var(--ink-muted); font: .72em var(--font-mono); }
.chart .blabel { fill: var(--ink); font-size: .78em; }
.chart .bar { fill: var(--accent); }

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