/* Portal stylesheet.
 *
 * This lives in an EXTERNAL file, not an inline <style>, and that is the whole
 * point. The portal serves `Content-Security-Policy: default-src 'self'`, which
 * blocks an inline <style> outright while permitting a same-origin stylesheet.
 * It was previously inlined to avoid CSP trouble, which is the exact inverse of
 * what the policy allows -- the portal rendered completely unstyled from
 * 2026-08-16 until this was found on 2026-08-19.
 *
 * How to check it is really applied, because the markup looks identical either
 * way and grepping the served HTML proves nothing:
 *   document.styleSheets.length > 0 && getComputedStyle(document.body).fontFamily
 * Times New Roman means it is blocked again.
 *
 * The design system is Kobus's package on feat/ux-ui-updates: design/DESIGN.md
 * holds every token below with its contrast ratio, and design/<page>/ holds the
 * mockup each screen is built to. Change a token there first, then here.
 */

/* Inter Tight, the CodeInfinity typeface. Self-hosted because `default-src
   'self'` forbids Google Fonts. One variable file covers 400 to 700, latin. */
@font-face {
  font-family: "Inter Tight"; font-style: normal; font-weight: 400 700;
  src: url("/fonts/inter-tight-latin.woff2") format("woff2");
  font-display: swap;
}

/* Tokens. Light is the default. Dark is set twice with the same values: once
   for the OS preference unless the viewer chose light with the toggle, and once
   for an explicit choice of dark (public/js/shell.js sets data-theme).

   Brand cyan #1dc7de is the primary-button FILL, with dark ink on it (8.70:1).
   It is never text or a thin edge on light (2.04:1 on white). Links and
   secondary buttons use the teal --accent, which passes. On dark the cyan
   passes as text, so --accent becomes the cyan there.

   Health colours have two tones: the base for text and outlines (AA on its
   ground), and a vivid -solid for fills that carry no text (dots, meters).

   --card, --ink, --muted and --line are the old names, kept as aliases so the
   rules further down that still use them follow the new palette. */
:root {
  --bg: #f6f7f9; --surface: #ffffff; --surface-2: #eef1f5;
  --border: #e3e7ec; --border-strong: #c9d1da;
  --text-1: #16181b; --text-2: #5b6672; --text-3: #6b7480;
  --brand: #1dc7de; --brand-hover: #16b2cf; --brand-active: #109db8; --brand-ink: #16181b;
  --accent: #0e6d80; --accent-hover: #0a5361; --accent-active: #073e49;
  --accent-tint: #e7f0f2; --accent-ink: #ffffff; --hover-tint: rgba(14, 109, 128, .06);
  --role-viewer: #4a4979;
  --ok: #217a3c; --ok-solid: #22c55e; --ok-tint: #e9f9ef;
  --warn: #c2410c; --warn-solid: #f97316; --warn-tint: #fff3e2;
  --error: #dc2626; --error-solid: #ef4444; --error-tint: #ffe9e9; --error-ink: #ffffff;
  --error-hover: #b41f28;
  --unknown: #6b7480; --unknown-solid: #8b95a1; --unknown-tint: #eef0f2;
  --info: #2a5c8f; --info-tint: #e9eef4;
  --radius: 12px; --radius-sm: 4px;
  --shadow-md: 0 4px 14px rgba(22, 24, 27, .12);
  --focus-ring: 0 0 0 3px rgba(14, 109, 128, .28), 0 0 0 1px #0e6d80;
  --disabled-opacity: .45;
  --duration-fast: 100ms; --duration-base: 200ms; --ease-out: cubic-bezier(.2, 0, 0, 1);
  --icon-xs: 14px; --icon-sm: 16px; --icon-md: 20px; --icon-lg: 24px; --icon-xl: 32px;
  --z-sticky: 200; --z-header: 300; --z-tooltip: 800;
  --font: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --card: var(--surface); --ink: var(--text-1); --muted: var(--text-2); --line: var(--border);
  --danger: var(--error); --code-bg: var(--surface-2);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f1215; --surface: #16181b; --surface-2: #1d2126;
    --border: #2a3038; --border-strong: #3a424c;
    --text-1: #f3f5f7; --text-2: #aab3bd; --text-3: #8391a0;
    --accent: #1dc7de; --accent-hover: #5cbfd0; --accent-active: #16b2cf;
    --accent-tint: rgba(29, 199, 222, .14); --accent-ink: #16181b; --hover-tint: rgba(29, 199, 222, .08);
    --role-viewer: #9b9ad0;
    --ok: #4ade80; --ok-solid: #22c55e; --ok-tint: #203629;
    --warn: #fb923c; --warn-solid: #f97316; --warn-tint: #3a311e;
    --error: #f87171; --error-solid: #ef4444; --error-tint: #3d2827; --error-ink: #16181b;
    --error-hover: #fca5a5;
    --unknown: #8391a0; --unknown-solid: #8391a0; --unknown-tint: #2a2e33;
    --info: #6fa3dd; --info-tint: #26313e;
    --shadow-md: 0 4px 14px rgba(0, 0, 0, .5);
    --focus-ring: 0 0 0 3px rgba(29, 199, 222, .35), 0 0 0 1px #1dc7de;
  }
}
:root[data-theme="dark"] {
  --bg: #0f1215; --surface: #16181b; --surface-2: #1d2126;
  --border: #2a3038; --border-strong: #3a424c;
  --text-1: #f3f5f7; --text-2: #aab3bd; --text-3: #8391a0;
  --accent: #1dc7de; --accent-hover: #5cbfd0; --accent-active: #16b2cf;
  --accent-tint: rgba(29, 199, 222, .14); --accent-ink: #16181b; --hover-tint: rgba(29, 199, 222, .08);
  --role-viewer: #9b9ad0;
  --ok: #4ade80; --ok-solid: #22c55e; --ok-tint: #203629;
  --warn: #fb923c; --warn-solid: #f97316; --warn-tint: #3a311e;
  --error: #f87171; --error-solid: #ef4444; --error-tint: #3d2827; --error-ink: #16181b;
  --error-hover: #fca5a5;
  --unknown: #8391a0; --unknown-solid: #8391a0; --unknown-tint: #2a2e33;
  --info: #6fa3dd; --info-tint: #26313e;
  --shadow-md: 0 4px 14px rgba(0, 0, 0, .5);
  --focus-ring: 0 0 0 3px rgba(29, 199, 222, .35), 0 0 0 1px #1dc7de;
}
:root { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { color-scheme: dark; } }
@media (prefers-reduced-motion: reduce) { :root { --duration-fast: 0ms; --duration-base: 0ms; } }

/* The logo comes in two inks: dark on light, white on dark. Same three-state
   rule as the tokens. */
.logo-light { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-dark { display: none; }
  :root:not([data-theme="light"]) .logo-light { display: block; }
}
:root[data-theme="dark"] .logo-dark { display: none; }
:root[data-theme="dark"] .logo-light { display: block; }

* , *::before, *::after { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--text-1);
  font: 400 16px/1.55 var(--font);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); }
a:hover { color: var(--accent-hover); }
:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 6px; }
h1, h2, h3 { text-wrap: balance; }
code, .mono { font-family: var(--mono); font-size: 14px; }
.skip {
  position: absolute; left: -9999px; top: 8px; z-index: 900;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 8px 14px; color: var(--text-1); text-decoration: none;
}
.skip:focus { left: 16px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.sprite { display: none; }

/* Icons: Tabler outline, 2px stroke, inline SVG from the sprite in layout.html
   (<svg class="i"><use href="#t-name"/></svg>). One library, one weight. */
.i {
  width: var(--icon-md); height: var(--icon-md); flex: none;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.i.xs { width: var(--icon-xs); height: var(--icon-xs); }
.i.sm { width: var(--icon-sm); height: var(--icon-sm); }
.i.lg { width: var(--icon-lg); height: var(--icon-lg); }

/* Shell. The lockup sits in the header at 40px tall, below the 120px floor
   measured for the full logo. Kobus accepted that for the header only
   (DESIGN.md, Logo rule 3): "CODE∞" stays clear, the tagline reads as texture. */
header {
  background: var(--surface); border-bottom: 1px solid var(--border);
  box-shadow: inset 0 -3px 0 -2px var(--brand);
  padding: 14px 24px; display: flex; align-items: center; gap: 20px;
  position: sticky; top: 0; z-index: var(--z-header);
}
header .brand { display: flex; align-items: center; text-decoration: none; }
header .brand img { height: 40px; width: auto; }
header nav { margin-left: auto; display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
header nav a { color: var(--text-2); text-decoration: none; font-size: 14px; }
header nav a:hover { color: var(--text-1); }
header nav a[aria-current="page"] { color: var(--text-1); font-weight: 600; }
.nav-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 6px; margin-left: 6px; border-radius: 999px;
  background: var(--error); color: var(--error-ink); vertical-align: middle;
  font: 700 12px/1 var(--font); font-variant-numeric: tabular-nums;
}
.nav-toggle {
  display: none; position: relative; width: 40px; height: 40px; border-radius: 999px;
  background: transparent; border: 1px solid var(--border); color: var(--text-1);
  align-items: center; justify-content: center; cursor: pointer;
}
.nav-toggle:hover { border-color: var(--border-strong); }
.nav-toggle .dot {
  position: absolute; top: 6px; right: 6px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--error); display: none;
}
.theme-toggle .i-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .i-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .i-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .i-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .i-moon { display: none; }
}

/* 1100px on every page (Kobus, issue #78): at 1040 rows wrapped for want of
   room. `wide` is kept on the templates that set it, and now changes nothing. */
main { max-width: 1100px; margin: 0 auto; padding: 32px 24px 64px; }
main.wide { max-width: 1100px; }
h1 { font-size: 28px; font-weight: 600; letter-spacing: -.01em; margin: 0 0 6px; }
h2 { font-size: 18px; font-weight: 600; margin: 32px 0 12px; }
.sub { color: var(--text-2); margin: 0 0 24px; max-width: 70ch; }
.sub .mono { font-size: .9em; }
.meta { color: var(--text-3); font-size: 14px; }
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); min-width: 0;
  padding: 18px 20px; margin-bottom: 14px;
}
.card h3 { margin: 0 0 4px; font-size: 17px; font-weight: 600; }
.card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.card-head .i { color: var(--text-2); }
.card-head h3 { margin: 0; font-size: 18px; }
.card-head .count { color: var(--text-3); font-size: 14px; }
.card-sub { color: var(--text-3); font-size: 14px; margin: -4px 0 12px; }
.right { margin-left: auto; }
.group { margin: 0 0 22px; }
.eyebrow {
  display: block; margin: 0 0 8px; color: var(--text-2);
  font-size: 13px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
}
/* A card that is not ok takes its state's tint and a 1px border in the state
   colour. No coloured left stripe anywhere (DESIGN.md, 18 September). Small
   text on a tint steps up to --text-2: --text-3 fails on every tint. */
.card.is-warn { background: var(--warn-tint); border-color: var(--warn); }
.card.is-error { background: var(--error-tint); border-color: var(--error); }
.card.is-unknown { background: var(--unknown-tint); border-color: var(--unknown); }
.card.is-info { background: var(--info-tint); border-color: var(--info); }
.card[class*="is-"] .meta, .card[class*="is-"] .note { color: var(--text-2); }

/* Summary strip: the page's one-line "what you hold". */
.summary { display: flex; align-items: center; gap: 14px 18px; flex-wrap: wrap; }
.summary .headline { font-size: 18px; font-weight: 600; margin: 0; letter-spacing: -.01em; flex: 1 1 auto; }
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font-size: 13px; font-weight: 600; padding: 3px 10px 3px 8px; border-radius: 999px;
  border: 1px solid var(--accent); color: var(--accent); background: var(--surface);
}
.chip .n { font-variant-numeric: tabular-nums; }
.chip .i { width: 14px; height: 14px; }

/* Role pills: dot + word. Admin accent, developer/contributor green, viewer
   and read-only indigo -- told apart by colour AND by the word. */
.role {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; vertical-align: 2px;
  font-size: 13px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 9px 2px 8px; border-radius: 999px; margin-left: 8px;
  border: 1px solid var(--border); color: var(--text-2); background: var(--surface);
}
.role .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: currentColor; }
.role-admin, .role.admin { color: var(--accent); border-color: var(--accent); }
.role-developer, .role.developer, .role.contributor { color: var(--ok); border-color: var(--ok); }
.role-developer .dot, .role.developer .dot, .role.contributor .dot { background: var(--ok-solid); }
.role-viewer, .role.viewer, .role.readonly { color: var(--role-viewer); border-color: var(--role-viewer); }

/* Health status pill: outlined, glyph + word, never colour alone. */
.status-pill {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  font-size: 13px; font-weight: 600; padding: 2px 9px 2px 7px; border-radius: 999px;
  border: 1px solid currentColor; background: var(--surface);
}
.status-pill .i { width: 14px; height: 14px; }
.status-pill.ok { color: var(--ok); }
.status-pill.warn { color: var(--warn); }
.status-pill.error { color: var(--error); }
.status-pill.unknown { color: var(--unknown); }
.status-pill.info { color: var(--info); }

/* External hand-off: "this opens a separate tool". */
.ext { display: inline-flex; align-items: center; gap: 7px; }
.ext .i { width: 15px; height: 15px; color: var(--text-3); }
.ext-note { color: var(--text-3); font-size: 14px; margin: 6px 0 0; display: flex; align-items: flex-start; gap: 6px; }
.ext-note .i { width: 15px; height: 15px; margin-top: 2px; }

/* Search box: the pill field with a magnifier. */
.search { position: relative; }
.search .i {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; color: var(--text-3); pointer-events: none;
}
.search input { width: 100%; padding: 9px 12px 9px 36px; border-radius: 999px; border-color: var(--border-strong); background: var(--surface); }

.ns { color: var(--text-2); font-size: 14px; margin-top: 8px; }
.ns code {
  background: var(--surface-2); border-radius: var(--radius-sm);
  padding: 2px 7px; margin-right: 5px; display: inline-block; font-size: 13px; color: var(--text-2);
}
.desc { color: var(--text-2); font-size: 15px; margin: 6px 0 0; }

/* Buttons. Primary is the brand cyan with dark ink; hover and pressed go to
   the site's own darker cyans, which keep the ink above 5.5:1. Secondary is
   outlined teal. */
.btn, a.btn, button.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 40px; padding: 8px 18px; border-radius: 999px;
  border: 1px solid var(--brand); background: var(--brand); color: var(--brand-ink);
  font: 600 14px/1 var(--font); text-decoration: none; cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.btn:hover, a.btn:hover, button.btn:hover { background: var(--brand-hover); border-color: var(--brand-hover); color: var(--brand-ink); }
.btn:active { background: var(--brand-active); border-color: var(--brand-active); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: var(--disabled-opacity); cursor: not-allowed; }
.btn.secondary, a.btn.secondary, button.btn.secondary { background: transparent; color: var(--accent); border-color: var(--accent); }
.btn.secondary:hover, a.btn.secondary:hover, button.btn.secondary:hover { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }
.btn.icon { padding: 8px; min-width: 40px; }
.btn.sm { min-height: 32px; padding: 5px 12px; font-size: 13px; }
/* An icon inside a button (issue #75) follows the button's text colour and size. */
.btn .i { width: var(--icon-sm); height: var(--icon-sm); color: currentColor; }
pre {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px; overflow-x: auto; font: 13px/1.5 var(--mono);
}
ol li, ul li { margin-bottom: 8px; }
.empty { color: var(--text-2); }
footer { color: var(--text-3); font-size: 14px; margin-top: 36px; max-width: 80ch; }

/* Mobile nav. The full nav needs about 900px, so it collapses to a drawer at
   960px rather than 640px: in between it wrapped onto two lines and collided
   with the logo (found on My access, 21 September). Kept in its own block,
   apart from the 640px content-stacking rules. */
@media (max-width: 960px) {
  header { flex-wrap: wrap; gap: 12px; }
  .nav-toggle { display: inline-flex; margin-left: auto; }
  .nav-toggle.has-urgent .dot { display: block; }
  header nav {
    display: none; order: 3; flex-basis: 100%; margin-left: 0;
    flex-direction: column; align-items: stretch; gap: 0;
  }
  header.nav-open nav { display: flex; }
  header nav a { padding: 13px 2px; border-top: 1px solid var(--border); font-size: 16px; }
  header nav .theme-toggle { align-self: flex-start; margin: 10px 0 0; }
}
@media (max-width: 640px) {
  header { padding: 12px 16px; }
  main { padding: 24px 16px 48px; }
  .summary .headline { flex-basis: 100%; }
}


/* ── Admin screens: People, Databases, Activity ───────────────────────────────
 * These rules lived in inline <style> blocks inside people.html, databases.html
 * and activity.html. Every one of them was blocked by `default-src 'self'`, the
 * same policy that hid this whole stylesheet until 2026-08-19, so none of these
 * screens has ever been styled. Consolidated here because all three want the
 * same table, and because an external sheet is the only kind the policy allows.
 */
table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left; padding: 9px 8px; border-bottom: 1px solid var(--line);
  font-size: 14px; vertical-align: top;
}
tbody tr:hover { background: var(--hover-tint); }
th {
  color: var(--text-2); font-weight: 600; font-size: 13px;
  text-transform: uppercase; letter-spacing: .04em;
}
td.who strong { display: block; }
td.who span { color: var(--text-3); font-size: 14px; }

/* Form controls. Inputs are deliberately styled here rather than per screen --
 * they must live in the STATIC part of an island template, never inside a
 * reactive block, or they lose focus on every keystroke. */
input, select, textarea, .sqlbox {
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
  border-radius: 6px; padding: 8px 10px; font-size: 14px; font-family: inherit;
}
.sqlbox { width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
.grid4 { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin: 12px 0; }

/* Pills. Same three brand colours as the role badges, for the same reason. */
.pill, .env {
  display: inline-block; font-size: 13px; border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 8px; margin: 2px 4px 2px 0; color: var(--muted);
}
.pill.admin { color: var(--accent); border-color: var(--accent); }
.env.production { color: var(--warn); border-color: var(--warn); }
/* 40.1: a person with no email cannot receive an invitation. Filled, so it reads
   as a problem rather than as one more role. */
.pill.warning { color: var(--card); background: var(--warn); border-color: var(--warn); }
/* 40.2: a person's GitHub usernames, one per line with its actions inline. */
.alias-list { list-style: none; margin: 0 0 .5rem; padding: 0; }
.alias-list li { display: flex; align-items: center; gap: .6rem; padding: .2rem 0; }
.btn.link { background: none; border: none; padding: 0; min-height: 0; font-weight: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; font: inherit; }
/* `button.btn:hover` fills the button and outranks `.btn.link` (0,2,1 over 0,2,0), so link
   buttons turned into solid blobs on hover (live on v83). Three classes win. */
.btn.link:hover, .btn.link:focus { background: none; color: var(--accent-hover); }
.pill.developer { color: var(--ok); border-color: var(--ok); }
.pill.viewer { color: var(--role-viewer); border-color: var(--role-viewer); }
/* A repository, not a project: dashed, so the two read apart at a glance. */
.pill.repo { border-style: dashed; }
/* 17 September: two rows of labels, the rest behind a chevron (people-admin.js
   accessCell / fitTwoRows). The chevron only appears when there is more. */
.access-cell { display: flex; align-items: flex-start; gap: 4px; }
.access-pills { flex: 1; min-width: 0; }
.access-pills .beyond { display: none; }
.access-pills .empty { margin-right: 6px; }
/* Two classes: `button.btn { display: inline-block }` outranks one. And the
   chevron always holds its place, only its visibility changes: appearing after
   the labels were measured, it took their width and re-wrapped them onto a third
   row. Both found live on v79. */
.access-cell .pills-toggle { display: inline-flex; visibility: hidden; align-items: center; margin-top: 3px; text-decoration: none; }
.access-cell[data-overflow] .pills-toggle { visibility: visible; }
.pills-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }

/* Two screens used `.note` for opposite things before these were consolidated:
 * Databases meant small muted inline text, People meant a bordered warning box
 * for the one-time password. Merging them boxed every muted note on Databases.
 * `.note` keeps the common meaning; the box is now `.credential`, named for what
 * it actually holds. */
.note { color: var(--text-2); font-size: 14px; }
.credential {
  background: var(--bg); border: 1px solid var(--warn); border-radius: var(--radius);
  padding: 12px 14px; margin-top: 12px;
}
.credential code { font-size: 15px; user-select: all; }
.err { color: var(--error); }
td.op { font-weight: 600; }
td.op.DELETE { color: var(--error); }
td.op.CREATE { color: var(--ok); }
a.dl { color: var(--accent); text-decoration: none; font-weight: 500; }

/* A <pre> inside a table cell sets the table's MIN-CONTENT width, so one long
 * line pushes every column past the card's right edge. That is what the
 * `kubectl port-forward` line on the Gas Zone production row did: the buttons
 * on every other row rendered outside the card, which looks like a button
 * problem and is not.
 *
 * `overflow-x: auto` on the pre is not enough here. A scroll container only
 * shrinks below its content when it has a definite width or min-width: 0, and
 * inside a table cell it has neither. So wrap the text instead -- a command
 * that wraps is more readable in a table than one behind a scrollbar.
 */
td pre {
  margin: 6px 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-width: 100%;
}

/* Belt and braces for the next wide thing someone puts in a cell: the table
 * scrolls inside its card rather than escaping it. */
.card > .table-scroll { overflow-x: auto; }
a.dl:hover { text-decoration: underline; }
.status { color: var(--text-2); font-size: 14px; margin-left: 8px; }

/* Replacements for inline style attributes (11 September 2026).
 *
 * The portal serves `default-src 'self'`, which blocks every inline style
 * attribute: the browser dropped them and logged a console error for each.
 * tests/csp-inline.test.ts now fails on any new one.
 *
 * `!important` is deliberate. An inline style outranks every stylesheet rule,
 * and these classes stand in for exactly those declarations. Without it, a
 * rule such as `.note` or `.card p` could win and the layout would shift from
 * what the markup asked for. Use these only where an inline style would have
 * gone; everything else belongs in a normal rule above. */
.u-m0 { margin: 0 !important; }
.u-mt0 { margin-top: 0 !important; }
.u-mb0 { margin-bottom: 0 !important; }
.u-ml8 { margin-left: 8px !important; }
.u-pull-up-6 { margin-top: -6px !important; }
.u-pull-up-8 { margin-top: -8px !important; }
.u-right { text-align: right !important; }
.field-label { font-size: 14px !important; color: var(--muted) !important; }
.field-select { width: 100% !important; margin-top: 4px !important; }
.subhead { font-size: 15px !important; margin: 16px 0 4px !important; }
.hint-left { margin: 6px 0 0 !important; font-size: 14px !important; text-align: left !important; }

/* 39.1: one block per project a person holds. */
.project-block { border: 1px solid var(--line, rgba(127,127,127,.25)); border-radius: 8px; padding: .75rem 1rem; margin: 0 0 .75rem; }
.project-block .project-name { margin: 0 0 .5rem; font-size: 1rem; }
.project-part { margin-top: .6rem; }
.project-part .part-label { display: block; font-size: .8rem; color: var(--muted); margin-bottom: .25rem; }
.plain-list { list-style: none; margin: 0; padding: 0; }
.plain-list li { padding: .1rem 0; }

/* Card 10304826028: a recorded GitHub username with nothing granted yet. */
.warning-note { color: var(--warn); }

/* A destructive action reads as destructive. Used by Delete on a person
   (18 September); the confirmation beside it is what actually protects. */
/* Infrastructure screen (18 September). One class per block on purpose: the
   markup names its parts so a restyle is a change here and not in the island. */
.infra-section { margin-bottom: 16px; }
.infra-cluster { border-top: 1px solid var(--line, #e3e6ea); padding: 12px 0; }
.infra-cluster:first-of-type { border-top: 0; }
.infra-facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 8px 0 0; }
.infra-facts dt { color: var(--muted, #5b6672); font-size: 0.9em; }
.infra-facts dd { margin: 0; word-break: break-all; }
.infra-needs { margin: 8px 0 0; padding-left: 20px; }
.infra-needs li { margin-bottom: 4px; }
.infra-repo { display: flex; justify-content: space-between; gap: 16px; padding: 6px 0; border-top: 1px solid var(--line, #e3e6ea); }
.infra-repo:first-of-type { border-top: 0; }
.infra-config { background: var(--code-bg, #f5f7f9); padding: 12px; border-radius: 6px; overflow-x: auto; white-space: pre-wrap; }
.infra-input { min-width: 0; }
.infra-pager { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.infra-page-size { margin-left: auto; }
.project-page-size { margin-left: auto; }
.project-filter { margin-bottom: 4px; }

.btn.danger { background: var(--error); border-color: var(--error); color: var(--error-ink); }
.btn.danger:hover { background: var(--error-hover); border-color: var(--error-hover); color: var(--error-ink); }

/* The "i" on each tool block (Chanelle's feedback, 14 September; src/help.ts).
   The summary is drawn by ::after from data-summary: no script, and nothing inline
   for the Content-Security-Policy to block. Hover and keyboard focus both show it;
   a click opens the plain-English page. */
.card { position: relative; }
.card > .info-dot, .card > .info-dots { position: absolute; top: 12px; right: 12px; }
.info-dots { display: inline-flex; gap: 6px; }
/* The Tabler info-circle, bare, in the accent colour (Kobus, issue #77). It was
   a serif italic "i" in a ring, the one icon not drawn from the icon set. */
.info-dot {
  position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 22px; height: 22px; border-radius: 50%; border: 0; background: none;
  color: var(--accent); text-decoration: none; vertical-align: middle;
}
.info-dot .i { width: 18px; height: 18px; }
.info-dot:hover, .info-dot:focus-visible { background: none; color: var(--accent-hover); }
.info-dot:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.info-dot::after {
  content: attr(data-summary); position: absolute; top: 28px; right: 0; z-index: 20;
  width: max-content; max-width: 280px; padding: 8px 10px; border-radius: 8px;
  background: var(--ink); color: var(--card); font: 400 13px/1.4 inherit; font-style: normal;
  text-align: left; box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
  opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .12s;
}
.info-dot:hover::after, .info-dot:focus-visible::after { opacity: 1; visibility: visible; }
.help-section p:first-child { margin-top: 0; }
.help-section p:last-child, .help-steps:last-child { margin-bottom: 0; }
.help-steps li { margin: 4px 0; }
.lead { font-size: 17px; color: var(--muted); }
/* Portal hardening, 17 September: the signed-out banner (public/js/signed-out.js)
   and the repository filter in a person's GitHub panel. */
.signed-out {
  display: flex; align-items: center; gap: 12px; margin: 0 0 16px; padding: 12px 16px;
  border: 1px solid var(--warn); border-radius: var(--radius);
  background: var(--warn-tint); color: var(--warn); font-size: 15px; font-weight: 500;
}
.signed-out span { color: var(--text-1); }
.signed-out a { color: var(--accent); font-weight: 600; }
/* An element with a `display` rule of its own ignores the browser's styling for
   the `hidden` attribute, so a "hidden" row stays on screen. That is what broke
   the repository filter on v148: the rows carried hidden="" and were visible.
   One rule, so the next list to be hidden does not have to rediscover it. */
[hidden] { display: none !important; }

.signed-out[hidden], .repo-cell[hidden], .repo-filter-empty[hidden] { display: none; }
.repo-filter { width: 100%; max-width: 360px; }
