/* ================= itestra inside – design tokens (2026 corporate system) =================
   Ported from the approved landing-page mockup (itestra-inside-mockup). */

/* Primary face — the classic rsms "Inter var" the mockup was designed with.
   Self-hosted (extracted from the approved mockup) for exact visual parity.
   The Google "Inter" subsets below stay as fallback for glyphs this subset lacks. */
@font-face{
  font-family:'Inter var';
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url('../fonts/inter-var.woff2') format('woff2');
}
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:100 900;
  font-display:optional;
  src:url('../fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:100 900;
  font-display:optional;
  src:url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root{
  --blue:#2323FF;          /* primary action */
  --blue-dark:#1B1BD1;     /* primary hover */
  --deep:#0E3BD8;          /* deep feature blue */
  --navy:#051137;          /* dark navy */
  --cyan:#009EE3;          /* accent cyan */
  --cyan-dark:#0084BD;
  --sky:#CCECF9;           /* light cyan panel */
  --sky-soft:#E6F5FC;
  --tint:#E6EBF6;          /* light blue-gray */
  --tint-soft:#ECF0F9;
  --ink:#111827;
  --body:#374151;
  --muted:#6B7280;
  --line:#E5E7EB;
  --surface:#F9FAFB;
  /* Channel form of --surface, for the one place that needs it at partial
     opacity: Tailwind cannot compose an alpha onto a var() that holds a whole
     colour, so a utility like bg-gray-50/40 would silently not be emitted. */
  --surface-rgb:249 250 251;
  --amber-bg:#FEF3C7; --amber-tx:#92400E;
  --green-bg:#DCFCE7; --green-tx:#166534;
  --red-bg:#FEE2E2; --red-tx:#991B1B;
  /* The AI accent — the one hue in the app that is NOT the corporate blue, so
     that "this is the assistant" reads at a glance on pages that are otherwise
     blue throughout. Magenta clears AA on white (4.6:1), so it is safe as text
     and as a fill under white. Used only where the AI itself is: the editor's
     content assistant today. --ai-strong is its hover; --ai-tint is a soft wash
     for the identity mark. */
  --ai:#D80EA5; --ai-strong:#B50C88; --ai-tint:#FCEBF6;
  --radius-card:24px;
  --shadow-card:0 6px 28px rgba(5,17,55,.14);
  --shadow-pop:0 24px 56px -20px rgba(5,17,55,.35);
  /* One shell for the whole site — it has to be, since the logo, the breadcrumb
     and the content share a left edge on every page, and a shell that differed
     between pages would put them on different ones.

     76.5rem is derived, not picked: 64 padding + 208 nav + 32 gap + a 920px
     article, which is the 800px measure plus real slack. The article term is
     load-bearing — 920px is what the widest table (projects-using-ai) was tuned
     to fit — so any change to the nav column or its gap belongs in this sum,
     not on top of it. The previous 80rem carried the same article beside a
     16rem nav; the nav's real entries used barely 12 of those rems and the rest
     read as a blank moat between nav and content. */
  --maxw:76.5rem;
  /* The measure: how far a line of body text may run before the eye loses its
     way back to the left margin. ~80 characters at the body size. */
  --measure:50rem;

  /* The sticky chrome, in two parts. --top-h is the top bar. --chrome is
     everything a scrolled reader still has above them — the bar PLUS the
     collapsed page head, once there is one: page-head.js measures the strip and
     overwrites this. Anything that has to clear the chrome (the doc nav, the
     floating Edit control, every #anchor's scroll-margin) is written in terms
     of it, so the strip's height is stated once and nothing has to be kept in
     sync with it by hand. Pages with no head (the editor, the landing page)
     never get the override and keep the bar's own height. */
  --top-h:64px;
  --chrome:var(--top-h);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
  font-family:'Inter var','Inter',system-ui,-apple-system,sans-serif;
  color:var(--body);
  background:#fff;
  font-size:16px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* Column so a short page can still fill the viewport: the footer takes the
     slack (margin-top:auto) instead of leaving white below itself. */
  min-height:100dvh;
  display:flex;flex-direction:column;
}
h1,h2,h3,h4{color:var(--ink);line-height:1.15;letter-spacing:-.02em;text-wrap:balance}
a{color:inherit;text-decoration:none}
button{font:inherit;background:none;border:none;cursor:pointer;color:inherit}
svg.i{width:1.25em;height:1.25em;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none;vertical-align:-.22em}
/* A filled glyph, not a line one: brand marks (ic-mattermost) are solid shapes,
   so they fill with the current colour and drop the stroke the rest wear. */
svg.i-brand{fill:currentColor;stroke:none}
:focus-visible{outline:2px solid var(--blue);outline-offset:2px;border-radius:6px}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 clamp(1rem,3vw,2rem)}
.eyebrow{font-size:.78rem;font-weight:650;letter-spacing:.12em;text-transform:uppercase;color:var(--blue)}
.section-title{font-size:clamp(1.45rem,2.4vw,1.85rem);font-weight:700}
.arrow-link{display:inline-flex;align-items:center;gap:.45rem;font-weight:600;font-size:.92rem;color:var(--blue)}
.arrow-link svg.i{transition:transform .18s ease}
.arrow-link:hover svg.i{transform:translateX(3px)}
mark{background:var(--sky);color:inherit;border-radius:3px;padding:0 1px}

/* ================= header ================= */
header.top{
  position:sticky;top:0;z-index:50;
  background:rgba(255,255,255,.92);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line);
}
.top-in{display:flex;align-items:center;gap:2rem;height:var(--top-h)}
/* The header centres every item's *box*, but the wordmark is set 1.35rem against
   the nav's .92rem, so its taller line box carries more descent space and drops
   its glyphs 5px below the nav's baseline — cap-heights level, feet not, which
   reads as the logo sagging. Lifting the lockup the full 5px does align the
   baselines, but a wordmark that much larger then towers over the nav's cap line
   and reads high instead. 3px is the optical settlement: most of the sag gone,
   the logo still sitting into the row rather than on top of it. The whole lockup
   moves (word + pill), so their own baseline relationship is untouched, and a
   transform rather than a margin means nothing else in the header reflows. */
.brand{display:flex;align-items:baseline;gap:.55rem;flex:none;transform:translateY(-3px)}
.brand .word{font-size:1.35rem;font-weight:800;letter-spacing:-.03em;color:var(--ink)}
.brand .inside{font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--blue);border-radius:999px;padding:.18rem .6rem;transform:translateY(-2px)}
nav.main{display:flex;gap:.25rem;margin-right:auto}
nav.main a{display:inline-flex;align-items:center;gap:.3rem;white-space:nowrap;font-size:.92rem;font-weight:550;color:var(--body);padding:.42rem .8rem;border-radius:999px;transition:background .15s,color .15s}
nav.main a:hover{background:var(--tint-soft);color:var(--ink)}
nav.main a[aria-current]{color:var(--blue);font-weight:650}
.top-actions{display:flex;align-items:center;gap:.75rem;position:relative}
.search-mini{display:flex;align-items:center;gap:.55rem;font-size:.85rem;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:.42rem .85rem;transition:border-color .15s,box-shadow .15s;cursor:pointer}
.search-mini:hover{border-color:#c7cdd9}
.s-kbd{display:inline-flex;align-items:center;gap:.2rem;margin-left:.2rem}
.s-kbd kbd{font:inherit;font-size:.68rem;line-height:1;color:var(--muted);background:var(--tint-soft);border:1px solid var(--line);border-radius:5px;padding:.16rem .38rem;min-width:1.1em;text-align:center}
.avatar{width:36px;height:36px;border-radius:999px;background:var(--navy);color:#fff;font-size:.78rem;font-weight:700;display:grid;place-items:center;letter-spacing:.02em;cursor:pointer;border:none;padding:0}
/* With a photo, the navy disc must go: the photo is clipped round on top of it,
   and antialiasing lets a hairline of the disc show around the edge — read as a
   faint gray border that nobody drew. */
.avatar.has-photo{overflow:hidden;background:transparent}
.avatar.has-photo img{width:100%;height:100%;object-fit:cover;display:block}

/* user menu */
.user-menu{position:relative}
.user-menu .panel{
  position:absolute;right:0;top:calc(100% + .4rem);min-width:13rem;z-index:60;
  background:#fff;border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow-pop);
  padding:.3rem;display:none;
}
.user-menu.is-open .panel{display:block}
/* No rule under the identity block: it is a heading, not a group, and its own
   weight and spacing already set it apart. The rules belong between the groups
   of ITEMS below. */
.user-menu .who{padding:.6rem .8rem}
.user-menu .who .n{font-weight:650;color:var(--ink);font-size:.92rem}
.user-menu .who .r{font-size:.75rem;color:var(--muted);margin-top:.05rem}
/* The rule BETWEEN groups, never above the first or below the last — a group
   the template gated away is not in the DOM, so its separator is not either
   (base.html says why). Same adjacent-sibling trick as .sp-group + .sp-group in
   the search panel, but NOT its colour: --surface (#F9FAFB) on this panel's
   white is six units of difference, which renders a line that is there and
   cannot be seen. --line is the hairline this system uses where a border is
   meant to read — the panel's own edge, .card, the rules on a profile. The
   padding keeps an item's hover pill off it. */
.user-menu .grp + .grp{border-top:1px solid var(--line);margin-top:.3rem;padding-top:.3rem}
.user-menu .panel a{display:flex;align-items:center;gap:.6rem;padding:.55rem .8rem;border-radius:10px;font-size:.9rem;color:var(--body)}
.user-menu .panel a:hover{background:var(--tint-soft);color:var(--ink)}

/* ================= command deck ================= */
.deck{background:var(--navy);color:#fff;position:relative;z-index:10}
.deck .wm-clip{position:absolute;inset:0;overflow:hidden;pointer-events:none}
.deck::before{
  content:"";position:absolute;inset:0;
  background-image:radial-gradient(circle at 1px 1px,rgba(255,255,255,.07) 1px,transparent 0);
  background-size:26px 26px;pointer-events:none;
}
.deck .watermark{
  position:absolute;right:-1rem;bottom:-3.4rem;font-size:11rem;font-weight:800;letter-spacing:-.05em;
  color:rgba(255,255,255,.035);line-height:.8;user-select:none;pointer-events:none;white-space:nowrap;
}
.deck-in{position:relative;padding:clamp(2.6rem,5vw,4rem) 0 clamp(2.4rem,4.5vw,3.4rem);max-width:56rem}
.deck .eyebrow{color:#66C8F0}
.deck h1{color:#fff;font-size:clamp(1.7rem,3.2vw,2.4rem);font-weight:700;margin:.5rem 0 1.6rem}
.deck h1 .dim{color:rgba(255,255,255,.55);font-weight:550}

/* search */
.search-shell{position:relative;z-index:5}
/* The overlay the top-bar search opens on every page but the landing: a blurred
   backdrop with the same shell floated near the top, spotlight-style. The shell
   keeps its own styling — this only positions it and dims what is behind. */
.search-overlay{
  position:fixed;inset:0;z-index:100;display:none;
  background:rgba(5,17,55,.5);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.search-overlay.open{display:block}
.search-overlay-in{max-width:46rem;margin:0 auto;padding:10vh 1rem 2rem}
@media (max-width:640px){.search-overlay-in{padding:6vh .8rem 1rem}}
.search-bar{
  display:flex;align-items:center;gap:.8rem;background:#fff;border-radius:28px;
  padding:.35rem .5rem .35rem 1.3rem;min-height:60px;
  box-shadow:0 10px 40px rgba(0,0,0,.25);
  transition:border-radius .15s ease;
}
.search-shell.open .search-bar{border-radius:24px 24px 0 0;box-shadow:none}
/* Focus pulse. The top-bar search and Cmd/Ctrl-K send focus into this bar
   without moving anything else on the page (unlike the overlay on other pages,
   which dims the whole screen behind a spotlight). A bare caret is easy to miss,
   so the shell answers the focus with one cyan ring that hugs the pill and fades
   outward — added by search.js, cleared by it a beat later. Cyan, not the action
   blue, because the deck it sits on is near-black navy and a blue ring vanishes
   into it; cyan is the palette's one hue that stays bright against that ground.

   Reduced-motion users can't get feedback from the pulse: the global reduce rule
   below force-zeroes every animation. So the base rule is a still ring that only
   needs to appear and disappear; the expanding pulse is layered on top for those
   who have not asked motion to stop. */
.search-shell.flash::after{
  content:"";position:absolute;inset:0;border-radius:28px;pointer-events:none;
  box-shadow:0 0 0 3px rgba(0,158,227,.5);
}
@media (prefers-reduced-motion:no-preference){
  .search-shell.flash::after{box-shadow:none;animation:searchFlash .8s ease-out forwards}
}
@keyframes searchFlash{
  0%{box-shadow:0 0 0 0 rgba(0,158,227,.7)}
  70%{box-shadow:0 0 0 12px rgba(0,158,227,0)}
  100%{box-shadow:0 0 0 12px rgba(0,158,227,0)}
}
.search-bar svg.i{color:var(--muted);width:1.35rem;height:1.35rem}
.search-bar input{flex:1;border:none;outline:none;font:inherit;font-size:1.02rem;color:var(--ink);background:transparent;min-width:0}
.search-bar input::placeholder{color:#9AA1AE}
.ask-btn{
  flex:none;display:inline-flex;align-items:center;gap:.5rem;background:var(--blue);color:#fff;font-weight:600;font-size:.92rem;
  border-radius:999px;padding:.62rem 1.25rem;transition:background .15s;
}
.ask-btn:hover{background:var(--blue-dark)}

/* search panel
   The shape and the scrolling are on two different elements on purpose. A
   scrollbar lives inside the border box of the element that scrolls and is NOT
   clipped by that element's own border-radius, so a rounded, scrolling panel
   draws a square white gutter straight through its bottom-right corner. Give the
   radius to a parent that only clips (overflow:hidden), and the child's scrollbar
   is clipped along the curve like any other painting. */
.search-panel{
  display:none;position:absolute;left:0;right:0;top:100%;
  background:#fff;border-radius:0 0 24px 24px;border-top:1px solid var(--line);
  box-shadow:0 30px 60px -12px rgba(5,17,55,.45);
  overflow:hidden;color:var(--body);
}
.search-shell.open .search-panel{display:block}
/* Inset from the edge (a transparent border the thumb is clipped to), so the
   thumb never reaches the corner the radius is cutting. */
.sp-scroll{
  max-height:min(30rem,60vh);overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:#C7CDD8 transparent;
}
.sp-scroll::-webkit-scrollbar{width:12px}
.sp-scroll::-webkit-scrollbar-track{background:transparent}
.sp-scroll::-webkit-scrollbar-thumb{
  background:#C7CDD8;border-radius:999px;
  border:4px solid transparent;background-clip:content-box;
}
.sp-scroll::-webkit-scrollbar-thumb:hover{background:#AAB2C0;background-clip:content-box}
.sp-group{padding:.65rem .65rem .3rem}
.sp-group + .sp-group{border-top:1px solid var(--surface)}
.sp-label{font-size:.7rem;font-weight:650;letter-spacing:.11em;text-transform:uppercase;color:var(--muted);padding:.35rem .75rem}
.sp-item{display:flex;align-items:center;gap:.85rem;width:100%;text-align:left;padding:.6rem .75rem;border-radius:14px;font-size:.95rem;color:var(--ink)}
.sp-item:hover,.sp-item.active{background:var(--tint-soft)}
.sp-item .ico{width:34px;height:34px;border-radius:10px;background:var(--tint-soft);color:var(--deep);display:grid;place-items:center;flex:none}
.sp-item.active .ico,.sp-item:hover .ico{background:#fff}
/* min-width:0 so the snippet may be clipped rather than pushing the area label
   off the end — a flex item's floor is its content width until it is told
   otherwise. */
.sp-item .sp-text{flex:1;min-width:0}
.sp-item .t{display:block;font-weight:560}
.sp-item .t .in{color:var(--muted);font-weight:400}
/* The snippet is a window into the page, not a subtitle: it is as long as it
   needs to be to show the match in context, and two lines is where it stops. */
.sp-item .d{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;font-size:.8rem;line-height:1.45;color:var(--muted);margin-top:.1rem}
.sp-item .sec{margin-left:auto;flex:none;font-size:.7rem;font-weight:650;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.sp-footer{display:flex;gap:1.2rem;align-items:center;padding:.7rem 1.4rem;border-top:1px solid var(--surface);font-size:.78rem;color:var(--muted)}
.sp-empty{padding:1.6rem 1.4rem;font-size:.92rem;color:var(--muted)}
.sp-empty strong{color:var(--ink)}

/* AI answer mode */
.ai-head{display:flex;align-items:center;gap:.6rem;padding:1rem 1.4rem .4rem;font-size:.8rem;font-weight:650;letter-spacing:.1em;text-transform:uppercase;color:var(--deep)}
.ai-head .ico{width:26px;height:26px;border-radius:8px;background:#F0F0FF;color:var(--blue);display:grid;place-items:center}
.ai-query{padding:.1rem 1.4rem .6rem;font-size:1.05rem;font-weight:650;color:var(--ink)}
.activity{
  display:inline-flex;align-items:center;gap:.6rem;margin:.3rem 1.4rem .9rem;
  background:var(--surface);border:1px solid var(--line);border-radius:999px;
  padding:.42rem .95rem .42rem .6rem;font-size:.82rem;color:var(--muted);
}
.activity .spin{width:16px;height:16px;flex:none;border-radius:50%;border:2px solid var(--sky);border-top-color:var(--cyan);animation:rot .7s linear infinite}
@keyframes rot{to{transform:rotate(360deg)}}
/* The answer is markdown, rendered by the SAME renderer as a content page
   (content_md.render_answer) — so this panel now holds links, lists, code spans,
   code blocks and tables, and it needs prose styling for all of them.

   Not `.prose` itself, deliberately. That class carries a page's LAYOUT — 1rem
   type, a 6rem bottom, the measure/wide lane split — and a dropdown under a
   search bar is not a page: the type is smaller, the width is the panel's, and
   there is no gutter for a code block to bleed into. What is shared is what the
   markup means, and the class names carry that: .c-code-wrap, .c-table-wrap,
   .c-details and the pygments palette in css/code.css are written for both. */
/* `overflow-wrap:anywhere` because an answer cites paths, and a mined SVN path
   is a single token wider than the panel with nothing in it to break on. Without
   it the token hangs out of the box, and since .sp-scroll sets overflow-y the
   other axis computes to `auto` rather than `visible` — the whole dropdown grows
   a horizontal scrollbar for the sake of one URL. `anywhere` rather than
   `break-word`: only `anywhere` also shrinks min-content, which is what stops
   the token forcing the panel wide from the inside. */
.ai-answer{
  padding:0 1.4rem 1rem;font-size:.95rem;line-height:1.65;color:var(--body);
  max-width:64ch;overflow-wrap:anywhere;
}
.ai-answer p{margin:0 0 .6rem}
.ai-answer > *:last-child{margin-bottom:0}
.ai-answer strong{color:var(--ink);font-weight:650}
/* A link in an answer is the point of rendering the markdown at all — the model
   cites a download, an address, a repository path. It has to look like one. */
.ai-answer a{
  color:var(--blue);text-decoration:underline;
  text-decoration-color:color-mix(in srgb,var(--blue) 30%,transparent);
  text-underline-offset:2px;
}
.ai-answer a:hover{text-decoration-color:var(--blue)}
/* The reset zeroes padding, so bullets need the indent putting back. */
.ai-answer ul,.ai-answer ol{margin:0 0 .6rem;padding-left:1.35rem}
.ai-answer ul{list-style:disc}
.ai-answer ol{list-style:decimal}
.ai-answer li{margin:.2rem 0}
.ai-answer li::marker{color:var(--muted)}
.ai-answer li > ul,.ai-answer li > ol{margin:.2rem 0}
.ai-answer h2,.ai-answer h3,.ai-answer h4{color:var(--ink);font-weight:650;margin:1rem 0 .45rem}
.ai-answer h2{font-size:1.05rem}
.ai-answer h3,.ai-answer h4{font-size:.98rem}
.ai-answer blockquote{margin:.6rem 0;padding:.1rem 0 .1rem .9rem;border-left:3px solid var(--cyan);color:var(--muted)}
.ai-answer code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.86em;background:var(--surface);border:1px solid var(--line);
  border-radius:5px;padding:.1em .35em;color:var(--ink);
}
/* The <pre> carries the size and the line-height, not the <code>: an inline
   <code> cannot shrink the line box its parent's strut already established, and
   the block would come out double-spaced. Same reason as on a page. */
.ai-answer pre.c-code{
  margin:0;padding:.8rem .9rem;overflow-x:auto;
  background:#0d1117;border-radius:10px;font-size:.82rem;line-height:1.5;
}
.ai-answer pre.c-code code{
  background:none;border:0;padding:0;color:#E6EDF3;font-size:inherit;line-height:inherit;
}
.ai-answer .c-code-wrap,.ai-answer .c-table-wrap{margin:0 0 .7rem}
/* Everywhere but a table, where the wrap inherits and should not: a cell that
   breaks mid-token is a cell that never gets wide enough to reach
   .c-table-wrap's scroller, which is where a table too wide for the lane goes. */
.ai-answer table{width:100%;border-collapse:collapse;font-size:.85rem;overflow-wrap:normal}
.ai-answer thead th{
  text-align:left;font-weight:650;color:var(--ink);white-space:nowrap;
  border-bottom:2px solid var(--line);padding:.4rem 1rem .4rem 0;
}
.ai-answer tbody td{border-bottom:1px solid var(--line);padding:.4rem 1rem .4rem 0;vertical-align:top}
.ai-answer thead th:last-child,.ai-answer tbody td:last-child{padding-right:0}
/* A `:::` directive an answer may not have — a widget, which is machinery a
   model's output must never mount. mistune renders the unregistered directive as
   an escaped block, and this is what it looks like: the text it is. */
.ai-answer .error pre{
  white-space:pre-wrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.8rem;color:var(--muted);background:var(--surface);
  border:1px dashed var(--line);border-radius:10px;padding:.6rem .8rem;margin:0 0 .6rem;
}
.ai-answer .caret{display:inline-block;width:2px;height:1.05em;background:var(--blue);vertical-align:-.18em;animation:blink .9s steps(1) infinite}
@keyframes blink{50%{opacity:0}}
.ai-sources{display:flex;flex-wrap:wrap;gap:.5rem;padding:.2rem 1.4rem 1rem}
/* A chip carries a breadcrumb — area › document › section — so it can run long
   ("Tools › Communication › E-Mail Provider: Strato"). It wraps inside itself
   rather than pushing the panel sideways: max-width holds it to the panel, and
   the radius comes off the pill, because a pill cannot round two lines. */
.ai-sources a{
  display:inline-flex;align-items:center;gap:.45rem;max-width:100%;
  font-size:.8rem;font-weight:600;line-height:1.4;color:var(--deep);
  background:var(--sky-soft);border:1px solid #BEE3F5;border-radius:14px;padding:.32rem .8rem;
}
.ai-sources a:hover{background:var(--sky)}
/* A cited source that has no page: same chip, no link, no hover — it is a fact
   about where the answer came from, not somewhere to go. */
.ai-sources .no-page{
  display:inline-flex;align-items:center;gap:.45rem;max-width:100%;
  font-size:.8rem;font-weight:600;line-height:1.4;color:var(--muted);
  background:var(--surface);border:1px dashed var(--line);border-radius:14px;padding:.32rem .8rem;
}
.ai-sources .i{flex:none}
.ai-sources .crumbs{min-width:0;overflow-wrap:anywhere}
.ai-sources .sep{margin:0 .3rem;opacity:.55;font-weight:400}
/* Where the agent has offered to take you — an `action` tool's open_editor or
   navigate. A link and not a redirect: the agent opens the door, the person
   walks through it. */
.ai-go{
  display:inline-flex;align-items:center;gap:.4rem;margin:0 1.4rem .6rem;
  font-size:.85rem;font-weight:650;color:var(--deep);
  background:var(--sky-soft);border:1px solid #BEE3F5;border-radius:999px;padding:.4rem .9rem;
}
.ai-go:hover{background:var(--sky)}
/* A run that failed, or a stream that was lost. Says so where the answer would
   have been — a panel that just stops has the shape of an answer arriving. */
.ai-error{color:var(--muted)}

/* Close: pinned to the panel FRAME, so it holds the top-right corner while a
   long answer scrolls under it. White-backed so scrolling text never shows
   through. */
.search-panel .ai-close{
  position:absolute;top:.55rem;right:.85rem;z-index:3;
  display:inline-flex;align-items:center;justify-content:center;
  width:1.9rem;height:1.9rem;border:none;border-radius:8px;
  background:#fff;color:var(--muted);cursor:pointer;transition:background .12s,color .12s;
}
.search-panel .ai-close:hover{background:var(--surface);color:var(--ink)}
.search-panel .ai-close svg.i{width:1rem;height:1rem}

/* The answer footer: source chips on the left, rating thumbs on the right,
   sharing one line so the thumbs add no height of their own. */
.ai-foot{display:flex;align-items:center;gap:.6rem;padding:.2rem 1.4rem 1rem}
.ai-foot .ai-sources{flex:1 1 auto;min-width:0;padding:0}
.ai-foot .ai-rate-row{margin-left:auto;flex:none}

/* Rate the answer: two quiet thumbs. Light gray by default so they stay subtle,
   a little darker on hover, coloured once picked. Thumbs-down opens the box. */
.ai-rate-row{display:flex;align-items:center;gap:.25rem}
.ai-rate{
  display:inline-flex;align-items:center;justify-content:center;
  width:1.9rem;height:1.9rem;border:none;border-radius:8px;
  background:transparent;color:#B4B9C4;cursor:pointer;transition:background .12s,color .12s;
}
.ai-rate:hover{background:var(--surface);color:var(--muted)}
.ai-rate svg.i{width:1.05rem;height:1.05rem}
.ai-rate.is-on{color:var(--blue)}
.ai-rate[data-rate="down"].is-on{color:#B91C1C}
/* Rated already. The thumbs do not toggle off (search.js: rate), so the one that
   was picked keeps its colour and the other fades out of the way — the row reads
   as an answer given, not as a control waiting to be used. */
.ai-rate:disabled{cursor:default}
.ai-rate:disabled:hover{background:transparent}
.ai-rate:disabled:not(.is-on){opacity:.35}

/* Feedback box opened by thumbs-down — a note and a send, at the answer's foot. */
.ai-feedback{display:flex;flex-direction:column;gap:.5rem;padding:0 1.4rem 1.1rem}
.ai-feedback-input{
  width:100%;box-sizing:border-box;resize:vertical;min-height:3.2rem;
  font:inherit;font-size:.9rem;line-height:1.5;color:var(--ink);
  background:#fff;border:1px solid var(--line);border-radius:10px;padding:.55rem .7rem;
}
.ai-feedback-input:focus{outline:none;border-color:var(--cyan)}
.ai-feedback-input::placeholder{color:var(--muted)}
.ai-feedback-send{
  align-self:flex-end;background:var(--blue);color:#fff;font-weight:600;font-size:.85rem;
  border:none;border-radius:999px;padding:.45rem 1.1rem;cursor:pointer;transition:background .15s;
}
.ai-feedback-send:hover{background:var(--blue-dark)}
.ai-feedback-done{font-size:.85rem;color:var(--muted);padding:.2rem 0}
/* Only an admin ever sees this: their thumbs-down took the question out of the
   suggestions there and then, and a control whose effect is invisible is a shrug. */
.ai-feedback-note{font-size:.85rem;color:#B91C1C;padding:0 0 .1rem}
/* The veto note's other half: the question is back. A node of its own, because
   the box the red one sits inside is exactly what un-clicking takes away — so it
   carries the padding .ai-feedback would have lent it. Not red, and not an
   alarm: this one is the undo. */
.ai-unveto-note{font-size:.85rem;color:var(--muted);padding:0 1.4rem 1.1rem;margin:0}

/* ================= sections ================= */
section.band{padding:clamp(2.4rem,4.5vw,3.6rem) 0}
.band-head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;margin-bottom:1.5rem}
.band-head .sub{font-size:.92rem;color:var(--muted)}
/* The head's right slot, used by the landing's Upcoming events band. Not
   baseline-aligned, though the head is: a flex item's baseline is the one of its
   FIRST line, and the title block's first line is the eyebrow — so the link rode
   level with "Calendar" rather than the title, and its taller line box grew the
   head 2px past the identical head in the column beside it, which is enough to
   put the two lists out of step. End-aligned it sits on the title's line, where
   a "see all" belongs, and the head keeps the height of the block that sets it. */
.band-head .arrow-link{align-self:flex-end}

/* today cards */
.today-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.1rem}
.tcard{
  background:#fff;border:1px solid var(--line);border-radius:var(--radius-card);
  padding:1.35rem 1.45rem 1.3rem;display:flex;flex-direction:column;gap:.55rem;
  transition:box-shadow .2s,transform .2s;
}
.tcard:hover{box-shadow:var(--shadow-card);transform:translateY(-2px)}
.tcard .meta{display:flex;align-items:center;justify-content:space-between;gap:.6rem}
.tcard .sec{font-size:.7rem;font-weight:650;letter-spacing:.11em;text-transform:uppercase;color:var(--muted)}
.pill{font-size:.72rem;font-weight:650;border-radius:999px;padding:.22rem .7rem;white-space:nowrap}
.pill.cyan{background:var(--sky-soft);color:var(--cyan-dark)}
.pill.amber{background:var(--amber-bg);color:var(--amber-tx)}
.pill.blue{background:#F0F0FF;color:var(--blue)}
.pill.green{background:var(--green-bg);color:var(--green-tx)}
.pill.red{background:var(--red-bg);color:var(--red-tx)}
.pill.gray{background:var(--tint-soft);color:var(--muted)}
/* A pill that opens the thing it counts. Only one does (the unanswerable
   questions on the agents card), and only when it is not zero. */
.pill.click{cursor:pointer;user-select:none}
.pill.click:hover{filter:brightness(.95)}
.tcard h3{font-size:1.08rem;font-weight:650}
.tcard p{font-size:.88rem;color:var(--muted);flex:1}
.tcard .arrow-link{margin-top:.35rem}

/* areas bento */
.areas{background:var(--tint-soft)}
.bento{display:grid;grid-template-columns:repeat(12,1fr);gap:1.1rem}
.bcard{border-radius:var(--radius-card);padding:1.6rem 1.65rem;display:flex;flex-direction:column;gap:.7rem;position:relative;transition:box-shadow .2s,transform .2s}
.bcard:hover{box-shadow:var(--shadow-card);transform:translateY(-2px)}
/* No .blabel here any more. Row 1's cards wore an uppercase eyebrow with the
   area's name over a headline that said the same thing in a sentence, and both
   went when every card's arrow link became "More in <area>" — one naming slot
   per card, in the same place on all four. Media's white pill (.badge) was the
   last eyebrow left and has gone the same way since; the mockups in docs/mockups
   carry their own copies of this file's tokens and still use both. */
.bcard h3{font-size:1.28rem;font-weight:700}
.bcard p.lead{font-size:.92rem;opacity:.85;max-width:46ch}
.bcard .grow{flex:1}

/* Row 1 is two surfaces, told apart by HUE at a common darkness:
   --navy (Company) → --deep (Portfolio).

   It was three, and the third was the hard one: Present sat beside AI in the
   same --deep, dE 0, one blue slab with a gutter in it. A --cyan-deep was mixed
   for it (--cyan at 55% value, white at 7.90:1 — within a hair of --deep's 7.97,
   so no white-on-dark rule in this file needed special-casing), and two tones
   were tried and rejected on the way there: --sky, dE 10 from --tint-soft and so
   perceptually the band itself, and full --cyan, bright enough that white fails
   AA on it (3.00:1) and the card had to wear near-black — a signage palette, the
   one card in the band not speaking the system's language of light type on a
   deep colour. That whole argument is recorded in
   docs/mockups/areas-ai-card.html §§4-5 and is now moot: row 1 is two cards, the
   separation Company and Portfolio already have is 72.6, and --cyan-deep went with
   the card it was mixed for. It is written down here because the next person to
   want a third surface in row 1 should not have to re-derive it. */
.b-company{grid-column:span 6;background:var(--navy);color:#fff}
.b-company h3{color:#fff}
/* The arrow takes the same light cyan the other two dark cards use. --blue on
   --navy measures 2.4:1 — two dark blues, well under AA — and it stood only
   because this card had no arrow link at all until it lost What we do and grew
   one. #8FD8FA on navy is 11.8:1. */
.b-company .arrow-link{color:#8FD8FA}

/* Portfolio — the other half of row 1. It takes the row-1 shape (a sentence, in
   colour) because that shape is what the reader has learned row 1 means; a bare
   noun up here would read as a door that wandered.

   It needs no .linklist rules of its own: the base list below is written for a
   dark card (white text, hairline dividers, cyan glyphs) and it is .b-dir that
   overrides all of it back to white-card values. */
.b-feat{grid-column:span 6;background:var(--deep);color:#fff}
.b-feat h3{color:#fff}
.b-feat .arrow-link{color:#8FD8FA;margin-top:.7rem;font-size:.85rem}
.linklist{display:flex;flex-direction:column;margin-top:.2rem}
.linklist a{
  display:flex;align-items:center;gap:.6rem;padding:.55rem .2rem;font-size:.94rem;font-weight:550;
  color:rgba(255,255,255,.88);border-bottom:1px solid rgba(255,255,255,.1);transition:color .15s,padding-left .15s;
}
.linklist a:last-child{border-bottom:none}
.linklist a:hover{color:#fff;padding-left:.45rem}
.linklist svg.i{width:1rem;height:1rem;color:#66C8F0}
.linklist .note{margin-left:auto;font-size:.72rem;font-weight:500;color:rgba(255,255,255,.45)}

.b-media{grid-column:span 6;padding:0;overflow:hidden;background:var(--navy);color:#fff;justify-content:flex-end;min-height:280px}
.b-media .photo{position:absolute;inset:0;background:
  radial-gradient(120% 90% at 85% 10%,rgba(0,158,227,.55) 0%,transparent 55%),
  radial-gradient(130% 100% at 15% 90%,rgba(35,35,255,.5) 0%,transparent 60%),
  var(--navy);transition:transform .5s ease}
.b-media:hover .photo{transform:scale(1.05)}
/* When a library photo is featured, the inline background-image replaces the
   gradients above (longhand beats the shorthand's layers), so this supplies the
   sizing they never needed. --navy survives as the background-color and shows
   through until the image has loaded. */
.b-media.has-photo .photo{background-size:cover;background-position:center;background-repeat:no-repeat}
.b-media::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(5,17,55,.05) 30%,rgba(5,17,55,.88) 78%)}
/* A photo is busier than a gradient: darken it further so the title and lead
   keep their contrast over a bright sky or a white shirt. */
.b-media.has-photo::after{background:linear-gradient(180deg,rgba(5,17,55,.25) 0%,rgba(5,17,55,.55) 45%,rgba(5,17,55,.93) 85%)}
.b-media .inner{position:relative;z-index:2;padding:1.5rem 1.65rem;display:flex;flex-direction:column;gap:.45rem}
.b-media h3{color:#fff}
.b-media p{font-size:.85rem;color:rgba(255,255,255,.8)}
/* No .badge here any more. A white uppercase pill sat over the top-left of the
   photograph and said MEDIA, which the card's arrow link now says instead — one
   naming slot per card, and over an image a word reads as a caption for the
   picture rather than as the card's address (landing.html has the rule).

   What went with it, since it is the kind of thing that gets re-derived: the
   pill's right padding was short by half its tracking, because letter-spacing
   adds its .1em after the LAST letter too and equal paddings then centre that
   phantom space along with the glyphs. Only half, though — M's left side bearing
   (.05em) is wider than A's right (.003em) and pays back the rest. Vertically
   nothing was needed: with equal top/bottom padding the cap band's offset is
   ascender − descender − cap-height, which Inter draws as zero. */
.b-media .arrow-link{color:#8FD8FA}

/* The door — Infrastructure. A name and its links, on white, so row 2 recedes
   behind the two cards above it that hold something live. No slogan: the name is
   what is behind the door, and a sentence on a door is what made this band read
   as a brochure.

   Half a row wide, sharing row 2 with the Media photograph — and it is the only
   white card in the band, which is why every white-card override of .linklist
   lives on this class. A second door would have to wear .b-dir or repeat all six
   of them; the base list is dark, and a list that forgets this renders white on
   white and looks empty rather than broken. */
.b-dir{grid-column:span 6;background:#fff;border:1px solid var(--line)}
.b-dir h3{font-size:1.05rem;letter-spacing:.01em}
.b-dir .linklist{margin-top:.35rem}
.b-dir .linklist a{color:var(--body);border-color:var(--line);font-size:.92rem;padding:.5rem .2rem}
.b-dir .linklist a:hover{color:var(--blue)}
.b-dir .linklist svg.i{width:.95rem;height:.95rem;color:var(--muted)}
/* Trailing, muted, and after the label rather than before it: this marks where a
   link LEAVES the intranet, which is a different fact from what the row's own
   icon says, and it must not read as a second glyph in the same sentence. */
.b-dir .linklist .ext{margin-left:auto;display:inline-flex;color:var(--muted);opacity:.65}
.b-dir .linklist .ext svg.i{width:.85rem;height:.85rem}
.b-dir .arrow-link{margin-top:.7rem;font-size:.85rem}

/* The Events strip (docs/events.md §6.1): the ONE lifecycle state that asks the
   reader for something, above the areas rather than among them. Cyan, because
   the band's own rule is that colour means there is something here — and this is
   the only card on the page that is a task rather than a place. */
.ev-strip{background:var(--cyan);color:#fff;border-radius:var(--radius-card);
          padding:1.25rem 1.5rem;display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;
          margin-bottom:1.6rem}
.ev-strip .es-text{flex:1 1 20rem;display:flex;flex-direction:column;gap:.2rem}
.ev-strip .es-kicker{font-size:.72rem;font-weight:650;letter-spacing:.1em;text-transform:uppercase;color:rgba(255,255,255,.8)}
.ev-strip h2{font-size:1.28rem;font-weight:700;color:#fff}
.ev-strip .es-meta{font-size:.85rem;color:rgba(255,255,255,.85)}
.ev-strip .es-actions{display:flex;gap:.6rem;flex-wrap:wrap}
/* The first owed form is solid white — it is the next thing to do; any second
   waits its turn translucent. Same pair the tile used, at strip scale. */
.es-btn{border-radius:999px;padding:.62rem 1.15rem;font-weight:650;font-size:.92rem;
        display:inline-flex;align-items:center;gap:.5rem;transition:background .15s;
        background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.28);color:#fff}
.es-btn:hover{background:rgba(255,255,255,.24)}
.es-btn.primary{background:#fff;border-color:#fff;color:var(--cyan-dark)}
.es-btn.primary:hover{background:var(--sky-soft)}

/* news + upcoming */
/* Two equal columns pushed to the wrap's edges, rather than the mockup's 7fr/5fr.
   That ratio sized a news row that still had a chip in it (7.6rem + its gap);
   without one the wider track was ~370px of slack, and the band read as one
   column outgrowing the other. Equal tracks with the slack moved into the gutter
   keep both lists the same width and both outer edges aligned with the bands
   above. If the viewport squeezes, minmax(0,…) lets the tracks shrink before the
   gutter does, so they never collide. */
.duo{display:grid;grid-template-columns:repeat(2,minmax(0,30rem));justify-content:space-between;gap:2.4rem;align-items:start}
.news-list{display:flex;flex-direction:column}
/* The line-height is tuned, not chosen: five of these stand level with the four
   Upcoming events cards beside them, so the row's height is arithmetic — 5 × 73.7px
   of rows ≈ 4 × 82px of cards + their 3 gaps. It is spent on the LINE rather
   than the padding because that is where the height actually was: body's
   16px/1.6 strut set BOTH lines to 25.6px no matter that the title is 15px and
   the byline 12.5px, which is loose for a two-line row and generous for none of
   it. Change either list's row height and this stops being level. */
.news-item{display:block;padding:.85rem .2rem;line-height:1.42;border-bottom:1px solid var(--line)}
.news-item:hover{background:var(--surface)}
/* Separators, not a terminator: the rule under the last row would close a list
   that the next column's cards do not, and leave a hairline hanging in the gap
   below the band. */
.news-item:last-child{border-bottom:none}
.news-item .nt{font-size:.94rem;font-weight:560;color:var(--ink)}
.news-item .nd{font-size:.78rem;color:var(--muted);margin-top:.1rem}
/* Inherits its size and colour from the byline it now sits in — the date is part
   of that sentence, not a column of its own. */
.news-item time{font-variant-numeric:tabular-nums}
.up-list{display:flex;flex-direction:column;gap:.8rem}
.up-item{display:flex;gap:1rem;align-items:center;background:#fff;border:1px solid var(--line);border-radius:18px;padding:.8rem .95rem;transition:box-shadow .2s}
.up-item:hover{box-shadow:var(--shadow-card)}
.date-tile{flex:none;width:54px;height:54px;border-radius:14px;background:var(--tint-soft);display:flex;flex-direction:column;align-items:center;justify-content:center;line-height:1.05}
.date-tile .dd{font-size:1.25rem;font-weight:750;color:var(--blue);font-variant-numeric:tabular-nums}
.date-tile .mm{font-size:.66rem;font-weight:650;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.up-item .ut{font-weight:620;font-size:.94rem;color:var(--ink)}
.up-item .um{font-size:.79rem;color:var(--muted);margin-top:.1rem}
.up-item .pill{margin-left:auto;flex:none}
/* Latest updates and Upcoming events share the mockup's duo, which sizes both columns
   itself. The readable-column cap is therefore only for the fallback where one
   of the two has nothing to show and the other stands alone across the band:
   a row whose date is stranded at the far edge of 1150px reads as two columns
   that happen to share a line. (Below 1000px `duo` is one column already, and
   the wrap is narrow enough that a cap would only starve it.) */
.wrap:not(.duo) .latest-updates .news-list,
.wrap:not(.duo) .key-dates .up-list{max-width:640px}
/* No hover lift on an event: those are not links until events have pages. The
   news items ARE links, and keep theirs. */
.key-dates .up-item:hover{box-shadow:none}

/* ================= footer ================= */
/* auto, not a fixed margin: on a short page it eats the leftover viewport so the
   footer sits on the bottom edge; on a tall one it collapses to nothing. */
footer.site{background:var(--blue);color:#fff;margin-top:auto}
/* The whole footer is this one row now — no border-top, because there is
   nothing above it to be separated from. Padded a little more than it was as a
   closing line, so a band that is only 1rem of text still reads as the end of
   the page rather than as a stripe the layout forgot to finish. */
/* Wraps because the copyright is long: on a narrow phone the two stack centred
   rather than being squeezed onto one line. */
.foot-bottom{padding:1.5rem 0 1.6rem;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.5rem 1rem;font-size:.8rem;color:rgba(255,255,255,.75)}
.foot-bottom .be{font-weight:700;color:#fff}

/* load animation */
[data-animate]{opacity:0;transform:translateY(16px);animation:fadeUp .6s ease-out forwards}
@keyframes fadeUp{to{opacity:1;transform:none}}

/* ================= mobile nav ================= */
.nav-toggle{display:none;align-items:center;justify-content:center;flex:none;width:40px;height:40px;padding:0;border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--ink);cursor:pointer;transition:border-color .15s}
.nav-toggle:hover{border-color:#c7cdd9}
.nav-toggle svg{width:20px;height:20px;display:block}
.nav-toggle .ic-close{display:none}
.nav-toggle[aria-expanded="true"] .ic-open{display:none}
.nav-toggle[aria-expanded="true"] .ic-close{display:block}
.mobile-nav{
  position:fixed;left:0;right:0;top:var(--top-h);z-index:45;display:none;flex-direction:column;
  background:rgba(255,255,255,.98);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line);box-shadow:0 24px 40px -28px rgba(5,17,55,.4);
  padding:.4rem clamp(1rem,3vw,2rem) 1rem;max-height:calc(100dvh - var(--top-h));overflow-y:auto;
}
.mobile-nav.open{display:flex}
.mobile-nav a{display:flex;align-items:center;gap:.5rem;font-size:1rem;font-weight:560;color:var(--body);padding:.9rem .3rem;border-bottom:1px solid var(--line)}
.mobile-nav a:last-child{border-bottom:0}
.mobile-nav a:hover,.mobile-nav a:focus-visible{color:var(--blue)}
.mobile-nav a[aria-current]{color:var(--blue);font-weight:650}
.mobile-nav a svg.i{width:.85em;height:.85em;opacity:.5}
@media (min-width:1151px){.mobile-nav.open{display:none}}

/* ================= page shell (subpages, admin) ================= */
.page-head{background:var(--tint-soft);border-bottom:1px solid var(--line)}
.page-head-in{--head-pad:clamp(1.8rem,3.5vw,2.6rem);padding-block:var(--head-pad)}
.page-head h1{--head-h1:clamp(1.5rem,2.8vw,2rem);font-size:var(--head-h1);font-weight:700;margin-top:.35rem}
/* A shorter measure than the body's, and NOT --measure. The page head starts at
   the shell's left edge, under the logo; the body starts after the nav column.
   The two can never share a right edge, so matching their max-widths would buy
   nothing — and a standfirst reads better short anyway. */
.page-head .sub{font-size:.95rem;color:var(--muted);margin-top:.4rem;max-width:60ch}
.breadcrumbs{display:flex;gap:.5rem;align-items:center;font-size:.82rem;color:var(--muted)}
.breadcrumbs a:hover{color:var(--blue)}
.breadcrumbs .sep{opacity:.5}

/* ── the trail, only where nothing else carries the path ─────────────────────
   Every level of the tree has exactly one control that names where you are and
   offers the alternatives: the top bar owns the areas, the subsection strip
   owns an area's folders, the doc rail owns a folder's pages, and the <h1>
   names the page itself. On a wide screen all of them are on the page at once,
   and a trail that reads "inside / Company / Core Values" is then three echoes
   in a row — Company is lit in the top bar, Culture is the active tab, and Core
   Values is the heading an inch below the crumb naming it.

   It is not dead weight everywhere, though, which is why it is hidden rather
   than deleted: below 1151px the top bar folds into the hamburger and takes the
   area with it, and below 901px the rail folds into a summary line. At those
   widths the trail is the only thing left carrying the path, so it comes back.

   Only the area and content pages opt in (.crumbs-fallback). Admin, media and
   the editor have no strip and no rail, so nothing there restates their trail
   and it stays at every width. */
@media (min-width:1151px){
  .breadcrumbs.crumbs-fallback{display:none}
  /* With no trail above it the strip leads the head, and the gap it kept from
     the trail would sit on top of the head's own padding. Both selectors, or
     the collapsible rule's higher specificity would keep writing the gap back. */
  .page-head .area-subnav,
  .page-head.is-collapsible .area-subnav{margin-top:0}
  .page-head .area-subnav.at-foot,
  .page-head.is-collapsible .area-subnav.at-foot{margin-top:1.15rem}
}

/* ── the head collapses as you scroll, instead of leaving ─────────────────────
   Scrolled a screen into a long page, the reader used to have no idea what page
   they were on. So the head stays — but only the part that is still worth the
   room: the trail and the standfirst are answers to "how did I get here", read
   once and done, while the title is the answer to "where am I", which stays
   worth having. They fade and close up; the title shrinks and stays.

   Everything is a function of --head-p (0 at rest → 1 collapsed), which
   page-head.js writes from the scroll position. No transitions: the reader's
   finger IS the clock, which is also what makes it honest under reduced motion.

   The mechanic is the whole trick. Shrinking the head's box would drag the
   article up by the shrink AS WELL as by the scroll, so the page would run
   away from the finger at double speed. Instead the box keeps its full height
   and SLIDES up under the top bar by exactly what its content gives back
   (top: --top-h - shrink×p), with the content bottom-aligned inside it. The
   title therefore rises at 1:1 with the page and lands in the strip, and
   nothing below the head ever moves at any speed but the reader's.

   The class is added by JS: with scripting off, none of this applies and the
   head is the plain band it was, which is a fine way to fail. */
.page-head.is-collapsible{
  position:sticky;z-index:40;
  top:calc(var(--top-h) - var(--head-shrink,0px) * var(--head-p,0));
  height:var(--head-max,auto);
  display:flex;flex-direction:column;justify-content:flex-end;
}
/* width:100% is load-bearing, not a tidy-up. The head is a flex container now,
   which makes .wrap a flex item — and .wrap centres itself with `margin:0 auto`.
   An auto margin on the CROSS axis switches off the stretch, so the head's wrap
   shrank to fit its own text and floated to the middle of the page, off the
   shell's left edge that the logo, the nav and the article all share. */
.page-head.is-collapsible .page-head-in{
  width:100%;
  padding-block:calc(var(--head-pad) - (var(--head-pad) - .62rem) * var(--head-p,0));
}
/* Heights are closed with the MEASURED natural height (page-head.js), not with
   max-height or a scale: only a real height animates the collapse, and only a
   measured one can do it without the box ever having been laid out at 0. */
.page-head.is-collapsible .breadcrumbs{
  height:calc(var(--crumb-h,0px) * (1 - var(--head-p,0)));
  overflow:hidden;
  opacity:calc(1 - var(--head-p,0) * 2.4);   /* gone by a third of the way */
  transform:translateY(calc(var(--head-p,0) * -5px));
}
/* --sub-h is set on each standfirst, not on :root (page-head.js): a head may
   hold more than one, and one shared height would close the taller of them to
   the shorter one's box at rest. */
.page-head.is-collapsible .sub{
  height:calc(var(--sub-h,0px) * (1 - var(--head-p,0)));
  margin-top:calc(.4rem * (1 - var(--head-p,0)));
  overflow:hidden;
  opacity:calc(1 - var(--head-p,0) * 2);
}
.page-head.is-collapsible h1{
  font-size:calc(var(--head-h1) - (var(--head-h1) - 1.15rem) * var(--head-p,0));
  margin-top:calc(.35rem * (1 - var(--head-p,0)));
}

/* ── the area's subsections, inside the head ─────────────────────────────────
   Company has folders where the other areas have none; this is the way between
   them, and it is in the head rather than under it so that it sticks and
   collapses with it (see _area_subnav.html for that, and for why the strip sits
   above the title on a page inside a folder but below it on the area's index).

   It does not fade with --head-p: the trail and the standfirst are read once,
   but "which folder am I in, and what are the others" is worth having a screen
   down a long page — it is the only route between them once you are in one. Its
   margins do close up, so the collapsed strip is a compact bar rather than a
   band with the head's resting air still in it.

   The rule under the tabs is what makes the title below them read as the ACTIVE
   tab's page rather than the tabs' sibling — the same job a tab bar's rule does
   anywhere. On the index there is no title below to separate, and no active tab
   to anchor, so the rule comes off. */
.area-subnav{
  display:flex;gap:.25rem;overflow-x:auto;scrollbar-width:none;
  margin:.9rem 0 .55rem;border-bottom:1px solid var(--line);
}
.area-subnav::-webkit-scrollbar{display:none}
.area-subnav.at-foot{margin:1.15rem 0 0;border-bottom:0}
.area-subnav a{
  flex:none;padding:.55rem .7rem;font-size:.9rem;font-weight:560;color:var(--muted);
  border-bottom:2px solid transparent;margin-bottom:-1px;   /* over the strip's rule */
  transition:color .15s,border-color .15s;
}
.area-subnav a:first-child{margin-left:-.7rem}   /* the label, not its padding, on the shell edge */
.area-subnav a:hover{color:var(--ink)}
.area-subnav a.active{color:var(--blue);font-weight:650;border-bottom-color:var(--blue)}
.page-head.is-collapsible .area-subnav{
  margin-top:calc(.9rem - .5rem * var(--head-p,0));
  margin-bottom:calc(.55rem - .25rem * var(--head-p,0));
}
/* The at-foot strip is the head's last element: no strip below it wants a gap,
   so its bottom margin stays 0 (the head's own padding is the only space under
   it) — as .area-subnav.at-foot already asks. The collapsible rule above,
   written for the top strip, is more specific and would otherwise write .55rem
   back under it. That margin was also invisible to page-head.js, which measures
   with .is-collapsible off: the head came out ~9px short, so with flex-end the
   title rode up under the top bar and the un-measured margin showed as blank
   below the strip. Holding it at 0 in both states fixes the height and the gap. */
.page-head.is-collapsible .area-subnav.at-foot{margin-bottom:0}

/* ================= cards, forms & admin components ================= */
.card{background:#fff;border:1px solid var(--line);border-radius:var(--radius-card);padding:1.6rem 1.65rem}
.card + .card{margin-top:1.4rem}
.card > h2{font-size:1.15rem;font-weight:700}
.card .card-sub{font-size:.88rem;color:var(--muted);margin:.25rem 0 1.1rem}
.card[id^="section-"]{scroll-margin-top:calc(var(--chrome) + 1.4rem)}

/* A card that groups several concerns (the media library: connection, analysis,
   pipeline) separates them with a hairline rather than splitting into cards. */
.subsection{border-top:1px solid var(--line);margin-top:1.5rem;padding-top:1.3rem}
.subsection > h3{font-size:.98rem;font-weight:700}
.card-actions + .subsection{margin-top:1.1rem}

/* A "Recent jobs" list, collapsed by default so it stays out of the way until
   asked for. Same drawn caret as the doc-side disclosure. */
.jobs-collapse-toggle{
  display:flex;align-items:center;gap:.5rem;
  font-size:.9rem;font-weight:700;color:var(--ink);cursor:pointer;
  list-style:none;width:max-content;
}
.jobs-collapse-toggle::-webkit-details-marker{display:none}
.jobs-collapse-toggle::before{
  content:"";width:0;height:0;flex:none;
  border-left:5px solid var(--muted);
  border-top:4px solid transparent;border-bottom:4px solid transparent;
  transition:transform .15s;
}
.jobs-collapse[open] > .jobs-collapse-toggle::before{transform:rotate(90deg)}
.jobs-collapse-body{margin-top:.5rem}

/* The settings page shares the content pages' doc-layout: side nav on the
   left (see "content page layout" below), cards in the right column. */
.settings-col{min-width:0}

.field{display:flex;flex-direction:column;gap:.35rem}
.field > label{font-size:.82rem;font-weight:600;color:var(--ink)}
.field .help{font-size:.75rem;color:var(--muted)}
.input,select.input,textarea.input{
  width:100%;font:inherit;font-size:.92rem;color:var(--ink);background:var(--surface);
  border:1.5px solid var(--line);border-radius:12px;padding:.55rem .8rem;outline:none;
  transition:border-color .15s,box-shadow .15s,background .15s;
}
.input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(35,35,255,.12);background:#fff}
.input::placeholder{color:#9AA1AE}
.form-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}
.form-grid .span2{grid-column:span 2}

/* Action cell (e.g. a "Test connection" button) that must line up with the
   input in the sibling column: a spacer stands in for the missing label, and
   the button sits in a band the same height as an input, vertically centered. */
.field-spacer{height:1.31rem}
.field-action .action-row{display:flex;align-items:center;gap:.7rem;min-height:2.76rem}

/* ================= date picker (styled replacement for native type=date) ============= */
.datepicker{position:relative}
.datepicker-field{
  width:100%;display:flex;align-items:center;gap:.6rem;font:inherit;font-size:.92rem;
  color:var(--ink);background:var(--surface);border:1.5px solid var(--line);border-radius:12px;
  padding:.55rem .8rem;text-align:left;cursor:pointer;
  transition:border-color .15s,box-shadow .15s,background .15s;
}
.datepicker-field:hover{border-color:#c7cdd9}
.datepicker-field:focus-visible{border-color:var(--blue);box-shadow:0 0 0 3px rgba(35,35,255,.12);background:#fff;outline:none}
.datepicker.open .datepicker-field{border-color:var(--blue);box-shadow:0 0 0 3px rgba(35,35,255,.12);background:#fff}
.datepicker-field > svg.i{width:1.05em;height:1.05em;color:var(--muted);flex:none}
.datepicker-field .dp-label{flex:1;min-width:0}
.datepicker-field.empty .dp-label{color:#9AA1AE}

.datepicker-pop{
  position:absolute;z-index:70;top:calc(100% + .4rem);left:0;width:18rem;max-width:calc(100vw - 2rem);
  background:#fff;border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow-pop);padding:.85rem;
}
.dp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.4rem}
.dp-title{font-weight:650;color:var(--ink);font-size:.92rem}
.dp-head button{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;color:var(--body)}
.dp-head button:hover{background:var(--tint-soft);color:var(--ink)}
.dp-head svg.i{width:1.1em;height:1.1em}
.dp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.dp-dow{font-size:.66rem;font-weight:650;letter-spacing:.03em;text-transform:uppercase;color:var(--muted);text-align:center;padding:.35rem 0 .2rem}
.dp-day{height:32px;border-radius:9px;font-size:.82rem;font-weight:500;color:var(--body);display:grid;place-items:center;cursor:pointer;transition:background .12s,color .12s}
.dp-day:hover{background:var(--tint-soft);color:var(--ink)}
.dp-day.today{box-shadow:inset 0 0 0 1.5px var(--line);font-weight:650}
.dp-day.selected,.dp-day.selected:hover{background:var(--blue);color:#fff;font-weight:650;box-shadow:none}
.dp-day.blank{visibility:hidden;cursor:default}
.dp-foot{display:flex;justify-content:space-between;margin-top:.5rem;padding-top:.5rem;border-top:1px solid var(--surface)}
.dp-foot button{font-size:.82rem;font-weight:600;color:var(--blue);padding:.25rem .4rem;border-radius:8px}
.dp-foot button:hover{color:var(--blue-dark);background:var(--tint-soft)}
.check{display:flex;align-items:center;gap:.6rem;font-size:.9rem;font-weight:550;color:var(--ink);cursor:pointer}
.check input{width:1.05rem;height:1.05rem;accent-color:var(--blue)}

/* line-height:normal, not the 1.6 inherited from body. align-items:center centres
   the *line box*, and at 1.6 that box is much taller than the text it holds; the
   leading is then split by the font's own asymmetric ascent/descent, which left
   the label sitting ~1.5px high in every button. At `normal` the line box is the
   text box, so centring it centres the text exactly. The padding absorbs the
   leading the button loses, so the pills keep the height they have today. */
.btn{
  display:inline-flex;align-items:center;gap:.5rem;background:var(--blue);color:#fff;
  font-weight:600;font-size:.9rem;line-height:normal;
  /* A transparent border, not none: .outline and .danger carry a 1.5px one, so
     without it the filled and subtle variants stand 3px shorter than the
     buttons they sit next to in the same row. The border is in every variant's
     box; only its colour changes. */
  border:1.5px solid transparent;
  border-radius:999px;padding:.73rem 1.2rem;
  transition:background .15s,border-color .15s;
}
.btn:hover{background:var(--blue-dark)}
.btn:disabled{opacity:.55;cursor:not-allowed}
.btn[hidden]{display:none}
.btn.outline{background:#fff;color:var(--blue);border-color:var(--blue)}
.btn.outline:hover{background:#F0F0FF}
/* The border is what makes this one a BUTTON. Its fill is --tint-soft, and it is
   used on surfaces that are also --tint-soft (the draft banner) — where, with no
   outline, it reads as a line of text that happens to be bold. */
.btn.subtle{background:var(--tint-soft);color:var(--ink);border-color:var(--line)}
.btn.subtle:hover{background:var(--tint);border-color:#c7cdd9}
.btn.danger{background:#fff;color:#B91C1C;border-color:#FCA5A5}
.btn.danger:hover{background:var(--red-bg)}
.btn.sm{font-size:.8rem;padding:.52rem .85rem}

.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:.8rem;margin-top:1.2rem}
.status-msg{font-size:.85rem;display:none}
.status-msg.ok{display:inline;color:#15803D}
.status-msg.err{display:inline;color:#B91C1C}
/* Why an action failed, shown next to the button that failed. Unlike
   .status-msg it does not time out — it stays until the problem is fixed. */
.form-err{font-size:.8rem;color:#B91C1C;margin-right:auto;text-align:left}

.table{width:100%;border-collapse:collapse;font-size:.9rem}
.table th{text-align:left;font-size:.7rem;font-weight:650;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);padding:.5rem .6rem;border-bottom:1px solid var(--line)}
.table td{padding:.65rem .6rem;border-bottom:1px solid var(--surface);vertical-align:middle}
.table tr:hover td{background:var(--surface)}
.table .empty td{text-align:center;color:var(--muted);font-style:italic;padding:1.6rem}

/* ── sortable columns ───────────────────────────────────────────────────── */
.table th[data-sort]{cursor:pointer;user-select:none;white-space:nowrap}
.table th[data-sort]:hover{color:var(--body)}
.table th[data-sort]::after{content:"";display:inline-block;margin-left:.35rem;
  border-left:3.5px solid transparent;border-right:3.5px solid transparent;
  /* A neutral column keeps the arrow's space but stays invisible, so the header
     does not shift when it becomes the sorted one. */
  border-bottom:4px solid transparent;vertical-align:.08em}
.table th[aria-sort=ascending]::after{border-bottom:4px solid var(--blue)}
.table th[aria-sort=descending]::after{border-bottom:0;border-top:4px solid var(--blue)}
.table th[data-sort]:focus-visible{outline:2px solid var(--blue);outline-offset:-2px;border-radius:6px}

/* ── editable table: a row expands in place into its edit form ───────────── */
.table-editable tr.row-edit{cursor:pointer}
/* Row actions (Remove) keep quiet until the row is hovered. Guarded by a hover
   query: a touch device has no hover, so there the button is always visible —
   it would otherwise be unreachable. Faded rather than hidden, so the row does
   not reflow. Keyboard users get it back via :focus-visible. */
@media (hover: hover) and (pointer: fine){
  .table-editable tr.row-edit .row-action{opacity:0;transition:opacity .12s}
  .table-editable tr.row-edit:hover .row-action,
  .table-editable tr.row-edit:focus-within .row-action,
  .table-editable tr.row-edit .row-action:focus-visible{opacity:1}
}
.table-editable tr.row-edit:focus-visible{outline:2px solid var(--blue);outline-offset:-2px}
/* The row being edited keeps its place in the DOM but yields the floor to the
   editor row that follows it. */
.table-editable tr.is-editing{display:none}
.table-editable tr.row-editor-row td{padding:.2rem 0 .6rem;background:#fff}
.table-editable tr.row-editor-row:hover td{background:#fff}

/* White, like every other card. Inputs are filled with var(--surface) and lift
   to white on focus; on a var(--surface) panel they would be the same colour as
   their container and read as disabled. So the open row is set apart by border
   and shadow, never by a fill. */
.row-editor{background:#fff;border-radius:16px;padding:1.1rem 1.2rem;
  border:1.5px solid var(--line);box-shadow:0 4px 18px rgba(5,17,55,.10)}
.row-editor[hidden]{display:none}
/* Row action (e.g. Test) bottom-left, Cancel/Save bottom-right. */
.editor-actions{display:flex;align-items:center;justify-content:space-between;
  gap:.8rem;margin-top:1rem;flex-wrap:wrap}
.editor-actions-left,.editor-actions-right{display:flex;align-items:center;gap:.8rem}
.editor-actions .form-err{margin-right:0}

.perm-chips{display:flex;flex-wrap:wrap;gap:.35rem}
.perm-chip{font-size:.72rem;font-weight:650;border-radius:999px;padding:.18rem .6rem;background:var(--tint-soft);color:var(--deep)}
.perm-chip.publish{background:#F0F0FF;color:var(--blue)}
/* Early access (src/features.py) in amber, not blue: it is not a stronger
   grant on the same ladder, it is a different kind of thing — a temporary
   invitation to something unfinished — and the users table should not invite
   reading it as "publish and a bit more". */
.perm-chip.feature{background:var(--amber-bg);color:var(--amber-tx)}

.ue-features{margin-top:.9rem;padding-top:.9rem;border-top:1px solid var(--line)}
.ue-features-head{font-size:.72rem;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}
.ue-feature{align-items:flex-start;gap:.55rem}
.ue-feature + .ue-feature{margin-top:.45rem}
.ue-feature strong{display:block;color:var(--ink);font-weight:650}
.ue-feature-hint{display:block;font-size:.78rem;color:var(--muted);line-height:1.35}

/* combobox dropdown (GitLab user search) */
.combo{position:relative}
.combo .options{
  position:absolute;left:0;right:0;top:calc(100% + .3rem);z-index:30;background:#fff;
  border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-pop);
  max-height:16rem;overflow-y:auto;padding:.3rem;display:none;
}
.combo.open .options{display:block}
.combo .opt{display:flex;align-items:center;gap:.65rem;width:100%;text-align:left;padding:.5rem .65rem;border-radius:10px;font-size:.9rem}
.combo .opt:hover,.combo .opt.active{background:var(--tint-soft)}
.combo .opt .ava{width:28px;height:28px;border-radius:999px;background:var(--tint);color:var(--deep);font-size:.68rem;font-weight:700;display:grid;place-items:center;flex:none}
.combo .opt .un{color:var(--muted);font-size:.8rem;margin-left:auto}
.combo .none{padding:.7rem .65rem;font-size:.85rem;color:var(--muted)}

/* path-prefix permission editor: one row per grant.
   All three controls share the .input height; both dropdowns are .combo. */
.perm-rows{display:flex;flex-direction:column;gap:.55rem;margin-top:.3rem}
.perm-row{display:flex;align-items:stretch;gap:.5rem}
.perm-row .perm-path{flex:1;min-width:0}
.perm-row .perm-level{flex:none;width:10.5rem}
.perm-row .perm-level .options{left:auto;right:0;width:17rem}
.select-btn{display:flex;align-items:center;justify-content:space-between;gap:.5rem;cursor:pointer;text-align:left;height:100%}
.select-btn svg{flex:none;color:var(--muted)}
.perm-remove{flex:none;width:2.6rem;padding:0;border-radius:12px}
@media (max-width:640px){
  .perm-row{flex-wrap:wrap}
  .perm-row .perm-path{flex-basis:100%}
  .perm-row .perm-level{flex:1}
}

/* placeholder blocks on area pages */
.placeholder{
  border:1.5px dashed #C7CDD9;border-radius:var(--radius-card);background:var(--surface);
  padding:2.4rem;display:flex;flex-direction:column;align-items:flex-start;gap:.5rem;
}
.placeholder h3{font-size:1.05rem}
.placeholder p{font-size:.9rem;color:var(--muted);max-width:52ch}

/* ================= responsive ================= */
/* Collapse the full nav to the hamburger before the header row can overflow.
   Higher than the content-grid breakpoint below so grids keep their columns longer. */
@media (max-width:1150px){
  nav.main{display:none}
  .nav-toggle{display:inline-flex}
  .top-in{gap:1rem}
  .top-actions{margin-left:auto}
}
@media (max-width:1000px){
  .today-grid{grid-template-columns:1fr}
  /* Nothing about the bento changes here any more. It used to step row 1 from
     three-up to two-up; with four half-width cards the desktop layout IS the
     tablet layout, and it collapses in one move at 560. */
  .duo{grid-template-columns:1fr}
  .deck .watermark{display:none}
  .form-grid{grid-template-columns:1fr}
  .form-grid .span2{grid-column:span 1}
}
@media (max-width:560px){
  /* Once the row wraps, space-between strands whatever fell to the second line
     against the left edge. Centred, the two read as one closing block. */
  .foot-bottom{justify-content:center;text-align:center}
  .b-company,.b-feat,.b-media,.b-dir{grid-column:span 12}
  /* No `order` here, and that is worth a line: this block used to reorder the six
     cards on a phone so the deck tile did not lead — which meant markup order and
     visual order disagreed, the one place on the site where a screen reader
     announced a different sequence than the eye got. Four cards in nav order do
     not need it. Company, Portfolio, Infrastructure, Media reads the same way down a
     phone as it does across a desktop, so the mismatch is gone rather than
     managed. */
  .search-bar{padding-left:1rem}
  .ask-btn{padding:.62rem 1rem}
  .search-mini{padding:0;width:40px;height:40px;justify-content:center}
  .search-mini .s-label,.search-mini .s-kbd{display:none}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
  [data-animate]{opacity:1;transform:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   CONTENT PAGES — the prose layer
   ═══════════════════════════════════════════════════════════════════════════
   The design system is component-shaped: every heading, link and list on the
   rest of the site is styled by its component, so there is no styling for
   plain <p>/<ul>/<a>/<table> to inherit. Markdown emits exactly those, and
   without this block it renders broken — invisible inline links (a{color:
   inherit;text-decoration:none}), list bullets outside the content box (the
   reset zeroes padding while list-style stays disc), and paragraphs with no
   gap (p{margin:0}). Everything below is scoped to .prose so it cannot leak
   back into the components.

   The type scale deliberately runs SMALLER than UA defaults, to sit beside
   .section-title / .svc h3 / .principle h4 rather than shout over them. */

/* ONE sidebar, not two. Splitting "which page" (left) from "where in the page"
   (right) cost 40% of the width and made the reader look in two places. The
   area's pages live in one column, and the current page expands in place to
   show its own headings. */
/* Nav and article share one centred shell, so the page is symmetric and the
   nav's left edge sits on the logo's.

   Two rejected alternatives, recorded because both looked right on paper:

   - A wider shell for content pages only. Bought the room, cost the single left
     edge that makes a page read as composed — the logo sat on one, the
     breadcrumb and text on another.

   - The nav in the left gutter, outside the shell, with the article spanning the
     full shell. Aligned perfectly and gave the table 1152px, but a page cannot
     be balanced with weight on the left and nothing to answer it on the right:
     at 1866px it left 61px of margin on one side and 357px on the other. There
     is no right-hand element to counterweight it, because the two navs are
     deliberately merged into one.

   The room comes from the shell instead (--maxw). The disclosure is the
   BASE state and the column is the enhancement — never the reverse. A media
   query adds no specificity, so a `max-width` query that tries to *undo* a base
   rule loses to any later rule with the same selector, which is exactly how the
   nav once ended up sticky-positioned on top of a scrolling table. */
.doc-layout{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  align-items:start;
}

/* Base: a static, collapsed disclosure above the article. */
.doc-side{margin:1.2rem 0 .4rem;font-size:.9rem}
.doc-side-box{
  min-height:0;display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:12px;background:#fff;
}
.doc-side-toggle{
  display:flex;align-items:center;gap:.5rem;
  padding:.7rem .9rem;font-weight:600;color:var(--ink);cursor:pointer;
  list-style:none;
}
.doc-side-toggle::-webkit-details-marker{display:none}
/* The disclosure triangle, drawn rather than borrowed: the UA marker cannot be
   positioned or coloured consistently across browsers. */
.doc-side-toggle::before{
  content:"";width:0;height:0;flex:none;
  border-left:5px solid var(--muted);
  border-top:4px solid transparent;border-bottom:4px solid transparent;
  transition:transform .15s;
}
.doc-side-box[open] .doc-side-toggle::before{transform:rotate(90deg)}
.doc-side-count{
  margin-left:auto;font-size:.78rem;font-weight:600;color:var(--muted);
  background:var(--surface);border-radius:999px;padding:.1rem .5rem;
}
.doc-side-scroll{padding:0 .5rem .8rem}

/* Wide enough for two columns: the nav becomes one, sticky under the 64px
   header.

   A sticky box cannot leave its containing block — for a grid item, its grid
   area. At the foot of the page the footer sits below the grid and steals the
   room the nav needs, so a tall nav gets shoved up out of view. Padding inside
   the article does NOT buy that room back: it grows the grid and the document
   equally, leaving the grid's bottom edge in the same place at maximum scroll.
   The only real lever is the nav's own height, and how much room exists down
   there depends on the footer — which CSS cannot measure. So --doc-side-max is
   measured in content.js and the nav scrolls inside itself when it does not fit.
   The fallback keeps it sane with JS off. */
@media (min-width:901px){
  /* 13rem fits every current entry on one line ("Recipes & Best Practices"
     with ~10px to spare); a genuinely longer future title wraps, which is
     fine. Sized into --maxw's sum — grow one only by shrinking the other. */
  .doc-layout{grid-template-columns:13rem minmax(0,1fr);gap:2rem}
  /* A page with neither sibling pages nor headings has no rail (see
     content.html); with no aside the article would otherwise land in the 13rem
     nav column. Reclaim full width. */
  .doc-layout--solo{grid-template-columns:minmax(0,1fr)}
  .doc-side{
    margin:0;
    position:sticky;top:var(--chrome);
    max-height:var(--doc-side-max,calc(100vh - var(--chrome)));
    display:flex;flex-direction:column;
    font-size:.86rem;
  }
  .doc-side-box{border:0;background:none;border-radius:0}
  .doc-side-toggle{display:none}
  /* The 1.4rem top padding matches .prose's, so the first page in the nav starts
     on the same line as the first block of the article. */
  .doc-side-scroll{flex:0 1 auto;min-height:0;overflow-y:auto;padding:1.4rem .25rem 2rem 0}
}
.doc-pages{display:flex;flex-direction:column}
.page-block + .page-block{margin-top:.35rem}

/* A folder of sibling pages, folded (Services, Industries). The <details>
   starts closed and the template opens the one holding the current page, so
   the rail stays three lines tall until the reader steps into a group — and
   the other group is still one click away, no JS involved. The summary is the
   same quiet label the heading was, plus the rail's own count pill and a
   drawn disclosure caret (same reasoning as .doc-side-toggle: the UA marker
   cannot be positioned or coloured consistently). */
.doc-fold{margin-top:.9rem}
.doc-fold > .doc-group{
  display:flex;align-items:center;gap:.45rem;
  padding:.3rem .6rem;cursor:pointer;list-style:none;border-radius:8px;
  font-size:.72rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--muted);
}
.doc-fold > .doc-group:hover{background:var(--surface);color:var(--ink)}
.doc-fold > .doc-group::-webkit-details-marker{display:none}
.doc-fold > .doc-group::before{
  content:"";width:0;height:0;flex:none;
  border-left:4px solid currentColor;
  border-top:3.5px solid transparent;border-bottom:3.5px solid transparent;
  transition:transform .15s;
}
.doc-fold[open] > .doc-group::before{transform:rotate(90deg)}
.doc-fold > .page-block:first-of-type{margin-top:.25rem}
.doc-pages > .doc-fold:first-child{margin-top:0}

/* `overflow-wrap:anywhere` here and on the outline below, because the rail is
   a fixed 13rem track and German writes "Arbeitsunfähigkeitsbescheinigung" —
   one word, 239px, in a 204px box. Without it the word simply hangs out of the
   column, and because .doc-side-scroll sets overflow-y the other axis computes
   to `auto` rather than `visible`, so the whole rail grew a horizontal
   scrollbar for the sake of two entries. `anywhere` rather than `break-word`:
   only `anywhere` also shrinks min-content, which is what keeps a long word
   from forcing the box wide from the inside. */
.page-title{
  display:block;padding:.34rem .6rem;border-radius:8px;
  font-weight:600;color:var(--ink);line-height:1.35;
  overflow-wrap:anywhere;
}
.page-title:hover{background:var(--surface)}
.page-title.active{color:var(--blue)}
/* Above the first heading, nothing in the outline is active — so the page
   itself is where you are. Exactly one thing is ever highlighted. */
.page-title.active.at-top{background:var(--tint-soft,#ECF0F9)}

.page-subnav{display:flex;flex-direction:column;margin-top:.1rem}
/* A grid of one row rather than a block, so the unfold below can animate to the
   entry's OWN height (0fr -> 1fr) instead of to a guessed one. The label needs
   the wrapper span content.html gives it: `min-height:0` is what lets the row
   actually close, and the span's own overflow is what hides the text while it
   is closed. */
.page-subnav a{
  display:grid;grid-template-rows:1fr;padding:.28rem .6rem;border-radius:8px;
  color:var(--muted);line-height:1.35;overflow-wrap:anywhere;
  transition:color .15s,background .15s,grid-template-rows .18s ease,opacity .18s ease,padding .18s ease;
}
.page-subnav a > span{min-height:0;overflow:hidden}
.page-subnav a:hover{color:var(--ink);background:var(--surface)}
.page-subnav a.active{color:var(--blue);font-weight:600;background:var(--tint-soft,#ECF0F9)}

/* Progressive disclosure. The recipes page has two dozen headings; showing
   them all at once is a wall. H2s always show; H3/H4 unfold only while you are
   inside their section. They stay in the DOM so the transition can animate.

   The open state is `1fr` — the row the label needs — and NOT a max-height.
   A cap has to be a guess about how long a heading is, and this page is where
   that guess died: an FAQ's H3s are whole questions, so "Ich habe zusätzlich zu
   meinem Arbeitsvertrag eine Zusatzvereinbarung..." wants 133px, the 3rem cap
   gave it 48, and the reader got two lines with the second one sliced through
   the middle. Raising the cap only moves the length at which it happens again —
   and a cap far above the content also wrecks the animation it exists for,
   since max-height would spend most of the .18s crossing height the entry does
   not have. An `fr` row is measured, not guessed: nothing is ever cut, however
   long the heading, and the unfold still animates because grid-template-rows
   interpolates. */
.page-subnav a[data-level="2"]{padding-left:1.1rem}
.page-subnav a[data-level="3"],
.page-subnav a[data-level="4"]{
  grid-template-rows:0fr;opacity:0;padding-top:0;padding-bottom:0;
  pointer-events:none;
}
.page-subnav a[data-level="3"].is-revealed,
.page-subnav a[data-level="4"].is-revealed{
  grid-template-rows:1fr;opacity:1;padding-top:.28rem;padding-bottom:.28rem;pointer-events:auto;
}
.page-subnav a[data-level="3"].is-revealed{padding-left:1.9rem;font-size:.82rem}
.page-subnav a[data-level="4"].is-revealed{padding-left:2.7rem;font-size:.8rem;color:#9CA3AF}

/* The outline standing on its own, where the folder holds a single page. Same
   colours and states as any .page-subnav — muted inactive, blue-on-tint active
   — so it reads as one system with the multi-page rail; the ONE difference is
   position: with no page title above it, its H2s take the rail's left edge
   (matching a page title's own indent) and deeper levels shift in to match, so
   the nesting still reads true. */
.doc-toc a[data-level="2"]{padding-left:.6rem}
.doc-toc a[data-level="3"].is-revealed{padding-left:1.4rem}
.doc-toc a[data-level="4"].is-revealed{padding-left:2.2rem}

/* ── the prose itself ─────────────────────────────────────────────────────── */

.prose{color:var(--body);font-size:1rem;line-height:1.7;padding-top:1.4rem;padding-bottom:6rem}
.prose > *:first-child{margin-top:0}

/* Two lanes, because the page has two legitimate right edges.

   Prose stops being readable past ~80 characters; a table, a code block or a
   diagram wants every pixel it can get. There is no single column width that is
   right for both, which is why tuning one kept breaking the other. So stop
   picking: text runs in the measure lane, data spans the whole column, and each
   block declares which it is.

   The default is the measure and the wide lane is the opt-out, deliberately —
   a block type nobody has thought of yet (a Company page's org chart, say)
   inherits a readable width rather than a broken one.

   Expressed with max-width rather than `display: grid` with named lines. The
   grid version is the textbook one, but grid items do not collapse margins, so
   the prose rhythm (a paragraph's 1.05rem bottom meeting a heading's 2.6rem
   top) would start summing to 3.65rem and every vertical gap in the document
   would need re-tuning. Same lanes, none of that.

   Scoped to article.prose: the clause generator's output is .prose too, and it
   sets its own width inside the lightbox. */
article.prose > *{max-width:var(--measure)}
article.prose > .c-table-wrap,
article.prose > .c-code-wrap,
article.prose > pre,
article.prose > .c-widget,
article.prose > .c-cards,
article.prose > .c-details{max-width:none}

/* A details box is a container, so it takes the wide lane — but the prose
   inside it is still prose. */
.c-details-body > *{max-width:var(--measure)}
.c-details-body > .c-table-wrap,
.c-details-body > .c-code-wrap,
.c-details-body > pre,
.c-details-body > .c-widget{max-width:none}

.prose h2,.prose h3,.prose h4{
  color:var(--ink);line-height:1.25;letter-spacing:-.01em;
  /* so an #anchor jump clears the sticky chrome — which, on a page whose head
     collapses instead of leaving, is the top bar plus that head's strip. */
  scroll-margin-top:calc(var(--chrome) + 1.4rem);
}
.prose h2{font-size:1.4rem;font-weight:700;margin:2.6rem 0 .9rem}
.prose h3{font-size:1.1rem;font-weight:650;margin:2rem 0 .7rem}
.prose h4{font-size:.97rem;font-weight:650;margin:1.5rem 0 .5rem}
.prose h2 + h3,.prose h3 + h4{margin-top:1rem}

/* The heading anchor. Present but quiet — it appears on hover, and is always
   reachable by keyboard. */
.c-anchor{
  margin-left:.45rem;color:var(--line);font-weight:400;text-decoration:none;
  opacity:0;transition:opacity .12s ease;
}
.prose h2:hover .c-anchor,.prose h3:hover .c-anchor,.prose h4:hover .c-anchor,
.c-anchor:focus-visible{opacity:1;color:var(--blue)}

.prose p{margin:0 0 1.05rem}
.prose strong{color:var(--ink);font-weight:650}

/* Inline links must be visible in a paragraph — the site-wide rule makes them
   invisible, which is right for components and wrong for prose. */
.prose a{
  color:var(--blue);text-decoration:underline;
  text-decoration-color:color-mix(in srgb,var(--blue) 30%,transparent);
  text-underline-offset:2px;
}
.prose a:hover{text-decoration-color:var(--blue)}

/* The reset zeroes padding, so bullets need the indent putting back. */
.prose ul,.prose ol{margin:0 0 1.05rem;padding-left:1.35rem}
.prose ul{list-style:disc}
.prose ol{list-style:decimal}
.prose li{margin:.3rem 0}
.prose li::marker{color:var(--muted)}
.prose li > ul,.prose li > ol{margin:.3rem 0 .3rem}

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

.prose blockquote{
  margin:1.4rem 0;padding:.2rem 0 .2rem 1.1rem;
  border-left:3px solid var(--cyan);color:var(--muted);
}
.prose blockquote p:last-child{margin-bottom:0}

/* Tables: projects-using-ai is one big table, and there is no table styling
   anywhere on the site to fall back on.

   The scrolling happens on the WRAPPER. Putting `display:block;overflow-x:auto`
   on the <table> is the usual trick and it silently breaks it: the table stops
   being a table, <thead> and <tbody> get their own anonymous tables, and their
   columns no longer line up. */
.c-table-wrap{overflow-x:auto;margin:0 0 1.3rem;max-width:100%}
.prose table{
  width:100%;border-collapse:collapse;font-size:.9rem;
}
/* No outer padding: the first column starts on the article's left edge and the
   last ends on its right, so the whole cell padding budget goes into the gaps
   BETWEEN columns, where it does the work of separating them. Padding on the
   outer edges only pushed the columns inward and bought nothing — the table's
   own rules already mark where it begins and ends. */
.prose thead th{
  text-align:left;font-weight:650;color:var(--ink);white-space:nowrap;
  border-bottom:2px solid var(--line);padding:.55rem 1.5rem .55rem 0;
}
.prose tbody td{
  border-bottom:1px solid var(--line);padding:.55rem 1.5rem .55rem 0;
  vertical-align:top;
}
.prose thead th:last-child,
.prose tbody td:last-child{padding-right:0}
.prose tbody tr:hover{background:var(--surface)}

/* Code. Highlighting is pygments-rendered server-side (see css/code.css). */
.prose code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.86em;background:var(--surface);border:1px solid var(--line);
  border-radius:5px;padding:.1em .35em;color:var(--ink);
}
/* The font-size and line-height belong HERE, on the <pre>, not on the <code>
   inside it. The <pre> was inheriting the prose's 16px/1.7, and an inline
   <code> cannot shrink the line box its parent's strut already established —
   so every line of code sat in a 27px box regardless of what the code asked
   for, and the block came out looking double-spaced. */
/* The wrapper carries the margin and the positioning; the <pre> scrolls. The
   copy button is anchored to the wrapper, so it stays in the corner while a long
   line scrolls underneath it. */
.c-code-wrap{position:relative;margin:0 0 1.3rem}
.prose pre.c-code{
  margin:0;padding:1rem 1.1rem;overflow-x:auto;
  background:#0d1117;border-radius:10px;
  font-size:.875rem;line-height:1.5;
}
.prose pre.c-code code{
  background:none;border:0;padding:0;color:#E6EDF3;font-size:inherit;line-height:inherit;
}
/* Revealed on hover, as on the AI wiki — and pinned on a touch screen, where
   there is nothing to hover. Right offset clears a vertical scrollbar. */
.c-copy{
  position:absolute;top:.5rem;right:.5rem;
  width:2rem;height:2rem;display:inline-flex;align-items:center;justify-content:center;
  color:#C9D1D9;background:rgba(22,27,34,.85);
  border:1px solid rgba(255,255,255,.12);border-radius:6px;
  cursor:pointer;opacity:0;
  transition:opacity .15s,background .15s,color .15s,border-color .15s;
}
.c-copy svg{width:.95rem;height:.95rem}
.c-code-wrap:hover .c-copy,.c-copy:focus-visible{opacity:1}
.c-copy:hover{background:rgba(48,54,61,.95);color:#fff;border-color:rgba(255,255,255,.2)}
.c-copy.is-copied{color:#7EE787;border-color:rgba(126,231,135,.4);opacity:1}
.c-copy.is-failed{color:#FF7B72;border-color:rgba(255,123,114,.4);opacity:1}
@media (hover:none){.c-copy{opacity:1}}

/* @mention → Mattermost. The AI pages name their owners this way and people rely
   on it, so it is a link, not decoration.

   A link, though — not a pill. The pill's padding and its bold weight made the
   Contact column the widest in the projects table, for emphasis nobody asked
   for: a table of names does not need every name shouting. Underlined, in the
   link colour, at normal weight, exactly as the AI wiki had it. */
.c-mention{
  color:var(--blue);
  text-decoration:underline;text-decoration-color:var(--tint,#C7D3EA);
  text-underline-offset:2px;
}
.c-mention:hover{text-decoration-color:var(--blue)}

/* :::{details} — collapsible. The recipes page hides most of its setup
   instructions in these. */
.c-details{
  margin:0 0 1.1rem;border:1px solid var(--line);border-radius:12px;
  background:var(--surface);overflow:hidden;
}
.c-details > summary{
  cursor:pointer;padding:.7rem 1rem;font-weight:600;font-size:.92rem;
  color:var(--ink);list-style:none;display:flex;align-items:center;gap:.5rem;
}
.c-details > summary::-webkit-details-marker{display:none}
/* A square with two borders, rotated. The two states are NOT symmetric, so
   flex centring is not enough:

     closed, rotate(-45deg) -> a "›". The ink runs corner to corner, so it is
       already centred on the element's box.
     open,  rotate(45deg)  -> a "v". The same two borders land at (-5.66, 0),
       (0, +5.66), (+5.66, 0): the ink sits entirely in the LOWER half of the
       box, with its optical centre 2.83px below the geometric one.

   Hence the lift on the open state — the box is centred either way; only the
   mark inside it is not. */
.c-details > summary::before{
  content:"";width:.5rem;height:.5rem;flex:none;
  border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);
  transform:rotate(-45deg);transition:transform .15s ease;
}
/* :not(.js-closed): content.js keeps the <details> permanently [open] and
   folds the box by hiding its body instead (hidden="until-found", so the
   browser can still search it) — the class carries the folded state then. */
.c-details[open]:not(.js-closed) > summary::before{transform:translateY(-2.8px) rotate(45deg)}
.c-details > summary:hover{background:var(--tint-soft,#ECF0F9)}
/* Even padding, with the children's own margins zeroed at the ends. The .2rem
   top used to be a fudge against a first child's top margin — but nothing here
   HAS a top margin (paragraphs and code blocks carry theirs on the bottom), so
   it just made the box look bottom-heavy: 3px above the content, 16px below. */
.c-details-body{padding:1rem;border-top:1px solid var(--line);background:#fff}
/* hidden="until-found" is content-visibility:hidden, NOT display:none — the
   element's own box still renders. Folded, it must not show as an empty
   padded strip under the summary. */
.c-details-body[hidden]{padding:0;border-top:0}
/* .prose-qualified so these beat `.prose pre.c-code{margin:0 0 1.3rem}` — which
   is (0,2,1) and quietly outranked the bare `.c-details-body > *:last-child`,
   leaving a code block's bottom margin stacked on top of the box's padding. */
.prose .c-details-body > *:first-child{margin-top:0}
.prose .c-details-body > *:last-child{margin-bottom:0}

/* A folder's pages as boxes — the :::{cards} directive. The mockup's service
   grid (company.html .svc), simplified for a derived card: title, the child
   page's own summary as teaser, an arrow. The whole card is the link, so it
   sheds the prose underline and carries the hover lift the tile cards
   (.tcard/.bcard) already established. */
.c-cards{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:1.1rem;margin:1.2rem 0 2.2rem;
}
@media (max-width:720px){.c-cards{grid-template-columns:1fr}}
/* Not qualified by `a` any more: a directive card IS one link and stays an
   anchor, while an area landing's section card holds several and is an <article>
   (templates/area.html). Same box either way. */
.c-cards .c-card{
  display:flex;flex-direction:column;gap:.45rem;
  background:#fff;border:1px solid var(--line);border-radius:var(--radius-card);
  padding:1.35rem 1.5rem;text-decoration:none;color:var(--body);
  transition:box-shadow .15s,transform .15s;
}
/* The lift only where the whole card is the link. On a card whose click targets
   are the rows inside it, a card-wide hover response promises a card-wide click
   that is not there. */
.c-cards a.c-card:hover{box-shadow:var(--shadow-card);transform:translateY(-2px)}
.c-card-no{font-size:.85rem;font-weight:750;color:var(--blue);font-variant-numeric:tabular-nums}
.c-card-title{font-weight:700;color:var(--ink);font-size:1.18rem;line-height:1.3}
.c-cards .c-card p{margin:0;font-size:.9rem;color:var(--muted);line-height:1.55;flex:1;max-width:52ch}
.c-card-more{
  display:inline-flex;align-items:center;gap:.45rem;margin-top:.5rem;
  font-weight:600;font-size:.92rem;color:var(--blue);
}
.c-card-more svg.i{transition:transform .18s ease}
.c-cards a.c-card:hover .c-card-more svg.i,
.c-card-more:hover svg.i{transform:translateX(3px)}
.c-cards-note{color:var(--muted);font-size:.9rem}
/* A subsection with no page and no declared teaser: say so, quietly. */
.c-cards .c-card p.c-card-soon{font-style:italic}
/* The lead card, where the page's cards block asks for one (`featured:
   first`): the landing tiles' deep-blue treatment (.b-company, .b-media),
   spanning the row. The one loud thing in the grid — everything else stays
   white so it can be. */
.c-cards a.c-card--featured{
  grid-column:1/-1;background:var(--deep);border:none;color:#fff;
  padding:1.7rem 1.8rem;
}
.c-card--featured .c-card-no{color:#8FD8FA}
.c-card--featured .c-card-title{color:#fff;font-size:1.35rem}
.c-cards .c-card--featured p{color:rgba(255,255,255,.85);max-width:64ch}
.c-card--featured .c-card-more{color:#8FD8FA}

/* The section card on a generated area landing: a title, the section's own
   sentence, and then its pages. The rows are what a tab strip cannot show — the
   reader came for Agentic Engineering, not for the news that Services exists.

   Its own list rules rather than .linklist, which is written for a dark card and
   is reset to white-card values by .b-dir alone (see the landing bento). Reusing
   it here would mean either wearing .b-dir on a card that is not a door, or
   repeating all six overrides. This is the third white surface in the app that
   carries a list of links, and the first that is not a door. */
.c-card--section .c-card-title{font-size:1.15rem}
/* The sentence stops growing so the rows can: the arrow sits at the foot of
   every card in the row, and the row is as tall as the tallest. */
.c-cards .c-card--section p{flex:none}
.c-card-links{display:flex;flex-direction:column;flex:1;margin-top:.35rem}
.c-card-links a{
  display:flex;align-items:center;gap:.5rem;padding:.46rem .1rem;
  font-size:.92rem;font-weight:550;color:var(--body);
  border-bottom:1px solid var(--line);
  transition:color .15s,padding-left .15s;
}
.c-card-links a:last-child{border-bottom:none}
.c-card-links a:hover{color:var(--blue);padding-left:.35rem}
/* "3 more" is a count, not a page — muted and lighter, so it reads as the end of
   the list rather than as one more thing in it. */
.c-card-links a.c-card-rest{color:var(--muted);font-weight:500;font-size:.86rem}
.c-card-links a.c-card-rest:hover{color:var(--blue)}
.c-card--section .c-card-more{margin-top:auto;padding-top:.7rem;font-size:.88rem}
/* A card with no rows takes its own height instead of the row's — but only in a
   grid where some other card HAS rows.

   The grid stretches by default and everything above is built on that: the
   sentence is pinned (`flex:none`) so the LIST grows, and the arrow is pushed to
   the floor with `margin-top:auto`, which is what puts Platforms' "Read more"
   and Tools' on one line. A card with nothing to list has no growing element
   to spend that height on, so it inherits a floor set by its neighbour's rows
   and opens a hole between its sentence and its arrow — Presentation beside AI
   is four rows of it.

   THE `:has()` IS THE WHOLE RULE, not a flourish. Unscoped, this also unpins
   Company, where NO subsection lists pages and the cards are five plain blurbs:
   there the stretch costs nothing and squares off a row whose two summaries run
   to two lines and four. Blank space is only blank when something beside it is
   full. So the question the selector asks is what kind of grid this is — one
   with rows, or one of blurbs — and it leaves the second alone.

   align-self and not `align-items:start` on the grid, for the same reason one
   step down: the cards that do have rows go on sharing a bottom edge, and only
   the one with less to say is short. The tops align either way, and that is the
   alignment a reader follows. A short card reads as "there is less here"; a tall
   empty one reads as a card that failed to load. No-op in the one-column layout,
   where every card is its own row anyway. */
.c-cards:has(.c-card-links) .c-card--brief{align-self:start}

/* Widgets mount here. A widget holds real content (the skills list, the Orca
   downloads, the contract clauses), so an unhydrated one says so rather than
   leaving the reader an invisible gap. */
.c-widget{margin:0 0 1.3rem}
.c-widget-pending{
  padding:.7rem 1rem;border:1px dashed var(--line);border-radius:12px;
  background:var(--surface);color:var(--muted);font-size:.88rem;max-width:46rem;
}
.c-widget-pending code{font-size:.85em}
/* Shown while a widget hydrates (server-rendered placeholder, and the JS
   "Loading…" states) — a spinner, not a wall of "not available yet" text.
   Reuses the @keyframes rot the AI-answer activity pill spins on. */
.c-widget-loading{
  display:flex;align-items:center;justify-content:center;gap:.6rem;
  padding:.7rem 1rem;border:1px dashed var(--line);border-radius:12px;
  background:var(--surface);color:var(--muted);font-size:.88rem;max-width:46rem;
}
.c-widget-spinner{
  width:18px;height:18px;flex:none;border-radius:50%;
  border:2px solid var(--sky);border-top-color:var(--cyan);
  animation:rot .7s linear infinite;
}

/* Mermaid replaces the <pre> with an <svg>; until it does, don't flash the
   diagram source at the reader. */
.prose pre.mermaid{
  visibility:hidden;margin:0 0 1.4rem;text-align:center;
  background:none;border:0;padding:0;
}
.prose pre.mermaid[data-processed]{visibility:visible}

/* ── clause generator ─────────────────────────────────────────────────────
   A conditional-logic form over contract terms. The questions appear as they
   become relevant (the access mode only exists under Time & Material), and the
   assembled clauses update as you answer. All of that is decided server-side. */
.c-clausegen{
  margin:0 0 1.6rem;border:1px solid var(--line);border-radius:16px;
  background:var(--surface);overflow:hidden;
}
.cg-head{padding:.9rem 1.15rem;border-bottom:1px solid var(--line);background:#fff}
.cg-head h3{font-size:1rem;font-weight:650;color:var(--ink);margin:0}

/* The body owns the height: the questions pin at the top and only the clause
   text scrolls. */
.cg-body{
  flex:1 1 auto;min-height:0;display:flex;flex-direction:column;
  padding:1.15rem;background:#fff;
}

/* The questions are a toolbar, not a form: one wrapping row of segmented pill
   groups (as on the AI wiki), so the clauses below get the space. Nothing
   here scrolls — flex:none — or half the switches disappear. Option
   descriptions surface as native tooltips on hover. */
.cg-form{
  flex:none;display:flex;flex-wrap:wrap;gap:.55rem .9rem;
  align-items:center;margin-bottom:1.15rem;
}
.cg-group{display:inline-flex}
.cg-toggle{
  font-size:.84rem;font-weight:550;color:var(--body);background:#fff;
  border:1px solid var(--line);padding:.4rem .95rem;cursor:pointer;
  position:relative;transition:background .12s,border-color .12s,color .12s;
  white-space:nowrap;
}
.cg-group .cg-toggle + .cg-toggle{margin-left:-1px}
.cg-group .cg-toggle:first-child{border-radius:999px 0 0 999px}
.cg-group .cg-toggle:last-child{border-radius:0 999px 999px 0}
.cg-group .cg-toggle:only-child{border-radius:999px}
.cg-toggle:hover{background:var(--surface);border-color:#c7cdd9;z-index:1}
.cg-toggle.is-active{
  background:var(--blue);border-color:var(--blue);color:#fff;z-index:1;
}
.cg-toggle.is-active:hover{background:var(--blue-dark);border-color:var(--blue-dark)}

/* The wrap is the positioning context for the overlaid actions; the box inside
   it is what scrolls, so the buttons stay in the corner while the text moves. */
.cg-output-wrap{position:relative;flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.cg-output{
  flex:1 1 auto;min-height:0;max-height:32rem;overflow-y:auto;
  padding:1rem 1.2rem;font-size:.92rem;line-height:1.55;
  background:var(--surface);border:1px solid var(--line);border-radius:12px;
}
.cg-output > *:first-child{margin-top:0}
.cg-output > *:last-child{margin-bottom:0}
/* A legal document's rhythm, not an article's: every heading level renders at
   body size and leans on weight alone, so the 1.1.1.1. numbering stays readable
   without the paragraph numbers shouting over the text they introduce. */
.cg-output h1{font-size:1.25em}
.cg-output h2,.cg-output h3,.cg-output h4,.cg-output h5,.cg-output h6{font-size:1em}
.cg-output h1,.cg-output h2,.cg-output h3,.cg-output h4,.cg-output h5,.cg-output h6{
  font-weight:700;margin:1.1em 0 .5em;
}
.cg-output p{margin:.75em 0}
.cg-output.is-empty::after{
  content:"Answer the questions above to assemble the clauses.";
  color:var(--muted);font-size:.88rem;
}

/* Overlaid on the clause text's top-right corner, revealed on hover — the same
   affordance as a code block's copy button. Right offset clears the scrollbar. */
.cg-icon{
  position:absolute;top:.75rem;
  width:2rem;height:2rem;display:inline-flex;align-items:center;justify-content:center;
  color:var(--muted);background:#fff;border:1px solid var(--line);border-radius:8px;
  cursor:pointer;opacity:0;
  transition:background .12s,color .12s,border-color .12s,opacity .15s;
}
.cg-icon svg{width:.95rem;height:.95rem}
.cg-copy{right:1.5rem}
.cg-outline{right:4rem}
.cg-output-wrap:hover .cg-icon,.cg-icon:focus-visible,.cg-icon.is-active{opacity:1}
.cg-icon:hover{background:var(--surface);color:var(--ink);border-color:#c7cdd9}
.cg-icon.is-active{color:var(--blue);border-color:#a9c6e4;background:var(--tint-soft)}
.cg-icon.is-copied{color:#166534;border-color:#86efac;background:#f0fdf4;opacity:1}
.cg-icon.is-failed{color:#991b1b;border-color:#fecaca;background:#fef2f2;opacity:1}
/* Nothing to hover on a touch screen — the actions have to be there already. */
@media (hover:none){.cg-icon{opacity:1}}

/* Outline mode: headings only. The spacing that carries the hierarchy comes
   from --h-gap, which the widget measures per heading (see widgets.js). */
.cg-output.is-outline > *:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){display:none}
.cg-output.is-outline h1,.cg-output.is-outline h2,
.cg-output.is-outline h3,.cg-output.is-outline h4,
.cg-output.is-outline h5,.cg-output.is-outline h6{
  margin-top:calc(var(--h-gap,1) * .25rem);margin-bottom:0;
}
.cg-output.is-outline h1,.cg-output.is-outline h2{
  margin-top:calc(var(--h-gap,1) * .25rem + .5rem);
}
.cg-output.is-outline h1[data-first],.cg-output.is-outline h2[data-first]{margin-top:0}

/* Lightbox presentation: the page shows only a button; the generator opens on
   top of it. The clauses are a tool you pull up, not part of the reading flow —
   and the page decides this via the definition's `presentation: lightbox`. */
.cg-dialog{
  border:none;padding:0;background:transparent;
  width:min(68rem,calc(100vw - 4rem));
  max-height:calc(100vh - 4rem);
  /* A native <dialog> defaults to the top-left corner with auto margins that a
     margin:0 reset elsewhere kills; centre it explicitly. */
  margin:auto;
}
.cg-dialog::backdrop{background:rgba(5,17,55,.55);backdrop-filter:blur(3px)}
.cg-dialog .c-clausegen{
  margin:0;background:#fff;box-shadow:var(--shadow-pop);
  display:flex;flex-direction:column;max-height:calc(100vh - 4rem);
}
.cg-dialog .cg-body{min-height:0}
.cg-dialog .cg-output{max-height:none;min-height:8rem}
.cg-head{display:flex;align-items:center;justify-content:space-between}
.cg-close{
  flex:none;width:2rem;height:2rem;border-radius:8px;font-size:1.3rem;line-height:1;
  color:var(--muted);display:grid;place-items:center;
}
.cg-close:hover{background:var(--tint-soft);color:var(--ink)}
/* An editor's way from the clauses to their definition. Pushed to the right of
   the title (before the close, in the lightbox); quiet, since it is chrome for
   the few who may edit, not part of the reader's flow. */
.cg-edit{
  flex:none;margin-left:auto;font-size:.8rem;font-weight:550;
  color:var(--tint-strong,#0a6cff);text-decoration:none;white-space:nowrap;
}
.cg-edit:hover{text-decoration:underline}

/* ── salary calculator ────────────────────────────────────────────────────
   The prototype behind the `salary-calculator` feature flag
   (docs/salary-calculator.md). It borrows the clause generator's card above —
   white body, one-pixel line, 16px radius, segmented pills for the one choice
   that is a choice — because it is the same kind of object: a form whose answer
   is the point, on a page of prose. What it adds is a figure block, since the
   answer here is three numbers rather than a page of text. */
.c-salary{
  margin:0 0 1.6rem;border:1px solid var(--line);border-radius:16px;
  background:#fff;overflow:hidden;
}
.sc-head{
  display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;
  padding:.9rem 1.15rem;border-bottom:1px solid var(--line);
}
.sc-head h3{font-size:1rem;font-weight:650;color:var(--ink);margin:0}
/* Amber, and never subtle: while the bands are placeholders, every screenshot
   of this widget has to carry the fact with it. */
.sc-badge{
  font-size:.7rem;font-weight:700;border-radius:999px;padding:.2rem .6rem;
  background:var(--amber-bg);color:var(--amber-tx);
}
.sc-note{margin:0;padding:.7rem 1.15rem;font-size:.82rem;color:var(--muted);
  background:var(--surface);border-bottom:1px solid var(--line)}
.sc-body{padding:1.15rem}
.sc-field + .sc-field{margin-top:1.1rem}
.sc-label{
  display:flex;align-items:baseline;justify-content:space-between;gap:.6rem;
  font-size:.75rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  color:var(--muted);margin-bottom:.5rem;
}
.sc-years-value{color:var(--ink);font-size:.95rem;text-transform:none;letter-spacing:0}

.sc-group{display:flex;flex-wrap:wrap;gap:.4rem}
.sc-pill{
  font-size:.84rem;font-weight:550;color:var(--body);background:#fff;
  border:1px solid var(--line);border-radius:999px;padding:.4rem .95rem;
  cursor:pointer;transition:background .12s,border-color .12s,color .12s;
}
.sc-pill:hover{background:var(--surface);border-color:#c7cdd9}
.sc-pill.is-active{background:var(--blue);border-color:var(--blue);color:#fff}
.sc-pill.is-active:hover{background:var(--blue-dark);border-color:var(--blue-dark)}

.sc-slider{width:100%;accent-color:var(--blue)}

.sc-components{display:flex;flex-direction:column;gap:.45rem}
.sc-check{display:flex;align-items:flex-start;gap:.55rem;cursor:pointer;font-size:.87rem}
.sc-check input{margin-top:.2rem;accent-color:var(--blue)}
.sc-check-label{display:block;color:var(--body)}
.sc-check-hint{display:block;font-size:.78rem;color:var(--muted)}

/* The answer. Auto-fit rather than a fixed column count: two figures without
   components, three with them, and one per row on a phone. */
.sc-out{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));
  gap:.7rem;margin-top:1.3rem;
}
.sc-figure{
  border:1px solid var(--line);border-radius:14px;padding:.8rem .95rem;
  background:var(--surface);
}
.sc-figure.is-strong{background:var(--tint-soft);border-color:#d3ddf0}
.sc-figure-label{font-size:.72rem;font-weight:700;letter-spacing:.03em;
  text-transform:uppercase;color:var(--muted)}
.sc-figure-value{font-size:1.45rem;font-weight:700;color:var(--ink);
  margin-top:.2rem;font-variant-numeric:tabular-nums}
.sc-figure.is-strong .sc-figure-value{color:var(--deep)}
.sc-profile-note{grid-column:1/-1;margin:0;font-size:.82rem;color:var(--muted)}

.sc-progression{margin-top:1.4rem}
.sc-rows{display:flex;flex-direction:column;border:1px solid var(--line);
  border-radius:12px;overflow:hidden}
.sc-row{display:flex;align-items:baseline;justify-content:space-between;
  gap:.8rem;padding:.42rem .8rem;font-size:.85rem;color:var(--body)}
.sc-row + .sc-row{border-top:1px solid var(--line)}
.sc-row-value{font-variant-numeric:tabular-nums}
.sc-row.is-now{background:var(--tint-soft);color:var(--ink);font-weight:650}

/* ── GitLab-backed widgets: shared chrome ─────────────────────────────────
   The skills list and the release button ride on the clause generator's
   dialog classes; what follows is only what they add.

   These widgets mount inside .prose, and `.prose a` (0,1,1) outranks `.btn`
   (0,1,0) — without the qualified rules below, the download button renders
   its label blue-on-blue: invisible. Same specificity trap as the details
   box (README, "CSS"), third form. */
.prose a.btn{color:#fff;text-decoration:none}
.prose a.btn.subtle{color:var(--ink)}
.prose a.br-item{color:var(--body);text-decoration:none}
.prose a.br-item:hover{color:var(--ink)}
.prose a.sk-link{text-decoration:none}
.prose a.sk-link:hover{text-decoration:underline}
/* Same trap, worst case: the org chart is ~94 member chips, and `.prose a`
   underlines every name in it — the chart reads as a wall of links rather than
   an organisation. The chip already says "clickable" as a whole, on hover, with
   its border and its name colour; the underline only adds noise. */
.prose a.oc-member{text-decoration:none}
.wg-pill{
  display:inline-block;font-size:.72rem;font-weight:600;color:var(--muted);
  background:var(--tint-soft);border-radius:999px;padding:.14rem .55rem;
  vertical-align:.08em;white-space:nowrap;
}
/* The cloud-model notice (static/js/ai_consent.js). The clause generator's
   chrome again — .cg-dialog for the backdrop and the centring, .c-clausegen for
   the card — at the width of the thing it actually is: three short paragraphs
   and one button. 68rem of lightbox around them would read as a document to
   study rather than a sentence to take in. */
.aic-dialog{width:min(31rem,calc(100vw - 2.5rem))}
.aic-box{padding:1.25rem 1.4rem 1.15rem}
.aic-body p{margin:0 0 .7rem;font-size:.92rem;line-height:1.55;color:var(--ink)}
.aic-more{color:var(--muted)}
.aic-more a{color:var(--blue);text-decoration:underline}
.aic-actions{display:flex;align-items:center;justify-content:flex-end;gap:.75rem;margin-top:1rem}
/* Sits left of the button it is about, in the row the click happened in — a
   failure to store the agreement is about the button, not about the notice. */
.aic-err{margin:0;font-size:.82rem;color:var(--danger,#B42318)}

.wg-dialog{width:min(52rem,calc(100vw - 4rem))}
.wg-dialog .cg-head{gap:.75rem}
.wg-dialog .cg-head h3{flex:1 1 auto;min-width:0}
.wg-head-actions{display:flex;gap:.5rem;flex:none}
.wg-head-actions .btn svg{width:.9rem;height:.9rem}
/* Only the markdown scrolls; the header (and its actions) stay put. */
.wg-dialog-body{overflow-y:auto}
.sk-detail{font-size:.92rem}
.sk-detail > *:first-child{margin-top:0}

/* ── skills list ──────────────────────────────────────────────────────────
   A reading list, not a card grid: name, freshness, one line of what it is. */
.c-skills{max-width:46rem}
.c-skills ul{list-style:disc;padding-left:1.35rem;margin:0}
.c-skills li{margin:0 0 .8rem}
.c-skills li:last-child{margin-bottom:0}
.sk-link{font-weight:650;color:var(--blue);text-decoration:none}
.sk-link:hover{text-decoration:underline}
.sk-desc{color:var(--muted);font-size:.88rem;margin-top:.1rem}

/* ── binary release ───────────────────────────────────────────────────────
   One button carrying the version; the platforms open as a menu anchored to
   it, so the released-date pill below never pushes the menu around. */
.br-anchor{position:relative;display:inline-block}
.br-caret svg{width:.85rem;height:.85rem;display:block}
.br-btn[aria-expanded="true"] .br-caret svg{transform:rotate(180deg)}
/* `.prose ul` (0,1,1) would put disc bullets and an indent back on the menu;
   qualify past it. */
.br-menu,.prose ul.br-menu{
  position:absolute;top:calc(100% + .4rem);left:0;z-index:30;min-width:100%;
  margin:0;padding:.35rem;list-style:none;
  background:#fff;border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--shadow-pop);
}
.br-item{
  display:flex;align-items:center;gap:.55rem;white-space:nowrap;
  padding:.5rem .75rem;border-radius:8px;font-size:.88rem;font-weight:550;
  color:var(--body);text-decoration:none;
}
.br-item:hover{background:var(--surface);color:var(--ink)}
.br-item svg{width:1rem;height:1rem;flex:none;color:var(--muted)}
.br-meta{margin-top:.6rem}

/* A download the editor preview cannot wire up yet — it is page-anchored and
   works once saved. Shown, but plainly not clickable, with a tooltip that says so. */
.br-item.is-disabled,.btn.is-disabled{opacity:.5;cursor:not-allowed}
.br-item.is-disabled:hover{background:none;color:var(--body)}

/* ── org chart ─────────────────────────────────────────────────────────────
   The organisation as NESTED BOXES: an area's children are drawn inside its
   border, so containment — which is what the data says — is what the eye
   sees. The root is a quiet strip, its children full-width bands, everything
   deeper a bordered box flow-wrapped beside its siblings. No canvas, no
   pan/zoom: space is proportional to content and the page scrolls like a
   page. The same styles dress the read-only chart and the editor's. */
.c-orgchart{margin:0 0 1.6rem}
.oc-widget-head{display:flex;align-items:baseline;gap:.9rem;margin:0 0 .5rem}
.oc-widget-title{font-size:1.05rem;font-weight:650;color:var(--ink);margin:0}
.oc-edit,.prose a.oc-edit{
  font-size:.82rem;font-weight:600;color:var(--blue);text-decoration:none;
}
.oc-edit:hover{text-decoration:underline}

.oc-toolbar{display:flex;align-items:center;gap:.5rem;margin:0 0 .6rem}

/* One hue per band, inherited all the way down — the whole chart wears three
   colours, one per operational-model layer, never a per-box rainbow. Each
   box's fill is the SAME hue at a depth-driven strength (see ocTint in
   widgets.js), so two boxes at one level always match and folding never
   recolours anything. --oc-solid/--oc-solid-tx paint the band's solid header
   like the slide; --oc-wash is the base the depth tints are mixed from — a
   mid tone (navy lifts to a readable indigo) so faint fills still read as the
   hue, not grey. Solids are picked for contrast: navy and teal carry white
   text, gold flips to navy. Magenta stays reserved for the AI. */
.oc-accent-navy{--oc:var(--navy);--oc-wash:#2A3F7A;--oc-solid:var(--navy);--oc-solid-tx:#fff}
.oc-accent-gold{--oc:#E6A300;--oc-wash:#E6A300;--oc-solid:#E6A300;--oc-solid-tx:var(--navy)}
.oc-accent-teal{--oc:#0B7D8F;--oc-wash:#0B7D8F;--oc-solid:#0B7D8F;--oc-solid-tx:#fff}

.oc-node{min-width:0;max-width:100%}

/* Placement is COMPUTED in widgets.js (see the packing section there); the
   classes below only give its output shape. A .oc-kids is a vertical stack
   of segments; a segment is either a weighted .oc-row of boxes or a set of
   balanced .oc-cols column stacks. Widths come from inline styles the
   packer writes. */
.oc-kids{display:flex;flex-direction:column;gap:.55rem;min-width:0}
.oc-node.oc-depth-0 > .oc-kids{gap:.7rem}
.oc-row{display:flex;flex-wrap:wrap;gap:.55rem;align-items:flex-start;min-width:0}
.oc-cols{display:flex;gap:.55rem;align-items:flex-start;min-width:0}
.oc-col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:.55rem}

/* Depth 1 — a band: full width, its header a SOLID strip in the layer's own
   colour, exactly as the operational-model slide draws its three layers.
   The interior takes a whisper of the same hue, so the white cards inside
   sit visibly ON the layer rather than floating in blank space. */
.oc-depth-1{
  border:1px solid color-mix(in srgb, var(--oc-wash) 22%, var(--line));
  border-radius:14px;overflow:hidden;
  background:color-mix(in srgb, var(--oc-wash) 5%, #fff);
}
.oc-depth-1 > .oc-node-head{
  display:flex;align-items:center;gap:.45rem;
  padding:.55rem .85rem;background:var(--oc-solid);
}
.oc-depth-1 > .oc-node-head .oc-title{
  font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;
  color:var(--oc-solid-tx);
}
.oc-depth-1 > .oc-node-head .oc-badge{color:var(--oc-solid-tx);opacity:.8}
.oc-depth-1 > .oc-node-head .oc-toggle{color:var(--oc-solid-tx);opacity:.75}
.oc-depth-1 > .oc-node-head .oc-toggle:hover{
  color:var(--oc-solid-tx);opacity:1;background:rgba(255,255,255,.18);
}
.oc-depth-1 > .oc-node-head .oc-editable:hover{background:rgba(255,255,255,.16)}
.oc-depth-1 > .oc-kids,.oc-depth-1 > .oc-roles{padding:.75rem .85rem .85rem}
.oc-depth-1 > .oc-roles{padding-bottom:0}
.oc-depth-1 > .oc-roles + .oc-kids{padding-top:.6rem}

/* Depth 2+ — a box among its siblings; the packer decides its width. Every
   box wears one coat: a flat fill in the band's hue, its strength set by
   nesting depth (widgets.js writes background + border-color inline). A box
   that holds people and one that holds sub-areas look identical at the same
   level; sub-areas simply nest as lighter boxes inside, so containment reads
   as concentric shading, not as two different kinds of card. */
.oc-depth-2{
  border:1px solid var(--line);
  border-radius:10px;padding:.5rem .65rem .55rem;
}
.oc-depth-2 > .oc-kids{margin-top:.55rem}
/* Topics carry NO visual marker: in the Value Chain nearly every box is
   one, and a marker that is everywhere reads as broken typography — the
   same lesson as the deleted "Topic" chip. The distinction lives in the
   data and in the editor's tool row (a Topic offers "+ Topic", never
   "+ Bereich"). The dashed border keeps its universal meaning: nothing in
   this box yet. */
.oc-node.is-bare{border-style:dashed}

/* Every box header: the chevron pinned top-left, then a column holding the
   title and (when folded) the count line — stacked, so neither steals the
   other's width and a narrow box never crushes its title. */
.oc-node-head{display:flex;align-items:flex-start;gap:.35rem;min-width:0;flex-wrap:wrap}
/* flex-basis 0 (not auto): in the wrapping header the title column must never
   be the thing that wraps — it stays beside the chevron and grows to fill the
   line, so a long title breaks inside itself rather than dropping whole below
   the chevron. (The tool row, basis 100%, is what wraps to its own line.) */
.oc-head-main{flex:1 1 0;min-width:0}
.oc-title{
  display:block;font-size:.88rem;font-weight:650;color:var(--ink);
  line-height:1.3;min-width:0;hyphens:auto;overflow-wrap:break-word;
}
.oc-badge{display:block;font-size:.72rem;font-weight:550;color:var(--muted);margin-top:.1rem}

/* The fold control. In the read-only chart the whole header is the target;
   the chevron is the sign that there is something to fold. */
.oc-toggle{
  flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:20px;height:20px;padding:0;border:0;border-radius:6px;
  background:none;color:var(--muted);cursor:pointer;
}
.oc-toggle svg{width:14px;height:14px;transition:transform .2s}
.oc-toggle.is-collapsed svg{transform:rotate(-90deg)}
.oc-toggle:hover{color:var(--ink);background:rgba(5,17,55,.07)}
.c-orgchart:not(.is-editing) .oc-node.has-kids > .oc-node-head{cursor:pointer}

.oc-roles{display:flex;flex-direction:column;gap:.4rem;margin-top:.4rem}
.oc-depth-1 > .oc-roles{margin-top:0}
.oc-role{position:relative}
/* Muted, deliberately: the two loudest things in a box are its title and its
   people. A role with nobody in it yet recedes further rather than reading
   as a shouted vacancy. */
.oc-role-name{
  font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;
  color:var(--muted);display:block;margin-bottom:.22rem;
  hyphens:auto;overflow-wrap:break-word;
}
/* Recede an empty role by fading its NAME, not the whole row — the add-member
   "+" beneath it stays as solid as it is in a role that already has people. */
.oc-role.is-empty > .oc-role-name{opacity:.6}

/* Where a deep link landed (`#node=…`, see ocReveal in widgets.js) — a reader
   who clicked one role on a profile, dropped into a chart of a few hundred
   nodes. An outline, not a fill: every box in here already uses colour to say
   which band it belongs to, and a highlight that repaints one would be making a
   claim about the organisation instead of about the reader's last click. It
   stays put rather than fading — someone who scrolls away and back is still
   looking for the same thing. */
.oc-role.is-revealed,.oc-member.is-revealed,.oc-node.is-revealed{
  outline:2px solid var(--blue);outline-offset:3px;border-radius:4px;
  animation:oc-reveal .9s ease-out;
}
@keyframes oc-reveal{
  from{outline-color:transparent;outline-offset:9px}
  60%{outline-color:var(--blue)}
}
@media (prefers-reduced-motion:reduce){
  .oc-role.is-revealed,.oc-member.is-revealed,.oc-node.is-revealed{animation:none}
}
.oc-members{display:flex;flex-wrap:wrap;gap:.25rem .3rem;align-items:center}
.oc-member{
  display:inline-flex;align-items:center;gap:.3rem;max-width:100%;
  padding:.1rem .4rem .1rem .1rem;border-radius:999px;
  background:var(--surface);border:1px solid var(--line);
}
.oc-member.is-unlinked{border-style:dashed}
/* Reading the chart, a member the directory knows is a link to their profile
   (buildMember renders those as <a>, and only outside edit mode). */
a.oc-member{transition:background .15s,border-color .15s}
a.oc-member:hover{background:#fff;border-color:var(--blue)}
a.oc-member:hover .oc-member-name{color:var(--blue)}
.oc-member-name{font-size:.72rem;color:var(--body);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.oc-avatar{
  position:relative;width:20px;height:20px;flex:none;border-radius:50%;
  overflow:hidden;background:var(--tint);display:inline-flex;
  align-items:center;justify-content:center;
}
.oc-avatar img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.oc-initials{font-size:.58rem;font-weight:700;color:var(--muted);line-height:1}

/* ── org-chart editor extras ────────────────────────────────────────────────
   Edit mode adds selection, inline rename and per-box / per-role / per-member
   controls. All hidden in the read-only chart. */
.oc-node.is-selected{box-shadow:0 0 0 2px rgba(35,35,255,.22)}
.oc-editable{cursor:text;border-radius:4px;padding:0 .1rem;margin:0 -.1rem}
.oc-editable:hover{background:var(--sky-soft)}
.oc-edit-input{
  font:inherit;color:var(--ink);border:1px solid var(--blue);border-radius:4px;
  padding:0 .15rem;box-sizing:border-box;
  /* Fill the label's slot rather than the input's ~20ch intrinsic width, so a
     long name never spills past the card onto its neighbour. It replaces a
     block .oc-title / .oc-role-name inside a plain block, so width:100% (not
     flex, which would be inert here) is what bounds it. */
  display:block;width:100%;min-width:0;
}
.oc-add{
  font-size:.68rem;font-weight:600;color:var(--blue);background:none;border:0;
  cursor:pointer;padding:.1rem .2rem;border-radius:4px;align-self:flex-start;
}
.oc-add:hover{background:var(--sky-soft)}
.oc-add-member{border:1px dashed var(--line);color:var(--muted);line-height:1;
  width:20px;height:20px;border-radius:50%;padding:0;align-self:center}
.oc-add-member:hover{color:var(--blue);border-color:var(--blue)}

/* The container's add-a-child control: a resting "+" that gives way to the
   kinds it may hold on hover / keyboard focus (and .is-open, set by a tap on
   touch). The whole wrap owns the reveal so moving from the "+" onto a kind
   button never collapses it mid-reach. */
.oc-addwrap{display:inline-flex;align-items:center;gap:.3rem;align-self:flex-start;min-height:22px}
/* Nudged right so the first kind button's "+" lands under the trigger circle's
   centred "+", instead of jumping left as the row expands. */
.oc-add-kinds{display:none;gap:.3rem;align-items:center;flex-wrap:wrap;padding-left:.2rem}
/* Center the glyph the same way the trigger does (flex, line-height:1), so the
   "+" sits at the same height rather than a hair lower on its text baseline. */
.oc-add-kind{display:inline-flex;align-items:center;line-height:1}
.oc-addwrap:hover .oc-add-kinds,
.oc-addwrap:focus-within .oc-add-kinds,
.oc-addwrap.is-open .oc-add-kinds{display:inline-flex}
/* A mouse hands the "+" its slot over to the kinds it reveals; keyboard keeps it
   in place (hiding a focused control would drop focus), so it leads them. */
.oc-addwrap:hover .oc-add-trigger,
.oc-addwrap.is-open .oc-add-trigger{display:none}
/* The same dashed circle as the "Mitarbeiter hinzufügen" +: a subtle gray at
   rest, going blue on hover. The wrap's min-height reserves this circle's
   height, so revealing the shorter kind buttons on hover doesn't shift the row. */
.oc-add-trigger{
  width:20px;height:20px;padding:0;border:1px dashed var(--line);border-radius:50%;
  color:var(--muted);
  display:inline-flex;align-items:center;justify-content:center;line-height:1;
}
.oc-add-trigger:hover{color:var(--blue);border-color:var(--blue)}
.oc-del{
  border:0;background:none;color:var(--muted);cursor:pointer;font-size:.9rem;
  line-height:1;padding:0 .15rem;opacity:0;transition:opacity .12s;
}
.oc-role:hover .oc-del,.oc-member:hover .oc-del{opacity:.7}
.oc-del:hover{color:var(--red-tx);opacity:1}
.oc-del-member{margin-left:.05rem;font-size:.78rem}
.oc-role > .oc-del{position:absolute;top:0;right:0}

/* The per-box tool row, revealed in the selected box's header. It takes a full
   line of its own below the title (flex-basis:100% in the wrapping header), so
   it never competes with the title for width and cramp it on a narrow card. */
.oc-tools{display:none;gap:.25rem;flex-wrap:wrap;flex-basis:100%}
.oc-node.is-selected > .oc-node-head > .oc-tools{display:flex}
.oc-tool{
  font-size:.68rem;font-weight:600;color:var(--body);background:#fff;
  border:1px solid var(--line);border-radius:6px;padding:.12rem .4rem;cursor:pointer;
  box-shadow:var(--shadow-card);white-space:nowrap;
}
.oc-tool:hover{border-color:var(--blue);color:var(--blue)}
.oc-tool.del-node:hover{border-color:var(--red-tx);color:var(--red-tx)}

/* The directory autocomplete that adds/links a member. */
.oc-picker{
  position:absolute;z-index:40;min-width:15rem;max-height:16rem;overflow:auto;
  background:#fff;border:1px solid var(--line);border-radius:10px;
  box-shadow:var(--shadow-pop);padding:.3rem;
}
.oc-picker input{
  width:100%;box-sizing:border-box;font:inherit;padding:.35rem .5rem;
  border:1px solid var(--line);border-radius:7px;margin-bottom:.3rem;
}
.oc-picker ul{list-style:none;margin:0;padding:0}
.oc-picker li{
  display:flex;align-items:center;gap:.5rem;padding:.35rem .45rem;border-radius:7px;
  cursor:pointer;font-size:.85rem;color:var(--body);
}
.oc-picker li:hover,.oc-picker li.is-active{background:var(--sky-soft);color:var(--ink)}
.oc-picker li .oc-avatar{width:22px;height:22px}
.oc-picker-free{color:var(--muted);font-style:italic}

/* The org-chart editor page: a title field, then the full-width editable chart
   with the outline source folding out below it. */
.edit-orgchart .oc-title-field{
  font-size:1.35rem;font-weight:700;color:var(--ink);border:1px solid transparent;
  border-radius:8px;padding:.1rem .35rem;margin:-.1rem -.35rem;width:100%;
  background:transparent;
}
.edit-orgchart .oc-title-field:hover{background:var(--surface)}
.edit-orgchart .oc-title-field:focus{border-color:var(--blue);background:#fff;outline:none}
/* The editing body is the page/clause editor's split: it fills the shell below
   the toolbar so the PAGE never scrolls and each pane scrolls inside itself.
   Closed, the chart is the row's only visible child and takes the full width;
   open, the source column sits to its LEFT with the gutter between. */
.edit-org-body{margin-top:.6rem}
.edit-org-chart{flex:1 1 0;min-width:0;min-height:0;overflow:auto}
/* The source column reuses .edit-source-col (a filling flex column); the hint
   pins at its top and the outline textarea grows to the column's bottom. */
.edit-org-source{min-height:0}
.edit-org-source .ew-hint{flex:none;margin:0}
/* The outline sits in a clipping box that carries the frame; the textarea is
   flush and transparent inside it, so a highlight band can be laid UNDER the
   text to mark the selected node's line (an unfocused textarea shows no native
   selection, and focus stays on the chart's inline input). */
.oc-src-box{
  position:relative;flex:1 1 0;min-height:0;min-width:0;display:flex;
  border:1px solid var(--line);border-radius:14px;overflow:hidden;background:#fff;
}
.edit-org-source .ew-src{
  flex:1 1 0;width:100%;min-height:8rem;box-sizing:border-box;
  border:none;border-radius:0;background:transparent;
  position:relative;z-index:1;   /* above the highlight band, so text stays on top */
}
.edit-org-source .ew-src:focus-visible{border:none}
.oc-src-hl{
  position:absolute;pointer-events:none;z-index:0;
  background:rgba(35,35,255,.18);border-radius:4px;
}
/* The gutter and source only exist while "Source" is on; the media query that
   turns the gutter into a visible drag strip outranks a bare [hidden], so say
   it again here with the class for specificity. */
.edit-orgchart .edit-gutter[hidden],
.edit-org-source[hidden]{display:none}
/* An absolute floor on the source width: much below this the outline's lines
   wrap and the YAML becomes unreadable, so the seam cannot be dragged (nor a
   narrow window squeeze it) past that. 430px keeps the deepest line — a member
   at full indentation with a username — on one line, with a little headroom.
   Only while the panes sit side by side — stacked on a narrow window the source
   is full-width and this floor would force an overflow. The compound selector
   outranks .edit-source-col{min-width:0}. */
@media (min-width:901px){
  .edit-orgchart .edit-org-source{min-width:430px}
}

/* ================= content editor ================= */
/* The page head's title row: heading on the left, the Edit button (for those
   whose grants cover this page) on the right. This is the BASE state — on wide
   screens the floating control below replaces it and this one is hidden. */
/* stretch, not flex-start, so the title column can centre its own contents: at
   rest it is taller than the button and centring changes nothing, but once the
   head has collapsed to a single line the button is the taller of the two, and
   a top-aligned title would sit off-centre in the strip beside it. */
.page-head-row{display:flex;align-items:stretch;justify-content:space-between;gap:1.2rem}
.page-head-row > div:first-child{display:flex;flex-direction:column;justify-content:center;min-width:0}
.page-head-row .page-pill--head{margin-top:calc(.6rem * (1 - var(--head-p,0)));flex:none;align-self:flex-start}
/* A plain action button in the head (Events → "New event"): the row stretches
   its children, which would pull a pill button to the full height of the two-line
   title block. Keep it its own size, vertically centred against the heading.
   margin-left:auto packs it right even when the edit/comment pill stands beside
   it (an event page's "Manage event" next to Edit) — space-between alone would
   strand the middle child of a three-child row in the centre. */
.page-head-row > .btn{flex:none;align-self:center;margin-left:auto}

/* Comment + Edit share one pill (docs/comments.md §3): a bordered chip whose
   segments are divided by a hairline. The same pill sits in the head on narrow
   screens and floats in the gutter on wide ones — see the width switch below. */
.page-pill{display:inline-flex;align-items:stretch;background:#fff;
  border:1px solid var(--line);border-radius:999px;overflow:hidden;
  box-shadow:0 2px 10px rgba(5,17,55,.08)}
/* [hidden] must beat the display above: an empty pill (comment-only reader, no
   comments yet) collapses, and the Comment segment stays gone until there are
   comments to reveal. */
.page-pill[hidden],.page-pill-btn[hidden]{display:none}
.page-pill-btn{display:inline-flex;align-items:center;gap:.4rem;
  padding:.5rem .85rem;font-size:.8rem;font-weight:600;line-height:normal;
  color:var(--body);background:none;border:0;cursor:pointer;text-decoration:none;
  transition:color .15s,background .15s}
/* The divider sits between two SHOWN buttons only — a hidden Comment must not
   leave a stray line down the left of a lone Edit. */
.page-pill-btn:not([hidden]) + .page-pill-btn:not([hidden]){border-left:1px solid var(--line)}
.page-pill-btn:hover{color:var(--blue);background:var(--tint-soft)}
.page-pill-btn svg.i{width:1em;height:1em}
/* The Comment segment is icon + count only — no label. It sits at the pill's
   right edge, so the outer (right) side gets the room and the divider side less. */
.page-pill-btn.is-comment{padding-left:.6rem;padding-right:.75rem;gap:.35rem}
/* Pressed = the comment layer is revealed. */
.page-pill-btn.is-comment[aria-pressed="true"]{color:var(--blue);background:var(--sky-soft)}
/* The count: a small blue badge that reads on both the rest and pressed states.
   Centred by line-height = height (not flex): a single digit sits dead-centre in
   the circle where align-items left it looking high. */
.page-comment-count{display:inline-block;box-sizing:border-box;text-align:center;
  min-width:1.15rem;height:1.15rem;padding:0 .32rem;border-radius:999px;
  font-size:.68rem;font-weight:700;line-height:1.15rem;
  font-variant-numeric:tabular-nums;background:var(--blue);color:#fff}

/* The floating Edit control. Sticky below the header, so the way into edit mode
   is reachable from anywhere in the page rather than only from its top.

   It ends on the shell's INNER right edge — the one the avatar, the nav and the
   article all end on. Out in the gutter beyond the shell it was technically free
   (it cost the article no width) but it read as belonging to the window rather
   than to the page, and on a wide monitor it drifted away from the content it
   acts on. The rail is the sticky range: first line of content down to the
   band's end. */
section.band{position:relative}
.page-edit-rail{display:none}
/* 1380px was over-conservative: it left the head's Edit button showing on
   ordinary laptop widths, where there is plenty of room for the float.

   The float needs exactly one thing — room to the RIGHT of the text. The shell
   stops growing at --maxw (76.5rem) and prose stops at --measure (50rem), so
   the slack is `min(vw,maxw) - padding - nav - measure`. Measured, that first
   clears the ~73px control at about 1185px; 1200 is the round number above it,
   and leaves a ~40px gap between the last word and the button.

   Not lower, and not "always": below this the article column is narrower than
   the measure, so the prose fills it and a floating control would sit on top of
   the words. A button in the head is worse than a float — but it is much better
   than a float over the text. */
@media (min-width:1200px){
  .page-head-row .page-pill--head{display:none}
  .page-edit-rail{
    display:block;position:absolute;bottom:0;left:auto;
    /* Above the article's own positioned boxes. A code block is a
       `.c-code-wrap{position:relative}` (it anchors the copy button), and among
       positioned siblings with z-index:auto the LAST one in the DOM paints on
       top — so the article's code blocks were painting over a control that is
       meant to float above the page. Stays under the sticky header (50) and the
       editor's drawer (40): this floats above the article, not above the app. */
    z-index:5;
    /* The first prose block's top: the band's padding plus the article's. */
    top:calc(clamp(2.4rem,4.5vw,3.6rem) + 1.4rem);
    /* The shell's content edge: half the slack outside --maxw, plus .wrap's own
       padding. max() so a viewport narrower than the shell cannot push it off. */
    right:max(0px, calc(50% - var(--maxw) / 2 + clamp(1rem,3vw,2rem)));
  }
  /* The pill's LOOK is the shared .page-pill; the float only adds the sticky
     range — first line of content down to the band's end. */
  .page-pill--float{position:sticky;top:calc(var(--chrome) + .9rem)}
  .page-pill--float:hover{box-shadow:0 4px 14px rgba(5,17,55,.12)}
}

/* The editor escapes the shell (--maxw) on purpose: source and preview each
   want every pixel they can get, and there is no prose measure to protect —
   the preview pane enforces its own. Height is everything below the 64px
   sticky header, so the PAGE never scrolls; each pane scrolls inside itself.
   The stacked layout is the base and the columns are the min-width
   enhancement — never the reverse (a media query adds no specificity). */
.edit-shell{
  height:calc(100dvh - var(--top-h));
  display:flex;flex-direction:column;gap:.7rem;
  padding:.8rem clamp(1rem,2vw,1.6rem) 1rem;
}
/* The shell takes keyboard focus on load (tabindex=-1, set in JS) so the first
   Escape reaches the editor even when the page was reached by URL and the
   browser left focus in its chrome. It is a focus holder, not a control — it
   must never show a focus ring of its own. */
.edit-shell:focus{outline:none}
/* wrap-REVERSE, not wrap: the toolbar formats the source pane, so it must stay
   against it. When the row is too narrow to hold the toolbar and the actions
   together, plain wrapping drops the actions BELOW the toolbar — which pushes
   the toolbar a whole row away from the only thing it acts on. wrap-reverse
   stacks the overflow line above instead, so the toolbar keeps its place on the
   source pane, and the single-line case (where the toolbar is left and the
   actions are pushed right by margin-left:auto) is unchanged. */
.edit-top{display:flex;align-items:center;gap:1rem;flex-wrap:wrap-reverse}
.edit-toolbar{display:flex;align-items:center;gap:.55rem;flex-wrap:wrap}
.tb-group{display:flex;gap:.25rem}
.tb-btn{
  display:inline-flex;align-items:center;justify-content:center;
  height:2.1rem;min-width:2.3rem;padding:0 .55rem;
  font-size:.78rem;font-weight:650;line-height:normal;color:var(--body);
  background:#fff;border:1px solid var(--line);border-radius:9px;
  transition:background .12s,border-color .12s,color .12s;
}
.tb-btn:hover{background:var(--tint-soft);border-color:#c7cdd9;color:var(--ink)}
.tb-btn svg.i{width:1.05rem;height:1.05rem}
.edit-actions{margin-left:auto;display:flex;align-items:center;gap:.8rem}
/* Like .form-err: stays until the problem is fixed, does not time out. */
.edit-err{font-size:.8rem;color:#B91C1C}

.edit-split{flex:1 1 0;min-height:0;display:flex;flex-direction:column;gap:.8rem}
@media (min-width:901px){.edit-split{flex-direction:row}}

/* The drag handle between source and preview. A 1rem hit strip whose negative
   margins cancel the flex gaps around it, so the panes keep their .8rem
   separation and the strip sits ON the gap rather than widening it. Invisible
   until hovered/dragged — the panes' own borders already draw the seam.
   Stacked layouts (below 901px) have no seam to drag, so none of this. */
.edit-gutter{display:none}
@media (min-width:901px){
  .edit-gutter{
    display:block;flex:none;align-self:stretch;width:1rem;margin:0 -.9rem;
    position:relative;cursor:col-resize;touch-action:none;user-select:none;
  }
  .edit-gutter::before{
    content:"";position:absolute;top:0;bottom:0;left:calc(50% - 1px);width:2px;
    border-radius:2px;background:transparent;transition:background .12s;
  }
  .edit-gutter:hover::before,
  .edit-gutter.is-dragging::before,
  .edit-gutter:focus-visible::before{background:var(--cyan)}
  .edit-gutter:focus-visible{outline:none}
}

/* The area nav, same 13rem column as the content page's — but it scrolls
   inside itself instead of sticking: the shell is a fixed-height flex row, so
   there is no page scroll to stick against. Hidden (the base state) until the
   panes can spare the room. */
.edit-side{display:none}
@media (min-width:1200px){
  .edit-side{
    display:block;flex:0 0 13rem;min-height:0;overflow-y:auto;
    font-size:.86rem;padding:.4rem .25rem 1rem 0;
  }
  /* On the content page the tint marks "you are above the first heading"; here
     there is no scrollspy, so it simply marks the page being edited. */
  .edit-side .page-title.active{background:var(--tint-soft)}
  /* The toolbar formats the SOURCE, so it belongs over the source pane, not
     over the nav. Nav column + the split's gap — change together. Scoped to
     shells that actually have the nav: the widget editors reuse .edit-top with
     no .edit-side, and must not reserve room for a column that isn't there. */
  .edit-shell:has(.edit-side) .edit-top{padding-left:calc(13rem + .8rem)}
}
.edit-src,.edit-preview{
  flex:1 1 0;min-width:0;min-height:0;overflow:auto;
  background:#fff;border:1px solid var(--line);border-radius:14px;
  /* Radius and scroll live on the same element here (a textarea can't hand its
     scrollbar to a clipping parent), so the native scrollbar would draw a square
     gutter through the rounded corner. A thin bar with a transparent track and an
     inset thumb keeps the thumb clear of the curve — same remedy as .sp-scroll. */
  scrollbar-width:thin;scrollbar-color:#C7CDD8 transparent;
}
.edit-src::-webkit-scrollbar,.edit-preview::-webkit-scrollbar{width:12px;height:12px}
.edit-src::-webkit-scrollbar-track,.edit-preview::-webkit-scrollbar-track{background:transparent}
.edit-src::-webkit-scrollbar-thumb,.edit-preview::-webkit-scrollbar-thumb{
  background:#C7CDD8;border-radius:999px;
  border:4px solid transparent;background-clip:content-box;
}
.edit-src::-webkit-scrollbar-thumb:hover,
.edit-preview::-webkit-scrollbar-thumb:hover{background:#AAB2C0;background-clip:content-box}
.edit-src::-webkit-scrollbar-corner,.edit-preview::-webkit-scrollbar-corner{background:transparent}
.edit-src{
  resize:none;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.86rem;line-height:1.6;color:var(--ink);
  padding:1.1rem 1.2rem;
}
.edit-src:focus-visible{outline:none;border-color:var(--cyan)}

/* ── the source column and its assistant ─────────────────────────────────────
   The source no longer sits directly in the split row: it shares a column with
   the assistant, and that column is what the vertical gutter now resizes against
   the preview. The two children (source, assistant) split the column's height by
   flex-grow, exactly as source and preview split the row's width — flex-basis 0,
   so the ratio IS the pair of grow factors, set by edit.js. */
.edit-source-col{
  flex:1 1 0;min-width:0;min-height:0;
  display:flex;flex-direction:column;gap:.8rem;
}
.edit-src{flex:1 1 0}

/* The horizontal gutter is .edit-gutter's twin, rotated 90°: a 1rem hit strip
   on the seam between source and assistant, negative margins cancelling the
   column gap so the panes keep their .8rem separation and the strip sits ON the
   gap. Invisible until hovered/dragged — the panes' borders draw the seam. */
.edit-hgutter{
  flex:none;align-self:stretch;height:1rem;margin:-.9rem 0;
  position:relative;cursor:row-resize;touch-action:none;user-select:none;
}
.edit-hgutter::before{
  content:"";position:absolute;left:0;right:0;top:calc(50% - 1px);height:2px;
  border-radius:2px;background:transparent;transition:background .12s;
}
.edit-hgutter:hover::before,
.edit-hgutter.is-dragging::before,
.edit-hgutter:focus-visible::before{background:var(--cyan)}
.edit-hgutter:focus-visible{outline:none}

/* The assistant panel: finding cards stacked above the chat, both built by
   edit.js. flex-basis 0 so it splits the column height with the source. */
.edit-assist{
  flex:1 1 0;min-height:0;
  display:flex;flex-direction:column;gap:.55rem;
  background:#fff;
  /* The AI accent frame. This box is otherwise a twin of the source textarea
     above it — same white, same radius — so the magenta border is the whole of
     what says the one is a companion and the other is the page. Kept to 1px with
     a faint wash of the same hue behind it, so it is identity, not an alarm. */
  border:1px solid var(--ai);
  box-shadow:0 1px 10px rgba(216,14,165,.07);
  border-radius:14px;
  padding:.8rem;overflow:hidden;
}
/* The identity mark: the app's AI spark (as on the landing answer) in a magenta
   chip at the head of the action strip. A mark, not a label — it costs a corner,
   not a line — and it carries the meaning colour alone cannot: magenta says
   "different", the spark says "AI". */
.assist-brand{
  flex:none;display:grid;place-items:center;
  width:1.55rem;height:1.55rem;border-radius:8px;
  background:var(--ai-tint);color:var(--ai-strong);
}
.assist-brand svg.i{width:1rem;height:1rem}
/* One scroll region for the whole panel body — findings, then transcript — so
   there is a single scrollbar, not one per region. min-height:0 lets it shrink
   and scroll inside the fixed-height panel. */
.assist-scroll{
  flex:1 1 0;min-height:0;overflow-y:auto;
  display:flex;flex-direction:column;gap:.6rem;
}
/* Findings take exactly the height they need — no cap, no scrollbar of their own.
   A burst of them scrolls with the transcript, in .assist-scroll. An empty block
   takes no room and draws no gap. */
.assist-findings{
  flex:none;display:flex;flex-direction:column;gap:.5rem;
}
.assist-findings:empty{display:none}

/* One card shape for every finding; the colour is the severity and the card's
   own words say what it is, so it needs no heading above it. */
.finding{
  display:flex;align-items:center;justify-content:space-between;gap:.6rem;
  padding:.35rem .6rem;border:1px solid;border-radius:10px;
  font-size:.82rem;line-height:1.4;
}
.finding-body{min-width:0}
/* The pills inside a finding are compact — the card's height should follow its
   text, not the buttons. Scoped here so the app's .btn.sm is untouched. */
.finding-actions .btn.sm{padding:.26rem .7rem;font-size:.76rem}
.finding-body code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.9em;padding:.03rem .3rem;border-radius:5px;
  background:rgba(5,17,55,.06);
}
.finding-actions{flex:none;display:flex;align-items:center;gap:.35rem}
/* Dismiss: a quiet × that removes a REVIEW finding the author disagrees with.
   Only review findings carry it — a dead link is a fact, not an opinion, and it
   comes straight back on the next scan. Inherits the card's colour so it reads as
   part of the finding, not a control bolted on. */
.finding-dismiss{
  flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:1.5rem;height:1.5rem;padding:0;margin-left:.05rem;
  background:transparent;border:none;border-radius:999px;color:inherit;opacity:.55;
  cursor:pointer;transition:opacity .12s,background .12s;
}
.finding-dismiss:hover{opacity:1;background:rgba(5,17,55,.08)}
.finding-dismiss:focus-visible{outline:2px solid currentColor;outline-offset:1px;opacity:1}
.finding-dismiss svg.i{width:.9rem;height:.9rem}
.finding--error{background:#FEF2F2;border-color:#FECACA;color:#991B1B}
.finding--warning{background:#FFFBEB;border-color:#FDE68A;color:#92400E}
.finding--suggestion{background:var(--tint-soft);border-color:var(--line);color:var(--ink)}

/* The "Update page summary" proposal. It borrows the suggestion card's skin but
   not its row layout: a finding is one line with actions beside it, this is a
   whole sentence that has to breathe — a label, the proposed summary, the
   current one for contrast, then the actions under them. So it stacks. It lives
   in its own container above the findings, because runLint rebuilds the findings
   list on every keystroke and would otherwise wipe the proposal mid-read. */
.assist-summary{flex:none;display:flex;flex-direction:column;gap:.5rem;margin-bottom:.5rem}
.assist-summary:empty{display:none}
.assist-summary-card{flex-direction:column;align-items:stretch;gap:.45rem}
.assist-summary-label{
  font-size:.72rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;
  color:var(--ai-strong);
}
.assist-summary-text{color:var(--ink)}
.assist-summary-current{font-size:.78rem;color:var(--muted)}
.assist-summary-current-label{font-weight:600}
.assist-summary-card .finding-actions{justify-content:flex-end}
.assist-summary-note{padding:.35rem .6rem;font-size:.82rem;color:var(--muted)}
/* Same family as Review — an AI action, tinted, not a second solid control. */
.btn.assist-summarize{color:var(--ai-strong)}
.btn.assist-summarize:hover{background:var(--ai-tint);border-color:var(--ai)}

/* ── the chat, in the lower half of the assistant ────────────────────────────
   A transcript that scrolls, an input pinned under it. The agent's message body
   reuses .ai-answer (see the landing search) so a streamed answer looks the same
   here — only the padding is trimmed for the narrower column. */
.chat-log{
  flex:1 1 0;min-height:0;
  display:flex;flex-direction:column;gap:.6rem;
  padding:.1rem .2rem .2rem;
}
.chat-msg{font-size:.84rem;line-height:1.5;max-width:100%}
.chat-msg--user{
  align-self:flex-end;max-width:85%;
  padding:.4rem .65rem;border-radius:12px 12px 4px 12px;
  background:var(--tint-soft);color:var(--ink);
  white-space:pre-wrap;word-break:break-word;
}
.chat-msg--agent{align-self:flex-start;width:100%}
/* .ai-answer's own padding is for a wide answer panel; here the column is
   narrow and the message already has the log's padding around it. */
.chat-body.ai-answer{padding:0;font-size:.84rem;max-width:none}
.chat-body .ai-go{margin:.5rem 0 0}
.chat-error{color:#B91C1C}
.chat-note{color:var(--muted)}
/* The MARGIN is what the wide answer panel sized (1.4rem to match its own
   gutters) and what the narrow column trims — the message already has the log's
   padding around it. The pill keeps its own padding: it keeps its border, and a
   bordered chip with no room inside it pins the spinner and text to its edge. */
.chat-msg--agent .activity{margin:.1rem 0}

/* The document-action strip at the top of the panel: the Review trigger on the
   left, the state of the check on the right. A home for whole-document actions
   that Send is not. */
.assist-actions{flex:none;display:flex;align-items:center;gap:.4rem}
.assist-actions .btn.is-busy{color:var(--muted)}
/* Review is an AI action too, so its label carries the accent — but only the
   label. A second solid-magenta control beside Send would be the overdoing; this
   stays the quiet subtle button it was, just tinted into the family. The wash on
   hover is the accent, not the grey the base .subtle would bring. */
/* .btn.assist-review, not .assist-review: the base .btn.subtle sets the ink
   colour with two classes, so the accent needs two of its own to win. */
.btn.assist-review{color:var(--ai-strong)}
.btn.assist-review:hover{background:var(--ai-tint);border-color:var(--ai)}
/* Right-aligned status — "No problems", or the count when something is flagged.
   Gives the row a second, honest job and a balance the lone button lacked. */
.assist-status{margin-left:auto;font-size:.78rem;color:var(--muted)}

/* The divider belongs over a conversation, not over empty space — so it appears
   only once the transcript has something in it. An empty panel is just the status
   row, any findings, and a clean input. */
/* The input is the positioning context for the send button, which sits inside
   it — so the two read as one control, the chat convention. */
.chat-form{
  position:relative;flex:none;
  padding-top:.55rem;
}
.edit-assist.has-convo .chat-form{margin-top:.15rem;border-top:1px solid var(--line)}
.chat-input{
  display:block;width:100%;box-sizing:border-box;
  /* overflow stays hidden while the box grows to fit; the script switches it to
     auto only once the text is taller than the cap (see autosize in edit_assist.js).
     A permanent scrollbar would otherwise show on a single line and clip the
     rounded corners. */
  resize:none;min-height:2.75rem;max-height:120px;overflow-y:hidden;
  /* Vertical padding fills the min-height so a single line sits centred (a
     textarea won't align-items:center). Right padding reserves the send button's
     space so text never runs under it. */
  padding:.8rem 2.9rem .8rem .75rem;
  font:inherit;font-size:.84rem;line-height:1.4;color:var(--ink);
  background:#fff;border:1px solid var(--line);border-radius:12px;
}
.chat-input:focus{outline:none;border-color:var(--ai)}
.chat-input::placeholder{color:var(--muted)}

/* Send: a solid-blue circle INSIDE the input, pinned to its bottom-right — the
   chat convention (claude.ai, chatgpt) — so a message that grows to several lines
   keeps the button in reach. An up-arrow that becomes a stop square while an
   answer streams. */
.chat-send{
  position:absolute;right:.45rem;bottom:.45rem;
  width:1.9rem;height:1.9rem;
  display:inline-flex;align-items:center;justify-content:center;
  /* Magenta, not the site blue: this is the AI's own action, and it wears the
     AI's colour to say so — the blue Save/Publish actions elsewhere keep theirs. */
  background:var(--ai);color:#fff;border:none;border-radius:999px;
  cursor:pointer;transition:background .12s;
}
.chat-send:hover{background:var(--ai-strong)}
.chat-send:focus-visible{outline:2px solid var(--ai);outline-offset:2px}
.chat-send svg.i{width:1rem;height:1rem}
.chat-send.is-stop{background:#B91C1C}
.chat-send.is-stop:hover{background:#991B1B}

/* A proposed edit under the message that offered it: the reason, then Apply. */
.chat-apply{
  display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;
  margin:.5rem 0 0;padding:.5rem .6rem;
  background:var(--tint-soft);border:1px solid var(--line);border-radius:10px;
}
.chat-apply-why{flex:1 1 auto;min-width:0;font-size:.8rem;color:var(--body)}
.chat-apply .btn.sm{flex:none}

/* ── the draft inbox ─────────────────────────────────────────────────────── */
.empty-note{font-size:.95rem;color:var(--muted)}
.draft-list{display:flex;flex-direction:column;gap:.7rem}
.draft-card{
  display:flex;align-items:center;justify-content:space-between;gap:1.2rem;
  flex-wrap:wrap;
  padding:1rem 1.1rem;
  background:#fff;border:1px solid var(--line);border-radius:14px;
  transition:border-color .12s,box-shadow .12s;
}
.draft-card:hover{border-color:#c7cdd9;box-shadow:0 2px 12px rgba(5,17,55,.06)}
.draft-card.is-stale{background:#FFFBEB;border-color:#FDE68A}
.draft-main{min-width:0}
.draft-title{display:block;font-size:1rem;font-weight:650;color:var(--ink)}
.draft-title:hover{color:var(--blue)}
.draft-meta{margin-top:.25rem;font-size:.82rem;color:var(--muted)}
.draft-meta a{color:var(--muted);text-decoration:underline;text-underline-offset:2px}
.draft-meta a:hover{color:var(--blue)}
.draft-warn{margin-top:.4rem;font-size:.82rem;color:#92400E;max-width:70ch}
.draft-actions{display:flex;gap:.5rem;flex-wrap:wrap}

/* The commit message. Deliberately quiet — it is optional, and a field that
   looks required would make every fix feel like paperwork. It only grows into
   something worth looking at once it has focus. */
.edit-msg{
  width:11rem;height:2.1rem;padding:0 .6rem;
  font-size:.8rem;color:var(--ink);
  background:#fff;border:1px solid var(--line);border-radius:9px;
  transition:width .16s,border-color .12s;
}
.edit-msg:focus{outline:none;border-color:var(--cyan);width:17rem}
.edit-msg::placeholder{color:var(--muted)}
/* Explicit, like .btn[hidden]: the UA rule would do it today only because
   nothing above sets a display — one added `display:inline-flex` and the box
   would come back on an untouched editor. */
.edit-msg[hidden]{display:none}
/* "Saved 14:02" — the counterpart of .edit-err: says the last thing that went
   right, and is replaced by the next one. */
.edit-note{font-size:.8rem;color:var(--muted)}
.btn.subtle.danger{color:#B91C1C}
.btn.subtle.danger:hover{border-color:#B91C1C;background:#FEF2F2}

/* Draft banner: a stripe between the toolbar and the panes, so it cannot be
   scrolled away from. Editing someone's draft, or editing a page that HAS
   drafts, is context you must not be able to forget you are in. */
.edit-banner{
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
  flex-wrap:wrap;
  padding:.7rem .95rem;font-size:.85rem;color:var(--body);
  background:var(--tint-soft);border:1px solid var(--line);border-radius:12px;
}
.edit-banner strong{color:var(--ink)}
.edit-banner a{color:var(--blue);font-weight:600}
.edit-banner.is-stale{background:#FFFBEB;border-color:#FDE68A}
/* Unresolved conflict markers: not a warning, a blockage. It reads as an error
   because publishing is refused until it is settled. */
.edit-banner.is-conflicted{background:#FEF2F2;border-color:#FECACA}
.edit-banner.is-conflicted .edit-banner-warn{color:#991B1B}
.edit-banner-warn{margin-top:.3rem;color:#92400E;max-width:70ch}
.edit-banner-warn code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.edit-banner-actions{display:flex;gap:.5rem;flex-wrap:wrap}

/* ── the history drawer ──────────────────────────────────────────────────────
   An overlay pinned under the sticky header, not a fourth column: it answers a
   question and closes again, and source/preview have no pixels to lend. */
.edit-drawer{
  position:fixed;top:var(--top-h);right:0;bottom:0;z-index:40;
  width:min(30rem,100vw);
  display:flex;flex-direction:column;
  background:#fff;border-left:1px solid var(--line);
  box-shadow:-8px 0 30px rgba(5,17,55,.10);
}
.edit-drawer[hidden]{display:none}
.edit-drawer-top{
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:.9rem 1rem;border-bottom:1px solid var(--line);flex:none;
}
.edit-drawer-top h2{font-size:1rem;font-weight:700;color:var(--ink)}
.edit-drawer-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:.6rem 1rem 2rem}
.edit-drawer-body h3{
  font-size:.72rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);margin:1.1rem 0 .5rem;
}
.edit-drawer-body h3:first-child{margin-top:.5rem}
.edit-drawer-back{
  display:inline-flex;align-items:center;gap:.35rem;
  font-size:.8rem;font-weight:600;color:var(--blue);margin-bottom:.8rem;
}

/* One version, or one draft. The whole row is not a link: it carries two or
   three different actions, and a link wrapping buttons is a lie to the keyboard. */
.ver{padding:.65rem 0;border-top:1px solid var(--line)}
.ver:first-of-type{border-top:none}
.ver-msg{font-size:.87rem;font-weight:600;color:var(--ink);margin-bottom:.15rem}
.ver-meta{font-size:.78rem;color:var(--muted)}
.ver-meta code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.ver-actions{display:flex;gap:.4rem;flex-wrap:wrap;margin-top:.45rem}
.ver.is-current .ver-msg::after{
  content:"live";
  margin-left:.45rem;padding:.05rem .35rem;vertical-align:.08em;
  font-size:.66rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:#065F46;background:#D1FAE5;border-radius:5px;
}
.ver.is-draft{background:var(--tint-soft);border-radius:10px;padding:.65rem .7rem;margin-bottom:.4rem;border:none}
.ver.is-draft.is-stale{background:#FFFBEB}

/* The diff. Word-level marks inside line-level rows — a reworded clause shows
   as three highlighted words, not as a deleted and re-added paragraph. */
.diff-sum{font-size:.8rem;color:var(--muted);margin-bottom:.7rem}
.diff-sum .add{color:#065F46;font-weight:650}
.diff-sum .del{color:#991B1B;font-weight:650}
.diff-hunk{
  border:1px solid var(--line);border-radius:10px;overflow:hidden;
  margin-bottom:.8rem;
}
.diff-line{
  display:flex;gap:.6rem;
  padding:.15rem .6rem;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.76rem;line-height:1.55;
  white-space:pre-wrap;overflow-wrap:anywhere;
}
.diff-no{
  flex:none;width:2.2rem;text-align:right;
  color:var(--muted);user-select:none;
}
.diff-line.add{background:#ECFDF5}
.diff-line.del{background:#FEF2F2}
.diff-line.add .diff-txt mark{background:#A7F3D0;color:#064E3B}
.diff-line.del .diff-txt mark{background:#FECACA;color:#7F1D1D;text-decoration:line-through}
.diff-txt mark{border-radius:3px;padding:0 .05em}
.diff-none{font-size:.85rem;color:var(--muted);padding:.8rem 0}

.edit-preview{padding:0 1.5rem}
.edit-preview-head{padding:1.3rem 0 1.1rem;border-bottom:1px solid var(--line)}
.edit-preview-head h1{font-size:1.5rem;font-weight:700}
.edit-preview-head .sub{font-size:.95rem;color:var(--muted);margin-top:.35rem;max-width:60ch}
/* The article keeps .prose so the preview IS the page's rendering, but the
   pane needs less breathing room than the page. */
.edit-preview article.prose{padding-top:1.2rem;padding-bottom:4rem}

/* ================= clause (widget) editor ================= */
/* Reuses the page editor's shell, split, gutter and drawer; these are the parts
   that are its own — a YAML source column with an error bar under it, and a
   preview pane that hosts the live clause generator instead of rendered markdown. */
.ew-head{display:flex;flex-direction:column;gap:.15rem;margin-right:auto}
.ew-head h1{font-size:1.35rem;font-weight:700;line-height:1.2}
.ew-sub{font-size:.82rem;color:var(--muted)}
.edit-widget.is-busy{opacity:.65;pointer-events:none}

/* The source column: YAML fills it, the error bar pins to the bottom and only
   shows when the definition will not parse or compose. */
.ew-error{
  flex:none;background:var(--danger-bg,#fdecec);color:var(--danger,#b42318);
  border:1px solid var(--danger,#b42318);border-radius:10px;
  padding:.6rem .8rem;font-size:.82rem;line-height:1.5;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  white-space:pre-wrap;max-height:9rem;overflow:auto;
}

/* The preview pane hosts the clause generator; give it room and a quiet caption. */
.ew-preview{display:flex;flex-direction:column}
.ew-preview-head{
  flex:none;padding:1rem 0 .8rem;border-bottom:1px solid var(--line);
  font-size:.82rem;color:var(--muted);
}
/* The host is a flex column the generator FILLS to the pane's bottom (same shape
   the lightbox gives it): the questions pin at the top and only the clause text
   scrolls, so the host itself does not scroll — two scrollbars otherwise. */
.ew-preview-host{flex:1 1 0;min-height:0;display:flex;flex-direction:column;padding-top:1.1rem}
/* Inline in the editor the generator is chrome, not a page's own flow, so it
   drops the box borders/shadow it wears on the contracts page and stretches. */
.ew-preview-host .c-clausegen{
  flex:1 1 auto;min-height:0;display:flex;flex-direction:column;
  margin:0;border:none;box-shadow:none;background:transparent;
}
.ew-preview-host .cg-head{display:none}          /* the editor already shows the title */
.ew-preview-host .cg-body{min-height:0;padding:0;background:transparent}
.ew-preview-host .cg-output{max-height:none;min-height:8rem}

/* History drawer list. */
.ew-hist{list-style:none;margin:.3rem 0 1.2rem;padding:0;display:flex;flex-direction:column;gap:.5rem}
.ew-hist li{font-size:.85rem;color:var(--body);line-height:1.5}
.edit-drawer-body h3{font-size:.9rem;font-weight:650;margin:.4rem 0 .2rem}
.ew-hint{color:var(--muted);font-size:.85rem}

/* ================= comments (docs/comments.md) =================
   A reveal, not an always-on: the highlights and cards below exist only while
   the layer is revealed (comments.js paints them on the Comment toggle). The
   read view stays clean — the only standing mark is the pill's dot. */

/* Anchored text. A quiet amber wash + underline: enough to find, not enough to
   fight the prose. Resolved threads fade to grey. */
.comment-highlight{
  background:color-mix(in srgb, #F6C453 32%, transparent);
  box-shadow:inset 0 -2px 0 color-mix(in srgb, #E6A300 60%, transparent);
  border-radius:2px;cursor:pointer;transition:background .12s}
.comment-highlight:hover,.comment-highlight.is-active{
  background:color-mix(in srgb, #F6C453 52%, transparent)}
.comment-highlight.is-resolved{
  background:color-mix(in srgb, var(--muted) 16%, transparent);
  box-shadow:inset 0 -2px 0 color-mix(in srgb, var(--muted) 40%, transparent)}
/* The text a comment is being written about right now — a stronger amber than a
   resting highlight, so it clearly reads as "this is the selection". */
.comment-highlight.is-pending{
  background:color-mix(in srgb, #F6C453 62%, transparent);
  box-shadow:inset 0 -2px 0 var(--amber-tx)}

/* The floating "Comment" offered on a selection. A dark chip above the range. */
.comment-sel-pop{
  position:fixed;z-index:60;transform:translate(-50%,-100%);
  background:var(--navy);color:#fff;border:0;border-radius:999px;
  padding:.4rem .8rem;font-size:.78rem;font-weight:600;cursor:pointer;
  display:inline-flex;align-items:center;gap:.4rem;
  box-shadow:0 6px 18px rgba(5,17,55,.28)}
.comment-sel-pop svg{width:1em;height:1em}
.comment-sel-pop[hidden]{display:none}

/* The one floating card, reused for a new-thread composer and for reading a
   thread. comments.js positions it by an anchor's rect. */
.comment-card{
  position:fixed;z-index:59;width:min(22rem,calc(100vw - 1.5rem));
  background:#fff;border:1px solid var(--line);border-radius:14px;
  box-shadow:0 12px 34px rgba(5,17,55,.18);
  display:flex;flex-direction:column;max-height:min(28rem,70vh)}
.comment-card[hidden]{display:none}
.comment-card-head{
  display:flex;align-items:center;justify-content:space-between;gap:.6rem;
  padding:.6rem .8rem;border-bottom:1px solid var(--line)}
.comment-card-head .t{font-size:.7rem;font-weight:650;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.comment-card-close{background:none;border:0;cursor:pointer;color:var(--muted);
  padding:.15rem;display:inline-flex;border-radius:6px}
.comment-card-close:hover{color:var(--ink);background:var(--surface)}
.comment-card-close svg{width:1.05rem;height:1.05rem}
.comment-card-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:.4rem .8rem;
  display:flex;flex-direction:column;gap:.5rem}
/* Who can read this — a quiet standing footer (docs/comments.md §2). */
.comment-card-note{flex:none;border-top:1px solid var(--line);padding:.45rem .8rem;
  font-size:.7rem;color:var(--muted);line-height:1.4}

/* One comment (root or reply). */
.comment-item{border-top:1px solid var(--line);padding:.5rem 0}
.comment-item:first-child{border-top:0}
.comment-item.is-reply{margin-left:.8rem;border-top:0;
  border-left:2px solid var(--line);padding-left:.6rem}
.comment-meta{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;
  margin-bottom:.15rem}
.comment-author{font-size:.78rem;font-weight:650;color:var(--ink)}
.comment-time{font-size:.68rem;color:var(--muted)}
.comment-resolved-tag{font-size:.62rem;font-weight:650;letter-spacing:.04em;
  text-transform:uppercase;color:var(--green-tx);background:var(--green-bg);
  border-radius:999px;padding:.05rem .4rem}
.comment-text{font-size:.82rem;color:var(--body);line-height:1.5;
  white-space:pre-wrap;word-break:break-word}
.comment-actions{display:flex;gap:.2rem;margin-left:auto}
.comment-act{background:none;border:0;cursor:pointer;color:var(--muted);
  padding:.15rem;display:inline-flex;border-radius:6px}
.comment-act:hover{color:var(--ink);background:var(--surface)}
.comment-act.is-danger:hover{color:var(--red-tx);background:var(--red-bg)}
.comment-act.is-done:hover{color:var(--green-tx);background:var(--green-bg)}
.comment-act svg{width:.95rem;height:.95rem}

/* The composer — new thread and reply share it. No side padding: it sits inside
   the card body (which already insets), so the textarea fills the body width,
   flush with the comments above it. */
.comment-compose{border-top:1px solid var(--line);padding:.6rem 0 0;
  display:flex;flex-direction:column;gap:.5rem}
/* A new comment has nothing above the composer, so the separator line (and its
   padding) would only double the header's border. Drop it when it leads. */
.comment-card-body > .comment-compose:first-child{border-top:0;padding-top:0}
.comment-compose textarea{
  width:100%;resize:vertical;min-height:3.2rem;font:inherit;font-size:.82rem;
  color:var(--ink);background:var(--surface);border:1px solid var(--line);
  border-radius:10px;padding:.5rem .6rem}
.comment-compose textarea:focus{outline:none;border-color:var(--cyan);
  background:#fff}
.comment-compose-row{display:flex;align-items:center;justify-content:flex-end;
  gap:.5rem}
.comment-post{background:var(--blue);color:#fff;border:0;border-radius:999px;
  padding:.4rem .9rem;font-size:.78rem;font-weight:650;cursor:pointer}
.comment-post:disabled{opacity:.45;cursor:not-allowed}
.comment-empty{font-size:.8rem;color:var(--muted);font-style:italic;
  padding:.3rem 0}

/* Orphan banner — anchors whose text is gone. Never lost, just detached. */
.comment-orphans{margin:0 0 1rem;background:var(--amber-bg);color:var(--amber-tx);
  border:1px solid color-mix(in srgb, var(--amber-tx) 25%, transparent);
  border-radius:12px;padding:.6rem .85rem;font-size:.83rem}
.comment-orphans summary{cursor:pointer;font-weight:600}
.comment-orphans ul{margin:.5rem 0 0;padding-left:1.1rem;display:flex;
  flex-direction:column;gap:.3rem}
.comment-orphans .q{font-style:italic;text-decoration:line-through;opacity:.75}
.comment-orphans[hidden]{display:none}

/* ================= forms (docs/forms.md) ==========================================
   Filling in a form document. The controls reuse .field/.input/.check/.btn above —
   this section is only the parts a form has that a settings card does not: the
   question rhythm, the option lists, and the sections that hide with a `when:`. */
/* A shorter measure than the shell's, and capped on the CONTENT rather than on
   the .wrap: .wrap centers itself (`margin:0 auto`), so narrowing it would slide
   the form out from under the title in the head above it. The form starts at the
   shell's left edge, like every other page's body, and just stops sooner.

   `width:100%` is load-bearing. This .wrap is a direct child of the flex-column
   body, and .wrap's own `margin:0 auto` CANCELS the flex stretch that would
   otherwise size it — an auto cross-axis margin wins over `align-items:stretch`.
   Without this the element falls back to fit-content and centres on its own
   content, which puts the form a hundred-odd pixels right of the title above it.
   Every other .wrap sits inside a plain block and never meets this. */
.fm-wrap{width:100%;padding-block:1.6rem 3rem}
.fm-intro{max-width:44rem}
/* The name a new hire is greeted by, above the form's own title. A warm line,
   not the uppercase eyebrow — it is a hello, not a section label. */
.fm-welcome{font-size:1.05rem;font-weight:650;color:var(--blue);margin:0 0 .15rem}
.fm-form{display:flex;flex-direction:column;gap:1.4rem;max-width:48rem}

/* The read-only onboarding guide (docs/forms.md §7b) reuses .fm-form on a
   <fieldset> so one disabled attribute greys every control at once. A fieldset
   brings its own border, padding and `min-inline-size:min-content`; strip all
   three so it lays out as the plain flex column .fm-form already makes it. The
   greyed controls the browser draws for `disabled` are the point — the honest
   signal that this checklist is here to read, not to fill. */
.fm-guide{border:0;padding:0;margin:0;min-inline-size:0}

/* `hidden` on a flex child loses to `display:flex` on the parent's children in
   every browser — the attribute sets `display:none` at the UA level and any
   `display` we set here beats it. .fm-row is the element JS toggles, so the reset
   has to be explicit and has to out-specify nothing else that sets its display. */
.fm-row[hidden]{display:none}
.fm-row{display:flex;flex-direction:column;gap:.45rem}

/* A section heading (`type: content`) opens a group, so it needs air above it —
   but not when it is the first thing on the form, where it would only push the
   questions away from the title. */
.fm-row-content + .fm-row{margin-top:.1rem}
.fm-form > .fm-row-content{margin-top:1rem}
.fm-form > .fm-row-content:first-child{margin-top:0}
.fm-heading{font-size:1.05rem;font-weight:700;color:var(--blue);margin:0;
  padding-bottom:.4rem;border-bottom:1px solid var(--line)}
.fm-body{font-size:.88rem;color:var(--muted);margin:.4rem 0 0}

/* An address inside a form's own words (src/forms.py `linkify`) — in a heading,
   a question, an option or the intro, so it is styled by class and not by where
   it sits. The reset takes a link's colour and underline off site-wide, and an
   unmarked link in the middle of a checklist item is a link nobody sees; these
   are the .prose values, because it is the same job. overflow-wrap is the
   backstop for what the <wbr>s cannot break — one segment longer than the
   column, which the mined SVN filenames get close to. */
.fm-link{
  color:var(--blue);text-decoration:underline;
  text-decoration-color:color-mix(in srgb,var(--blue) 30%,transparent);
  text-underline-offset:2px;overflow-wrap:anywhere;
}
.fm-link:hover{text-decoration-color:var(--blue)}

/* The question itself. .field > label already styles the <label> case; this is
   the same weight for the grouped controls, which are labelled by a span. */
.fm-label{font-size:.82rem;font-weight:600;color:var(--ink)}
.fm-req{margin-left:.2rem;color:var(--red-tx);text-decoration:none;font-weight:700}
.fm-textarea{resize:vertical;min-height:5rem}

/* Options wrap into as many columns as fit rather than a fixed grid: the leisure
   survey has fifteen activities and a consent box has one, and neither should be
   told how wide the other is. */
.fm-options{display:grid;gap:.4rem .9rem;
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}
.fm-opt{font-weight:450;align-items:flex-start;line-height:1.35}
.fm-opt input{margin-top:.12rem;flex:none}

/* An option that has run out (docs/forms.md §10). It stays legible rather than
   fading away: someone scanning for the tour they were promised needs to find it
   and read why it is not on offer, and greying it into the background would only
   make them look harder. `cursor:default` because .check sets `pointer` and a
   pointing hand over something that cannot be clicked is a small lie. */
.fm-opt.is-full{color:var(--muted);cursor:default}
.fm-opt.is-full input{cursor:default}
.fm-full{display:inline-block;margin-left:.35rem;font-size:.62rem;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;
  padding:.05rem .3rem;border-radius:3px;color:var(--red-tx);
  background:var(--red-bg);vertical-align:.05rem}
.fm-check{font-weight:450;align-items:flex-start}
.fm-check input{margin-top:.12rem;flex:none}

/* A rating is really a radio group; the stars are drawn over it. The inputs stay
   in the DOM (and focusable) so it works by keyboard and without JS. */
.fm-rating{display:inline-flex;gap:.15rem}
/* position:relative is load-bearing, not decoration: the input below is taken
   out of flow, and without a positioned parent all of a rating's inputs escape
   to the nearest positioned ancestor and stack on one point — so a click lands
   on whichever happens to be on top rather than on the star under the pointer. */
.fm-star{cursor:pointer;line-height:1;position:relative;display:inline-flex}
.fm-star input{position:absolute;inset:0;opacity:0;width:100%;height:100%;
  margin:0;cursor:pointer}
.fm-star-mark{font-size:1.5rem;color:var(--line);transition:color .12s}
/* Hover previews the value it would set: every star up to the pointer lights up.
   Same forwards trick as the checked rules below — light the whole row, then dim
   the stars after the hovered one. */
.fm-rating:hover .fm-star .fm-star-mark{color:var(--amber-tx)}
.fm-rating .fm-star:hover ~ .fm-star .fm-star-mark{color:var(--line)}
.fm-star input:focus-visible + .fm-star-mark{outline:2px solid var(--blue);
  outline-offset:2px;border-radius:4px}
/* Fill every star up to the checked one: the checked input's star, and — reading
   backwards is impossible in CSS — every star AFTER it is dimmed instead, so the
   rule runs forwards from :has(). :not(:hover) hands the row to the hover
   preview: without it, hovering below the checked star could not dim the stars
   between the pointer and the saved value. */
.fm-rating:not(:hover):has(input:checked) .fm-star .fm-star-mark{color:var(--amber-tx)}
.fm-rating:not(:hover) .fm-star:has(input:checked) ~ .fm-star .fm-star-mark{color:var(--line)}
/* Visually hidden, still read aloud: the star is aria-hidden, so this carries the
   option's name. clip-path rather than display:none, which would take it out of
   the accessibility tree along with the pixels. */
.fm-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);
  white-space:nowrap}

.fm-todo{font-size:.83rem;color:var(--muted);font-style:italic;margin:0}

/* The signature pad: a canvas over a hidden input that holds the PNG. */
.fm-sign{display:flex;flex-direction:column;gap:.5rem;align-items:flex-start}
/* touch-action:none is load-bearing, not a nicety: without it a finger dragged
   across the canvas scrolls the page instead of drawing, so the control is
   unusable on precisely the devices people sign with. `block` because an inline
   canvas sits on the text baseline and leaves a gap under the border.
   aspect-ratio matches the 600x200 bitmap, so the pad scales down on a phone
   without the ink landing away from the finger (form.js maps through the rect). */
.fm-sign-pad{display:block;width:100%;max-width:420px;aspect-ratio:3/1;
  touch-action:none;cursor:crosshair;background:var(--card);
  border:1px dashed var(--line);border-radius:12px}
.fm-sign-pad:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.fm-sign-bar{display:flex;align-items:center;gap:.8rem;width:100%;max-width:420px;
  justify-content:space-between}
.fm-sign-hint{font-size:.78rem;color:var(--muted)}

.fm-actions{display:flex;align-items:center;gap:.8rem;justify-content:flex-end;
  border-top:1px solid var(--line);padding-top:1.1rem;margin-top:.4rem}
.fm-status{font-size:.8rem;color:var(--muted);margin-right:auto}
.fm-status.is-err{color:var(--red-tx)}

.fm-errors{background:var(--red-bg);color:var(--red-tx);border-radius:12px;
  border:1px solid color-mix(in srgb, var(--red-tx) 25%, transparent);
  padding:.7rem .9rem;font-size:.85rem}
.fm-errors[hidden]{display:none}
.fm-errors ul{margin:0;padding-left:1.1rem;display:flex;flex-direction:column;gap:.25rem}

/* ── the read-only summary ── */
.fm-done{background:var(--green-bg);color:var(--green-tx);border-radius:12px;
  border:1px solid color-mix(in srgb, var(--green-tx) 25%, transparent);
  padding:.8rem 1rem;font-size:.88rem;display:flex;flex-direction:column;gap:.15rem;
  margin-bottom:1.5rem;max-width:48rem}
.fm-summary{margin:0;display:flex;flex-direction:column;gap:0;max-width:48rem}
.fm-sum-row{display:grid;grid-template-columns:1fr;gap:.15rem;padding:.75rem 0;
  border-bottom:1px solid var(--line)}
/* overflow-wrap: labels and answers both carry unbreakable tokens (an svn://
   URL, a long mail address) that would otherwise walk out of their grid track
   and across the neighbouring column. */
.fm-sum-row dt{font-size:.8rem;font-weight:600;color:var(--muted);
  overflow-wrap:anywhere}
.fm-sum-row dd{margin:0;font-size:.92rem;color:var(--ink);overflow-wrap:anywhere}
/* The strokes are #111 on transparent (form.js), so the pane behind them must
   stay light in dark theme too or the signature vanishes. */
.fm-sum-sig{display:block;max-width:18rem;width:100%;height:auto;
  background:#fff;border:1px solid var(--line);border-radius:8px}
/* Label and answer side by side once there is room. A min-width enhancement, not
   a max-width undo — see the README's first CSS trap. Half the row each: the
   labels are the long half of most questions, and minmax(0,·) over a bare 1fr so
   an unbreakable token wraps instead of stretching its track. */
@media (min-width:44rem){
  .fm-sum-row{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:1rem;
    align-items:baseline}
}

/* ── the onboarding door (docs/forms.md §7) ──
   The email + one-time code page, and the stripped header the pages behind it
   share. Nearly all of it is the app's existing card and field styling: this is a
   form in the intranet, not a second front door, and it should not look like one
   — the point of §7 is that a new hire never meets a login page at all. */
/* The header with nothing in it but the mark. Centred, because a lone brand
   pinned left reads as a nav whose links failed to load. */
/* The mark sits where the real header puts it — left, against the same `.wrap`
   edge as the internal pages — so the door reads as the same site, not a
   separate microsite. (`.top-in` is flex-start by default; the onboarding
   header just has no nav to its right.) */
/* Left, not centred: the mark, the "Welcome" heading and its subtext all hang
   off the `.wrap` edge, and so does the form — one reading axis down the page,
   and the same axis the `/forms/...` page behind this door already uses. A card
   floated to the middle here reads as a standalone login, which is the one thing
   §7 says the door must not be. */
.ob-wrap{width:100%;padding-block:1.6rem 3rem;display:flex;flex-direction:column;
  align-items:flex-start;gap:1rem}
.ob-card{width:100%;max-width:26rem}
.ob-form{display:flex;flex-direction:column;gap:1.1rem}
.ob-error{background:var(--red-bg);color:var(--red-tx);border-radius:12px;
  border:1px solid color-mix(in srgb, var(--red-tx) 25%, transparent);
  padding:.7rem .9rem;font-size:.85rem}
/* The code is read off a printout and typed by hand: wide letters, wide spacing,
   and a monospace face so an O and a 0 are told apart on sight. (The alphabet has
   neither — see src/onboarding.py — but the reader does not know that.) */
.ob-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  letter-spacing:.12em;text-transform:uppercase}
.ob-submit{width:100%;justify-content:center;padding:.7rem 1rem}
.ob-aside{font-size:.8rem;color:var(--muted);max-width:26rem;text-align:left;
  margin:0}

/* The other end of the same code: the admin card that mints it. The plaintext
   lives in this box and nowhere else — only its hash is stored — so it is drawn
   to be read off a screen and copied without a slip. */
.ob-issued{margin-top:1rem;background:var(--green-bg);color:var(--green-tx);
  border:1px solid color-mix(in srgb, var(--green-tx) 25%, transparent);
  border-radius:12px;padding:.8rem 1rem;display:flex;flex-direction:column;gap:.4rem}
.ob-issued[hidden]{display:none}
/* Divides the two halves of the onboarding card: the saved settings above,
   the manual one-off invite below. */
.ob-sub-h{margin:1.6rem 0 .2rem;font-size:1rem}
.ob-issued-head{font-size:.85rem}
.ob-issued-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:1.25rem;font-weight:700;letter-spacing:.14em;user-select:all}

/* ── the form editor (the field list + the source) ──
   The chrome — .edit-shell, .edit-banner, .edit-top, .edit-split, .edit-gutter,
   .edit-source-col, .edit-hgutter, .edit-assist, .edit-drawer — is the org-chart
   and content editors', reused whole. Only the builder is new. */
.edit-form-body{margin-top:.6rem}
.edit-form-shell.is-busy{opacity:.65;pointer-events:none}
.ef-title{font-size:1.35rem;font-weight:700;line-height:1.2}
.edit-form-source{min-height:0}
/* The gutter and source only exist while "Source" is on, and neither hides on
   `hidden` alone: .edit-source-col sets display:flex and the gutter's media query
   sets display:block, and an author rule with any specificity at all outranks the
   UA sheet's [hidden]{display:none}. The org-chart editor says the same thing at
   .edit-org-source[hidden] — same trap, same fix, and it is the README's first
   specificity trap wearing a different hat. */
.edit-form-shell .edit-gutter[hidden],
.edit-form-source[hidden]{display:none}
.edit-form-source .ew-hint{flex:none;margin:0}
/* The YAML sits in the org-chart editor's clipping box (.oc-src-box carries the
   frame) so a highlight band can be laid UNDER the text to mark the selected
   field's lines; the textarea is flush and transparent inside it. The box, not
   the textarea, is the column's flex child — edit_form.js sets its flex-grow. */
.edit-form-source .ew-src{
  flex:1 1 0;width:100%;min-height:6rem;box-sizing:border-box;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.82rem;line-height:1.55;tab-size:2;
  border:none;border-radius:0;background:transparent;
  position:relative;z-index:1;   /* above the highlight band, so text stays on top */
}
.edit-form-source .ew-src:focus-visible{border:none}

/* The builder column: the real form, on the surface it will publish onto. It
   scrolls in itself — the shell is viewport-height and the page does not scroll,
   so a long survey has to scroll here or nowhere. */
.edit-form-preview{
  flex:1 1 0;min-width:0;min-height:0;overflow:auto;
  background:var(--surface);border:1px solid var(--line);border-radius:14px;
}
/* Sized so the drawn control ends up the SAME width as the published form, not
   narrower: the reader's `.fm-form` is 48rem, and here each field sits inside an
   `.ef-field` card (.75rem padding + border) within this padded column (1.4rem).
   At 46rem the control area fell to ~42rem and the options grid
   (minmax(15rem,…)) dropped from three columns to two — the WYSIWYG lied about
   the layout. 53rem puts the control back at ~48rem, so the preview matches. */
.ef-build{padding:1.2rem 1.4rem 2rem;max-width:53rem;
  display:flex;flex-direction:column;gap:.5rem}

/* The intro is a field of the form, edited where it renders. It reads as the
   paragraph it will be until it is focused, which is when it admits to being an
   input — the same trick the org chart's title field plays. */
.ef-intro{
  width:100%;box-sizing:border-box;resize:none;overflow:hidden;
  font:inherit;color:var(--body);line-height:1.6;
  background:transparent;border:1px dashed transparent;border-radius:8px;
  padding:.4rem .5rem;margin:0 0 .3rem;
}
.ef-intro:hover{border-color:var(--line)}
.ef-intro:focus{outline:none;background:#fff;border-style:solid;border-color:var(--cyan)}
.ef-intro::placeholder{color:var(--muted);font-style:italic}

.ef-fields{display:flex;flex-direction:column;gap:.5rem}
.ef-empty{color:var(--muted);font-size:.88rem;padding:1.4rem 0;text-align:center}

/* One field: what it is, on a strip above the control it draws. The strip is the
   editor; the control below it is the form. The grab cursor says what a press
   really does here: pick the card up (a click, released in place, selects). */
.ef-field{
  background:#fff;border:1px solid var(--line);border-radius:12px;
  padding:.5rem .75rem .75rem;cursor:grab;
}
.ef-field:hover{border-color:var(--tint)}
.ef-field.is-open{border-color:var(--cyan);box-shadow:0 0 0 3px var(--sky-soft)}
/* Selected: the card whose lines the source band marks — the org chart's ring. */
.ef-field.is-selected{border-color:var(--blue);box-shadow:0 0 0 2px rgba(35,35,255,.22)}
/* An applied assistant edit flashes the cards it touched — one pulse, then the
   ordinary selection ring is all that stays. */
@keyframes ef-touch{0%,25%{background:var(--sky-soft)}100%{background:#fff}}
.ef-field.is-touched{animation:ef-touch 1.4s ease}

/* Reordering by drag: the picked-up card goes position:fixed and follows the
   pointer (left/top set by edit_form.js); the slot it left holds its space as a
   dashed drop target; everyone else makes way by transform — animated, so it is
   always visible where the card would land. */
.ef-field.is-drag-ghost{
  position:fixed;z-index:1000;margin:0;cursor:grabbing;
  box-shadow:var(--shadow-pop);opacity:.95;
}
.ef-drop-slot{
  flex:none;box-sizing:border-box;
  border:1.5px dashed var(--tint);border-radius:12px;background:var(--sky-soft);
}
.ef-fields.is-rearranging .ef-field:not(.is-drag-ghost),
.ef-fields.is-rearranging .ef-drop-slot{transition:transform .16s ease}
.ef-field-head{
  display:flex;align-items:center;gap:.45rem;flex-wrap:wrap;
  padding-bottom:.4rem;margin-bottom:.5rem;border-bottom:1px solid var(--line);
  font-size:.74rem;color:var(--muted);
}
.ef-field-type{font-weight:600;color:var(--body)}
.ef-field-name,.ef-field-when code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.72rem;background:var(--tint-soft);border-radius:5px;padding:.05rem .3rem;
}
/* The tools sit at the far end of the strip, away from what they act on being
   read. margin-left:auto rather than a spacer element. */
.ef-tools{display:flex;align-items:center;gap:.1rem;margin-left:auto}
.ef-tool{padding:.25rem}
.ef-tool svg.i{width:.95rem;height:.95rem}
.ef-tool[disabled]{opacity:.3;cursor:default}
.ef-tool.danger:hover:not([disabled]){color:var(--red-tx);background:var(--red-bg)}
/* Where there is a mouse, the tools are chrome for the card under it: a resting
   card is just the field, and the pencil and trash fade in when it is pointed at.
   Touch devices have no hover, so this rule does not touch them and the tools
   stay put. Keyboard focus (tabbing onto a tool) and an open panel keep them up
   too, so what the pointer reveals is never the only way to reach them. */
@media (hover:hover) and (pointer:fine){
  .ef-tools{opacity:0;transition:opacity .12s ease}
  .ef-field:hover .ef-tools,
  .ef-field:focus-within .ef-tools,
  .ef-field.is-open .ef-tools{opacity:1}
}

/* The drawn control is a PICTURE of the control — its inputs are disabled in JS
   (so they are also out of the tab order, which pointer-events alone would not
   do). The card's grab cursor holds over it: a press here drags the card. */
.ef-field .fm-row{cursor:grab}
.ef-field .fm-row[hidden]{display:none}

.ef-add-row{padding-top:.4rem}

/* The configure panel: not a picture of the form, the form's own settings. It
   opens inside the field it is about, under it, so there is never a question of
   which field is being configured. Its controls are live (unlike the picture
   above them), so the card's grab cursor ends at its edge. */
.ef-panel{
  margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--line);
  display:flex;flex-direction:column;gap:.7rem;cursor:auto;
}
.ef-row{display:flex;flex-direction:column;gap:.25rem}
.ef-label{font-size:.78rem;font-weight:600;color:var(--body)}
.ef-hint{margin:0;font-size:.74rem;color:var(--muted);line-height:1.45}
.ef-hint code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.72rem;background:var(--tint-soft);border-radius:4px;padding:.02rem .25rem}
/* A checkbox is not a block: it belongs beside its label, not under it. */
.ef-row:has(> input[type=checkbox]){flex-direction:row;align-items:center;gap:.45rem;
  flex-wrap:wrap}
.ef-row:has(> input[type=checkbox]) .ef-label{order:2}
.ef-row:has(> input[type=checkbox]) .ef-hint{order:3;flex-basis:100%}

/* The "Shown when" name completer: a menu hung under the input (over the hint),
   listing the other fields' names as the condition is typed. The box is the
   positioning context so the menu tracks the input, not the whole row. */
.ef-ac-box{position:relative}
.ef-ac{
  position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:20;
  max-height:12rem;overflow-y:auto;
  background:#fff;border:1.5px solid var(--line);border-radius:10px;
  box-shadow:0 12px 34px rgba(5,17,55,.18);padding:.25rem;
}
.ef-ac-item{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.82rem;color:var(--ink);padding:.32rem .5rem;border-radius:7px;cursor:pointer;
}
.ef-ac-item.is-active{background:var(--sky-soft)}

.ef-options{display:flex;flex-direction:column;gap:.35rem}
.ef-option{display:flex;align-items:center;gap:.35rem}
.ef-option .input{flex:1 1 0;min-width:0}
.ef-option .ef-cap{flex:0 0 5.5rem}
.ef-panel-foot{display:flex;justify-content:flex-end}

/* The condition above a conditional field — used by the fill view's own preview
   route. The builder says the same thing in the field's head instead. */
.fm-when-badge{
  margin:.2rem 0 -.15rem;font-size:.72rem;color:var(--muted);
}
.fm-when-badge code{font-size:.72rem;background:var(--tint-soft);
  border-radius:5px;padding:.05rem .3rem}
.fm-preview{pointer-events:none}

/* The condition above a conditional field, in the preview only — the author sees
   every field, and this says which ones the reader will not. */
.fm-when-badge{
  margin:.2rem 0 -.15rem;font-size:.72rem;color:var(--muted);
}
.fm-when-badge code{font-size:.72rem;background:var(--tint-soft);
  border-radius:5px;padding:.05rem .3rem}

/* ================= profile (/people/<username>) =================
   One colleague, as the address book and the OrgChart know them. The identity
   block does the work: only 42 of 258 people hold a role in the chart, so it is
   the face/name/title row that has to carry the page for everyone else. */
/* No top margin: the identity block is the head's only child, so the head's own
   padding-block is the whole of the space around it — and it is symmetric. A
   margin here would land on top of the top padding and nowhere near the bottom,
   which read as a head sitting low in its own band, in both end states (the
   collapsed head keeps the margin while its padding shrinks, so it showed up
   worse there). */
.profile-id{display:flex;align-items:center;gap:clamp(1.1rem,2.6vw,1.9rem)}
.profile-portrait{
  --p:clamp(76px,11vw,120px);
  width:var(--p);height:var(--p);flex:none;border-radius:50%;
  background:var(--navy);color:#fff;display:grid;place-items:center;
  font-size:calc(var(--p) * .3);font-weight:700;letter-spacing:.02em;
  box-shadow:0 0 0 4px #fff, 0 0 0 5px var(--line);
}
/* The face collapses WITH the column beside it, keeping the two in the SAME
   ratio: when the shrinking text pulls the column in by x%, the disc loses x%
   too. The column is the right yardstick because it is the only thing the eye
   can measure the disc against — the two stand side by side on one axis, while
   the band's edges are tinted background with no landmark in them. At rest the
   disc overhangs the column by a few pixels top and bottom and reads as
   bracketing it; at that fixed ratio it still does, collapsed.

   What that ratio is worth depends on the column actually collapsing, and for a
   long time it did not: only the name moved, over a role and a facts row at
   fixed size, so the column gave back 14% where the band gave back 39% and the
   face grew louder the further you scrolled — a 103px portrait over an 18px
   name, three times the avatar in the bar directly above it. The fix belongs in
   the column (the role steps down, the facts close up — below), not in a
   different yardstick for the disc.

   How much x% is, CSS cannot know: it depends on wrapping and viewport. So
   page-head.js measures the column at both end states and hands over the disc's
   two sizes; here we only interpolate between them on --head-p. The fallbacks
   equal the resting size, so before the script runs — and with scripting off —
   the disc stays full, the same honest failure the rest of the head already
   fails to. */
.page-head.is-collapsible .profile-portrait{
  --rest:var(--portrait-rest, clamp(76px,11vw,120px));
  --p:calc(var(--rest) - (var(--rest) - var(--portrait-min, var(--rest))) * var(--head-p,0));
}
/* Same reason as .avatar.has-photo in the top bar: the navy disc has to go, or
   antialiasing leaves a hairline of it around the clipped photo. */
.profile-portrait.has-photo{overflow:hidden;background:var(--tint)}
.profile-portrait img{width:100%;height:100%;object-fit:cover;display:block}
.profile-id-main{min-width:0}
.page-head .profile-id-main h1{margin-top:0}
.profile-you{
  font-size:.62em;font-weight:600;color:var(--muted);background:#fff;
  border:1px solid var(--line);border-radius:999px;padding:.15em .6em;
  vertical-align:.22em;
}
.profile-title{--role:clamp(.95rem,1.5vw,1.08rem);font-size:var(--role);font-weight:550;color:var(--body);margin-top:.3rem}
.profile-facts{display:flex;flex-wrap:wrap;align-items:center;gap:.45rem .7rem;margin-top:.6rem}

/* ── what the column gives back on the way down ──────────────────────────────
   The head's rule, applied inside the identity block: what answers "how did I
   get here" goes, what answers "where am I" stays. The name and the role are
   who this page is about and stay; the contact group and the office are
   read-once metadata, the same kind of thing as the breadcrumbs, and close up
   the same way.

   The role has to shrink WITH the name, not just survive it. Left alone it
   ended up at 17.3px against a name collapsed to 18.4px — a step of 0.54 at
   rest flattened to nothing, so the two lines read as a pair of equals instead
   of a name with a role beneath it. It steps down to the clamp's own floor,
   which keeps the hierarchy legible at every width. */
.page-head.is-collapsible .profile-title{
  font-size:calc(var(--role) - (var(--role) - .95rem) * var(--head-p,0));
  margin-top:calc(.3rem - .1rem * var(--head-p,0));
}
.page-head.is-collapsible .profile-facts{
  height:calc(var(--facts-h,0px) * (1 - var(--head-p,0)));
  margin-top:calc(.6rem * (1 - var(--head-p,0)));
  overflow:hidden;
  opacity:calc(1 - var(--head-p,0) * 2);   /* gone by halfway */
}
.profile-badge{
  font-size:.75rem;font-weight:650;color:var(--deep);background:#fff;
  border:1px solid var(--line);border-radius:999px;padding:.22rem .7rem;
}
.profile-fact{display:inline-flex;align-items:center;gap:.35rem;font-size:.82rem;color:var(--muted)}
.profile-fact svg.i{width:.95rem;height:.95rem;opacity:.65}

/* A single readable column, not a main/aside split: with no roles card the
   aside would be left floating beside nothing on five profiles out of six.
   Inside the wrap rather than being it, so the column shares its left edge with
   the name above — overriding .wrap's own max-width would centre the cards
   under a head that starts further left. */
.profile-body{max-width:46rem}
.profile-body .card + .card{margin-top:1.2rem}
.role-list{list-style:none;margin:.9rem 0 0;padding:0;display:flex;flex-direction:column}
.role-item{border-top:1px solid var(--line)}
.role-item:first-child{border-top:none}
/* The padding lives on the LINK, not the row: a row of dead space beside a
   clickable name is a target the reader aims at and misses. Bled out to the
   card's padding so the hover pill is wider than the words, and kept uniform on
   the first row too — a flush-to-the-heading first row would hover differently
   from the rest. */
.role-link{display:block;padding:.7rem .6rem;margin:0 -.6rem;border-radius:10px;color:inherit}
.role-link:hover{background:var(--tint-soft)}
.role-name{display:block;font-size:.95rem;font-weight:650;color:var(--ink)}
.role-name .role-payload{color:var(--muted);font-weight:600}
.role-link:hover .role-name{color:var(--blue)}
.role-link:hover .role-payload{color:var(--blue);opacity:.7}
.role-path{display:block;font-size:.78rem;color:var(--muted);margin-top:.2rem}
.role-path .sep{opacity:.45;padding:0 .1rem}

/* Clear of the card title: with the sub-line gone there is nothing between the
   heading and the first address, and the link's own padding is not a gap. */
.profile-links{display:flex;flex-direction:column;gap:.2rem;margin-top:.9rem}
.profile-link{display:flex;align-items:center;gap:.6rem;font-size:.9rem;color:var(--body);padding:.5rem .6rem;margin-left:-.6rem;border-radius:10px}
.profile-link:hover{background:var(--tint-soft);color:var(--ink)}
.profile-link svg.i{width:1.05rem;height:1.05rem;color:var(--muted);flex:none}
.profile-link:hover svg.i{color:var(--deep)}

/* The Expertise card: same row rhythm as the roles list above it — a topic line
   with its path underneath — plus the owner's controls at the row's right edge. */
.expertise-hint{font-size:.8rem;color:var(--muted);margin-top:.35rem}
/* The heading line: title left, the consent checkbox at the far right. Baseline
   rather than centre, so the word sits on the same line as the title it belongs
   to instead of floating in the middle of its cap height. */
.expertise-head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem}
.expertise-head h2{margin:0}
/* The consent switch, own profile only: a plain .check, quieter than the title
   it shares a row with. It is a setting and it is styled as one — the sentence
   explaining it lives in the control's `title`. */
.expertise-share{flex:none;font-size:.85rem;font-weight:500;color:var(--body)}
/* Hierarchy: the ONE rule on the card belongs to the section header — label +
   hairline make a shelf the rows hang from, so a section reads as a shape and
   inter-section air does the separating. Rows themselves are single quiet
   lines (name leads, path follows inline and muted) with no rules of their
   own: fifteen equally-heavy two-line rows chopped by fifteen hairlines is
   what buried the grouping in the first place. */
.expertise-group{margin-top:1.5rem}
.expertise-group:first-of-type{margin-top:1.1rem}
.expertise-group-head{
  font-size:.7rem;font-weight:650;letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted);margin:0;padding-bottom:.4rem;border-bottom:1px solid var(--line)}
.expertise-group .expertise-list{margin-top:.4rem}
.expertise-list{list-style:none;margin:.9rem 0 0;padding:0}
.expertise-item{display:flex;align-items:center;gap:.6rem;padding:.3rem 0}
.expertise-main{min-width:0;flex:1;display:flex;align-items:baseline;gap:.6rem;flex-wrap:wrap}
.expertise-topic{font-size:.92rem;font-weight:600;color:var(--ink)}
.expertise-path{font-size:.78rem;color:var(--muted)}
.expertise-path .sep{opacity:.45;padding:0 .1rem}
.expertise-note{flex-basis:100%;font-size:.8rem;color:var(--body)}
/* The level is a quiet fact, not a link: no blue. `expert` sits one shade
   firmer than the rest, so on a mixed profile the DIFFERENCES carry the ink,
   not the repetition. */
.expertise-level{font-size:.72rem;font-weight:550;color:var(--muted);flex:none}
.expertise-level.lv-expert{color:var(--body);font-weight:650}
.expertise-del{
  flex:none;width:26px;height:26px;border-radius:8px;display:grid;place-items:center;
  font-size:1rem;color:var(--muted)}
.expertise-del:hover{background:var(--red-bg);color:#B91C1C}
/* Both dropdowns borrow the admin page's combobox (.combo/.input/.options/
   .select-btn) so behaviour and styling stay one component. In the add row all
   three controls share the .input height (the button stretches — same bargain
   as .perm-row); on an entry row the level combo wears a compact coat of the
   same cut. The options panel is right-aligned and wider than its button, so
   the hints fit without the closed control paying for them. */
.expertise-add{display:flex;gap:.5rem;margin-top:.9rem;align-items:stretch}
.expertise-add .expertise-add-topic{flex:1;min-width:0}
.expertise-add-topic .opt .un{flex:none;max-width:60%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.level-combo{flex:none;width:10.5rem}
.level-combo .options{left:auto;right:0;width:19.5rem}
.expertise-item .level-combo{width:9rem}
.expertise-item .level-combo .select-btn{padding:.3rem .6rem;font-size:.78rem;border-radius:9px}
.expertise-error{font-size:.8rem;color:#B91C1C;margin-top:.5rem}

@media (max-width:560px){
  .profile-id{gap:1rem}
  .profile-facts{gap:.4rem .55rem}
}

/* ── Present ───────────────────────────────────────────────────────────────
   PowerPoint cleanup & translation (templates/present.html, static/js/present.js).
   A single focused column — one deck at a time — rather than the site's wider
   content grid. */
/* The three feature cards stack in one column across the full content width. */
.present-page{display:flex;flex-direction:column;gap:1.25rem}
/* Air between a card's intro text and its drop zone. */
.present-page .present-drop{margin-top:1.15rem}
.present-hidden{display:none !important}
/* Icons inside Present's buttons carry no size class of their own; without this
   an unsized <svg> takes its intrinsic box and blows the button up. */
.present-page .btn svg{width:1.05rem;height:1.05rem;flex:none}

/* Section headers: a title with an optional right-aligned action (Refresh). The
   meta is nowrap, so on a phone it has to be allowed onto its own line — held on
   the title's line it pushed the whole head past the viewport edge. */
.present-section-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.35rem 1rem}
.present-section-head h2{margin:0}
.present-section-sub{margin:.55rem 0 0;color:var(--body);font-size:.95rem;line-height:1.5}

/* Feature 1 — the official deck as ONE card: a small preview of slide 1, the
   facts both languages agree on, and a download button per language.

   It was a pair of full-width 16:9 posters. But DE and EN are one artifact in two
   renditions, and slide 1 is the same template in both — so the page opened on
   the same photograph printed twice, ~400px tall, while the only thing that
   actually differed (which language you want) was demoted to a caption and the
   workbench below was pushed off the fold. One card, one preview sized like the
   recognition cue it is, and the language as what it really is: the choice. */
.present-decks-meta{color:var(--muted);font-size:.85rem;white-space:nowrap}
.present-decks-meta .company-refresh{
  background:none;border:none;padding:0;margin:0;font:inherit;
  color:var(--blue);text-decoration:underline;cursor:pointer;
}
.present-decks-meta .company-refresh:hover{color:var(--blue-dark)}
.present-decks-meta .company-refresh:disabled{color:var(--muted);cursor:default}

.deck-tiles{margin-top:1.2rem}
.deck-tiles .present-spin{margin:1rem 0}
.deck-card{
  display:flex;flex-wrap:wrap;align-items:center;gap:1.4rem;
  padding:1.1rem 1.3rem;background:#fff;
  border:1px solid var(--line);border-radius:var(--radius-card);
  box-shadow:0 1px 3px rgb(5 17 55 / .06);
}
/* Big enough to say "yes, that's the corporate deck", small enough not to BE the
   page. The card no longer lifts on hover — it is not a link any more; the two
   buttons inside it are, and they carry their own affordance. */
.deck-preview{
  flex:none;width:13rem;aspect-ratio:16/9;overflow:hidden;
  border:1px solid var(--line);border-radius:10px;background:var(--surface);
}
.deck-shot{width:100%;height:100%;object-fit:cover;display:block}
/* Drawn fallback (no rendered thumbnail): a minimal itestra title slide. Sized in
   rem, not vw — the preview box is a fixed 15rem, so viewport-scaled type would
   overflow it on a wide screen. */
.deck-figure{
  width:100%;height:100%;padding:9% 8%;
  display:flex;flex-direction:column;justify-content:space-between;
}
.deck-brand{font-weight:800;font-size:.72rem;letter-spacing:-.02em}
.deck-rule{width:2rem;height:3px;background:var(--cyan);border-radius:2px;margin-bottom:.4rem}
.deck-title{font-size:.95rem;font-weight:750;letter-spacing:-.03em;line-height:1.12;max-width:14ch}
.deck-de .deck-figure{background:linear-gradient(135deg,var(--navy) 0%,#0A2158 78%,#0E3BD8 130%);color:#fff}
.deck-de .deck-brand span{color:var(--cyan)}
.deck-en .deck-figure{background:#fff;color:var(--ink)}
.deck-en .deck-brand span{color:var(--blue)}

/* A basis floor rather than min-width:0, so a long button row (the diverged case,
   where each one carries its own date and slide count) wraps to its own line
   instead of squeezing the name into two. */
.deck-body{flex:1 1 16rem;min-width:min(16rem,100%)}
.deck-name{margin:0;font-size:1.05rem;font-weight:700;letter-spacing:-.01em}
.deck-note{margin:.2rem 0 0;color:var(--muted);font-size:.87rem}
/* No margin-left:auto — the body's flex-grow already pushes these to the right
   edge on a shared line, and leaves them flush left on the line they wrap onto
   rather than stranding them under an empty half of the card. */
.deck-gets{flex:none;display:flex;flex-wrap:wrap;gap:.6rem}
.deck-get{text-decoration:none}
/* Per-language facts the shared line above could not claim for both. The dot is
   on the span so the label reads as the button and the numbers as an aside. */
.deck-get-meta{color:var(--muted);font-weight:400}
.deck-get-meta::before{content:"·";margin-right:.4rem}
.deck-get:hover .deck-get-meta{color:var(--blue)}
.company-empty{margin-top:1.1rem;color:var(--muted);font-size:.92rem}
/* Extra air before the workbench, so the two sections read as two. */
.present-bench{margin-top:1.4rem}

/* Feature 2 — the "various issues" inline toggle + the list it reveals. */
.present-help-link{
  background:none;border:none;padding:0;font:inherit;color:var(--blue);
  text-decoration:underline;cursor:pointer;
}
.present-help-link:hover{color:var(--blue-dark)}
.present-help-box{
  margin-top:.9rem;padding:.85rem 1.1rem;background:var(--tint-soft);
  border:1px solid var(--line);border-radius:.6rem;font-size:.9rem;
}
.present-help-lead{font-weight:650;color:var(--ink);margin:0 0 .45rem}
.present-help-box ul{margin:0;padding-left:1.15rem;display:flex;flex-direction:column;gap:.32rem;color:var(--body)}

.present-optional{color:var(--muted);font-weight:400}
.present-done{margin-top:1.2rem;display:flex;align-items:center;gap:.5rem;color:#15803D;font-weight:600}
.present-done svg{width:1.15rem;height:1.15rem;flex:none}
/* The message textarea inside the "Send to marketing" dialog. */
.present-dialog textarea.input{width:100%;resize:vertical;min-height:3.6rem}

/* Drop zone. The whole card is a target; ``is-dragover`` is toggled by
   present.js while a file is over it. */
.present-drop{
  display:flex;flex-direction:column;align-items:center;text-align:center;gap:.5rem;
  padding:2.4rem 1.5rem;background:#fff;
  border:2px dashed var(--line);border-radius:var(--radius-card);
  cursor:pointer;transition:border-color .15s,background .15s;
}
.present-drop:hover,.present-drop:focus-visible{border-color:var(--blue);outline:none}
.present-drop.is-dragover{border-color:var(--blue);background:var(--tint-soft)}
.present-drop-icon{width:2.4rem;height:2.4rem;color:var(--muted)}
.present-drop-lead{font-size:1rem;color:var(--ink)}
.present-drop-lead code{
  font-size:.85em;background:var(--surface);border:1px solid var(--line);
  border-radius:5px;padding:.05rem .3rem;
}
.present-drop-hint{font-size:.85rem;color:var(--muted);max-width:40ch}
/* Slim variant: a single-line dropline for the workbench, so the resting page
   stays compact and the loaded panel below carries the weight. */
.present-drop-slim{
  flex-direction:row;align-items:center;text-align:left;gap:.85rem;
  padding:.95rem 1.2rem;border-width:1.5px;border-radius:14px;
}
.present-drop-slim .present-drop-icon{width:1.35rem;height:1.35rem}
.present-drop-slim .present-drop-lead{font-size:.95rem;margin:0}
/* A button that reads as an inline link — the click-to-choose affordance
   inside the sentence. */
.present-link{
  color:var(--blue);font:inherit;text-decoration:underline;cursor:pointer;
  padding:0;background:none;border:none;
}
.present-link:hover{color:var(--blue-dark)}

/* Shared inline spinner — the activity pill's ring, reused. */
.present-spin{
  width:1.05rem;height:1.05rem;flex:none;border-radius:50%;
  border:2px solid var(--sky);border-top-color:var(--cyan);
  animation:rot .7s linear infinite;
}
.present-busy,.present-progress{
  display:flex;align-items:center;gap:.6rem;
  font-size:.92rem;color:var(--body);
}
.present-busy{margin-top:1.2rem}

.present-error{
  display:flex;align-items:center;gap:.6rem;margin-top:1.2rem;
  padding:.7rem .9rem;font-size:.9rem;
  color:var(--red-tx);background:var(--red-bg);
  border:1px solid #FCA5A5;border-radius:12px;
}
.present-error svg{width:1.15rem;height:1.15rem;flex:none}

.present-panel{margin-top:1.4rem}
/* The loaded deck reads as one bordered card: a header strip naming the file,
   then a body with the findings grid and the action row. */
.present-work{border:1px solid var(--line);border-radius:18px;overflow:hidden}
.present-work-head{
  display:flex;align-items:center;gap:.6rem;
  padding:1rem 1.3rem;background:var(--surface);border-bottom:1px solid var(--line);
}
.present-work-body{padding:1.15rem 1.3rem 1.35rem}

.present-file{display:flex;align-items:center;gap:.6rem;min-width:0}
.present-file-icon{width:1.4rem;height:1.4rem;flex:none;color:var(--cyan-dark)}
.present-file-name{font-weight:650;color:var(--ink);word-break:break-word}

.present-langs{margin-top:1rem;font-size:.92rem;color:var(--body)}
.present-langs .lang-names{font-weight:650;color:var(--ink)}
.present-langs .lang-note{
  margin-top:.5rem;font-size:.84rem;color:var(--muted);
  padding-left:.9rem;border-left:2px solid var(--line);
}
.present-langs .lang-note code{
  font-size:.85em;background:var(--surface);border:1px solid var(--line);
  border-radius:4px;padding:.02rem .28rem;
}

.present-work-body .present-langs{margin-top:0;margin-bottom:0}

/* The findings head: detected languages on the left, "Fix issues" (or, when
   there is nothing to fix, the outcome in its place) pushed to the right. The
   action sits with the list it repairs, not down in the deck-wide toolbar. */
.present-findings-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  margin-bottom:.4rem;
}
.present-findings-head .present-fixrow,
.present-findings-head .present-fixnote{margin-left:auto;flex:none}

/* Issue rows in a three-column findings grid (warnings and passes read together
   the way a checklist does). Each is icon + label with an optional trailing
   action (the title fix). Marker colour keys off the row's state class. */
.present-issues{
  margin-top:1.1rem;display:grid;grid-template-columns:repeat(3,1fr);
  gap:.5rem 1.2rem;
}
.issue-row{display:flex;align-items:center;gap:.55rem;font-size:.9rem;color:var(--body)}
.issue-row svg{width:1.1rem;height:1.1rem;flex:none}
.issue-ok svg,.issue-fixed svg{color:#15803D}
.issue-nok svg{color:#B45309}
.issue-nok{color:var(--ink);font-weight:550}
.issue-row .btn.sm{margin-left:.35rem;flex:none;white-space:nowrap}

/* The deck-wide intents in one tidy toolbar: the translate group and Send to
   marketing — left-aligned, one line, vertically centred, each button the same
   height. Collapses to a stack on narrow screens. (Fix issues is not here: it
   acts on the findings, so it rides in their head row above.) */
.present-acts{
  margin-top:1.25rem;padding-top:1.15rem;border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;align-items:center;gap:.7rem;
}
.present-fixrow{display:inline-flex;align-items:center}
.present-translate{display:inline-flex;align-items:center;gap:.5rem;flex-wrap:nowrap}
/* A fixed, sensible width so the select never balloons and the Translate button
   stays on the same line beside it. Two-class selector to beat `.input`'s
   width:100% (equal specificity would otherwise lose on source order). */
.present-translate .present-select{width:13rem;flex:0 0 auto}
.present-send{margin-left:.2rem}
.present-fixnote{font-size:.88rem;color:var(--muted);text-align:right}
.present-progress{margin-top:1.1rem}
/* Full card width: at 52ch this wrapped into a narrow column beside a lot of
   empty card, which read as a stray fragment rather than a footnote. */
.present-disclaimer{margin-top:1rem;font-size:.82rem;color:var(--muted)}
/* Two hints read as one block of small print, not two paragraphs. The :not()
   matters: a hidden sibling is still an adjacent one, so without it the
   Send-to-marketing line loses its air the moment the caveat is taken away. */
.present-disclaimer:not(.present-hidden) + .present-disclaimer{margin-top:.3rem}

.present-actions{margin-top:1.3rem}

/* Title dialog — narrow, on the shared .cg-dialog chrome. */
.present-dialog{width:min(30rem,calc(100vw - 2.5rem))}
.present-dialog-box{padding:1.25rem 1.4rem 1.15rem}
.present-dialog-body{margin-top:.4rem}
.present-dialog-cur{font-size:.88rem;color:var(--muted);margin-bottom:.9rem}
.present-dialog-cur-val{color:var(--ink)}

@media (max-width:820px){
  /* Stack, but cap the preview: full-bleed it would be the tall poster again. */
  .deck-card{flex-direction:column;align-items:stretch;gap:1rem}
  .deck-preview{width:100%;max-width:16rem}
  /* The basis above is a WIDTH floor for the row layout; once the card is a
     column that same 16rem is a height, and the buttons drift a quarter-screen
     below the text they belong to. */
  .deck-body{flex:0 1 auto;min-width:0}
  .present-issues{grid-template-columns:1fr 1fr}
}
@media (max-width:560px){
  .deck-gets{flex-direction:column;align-items:stretch}
  .deck-gets .deck-get{justify-content:center}
  .present-issues{grid-template-columns:1fr}
  .present-acts{align-items:stretch;flex-direction:column}
  /* No room for text and button side by side — the fix drops under the language
     line, left-aligned with it rather than stretched. */
  .present-findings-head{flex-direction:column;align-items:flex-start;gap:.6rem}
  .present-findings-head .present-fixrow,
  .present-findings-head .present-fixnote{margin-left:0;text-align:left}
  .present-translate{align-items:stretch;flex-direction:column}
  .present-translate .present-select{width:100%}
  .present-translate .btn{width:100%}
  .present-send{margin-left:0}
  .present-send{margin-left:0}
}

/* ── Locations widget (Company → Locations) ──────────────────────────────────
   One duotone card per office — the cityscape under a navy wash, white text,
   echoing the landing page's dark bento cards — and below the grid a white
   detail panel listing that office's people, employees and students apart. */

/* While the widget hydrates, hold roughly the height the card grid will take
   (≈4 rows of 150px cards on a desktop wrap) so the swap does not shove the
   rest of the page — the spinner box is otherwise a 3rem strip and Lighthouse
   books the difference as layout shift. */
.c-widget[data-widget="locations"] .c-widget-loading{min-height:40rem;max-width:none}

.locs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1rem}

.locs-card{
  position:relative;isolation:isolate;overflow:hidden;
  display:flex;flex-direction:column;justify-content:space-between;gap:1.2rem;
  min-height:150px;padding:1.05rem 1.2rem;border:0;border-radius:18px;
  background:var(--navy);text-align:left;font:inherit;cursor:pointer;
  transition:transform .15s ease,box-shadow .15s ease;
}
.locs-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-card)}
.locs-card.on{box-shadow:0 0 0 3px var(--blue)}
.locs-card-photo{
  position:absolute;inset:0;z-index:-1;background-size:cover;background-position:center;
  opacity:.65;filter:saturate(.7);transition:opacity .2s ease;
}
.locs-card:hover .locs-card-photo{opacity:.8}
.locs-card-shade{
  position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(5,17,55,.05),rgba(5,17,55,.72) 80%);
}

.locs-card-top{display:flex;align-items:baseline;justify-content:space-between;gap:.5rem}
.locs-city{
  display:block;font-size:1.1rem;font-weight:750;letter-spacing:-.01em;color:#fff;
  text-shadow:0 1px 8px rgba(5,17,55,.5);
}
.locs-card .locs-country{font-size:.78rem;color:rgba(255,255,255,.75)}
.locs-count{
  font-size:1.45rem;font-weight:750;letter-spacing:-.02em;line-height:1;
  text-align:right;color:#fff;text-shadow:0 1px 8px rgba(5,17,55,.5);
}
.locs-count small{
  display:block;margin-top:.18rem;font-size:.64rem;font-weight:550;
  letter-spacing:.04em;text-transform:uppercase;color:rgba(255,255,255,.75);
}

.locs-card-bottom{display:flex;align-items:center;justify-content:space-between;gap:.5rem}
.locs-stack{display:flex;align-items:center}
.locs-stack .locs-avatar{width:28px;height:28px;font-size:.6rem;border:2px solid rgba(255,255,255,.9)}
.locs-stack .locs-avatar+.locs-avatar{margin-left:-8px}
.locs-more{background:var(--tint);color:var(--deep)}
.locs-card .locs-split{font-size:.78rem;white-space:nowrap;color:rgba(255,255,255,.85)}
.locs-card .locs-split b{font-weight:700;color:#fff}

/* The detail panel — a plain white card, people in an auto-fill chip grid. */
.locs-detail{
  margin-top:1.4rem;padding:1.6rem 1.65rem;background:#fff;
  border:1px solid var(--line);border-radius:var(--radius-card);
  /* --chrome, not --top-h: picking an office scrolls this card to the top
     (widgets.js locationsWidget), and the reader still has the collapsed page
     head above them as well as the bar. Against --top-h the card landed 80px
     high — its own head row, the city name and the counts, sat behind the strip
     and the panel opened on "EMPLOYEES". Same offset the prose headings and the
     settings sections use for their anchor jumps. */
  box-shadow:var(--shadow-card);scroll-margin-top:calc(var(--chrome) + 1rem);
}
/* Top-aligned, not centred: the city name, the pills and the close button all
   sit on one line at the card's top padding, so the button's offset from the top
   matches its offset from the right, and the thumbnail hangs below the name
   rather than pulling everything to its own centre. */
.locs-detail-head{display:flex;align-items:flex-start;gap:.9rem;margin-bottom:1.4rem}
.locs-detail-thumb{flex:none;width:52px;height:52px;border-radius:14px;object-fit:cover}
.locs-detail-cityrow{display:flex;align-items:center;gap:.5rem}
.locs-detail-city{margin:0;font-size:1.35rem;line-height:1.2}
.locs-detail-map{flex:none;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;color:var(--muted);transition:color .12s,background .12s}
.locs-detail-map svg{width:17px;height:17px}
.locs-detail-map:hover,.locs-detail-map:focus-visible{color:var(--blue);background:var(--tint-soft)}
.locs-detail-id .locs-country{font-size:.85rem;color:var(--muted)}
/* The id block claims the free space in the header, so whatever follows it —
   the pills AND the close button, or on mobile the close button alone once the
   pills are hidden — is pushed to the top-right corner. Putting this here rather
   than on the pills is what keeps the close button pinned right when the pills
   are gone (see the max-width:560px block). */
.locs-detail-id{margin-right:auto}
/* min-height matches the 32px close button beside it, with the pills centred
   within — so the shorter chips share the button's vertical centre instead of
   top-aligning above it. */
.locs-detail-pills{display:flex;align-items:center;gap:.4rem;min-height:32px}
.locs-detail-close{
  flex:none;width:32px;height:32px;border:0;border-radius:999px;
  background:var(--tint-soft);color:var(--muted);font-size:1rem;line-height:1;cursor:pointer;
}
.locs-detail-close:hover{background:var(--tint);color:var(--ink)}

.locs-group+.locs-group{margin-top:1.35rem;padding-top:1.35rem;border-top:1px solid var(--line)}
.locs-group-label{
  margin:0 0 .55rem;font-size:.78rem;font-weight:650;
  text-transform:uppercase;letter-spacing:.1em;color:var(--muted);
}
.locs-group-count{color:var(--blue);font-weight:700;letter-spacing:0}

.locs-people{display:grid;grid-template-columns:repeat(auto-fill,minmax(215px,1fr));gap:.15rem .9rem}
.locs-person{
  display:flex;align-items:center;gap:.7rem;min-width:0;
  padding:.5rem .6rem;border-radius:12px;text-decoration:none;color:inherit;
}
/* The mount sits inside .prose, whose inline-link rule would underline every
   name — a chip is a component, not prose. */
.prose a.locs-person,.prose a.locs-person:hover{color:inherit;text-decoration:none}
.locs-person:hover{background:var(--surface)}
/* flex:none — the avatar is a fixed 36px disc; without it the flex row shrinks
   the avatar's width to make room for long titles, squashing the round photo
   into an ellipse (seen on students, whose titles run longer). */
.locs-person .locs-avatar{cursor:inherit;flex:none}
.locs-person-meta{display:flex;flex-direction:column;min-width:0}
.locs-person-name{
  font-weight:600;font-size:.93rem;line-height:1.3;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.locs-person-title{
  font-size:.8rem;line-height:1.35;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.locs-head-role{color:var(--blue);font-weight:650}

@media (max-width:560px){
  .locs-card .locs-split{display:none}
  .locs-detail{padding:1.2rem 1.1rem}
  .locs-detail-pills{display:none}
  /* The close button already top-aligns from the header's align-items:flex-start,
     so with the pills hidden here it stays pinned to the top-right corner. */
}

/* A profile's location fact links to its office on Company → Locations. Same
   quiet chip as the span next to it; the hover says it goes somewhere. */
a.profile-fact{color:inherit;text-decoration:none}
a.profile-fact:hover{color:var(--blue)}

/* ── Events landing (app.py events_landing, templates/events.html) ──────────────
   Reuses the site landing's date tiles and up-list for the upcoming/past rows;
   the hero and its CTA are the new pieces. Same tokens as everything else. */
/* The landing stacks plain white bands, and two full band paddings between
   Upcoming and Past read as a hole in the page — a later band keeps its own
   bottom padding but sits close to the one before it. */
body[data-app-kind="events"] section.band + section.band{padding-top:.6rem}
.events-col{max-width:720px}
.event-hero{display:flex;flex-wrap:wrap;gap:1.6rem;justify-content:space-between;
  align-items:flex-start;background:#fff;border:1px solid var(--line);
  border-radius:22px;padding:1.6rem 1.7rem;box-shadow:var(--shadow-card)}
.event-hero-body{flex:1 1 320px;min-width:0}
.event-season{font-size:.72rem;font-weight:650;letter-spacing:.1em;
  text-transform:uppercase;color:var(--blue)}
.event-hero h2{font-size:clamp(1.35rem,2.4vw,1.7rem);font-weight:750;margin:.3rem 0 .55rem}
.event-hero h2 a{color:var(--ink);text-decoration:none}
.event-hero h2 a:hover{color:var(--blue)}
.event-meta{display:flex;flex-wrap:wrap;gap:.45rem 1rem;font-size:.9rem;
  font-weight:600;color:var(--muted)}
.event-meta span{display:inline-flex;align-items:center}
.event-meta span + span::before{content:"·";margin-right:1rem;color:var(--line)}
.event-summary{font-size:.94rem;color:var(--muted);margin-top:.7rem;max-width:54ch}
.event-cta{flex:0 0 auto;min-width:220px;max-width:280px}
.event-cta-head{font-size:.78rem;font-weight:650;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);margin-bottom:.6rem}
.event-cta-list{display:flex;flex-direction:column;gap:.55rem}
.event-cta-list .btn{width:100%;justify-content:flex-start;text-align:left}
.event-allset{font-size:.98rem;font-weight:650;color:var(--ink);margin-bottom:.5rem}
/* A calendar-only tile is not a link — no hover lift, a lighter face. */
.up-item.is-calendar{cursor:default}
.up-item.is-calendar:hover{box-shadow:none}
a.up-item{text-decoration:none}
@media (max-width:560px){
  .event-hero{padding:1.3rem 1.2rem}
  .event-cta{min-width:0;max-width:none;width:100%}
}

/* ── the event manager's console (docs/events.md §7) ─────────────────────── */
.ev-new-form{margin-bottom:1.6rem}
.ev-new-fields{display:flex;gap:1rem;flex-wrap:wrap}
.ev-field{display:flex;flex-direction:column;gap:.35rem;flex:1 1 14rem;font-size:.86rem;font-weight:600}
.ev-field input{padding:.6rem .7rem;border:1px solid var(--line);border-radius:10px;font:inherit}
.ev-field input:focus{outline:2px solid var(--blue);outline-offset:1px;border-color:var(--blue)}
.ev-opt{font-weight:400;color:var(--muted)}
.ev-error{color:var(--red-tx);background:var(--red-bg);border-radius:10px;padding:.55rem .7rem;font-size:.86rem;margin-top:.9rem}

.ev-table-wrap{overflow-x:auto}
.ev-table{width:100%;border-collapse:collapse;font-size:.92rem}
.ev-table th{text-align:left;font-size:.74rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:650;padding:.55rem .7rem;border-bottom:1px solid var(--line)}
.ev-table td{padding:.75rem .7rem;border-bottom:1px solid var(--line);vertical-align:middle}
.ev-title{font-weight:650;text-decoration:none;color:var(--ink)}
.ev-title:hover{color:var(--blue)}
.ev-sub{display:block;font-size:.8rem;color:var(--muted)}
.ev-row-actions{white-space:nowrap;text-align:right}
.ev-row-actions .btn+.btn{margin-left:.4rem}

.ev-pill{display:inline-block;font-size:.72rem;font-weight:650;letter-spacing:.04em;padding:.2rem .55rem;border-radius:999px;text-transform:uppercase}
.ev-upcoming{background:var(--tint);color:var(--blue-dark)}
.ev-running{background:#DCFCE7;color:#166534}
.ev-past{background:#F1F1F4;color:var(--muted)}

.ev-head-actions{display:flex;gap:.5rem;align-items:center}
.ev-forms{display:flex;flex-direction:column;gap:1.1rem}
.ev-form-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
.ev-form-head h3{font-size:1.1rem;font-weight:700}
.ev-form-path{font-size:.8rem;color:var(--muted);word-break:break-all;margin-top:.15rem}
.ev-missing{color:var(--red-tx)}
.ev-form-count{text-align:right;white-space:nowrap}
.ev-count-n{font-size:1.5rem;font-weight:700;color:var(--blue-dark)}
.ev-count-of{color:var(--muted);font-weight:600}
.ev-count-label{display:block;font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
.ev-window{font-size:.82rem;color:var(--muted);margin-top:.5rem}
.ev-form-actions{justify-content:flex-start;flex-wrap:wrap}
.ev-locked{font-size:.8rem;color:var(--muted);align-self:center}

.ev-entries{margin-top:1rem;border-top:1px solid var(--line);padding-top:.9rem}
.ev-entries-status{font-size:.85rem;color:var(--muted);margin-bottom:.6rem}
.ev-entries-table-scroll{overflow-x:auto}
.ev-entries-table{width:100%;border-collapse:collapse;font-size:.82rem}
.ev-entries-table th{text-align:left;font-weight:650;color:var(--muted);padding:.4rem .55rem;border-bottom:1px solid var(--line);white-space:nowrap}
.ev-entries-table td{padding:.4rem .55rem;border-bottom:1px solid var(--line);vertical-align:top}

/* Assignments import (docs/events.md §4, §7) */
.ev-import{max-width:44rem}
.ev-import-lede{color:var(--muted);font-size:.9rem;line-height:1.5}
.ev-import-count{font-size:.85rem;color:var(--muted);margin:.4rem 0 .8rem}
.ev-import-form{display:flex;flex-wrap:wrap;align-items:center;gap:.7rem}
.ev-import-form input[type=file]{font-size:.85rem}
.ev-import-result{margin-top:.8rem;font-size:.88rem}
.ev-import-result.is-ok{color:var(--ok,#1a7f46)}
.ev-import-result.is-error{color:var(--danger,#b3261e)}
.ev-import-unmatched{margin-top:.8rem;font-size:.85rem}
.ev-import-unmatched-lede{color:var(--muted);margin-bottom:.3rem}
.ev-import-unmatched ul{margin:0;padding-left:1.2rem}
.ev-import-unmatched li{font-family:var(--mono,monospace);font-size:.82rem}

/* your-assignment widget (docs/events.md §5.3) — the per-user hotel/table lines */
.c-assignment{display:flex;flex-direction:column;gap:.3rem}
.c-assignment .as-line{margin:0;line-height:1.55}
.c-assignment .as-label{color:var(--muted)}
.c-assignment .as-value{font-weight:650}

/* ── event timeline & agenda widgets (docs/events.md §5.1, §5.2) ──────────────
   The Fri/Sat/Sun day skeleton and the plenum/parallel session grid, rendered
   from YAML (widgets.js drawTimeline / drawAgenda). One card look, the corporate
   blue for the active tab and the plenum accent, matching the clause generator
   and the locations cards. */
.c-timeline,.c-agenda{margin:0 0 1.6rem}
.evw-title{font-size:1.05rem;font-weight:650;color:var(--ink);margin:0 0 .7rem}
.evw-empty{font-size:.9rem;color:var(--muted);margin:0}

/* Day tabs: a segmented pill row, the active day filled blue. */
.evt-tabs{display:flex;flex-wrap:wrap;gap:.4rem;margin:0 0 .9rem}
.evt-tab{
  border:1px solid var(--line);background:#fff;color:var(--body);
  border-radius:999px;padding:.4rem .95rem;font:inherit;font-size:.88rem;
  font-weight:600;cursor:pointer;transition:background .12s,color .12s,border-color .12s;
}
.evt-tab:hover{background:var(--tint-soft);color:var(--ink)}
.evt-tab.on{background:var(--blue);border-color:var(--blue);color:#fff}
.evt-tab:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}

.evt-panel{
  border:1px solid var(--line);border-radius:16px;background:#fff;overflow:hidden;
}
.evt-table{width:100%;border-collapse:collapse;font-size:.93rem}
.evt-table tr + tr th,.evt-table tr + tr td{border-top:1px solid var(--line)}
.evt-time{
  text-align:left;vertical-align:top;white-space:nowrap;font-weight:650;
  color:var(--ink);padding:.7rem 1rem;width:1%;background:var(--surface);
  border-right:1px solid var(--line);
}
.evt-what{padding:.7rem 1rem;vertical-align:top}
.evt-row-title{display:block;color:var(--ink);font-weight:600}
.evt-row-detail{display:block;color:var(--muted);font-size:.86rem;margin-top:.15rem}

/* Agenda: a day is a stack of time slots; each slot is a time column beside its
   session(s). A plenum fills the width; parallel sessions wrap as cards. */
.ag-day + .ag-day{margin-top:1.4rem}
.ag-day-head{display:flex;align-items:baseline;gap:.6rem;margin:0 0 .6rem}
.ag-day-label{font-size:1rem;font-weight:650;color:var(--ink);margin:0}
.ag-day-date{font-size:.8rem;color:var(--muted)}
.ag-slot{display:flex;gap:.9rem;align-items:flex-start}
.ag-slot + .ag-slot{margin-top:.7rem}
.ag-slot-time{
  flex:0 0 7.5rem;font-weight:650;color:var(--ink);font-size:.9rem;
  padding-top:.85rem;white-space:nowrap;
}
.ag-slot-body{flex:1 1 auto;min-width:0}
.ag-parallel{display:flex;flex-wrap:wrap;gap:.7rem}
.ag-parallel .ag-session{flex:1 1 15rem}
.ag-session{
  border:1px solid var(--line);border-radius:14px;background:#fff;padding:.8rem .95rem;
}
.ag-session.is-plenum{background:var(--sky-soft);border-color:var(--sky)}
.ag-session-title{margin:0;color:var(--ink);font-weight:650;font-size:.95rem}
.ag-session-meta{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.5rem}
.ag-tag{
  display:inline-flex;align-items:baseline;font-size:.78rem;color:var(--body);
  background:var(--surface);border:1px solid var(--line);border-radius:999px;
  padding:.18rem .6rem;
}
.ag-tag-label{color:var(--muted);font-weight:600;margin-right:.15rem}
.ag-speaker{background:var(--tint-soft);border-color:var(--tint)}
.ag-session-desc{margin:.5rem 0 0;color:var(--muted);font-size:.86rem;line-height:1.5}

@media (max-width:560px){
  .ag-slot{flex-direction:column;gap:.35rem}
  .ag-slot-time{padding-top:0}
}

/* ── event-collage (past-event recap, docs/events.md §5.4) ─────────────────── */
.c-collage{margin:0 0 1.6rem}
/* The finished-event video as a prominent link above the grid. */
.cc-video{
  display:inline-flex;align-items:center;gap:.6rem;margin:0 0 1rem;
  padding:.6rem 1rem;border-radius:999px;text-decoration:none;
  background:var(--blue);color:#fff;font-weight:650;font-size:.95rem;
  border:1px solid var(--blue);
}
.cc-video:hover{background:var(--blue-dark);border-color:var(--blue-dark)}
.cc-video-play{
  display:inline-flex;align-items:center;justify-content:center;
  width:1.5rem;height:1.5rem;border-radius:50%;
  background:rgba(255,255,255,.22);font-size:.7rem;line-height:1;
}
/* The finished-event video STANDALONE — when there are no photo tiles to weave
   it into: the same thumbnail-with-play-button, capped so it never dwarfs the
   page. A <button>, so every default of that element is reset here. */
.cc-video-thumb{
  position:relative;display:block;width:100%;max-width:720px;margin:0 0 1rem;
  padding:0;border:1px solid var(--line);border-radius:14px;overflow:hidden;
  background:#000;cursor:pointer;
}
.cc-video-thumb .cc-img{width:100%;height:auto}
/* Span masonry: photos keep their natural aspect ratio by spanning thin (6px)
   grid rows — widgets.js computes each tile's span from its loaded poster's
   dimensions, so nothing is cropped. `dense` lets short and tall tiles pack
   flush. The default span holds a tile's place until its poster loads. */
.c-collage-grid{
  display:grid;gap:.5rem;grid-template-columns:repeat(3,1fr);
  grid-auto-rows:6px;grid-auto-flow:dense;
}
.cc-tile{
  margin:0;overflow:hidden;border-radius:12px;border:1px solid var(--line);
  background:var(--surface);grid-row:span 20;
}
.cc-img{
  display:block;width:100%;height:100%;max-width:100%;object-fit:cover;
}
/* Each photo opens its full-size original in a new tab. The anchor has to fill
   the tile — the figure's height is a ROW SPAN computed from the photo's aspect
   ratio, so an inline anchor would sit on a text baseline inside it and leave a
   sliver of the tile unclickable along the bottom. No cursor of its own: an
   anchor with an href already draws the hand, and that is what the reader is
   being told — this is a link, like every other one on the page. */
.cc-open{display:block;width:100%;height:100%}
/* "See all", closing the grid. The page's own arrow-link, so it carries
   the same colour, weight and arrow nudge as every other one; only the space
   above it belongs here.

   Qualified with .prose, and that is the specificity trap this file keeps
   hitting (README, "CSS"; see .btn and .br-item above): a recap page's collage
   mounts inside .prose, where `.prose a` (0,1,1) beats `.arrow-link` (0,1,0)
   and dresses the link as inline prose — underlined, which no arrow-link on the
   site is. The colour survives either way, both being --blue; the underline does
   not, so it is turned off where it can actually win. */
.cc-all{margin-top:.9rem}
.prose a.cc-all,.prose a.cc-open{text-decoration:none}
/* The video as the grid's lead tile: double width, woven into the flow — the
   hand-picked thumbnail with a play button, a <button> opening the lightbox. */
.cc-tile-video{
  grid-column:span 2;background:#000;position:relative;
  padding:0;cursor:pointer;font:inherit;
}
.cc-tile-video .cc-img{opacity:.94;transition:opacity .18s ease}
.cc-tile-video:hover .cc-img{opacity:1}
/* The play button, over whichever thumbnail shape carries it. */
.cc-play{
  position:absolute;left:50%;top:50%;translate:-50% -50%;
  width:3.4rem;height:3.4rem;border-radius:50%;pointer-events:none;
  background:rgba(5,17,55,.55);backdrop-filter:blur(2px);
  transition:scale .18s ease;
}
.cc-play::after{
  content:"";position:absolute;left:55%;top:50%;translate:-50% -50%;
  border-style:solid;border-width:.6rem 0 .6rem 1rem;
  border-color:transparent transparent transparent #fff;
}
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .cc-tile-video:hover .cc-play,.cc-video-thumb:hover .cc-play{scale:1.08}
}
/* The lightbox: the original video, centred, on the app's dialog backdrop. */
.cc-lightbox{
  border:none;padding:0;margin:auto;background:transparent;overflow:visible;
}
.cc-lightbox::backdrop{background:rgba(5,17,55,.72);backdrop-filter:blur(3px)}
.cc-lightbox video{
  display:block;max-width:min(76rem,calc(100vw - 3rem));
  max-height:calc(100vh - 5rem);border-radius:12px;background:#000;
  box-shadow:var(--shadow-pop);
}
.cc-lightbox-close{
  position:absolute;top:-2.6rem;right:-.4rem;width:2.2rem;height:2.2rem;
  border:none;border-radius:8px;background:transparent;color:#fff;
  font-size:1.6rem;line-height:1;cursor:pointer;display:grid;place-items:center;
}
.cc-lightbox-close:hover{background:rgba(255,255,255,.16)}
@media (max-width:560px){
  .c-collage-grid{grid-template-columns:repeat(2,1fr)}
}

/* ================= corpus review (docs/corpus.md §8) =================
   The curator's desk on the admin page: a queue of mined SVN candidates, each
   waiting for one human decision. The card is deliberately not a table — a
   decision here is made from the evidence (the summary, the keywords, the
   customer-specific flag AND what the miner saw to raise it, the real SVN
   path), and a row of cells would ask a curator to clear material from a
   truncated cell. So one candidate is one block, wide as the card. */
.corpus-pills{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}
/* The endpoints-are-missing notice. Red, and it stays: an inert card that looks
   like an empty queue is the one failure mode this card must not have. */
.corpus-offline{
  font-size:.85rem;color:var(--red-tx);background:var(--red-bg);
  border-radius:12px;padding:.7rem .9rem;margin:0 0 1rem;
}
.corpus-offline[hidden]{display:none}
.corpus-transfer{display:flex;gap:.6rem;flex-wrap:wrap;align-items:center}
/* The state.json download is an <a>, so it has no disabled state of its own —
   and it must look refused when the routes behind the card are not there, or it
   reads as a link that simply did nothing. */
.corpus-transfer .btn[aria-disabled="true"]{opacity:.55;cursor:not-allowed}
.corpus-queue-bar{display:flex;gap:.8rem;flex-wrap:wrap;align-items:center;margin-bottom:1rem}
/* The filter selects carry their own name in their default option, so they sit
   in the bar unlabelled. Sized to their content, not to the widest option: a
   select that resizes as you choose makes the whole bar jump. */
select.corpus-filter{width:auto;padding:.35rem 1.6rem .35rem .6rem;font-size:.85rem}
.corpus-scope{display:flex;align-items:center;gap:.5rem;font-size:.82rem;font-weight:600;color:var(--ink)}
/* The one select on the page that must not fill its column: it sits inline in a
   toolbar, not in a form grid. */
.corpus-scope select.input{width:auto;padding:.35rem 1.6rem .35rem .6rem;font-size:.85rem}
.corpus-scope[hidden]{display:none}
/* The search takes the slack on a wide bar and the whole width on a narrow one:
   it is the only control here whose useful size depends on what is typed into
   it, and the two selects beside it are fixed labels. */
.corpus-search{flex:1 1 14rem;min-width:0;font-size:.85rem;padding:.35rem .6rem}
/* A filter that is ON has to look pressed, not merely focused — it hides rows,
   and a hidden row nobody remembers filtering out is a backlog that looks done. */
.corpus-toggle[aria-pressed="true"],.corpus-toggle.is-on{
  background:var(--amber-bg);border-color:var(--amber-tx);color:var(--amber-tx);font-weight:600}
.corpus-filter-note{margin:-.4rem 0 .9rem;color:var(--muted)}
.corpus-filter-note[hidden]{display:none}
/* The archiving fields. Both grow, because both hold prose whose useful width
   depends on what is typed: an SVN scope is a long path a typo hides in, and the
   reason is a sentence the next curator reads. `flex-basis` differs so the path
   wins the space on a narrow bar — it is the one that must be checkable. */
.corpus-retire-field{flex:1 1 20rem;min-width:0;font-size:.85rem;padding:.35rem .6rem}
#corpus-retire-reason{flex-basis:14rem}
.corpus-collapse{margin-top:1.3rem;border-top:1px solid var(--line);padding-top:1.1rem}
/* The two report tables carry deck filenames and full SVN paths, neither of
   which wraps into a phone-width column. They scroll inside their own box so the
   page never does — the card is the width of the screen either way. */

/* One desk, one pile. The heading names the ROLE and where it sits in the chart,
   never a person: a clearing role is held by two people as often as by one, and
   picking one of them as the label would invent a hierarchy the chart does not
   state. The people are named under it, which is a different claim. */
.cq-group + .cq-group{margin-top:1.6rem}
.cq-desk-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;
  font-size:.9rem;font-weight:700;color:var(--ink);margin:0 0 .15rem}
.cq-desk-path{font-size:.75rem;font-weight:500;color:var(--muted)}
.cq-desk-people{margin:0 0 .6rem;font-size:.78rem;color:var(--muted)}
/* Somebody else's desk. The entry is still fully readable — §8.2 makes the whole
   corpus visible — so this replaces the buttons and nothing else. */
.cq-readonly{margin:.9rem 0 0;font-size:.78rem;color:var(--muted)}

/* The queue is a LIST of collapsed rows, one per candidate: a desk holds 71 of
   these on the real corpus, and a card each made the page too long to read as a
   queue. Not a table either — the title is a deck filename and the longest thing
   on the row, while `kind` and the signals are chips a few characters wide, so
   columns spent the width in exactly the wrong places and scrolled sideways.
   The title takes the full width and what qualifies it sits under it.

   What a decision is SCANNED on is in the row; what it is MADE on is in
   `.cq-detail`, one click away and never dropped (§8.2). */
.cq-desk-count{font-size:.75rem;font-weight:600;color:var(--muted);
  background:var(--tint-soft);border-radius:999px;padding:.1rem .5rem}
.cq-list{list-style:none;margin:0;padding:0;
  border:1px solid var(--line);border-radius:12px;overflow:hidden;background:#fff}
.cq-entry + .cq-entry{border-top:1px solid var(--line)}
.cq-row{display:flex;align-items:flex-start;gap:.75rem;
  padding:.6rem .8rem;cursor:pointer;transition:background .12s}
.cq-row:hover{background:var(--tint-soft)}
.cq-row:focus-visible{outline:2px solid var(--blue);outline-offset:-2px}
.cq-entry.is-open > .cq-row{background:var(--tint-soft)}
/* The title line takes the slack; min-width:0 is what lets a long filename wrap
   inside the flex item instead of widening the row past the card. */
.cq-row-main{flex:1;min-width:0}
.cq-row-title{display:block;font-size:.9rem;font-weight:600;color:var(--ink);
  overflow-wrap:anywhere}
.cq-row-sub{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;margin-top:.2rem}
/* One line, one font: the source sits in it as prose, not as a code chip. It is
   a name a curator reads, not a token they retype. */
.cq-row-who{font-size:.76rem;color:var(--muted);overflow-wrap:anywhere}
.cq-row-sub .pill{font-size:.68rem;padding:.08rem .45rem}
.cq-detail-meta{margin:.7rem 0 0;font-size:.76rem;color:var(--muted)}
/* The verdicts never wrap and never move: this is the one place a curator's
   cursor goes on every row, and a queue where they sit at different x positions
   is a queue that has to be aimed at rather than worked through. */
.cq-row-act{flex:none;display:flex;align-items:center;gap:.3rem}
.cq-row-readonly{font-size:.72rem;color:var(--muted);font-style:italic;white-space:nowrap}
/* The decided and missing lists share the row but not its behaviour: there is
   nothing behind them to open, so they must not offer the hover and the pointer
   that say there is. */
.cq-row-static{cursor:default}
.cq-row-static:hover{background:transparent}
.cq-row-reason{margin:.3rem 0 0;font-size:.8rem;color:var(--body)}
.cq-row-path{margin:.3rem 0 0;font-size:.75rem;color:var(--muted);overflow-wrap:anywhere}
/* Square, so the two verdicts read as a pair of controls rather than as two
   buttons that happen to be short. The word is in `title`/`aria-label`. */
.btn.icon-only{display:inline-flex;align-items:center;justify-content:center;
  padding:0;width:1.9rem;height:1.9rem;border-radius:8px}
.btn.icon-only svg.i{width:1rem;height:1rem}
@media (hover:hover){
  .cq-row .row-action{opacity:.45;transition:opacity .12s}
  .cq-row:hover .row-action,.cq-row:focus-within .row-action,
  .cq-entry.is-open .row-action,.cq-row .row-action:focus-visible{opacity:1}
}
.cq-detail{background:var(--tint-soft);padding:.2rem .85rem 1rem;
  border-top:1px solid var(--line)}
.cq-detail[hidden]{display:none}
.cq-topic{font-size:.82rem;color:var(--body);margin-top:.5rem}
/* Why the entry is back in the queue after being cleared once. A sentence, not a
   pill: pills are nowrap, and this one has to explain itself. */
.cq-recheck{margin:.5rem 0 0;font-size:.8rem;color:var(--amber-tx)}
.cq-summary{font-size:.88rem;color:var(--body);margin:.7rem 0 0}
.cq-keys{display:flex;flex-wrap:wrap;gap:.35rem;margin-top:.6rem}
.cq-key{font-size:.72rem;font-weight:600;border-radius:999px;padding:.18rem .6rem;
  background:var(--tint-soft);color:var(--deep)}
.cq-keys-none{font-size:.78rem;color:var(--amber-tx)}
/* The flag is the reason a curator is being asked at all, so it is the one
   block on the entry with a fill. The evidence is inside it: a flag without the
   sentence that raised it is not reviewable, only alarming. */
.cq-flag{margin-top:.7rem;font-size:.82rem;color:var(--amber-tx);background:var(--amber-bg);
  border-radius:10px;padding:.55rem .75rem}
.cq-clean{margin:.7rem 0 0;font-size:.75rem;color:var(--muted)}
.cq-paths{list-style:none;margin:.7rem 0 0;padding:0;display:flex;flex-direction:column;gap:.35rem}
.cq-paths li{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;
  font-size:.78rem;overflow-wrap:anywhere}
.cq-paths a{color:var(--blue);font-weight:600}
.cq-path-text{color:var(--body)}
.cq-paths-none,.cq-path-cell{font-size:.78rem;color:var(--muted);overflow-wrap:anywhere}
.cq-reason-cell{font-size:.8rem;color:var(--muted);overflow-wrap:anywhere}
.cq-when{font-size:.75rem;color:var(--muted)}
/* Stacked is the base — on a phone the reason field and the two verdicts each
   want the full width, and a media query adds no specificity to undo a row with
   (README, "CSS"). The row is the min-width enhancement. */
.cq-actions{display:flex;flex-direction:column;gap:.6rem;margin-top:.9rem}
.cq-err{font-size:.8rem;color:var(--red-tx);margin-top:.5rem}
.cq-err:empty{display:none}
.cq-empty{font-size:.88rem;color:var(--muted);font-style:italic;margin:0}
@media (min-width:44rem){
  .cq-actions{flex-direction:row;align-items:center}
  /* The reason takes the slack: it is prose, the buttons are fixed labels. */
  .cq-actions .cq-reason{flex:1;min-width:0}
}
