:root{
  --bg:#f4f4f2; --surface:#fff; --surface-2:#eeeeec; --border:#dcdcd8;
  --text:#15181c; --text-dim:#4b5158; --text-faint:#83898f;
  --accent:#3a6ea5; --accent-text:#fff;
  --allow:#3F7A52; --deny:#A6453B; --hold:#A87A2A;
  /* Text sitting directly on a solid --allow/--deny fill (buttons,
     "done" step badges). Light theme's --allow/--deny are medium-dark
     and saturated, so dark-on-them (the old hardcoded value, tuned only
     for dark theme's much brighter versions of these colors) read as
     muddy, low-contrast black text — white is what actually reads here. */
  --on-allow:#fff; --on-deny:#fff;
  --mono-bg:#111318; --shadow:0 1px 2px rgba(0,0,0,.04);
}
/* The system preference is the default; an explicit choice from the
   theme-toggle button (persisted in localStorage by theme.js, applied
   as data-theme on <html>) overrides it in either direction. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg:#0b0d10; --surface:#14171b; --surface-2:#1b1f24; --border:#2a2f36;
    --text:#eef0f2; --text-dim:#a9afb6; --text-faint:#6f767e;
    --accent:#7fb1e0; --accent-text:#0b0d10;
    --allow:#6FBE86; --deny:#E08479; --hold:#E0B15C;
    /* Dark theme's --allow/--deny are bright and pastel, so it's dark
       text that reads well on them here — the reverse of light theme. */
    --on-allow:#08130c; --on-deny:#1a0a08;
    --mono-bg:#0a0b0d; --shadow:0 1px 3px rgba(0,0,0,.35);
  }
}
:root[data-theme="dark"]{
  --bg:#0b0d10; --surface:#14171b; --surface-2:#1b1f24; --border:#2a2f36;
  --text:#eef0f2; --text-dim:#a9afb6; --text-faint:#6f767e;
  --accent:#7fb1e0; --accent-text:#0b0d10;
  --allow:#6FBE86; --deny:#E08479; --hold:#E0B15C;
  --on-allow:#08130c; --on-deny:#1a0a08;
  --mono-bg:#0a0b0d; --shadow:0 1px 3px rgba(0,0,0,.35);
}

*{box-sizing:border-box;}
body{
  margin:0; background:var(--bg); color:var(--text);
  font-family:'IBM Plex Sans', system-ui, sans-serif;
  padding:20px 24px 40px;
}
/* Not display:flex — an h2 followed by a long .hint/.count span (several
   panels across this app pair a heading with a trailing explanation) can
   wrap to multiple lines on a narrow viewport, and align-items:center
   used to center the trailing span against the WHOLE wrapped heading
   instead of letting it sit inline right after the text — the same bug
   class as .preset-card .label's custom-badge, fixed the same way:
   normal inline flow, spacing from margin-left on the trailing span. */
h2{font-size:.9rem; font-weight:600; margin:0 0 12px;}
code, .mono{font-family:'IBM Plex Mono', monospace;}

.top{display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; flex-wrap:wrap; gap:12px;}
.wordmark{display:flex; align-items:baseline; gap:8px; font-weight:700; font-size:1.15rem;}
.wordmark .mark{width:11px; height:11px; border:2px solid var(--accent); border-radius:2px; display:inline-block; transform:translateY(-1px);}
.wordmark .by{font-size:.75rem; font-weight:500; color:var(--text-faint);}
.top-right{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
/* Same shape as the pitch deck's own founder avatar (.team .avatar in
   inlock-page/index.html's Team section) — a plain circular initials
   badge, just sized for a header instead of a bio card. Shared here
   (not just manager.html's own inline style) now that index.html's
   header uses it too, as a fixed "RB" identity badge in place of the
   free-text reviewer field it replaced. */
.avatar{
  width:32px; height:32px; border-radius:50%; background:var(--surface-2); border:1px solid var(--border);
  display:flex; align-items:center; justify-content:center; font-family:'Archivo',sans-serif; font-weight:700;
  font-size:.76rem; color:var(--accent); flex-shrink:0;
}
.avatar.sm{width:26px; height:26px; font-size:.66rem;}
.nav-link{
  font-size:.78rem; font-weight:600; color:var(--accent); text-decoration:none;
  padding:5px 9px; border-radius:6px;
}
.nav-link:hover{background:var(--surface-2);}
/* The current page's own entry in the always-identical nav set —
   rendered as a <span>, not an <a>, so it's genuinely not clickable
   (not just visually disabled while still navigable), with no hover
   background since there's nowhere for it to go. */
.nav-link.current{
  color:var(--text); text-decoration:underline; text-underline-offset:4px; text-decoration-color:var(--accent);
  cursor:default;
}
.nav-link.current:hover{background:none;}
.dot{width:8px; height:8px; border-radius:50%; background:var(--text-faint); display:inline-block;}
.dot.live{background:var(--allow); box-shadow:0 0 0 3px color-mix(in srgb, var(--allow) 25%, transparent);}
.dot.down{background:var(--deny);}

.theme-toggle{
  width:30px; height:30px; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--border); background:var(--surface-2); border-radius:6px;
  color:var(--text-dim); cursor:pointer; flex-shrink:0;
}
.theme-toggle:hover{border-color:var(--accent); color:var(--accent);}
.theme-toggle svg{width:16px; height:16px;}
/* Shows the icon for the theme a click would switch TO, not the
   current one — moon (go dark) while light, sun (go light) while dark. */
.theme-toggle .icon-sun{display:none;}
.theme-toggle .icon-moon{display:block;}
.theme-toggle.is-dark .icon-sun{display:block;}
.theme-toggle.is-dark .icon-moon{display:none;}

.create-gate{margin-bottom:16px;}
.create-gate-top{display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;}
/* The intro takes the slack and wraps its own text; without a basis it
   claims the width of its longest sentence and pushes the button onto a
   line of its own. */
.create-gate-intro{flex:1 1 320px; min-width:0;}
.create-gate-top > h2{margin-bottom:0;}
.create-gate-intro h2{margin-bottom:4px;}
.hint-text{margin:0; font-size:.8rem; color:var(--text-faint);}

/* Shared by any page admitting its own auth/identity limits up front —
   manager.html's "viewing as" and org.html's employee/team CRUD both
   have the same real caveat (no actual sign-in) and read the same way. */
.warn-banner{
  position:relative;
  background:var(--surface-2); border:1px solid var(--border);
  border-radius:8px; padding:12px 38px 12px 16px; font-size:.84rem; color:var(--text-dim); margin-bottom:18px; line-height:1.55;
}
.warn-banner b{color:var(--text);}
.warn-banner[data-dismissed]{display:none !important;}

/* org.html's employee/team rows — one row per record, edit/delete
   flush right, the same "avatar + name/title, stats or actions on the
   right" shape manager.html's .team-row already established. */
.org-list{display:flex; flex-direction:column; gap:0;}
.org-row{display:flex; align-items:center; gap:10px; padding:10px 0; border-top:1px solid var(--border); flex-wrap:wrap;}
.org-row:first-child{border-top:none;}
.org-row .name{font-weight:700; font-size:.9rem;}
.org-row .meta{color:var(--text-faint); font-size:.76rem;}
.org-row .actions{display:flex; gap:6px; margin-left:auto; flex-shrink:0;}
.org-row .actions .btn{font-size:.74rem; padding:5px 10px;}
.org-row .twisty{color:var(--text-faint); margin-right:2px; font-family:'IBM Plex Mono',monospace;}

/* org.html's org-chart diagram — real HTML nodes (org.js's
   renderOrgChart), not a static image or a monolithic SVG: each person
   is a positioned div, sized and placed at a fixed, always-legible
   scale. A fixed-height viewport with scroll, not shrink-to-fit — a
   deep/wide org scrolls instead of rendering everyone illegibly small.
   safe center keeps a SMALL org centered instead of pinned to a
   scrolling container's top-left start corner (the plain "center"
   keyword can clip an overflowing child there in some browsers; "safe"
   falls back to start alignment only once content actually overflows). */
.org-chart{
  display:flex; align-items:safe center; justify-content:safe center; height:260px; overflow:auto;
  background:var(--surface-2); border:1px solid var(--border); border-radius:8px; padding:8px;
}
/* Org & Teams: the chart sizes to the org rather than living in a 260px
   scroll box — it is a picture, and scrolling a picture to see the shape
   of a company defeats it. The list beside it takes the scrolling. */
/* Members tab: reference above, working surface below. org.js sizes the
   list to whatever is left of the window (fitMembersHeight), the same way
   it sizes the org chart. */
.members-tab{display:flex; flex-direction:column; gap:16px;}
.members-tab[hidden]{display:none;}
.roles-panel{flex:0 0 auto; padding:14px 16px;}
.roles-panel > summary{
  margin:0; cursor:pointer; list-style:none;
  display:flex; align-items:center; gap:8px;
}
.roles-panel > summary::-webkit-details-marker{display:none;}
.roles-panel > summary::before{content:'\25b8'; color:var(--text-faint); font-size:.8rem;}
.roles-panel[open] > summary::before{content:'\25be';}
.roles-panel[open] > summary{margin-bottom:10px;}
.roles-panel .role-card{padding:10px 12px;}
.roles-panel .role-card span{font-size:.75rem; line-height:1.45;}
.members-panel{flex:1 1 auto; min-height:0; display:flex; flex-direction:column;}
/* align-content:start, or the grid hands its spare height to the rows and
   three people sit a hundred pixels apart down an otherwise empty panel. */
.members-panel .members-list{flex:1 1 auto; min-height:0; overflow-y:auto; padding-right:4px; align-content:start;}
.members-panel .create-gate-top{flex:0 0 auto;}

#employeesTab .cols{align-items:stretch;}
#employeesTab .org-chart{height:auto; min-height:280px; max-height:min(62vh, 620px);}
#employeesTab .org-list{min-height:0; max-height:min(62vh, 620px); overflow-y:auto; padding-right:4px;}
.org-chart-canvas{position:relative; flex-shrink:0;}
.org-chart-lines{position:absolute; top:0; left:0; pointer-events:none;}
.org-chart-node{
  position:absolute; display:flex; flex-direction:column; align-items:center; text-align:center;
  padding:2px; border-radius:8px;
}
/* Only clickable for someone who may actually change the chart (org.js's
   canEditOrg). Without the cursor and the hover lift it reads as a
   diagram rather than a control, which is what it is for everyone else. */
.org-chart.is-editable .org-chart-node{cursor:pointer;}
.org-chart.is-editable .org-chart-node:hover{background:color-mix(in srgb, var(--accent) 8%, transparent);}
.org-chart.is-editable .org-chart-node:hover .org-chart-circle{border-width:2.5px;}
.org-chart-circle{
  width:44px; height:44px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center;
  background:var(--surface); border:1.5px solid var(--accent); color:var(--text);
  font-size:13px; font-weight:700;
}
.org-chart-name{
  margin-top:6px; font-size:11px; font-weight:600; color:var(--text); max-width:100%;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.org-chart-title{
  font-size:9px; color:var(--text-faint); max-width:100%;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* docs.html's explanatory lists — each item its own paragraph-ish block,
   not a tight bulleted list, since these read more like short FAQ
   entries than a scannable list. */
.docs-list{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px;}
.docs-list li{font-size:.84rem; color:var(--text-dim); line-height:1.55; padding-left:16px; position:relative;}
.docs-list li::before{content:'\2013'; position:absolute; left:0; color:var(--text-faint);}
.docs-list b{color:var(--text);}

/* Agent cards in the Governance panel — governed ones (green, with gate
   pills) and connected-but-ungoverned ones (dashed, with a Govern button),
   so a newly added agent is visible here before anyone attaches a gate. */
.governed-list{display:grid; grid-template-columns:repeat(auto-fill, minmax(220px,1fr)); gap:8px; margin-top:14px;}
.fleet-card.ungoverned{border:1px dashed var(--border); background:var(--surface-2);}
.fleet-card.ungoverned .id{color:var(--text-dim);}
.fleet-card.ungoverned .plug-icon{color:var(--text-faint);}

/* Ungoverned agents are grouped under this rather than mixed in or, worse,
   hidden once something else is governed — the panel must not go quiet
   exactly when someone believes they are covered. It only exists when
   there is something under it, so a fully governed fleet reads clean
   because it is, not because the list was tidied. */
.fleet-divider{
  grid-column:1/-1; display:flex; align-items:baseline; gap:8px; flex-wrap:wrap;
  margin-top:6px; padding-top:10px; border-top:1px solid var(--border);
  font-size:.72rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--hold);
}
.fleet-divider .count{
  font-family:'IBM Plex Mono',monospace; font-size:.68rem; font-weight:700; letter-spacing:0;
  color:var(--hold); background:color-mix(in srgb, var(--hold) 15%, transparent);
  border:1px solid color-mix(in srgb, var(--hold) 40%, transparent); border-radius:10px; padding:0 7px;
}
.fleet-divider .fd-note{font-weight:400; text-transform:none; letter-spacing:0; color:var(--text-faint);}

/* The one liveness fact this dashboard observes rather than infers — see
   app.js's lastSeen. Never "running": the fleet only ever grows, so that
   would be a guess for every agent the Agent console doesn't drive. */
.fleet-card .last-seen{font-size:.68rem; color:var(--text-faint); font-family:'IBM Plex Mono',monospace;}
.govern-btn{font-size:.72rem; padding:5px 10px;}
.simulated-badge{
  font-family:'IBM Plex Mono',monospace; font-size:.62rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
  color:var(--hold); border:1px solid color-mix(in srgb, var(--hold) 45%, transparent); border-radius:4px; padding:1px 5px; vertical-align:2px;
}
.governed-list .empty{grid-column:1/-1;}
.fleet-card{
  border:1px solid color-mix(in srgb, var(--allow) 30%, var(--border)); border-radius:6px; padding:10px 12px;
  background:color-mix(in srgb, var(--allow) 6%, var(--surface-2)); font-size:.8rem;
  display:flex; flex-direction:column; gap:4px; min-width:0;
}
/* Same skeleton on every tile — one-line id, role clamped to 3 lines,
   status, then the action pinned to the bottom — so tiles in a row line
   up (the grid stretches them to equal height). Full text is in title. */
.fleet-card .id{
  font-family:'IBM Plex Mono',monospace; color:var(--accent); font-weight:600;
  display:flex; align-items:center; gap:6px; min-width:0;
}
.fleet-card .id-text{overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.fleet-card .role{display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;}
.fleet-card .fc-foot{margin-top:auto; padding-top:8px;}
.fleet-card .id .plug-icon{width:14px; height:14px; color:var(--allow); flex-shrink:0;}
.fleet-card .role{color:var(--text-faint); font-size:.74rem; line-height:1.35;}
.fleet-card .gates{display:flex; flex-wrap:wrap; gap:5px; margin-top:4px;}
.gate-pill{
  font-family:'IBM Plex Mono',monospace; font-size:.66rem; font-weight:600;
  color:var(--accent); background:color-mix(in srgb, var(--accent) 12%, transparent);
  border:1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius:20px; padding:2px 6px 2px 9px; max-width:100%; overflow-wrap:anywhere;
  display:inline-flex; align-items:center; gap:5px;
}
.pill-x{
  font-family:inherit; font-size:.8em; line-height:1; color:inherit; opacity:.6;
  background:none; border:none; cursor:pointer; padding:1px 2px; border-radius:50%;
}
.pill-x:hover{opacity:1; background:color-mix(in srgb, var(--deny) 25%, transparent); color:var(--deny);}
.unplug-btn{
  font-family:'IBM Plex Sans', sans-serif; font-size:.7rem; font-weight:600;
  color:var(--text-faint); background:none; border:none; cursor:pointer; padding:0;
  text-decoration:underline; text-underline-offset:2px;
}
.unplug-btn:hover{color:var(--deny);}

/* ---------- Create-gate wizard ---------- */
.wizard-overlay{
  position:fixed; inset:0; background:color-mix(in srgb, black 45%, transparent);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  display:flex; align-items:center; justify-content:center; z-index:100; padding:20px;
}
.wizard-overlay[hidden]{display:none;}
.wizard{
  background:var(--bg); border:1px solid var(--border); border-radius:10px;
  box-shadow:0 8px 30px rgba(0,0,0,.3); width:100%; max-width:760px;
  max-height:88vh; display:flex; flex-direction:column; overflow:hidden;
}
/* The Create-gate wizard keeps one height across every step and mode —
   switching tabs scrolls the body instead of resizing the dialog. */
#wizardOverlay .wizard{height:min(88vh, 720px);}
.wizard-header{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:14px 18px; border-bottom:1px solid var(--border); flex-shrink:0;
}
.wizard-steps{display:flex; gap:18px;}
.wizard-step-label{
  font-size:.78rem; color:var(--text-faint); display:flex; align-items:center; gap:6px;
}
.wizard-step-label b{
  font-family:'IBM Plex Mono',monospace; width:20px; height:20px; border-radius:50%;
  display:inline-flex; align-items:center; justify-content:center; font-size:.72rem;
  background:var(--surface-2); border:1px solid var(--border); color:var(--text-faint);
}
.wizard-step-label.active{color:var(--text); font-weight:600;}
.wizard-step-label.active b{background:var(--accent); border-color:var(--accent); color:var(--accent-text);}
.wizard-step-label.done b{background:var(--allow); border-color:var(--allow); color:var(--on-allow);}
.wizard-close{
  background:none; border:none; color:var(--text-faint); font-size:1.1rem; cursor:pointer;
  line-height:1; padding:4px 8px; border-radius:4px;
}
.wizard-close:hover{background:var(--surface-2); color:var(--text);}

.wizard-body{padding:18px; overflow-y:auto; flex:1;}
.wizard-panel h3{font-size:.95rem; font-weight:700; margin:0 0 12px;}
.wizard-panel[hidden]{display:none;}

.agent-palette{display:grid; grid-template-columns:repeat(auto-fill, minmax(200px,1fr)); gap:10px;}
.agent-card, .template-card{
  border:1.5px solid var(--border); border-radius:8px; padding:12px;
  background:var(--surface); cursor:pointer; text-align:left; display:flex;
  flex-direction:column; gap:6px; transition:border-color .15s, background .15s;
}
.agent-card:hover, .template-card:hover{border-color:var(--accent);}
.agent-card.selected, .template-card.selected{
  border-color:var(--accent); background:color-mix(in srgb, var(--accent) 8%, var(--surface));
}
.agent-card .id{font-family:'IBM Plex Mono',monospace; font-weight:600; color:var(--text); font-size:.86rem;}
.agent-card .role{font-size:.76rem; color:var(--text-dim); line-height:1.4;}
.agent-card .runtime{
  font-family:'IBM Plex Mono',monospace; font-size:.66rem; color:var(--text-faint);
  border-top:1px solid var(--border); padding-top:6px; margin-top:2px;
}

.gate-mode-toggle{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:16px;}
.gate-mode-toggle[hidden]{display:none;}
.gate-mode-toggle .mode-btn{
  white-space:nowrap;
  font-family:'IBM Plex Mono',monospace; font-size:.76rem; padding:7px 14px; border-radius:20px;
  border:1.5px solid var(--border); background:var(--surface); color:var(--text-dim); cursor:pointer;
}
.gate-mode-toggle .mode-btn:disabled{opacity:.45; cursor:not-allowed;}
.gate-mode-toggle .mode-btn.active{border-color:var(--accent); background:color-mix(in srgb, var(--accent) 12%, var(--surface)); color:var(--text); font-weight:600;}
.tab-count{
  display:inline-block; margin-left:5px; font-size:.66rem; font-weight:700; padding:1px 6px; border-radius:10px;
  background:color-mix(in srgb, var(--text) 14%, transparent); color:inherit;
}
.gate-mode-toggle .mode-btn.active .tab-count{background:color-mix(in srgb, var(--accent) 30%, transparent);}
#customMode .schema-intro{margin-top:-6px; margin-bottom:6px;}
#customMode label{margin-top:12px;}
/* Paired fields (label + tool, when + action) side by side; stacks on
   narrow screens. minmax(0,1fr) so long placeholders can't widen a column. */
.schema-row{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:12px;}
.schema-field-hint{margin-top:6px;}
.schema-add-condition{margin-top:10px;}
@media (max-width:560px){ .schema-row{grid-template-columns:minmax(0,1fr); gap:0;} }
#customMode .hint-text code{font-family:'IBM Plex Mono',monospace; font-size:.74rem; background:var(--surface-2); padding:1px 5px; border-radius:4px;}
.error-text{color:var(--deny); text-transform:none; letter-spacing:normal;}
.schema-preview{margin-top:18px; padding-top:14px; border-top:1px solid var(--border);}
.schema-preview .hint-text{margin-top:6px;}
.schema-preview-result{
  margin-top:10px; background:var(--surface-2); border:1px solid var(--border); border-radius:8px;
  padding:12px 14px; font-size:.82rem;
}
.schema-preview-result .summary-line{font-weight:600; color:var(--text); margin-bottom:8px;}
.schema-preview-result .summary-line .would-fire{color:var(--hold);}
.preview-match-list{display:flex; flex-direction:column; gap:6px;}
.preview-match{
  font-family:'IBM Plex Mono',monospace; font-size:.74rem; color:var(--text-dim);
  border-left:2px solid var(--hold); padding-left:8px;
}
.preview-match .tool{color:var(--text);}
.preview-more{font-size:.72rem; color:var(--text-faint); font-style:italic; margin-top:2px;}
.schema-conditions{display:flex; flex-direction:column; gap:8px; margin-top:10px;}
.schema-conditions[hidden]{display:none;}

/* "If nobody answers in 30 seconds, deny it." Sits under the note
   checkbox in both gate builders; hidden entirely for a gate that denies
   outright, which has nothing to wait for. */
.self-resolve{margin-top:10px;}
.self-resolve[hidden]{display:none;}
.self-resolve-fields{display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:8px 0 0 26px;}
.self-resolve-fields[hidden]{display:none;}
.self-resolve-fields label{margin:0;}
.self-resolve-fields input[type="number"]{width:88px;}
.self-resolve-fields select{width:auto; min-width:104px;}
.self-resolve-unit{font-size:.82rem; color:var(--text-dim);}
.self-resolve .hint-text{margin:6px 0 0 26px;}
.self-resolve .hint-text[hidden]{display:none;}
.visually-hidden{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap;}
.schema-condition-row{display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) minmax(0,1fr) auto; gap:8px; align-items:center;}
.schema-condition-row input, .schema-condition-row select{
  font-family:'IBM Plex Mono',monospace; font-size:.82rem; color:var(--text);
  background:var(--surface-2); border:1px solid var(--border); border-radius:6px; padding:8px 9px;
  min-width:0; width:100%; overflow:hidden; text-overflow:ellipsis;
}
.schema-condition-row .remove-condition{
  background:none; border:1px solid var(--border); color:var(--text-faint); border-radius:6px;
  width:30px; height:30px; cursor:pointer; flex-shrink:0;
}
.schema-condition-row .remove-condition:hover{border-color:var(--deny); color:var(--deny);}
.schema-condition-row .schema-condition-extra{grid-column:1/-1; display:flex; flex-wrap:wrap; gap:8px; align-items:center; font-size:12.5px; color:var(--text-faint); font-family:inherit;}
.schema-condition-row .schema-condition-extra input{width:96px;}

.template-palette{display:flex; flex-direction:column; gap:10px;}
.template-grid{display:grid; grid-template-columns:repeat(auto-fill, minmax(210px,1fr)); gap:10px;}
.palette-group-label{font-size:.7rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--text-faint); margin-top:6px;}
.palette-group-label:first-child{margin-top:0;}
.custom-tag{
  font-family:'IBM Plex Mono',monospace; font-size:.58rem; font-weight:700; letter-spacing:.04em; text-transform:uppercase; vertical-align:1px;
  color:var(--accent); border:1px solid color-mix(in srgb, var(--accent) 45%, transparent); border-radius:4px; padding:1px 4px;
}
.template-card{flex-direction:row; align-items:flex-start;}
.template-card .icon{width:44px; height:44px; flex-shrink:0;}
.template-card .tmeta{display:flex; flex-direction:column; gap:3px; min-width:0;}
.template-card .label{font-weight:600; font-size:.84rem; color:var(--text);}
.template-card .desc{font-size:.74rem; color:var(--text-dim); line-height:1.4;}

.wizard-summary{
  background:var(--surface-2); border:1px solid var(--border); border-radius:8px;
  padding:12px 14px; margin-bottom:14px; font-size:.85rem; display:flex;
  flex-direction:column; gap:4px;
}
.wizard-summary .row{display:flex; gap:8px;}
.wizard-summary .row .k{color:var(--text-faint); min-width:90px;}
.wizard-summary .row .v{font-family:'IBM Plex Mono',monospace; color:var(--text); font-weight:600;}

.wizard-body label{
  display:flex; flex-direction:column; gap:4px; font-size:.72rem; color:var(--text-faint);
  text-transform:uppercase; letter-spacing:.04em;
}
.wizard-body label[hidden]{display:none;}
.wizard-body .checkbox-label{
  flex-direction:row; align-items:center; gap:8px; text-transform:none;
  letter-spacing:normal; font-size:.82rem; color:var(--text-dim); margin-top:14px;
  cursor:pointer;
}
.wizard-body .checkbox-label input[type="checkbox"]{width:15px; height:15px; padding:0; flex-shrink:0; cursor:pointer;}
.wizard-body input, .wizard-body select, .wizard-body textarea{
  font-family:'IBM Plex Mono',monospace; font-size:.85rem; color:var(--text);
  background:var(--surface-2); border:1px solid var(--border); border-radius:6px;
  padding:9px 10px; text-transform:none; letter-spacing:normal; width:100%; box-sizing:border-box;
}
.wizard-body textarea{resize:vertical; font-family:inherit;}
/* "Describe it" fills the wizard's fixed height: the panel, the mode, and
   the prompt box each stretch into the space left below the intro, so the
   box is as tall as the dialog allows instead of three rows over a void. */
.wizard-body{display:flex; flex-direction:column;}
.wizard-panel{flex-shrink:0;}
.wizard-panel[data-step="2"]:not([hidden]){flex:1; display:flex; flex-direction:column;}
#describeMode:not([hidden]){flex:1; display:flex; flex-direction:column;}
#describeMode label{flex:1; margin-top:14px;}
#describePrompt{flex:1; min-height:140px; resize:none; line-height:1.55;}


/* ---------- Presets page ---------- */
.preset-list{display:flex; flex-direction:column; gap:10px; margin-top:14px;}
/* A list longer than the window used to run off the bottom edge of the
   page. It scrolls in place now, inside the panel. (A fade over the last
   rows was tried as a "there's more" cue and read as a rendering fault —
   the scrollbar already says it.) */
.preset-list-wrap{position:relative; min-height:0;}
.preset-list-wrap > .preset-list{max-height:100%; overflow-y:auto; padding-right:6px;}
body.presets-page{height:100vh; display:flex; flex-direction:column; overflow:hidden;}
body.presets-page > header.top,
body.presets-page > .gate-mode-toggle{flex:0 0 auto;}
body.presets-page > .panel{flex:1 1 auto; min-height:0; display:flex; flex-direction:column;}
body.presets-page > .panel[hidden]{display:none;}
body.presets-page .preset-list-wrap{flex:1 1 auto;}
.preset-card{
  display:flex; align-items:flex-start; gap:14px; border:1px solid var(--border);
  border-radius:8px; padding:14px; background:var(--surface);
}
.preset-card.custom{border-color:color-mix(in srgb, var(--accent) 35%, var(--border)); background:color-mix(in srgb, var(--accent) 5%, var(--surface));}
.preset-card .icon{width:44px; height:44px; flex-shrink:0;}
.preset-card .icon svg{width:100%; height:100%;}
.preset-card .body{flex:1; min-width:0; display:flex; flex-direction:column; gap:5px;}
/* Not display:flex — a preset's label can wrap to multiple lines on a
   narrow viewport (the Browser pane's own default is ~370px), and
   align-items:center used to center the custom-badge against the whole
   wrapped block instead of letting it sit inline right after the text,
   which read as the badge floating disconnected from its label. Normal
   inline flow (the badge as display:inline-block below) wraps the same
   way a trailing word would. */
.preset-card .label{font-weight:700; font-size:.92rem; color:var(--text);}
.preset-card .desc{font-size:.8rem; color:var(--text-dim); line-height:1.4;}
.preset-card .desc code{font-family:'IBM Plex Mono',monospace; font-size:.78rem; background:var(--surface-2); padding:1px 5px; border-radius:4px;}
.preset-card .behavior, .preset-card .config-hint{font-size:.76rem; color:var(--text-faint); line-height:1.45;}
.preset-card .behavior b, .preset-card .config-hint b, .preset-card .desc b{color:var(--text-dim); font-weight:600;}
.preset-card .dup-btn{flex-shrink:0; white-space:nowrap; align-self:center;}
.preset-actions{display:flex; flex-direction:column; gap:6px; flex-shrink:0; align-self:center;}
.preset-actions .btn{font-size:.76rem; padding:6px 12px; white-space:nowrap;}
.custom-badge{
  display:inline-block; vertical-align:middle; margin-left:8px;
  font-family:'IBM Plex Mono',monospace; font-size:.6rem; font-weight:700; text-transform:uppercase;
  letter-spacing:.04em; color:var(--accent); background:color-mix(in srgb, var(--accent) 14%, transparent);
  border:1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius:20px; padding:2px 8px;
}
@media (max-width:600px){ .preset-card{flex-direction:column;} .preset-card .dup-btn{align-self:stretch;} .preset-actions{align-self:stretch; flex-direction:row;} }

/* ---------- Audit log page ---------- */
.chain-status{display:flex; align-items:center; gap:10px; flex-wrap:wrap; font-size:.85rem;}
.chain-status .btn{margin-left:auto;}

/* audit.html fills the window instead of growing past it: the table is
   the only part that scrolls, so the chain check, the count, Load more
   and the footnote are all on screen whatever the row count. */
body.audit-page{height:100vh; display:flex; flex-direction:column; overflow:hidden;}
/* Filter chips inside a panel's toolbar. Deliberately unlike the pills
   at the top of a page: those choose what you are looking at, these
   narrow what you are already looking at, and one shape for both made a
   filter read as a tab that had failed to do anything. Square, quiet,
   and sat among the other filters rather than above them. */
/* A note under a panel's toolbar needs air above it — against the filters
   it reads as another control's label. */
.panel-note{margin:14px 0 12px;}

.filter-chips{display:flex; flex-wrap:wrap; gap:4px;}
.filter-chips .chip{
  font-family:'IBM Plex Mono',monospace; font-size:.68rem; line-height:1;
  padding:6px 9px; border-radius:5px; cursor:pointer;
  border:1px solid var(--border); background:var(--surface-2); color:var(--text-faint);
}
.filter-chips .chip:hover{color:var(--text);}
.filter-chips .chip.active{
  border-color:color-mix(in srgb, var(--accent) 55%, var(--border));
  background:color-mix(in srgb, var(--accent) 14%, var(--surface));
  color:var(--text); font-weight:600;
}

body.audit-page > header.top,
body.audit-page > .gate-mode-toggle,
body.audit-page > .audit-footnote{flex:0 0 auto;}
body.audit-page > .audit-panel{flex:1 1 auto; min-height:0; display:flex; flex-direction:column;}
body.audit-page > .audit-panel[hidden]{display:none;}
.audit-footnote{margin:12px 0 0;}
.audit-panel > .audit-table-wrap{flex:1 1 auto; min-height:140px; max-height:none;}
/* One bar under the table: the chain check on the left, how much is shown
   and Load more on the right. */
.audit-statusbar{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  flex-wrap:wrap; margin-top:12px; flex:0 0 auto;
}
.audit-statusbar .chain-status{flex:0 1 auto; min-width:0;}
.audit-statusbar .chain-status .btn{margin-left:4px;}
.audit-paging{display:flex; align-items:center; gap:10px; margin-left:auto;}
.audit-paging .hint-text{white-space:nowrap;}
.audit-paging .btn[hidden]{display:none;}
#exportBtn{text-decoration:none; display:inline-flex; align-items:center; background:var(--surface-2); border:1px solid var(--border); color:var(--text);}
#exportBtn:hover{border-color:var(--accent);}

/* Also used by docs.html's #compliance section — not audit-page-specific
   despite the file section header above, kept here since audit.html is
   still what links to it. */
.compliance-grid{display:grid; grid-template-columns:repeat(auto-fill, minmax(230px,1fr)); gap:10px; margin-top:12px;}
.compliance-card{border:1px solid var(--border); border-radius:8px; padding:12px 14px; background:var(--surface);}
.compliance-card .fw-name{font-weight:700; font-size:.86rem; margin-bottom:6px;}
.compliance-card .fw-row{font-size:.76rem; color:var(--text-dim); line-height:1.45; margin-bottom:6px;}
.compliance-card .fw-row b{color:var(--text); font-weight:600;}
.compliance-card .fw-row.gap{color:var(--text-faint);}
.compliance-card .fw-row.gap b{color:var(--text-dim);}

.audit-toolbar{display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;}
.audit-filters{display:flex; gap:8px; flex-wrap:wrap;}
.audit-filters input, .audit-filters select{
  font-family:'IBM Plex Mono',monospace; font-size:.8rem; color:var(--text);
  background:var(--surface-2); border:1px solid var(--border); border-radius:6px; padding:7px 9px;
}
.audit-table-wrap{
  overflow-x:auto; overflow-y:auto; margin-top:12px;
  border:1px solid var(--border); border-radius:8px;
  /* Fixed to the available viewport height, not the row count — a
     table that keeps growing the whole PAGE's height made "scroll all
     the way down" mean scrolling the entire page, and any reload
     anywhere on it (this table's own, or an unrelated layout shift)
     could throw that scroll position away. Scrolls internally instead;
     340px is a rough allowance for the header, chain-integrity panel,
     and toolbar above it — not exact, but close enough that this
     rarely needs a page-level scrollbar at all on a normal window.
     The allowance covers what sits above it (header, record tabs,
     toolbar) plus the Load more row directly below, so that row stays on
     screen instead of hiding past the bottom of a long table. */
  max-height:calc(100vh - 300px);
  min-height:240px;
}
.new-entries-banner{
  display:block; width:100%; margin-top:12px; padding:8px 14px;
  font-family:'IBM Plex Sans', sans-serif; font-size:.8rem; font-weight:600; text-align:center;
  color:var(--accent-text); background:var(--accent); border:none; border-radius:6px; cursor:pointer;
}
.new-entries-banner[hidden]{display:none;}
.new-entries-banner:hover{filter:brightness(1.08);}
.audit-table{width:100%; border-collapse:collapse; font-size:.78rem; white-space:nowrap;}
.audit-table th{
  text-align:left; font-size:.68rem; text-transform:uppercase; letter-spacing:.04em; color:var(--text-faint);
  padding:6px 8px; border-bottom:1px solid var(--border); position:sticky; top:0; background:var(--surface);
}
.audit-table td{padding:6px 8px; border-bottom:1px solid var(--border); color:var(--text-dim);}
/* A long argument list used to wrap and make one row as tall as six —
   the table became unreadable exactly when there was most to read. One
   line, the rest in the entry dialog. */
.audit-table td.args{max-width:250px;}
.audit-table td.args .args-text{
  display:inline-block; max-width:210px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; vertical-align:middle;
}
.args-more{
  font-family:inherit; font-size:.72rem; line-height:1; cursor:pointer; vertical-align:middle;
  margin-left:6px; padding:3px 6px; border-radius:5px;
  color:var(--text-dim); background:var(--surface-2); border:1px solid var(--border);
}
.args-more:hover{border-color:var(--accent); color:var(--text);}
.entry-facts{display:grid; grid-template-columns:auto minmax(0,1fr); gap:6px 14px; margin:0 0 16px; font-size:.82rem;}
.entry-facts dt{color:var(--text-faint);}
.entry-facts dd{margin:0; color:var(--text); word-break:break-word;}
.entry-heading{font-size:.78rem; text-transform:uppercase; letter-spacing:.04em; color:var(--text-faint); margin:0 0 6px;}
.entry-json{
  margin:0 0 10px; padding:12px 14px; max-height:320px; overflow:auto;
  font-size:.78rem; line-height:1.6; white-space:pre-wrap; word-break:break-word;
  background:var(--surface-2); border:1px solid var(--border); border-radius:8px;
}
.audit-table td.finding{white-space:normal; max-width:170px; word-break:break-word; color:var(--text-faint);}
/* "1 gate checked" wrapping to two lines made every row twice as tall. */
.audit-table .trace summary{white-space:nowrap;}
/* Ten columns have to fit a normal window, or the hash — the one column
   that makes an entry checkable — falls off the right edge. The long
   ones clip with the full value on hover. */
.audit-table td.clip > span{display:inline-block; max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:middle;}
.audit-table td.clip.tool > span{max-width:180px;}
/* Finding and note are sentences, so they get more room than an id — but
   they clip like everything else now. A gate label that wrapped to four
   lines set the height of the whole row. */
.audit-table td.finding.clip > span{max-width:200px;}
/* Not a person, and not "we forgot to record one" — see audit.js's
   reviewerCell. */
.audit-table .no-reviewer{color:var(--text-faint); font-style:italic;}
/* The row opener is always there for a row whose arguments are cut, and
   appears on hover for the rest — every entry can be opened and copied,
   without a button on all ten rows of a full screen. */
.audit-table .args-more{opacity:0; transition:opacity .12s;}
.audit-table tr:hover .args-more,
.audit-table .args-more:focus-visible,
.audit-table .args-more.is-needed{opacity:1;}
.audit-table .trace-row{display:flex; flex-direction:column; gap:1px; font-size:.68rem;}
.audit-table .trace-row .trace-preset{font-weight:600;}
.audit-table td.hash{color:var(--text-faint); cursor:help;}
.audit-table td.table-empty{
  padding:22px 12px; text-align:center; white-space:normal;
  color:var(--text-faint); font-style:italic; font-size:.82rem;
}
.audit-table .mono{font-family:'IBM Plex Mono',monospace;}

.wizard-footer{
  display:flex; justify-content:space-between; gap:10px; padding:14px 18px;
  border-top:1px solid var(--border); flex-shrink:0;
}
.wizard-footer .btn[hidden]{display:none;}
.wizard-footer .btn.approve{margin-left:auto;}

.metrics{
  display:grid; grid-template-columns:repeat(6, minmax(0,1fr)); gap:1px;
  background:var(--border); border:1px solid var(--border); border-radius:8px;
  overflow:hidden; margin-bottom:20px; box-shadow:var(--shadow);
}
.metric{background:var(--surface); padding:12px 14px; display:flex; flex-direction:column; gap:2px;}
.metric .v{font-family:'IBM Plex Mono',monospace; font-size:1.25rem; font-weight:600;}
.metric .l{font-size:.68rem; color:var(--text-faint); text-transform:uppercase; letter-spacing:.04em;}
.metric.allow .v{color:var(--allow);}
.metric.hold .v{color:var(--hold);}
.metric.deny .v{color:var(--deny);}
@media (max-width:820px){ .metrics{grid-template-columns:repeat(3, minmax(0,1fr));} }
@media (max-width:480px){ .metrics{grid-template-columns:repeat(2, minmax(0,1fr));} }

/* Same frame as the Dashboard: the fleet summary keeps its height and
   the per-agent breakdown takes the rest of the window, scrolling its own
   list instead of the page. A dozen agents used to push the summary — the
   numbers you came for — off the top as you scrolled down the list. */
body.costs-page{height:100vh; display:flex; flex-direction:column; overflow:hidden; padding-bottom:20px;}
body.costs-page > header.top,
body.costs-page > .stale-banner,
body.costs-page > section[aria-label="Fleet cost summary"]{flex:0 0 auto;}
body.costs-page > #perAgentPanel{flex:1 1 auto; min-height:0; display:flex; flex-direction:column;}
body.costs-page #costList{flex:1 1 auto; min-height:0; overflow-y:auto; padding-right:6px;}
@media (max-width:900px){
  body.costs-page{height:auto; overflow:visible; padding-bottom:40px;}
  body.costs-page > #perAgentPanel{flex:0 0 auto;}
  body.costs-page #costList{overflow-y:visible;}
}

/* Projection donuts — a straight-line "if this rate holds" extrapolation
   to whichever window the toggle picks (month, quarter, year), not a
   forecasting model. The ring is a plain CSS conic-gradient driven by the
   --pct custom property set from JS, so no chart library is pulled in for
   two numbers. The ring is the share of that window actually observed, so
   a longer window honestly shows a thinner ring. */
.donut-row{display:flex; gap:14px; flex-wrap:wrap; align-items:stretch;}
/* Stacked, and in the row with the cards: the window is picked far less
   often than the numbers are read, so it earns the narrow column, not a
   band of its own above two half-empty cards. */
.horizon-toggle{flex:0 0 auto; flex-direction:column; justify-content:center; gap:6px; margin-bottom:0;}
.horizon-toggle .mode-btn{font-size:12px; padding:5px 13px; text-align:center;}
.projection-row.is-collecting .donut-row{display:none;}
.projection-row:not(.is-collecting) .projection-collecting{display:none;}
.projection-collecting{margin:0;}
.donut-card{
  display:flex; align-items:center; gap:14px; flex:1; min-width:230px;
  background:var(--surface-2); border:1px solid var(--border); border-radius:8px; padding:12px 16px;
}
.donut{
  position:relative; width:58px; height:58px; border-radius:50%; flex-shrink:0;
  background:conic-gradient(var(--accent) calc(var(--pct, 0) * 1%), var(--border) 0);
}
.donut::before{content:''; position:absolute; inset:7px; border-radius:50%; background:var(--surface-2);}
.donut .donut-label{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-family:'IBM Plex Mono',monospace; font-size:.66rem; font-weight:700; color:var(--text);
}
.donut-meta{display:flex; flex-direction:column; gap:1px; min-width:0;}
.donut-meta .dt{font-size:.66rem; color:var(--text-faint); text-transform:uppercase; letter-spacing:.04em;}
.donut-meta .dv{font-family:'IBM Plex Mono',monospace; font-size:1.05rem; font-weight:700; color:var(--text);}
.donut-meta .dsub{font-size:.7rem; color:var(--text-dim);}

.cols{display:grid; grid-template-columns:minmax(0,1.6fr) minmax(0,1fr); gap:16px; align-items:start;}
@media (max-width:900px){ .cols{grid-template-columns:minmax(0,1fr);} }

/* index.html only. The metric bar runs across the top; under it Governance
   keeps a column to itself, and the other column stacks Pending approvals
   over Live activity — what is waiting on a person above what is only
   worth watching. A narrower split than .cols's default: the right column
   now carries two panels rather than one, and the agent cards on the left
   are a fixed-minimum grid that reflows rather than squeezing.
   Collapsing to one column keeps that same order. */
.dash-cols{grid-template-columns:minmax(0,1.3fr) minmax(0,1fr); align-items:stretch;}
.dash-cols > .create-gate{margin-bottom:0;}
.col-stack{display:flex; flex-direction:column; gap:16px; min-width:0; min-height:0;}

/* The page itself is the frame: header and metric bar keep their height,
   the two columns take everything left over down to the bottom edge, and
   the lists inside them scroll rather than the window. Governance and
   Live activity are the two that grow — Pending approvals is as tall as
   what is actually waiting, up to a point, because an empty queue should
   not hold open half the screen. */
body.dash-page{height:100vh; display:flex; flex-direction:column; overflow:hidden; padding-bottom:20px;}
body.dash-page > header.top,
body.dash-page > .stale-banner{flex:0 0 auto;}
/* flex-basis 0 rather than auto, and explicitly shrinkable rows: a grid
   row is content-sized by default, so the column that held seventeen feed
   entries simply made the grid taller than the screen and the page
   scrolled again. minmax(0,1fr) is what lets a row take the height it is
   given instead of the height it wants.
   Two rows: the metric bar over Governance, sharing its width, and the
   right column spanning both so the queue and the feed get the whole
   height of the page instead of what is left under the numbers. */
body.dash-page .dash-cols{flex:1 1 0; min-height:0; grid-template-rows:auto minmax(0,1fr);}
.dash-cols > #metrics{grid-column:1; grid-row:1; margin-bottom:0;}
.dash-cols > .create-gate{grid-column:1; grid-row:2;}
.dash-cols > .col-stack{grid-column:2; grid-row:1 / span 2;}
/* Six tiles across a column narrower than the page: let them reflow to
   two rows rather than squeezing "avg classify" into 90 pixels. */
.dash-cols > #metrics{grid-template-columns:repeat(auto-fit, minmax(128px,1fr));}
body.dash-page .dash-cols > .create-gate,
body.dash-page .col-stack > .panel{display:flex; flex-direction:column; min-height:0;}
body.dash-page .governed-list{flex:1 1 auto; min-height:0; overflow-y:auto; align-content:start;}
/* Its own content's height, and no less: flex-shrink here let the feed
   below pull it down to a sliver with the top of a hold card peeking out
   of it. The cap is what stops a long queue taking the whole column —
   past it the list inside scrolls. */
body.dash-page .col-stack > .panel.pending{flex:0 0 auto; max-height:55%;}
body.dash-page .pending-list{flex:1 1 auto; min-height:0; overflow-y:auto;}
body.dash-page .col-stack > .panel.feed{flex:1 1 auto;}
body.dash-page .feed-list{flex:1 1 auto; min-height:0; max-height:none;}

/* Below the breakpoint the columns stack, so a fixed-height page would
   trap both lists in a sliver each — the window scrolls again instead. */
@media (max-width:900px){
  .dash-cols{grid-template-columns:minmax(0,1fr);}
  /* One column: the spans above would leave holes, so everything simply
     stacks in source order — numbers, Governance, queue, feed. */
  .dash-cols > #metrics,
  .dash-cols > .create-gate,
  .dash-cols > .col-stack{grid-column:1; grid-row:auto;}
  body.dash-page{height:auto; overflow:visible; padding-bottom:40px;}
  body.dash-page .dash-cols{flex:0 0 auto; grid-template-rows:none;}
  body.dash-page .governed-list,
  body.dash-page .pending-list{overflow-y:visible;}
  body.dash-page .col-stack > .panel.pending{max-height:none;}
  body.dash-page .feed-list{max-height:70vh;}
}

/* Stale-policy banner — shown when the dashboard process is enforcing
   an older version of the policy files than the repo currently holds.
   Uses the hold colour rather than deny: nothing has failed, but what
   is running is not what you are reading. */
.stale-banner{
  position:relative;
  display:flex; flex-direction:column; gap:3px;
  border:1px solid color-mix(in srgb, var(--hold) 55%, var(--border));
  background:color-mix(in srgb, var(--hold) 12%, var(--surface));
  border-radius:8px; padding:11px 38px 11px 14px; margin-bottom:16px;
  font-size:.8rem; line-height:1.45;
}
.stale-banner strong{color:var(--hold); font-size:.82rem;}
.stale-banner span{color:var(--text-dim);}
.stale-banner[hidden]{display:none;}
/* Dismissed wins over whatever the page script does with .hidden next —
   see theme.js's makeDismissible for why it isn't just another .hidden. */
.stale-banner[data-dismissed]{display:none !important;}
.banner-close{
  position:absolute; top:7px; right:7px; line-height:1; cursor:pointer;
  background:none; border:0; border-radius:6px; padding:4px 6px;
  font-size:.82rem; color:var(--text-faint);
}
.banner-close:hover{background:color-mix(in srgb, var(--text) 12%, transparent); color:var(--text);}

.panel{
  background:var(--surface); border:1px solid var(--border); border-radius:8px;
  padding:16px; box-shadow:var(--shadow);
}

/* ---------- Docs page — Confluence-style: sidebar of articles, one
   shown at a time on the right. minmax(0,1fr) on the content column for
   the same reason every other grid in this file needs it (see .row,
   .cols) — without it a long code span or table can force the whole
   grid wider than the viewport instead of wrapping. */
.wiki{display:grid; grid-template-columns:210px minmax(0,1fr); gap:24px; align-items:start;}
.wiki-sidebar{position:sticky; top:16px; display:flex; flex-direction:column; gap:2px;}

/* docs.html keeps its header and its list of articles still, and scrolls
   the article alone — reading one section shouldn't take the way back to
   the others off the screen with it. */
body.docs-page{height:100vh; display:flex; flex-direction:column; overflow:hidden;}
body.docs-page > header.top{flex:0 0 auto;}
body.docs-page > .wiki{flex:1 1 auto; min-height:0; align-items:stretch;}
.docs-page .wiki-sidebar{position:static; top:auto; min-height:0; overflow-y:auto; padding-right:4px;}
.docs-page .wiki-content{min-height:0; overflow-y:auto; padding-right:4px;}
.wiki-group-label{
  font-size:.64rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
  color:var(--text-faint); margin:16px 0 4px; padding:0 10px;
}
.wiki-group-label:first-child{margin-top:0;}
.wiki-link{
  text-align:left; background:none; border:none; border-radius:6px; padding:8px 10px;
  font-size:.84rem; color:var(--text-dim); cursor:pointer; font-family:inherit;
}
.wiki-link:hover{background:var(--surface-2); color:var(--text);}
.wiki-link.active{background:color-mix(in srgb, var(--accent) 12%, var(--surface-2)); color:var(--text); font-weight:600;}

.wiki-article{padding:20px 24px;}
.wiki-article[hidden]{display:none;}
.wiki-article h1{font-size:1.05rem; margin:0 0 4px;}
.wiki-article .lede{color:var(--text-faint); font-size:.8rem; margin:0 0 18px;}
.wiki-article h2{font-size:.86rem; margin:22px 0 8px;}
.wiki-article h2:first-of-type{margin-top:2px;}
.wiki-article p{font-size:.84rem; color:var(--text-dim); line-height:1.65; margin:0 0 10px;}
.wiki-article code{font-family:'IBM Plex Mono',monospace; font-size:.8rem; background:var(--surface-2); padding:1px 5px; border-radius:4px;}
.wiki-article ul, .wiki-article ol{margin:0 0 10px; padding-left:20px; font-size:.84rem; color:var(--text-dim); line-height:1.65;}
.wiki-article li{margin-bottom:7px;}
.wiki-article li b, .wiki-article p b{color:var(--text);}
.arch-diagram{width:100%; height:auto; max-width:560px; display:block; margin:4px auto 18px;}
.wiki-article li:last-child{margin-bottom:0;}

@media (max-width:760px){
  .wiki{display:block;}
  .wiki-sidebar{
    position:static; flex-direction:row; flex-wrap:wrap; gap:6px;
    margin-bottom:16px; border-bottom:1px solid var(--border); padding-bottom:14px;
  }
  .wiki-group-label{display:none;}
  .wiki-link{border:1px solid var(--border); border-radius:20px; padding:6px 12px;}
  .wiki-link.active{border-color:var(--accent);}
}
.panel .hint{font-size:.7rem; font-weight:400; color:var(--text-faint); text-transform:none; letter-spacing:0; margin-left:10px;}
.panel .count{
  display:inline-block; vertical-align:middle; margin-left:10px;
  font-family:'IBM Plex Mono',monospace; font-size:.72rem; background:var(--surface-2);
  border:1px solid var(--border); border-radius:20px; padding:1px 9px; color:var(--text-dim);
}

/* Between the heading and the list, and never at the list's expense —
   the list is the flexing child of this panel, so the banner has to
   refuse to grow or it eats rows as the count gets longer. */
.feed-banner{flex:0 0 auto; margin:2px 0 10px;}
.feed-list{display:flex; flex-direction:column; gap:6px; max-height:70vh; overflow-y:auto;}
.row{
  display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:10px; align-items:baseline;
  padding:8px 10px; border:1px solid var(--border); border-radius:6px; background:var(--surface-2);
  font-size:.85rem; animation:enter .25s ease-out;
}
.row .time{font-family:'IBM Plex Mono',monospace; font-size:.68rem; color:var(--text-faint); white-space:nowrap;}
.row .body{min-width:0;}
.row .agent{color:var(--accent); font-family:'IBM Plex Mono',monospace; font-size:.78rem;}
.row .tool{font-family:'IBM Plex Mono',monospace; font-size:.8rem; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:block;}
.row .label{color:var(--text-faint); font-size:.75rem;}
.row .note{color:var(--text-dim); font-size:.75rem; font-style:italic; display:block; margin-top:2px;}
.row .decision-col{display:flex; flex-direction:column; align-items:flex-end; gap:2px;}
.row .reviewer{font-family:'IBM Plex Mono',monospace; font-size:.62rem; color:var(--text-faint); white-space:nowrap;}
/* A call still waiting on a person, in a feed that is mostly settled
   history — the same hold colour the approval card uses, at a fraction
   of the strength, so it reads as "this one is live" without shouting. */
.row.is-hold{
  border-color:color-mix(in srgb, var(--hold) 45%, var(--border));
  background:color-mix(in srgb, var(--hold) 7%, var(--surface-2));
}
.row .trace{grid-column:1 / -1; margin-top:2px;}
.trace summary{
  cursor:pointer; font-family:'IBM Plex Mono',monospace; font-size:.66rem; color:var(--text-faint);
  list-style:none; user-select:none;
}
.trace summary::-webkit-details-marker{display:none;}
.trace summary::before{content:'\25b8 '; display:inline-block;}
.trace[open] summary::before{content:'\25be ';}
.trace-list{display:flex; flex-direction:column; gap:3px; margin-top:6px; padding-left:12px; border-left:2px solid var(--border);}
.trace-row{display:grid; grid-template-columns:minmax(0,1fr) auto auto minmax(0,2.4fr); gap:8px; align-items:baseline; font-size:.72rem;}
.trace-row .trace-preset{
  font-family:'IBM Plex Mono',monospace; color:var(--text-dim); overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; min-width:0;
}
.trace-row .trace-tier{font-family:'IBM Plex Mono',monospace; color:var(--text-faint); white-space:nowrap;}
.trace-row .trace-verdict{
  font-family:'IBM Plex Mono',monospace; text-transform:uppercase; font-size:.62rem; font-weight:600;
  color:var(--text-faint); white-space:nowrap;
}
.trace-row.fired .trace-verdict{color:var(--hold);}
.trace-row .trace-label{color:var(--text-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.trace-row.fired .trace-label{color:var(--text-dim);}
.decision{
  font-weight:600; padding:2px 8px; border-radius:4px; font-size:.68rem; letter-spacing:.03em;
  font-family:'IBM Plex Mono',monospace; white-space:nowrap; text-transform:uppercase;
}
.decision.allow, .decision.allow-after-approval{color:var(--allow); background:color-mix(in srgb, var(--allow) 14%, transparent);}
.decision.hold{color:var(--hold); background:color-mix(in srgb, var(--hold) 16%, transparent);}
.decision.deny, .decision.deny-after-hold{color:var(--deny); background:color-mix(in srgb, var(--deny) 14%, transparent);}
.unchecked-pill{display:inline-block; margin-left:6px; padding:1px 7px; border-radius:999px; font-size:11px; color:var(--text-dim); border:1px dashed var(--border); white-space:nowrap;}
/* Two different gaps, told apart on purpose. .unchecked-pill: no gate was
   attached, so nothing looked at the call. This one: a gate did look, its
   classifier could not answer, and the call was held for want of a
   verdict. Neither is a gate objecting, and neither should read as one. */
.unclassified-tag{
  display:inline-block; margin-right:6px; padding:1px 7px; border-radius:999px;
  font-size:11px; white-space:nowrap; color:var(--hold);
  border:1px solid color-mix(in srgb, var(--hold) 45%, transparent);
}

.pending-list{display:flex; flex-direction:column; gap:10px;}
.empty{color:var(--text-faint); font-size:.85rem; padding:8px 2px;}
/* One hold per row: what is waiting on the left, the two buttons on the
   right, and the note field (when the gate demands one) across the
   bottom. Stacked, each card ran to five lines and three holds filled the
   panel — the queue is meant to be scannable. */
.card{
  display:grid; grid-template-columns:minmax(0,1fr) auto; gap:6px 12px; align-items:center;
  border:1px solid var(--hold); border-radius:6px; padding:10px 12px;
  background:color-mix(in srgb, var(--hold) 8%, var(--surface)); animation:enter .25s ease-out;
}
.card .card-main{min-width:0;}
/* One line each, with the whole value on hover and all of it behind
   "Show the call" — the same bargain the audit table makes. A tool name
   or a finding that wrapped set the height of the card, and four cards
   filled the panel. */
.card .tool, .card .why{overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
/* The manager panel's queue stacks the same cards in a narrower column.
   (The spacing used to be an inline style on each card; it belongs to the
   list, not to the card, now that both panels share the card.) */
#pendingContainer{display:flex; flex-direction:column; gap:10px;}
.card .top-row{display:flex; justify-content:space-between; gap:8px; align-items:baseline; margin-bottom:2px;}
.card .agent{font-family:'IBM Plex Mono',monospace; font-size:.78rem; color:var(--accent);}
.card .age{font-size:.68rem; color:var(--text-faint); font-family:'IBM Plex Mono',monospace;}
.card .tool{font-family:'IBM Plex Mono',monospace; font-size:.85rem; word-break:break-word;}
.card .why{font-size:.78rem; color:var(--text-dim); margin-top:2px;}
/* A hold whose gate reached its classifier and got no answer, rather than
   one a gate objected to. Distinct from .unchecked-pill in the audit
   table, which means no gate was attached at all — a different gap.
   Marked in words, not colour alone: a reviewer sorting a queue of these
   is answering a different question from the usual one. */
.card.unclassified{border-color:color-mix(in srgb, var(--hold) 45%, var(--border));}
.card .unclassified-tag{
  font-family:'IBM Plex Mono',monospace; font-size:.62rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.04em; white-space:nowrap;
  color:var(--hold); border:1px solid color-mix(in srgb, var(--hold) 45%, transparent);
  border-radius:20px; padding:1px 7px;
}
/* The clock a reviewer is racing, when the gate set one. */
.card .self-clock{
  grid-column:1/-1; font-family:'IBM Plex Mono',monospace; font-size:.68rem; font-weight:600;
}
.card .self-clock.self-deny{color:var(--deny);}
.card .self-clock.self-allow{color:var(--allow);}
.card .hold-details{grid-column:1/-1; margin-top:2px;}
.card .hold-details .trace-row{grid-template-columns:minmax(0,1fr) auto minmax(0,2fr);}
.card .hold-args{
  margin:6px 0 0; padding:8px 10px; font-size:.72rem; line-height:1.5;
  background:var(--surface); border:1px solid var(--border); border-radius:6px;
  max-height:160px; overflow:auto; white-space:pre-wrap; word-break:break-word;
}
.card .note-input{
  grid-column:1/-1; margin-top:2px;
  width:100%; font-family:'IBM Plex Sans', sans-serif; font-size:.78rem; color:var(--text);
  background:var(--surface); border:1px solid var(--hold); border-radius:6px;
  padding:7px 9px; resize:vertical; box-sizing:border-box;
}
.card .actions{display:flex; gap:8px; flex-shrink:0;}
/* Narrow panels put the buttons back under the text rather than squeezing
   the tool name into a column two characters wide. */
@media (max-width:1150px){
  .card{grid-template-columns:minmax(0,1fr);}
  .card .actions{justify-content:flex-start;}
}
.btn{
  font-family:'IBM Plex Sans', sans-serif; font-weight:600; font-size:.78rem;
  padding:6px 14px; border-radius:6px; border:1px solid transparent; cursor:pointer;
}
.btn.approve{background:var(--allow); color:var(--on-allow);}
.btn.deny{background:var(--deny); color:var(--on-deny);}
.btn:hover{filter:brightness(1.08);}
.btn:disabled{opacity:.5; cursor:default;}

@keyframes enter{ from{opacity:0; transform:translateY(-4px);} to{opacity:1; transform:none;} }

/* ---------- "How to use Inlock" guide (guide.js) ---------- */
.guide-btn{
  display:inline-flex; align-items:center; gap:8px; font-family:inherit; font-size:.82rem; font-weight:600; white-space:nowrap;
  color:var(--accent); background:color-mix(in srgb, var(--accent) 10%, var(--surface));
  border:1.5px solid color-mix(in srgb, var(--accent) 55%, transparent); border-radius:999px; padding:6px 14px 6px 8px;
  cursor:pointer; box-shadow:0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent);
}
.guide-btn:hover{background:color-mix(in srgb, var(--accent) 18%, var(--surface)); border-color:var(--accent);}
.guide-btn .q{
  width:20px; height:20px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center;
  background:var(--accent); color:var(--accent-text); font-size:.72rem; font-weight:700;
}

html.guide-open, html.guide-open body{overflow:hidden;}
.guide-overlay{
  position:fixed; inset:0; z-index:300; display:flex; align-items:center; justify-content:center; padding:24px;
  background:color-mix(in srgb, black 48%, transparent);
  backdrop-filter:blur(10px) saturate(.9); -webkit-backdrop-filter:blur(10px) saturate(.9);
}
.guide-overlay[hidden]{display:none;}
.guide{
  /* Sizes to its content up to the viewport cap, rather than always
     standing at full height — the welcome screen is short, and a fixed
     height left most of the dialog empty below it. The min-height keeps
     the step-to-step nav from jumping around on the shorter articles. */
  width:min(1080px, 100%); max-height:min(92vh, 980px); min-height:min(520px, 80vh);
  display:flex; flex-direction:column; overflow:hidden;
  background:var(--bg); border:1px solid var(--border); border-radius:14px; box-shadow:0 24px 70px rgba(0,0,0,.35);
}
/* Each page's header lives inside the scrolling body, sticky, on the
   dialog's own background — title top-left, close top-right — with a
   short gradient under it so content fades out as it scrolls behind. */
.guide{--gpad:44px;}
.guide-body{flex:1; overflow:auto; padding:0 var(--gpad) 36px;}
.guide-top{
  position:sticky; top:0; z-index:2; display:flex; align-items:flex-start; justify-content:space-between; gap:16px;
  margin:0 calc(-1 * var(--gpad)) 26px; padding:24px var(--gpad) 12px; background:var(--bg);
}
.guide-top::after{
  content:''; position:absolute; left:0; right:0; top:100%; height:24px; pointer-events:none;
  background:linear-gradient(var(--bg), transparent);
}
.guide-top-title{display:flex; flex-direction:column; gap:4px; min-width:0;}
.guide-top h1{display:flex; align-items:center; gap:12px; margin:0; font-size:1.45rem; line-height:1.2;}
.guide-top h1 .mark{width:16px; height:16px; border:3px solid var(--accent); border-radius:3px; flex-shrink:0;}
.guide-eyebrow{font-size:.68rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--text-faint);}
.guide-close{
  flex-shrink:0; font-family:inherit; font-size:1rem; line-height:1; background:none; border:none; cursor:pointer;
  color:var(--text-dim); padding:6px 8px; border-radius:6px; margin:-2px -8px 0 0;
}
.guide-close:hover{color:var(--text); background:var(--surface-2);}
.guide-foot{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px 18px; border-top:1px solid var(--border); background:var(--surface);}
.guide-foot[hidden]{display:none;}
.gd-dots{display:flex; gap:8px; align-items:center;}
.gd-dot{width:9px; height:9px; border-radius:999px; border:none; padding:0; cursor:pointer; background:var(--border); transition:width .15s;}
.gd-dot:hover{background:var(--text-faint);}
.gd-dot.active{width:26px; background:var(--accent);}

.gd-lede{font-size:1.02rem; line-height:1.6; color:var(--text-dim); margin:0 0 22px; max-width:760px;}
.gd-points{display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:12px;}
.gd-point{display:flex; flex-direction:column; gap:6px; padding:2px 4px 0 0;}
/* Text, not cards: on this screen only what can be clicked has a frame. */
.gd-point b{font-size:.9rem; display:flex; align-items:baseline; gap:7px;}
.gd-point b::before{content:'✓'; color:var(--accent); font-weight:700; flex:none;}
.gd-point span{font-size:.82rem; line-height:1.5; color:var(--text-dim);}
.gd-sandbox{margin:18px 0 22px; font-size:.82rem; line-height:1.55; color:var(--text-dim); border-left:3px solid var(--hold); padding:4px 0 4px 12px;}
.gd-choices-label{font-size:.7rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--text-faint); margin:0 0 8px;}
.gd-choices{display:grid; grid-template-columns:1fr 1fr; gap:14px;}
.gd-choice{
  position:relative; text-align:left; font-family:inherit; color:inherit; cursor:pointer;
  display:flex; flex-direction:column; gap:6px; padding:18px 44px 18px 18px;
  background:var(--surface); border:1.5px solid var(--border); border-radius:12px;
}
.gd-choice b{font-size:1.05rem;}
.gd-choice span{font-size:.82rem; line-height:1.5; color:var(--text-dim);}
.gd-choice i{position:absolute; right:18px; top:50%; transform:translateY(-50%); font-style:normal; font-size:1.2rem; color:var(--accent);}
.gd-choice:hover{border-color:var(--accent);}
.gd-choice.primary{border-color:var(--accent); background:color-mix(in srgb, var(--accent) 9%, var(--surface));}

.gd-intro{font-size:.92rem; line-height:1.6; color:var(--text-dim); margin:0 0 18px;}
.gd-section{margin-top:26px;}
.gd-section h2{font-size:1rem; margin:0 0 6px;}
.gd-section p{font-size:.86rem; line-height:1.6; color:var(--text-dim); margin:0 0 12px;}
.gd-diagram{display:block; width:100%; max-width:760px; height:auto; margin:6px auto 4px; font-family:'IBM Plex Sans', system-ui, sans-serif;}
.gd-cards{display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:10px; margin-bottom:10px;}
.gd-card{background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:12px 14px; display:flex; flex-direction:column; gap:4px;}
.gd-card b{font-size:.86rem;}
.gd-card span{font-size:.8rem; line-height:1.5; color:var(--text-dim);}

.gd-shot{margin:22px auto 12px;}
.gd-frame{position:relative; width:100%; border:1px solid var(--border); border-radius:10px; overflow:hidden; background:var(--surface); box-shadow:0 6px 24px rgba(0,0,0,.08);}
.gd-img{position:absolute; inset:0; width:100%; height:100%; display:block;}
.gd-img-dark{display:none;}
:root[data-theme="dark"] .gd-img-dark{display:block;}
:root[data-theme="dark"] .gd-img-light{display:none;}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .gd-img-dark{display:block;}
  :root:not([data-theme="light"]) .gd-img-light{display:none;}
}
.gd-overlay{position:absolute; inset:0; width:100%; height:100%;}
.gd-dim{fill:#000; opacity:.26;}
:root[data-theme="dark"] .gd-dim{opacity:.42;}
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]) .gd-dim{opacity:.42;} }
/* Annotation color — magenta, deliberately absent from the app's own
   palette (accent blue, allow/hold/deny), and lettered tags instead of
   numbered circles, so a guide marker never reads as a real UI step. */
.guide, .wiki{--mark:#c2255c; --mark-text:#fff;}
:root[data-theme="dark"] .guide, :root[data-theme="dark"] .wiki{--mark:#f06595; --mark-text:#1a0610;}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .guide, :root:not([data-theme="light"]) .wiki{--mark:#f06595; --mark-text:#1a0610;}
}
.gd-ring{fill:none; stroke:var(--mark); stroke-width:2.5; stroke-dasharray:7 4; vector-effect:non-scaling-stroke;}
.gd-callout path{fill:none; stroke:var(--mark); stroke-width:2.5; vector-effect:non-scaling-stroke;}
.gd-callout rect{fill:var(--mark);}
.gd-callout text{fill:var(--mark-text); font-family:'IBM Plex Sans', system-ui, sans-serif; font-size:15px; font-weight:700;}
.gd-arrowhead{fill:var(--mark);}
.gd-badge, .gd-num{
  display:inline-flex; align-items:center; justify-content:center; flex-shrink:0;
  font-family:'IBM Plex Mono', monospace; font-weight:700; line-height:1;
  background:var(--mark); color:var(--mark-text); border-radius:6px;
}
.gd-badge{
  position:absolute; transform:translate(-40%, -40%) rotate(-6deg); width:25px; height:25px; font-size:.8rem;
  border:2px solid var(--bg); box-shadow:0 3px 8px rgba(0,0,0,.3);
}
.gd-steps{list-style:none; margin:0 0 8px; padding:0; display:flex; flex-direction:column; gap:8px;}
.gd-steps li{display:flex; gap:10px; align-items:flex-start; font-size:.88rem; line-height:1.55; color:var(--text-dim);}
.gd-steps li b{color:var(--text);}
.gd-num{width:22px; height:22px; margin-top:1px; font-size:.74rem; transform:rotate(-6deg);}

/* Last How-to step's call to action — a slowly drifting accent→allow
   gradient that grows on hover. Motion is dropped for reduced-motion users. */
.gd-finish{
  font-family:inherit; font-size:.86rem; font-weight:700; cursor:pointer; border:none; border-radius:999px; padding:9px 20px;
  color:var(--accent-text); background:linear-gradient(110deg, var(--accent), var(--allow), var(--accent)); background-size:220% 100%;
  box-shadow:0 4px 16px color-mix(in srgb, var(--accent) 35%, transparent);
  animation:gdShift 4s linear infinite; transition:transform .18s ease, box-shadow .18s ease;
}
.gd-finish:hover{transform:scale(1.07); box-shadow:0 8px 26px color-mix(in srgb, var(--allow) 45%, transparent);}
.gd-finish:active{transform:scale(1.02);}
@keyframes gdShift{from{background-position:0% 50%;} to{background-position:220% 50%;}}
@media (prefers-reduced-motion: reduce){ .gd-finish{animation:none;} .gd-finish:hover{transform:none;} }
.gd-note{margin:18px 0 0; font-size:.84rem; line-height:1.55; color:var(--text-dim); background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:6px; padding:10px 14px;}
.gd-docnav{display:flex; justify-content:space-between; gap:12px; margin-top:26px; padding-top:14px; border-top:1px solid var(--border); font-size:.84rem;}
.gd-docnav a{color:var(--accent); text-decoration:none; font-weight:600;}
.gd-docnav a:hover{text-decoration:underline;}

@media (max-width:760px){
  .guide-overlay{padding:0;}
  .guide{height:100%; max-height:none; min-height:0; border-radius:0;}
  .guide{--gpad:16px;}
  .guide-top{padding-top:18px;}
  .guide-top h1{font-size:1.2rem;}
  .gd-points, .gd-choices, .gd-cards{grid-template-columns:1fr;}
  .gd-callout{display:none;}
  .gd-badge{width:20px; height:20px; font-size:.68rem;}
  .guide-foot .btn{font-size:.74rem; padding:6px 9px;}
}

/* ---------- Sign-in & access (auth.js, signin.html, org.html#members) ---------- */
/* Controls a role can't use are hidden; the server refuses them anyway. */
/* Before api/auth/me answers we do not know the role, so nothing that a
   role could take away is drawn at all. The rules below are subtractive
   and need data-role, which arrives a fetch after first paint — without
   this, every page briefly showed an admin's header to everyone. An
   allowed control appearing a moment late is a far smaller lie than a
   forbidden one appearing and being withdrawn. Fails closed: if that
   request never answers, the attribute stays and the controls stay
   hidden, which matches what the server would do with them anyway. */
html[data-auth-pending] .needs-approve,
html[data-auth-pending] .needs-edit,
html[data-auth-pending] .needs-admin,
html[data-auth-pending] .auth-on-only,
html[data-auth-pending] .no-auth-only,
html[data-auth-pending] .needs-role,
html[data-auth-pending] .needs-audit,
html[data-auth-pending] a.nav-link[href="manager.html"]{display:none !important;}

/* One line per role, naming the capabilities it does not have — the
   mirror of CAPABILITIES in dashboard/auth.mjs. Written this way round
   because roles are no longer a ladder: an Auditor reads the audit log
   that a Member cannot, and never sees the approval queue that a Member
   works. "Hide everything below rung N" has nothing to say about that.
   The server refuses all of it regardless; this only keeps people from
   pressing buttons that would come back 403. */
html[data-role="auditor"] .needs-approve,
html[data-role="auditor"] .needs-edit,
html[data-role="auditor"] .needs-admin,
html[data-role="member"] .needs-audit,
html[data-role="member"] .needs-admin{display:none !important;}

/* The approval queue is withheld from an Auditor by the server (it is
   absent from /api/snapshot and never streamed), so this only stops the
   empty panel and its heading standing over nothing. */
html[data-role="auditor"] .panel.pending{display:none !important;}
/* "There's no real sign-in" caveats only apply when sign-in is off. */
html[data-auth="on"] .no-auth-only{display:none !important;}
html:not([data-auth="on"]) .auth-on-only{display:none !important;}

.user-menu{position:relative;}
.user-menu-btn{cursor:pointer; font-family:inherit;}
.user-menu-btn:hover{border-color:var(--accent);}
.user-menu-pop{
  position:absolute; right:0; top:calc(100% + 8px); z-index:50; min-width:230px;
  background:var(--surface); border:1px solid var(--border); border-radius:10px; box-shadow:0 12px 32px rgba(0,0,0,.22);
  padding:6px; display:flex; flex-direction:column;
}
.user-menu-pop[hidden]{display:none;}
.user-menu-who{display:flex; flex-direction:column; gap:2px; padding:8px 10px 10px; border-bottom:1px solid var(--border); margin-bottom:4px;}
.user-menu-who b{font-size:.86rem;}
.user-menu-who span{font-size:.76rem; color:var(--text-faint);}
.user-role-pill{
  align-self:flex-start; margin-top:4px; font-family:'IBM Plex Mono',monospace; font-size:.64rem !important; font-weight:700;
  text-transform:uppercase; letter-spacing:.04em; color:var(--accent) !important;
  border:1px solid color-mix(in srgb, var(--accent) 45%, transparent); border-radius:4px; padding:1px 6px;
}
.user-menu-item{
  display:block; width:100%; text-align:left; font-family:inherit; font-size:.82rem; color:var(--text); text-decoration:none;
  background:none; border:none; border-radius:6px; padding:8px 10px; cursor:pointer;
}
.user-menu-item:hover{background:var(--surface-2);}

.auth-toast{
  position:fixed; left:50%; bottom:24px; transform:translate(-50%, 20px); opacity:0; pointer-events:none; z-index:400;
  max-width:min(560px, calc(100vw - 32px)); padding:10px 16px; border-radius:8px; font-size:.84rem;
  background:var(--text); color:var(--bg); box-shadow:0 10px 30px rgba(0,0,0,.25); transition:opacity .2s, transform .2s;
}
.auth-toast.show{opacity:1; transform:translate(-50%, 0);}

body.signin-page{min-height:100vh; display:flex; align-items:center; justify-content:center; padding:24px;}
.signin-card{
  width:min(440px, 100%); background:var(--surface); border:1px solid var(--border); border-radius:14px;
  box-shadow:0 20px 60px rgba(0,0,0,.12); padding:28px 30px 30px;
  border-top:4px solid var(--org-accent, var(--border));
}
.signin-card section[hidden]{display:none;}
/* The open sandbox's sign-up (platform/public/signup.html): wider, with
   its fields in two columns, so the form fits a laptop screen without
   scrolling; one column on a phone. */
.signin-card.signup-card{width:min(640px, 100%);}
.signup-card .signin-form.two-col{display:grid; grid-template-columns:1fr 1fr; gap:12px 16px;}
.signup-card .two-col .span-2{grid-column:1 / -1;}
@media (max-width:560px){ .signup-card .signin-form.two-col{grid-template-columns:1fr;} }
.signin-brand{display:flex; align-items:baseline; gap:9px; font-weight:700; font-size:1.05rem; margin-bottom:22px;}
.signin-brand .mark{align-self:center; width:12px; height:12px; border:2px solid var(--accent); border-radius:2px;}
.signin-card h1{font-size:1.3rem; margin:0 0 8px;}
.signin-lede{font-size:.9rem; line-height:1.55; color:var(--text-dim); margin:0 0 14px;}
.signin-form{display:flex; flex-direction:column; gap:12px; margin-top:6px;}
/* Without this, every form on account.html shows at once: `hidden` is a
   UA display:none, and the rule above outranks it. */
.signin-form[hidden]{display:none;}
/* The small print under a form: clear of the button, and of each other. */
.signin-card .signin-form + .hint-text{margin-top:14px;}
.signin-card .hint-text + .hint-text{margin-top:8px;}
/* A group of fields inside a form keeps the form's own spacing. */
.signin-form .field-group:not([hidden]){display:flex; flex-direction:column; gap:12px;}
.signin-form label{display:flex; flex-direction:column; gap:5px; font-size:.72rem; color:var(--text-faint); text-transform:uppercase; letter-spacing:.04em;}
.signin-form input{
  font-family:inherit; font-size:.92rem; color:var(--text); background:var(--surface-2); text-transform:none; letter-spacing:normal;
  border:1px solid var(--border); border-radius:8px; padding:10px 12px;
}
.signin-form .btn{margin-top:6px; padding:10px 14px; font-size:.88rem;}
.signin-error{margin-top:14px; font-size:.84rem;}
.signin-help{margin-top:12px; line-height:1.5;}
.optional-tag{font-size:.7rem; font-weight:600; color:var(--text-faint); text-transform:uppercase; letter-spacing:.05em; vertical-align:3px; margin-left:4px;}
.totp-qr{text-align:center; margin:14px 0;}
.totp-qr img{border-radius:8px; image-rendering:pixelated; background:#fff;}
.totp-secret{font-size:1rem; letter-spacing:.06em; word-break:break-all;}
.text-link{color:var(--accent); font-weight:600;}
.recovery-codes{padding:12px 14px; border:1px solid var(--border); border-radius:10px; background:var(--surface-2); line-height:1.7; margin:8px 0;}
.recovery-check{display:flex; gap:8px; align-items:center; margin:10px 0 14px; font-size:.86rem;}

/* account.html */
.account-grid{display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:16px; align-items:start;}
.account-grid .panel{margin:0;}
.account-facts{display:grid; grid-template-columns:auto 1fr; gap:8px 16px; margin:12px 0 0; font-size:.88rem;}
.account-facts dt{color:var(--text-faint);}
.account-facts dd{margin:0; color:var(--text); min-width:0; overflow-wrap:anywhere;}
.account-form{max-width:360px; margin-top:12px;}
.account-grid .hint-text{margin-top:8px; line-height:1.5;}
.ok-text{color:var(--allow);}

/* The columns belong to the list, not to each row. Every row used to be
   its own grid, so `1fr` and `auto` were resolved from that row's own
   content — the person with one button got a different layout from the
   person with three, and nothing lined up down the page. Subgrid makes
   all the rows share one set of column widths, each sized by the widest
   cell anywhere in the list. */
.members-list{
  display:grid; grid-template-columns:36px minmax(0,1.6fr) max-content max-content max-content;
  margin-top:14px;
}
.member-row{
  display:grid; grid-template-columns:subgrid; grid-column:1 / -1;
  gap:12px; align-items:center;
  padding:12px 0; border-top:1px solid var(--border);
}
.members-list > .empty{grid-column:1 / -1;}
.member-row:first-child{border-top:none;}
/* A key's third column is a sentence (owner, agents), not a role select,
   and it should take the slack rather than the name column. */
#keyList{grid-template-columns:36px max-content minmax(0,1fr) max-content max-content;}

/* Anything that can't do subgrid falls back to the old per-row grid —
   fixed widths rather than fractions, so the columns still agree even
   though each row measures itself. */
@supports not (grid-template-columns: subgrid){
  .members-list{display:flex; flex-direction:column;}
  .member-row{grid-template-columns:36px minmax(0,1fr) 150px 150px 290px;}
  #keyList .member-row{grid-template-columns:36px 180px minmax(0,1fr) 120px 200px;}
}
.member-row.is-disabled .member-who, .member-row.is-disabled .member-avatar{opacity:.5;}
.member-avatar{
  width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--surface-2); border:1px solid var(--border); font-size:.76rem; font-weight:700; color:var(--accent);
}
.member-who{display:flex; flex-direction:column; min-width:0;}
/* The name and the "you" tag are a row of their own. The tag used to sit
   inside the <b>, which clips its own overflow for the ellipsis — and a
   bordered box nudged up by vertical-align has its top edge clipped off
   by exactly that. Out here it is a flex item with room to be a box. */
.member-name{display:flex; align-items:center; gap:6px; min-width:0;}
.member-name b{font-size:.88rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
/* The email only. A `.member-who > span` rule also matched .member-name,
   which put the clipping back on the row the tag lives in — the same
   overflow:hidden that was cutting its top border off inside the <b>. */
.member-email{font-size:.78rem; color:var(--text-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.you-tag{
  flex-shrink:0; font-family:'IBM Plex Mono',monospace; font-size:.6rem; font-weight:700; line-height:1.4;
  text-transform:uppercase; color:var(--accent);
  border:1px solid color-mix(in srgb, var(--accent) 45%, transparent); border-radius:4px; padding:1px 5px;
}
/* Your own role is not yours to change, so it isn't a control. A disabled
   select still looks like one you could use if you tried harder. */
.member-role-fixed{font-size:.82rem; color:var(--text-dim); padding:6px 2px;}
/* Badge over "seen 3 min ago", both starting at the same x in every row. */
.member-meta{display:flex; flex-direction:column; gap:3px; align-items:flex-start; white-space:nowrap;}
.member-status{font-family:'IBM Plex Mono',monospace; font-size:.64rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em; border-radius:4px; padding:2px 6px;}
.member-status.active{color:var(--allow); background:color-mix(in srgb, var(--allow) 14%, transparent);}
.member-status.invited{color:var(--hold); background:color-mix(in srgb, var(--hold) 14%, transparent);}
.member-status.off{color:var(--text-faint); background:var(--surface-2);}
.member-role{
  font-family:inherit; font-size:.82rem; color:var(--text); background:var(--surface-2);
  border:1px solid var(--border); border-radius:6px; padding:6px 8px;
}
.member-actions{display:flex; gap:6px; flex-wrap:wrap; justify-content:flex-end;}
.member-actions .btn{font-size:.74rem; padding:5px 10px;}
.member-extra{grid-column:2 / -1;}
.link-box{background:var(--surface-2); border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:8px; padding:12px 14px;}
.link-box-top{display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap; font-size:.84rem; margin-bottom:8px;}
.link-box-row{display:flex; gap:8px;}
.link-box-row input{
  flex:1; min-width:0; font-family:'IBM Plex Mono',monospace; font-size:.76rem; color:var(--text);
  background:var(--surface); border:1px solid var(--border); border-radius:6px; padding:8px 10px;
}
.link-box .hint-text{margin-top:8px;}
.members-dialog{max-width:620px;}
/* However many roles there are — it was four, it is three — rather than a
   fixed count that leaves a hole when one is retired. */
.role-grid{display:grid; grid-template-columns:repeat(auto-fit, minmax(240px,1fr)); gap:10px;}
.role-card{background:var(--surface-2); border:1px solid var(--border); border-radius:8px; padding:12px 14px; display:flex; flex-direction:column; gap:5px;}
.role-card b{font-size:.86rem;}
.role-card span{font-size:.78rem; line-height:1.5; color:var(--text-dim);}
@media (max-width:900px){
  /* Too narrow for five columns: the row becomes three, and everything
     after the name stacks under it. Back to a row-level grid, since the
     rows no longer share a shape worth aligning. */
  .members-list, #keyList{display:flex; flex-direction:column;}
  .member-row, .key-row{grid-template-columns:36px minmax(0,1fr) auto;}
  .member-meta, .member-role, .key-owner, .member-actions{grid-column:2 / -1;}
  .member-actions{justify-content:flex-start;}
  .role-grid{grid-template-columns:repeat(2, minmax(0,1fr));}
}

/* Customer branding (per-customer instances under platform/server.mjs):
   "Inlock for [logo] [name]", all on the text baseline. The logo is
   seated by brand-logo.js (its own letters' baseline, not its box); until
   then, or when it can't be read, it's centered. */
.wordmark.branded{align-items:baseline;}
.org-chip{display:inline-flex; align-items:baseline; gap:7px; margin-left:2px; font-size:.95rem; font-weight:600; color:var(--text);}
.org-chip[hidden]{display:none;}
.org-for{font-size:.8rem; font-weight:500; color:var(--text-faint);}
.org-chip img{display:block; height:22px; width:auto; max-width:120px; object-fit:contain; align-self:center;}
.org-chip img.on-baseline{align-self:baseline; transform:translateY(calc(var(--logo-descent) * 100%));}
/* With a customer's brand in the header, it replaces the page subtitle. */
.wordmark.branded .by{display:none;}

.roles-heading{font-size:.86rem; margin:26px 0 10px;}

/* Nobody reports to you: no Manager Panel link. (Admins always see it.) */
html[data-auth="on"][data-manages="no"] a.nav-link[href="manager.html"] { display: none; }
.manager-filter { display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center; }
.manager-filter label { display: flex; gap: 8px; align-items: center; font-weight: 600; }
.owner-tag { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.owner-select {
  font: inherit; font-size: 11px; color: var(--text); max-width: 160px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 3px 6px;
}
.manager-filter select {
  font: inherit; font-weight: 400; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 6px 9px;
}
.create-gate-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.view-toggle { margin-bottom: 0; }
.view-toggle .mode-btn { font-size: 12px; }

/* org.html → Branding */
.brand-layout{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:24px; margin-top:16px; align-items:start;}
.brand-form{max-width:440px;}
.brand-logo-row{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
.signin-form .brand-logo-row .btn{margin-top:0;}
.brand-logo-current{display:inline-flex; align-items:center; min-height:40px; min-width:80px; padding:6px 10px; border:1px dashed var(--border); border-radius:8px; background:var(--surface-2);}
.brand-logo-current img{height:28px; width:auto; max-width:160px; object-fit:contain; display:block;}
.signin-form label.brand-check{flex-direction:row; align-items:flex-start; gap:8px; text-transform:none; letter-spacing:normal; font-size:.84rem; color:var(--text);}
.brand-check input{margin-top:2px;}
.signin-form label.brand-accent{flex-direction:row; align-items:center; gap:10px;}
.signin-form .brand-accent input{width:48px; height:32px; padding:2px;}
.brand-actions{display:flex; align-items:center; gap:12px;}
/* This form borrows .signin-form for its field styling, which also
   carries that page's tall full-width primary button. Right there, wrong
   here: this is a small action sitting inline beside a note.
   `flex:0 0 auto` is the part that matters — as a shrinkable flex item
   it lost the width fight with the note next to it, wrapped "Save
   branding" onto two lines, and that wrap, not the padding, was what
   made it twice as tall as it should be. */
.brand-actions .btn{flex:0 0 auto; white-space:nowrap; margin-top:0; padding:8px 28px;}
/* Prose that is read before an irreversible-looking button is prose that
   has to be readable: a measure, and space between the paragraphs. */
#workspaceTab .hint-text{max-width:78ch;}
#workspaceTab .hint-text + .hint-text{margin-top:10px;}
/* The confirm field and its button on one line, the button refusing to
   shrink — the same wrap that made Save branding twice as tall. */
.reset-box{display:flex; align-items:flex-end; gap:12px; margin-top:14px; max-width:440px;}
.reset-box label{display:flex; flex-direction:column; gap:5px; flex:1 1 auto; font-size:.72rem; color:var(--text-faint); text-transform:uppercase; letter-spacing:.04em;}
.reset-box label b{text-transform:none; color:var(--text); font-family:'IBM Plex Mono',monospace;}
.reset-box input{
  font-family:inherit; font-size:.92rem; color:var(--text); background:var(--surface-2);
  text-transform:none; letter-spacing:normal; border:1px solid var(--border); border-radius:8px; padding:10px 12px;
}
.reset-box .btn{flex:0 0 auto; white-space:nowrap; padding:11px 22px;}
.reset-box .btn:disabled{opacity:.45; cursor:not-allowed;}
.brand-preview-wrap{display:flex; flex-direction:column; gap:8px;}
/* A mock of the sign-in card, because that is the page this branding
   actually appears on and the first thing everyone in the workspace
   sees. Tall enough to read as a page rather than as a strip: a wordmark
   alone gave no sense of what the accent stripe sits above. */
.brand-preview{
  padding:18px 20px 20px; border:1px solid var(--border); border-top:4px solid var(--org-accent, var(--border));
  border-radius:10px; background:var(--surface); min-height:260px;
}
.brand-preview .preview-title{font-size:1.05rem; font-weight:700; margin:18px 0 6px;}
.brand-preview .preview-lede{font-size:.82rem; color:var(--text-dim); margin:0 0 14px; line-height:1.5;}
/* Field shapes, not fields: nothing here is focusable or submittable,
   because it is a picture of a form inside another form. */
.brand-preview .preview-field{margin-bottom:10px;}
.brand-preview .preview-label{
  display:block; font-size:.66rem; color:var(--text-faint);
  text-transform:uppercase; letter-spacing:.04em; margin-bottom:5px;
}
.brand-preview .preview-input{
  height:34px; border:1px solid var(--border); border-radius:8px; background:var(--surface-2);
}
.brand-preview .preview-btn{
  margin-top:12px; padding:8px 14px; border-radius:6px; text-align:center;
  background:var(--allow); color:var(--on-allow); font-size:.84rem; font-weight:600;
}
/* No align-self here: it would outrank .org-chip img.on-baseline and pin
   the preview's logo to the middle of the line, which is the one place
   that must agree with the header it is previewing. */
.brand-preview .org-chip img{height:22px; width:auto; max-width:120px; object-fit:contain; display:block;}
@media (max-width:760px){ .brand-layout{grid-template-columns:1fr;} }
