/* Keygrip app styles — brand tokens mirror docs/style-guide.md (keygrip.ai). */
@font-face { font-family: 'Plus Jakarta Sans'; src: url("fonts/PlusJakartaSans-400.503e7cbdb484.woff2") format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Plus Jakarta Sans'; src: url("fonts/PlusJakartaSans-500.e5e6761eaecb.woff2") format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'Plus Jakarta Sans'; src: url("fonts/PlusJakartaSans-600.79e62f3332cd.woff2") format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: 'Plus Jakarta Sans'; src: url("fonts/PlusJakartaSans-700.5b629bd8abb4.woff2") format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }

@font-face { font-family: 'Tid'; src: url("fonts/Tid-Regular.62f881ca3d94.woff2") format('woff2'); font-weight: 400 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'Tid'; src: url("fonts/Tid-Bold.aa2352324567.woff2") format('woff2');    font-weight: 600 700; font-style: normal; font-display: swap; }

:root {
  --kg-cyan: #61D9EF;
  --kg-cyan-strong: #2bb6d1;
  --kg-ink: #0F172A;
  --kg-ink-hover: #1c2942;  /* hover state of an ink-filled button (style-guide "Color tokens") */
  --kg-grey: #475569;
  --kg-canvas: #EEF2F6;
  --kg-surface: #FFFFFF;
  --kg-line: #E2E8F0;
  /* Danger / error (docs/style-guide.md → "Status colors"). The one non-brand hue in the system,
     reserved for ACTUAL state — a failed action, a form error, a destructive button. Never for
     emphasis, and never on the KD ramp (a hard keyword is not an error). Defined here rather than
     inline because signup.css consumes it too, and base.html loads this sheet first. */
  --kg-danger: #b42318;          /* text, borders, destructive-button ink */
  --kg-danger-surface: #fef3f2;  /* .kg-alert background */
  --kg-danger-line: #fda29b;     /* .kg-alert border */
  /* The good-direction counterpart, used ONLY as text on a delta ("▲ 12.4% vs prev 28 days").
     5.95:1 here / 5.29:1 on --kg-canvas, so it clears 4.5:1 as text on both. The arrow and the
     period label carry the direction too — the colour never carries it alone. */
  --kg-success: #177245;
  /* Soft backdrop pair for a good-delta CHIP (monthly report scorecards), mirroring the danger
     surface/line pair above. Backdrop only — the chip's text stays --kg-success, which is what
     carries the contrast. */
  --kg-success-surface: #ecfdf3;
  --kg-success-line: #abefc6;
  /* The in-between state (docs/style-guide.md → "Status colors"): the "Behind" chip on the
     client health card. Text 5.43:1 on --kg-surface; the chip carries its word too. */
  --kg-warning: #b54708;
  --kg-warning-surface: #fffaeb;
  --kg-warning-line: #fedf89;
  /* Keyword-difficulty ramp (docs/style-guide.md → "Data-viz ramp"). Sequential: one hue,
     light→dark, validated for monotone lightness + adjacent ΔL + light-end contrast on
     --kg-surface. -null is NOT step 0 — "no data" must not read as "easy". */
  --kg-kd-1: #4FB3CB;
  --kg-kd-2: #2F91AC;
  --kg-kd-3: #1F6E86;
  --kg-kd-4: #144B5E;
  --kg-kd-5: #0A2B37;
  --kg-kd-null: #94A3B8;
  /* Neutral fill for magnitude bars (keyword-map volume). Deliberately NOT an accent: a
     volume bar is context for the row it sits in, and the accent is reserved for the
     column the UI is actually recommending on. Shares a hex with --kg-kd-null but not a
     meaning — one says "no data", this one says "not the signal". */
  --kg-bar-fill: #94A3B8;

  /* Pipeline phase hues (docs/style-guide.md → "Pipeline phases"). Five muted, equal-weight
     colours for the run lineage ribbon — deliberately NOT the cyan accent and NOT the
     --kg-kd-* difficulty ramp, which is sequential and would imply an ordering phases do
     not have. Carried over unchanged from docs/studio-map-build.py, where this palette was
     already in use, so the generated engine map and the in-app viewer read alike. */
  --kg-phase-report: #8b9272;
  --kg-phase-draft: #6689a6;
  --kg-phase-tail: #b0688a;
  --kg-phase-package: #9a8eb4;
  --kg-phase-compile: #7d9a9b;
  /* Trend-series hue (docs/style-guide.md → "Trend series"). NOT --kg-cyan-strong: that accent is
     2.41:1 on --kg-surface, under the 3:1 floor for a graphical object, and a 2px line is exactly
     what that floor is for. This is 4.56:1. One series per chart, so it never needs a second. */
  --kg-series: #17809C;
  /* Loss fill (docs/style-guide.md → "Loss fill"): the brand's own bar when it trails the rivals
     an AI answer named instead of it. Deliberately outside the cyan family so a competitor
     count never reads as a Keygrip-positive value. 5.8:1 on --kg-surface. */
  --kg-rival: #A14D3A;
  /* Rubric dimension series (docs/style-guide.md → "Rubric dimension series"). FIVE series
     on one plot is allowed here ONLY because they share a single scale: each is drawn as a
     percentage of its own max, since the maxima differ (eeat /12, aeo /8, technical /8,
     keyword /6, editorial /6). All clear the 3:1 graphical-object floor on --kg-surface. */
  --kg-dim-technical: #7A5AA6;
  --kg-dim-keyword: #B0455F;
  --kg-dim-editorial: #5F7031;
  --kg-dim-eeat: #17809C;
  --kg-dim-aeo: #A65A2E;
  /* Categorical series (docs/style-guide.md → "Categorical series — the Growth page", #1951).
     Five hues in a FIXED order, assigned per entity (Google is always -1), never by rank, so a
     channel keeps its colour when the ranking moves. Three of them fall under the 3:1 floor on
     --kg-surface (the guide records the ratios); the chips, legend and tooltip name every line. */
  --kg-cat-1: #2a78d6;
  --kg-cat-2: #eb6834;
  --kg-cat-3: #1baf7a;
  --kg-cat-4: #eda100;
  --kg-cat-5: #e87ba4;
  --kg-serif: 'Tid', Georgia, "Times New Roman", serif;
  --kg-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --kg-radius: 14px;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  background: var(--kg-canvas);
  color: var(--kg-ink);
  font-family: var(--kg-sans);
}

.kg-shell { margin: auto; width: 100%; max-width: 34rem; padding: 2rem 1.25rem; }

.kg-wordmark {
  font-family: var(--kg-serif);
  font-size: 4rem;
  line-height: 1;
  text-align: center;
  color: var(--kg-ink);
  margin-bottom: 1.75rem;
}

.kg-card {
  background: var(--kg-surface);
  border: 1px solid var(--kg-line);
  border-radius: var(--kg-radius);
  box-shadow: 0 12px 40px rgba(15, 23, 42, 0.10);
  padding: 2.25rem 2rem;
}
/* Pages that stack one card per section (head-terms summary, rank + its error card) would
   otherwise butt the cards flush together, reading as one card instead of the start of a
   new one. */
.kg-card + .kg-card { margin-top: 1.5rem; }
/* An element that OPENS a card must not stack its own top margin on the card's padding.
   .kg-h2 carries margin-top: 1.6rem to separate sections *within* flowing content; against
   the card's 2.25rem padding that compounds to ~62px above a title with ~10px below it,
   which reads as a mis-set card rather than a heading. Brand-voice already worked around
   this locally (.kg-bv-h2 zeroes its own top margin), so fix it once at the source instead
   of per component. Direct children only — a heading nested inside a card's inner block
   still wants its normal separation. */
.kg-card > :first-child { margin-top: 0; }

.kg-title { font-family: var(--kg-serif); font-weight: 600; font-size: 1.6rem; margin: 0 0 0.35rem; }
.kg-sub   { color: var(--kg-grey); margin: 0 0 1.5rem; font-size: 0.95rem; }

.kg-meta { display: grid; grid-template-columns: auto 1fr; gap: 0.65rem 1.1rem; align-items: center; margin: 0 0 1.75rem; }
.kg-meta dt { color: var(--kg-grey); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; }
.kg-meta dd { margin: 0; font-weight: 500; word-break: break-word; }

.kg-roles { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.kg-pill {
  background: rgba(97, 217, 239, 0.18);
  border: 1px solid var(--kg-cyan);
  color: var(--kg-ink);
  border-radius: 999px;
  padding: 0.15rem 0.65rem;
  font-size: 0.78rem;
  font-weight: 600;
}
.kg-muted { color: var(--kg-grey); }
/* Visually hidden, still announced. For state a sighted user reads from a glyph or a color
   that a screen-reader user would otherwise never hear — e.g. which column a table is
   sorted by. NOT display:none, which removes it from the accessibility tree too. */
.kg-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

.kg-btn {
  font-family: var(--kg-sans);
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  background: transparent;
  color: var(--kg-ink);
  border: 1px solid var(--kg-line);
  border-radius: 8px;
  padding: 0.6rem 1.15rem;
  transition: border-color 0.15s, background 0.15s;
}
.kg-btn:hover { border-color: var(--kg-ink); background: rgba(15, 23, 42, 0.04); }
/* Disabled covers two cases that must look the same: a form that isn't valid yet (the
   head-terms submit starts disabled) and an in-flight request htmx has locked out via
   hx-disabled-elt. `not-allowed` + no hover feedback is the signal that a second click
   won't do anything. */
.kg-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.kg-btn:disabled:hover { border-color: var(--kg-line); background: transparent; }
/* Anchors styled as buttons (Continue/Re-rank links) must not read as underlined links. */
a.kg-btn { text-decoration: none; display: inline-block; }
.kg-btn-ghost { border-color: transparent; color: var(--kg-grey); }
/* Filled primary — at most ONE per form, for the submit the form exists for (the studio's
   Optimize / Generate). Outlined .kg-btn stays the default everywhere else. Declared after
   .kg-btn's :hover/:disabled rules so equal-specificity overrides land on the primary. */
.kg-btn-primary { background: var(--kg-ink); border-color: var(--kg-ink); color: var(--kg-surface); }
.kg-btn-primary:hover { background: var(--kg-ink-hover); border-color: var(--kg-ink-hover); color: var(--kg-surface); }
.kg-btn-primary:disabled:hover { background: var(--kg-ink); border-color: var(--kg-ink); }

/* App shell — authenticated multi-tenant layout (header + main) */
.kg-app { width: 100%; min-height: 100vh; display: flex; flex-direction: column; }
.kg-header {
  display: flex; align-items: center; gap: 0.4rem 1.25rem; flex-wrap: wrap;
  padding: 0.8rem 1.5rem;
  background: var(--kg-surface); border-bottom: 1px solid var(--kg-line);
}
.kg-brand { font-family: var(--kg-serif); font-size: 1.5rem; color: var(--kg-ink); text-decoration: none; }
.kg-nav { display: flex; gap: 1rem; }
.kg-nav a {
  color: var(--kg-grey); text-decoration: none; font-weight: 600; font-size: 0.9rem;
  padding: 0.2rem 0; border-bottom: 2px solid transparent;
}
.kg-nav a:hover { color: var(--kg-ink); }
.kg-nav a.is-active { color: var(--kg-ink); border-bottom-color: var(--kg-cyan); }
.kg-account { margin-left: auto; display: flex; align-items: center; gap: 0.9rem; font-size: 0.85rem; }
.kg-org { font-weight: 600; }
.kg-btn-sm { padding: 0.35rem 0.75rem; font-size: 0.82rem; }
.kg-main { flex: 1; width: 100%; max-width: 60rem; margin: 0 auto; padding: 2rem 1.5rem; }
/* Wide variant for data-dense surfaces (the keyword map): a six-column tree needs the
   keyword column to hold real phrases without wrapping. Opt-in per page via the
   main_class block in app_base.html. */
.kg-main-wide { max-width: 88rem; }
.kg-page-head { margin-bottom: 1.5rem; }

.kg-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.kg-list-item {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--kg-surface); border: 1px solid var(--kg-line);
  border-radius: 10px; padding: 0.9rem 1.1rem;
}
.kg-list-name { font-weight: 600; }
/* Project card (home.html). Replaces a flat row of nine ghost buttons that read as a
   toolbar: no ordering, and three onboarding surfaces missing from it entirely. The card is
   one project — identity and primary action on top, then the surfaces grouped by what you go
   there to do. Groups wrap independently, so a narrow window stacks them rather than
   reflowing one long line into an unreadable block. */
.kg-proj {
  background: var(--kg-surface); border: 1px solid var(--kg-line);
  border-radius: var(--kg-radius); padding: 1.1rem 1.25rem;
}
.kg-proj-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 0.9rem; margin-bottom: 0.9rem;
  border-bottom: 1px solid var(--kg-line);
}
.kg-proj-id { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; min-width: 0; }
.kg-proj-name { font-family: var(--kg-serif); font-weight: 600; font-size: 1.15rem; }
.kg-proj-domain { color: var(--kg-grey); font-size: 0.85rem; }
/* Which org a project belongs to. Only rendered for multi-org members, where the list
   merges several tenants and the name alone is ambiguous (see home.html). */
.kg-proj-org { color: var(--kg-grey); background: var(--kg-canvas);
  border: 1px solid var(--kg-line); border-radius: 999px;
  padding: 0.1rem 0.5rem; font-size: 0.75rem; }
/* Destructive, but not the thing the eye should land on — ghost until hover. */
.kg-proj-del { color: var(--kg-grey); }
.kg-proj-del:hover { color: var(--kg-danger); border-color: var(--kg-danger); background: transparent; }

/* Grid, not flex-wrap: with flex the widest group (Context, four links) pushed Measure onto
   a ragged second row by itself. Equal columns keep the three group headings on one baseline
   and collapse to one column on a narrow window. */
.kg-proj-groups {
  display: grid; gap: 1.25rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
.kg-proj-group { min-width: 0; }
.kg-proj-group-label {
  display: flex; align-items: center; gap: 0.4rem;
  margin: 0 0 0.45rem; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--kg-grey);
}
.kg-proj-links { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; }
.kg-proj-link {
  display: inline-flex; align-items: center; gap: 0.4rem;
  color: var(--kg-ink); text-decoration: none; font-size: 0.9rem; font-weight: 500;
  border-bottom: 1px solid transparent; padding-bottom: 1px;
}
.kg-proj-link:hover { border-bottom-color: var(--kg-ink); }
/* An unconfigured context surface. Cyan, per the style guide's "sparing accent" — this is the
   only accent on the page, and it marks the one thing worth doing next. The dot never carries
   the meaning alone: .kg-sr says "not set up" and the group count repeats it as text. */
.kg-proj-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--kg-cyan-strong);
}
.kg-proj-count {
  font-weight: 600; font-size: 0.72rem; letter-spacing: 0;
  color: var(--kg-grey); background: var(--kg-canvas);
  border-radius: 999px; padding: 0.05rem 0.4rem;
}
.kg-proj-count-todo { color: var(--kg-ink); }

.kg-empty {
  background: var(--kg-surface); border: 1px dashed var(--kg-line);
  border-radius: var(--kg-radius); padding: 2.5rem 1.5rem; text-align: center;
}
.kg-empty-title { font-family: var(--kg-serif); font-weight: 600; font-size: 1.1rem; margin: 0 0 0.35rem; }

.kg-acct-link { color: var(--kg-grey); text-decoration: none; font-weight: 600; font-size: 0.85rem; }
.kg-acct-link:hover { color: var(--kg-ink); }

/* Staff impersonation picker (staff_accounts.html). A grid, not a <table>: the action cell holds
   a POST form per row, and one shared column template keeps the header aligned with the rows. */
.kg-acct-search { margin-bottom: 1.25rem; }
.kg-acct-grid {
  display: grid; grid-template-columns: minmax(0, 2.2fr) minmax(0, 1.6fr) auto auto;
  gap: 0.75rem 1rem; align-items: center;
}
.kg-acct-cols {
  padding-bottom: 0.5rem; border-bottom: 1px solid var(--kg-line);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--kg-grey);
}
.kg-acct-row { padding: 0.6rem 0; border-bottom: 1px solid var(--kg-line); }
.kg-acct-row:last-child { border-bottom: 0; }
.kg-acct-who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; min-width: 0; }
.kg-acct-name { font-weight: 600; overflow-wrap: anywhere; }
.kg-acct-email, .kg-acct-org, .kg-acct-seen { font-size: 0.85rem; overflow-wrap: anywhere; }
.kg-acct-seen { white-space: nowrap; }
.kg-acct-act { justify-self: end; }
/* An inactive account can still be impersonated (that is often WHY you are looking at it), so
   the pill is informational — it must not read as the same thing as the "staff" pill. */
.kg-pill-off { background: var(--kg-canvas); color: var(--kg-grey); }

/* API credentials page */
.kg-input { font-family: var(--kg-sans); font-size: 0.95rem; padding: 0.55rem 0.8rem; border: 1px solid var(--kg-line); border-radius: 8px; min-width: 18rem; }
.kg-btn-danger { color: var(--kg-danger); border-color: var(--kg-line); }
/* The hover wash is --kg-danger at 6%. Left as a literal rgba() rather than color-mix(): the
   token is the color, the alpha is this one control's affordance, and nothing else consumes it. */
.kg-btn-danger:hover { border-color: var(--kg-danger); background: rgba(180, 35, 24, 0.06); }
.kg-alert { background: var(--kg-danger-surface); border: 1px solid var(--kg-danger-line); color: var(--kg-danger); border-radius: 8px; padding: 0.7rem 1rem; margin-bottom: 1rem; font-size: 0.9rem; }
.kg-secret { background: var(--kg-surface); border: 1px solid var(--kg-cyan); border-radius: var(--kg-radius); padding: 1.25rem 1.5rem; margin-bottom: 1.5rem; }

/* Generation readiness notice (studio Generate form). Informational, NOT an error:
   .kg-alert is the danger pair and this is an advisory the operator may ignore, so it
   borrows the cyan accent .kg-secret already uses rather than introducing a warning
   colour the style guide does not define. */
.kg-readiness {
  background: var(--kg-surface); border: 1px solid var(--kg-line);
  border-left: 3px solid var(--kg-cyan); border-radius: 8px;
  padding: 0.85rem 1rem; margin-bottom: 1rem; font-size: 0.9rem;
}
.kg-readiness-head { margin: 0 0 0.5rem; font-weight: 600; }
.kg-readiness-list { margin: 0 0 0.5rem; padding-left: 1.1rem; }
.kg-readiness-list li { margin-bottom: 0.25rem; }
.kg-readiness-link { font-weight: 600; }
.kg-secret-title { font-weight: 600; margin: 0 0 0.75rem; }
.kg-code { background: var(--kg-canvas); border: 1px solid var(--kg-line); border-radius: 8px; padding: 0.8rem 1rem; margin: 0.5rem 0 0; font-size: 0.78rem; line-height: 1.55; overflow-x: auto; white-space: pre; }

/* Temporary testing/identity panel */
.kg-panel { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--kg-line); }
.kg-token {
  display: inline-block; max-width: 100%; overflow-wrap: anywhere; word-break: break-all;
  font-size: 0.72rem; background: var(--kg-canvas); border: 1px solid var(--kg-line);
  border-radius: 6px; padding: 0.4rem 0.55rem;
}

/* Impersonation banner (django-hijack, ADR 0010) — fixed full-width top bar.
   position:fixed takes it out of the (flex) body flow so it spans the top instead of
   becoming a side column; body.kg-imp pads content down to clear it. */
.kg-impersonating {
  position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center; gap: 1rem; flex-wrap: wrap;
  background: var(--kg-ink); color: #fff;
  /* min-height, no vertical padding: the bar is then exactly the 2.75rem --kg-topbar/padding-top
     below assume (measured 2026-10-25: with 0.5rem padding it was 49px, 5px over the 44px). */
  box-sizing: border-box; min-height: 2.75rem; padding: 0 1rem; font-size: 0.85rem;
  border-bottom: 3px solid var(--kg-cyan);
}
.kg-impersonating .kg-btn { background: #fff; }
body.kg-imp { padding-top: 2.75rem; }   /* clear the fixed impersonation bar */
/* How much fixed banner is pinned over the viewport top. Sticky elements (the map
   tables' frozen header rows, the public docs pages' nav) offset by this instead of 0, or they anchor UNDERNEATH
   the dev/impersonation bars and read as not sticking at all. Keep in lockstep with
   the padding-top values above/below. */
body { --kg-topbar: 0px; }
body.kg-dev { --kg-topbar: 2.1rem; }
body.kg-imp { --kg-topbar: 2.75rem; }
body.kg-dev.kg-imp { --kg-topbar: 4.85rem; }

/* Dev-instance banner — fixed full-width top bar shown on local/mc instances only (never prod),
   so a throwaway dev tab is never mistaken for production. Same fixed-out-of-flex-flow trick as
   the impersonation bar; cyan accent on ink text marks it clearly non-prod and on-brand. */
.kg-devbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 1001;
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  background: var(--kg-cyan); color: var(--kg-ink);
  box-sizing: border-box; min-height: 2.1rem; padding: 0 1rem; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.02em;
  border-bottom: 1px solid var(--kg-cyan-strong);
}
body.kg-dev { padding-top: 2.1rem; }   /* clear the fixed dev bar */
/* When impersonating on a dev instance, stack the two bars: dev on top, imp below it. */
body.kg-dev .kg-impersonating { top: 2.1rem; }
body.kg-dev.kg-imp { padding-top: 4.85rem; }

/* Shared page furniture */
.kg-h2 { font-family: var(--kg-serif); font-weight: 600; font-size: 1.05rem; color: var(--kg-ink); margin: 1.6rem 0 0.6rem; }
.kg-h3 { font-family: var(--kg-serif); font-weight: 600; font-size: 0.95rem; color: var(--kg-ink); margin: 1.1rem 0 0.4rem; }
.kg-row { display: flex; gap: 0.5rem; align-items: center; }
/* Create-credential form: clear separation from the list below (was a CSP-dropped inline style). */
.kg-creds-form { margin: 1.5rem 0 2.5rem; }
.kg-out { margin-top: 0.6rem; padding: 0.6rem 0.8rem; background: var(--kg-canvas); border: 1px solid var(--kg-line); border-radius: 8px; font-size: 0.9rem; }
[x-cloak] { display: none !important; }

/* Staff menu (app chrome). A <details> disclosure, not a JS dropdown: the strict CSP
   carries no `unsafe-inline` for script and Alpine's CSP build cannot evaluate inline
   expressions, so the browser's own semantics are both cheaper and more accessible.
   `list-style: none` + the ::-webkit-details-marker reset hide the default triangle;
   the caret is drawn in content so it can flip on [open]. */
.kg-staff-menu { position: relative; }
.kg-staff-summary {
  cursor: pointer;
  list-style: none;
  padding: 0.25rem 0.5rem;
  border-radius: var(--kg-radius);
  color: var(--kg-ink);
  font-weight: 600;
}
.kg-staff-summary::-webkit-details-marker { display: none; }
.kg-staff-summary::after { content: " \25BE"; color: var(--kg-grey); }
.kg-staff-menu[open] > .kg-staff-summary::after { content: " \25B4"; }
.kg-staff-summary:hover { color: var(--kg-ink-hover); }
.kg-staff-items {
  position: absolute;
  z-index: 20;
  top: 100%;
  left: 0;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding: 0.375rem;
  background: var(--kg-canvas);
  border: 1px solid var(--kg-line);
  border-radius: var(--kg-radius);
}
.kg-staff-items a {
  padding: 0.375rem 0.5rem;
  border-radius: var(--kg-radius);
  white-space: nowrap;
}
.kg-staff-items a:hover { background: var(--kg-line); }

/* App frame, sidebar layout (#1821, app_base.html). NOT .kg-shell — that is the narrow centred
   card the login/onboarding pages use. One frame for every signed-in person: the
   sidebar carries the project switcher, the project's pages and the workspace links; the top bar
   carries the breadcrumb and the page's own actions. Below 900px the sidebar stacks on top and
   its link lists wrap — no JS, so nothing here depends on a script the CSP would have to allow. */
.kg-frame { display: flex; min-height: 100vh; width: 100%; }
.kg-side {
  flex: 0 0 15.5rem; display: flex; flex-direction: column; gap: 1.1rem;
  padding: 1.1rem 0.85rem; background: var(--kg-surface); border-right: 1px solid var(--kg-line);
  position: sticky; top: var(--kg-topbar); height: calc(100vh - var(--kg-topbar)); overflow-y: auto;
}
.kg-side .kg-brand { padding: 0 0.4rem; }
.kg-frame-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.kg-topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  padding: 0.85rem 1.75rem; background: var(--kg-surface); border-bottom: 1px solid var(--kg-line);
  min-height: 3.6rem;
}
.kg-crumbs { margin: 0; font-size: 0.88rem; color: var(--kg-grey); }
.kg-crumbs a { color: var(--kg-grey); text-decoration: none; }
.kg-crumbs a:hover { color: var(--kg-ink); }
.kg-crumbs strong { color: var(--kg-ink); font-weight: 600; }
.kg-crumb-sep { margin: 0 0.35rem; }
.kg-topbar-actions { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.kg-frame .kg-main { max-width: 72rem; padding: 2rem 1.75rem; }
.kg-frame .kg-main-wide { max-width: 100rem; }

/* Project switcher: a <details> card listing every project the account can see. */
.kg-switch { position: relative; }
.kg-switch-card {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 0.6rem;
  padding: 0.55rem 0.65rem; border: 1px solid var(--kg-line); border-radius: 10px;
  background: var(--kg-canvas);
}
.kg-switch-card::-webkit-details-marker { display: none; }
.kg-switch-mark {
  flex: 0 0 1.75rem; height: 1.75rem; display: grid; place-items: center; border-radius: 6px;
  background: var(--kg-ink); color: var(--kg-surface); font-weight: 700; font-size: 0.8rem;
}
.kg-switch-id { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.kg-switch-name { font-weight: 600; font-size: 0.9rem; color: var(--kg-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kg-switch-domain { font-size: 0.75rem; color: var(--kg-grey);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kg-switch-caret::after { content: "\2195"; color: var(--kg-grey); font-size: 0.8rem; }
.kg-switch-list {
  position: absolute; z-index: 30; top: calc(100% + 0.25rem); left: 0; right: 0;
  max-height: 22rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.35rem; background: var(--kg-surface); border: 1px solid var(--kg-line);
  border-radius: 10px;
}
.kg-switch-list a { padding: 0.4rem 0.5rem; border-radius: 6px; font-size: 0.85rem;
  color: var(--kg-ink); text-decoration: none; }
.kg-switch-list a:hover, .kg-switch-list a[aria-current] { background: var(--kg-canvas); }

/* Sidebar link groups. */
.kg-side-group { display: flex; flex-direction: column; gap: 0.1rem; }
.kg-side-label {
  margin: 0 0 0.3rem; padding: 0 0.55rem; font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--kg-grey);
}
/* The Developers group folds (#1977): closed by default, open on one of its own pages. */
.kg-side-fold { display: block; }
.kg-side-foldhead { display: flex; align-items: center; justify-content: space-between; gap: 0.4rem;
  min-height: 32px; margin: 0; border-radius: 6px; cursor: pointer; list-style: none; }
.kg-side-foldhead::-webkit-details-marker { display: none; }
.kg-side-foldhead::after { content: ""; width: 6px; height: 6px; margin-right: 0.2rem;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); transition: transform 0.15s; }
.kg-side-fold[open] > .kg-side-foldhead::after { transform: translateY(-2px) rotate(45deg); }
.kg-side-foldhead:hover { color: var(--kg-ink); }
.kg-side-foldhead:focus-visible { outline: 2px solid var(--kg-ink); outline-offset: 2px; }
.kg-side-fold > .kg-side-group { margin-top: 0.2rem; }
.kg-side-link {
  display: flex; align-items: center; gap: 0.55rem; padding: 0.45rem 0.55rem; border-radius: 8px;
  color: var(--kg-ink); text-decoration: none; font-size: 0.9rem;
}
.kg-side-link:hover { background: var(--kg-canvas); }
.kg-side-link.is-active { background: var(--kg-canvas); font-weight: 600; }
.kg-side-link.is-active .kg-side-icon { stroke: var(--kg-series); }
.kg-side-sub { padding-top: 0.3rem; padding-bottom: 0.3rem; font-size: 0.84rem; color: var(--kg-grey); }
.kg-side-icon { width: 1rem; height: 1rem; flex: 0 0 1rem; fill: none; stroke: currentColor;
  stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.kg-side-count { margin-left: auto; font-size: 0.75rem; color: var(--kg-grey); font-weight: 500; }
.kg-side .kg-staff-items { position: static; margin: 0.15rem 0 0 0.55rem; background: transparent;
  border: none; padding: 0; }

/* Account block at the foot of the sidebar. */
.kg-side-user { margin-top: auto; display: flex; gap: 0.6rem; align-items: flex-start;
  padding: 0.9rem 0.4rem 0; border-top: 1px solid var(--kg-line); }
.kg-avatar { flex: 0 0 2rem; height: 2rem; display: grid; place-items: center; border-radius: 50%;
  background: var(--kg-canvas); border: 1px solid var(--kg-line); font-size: 0.72rem; font-weight: 700; }
.kg-side-who { display: flex; flex-direction: column; min-width: 0; font-size: 0.85rem; }
.kg-side-who .kg-user { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.kg-side-who .kg-org { font-weight: 400; font-size: 0.78rem; }
.kg-side-acct { display: flex; gap: 0.75rem; align-items: center; margin-top: 0.2rem; font-size: 0.78rem; }
.kg-side-acct form { margin: 0; }
/* A form button that reads as a link (Log out is a POST, so it cannot be an <a>). */
.kg-linkbtn { background: none; border: none; padding: 0; font: inherit; color: var(--kg-grey);
  text-decoration: underline; cursor: pointer; }
.kg-linkbtn:hover { color: var(--kg-ink); }

@media (max-width: 900px) {
  .kg-frame { flex-direction: column; }
  .kg-side { position: static; height: auto; flex: none; border-right: none;
    border-bottom: 1px solid var(--kg-line); gap: 0.75rem; }
  .kg-side-group { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .kg-side-label { width: 100%; }
  .kg-side-user { margin-top: 0; }
  .kg-topbar, .kg-frame .kg-main { padding-left: 1rem; padding-right: 1rem; }
}

/* Team-only marker (#1891, Brad 2026-10-28): the admin and client UIs are one UI, and cyan
   marks what a client never sees. A whole card carries the left rule and a "Team only" tag;
   a control inside a shared card (a button, a link) carries the cyan underline. The Client
   health card's "Admin only" tag is the narrower, superuser-only tier of the same mark. */
/* compound too: a card class (.kg-g-card, .kg-card) loaded after this file sets the whole border */
.kg-team-only, .kg-g-card.kg-team-only, .kg-card.kg-team-only { border-left: 4px solid var(--kg-cyan); }
.kg-team-tag {
  display: inline-block; font-family: var(--kg-sans); font-size: 0.75rem; font-weight: 600;
  line-height: 1.4; color: var(--kg-ink); background: var(--kg-cyan);
  border-radius: var(--kg-radius); padding: 0.1rem 0.5rem; vertical-align: middle;
}
.kg-team-ctl { box-shadow: inset 0 -2px 0 var(--kg-cyan); }
