/* =====================================================================   Prime Foundry — F2 "Daylight (brand)" design system, for the base apps.

   Sections 1-9 are COPIED VERBATIM from the cockpit's source of truth:
     Allonsygmbh/foundry  app/assets/stylesheets/tokens.css
                          app/assets/stylesheets/brand-fonts.css
                          app/assets/stylesheets/inter-font.css
   Do not "improve" those values here. If the cockpit's tokens change, re-copy.
   The base apps must read as the same product as the cockpit.

   Section 10 is the only original CSS: the handful of classes the boards need
   (stat trio, #N chip, to-do rows, prose bodies). It derives every colour,
   radius and font from the tokens above — no new palette, no ad-hoc values.
   ============================================================================ */

/* ---- 1. brand type: Source Serif 4 (display) + IBM Plex Mono (data) + Inter (body)
        The same Google Fonts import the cockpit's brand-fonts.css / inter-font.css use. ---- */
@import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,600;8..60,700&family=IBM+Plex+Mono:wght@400;500&family=Space+Grotesk:wght@500;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');

/* ---- 2. palette + type tokens (verbatim) ---- */
:root {
  /* SAY WHAT THIS PAGE IS. The Cockpit is a light design — every token below
     assumes it — but it never declared so, and a browser that is not told
     assumes it may help. Brave and Chromium's auto-dark then force-darken the
     page by heuristic, inverting some colours and not others: the owner's
     selected Brain chip kept its navy fill while its text flipped dark, and the
     label he had chosen became unreadable (2026-08-07). One declaration ends
     the guessing for every page that loads these tokens. */
  color-scheme: light;

  /* Palette — paper base, white surfaces, hairline neutrals, navy accent, status set */
  --pf-paper:     #FAF3E3;  /* app background */
  --pf-bg:        #FAF3E3;  /* alias — app background */
  --pf-surface:   #FFFFFF;  /* panels / cards */
  --pf-surface-2: #F2ECDF;  /* raised / hover / active nav */
  --pf-line:      #E7E3DA;  /* hairlines */
  --pf-line-2:    #D9D3C5;  /* stronger divider */
  --pf-ink:       #1C1E21;  /* primary text */
  --pf-muted:     #545A61;  /* secondary text */
  --pf-faint:     #8A9099;  /* tertiary / labels */
  --pf-navy:      #122F4D;  /* accent navy */
  --pf-navy-2:    #0C2138;  /* accent navy hover */
  --pf-accent:    #122F4D;  /* alias — primary action */
  --pf-accent-2:  #0C2138;  /* alias — accent hover */
  --pf-green:     #274F42;
  --pf-mint:      #B7DFD2;
  /* A6 anvil brand accents (2026-07): brass = secondary detail (never body
     text on cream), ember = pure signal (glow/working states, never text) */
  --pf-brass:     #BA9745;
  --pf-ember:     #E8641E;
  /* status: live/online = green, casting = amber, attention = red */
  --pf-ok:        #274F42;  /* online / authed / ready  (live) */
  --pf-warn:      #B4732A;  /* pending / casting        (cast) */
  --pf-bad:       #B23A3A;  /* offline / logged-out     (dead) */
  --pf-radius:    12px;
  --pf-radius-sm: 8px;
  --pf-disp: "Source Serif 4", Georgia, "Times New Roman", serif;
  /* Brand wordmark only (2026-08-19, Hendriks Typo-Pick): Space Grotesk.
     Headings stay Source Serif — the pick was about the logo lockup. */
  --pf-brand-type: "Space Grotesk", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --pf-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --pf-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  /* Desk / PF-Chat night panel — copied verbatim from the Hub's tokens.css so
     the Cockpit's Desk and the Hub's Desk are the same surface, not a
     lookalike. Brass + ember already live above; this completes the set. */
  --pf-night:       #0C2138;  /* desk panel background */
  --pf-night-ink:   #F2ECDC;  /* light text on night */
  --pf-night-muted: #93A0B0;  /* secondary text on night */
  --pf-night-line:  color-mix(in srgb, #FAF3E3 13%, transparent); /* hairline on night */
  --pf-night-well:  #081827;  /* code well inside perm card */
}

/* ---- 3. base (verbatim) ---- */
/* No reset ships with these pages, so the browser's default 8px body margin let
   the paper background show above and beside the full-bleed topbar — the header
   read as a floating white card with cream down both sides, and the brand mark
   sat 8px from the screen edge instead of on the content gutter (owner: "der
   Hammer sieht auf Mobile abgeschnitten aus ganz links", 2026-07-29). The Hub
   carried this line in its own overlay; it belongs to both. */
body.pf { margin: 0; }
.pf { background: var(--pf-paper); color: var(--pf-ink); font-family: var(--pf-sans);
      font-size: 14px; line-height: 1.55; -webkit-font-smoothing: antialiased; }
.pf-mono { font-family: var(--pf-mono); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.pf-muted { color: var(--pf-muted); }
.pf-label { font-family: var(--pf-sans); font-size: 12px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; color: var(--pf-navy); }

/* ---- 4. brand lockup: anvil badge + Space Grotesk wordmark (verbatim) ---- */
.pf-wrap { max-width: 1160px; margin: 0 auto; padding: 0 30px; }
.pf-brand { display: inline-flex; align-items: center; gap: 12px; text-decoration: none;
            font-family: var(--pf-brand-type); font-weight: 700; font-size: 19px; letter-spacing: -.03em; color: var(--pf-ink); }
.pf-brand .pf-word-2 { color: var(--pf-navy); font-weight: 500; }
.pf-brand .pf-mark, .pf-brand img.pf-mark { width: auto; height: 30px; flex: none; display: block; }

/* ---- 5. top-bar cockpit shell (verbatim) ---- */
.pf-topbar-wrap { background: var(--pf-surface); border-bottom: 1px solid var(--pf-line); }
.pf-bar { display: flex; align-items: center; justify-content: space-between; gap: 18px; height: 72px; }
/* Header C (copied from the Hub): the nav sits left, next to the brand, and the
   account chip is the only thing on the right. `margin-right: auto` is what
   pushes it there — the bar's space-between then has nothing left to spread. */
.pf-topnav { display: flex; gap: 2px; margin-right: auto; margin-left: 14px; }
.pf-topnav a { font-family: var(--pf-sans); font-size: 13px; font-weight: 500; color: var(--pf-muted);
               text-decoration: none; padding: 8px 13px; border-radius: 8px; }
/* Above 900px the pills stop giving way to the chip beside them.
 *
 * They were what gave way to a long Brain topic. The chip's label was capped
 * only at 42vw — about 600px on a desktop, wide enough to render a whole
 * 80-character topic (CodingVm::TOPIC_MAX) — so "To-do" broke across two lines,
 * the pill row grew from 36px to 56px, and every pill in the bar moved because
 * a Brain had been renamed: "wenn der Topic-Namen oben zu lang ist, springt die
 * gesamte Navigationsleiste" (owner, with screenshot, 2026-09-02).
 *
 * 900px, and that number is measured rather than picked. The bar hands over to
 * the bottom tab bar at 760px, so between 761 and ~880 the full nav is on
 * screen in a window it does not fit: brand 147 + pills 460 + the chip's own
 * irreducible ~120 (dot, "N active", caret) + gutters comes to about 820px.
 * Pinning the pills there makes no room, it only moves the shortfall onto the
 * chip, which then hangs off the right edge — which is what the CI breakpoint
 * audit sees at tablet-768 as L1-OVERFLOW. So below 900px the bar keeps the
 * behaviour it has always had and the pills absorb the squeeze; above it, where
 * there is room for all three, they hold their width and their line. */
@media (min-width: 900px) {
  .pf-topnav { flex: none; }
  .pf-topnav a { white-space: nowrap; }
}
.pf-topnav a:hover { color: var(--pf-ink); background: var(--pf-surface-2); }
.pf-topnav a.active { color: var(--pf-navy); background: var(--pf-surface-2); }
.pf-acct { position: relative; display: flex; align-items: center; gap: 12px; font-family: var(--pf-mono); font-size: 12px; color: var(--pf-muted); min-width: 0; flex-shrink: 1; }
/* Vertical padding only — the horizontal inset comes solely from .pf-wrap, so
   the content column lines up with the topbar brand (the Hub's rule, verbatim). */
/* Longhand on purpose: <main class="pf-wrap pf-content"> carries BOTH
   classes, and the shorthand form of this rule (source order winner) was
   zeroing .pf-wrap's horizontal gutters — content sat flush against the
   screen edge on every phone (owner report, Hub settings, 2026-07-29). */
.pf-content { padding-top: 30px; padding-bottom: 60px; box-sizing: border-box; }
/* border-box above: both shells stretch .pf-content to width:100%, and a
   content-box 100% plus the gutters is a page that scrolls sideways. */

/* ---- the two doors out: Hub, Sign out ----
   This was an avatar chip over a popover, ported from the Hub. It hid the only
   link back to the Hub behind a click on an anonymous circle, and the owner
   duly failed to find it. Both actions are now labelled links in the bar, so
   there is no open state to style and no controller to run: a header that
   cannot be closed cannot lock anyone in.

   Both read at --pf-muted; Sign out separates itself on hover, where it turns
   --pf-bad. Leaving is the rarer intent, but it does not get to be quieter than
   the rest of the chrome to say so — --pf-faint is under the AA floor for text,
   and a door is the last thing to render at 2.91:1. A permanently red word in
   the bar reads as an error the page is reporting rather than an offer, so the
   colour waits for the pointer. */
/* Element-qualified on purpose: this skin sits ABOVE the button base in the
   file, and (0,1,1) is what lets a quiet bar action stay quiet under the
   later (0,1,0) base rule. */
a.pf-baract, button.pf-baract { gap: 7px; font-weight: 500; color: var(--pf-muted); min-height: 0;
             padding: 8px 12px; border-radius: 8px;
             border-color: transparent; background: none; box-shadow: none; }
.pf-baract svg { width: 16px; height: 16px; stroke: currentColor; fill: none; flex: none; }
.pf-baract:hover { color: var(--pf-ink); background: var(--pf-surface-2); }
.pf-baract-out:hover { color: var(--pf-bad); background: color-mix(in srgb, var(--pf-bad) 7%, transparent); }

/* The plain bar links wait while the account menu is expected — see the inline
   script in each page's head. brainmenu.js removes .pf-acct-plain the moment it
   takes over, so this hides nothing once the control is there; if the script
   never runs, the class comes off after 2s and the doors are visible again. */
.pf-menu-pending .pf-acct-plain { visibility: hidden; }
/* The pair sits tighter than the chip did: two words need less air than an
   avatar, and .pf-acct's 12px gap between them reads as two separate widgets. */
.pf-acct-plain { gap: 4px; }
/* A door that wraps mid-word is a door that reads broken: the bar's brand and
   pills never wrap — the bar itself is what gives way (flex gaps shrink). */
.pf-brand > span { white-space: nowrap; }
.pf-baract { white-space: nowrap; }

/* ---- quiet footer (header C, copied from the Hub) ---- */
.pf-foot { border-top: 1px solid var(--pf-line); }
.pf-foot-inner { display: flex; align-items: center; justify-content: space-between; gap: 14px;
                 padding: 16px 0 18px; font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); }
.pf-foot a { color: var(--pf-muted); text-decoration: underline; text-underline-offset: 2px; }
.pf-foot a:hover { color: var(--pf-muted); }

/* ---- mobile bottom tab bar (header C, copied from the Hub) ----
   Fixed app-style primary nav with >=44px targets and safe-area padding.
   Hidden on desktop; never rendered on the immersive Desk (the chat page's
   own back arrow is the way out there, exactly as in the Hub). */
.pf-tabbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 40; display: none;
             background: var(--pf-surface); border-top: 1px solid var(--pf-line-2);
             padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
             box-shadow: 0 -14px 30px -24px color-mix(in srgb, var(--pf-ink) 40%, transparent); }
.pf-tabbar-inner { display: flex; max-width: 560px; margin: 0 auto; }
.pf-tab { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
          min-height: 48px; text-decoration: none; color: var(--pf-muted); border-radius: 10px; }
.pf-tab svg { width: 22px; height: 22px; stroke: currentColor; fill: none; display: block; }
.pf-tab-label { font: 600 10px/1 var(--pf-sans); letter-spacing: .01em; }
.pf-tab.active { color: var(--pf-navy); }
.pf-tab:active { background: var(--pf-surface-2); }
/* The tab bar's own badge: same meaning as the nav pill's, sized for an icon. */
/* The tab bar's own badge. It cannot inherit the pill badge's look — that rule
   is scoped to `.pf-topnav a` — so it is stated here in full, as a count on the
   icon's shoulder rather than a chip after a label there is no room for. */
.pf-tab-ico { position: relative; display: block; }
.pf-tab .nav-badge { position: absolute; top: -5px; left: 12px; margin: 0;
                     min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box;
                     border-radius: 999px; background: var(--pf-navy); color: var(--pf-paper);
                     font: 600 10px/16px var(--pf-sans); text-align: center;
                     border: 2px solid var(--pf-surface); }

/* ---- 6. primitives (verbatim) ---- */
.pf-card { background: var(--pf-surface); border: 1px solid var(--pf-line); border-radius: var(--pf-radius);
           padding: 18px 20px; box-shadow: 0 14px 30px -24px rgba(28,30,33,.35); }
/* ============================  THE BUTTON  ================================
   One component, every pill (owner order 2026-07-31, after two rounds of
   "identical" buttons rendering at different sizes). The BASE below owns the
   box model, layout, typography, floors, focus ring, press and busy states —
   for .pf-btn AND for every legacy family name that still appears in markup
   (aliased into the same selector). A family may keep a SKIN (colors, its
   own radius or padding) further down or in chat.css, but never its own box
   model or type again. If a new button is not a .pf-btn or one of these
   aliases, it is a bug by definition. The Hub renders this same component
   through ButtonComponent (ViewComponent): one recipe, two renderers. */
.pf-btn, .need-btn, .chat-starter, .perm-btn, .pf-baract,
.pf-voice-btn, .pf-model-btn, .chat-signedout-fix {
  box-sizing: border-box; appearance: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 36px; padding: 8px 14px;
  border: 1px solid var(--pf-line-2); border-radius: var(--pf-radius-sm);
  background: linear-gradient(180deg,#fff,#f3f5f7); color: var(--pf-ink);
  font: 600 13px/1 var(--pf-sans); text-decoration: none; white-space: nowrap;
  cursor: pointer; box-shadow: inset 0 1px 0 #fff;
  transition: background-color .15s ease, border-color .15s ease,
              color .15s ease, transform .12s ease;
}
/* An icon inside a pill is a flex item, never inline text: display:block
   removes the baseline slot (and its descender gap) entirely, so an icon can
   only ever sit where align-items puts it — dead centre. */
.pf-btn > svg { display: block; flex: none; }
.pf-btn:hover { border-color: var(--pf-faint); }
.pf-btn:focus-visible, .need-btn:focus-visible, .chat-starter:focus-visible,
.pf-baract:focus-visible, .pf-voice-btn:focus-visible, .pf-model-btn:focus-visible {
  outline: none; border-color: var(--pf-navy);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pf-navy) 15%, transparent);
}
.pf-btn:disabled, .need-btn:disabled, .pf-model-btn:disabled {
  opacity: .5; cursor: not-allowed; }
/* faces */
.pf-btn-primary { background: var(--pf-navy); border-color: var(--pf-navy); color: #fff;
                  box-shadow: 0 8px 20px -8px rgba(18,47,77,.55); }
.pf-btn-primary:hover { background: var(--pf-navy-2); border-color: var(--pf-navy-2); }
.pf-btn--ink { background: var(--pf-ink); border-color: var(--pf-ink); color: var(--pf-paper); box-shadow: none; }
.pf-btn--ink:hover { border-color: var(--pf-ink); transform: translateY(-1px); }
.pf-btn-danger { background: color-mix(in srgb, var(--pf-bad) 8%, #fff);
                 border-color: color-mix(in srgb, var(--pf-bad) 40%, transparent); color: var(--pf-bad); }
.pf-btn-danger:hover { border-color: var(--pf-bad); }
.pf-btn--bare { background: none; box-shadow: none; }
/* On-dark faces: a CTA sitting on a navy panel (the landing's contact card,
   dark surfaces generally). Paper = solid light on dark; its outline twin is
   the quiet second action beside it. */
.pf-btn--paper { background: #FAF3E3; border-color: transparent; color: var(--pf-navy); box-shadow: none; }
.pf-btn--paper:hover { background: #fff; border-color: transparent; }
.pf-btn--paper-outline { background: none; border-color: rgba(250,243,227,.35); color: #FAF3E3; box-shadow: none; }
.pf-btn--paper-outline:hover { border-color: rgba(250,243,227,.7); }
/* shapes + sizes */
.pf-btn--pill { border-radius: 999px; }
.pf-btn-sm { min-height: 30px; padding: 6px 10px; font-size: 12px; }
.pf-btn-lg { min-height: 48px; padding: 12px 24px; font-size: 14.5px; border-radius: 10px; }
.pf-btn-xs { min-height: 0; padding: 3px 9px; font-size: 11.5px; font-weight: 500; border-radius: 999px; }
/* press + async states (moved out of hub.css so both renderers share them) */
.pf-btn:active { transform: translateY(1px); box-shadow: inset 0 1px 3px rgba(0,0,0,.12); }
.pf-btn.is-busy { transform: translateY(1px); cursor: progress; color: var(--pf-muted);
  background: linear-gradient(180deg,#eef0f2,#e6e9ec); box-shadow: inset 0 1px 3px rgba(0,0,0,.14);
  border-color: #b8bdc4; }
.pf-btn.is-done { color: var(--pf-ok);
  border-color: color-mix(in srgb, var(--pf-ok) 32%, #c8ccd2);
  background: linear-gradient(180deg, #fdfffe, color-mix(in srgb, var(--pf-mint) 26%, #eef0f2)); }
.spin { width: 11px; height: 11px; border-radius: 50%; flex: none; display: inline-block;
  border: 1.5px solid rgba(18,47,77,.22); border-top-color: var(--pf-navy);
  animation: pf-spin .8s linear infinite; }
.pf-btn .spin { margin-right: 6px; vertical-align: -1px; }
@keyframes pf-spin { to { transform: rotate(360deg); } }
/* the thumb floor: EVERY pill of the family, one rule (xs chips exempt on
   purpose — a filter chip is not a primary action) */
@media (max-width: 760px) {
  .pf-btn, .need-btn, .chat-starter, .perm-btn, .pf-baract,
  .pf-voice-btn, .chat-signedout-fix { min-height: 44px; }
  .pf-btn-xs { min-height: 0; }
}
/* the model-swap chip: an xs pill, quiet by default */
.pf-model-btn { min-height: 0; padding: 3px 9px; border-radius: 999px;
                background: var(--pf-surface); color: var(--pf-muted);
                font: 500 11.5px/1.5 var(--pf-sans); box-shadow: none; }
.pf-model-btn:hover { border-color: var(--pf-faint); color: var(--pf-ink); }

.pf-badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 600; }
.pf-badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.pf-badge-ok      { color: var(--pf-ok);   background: color-mix(in srgb, var(--pf-ok) 12%, transparent); }
.pf-badge-warn    { color: var(--pf-warn); background: color-mix(in srgb, var(--pf-warn) 14%, transparent); }
.pf-badge-bad     { color: var(--pf-bad);  background: color-mix(in srgb, var(--pf-bad) 12%, transparent); }

/* ---- 8. typography + layout helpers (verbatim) ---- */
.pf-h1 { font-family: var(--pf-disp); font-size: 32px; font-weight: 700; letter-spacing: -.02em; line-height: 1.06; margin: 0 0 6px; }
.pf-h2 { font-family: var(--pf-disp); font-size: 20px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 12px; }
.pf-eyebrow { font-family: var(--pf-sans); font-size: 12px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; color: var(--pf-navy); }
.pf-lead { color: var(--pf-muted); margin: 0 0 18px; font-size: 15px; }
.pf-stack > * + * { margin-top: 14px; }
/* A section owns the air above it, whatever happens to sit there. It used not
   to: only `.pf-section + .pf-section` had a margin, so the apps shelf borrowed
   its top gap from "Running without you" standing between it and the needs
   feed. Remove that block (1.18.9) and "Your apps" landed flush on the card
   above it — "das Padding stimmt da nicht … das sieht doch nicht professionell
   aus" (owner, 2026-08-12). Spacing that depends on a neighbour is spacing that
   breaks the day the neighbour goes. */
.pf-section { margin-top: 40px; }
.pf-section + .pf-section { margin-top: 22px; }
.pf-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pf-row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.pf-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }
.pf-hint { color: var(--pf-muted); font-size: 12px; }
.pf-link { color: var(--pf-navy); text-decoration: none; }
.pf-link:hover { text-decoration: underline; }

.pf-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.pf-field > label { font-size: 12px; font-weight: 600; color: var(--pf-muted); }
/* One control height. A text input sits next to a .pf-btn in every inline
   form (secrets, custom keys, the domain field), and a 40px input beside a
   36px pill reads as a mistake, not a hierarchy. 14px/1.4 + 7px padding +
   1px borders = 36px, the same floor the button family has above. */
.pf-input, .pf-select, textarea.pf-input {
  width: 100%; box-sizing: border-box; min-height: 36px; padding: 7px 11px; border-radius: var(--pf-radius-sm);
  border: 1px solid var(--pf-line-2); background: var(--pf-surface); color: var(--pf-ink);
  font: 400 14px/1.4 var(--pf-sans); }
.pf-input:focus, .pf-select:focus, textarea.pf-input:focus {
  outline: none; border-color: var(--pf-navy); box-shadow: 0 0 0 3px rgba(18,47,77,.12); }
.pf-input.pf-mono { font-family: var(--pf-mono); }

.pf-empty { color: var(--pf-muted); border: 1px dashed var(--pf-line-2); border-radius: var(--pf-radius); padding: 22px; text-align: center; }

/* ---- 9. focus visibility + responsive + reduced motion (verbatim) ---- */
.pf a:focus-visible, .pf button:focus-visible, .pf-topnav a:focus-visible,
.pf-input:focus-visible, .pf-select:focus-visible, .forge:focus-visible {
  outline: 2px solid var(--pf-navy); outline-offset: 2px; }

@media (max-width: 760px) {
  /* inputs follow the pills to the 44px thumb floor, same rule as above */
  .pf-input, .pf-select { min-height: 44px; }
  .pf-wrap { padding: 0 18px; }
  .pf-content { padding-top: 22px; padding-bottom: 44px; }
  .pf-h1 { font-size: 26px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* =====================================================================   10. Base-apps surfaces. The ONLY original CSS in this file. Everything below
   is composed from the tokens above — no new colours, radii or fonts.
   ============================================================================ */

/* ---- page head: eyebrow / two-tone display headline / lead ----
   Kept tight to the list below it: a big head over a far-away list reads
   top-heavy, and the board should feel composed as one block. */
.pf-head { margin-bottom: 20px; }
.pf-h1 .pf-h1-2 { display: block; color: var(--pf-muted); }
.pf-head .pf-lead { max-width: 46ch; }

/* The account slot used to be dropped on a phone: the old bar WRAPPED, and a
   third row of chrome for a single-owner box was not worth the height. Header C
   (2026-07) keeps the bar to one slim row and puts identity in a 44px chip, so
   it now fits — and it is the only control left up there once the nav has moved
   to the bottom tab bar. It stays. */
@media (max-width: 760px) {
  .pf-head .pf-stats { margin-top: 18px; }
}

/* ---- "How this board works" explainer card ----
   Quiet instructional card at the foot of a board: body-toned prose, roomy
   line-height, a real list. Sits below the content because it is read once —
   the board itself stays the hero. */
/* ---- "How this board works" — THE standard explainer component -----------
   One component on every board (todo, research, meetings, emails); only the
   prose differs. Markup contract, exactly this shape:

     <section class="pf-section pf-how">
       <details class="pf-card">
         <summary>How this board works</summary>
         <div class="pf-how-body"> ...board-specific prose... </div>
       </details>
     </section>

   Collapsed by default (it is read once; it does not get to be the first
   screen). The chevron is drawn HERE via a CSS mask so pages cannot drift,
   and the spacing to head above / board below is owned HERE — never by the
   pages. */
.pf-how { margin: 0 0 28px; }
.pf-how .pf-card { padding: 0; color: var(--pf-muted); font-size: 14px; line-height: 1.65; }
.pf-how summary { display: flex; align-items: center; justify-content: space-between; gap: 12px;
                  padding: 13px 20px; cursor: pointer; list-style: none;
                  font: 600 13px/1.4 var(--pf-sans); color: var(--pf-muted); }
.pf-how summary::-webkit-details-marker { display: none; }   /* Safari's own triangle */
.pf-how summary:hover { color: var(--pf-ink); }
.pf-how summary::after { content: ""; width: 14px; height: 14px; flex: none;
                  background: var(--pf-faint);
                  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
                  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
                  transition: transform .16s ease; }
.pf-how details[open] summary { border-bottom: 1px solid var(--pf-line); }
.pf-how details[open] summary::after { transform: rotate(180deg); }
.pf-how-body { padding: 16px 20px 18px; }
.pf-how-body strong, .pf-how-body em { color: var(--pf-ink); }
.pf-how-body p { margin: 0 0 10px; }
.pf-how-body p:last-child { margin-bottom: 0; }
.pf-how-body ul { margin: 0 0 10px; padding-left: 22px; }
.pf-how-body li { margin: 4px 0; }
.pf-how-body code { font-family: var(--pf-mono); font-size: 12.5px; background: var(--pf-surface-2);
  padding: 1px 5px; border-radius: 5px; }
.pf-how-body a { color: var(--pf-navy); }

/* ---- pending-count badge on a nav pill (today: Emails drafts) ----
   A small navy counter so unreviewed drafts are visible from every board.
   Sits inside the pill so the hover/active backgrounds keep wrapping both. */
.pf-topnav a .nav-badge { display: inline-block; margin-left: 6px; min-width: 18px;
  padding: 1px 5px; border-radius: 999px; background: var(--pf-navy); color: #fff;
  font-family: var(--pf-mono); font-variant-numeric: tabular-nums;
  font-size: 10.5px; font-weight: 500; line-height: 1.5; text-align: center;
  vertical-align: 1px; }
/* The slot exists from the first paint; the number arrives into it. An EMPTY
   badge takes no space at all: keeping its box reserved looked like broken
   padding inside the active pill ("padding im header das kaputt ist neben
   Emails", owner 2026-08-11). No jump in the common case regardless — the
   last seen count paints synchronously from cache, so the slot is born with
   its number; only a count crossing 0 moves anything.
   SCOPED to beat the display each context sets above: the bare
   `.nav-badge:empty` (0,2,0) LOST to `.pf-topnav a .nav-badge` (0,2,1), and
   the empty pill rendered as a navy dash beside the label — caught live the
   same evening. Specificity, not `!important`. */
.pf-topnav a .nav-badge:empty { display: none; }
.pf-tab .nav-badge:empty { display: none; }

/* ---- stat trio: big mono numerals over tiny uppercase labels ---- */
.pf-stats { display: flex; gap: 30px; }
.pf-stat { text-align: right; }
.pf-stat-n { font-family: var(--pf-mono); font-variant-numeric: tabular-nums; font-size: 26px;
             font-weight: 500; line-height: 1; color: var(--pf-ink); }
.pf-stat-l { font-family: var(--pf-sans); font-size: 10.5px; font-weight: 600; letter-spacing: .14em;
             text-transform: uppercase; color: var(--pf-muted); margin-top: 6px; }
.pf-stat-ok   .pf-stat-n { color: var(--pf-ok); }
.pf-stat-warn .pf-stat-n { color: var(--pf-warn); }
.pf-stat-wip  .pf-stat-n { color: var(--pf-warn); }

/* ---- the stable item number: a ledger line-number, not a chip ----
   A slim mono figure that sits quietly just before the text it names. Tabular
   figures in a fixed, right-aligned column so #9 and #10 line up and the text
   column starts on a true vertical. Never boxed: a border here turns a
   reference mark into something that looks pressable. */
.num { flex: none; width: 2.4em; text-align: right;
       font-family: var(--pf-mono); font-variant-numeric: tabular-nums;
       font-size: 12.5px; font-weight: 400; letter-spacing: -.01em;
       color: var(--pf-muted); white-space: nowrap; user-select: none; }

/* ---- ONE frame width, on every page ----
   Emails and Meetings used to hold a 46rem reading column while To-do, Research
   and the Desk spanned the shell. Three widths across six pages means the white
   box moves every time you change page — "sehr, sehr frustrierend als User"
   (owner, 2026-08-24), and he is right: nothing else on the page moves, so the
   frame jumping is the only thing the eye tracks.

   The shell wins the tie because the Desk has to be the best surface in the
   product and it is the one page that genuinely uses the width (owner: "Chat
   muss maximal gute UI/UX haben"). Narrowing everything to 46rem would have cost
   the Desk a third of its thread and squeezed the Kanban into a strip.

   What the 46rem measure was protecting is real, though — a long line of body
   text is hard to read, and a list stretched wide puts the task at the far left
   and its timestamp at the far right with a lake of nothing between. So the
   measure moves off the FRAME and onto the TEXT: .prose below keeps it, which is
   where the reading actually happens. Row heads still span, as ledger lines
   should. */
#board, .pf-head-col { max-width: none; }

/* ---- the ledger: ONE framed panel of hairline-divided rows ----
   Not a stack of floating cards. The frame is the card; the rows are content.
   `overflow: hidden` lets a row's hover tint reach the rounded corners. */
.ledger { background: var(--pf-surface); border: 1px solid var(--pf-line);
          border-radius: var(--pf-radius); box-shadow: 0 14px 30px -24px rgba(28,30,33,.35);
          overflow: hidden; }
.ledger > * + * { border-top: 1px solid var(--pf-line); }

/* A to-do row: [#N] [task text — the hero] [when]. One line, ~50px tall. */
.ledger-row { display: flex; align-items: baseline; gap: 14px; padding: 14px 18px;
              transition: background-color .12s ease; }
.ledger-row:hover { background: var(--pf-surface-2); }
.todo-text { flex: 1; min-width: 0; margin: 0;
             font-family: var(--pf-sans); font-size: 16.5px; font-weight: 500; line-height: 1.3;
             color: var(--pf-ink); overflow-wrap: anywhere; }
.todo-time { flex: none; font-family: var(--pf-mono); font-variant-numeric: tabular-nums;
             font-size: 11.5px; color: var(--pf-muted); }

/* Done recedes: tighter, struck, a quiet tick rather than a badge, and a grey
   edge down the left to mark the block. It is history, not work.

   It used to recede by being PAPER-TONED, which worked while the page behind it
   was white-on-nothing. On the white sheet that inverted: a warm fill is the
   loudest thing on a white page, so the archive became the first thing the eye
   landed on — and on a board that is 19-of-19 archived it was the ONLY thing on
   the page. Receding is now carried by the same device the in-progress panel
   uses (a left edge, not a tint), which cannot flip meaning with the fill
   behind it. */
.ledger-done { background: var(--pf-surface); border-left: 2px solid var(--pf-line-2); }
.ledger-done .ledger-row { padding: 11px 18px; }
.ledger-done .todo-text { font-size: 15px; font-weight: 400; color: var(--pf-muted);
                          text-decoration: line-through; text-decoration-color: var(--pf-line-2); }
.tick { flex: none; color: var(--pf-ok); font-size: 12px; line-height: 1; }

/* In progress leans the other way from Done: it is the work in hand, so it is
   the loudest panel on the board, not the quietest. A warm hairline down the
   left edge marks the whole panel — cheaper than tinting every row, and it
   survives the row hover. The chip states it in words rather than a colour dot
   alone, so it still reads without colour. */
.ledger-doing { border-left: 2px solid var(--pf-warn); }
.wip { flex: none; font-family: var(--pf-sans); font-size: 10.5px; font-weight: 600;
       letter-spacing: .08em; text-transform: uppercase; color: var(--pf-warn);
       white-space: nowrap; }

/* A quiet "Done · 1" rule, rather than a second display heading competing with
   the page headline. */
.ledger-rule { display: flex; align-items: center; gap: 12px; margin: 24px 0 10px; }
.ledger-rule::after { content: ""; flex: 1; height: 1px; background: var(--pf-line); }
.ledger-rule-label { font-family: var(--pf-mono); font-size: 11px; letter-spacing: .04em;
                     color: var(--pf-muted); white-space: nowrap; }

/* ---- research + emails: same frame, same rhythm, each entry carries a body ----
   The header line IS a ledger row; the prose hangs beneath it, indented to the
   text column so the #N gutter stays true all the way down the panel. */
.ledger-entry { padding: 15px 18px 17px; transition: background-color .12s ease; }
.ledger-entry:hover { background: var(--pf-surface-2); }
.entry-head { display: flex; align-items: baseline; gap: 14px; }
.entry-title { flex: 1; min-width: 0; margin: 0;
               font-family: var(--pf-disp); font-size: 17px; font-weight: 700; letter-spacing: -.01em;
               line-height: 1.25; color: var(--pf-ink); overflow-wrap: anywhere; }
.entry-when { flex: none; font-family: var(--pf-mono); font-variant-numeric: tabular-nums;
              font-size: 11.5px; color: var(--pf-muted); }
.entry-sub { margin: 4px 0 0; font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); }
.entry-sub + .prose { margin-top: 9px; }
.entry-head + .prose { margin-top: 8px; }
.prose + .entry-sub { margin-top: 10px; }
/* Emails carry a #N, so their body hangs from the text column: num + row gap.
   Research has no number and starts flush (an empty gutter reads as a mistake).
   The indent holds at every width — zeroing it on mobile put the body to the
   LEFT of the title it belongs to, which reads as a broken layout. */
.entry-indent { padding-left: calc(2.4em + 14px); margin-top: 8px; }

/* ---- the draft's action row ----
   There is no outbox: the only way a draft leaves this board is through the
   owner's clipboard, so these buttons ARE the feature, not a garnish. They sit
   under the body, in the text column, separated by a hairline so they read as
   an action on the draft above rather than the start of the next one.
   `is-done` / `is-bad` are the button's own receipt — pressed, it says what
   happened, in place. */
.entry-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px;
                 padding-top: 12px; border-top: 1px solid var(--pf-line); }
.entry-actions .pf-btn.is-done { color: var(--pf-ok); border-color: var(--pf-ok); }
.entry-actions .pf-btn-primary.is-done { background: var(--pf-ok); border-color: var(--pf-ok); color: #fff; }
.entry-actions .pf-btn.is-bad { color: var(--pf-bad); border-color: var(--pf-bad); }
.entry-actions .pf-btn-primary.is-bad { background: var(--pf-bad); border-color: var(--pf-bad); color: #fff; }
/* A button stays disabled while it shows its receipt, to swallow a double-click.
   The base `.pf-btn:disabled` dims to 50%, which would render "Copied" as a
   greyed-out control — i.e. as a failure. Keep it lit. */
.entry-actions .pf-btn:disabled { opacity: 1; }

/* Archived drafts recede like a struck to-do, but their buttons must stay
   legible: the panel tint is the signal, not a faded control. */
.ledger-done .entry-title { font-weight: 600; color: var(--pf-muted); }

@media (max-width: 760px) {
  .ledger-row, .ledger-entry { padding-left: 14px; padding-right: 14px; }
  /* At 390px four buttons in a row become four buttons on four lines with the
     gutter eating half the width. Drop the entry indent's gutter for the action
     row only, so they wrap into two clean pairs. */
  .entry-actions { margin-left: calc(-2.4em - 14px); }
}

/* ---- section header: serif h2 on the left, mono count on the right ---- */
.pf-section-head { display: flex; align-items: baseline; justify-content: space-between;
                   gap: 12px; margin: 0 0 12px; }
.pf-section-head .pf-h2 { margin: 0; }
.pf-count { font-family: var(--pf-mono); font-size: 11.5px; letter-spacing: .02em; color: var(--pf-muted); }

/* ---- dashboard tiles ---- */
a.pf-card { display: block; text-decoration: none; color: inherit;
            transition: border-color .12s ease, transform .12s ease, box-shadow .12s ease; }
a.pf-card:hover { border-color: var(--pf-navy); transform: translateY(-1px);
                  box-shadow: 0 18px 34px -22px rgba(18,47,77,.45); }
.tile-desc { color: var(--pf-muted); margin: 0 0 18px; min-height: 2.8em; }
.tile-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.tile-stat { font-family: var(--pf-disp); font-size: 24px; font-weight: 700; color: var(--pf-navy);
             letter-spacing: -.01em; }

/* ---- prose bodies (research markdown, email drafts) ---- */
/* The reading measure, now that the frame no longer carries it: body copy stops
   at 46rem however wide the board gets, so a mail draft is never a 150-character
   line. Everything else in a row (title, timestamp, actions) still spans. */
.prose { color: var(--pf-ink); font-size: 14px; line-height: 1.65; max-width: 46rem; }
.prose > *:first-child { margin-top: 0; }
.prose > *:last-child { margin-bottom: 0; }
.prose h3, .prose h4, .prose h5, .prose h6 {
  font-family: var(--pf-disp); font-weight: 700; letter-spacing: -.01em;
  margin: 1.2rem 0 .4rem; color: var(--pf-ink); }
.prose p { margin: .6rem 0; }
.prose ul { margin: .6rem 0; padding-left: 1.25rem; }
.prose li { margin: .2rem 0; }
.prose a { color: var(--pf-navy); text-decoration: underline; text-underline-offset: 2px; }
.prose code { font-family: var(--pf-mono); font-size: .86em; background: var(--pf-surface-2);
              border: 1px solid var(--pf-line); border-radius: 5px; padding: .1em .34em; }
.prose strong { font-weight: 700; }

/* A long writeup scrolls inside its card rather than stretching the page. */
.prose-scroll { max-height: 26rem; overflow-y: auto; }

/* Email drafts are plain text: line breaks are content. */
.prose-pre { white-space: pre-wrap; }

/* The source path under a research card: mono, faint, wraps on mobile. */
.source-path { font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted);
               overflow-wrap: anywhere; }

/* ---- long tokens must never push a panel wider than the screen ----
   Model output and vault paths contain unbreakable strings (package specs,
   URLs, hashes). The ledger panel clips nothing, so the string has to break. */
.prose { overflow-wrap: anywhere; }
.prose code { overflow-wrap: anywhere; }
.prose-pre { overflow-wrap: anywhere; }
.entry-title, .todo-text { overflow-wrap: anywhere; }

/* =====================================================================   The mobile primary nav — the Hub's bottom tab bar, below 760px.

   The hamburger sheet that used to live here is gone (2026-07). The Hub's
   Cockpit answers the same question with a fixed bottom tab bar, and the owner
   asked for one product, not two: below 760px the inline `.pf-topnav` pills
   give way to `.pf-tabbar` (built by nav.js from the SAME entries), and the
   account chip is the only control left in the bar. `display` for both is
   decided here, in CSS, never by script — so no viewport can end up with both.
   ============================================================================ */

/* The account popover hangs off the bar, so the bar is the positioning context. */
.pf-topbar-wrap { position: relative; }

/* The cockpit shell is a column so the quiet footer sits at the bottom even on
   a short page; the tab-bar body padding keeps the last row above the fixed nav. */
body.pf-cockpit { display: flex; flex-direction: column; min-height: 100dvh; }
body.pf-cockpit .pf-content { flex: 1 0 auto; width: 100%; }

/* ---- THE SHEET: the Cockpit's content sits on white ----------------------
   The paper base (--pf-paper) was the background of the PAGE, of a kanban
   COLUMN, and of a done CARD at the same time. On a board that is mostly done
   — 33 struck, none open — all three landed on #FAF3E3 and the screen had no
   contrast left to read by (owner, 2026-08-21: "why is there not a white
   background here ... this is a bit hard to read").

   Every other board already framed its content in white (.ledger). This makes
   that the rule rather than the exception: one white sheet per page, paper
   demoted to the frame around it. It is scoped to `body.pf-cockpit` because the
   Hub shares this stylesheet and keeps its own paper layout.

   Horizontal padding is deliberately NOT touched — it belongs to .pf-wrap, and
   overriding it is what once pushed content flush against the screen edge. The
   sheet paints the full 1160px wrap box, so its edge lands outside the gutter
   and the content column still lines up with the topbar brand. */
body.pf-cockpit:not(.pf-chat) .pf-content {
  background: var(--pf-surface);
  border: 1px solid var(--pf-line);
  border-radius: 16px;
  box-shadow: 0 24px 50px -34px rgba(28, 30, 33, .40);
  margin-top: 26px;
  margin-bottom: 34px;
  padding-bottom: 44px;
}

/* A panel ON the sheet is a field, not a second floating card: the drop shadows
   that read as "lifted" against paper read as clutter against white. Hairlines
   and fills still carry the grouping, so nothing loses its edges. */
body.pf-cockpit:not(.pf-chat) .pf-content .ledger,
body.pf-cockpit:not(.pf-chat) .pf-content .pf-card,
body.pf-cockpit:not(.pf-chat) .pf-content .mt,
body.pf-cockpit:not(.pf-chat) .pf-content .lead-card,
body.pf-cockpit:not(.pf-chat) .pf-content .pf-skel-card { box-shadow: none; }

/* Below the content column's own width the sheet cannot float — it already
   spans the viewport, and a rounded corner hard against the screen edge reads
   as a clipped card rather than a frame. So under 1160px it stops pretending to
   be a card and becomes a band: full bleed, top hairline only. The .pf-wrap
   gutter still holds the text off the edge, so this changes the frame and not
   the text inset. */
@media (max-width: 1160px) {
  body.pf-cockpit:not(.pf-chat) .pf-content {
    margin-top: 0; margin-bottom: 0;
    border-radius: 0; border-left: 0; border-right: 0;
    box-shadow: none;
  }
}

@media (max-width: 760px) {
  /* Header C: the bar stays one fixed slim row. */
  /* Vertical padding only. `padding: 0` also zeroed .pf-wrap's 18px gutter, so
     the brand started at the screen edge while every card below it started at
     18px — the misalignment that reads as a clipped logo. */
  .pf-bar { height: 56px; flex-wrap: nowrap; padding: 0 18px; gap: 12px; }
  .pf-brand { font-size: 17px; }
  .pf-brand .pf-mark, .pf-brand img.pf-mark { height: 26px; }
  .pf-topnav { display: none; }
  .pf-tabbar { display: block; }
  body.pf-cockpit.pf-has-tabbar { padding-bottom: calc(70px + env(safe-area-inset-bottom)); }
  /* The Cockpit's quiet footer only ever held the build line, and on a phone it
     sits under the fixed tab bar as dead weight. The build line lives in the
     Brainies sheet now (brainmenu.js); the footer is a desktop-only courtesy.
     Scoped to the Cockpit: the Hub shares this stylesheet and keeps a real footer
     (guests reach the API docs from it), so this must not touch a non-Cockpit page. */
  body.pf-cockpit .pf-foot { display: none; }
}

/* =====================================================================   10a. THE LOADING SKELETON — one shimmer, reused (board.js `skeleton()`).

   Every board and the research entry showed a bare "Loading…" while they fetched.
   A skeleton in the shape of the content reads as faster (owner, 2026-08-09): a
   light sweep travels across grey placeholders so the wait feels alive, and the
   real render replaces it in place. Two shapes only: cards, or a title+paragraphs.
   ============================================================================ */
.pf-skel { background: linear-gradient(100deg, var(--pf-line) 30%, var(--pf-surface-2) 50%, var(--pf-line) 70%);
  background-size: 200% 100%; animation: pf-skel-sweep 1.3s linear infinite; border-radius: 8px; }
@keyframes pf-skel-sweep { 0% { background-position: 180% 0; } 100% { background-position: -80% 0; } }
.pf-skel-card { background: var(--pf-surface); border: 1px solid var(--pf-line); border-radius: var(--pf-radius);
  padding: 16px; margin-bottom: 12px; }
.pf-skel-line { height: 12px; border-radius: 6px; margin-bottom: 10px; }
.pf-skel-line:last-child { margin-bottom: 0; }
.pf-skel-h { height: 34px; width: 70%; margin-bottom: 16px; }
.pf-skel-gap { height: 10px; }
@media (prefers-reduced-motion: reduce) { .pf-skel { animation: none; } }

/* =====================================================================   10b. THE ACCOUNT MENU — current Brain in the bar, the fleet in a sheet.

   Built by brainmenu.js and used on every non-immersive page. The bar shows the
   Brain you are on and a live "N aktiv" count (the one thing the owner opens the
   app for: are my Brains working?); a tap opens the sheet with every Brain, its
   status, and the two doors out. A list scales where a chip row could not.
   ============================================================================ */
/* center, and the baseline pairing moved one level in — because `align-items:
   baseline` on the PILL only lines the text up while the pill is exactly as
   tall as its text. On a touch device it is not: the 44px hit floor below
   stretches it, and a baseline item in a stretched row sits at the TOP of it.
   So "Brainy 2" floated a third of the pill above the dot and the caret, which
   both carry align-self:center — a label hanging in the upper half of an empty
   pill, which is what the owner saw and read as a stray line break (report with
   screenshot, 2026-08-31).
   The 2026-08-24 fix is intact, one level down: the name is 13.5px and the
   count 12px, and centring two text boxes of different heights lines up their
   MIDDLES while their baselines part ("1 active" sat a hair above "Brainy 2").
   Both are true at once now — the pair shares a baseline inside
   .pf-brainctl-text, and the pair as a whole is centred in the pill. */
.pf-brainctl { display: inline-flex; align-items: center; gap: 8px; line-height: 1;
  border: 1px solid var(--pf-line-2); background: var(--pf-surface); border-radius: 999px;
  padding: 6px 10px 6px 11px; cursor: pointer; font-family: var(--pf-sans);
  font-weight: 700; font-size: 13.5px; color: var(--pf-ink);
  min-width: 0; max-width: 100%; }
.pf-brainctl:hover { background: var(--pf-surface-2); }
.pf-brainctl-text { display: inline-flex; align-items: baseline; gap: 8px; min-width: 0; }
/* Two caps, the smaller one wins. 42vw alone is a phone measure that reads as
   ~600px on a desktop — wide enough for the whole of an 80-character topic
   (CodingVm::TOPIC_MAX), which is how one Brain's topic came to sit across the
   nav. 26ch keeps the label to about the length you can actually read at a
   glance and truncates the rest; 42vw still governs the narrow end, where the
   nav is not on screen at all. This cap holds at EVERY width — it is the half
   of the fix that costs no room, so it needs no breakpoint. */
.pf-brainctl-name { min-width: 0; max-width: min(26ch, 42vw); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-brainctl-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--pf-line-2); flex: none;
  align-self: center; }
.pf-brainctl-dot.is-working { background: #3f9d6b; box-shadow: 0 0 0 3px rgba(63,157,107,.18); }
/* Never wraps, never shrinks. A topic in the bar is long enough to squeeze the
   count, and "1 active" broken across two lines put a second line inside a pill
   whose whole job is to be one — the label gives way instead, it already has an
   ellipsis for exactly that. */
.pf-brainctl-cnt { font-weight: 600; font-size: 12px; color: #2f8f5f;
  flex: none; white-space: nowrap; }
.pf-brainctl svg { width: 15px; height: 15px; color: var(--pf-muted); flex: none;
  align-self: center; }

.pf-brainsheet-backdrop { position: fixed; inset: 0; background: rgba(20,28,44,.34); z-index: 60;
  opacity: 0; visibility: hidden; transition: opacity .18s ease; }
.pf-brainsheet-backdrop.is-open { opacity: 1; visibility: visible; }
.pf-brainsheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 61; background: var(--pf-surface);
  border-top-left-radius: 20px; border-top-right-radius: 20px; box-shadow: 0 -14px 40px rgba(20,30,50,.25);
  padding: 8px 0 calc(12px + env(safe-area-inset-bottom)); max-height: 86vh; display: flex; flex-direction: column;
  transform: translateY(101%); transition: transform .22s cubic-bezier(.32,.72,0,1); }
.pf-brainsheet.is-open { transform: translateY(0); }
.pf-brainsheet-grip { width: 40px; height: 4px; border-radius: 2px; background: var(--pf-line-2); margin: 6px auto 8px; }
/* Heading and its one control on the same line: the control belongs to the
   list, and putting it anywhere else would make the owner hunt for it. */
.pf-brainsheet-top { display: flex; align-items: baseline; gap: 12px; padding-right: 14px; }
.pf-brainsheet-h { padding: 2px 20px; font-family: var(--pf-disp); font-weight: 700; font-size: 20px; color: var(--pf-ink); }
/* Quiet until it is on. "Reorder" is an invitation and must not compete with
   the heading; "Done" is the way out and says so by going solid. */
.pf-brainsheet-edit { margin-left: auto; flex: none; border: 1px solid var(--pf-line-2);
  background: none; border-radius: 999px; padding: 6px 12px; cursor: pointer;
  font-family: var(--pf-sans); font-weight: 600; font-size: 12.5px; color: var(--pf-muted); }
.pf-brainsheet-edit:hover { background: var(--pf-surface-2); color: var(--pf-ink); }
.pf-brainsheet-edit.is-on { background: var(--pf-navy); border-color: var(--pf-navy); color: var(--pf-paper); }
.pf-brainsheet-edit[hidden] { display: none; }
/* Auto-Sort only exists inside reorder mode, so it never competes with the
   heading in the resting sheet. Same pill as Reorder, one shade quieter: it is
   the shortcut, and the arrows are still the thing that decides the order. */
.pf-brainsheet-auto { margin-left: auto; flex: none; border: 1px solid var(--pf-line-2);
  background: none; border-radius: 999px; padding: 6px 12px; cursor: pointer;
  font-family: var(--pf-sans); font-weight: 600; font-size: 12.5px; color: var(--pf-muted); }
.pf-brainsheet-auto:hover { background: var(--pf-surface-2); color: var(--pf-ink); }
.pf-brainsheet-auto:disabled { cursor: default; color: var(--pf-muted); opacity: .6; }
.pf-brainsheet-auto[hidden] { display: none; }
/* With both pills up, only the first one pushes off the heading — otherwise the
   second inherits the auto margin too and they split to opposite edges. */
.pf-brainsheet-auto:not([hidden]) ~ .pf-brainsheet-edit { margin-left: 0; }
.pf-brainsheet-sub { padding: 2px 20px 10px; font-family: var(--pf-mono); font-size: 12px; color: var(--pf-muted); }
.pf-brainsheet-sub b { color: #2f8f5f; }
.pf-brainsheet-list { overflow: auto; border-top: 1px solid var(--pf-line); }
.pf-brow { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: none;
  border: 0; border-bottom: 1px solid var(--pf-line); padding: 12px 20px; cursor: pointer;
  font-family: var(--pf-sans); color: var(--pf-ink); }
.pf-brow:hover { background: var(--pf-surface-2); }
.pf-brow.is-current { background: var(--pf-surface-2); cursor: default; }
.pf-brow-st { width: 24px; flex: none; display: flex; align-items: center; justify-content: center; }
.pf-brow-dot { width: 9px; height: 9px; border-radius: 50%; background: #c9c3b6; }
.pf-brow-eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.pf-brow-eq i { width: 2.6px; background: #3f9d6b; border-radius: 2px; animation: pf-brow-eq .9s ease-in-out infinite; }
.pf-brow-eq i:nth-child(1) { height: 6px; animation-delay: 0s; }
.pf-brow-eq i:nth-child(2) { height: 14px; animation-delay: .15s; }
.pf-brow-eq i:nth-child(3) { height: 9px; animation-delay: .3s; }
@keyframes pf-brow-eq { 0%,100% { transform: scaleY(.4); } 50% { transform: scaleY(1); } }
.pf-brow-nm { font-weight: 600; font-size: 15px; min-width: 0; }
.pf-brow-nm small { display: block; font-weight: 500; font-size: 12px; color: var(--pf-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 230px; }
.pf-brow.is-working .pf-brow-nm small { color: #2f8f5f; }
/* Reorder mode. The row keeps every pixel it had — it is the same element —
   and the arrows sit beside it in a slot, so nothing about the ordinary sheet
   moves when the mode is off. */
.pf-brow-slot { display: flex; align-items: stretch; border-bottom: 1px solid var(--pf-line); }
/* width:100% is what a row is on its own; inside the slot it has to GIVE that
   width back, or the arrows are pushed off the sheet's right edge — which is
   exactly what the first render did (measured 390px, the last arrow clipped). */
.pf-brow-slot .pf-brow { border-bottom: 0; width: auto; flex: 1 1 auto; min-width: 0; }
.pf-brow-slot .pf-brow:disabled { opacity: 1; cursor: default; }
.pf-brow-move { display: flex; align-items: center; gap: 2px; flex: none; padding-right: 12px; }
/* 44px each, because this is the one place in the sheet where two targets sit
   side by side and a miss moves the WRONG Brain. */
.pf-brow-mv { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--pf-line-2); border-radius: 10px; background: var(--pf-surface);
  color: var(--pf-ink); cursor: pointer; }
.pf-brow-mv svg { width: 18px; height: 18px; }
.pf-brow-mv:hover:not(:disabled) { background: var(--pf-surface-2); }
.pf-brow-mv:disabled { opacity: .3; cursor: default; }
/* The save failed. Said once, on the list, rather than in an alert that steals
   the sheet — the order on screen is still the order he wanted. */
.pf-brainsheet-list.is-unsaved::after { content: "Couldn't save the order — it will be back to the old one next time.";
  display: block; padding: 10px 20px; font-family: var(--pf-sans); font-size: 12px; color: var(--pf-bad); }
.pf-brow-chk { margin-left: auto; flex: none; color: var(--pf-navy); display: flex; }
.pf-brow-chk svg { width: 20px; height: 20px; stroke-width: 2.4; }
.pf-brainsheet-foot { border-top: 1px solid var(--pf-line-2); }
.pf-brainsheet-foot a { display: flex; align-items: center; gap: 11px; padding: 14px 20px; text-decoration: none;
  color: var(--pf-ink); font-family: var(--pf-sans); font-weight: 600; font-size: 15px; }
.pf-brainsheet-foot a:hover { background: var(--pf-surface-2); }
.pf-brainsheet-foot svg { width: 18px; height: 18px; color: var(--pf-muted); flex: none; }
.pf-brainsheet-ver { padding: 12px 20px 2px; font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted); }
@media (prefers-reduced-motion: reduce) {
  .pf-brainsheet { transition: none; }
  .pf-brow-eq i { animation: none; }
}
/* On a desktop the sheet is a small card anchored bottom-right, not a full-width
   drawer — there is room, and a page-wide drawer over a wide layout reads wrong. */
@media (min-width: 761px) {
  .pf-brainsheet { left: auto; right: 16px; bottom: 16px; width: 380px; border-radius: 18px;
    max-height: min(560px, 80vh); transform: translateY(8px); opacity: 0; visibility: hidden;
    transition: opacity .16s ease, transform .16s ease; }
  .pf-brainsheet.is-open { transform: translateY(0); opacity: 1; visibility: visible; }
}

/* Ask one of your Brainies — a quick-ask bar on Today that routes to a free Brain. */
.pf-quickask { margin: 16px 0 4px; }
.pf-quickask[hidden] { display: none; }
.pf-quickask-lbl { display: block; font-family: var(--pf-sans); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--pf-navy); margin-bottom: 9px; }
.pf-quickask-bar { display: flex; align-items: center; gap: 8px; background: var(--pf-surface);
  border: 1px solid var(--pf-line-2); border-radius: 999px; padding: 4px 4px 4px 16px; }
.pf-quickask-input { flex: 1; min-width: 0; border: 0; outline: 0; background: none;
  font-family: var(--pf-sans); font-size: 16px; color: var(--pf-ink);
  min-height: 44px; /* T1: the input is a tap target too */ }
.pf-quickask-input::placeholder { color: var(--pf-muted); }
.pf-quickask-mic, .pf-quickask-send { width: 44px; height: 44px; flex: none; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.pf-quickask-mic { background: var(--pf-surface-2); color: var(--pf-navy); }
.pf-quickask-mic.is-rec { background: #c0392b; color: #fff; animation: pf-quickask-pulse 1.1s ease-in-out infinite; }
.pf-quickask-mic.is-busy { opacity: .6; }
.pf-quickask-send { background: var(--pf-ink); color: #fff; }
.pf-quickask-send:disabled { opacity: .5; cursor: default; }
.pf-quickask-mic svg, .pf-quickask-send svg { width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.pf-quickask-hint { font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); margin: 8px 2px 0; }
.pf-quickask-hint b { color: #2f8f5f; }
@keyframes pf-quickask-pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(192,57,43,.4); } 50% { box-shadow: 0 0 0 6px rgba(192,57,43,0); } }
@media (prefers-reduced-motion: reduce) { .pf-quickask-mic.is-rec { animation: none; } }

/* The fleet list on Today — every Brainy, compact, above the active console. */
/* ── Needs you ────────────────────────────────────────────────────────────────
   The first thing on Today when a Brain has answered and he has not looked.
   Reads as a stack of cards, not as a table row: a card can carry the words
   that were actually said, and those words are the whole point of the section.
   A decision (a permission request) is warmer than a chat line and says so with
   a left edge, because that Brain is BLOCKED until he answers. */
.pf-attn{margin:16px 0 6px}
.pf-attn[hidden]{display:none}
.pf-attn-h{display:flex;align-items:baseline;justify-content:space-between;margin:0 2px 10px}
.pf-attn-lbl{font-family:var(--pf-sans);font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--pf-ember)}
.pf-attn-cnt{font-family:var(--pf-mono);font-size:12px;color:var(--pf-muted)}
.pf-attn-cnt b{color:var(--pf-ink)}
.pf-attn-list{display:flex;flex-direction:column;gap:8px}
.pf-attn-card{display:block;background:var(--pf-surface);border:1px solid var(--pf-line);
  border-left:3px solid var(--pf-brass);border-radius:var(--pf-radius);
  padding:12px 14px;text-decoration:none;color:var(--pf-ink)}
.pf-attn-card:hover{background:var(--pf-surface-2)}
.pf-attn-card.is-decision{border-left-color:var(--pf-ember)}
.pf-attn-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.pf-attn-kicker{font-family:var(--pf-sans);font-weight:600;font-size:15px;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-attn-tag{flex:none;font-family:var(--pf-mono);font-size:11px;color:var(--pf-muted);
  border:1px solid var(--pf-line-2);border-radius:999px;padding:2px 8px}
.pf-attn-card.is-decision .pf-attn-tag{color:var(--pf-ember);border-color:var(--pf-ember)}
.pf-attn-who{display:block;margin-top:2px;font-family:var(--pf-mono);font-size:12px;color:var(--pf-muted)}
/* Five lines, then an ellipsis. The excerpt is the END of the message because
   that is where the question lives — and at 390px, 200 characters are four and
   a half lines, so a tighter clamp cut off exactly the sentence the card exists
   to show (seen in the audit, 2026-09-01). The Hub's 200-character cap is the
   real limit; this is the belt. */
.pf-attn-say{display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden;
  margin-top:8px;font-family:var(--pf-sans);font-size:13.5px;line-height:1.45;color:var(--pf-muted)}
.pf-fleet{margin:16px 0 6px}
.pf-fleet[hidden]{display:none}
.pf-fleet-h{display:flex;align-items:baseline;justify-content:space-between;margin:0 2px 10px}
.pf-fleet-lbl{font-family:var(--pf-sans);font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--pf-navy)}
.pf-fleet-cnt{font-family:var(--pf-mono);font-size:12px;color:var(--pf-muted)}
.pf-fleet-cnt b{color:#2f8f5f}
.pf-fleet-list{background:var(--pf-surface);border:1px solid var(--pf-line);border-radius:14px;overflow:hidden}
.pf-frow{display:flex;align-items:center;gap:11px;padding:14px 15px;border-bottom:1px solid var(--pf-line);text-decoration:none;color:var(--pf-ink)}
.pf-frow:last-child{border-bottom:0}
.pf-frow:hover{background:var(--pf-surface-2)}
.pf-frow.is-current{background:var(--pf-surface-2)}
.pf-frow-st{width:22px;flex:none;display:flex;align-items:center;justify-content:center}
.pf-frow-dot{width:9px;height:9px;border-radius:50%;background:#c9c3b6}
.pf-frow-eq{display:inline-flex;align-items:flex-end;gap:2px;height:13px}
.pf-frow-eq i{width:2.6px;background:#3f9d6b;border-radius:2px;animation:pf-brow-eq .9s ease-in-out infinite}
.pf-frow-eq i:nth-child(1){height:5px}.pf-frow-eq i:nth-child(2){height:13px}.pf-frow-eq i:nth-child(3){height:8px}
/* flex:1 1 0 — the name absorbs the row's slack, so everything AFTER it lines up
   in a column down the list. It used to be content-sized with the status pushed
   right by margin-left:auto; that works for one trailing element and breaks the
   moment there are two, because a second auto margin splits the free space and
   the gauges would sit at a different x on every row.

   The basis is ZERO, not auto, and that is the whole fix for the phone: the row
   wraps there (so the gauges get their own line), and a name box that demands
   its content width makes the browser wrap the STATUS and the CHEVRON onto lines
   of their own rather than ellipsising a long topic — a chevron orphaned under
   the row, seen at 390px. Basis 0 means the name never demands width, so it
   ellipsises as it always did and the trailing pieces stay put. Rendering is
   unchanged for a row without gauges. */
.pf-frow-nm{font-family:var(--pf-sans);font-weight:600;font-size:15px;min-width:0;flex:1 1 0}
.pf-frow-nm small{display:block;font-weight:500;font-size:12px;color:var(--pf-muted);font-family:var(--pf-mono);letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* With a topic the row leads with prose the owner typed, so the top line wraps
   nothing and the line under it carries the Brain and its model in the mono face
   the model always had. Both stay one ellipsised line however long he wrote —
   this list is read on a phone. */
.pf-frow-nm.is-topic{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pf-frow-nm.is-topic small{font-family:var(--pf-mono)}
/* What each machine is spending, in the row. A fixed strip in a fixed place so
   the bars line up in a column down the list — the point of these numbers is the
   comparison, not the individual reading. The alignment comes from .pf-frow-nm
   growing (see above), not from an auto margin here — two auto margins in one
   flex row split the slack and would stagger the gauges. */
.pf-fuse{flex:none;display:flex;align-items:center;gap:12px}
.pf-fuse-i{display:inline-flex;align-items:center;gap:5px}
/* --pf-muted, never --pf-faint: faint is 2.91:1 on paper and was retired as a
   text colour in the July 2026 contrast pass. A 9.5px label is exactly the size
   that cannot afford to lose contrast. */
.pf-fuse-l{font-family:var(--pf-sans);font-size:9.5px;font-weight:600;letter-spacing:.08em;color:var(--pf-muted)}
.pf-fuse-bar{width:34px;height:4px;border-radius:2px;background:var(--pf-line-2);overflow:hidden}
/* min-width: a 0.4% disk is a real reading, and a bar of zero pixels renders it
   as "unmeasured" — the one thing this strip must never do. */
.pf-fuse-bar>span{display:block;height:100%;border-radius:2px;background:var(--pf-navy);min-width:2px}
.pf-fuse-v{font-family:var(--pf-mono);font-size:11px;font-weight:600;color:var(--pf-muted);
  font-variant-numeric:tabular-nums;min-width:30px;text-align:right}
.pf-fuse-i.is-warn .pf-fuse-bar>span{background:var(--pf-warn)}
.pf-fuse-i.is-warn .pf-fuse-v{color:var(--pf-warn)}
.pf-fuse-i.is-critical .pf-fuse-bar>span{background:var(--pf-bad)}
.pf-fuse-i.is-critical .pf-fuse-v{color:var(--pf-bad)}
/* On a phone the row cannot carry name, three gauges, a status word and a
   chevron side by side — the name would be ellipsised to nothing, and the name
   is what the row is FOR. So the strip wraps onto its own full line under it and
   keeps every gauge, rather than dropping two of them to stay on one line. */
@media (max-width:720px){
  .pf-frow{flex-wrap:wrap}
  /* padding, not margin, and box-sizing with it: `flex-basis:100%` measures the
     CONTENT box, so a left margin on top of it pushes the strip past the row and
     the page grows a horizontal scrollbar at 390px. The 33px indent lines the
     gauges up under the name, past the status glyph.
     Everything then SHRINKS rather than being sized: three fixed-width gauges
     plus their labels are ~360px of content, which fits a 390px phone and not a
     320px one. Letting the cells share the row and the bars take what is left
     makes the strip fit any width there will ever be — the same lesson the
     ellipsised topic line above had to learn. Both caught by shot's
     --assert-no-overflow probe, which is why that probe exists. */
  .pf-fuse{order:5;flex:1 1 100%;min-width:0;box-sizing:border-box;padding-left:33px;
    margin-top:2px;justify-content:flex-start;gap:12px}
  .pf-fuse-i{flex:1 1 0;min-width:0}
  .pf-fuse-bar{flex:1 1 auto;width:auto;min-width:20px}
}
/* The narrowest phone we support. Three labels, three bars and three
   percentages need ~240px of TEXT that cannot shrink, and at 320px the indent
   and the gaps were spending the last of it — the cells overlapped into
   "86%RAM" and the final figure was clipped off the row. The page never
   scrolled sideways, so --assert-no-overflow passed and only the screenshot
   caught it: an overflow probe proves the page fits, not that the text does.
   So the indent goes (the strip aligns with the row's own padding) and the gaps
   tighten. Nothing is dropped: all three gauges still have to be comparable. */
@media (max-width:400px){
  .pf-fuse{padding-left:0;gap:8px}
  .pf-fuse-i{gap:4px}
  .pf-fuse-l{letter-spacing:.04em}
  .pf-fuse-bar{min-width:14px}
  .pf-fuse-v{min-width:0}
}
.pf-frow-status{font-family:var(--pf-sans);font-size:12.5px;color:var(--pf-muted)}
.pf-frow.is-working .pf-frow-status{color:#2f8f5f;font-weight:600}
.pf-frow-go{flex:none;color:var(--pf-muted);display:flex}
.pf-frow-go svg{width:18px;height:18px}
@media (prefers-reduced-motion: reduce){ .pf-frow-eq i{animation:none} }

/* =====================================================================   11. THE COCKPIT — the dashboard's own components.

   The dashboard used to be three tiles of counts. A count is a question ("16
   entries" — of what?), and an executive should not have to click to find the
   answer. So the cockpit shows the WORK: what the Brain is doing right now, and
   the newest lines on each board, by name.

   Everything here derives from the tokens above — the same Source Serif 4 /
   Inter / IBM Plex Mono, the same paper, navy and status colours as the cockpit.
   The one new idea is the INSTRUMENT: a single dark bar, the only dark object on
   a light page, carrying the machine itself. Its pulse is not decoration — it
   animates only while a to-do is actually in progress, and sits still when the
   Brain is idle. If it moves, work is happening.
   ============================================================================ */

/* ---- the verdict: one sentence, the whole point of the page ---- */
.pf-verdict { padding: 30px 0 10px; }
/* Wide enough that the verdict lands on two lines, not four — it is the one
   sentence the owner reads, and a cramped column makes it look like a caption. */
.pf-verdict .pf-h1 { max-width: 26ch; }
@keyframes pf-breathe { 0%, 100% { opacity: 1 } 50% { opacity: .3 } }

/* The third door: voice. The pill only STARTS the thing — the performance
   happens on the machine itself (see .is-listening below), because the point
   of speaking is that the Brain hears you, not that a button lights up.
   `--lvl` is written by JS every frame: 0 (silence) → 1 (loud). Everything
   warm and moving here is driven by a real microphone, never by a timer. */
.pf-voice { display: flex; align-items: center; gap: 12px; margin: 12px 2px 0; flex-wrap: wrap; }
.pf-voice-btn { --lvl: 0;
                /* skin over THE BUTTON: position for the halo, pill shape,
                   its own gap — box model and type come from the base. */
                position: relative; gap: 8px; border-radius: 999px; }
.pf-voice-btn:hover { border-color: var(--pf-faint); transform: translateY(-1px); }
.pf-voice-btn:focus-visible { outline: 2px solid var(--pf-warn); outline-offset: 3px; }
/* the halo: a ring that breathes with your actual voice, not with a clock. */
.pf-voice-btn .pf-voice-halo { position: absolute; inset: -1px; border-radius: 999px; pointer-events: none;
                               box-shadow: 0 0 0 0 color-mix(in srgb, var(--pf-warn) 40%, transparent);
                               opacity: 0; transition: opacity .2s ease; }
.pf-voice-btn.is-recording { background: var(--pf-warn); border-color: var(--pf-warn); color: #fff; }
.pf-voice-btn.is-recording .pf-voice-halo {
  opacity: 1;
  box-shadow: 0 0 0 calc(3px + var(--lvl) * 11px) color-mix(in srgb, var(--pf-warn) calc(30% - var(--lvl) * 22%), transparent);
}
.pf-voice-btn.is-busy { color: var(--pf-muted); cursor: progress; }
.pf-voice-ico { display: inline-flex; }
.pf-voice-btn .pf-ico-stop { display: none; }
.pf-voice-btn.is-recording .pf-ico-mic  { display: none; }
.pf-voice-btn.is-recording .pf-ico-stop { display: inline; }
/* the clock only exists while it is counting — a frozen 0:00 is furniture. */
.pf-voice-time { font-family: var(--pf-mono); font-size: 11.5px; font-weight: 500;
                 padding-left: 8px; margin-left: 2px; border-left: 1px solid rgba(255,255,255,.35);
                 font-variant-numeric: tabular-nums; }
.pf-voice-status { font-size: 12.5px; color: var(--pf-muted); min-height: 1em; }
.pf-voice-status .pf-voice-hint { color: var(--pf-muted); }
.pf-voice-status kbd { font: 500 11px/1 var(--pf-mono); border: 1px solid var(--pf-line-2);
                       border-bottom-width: 2px; border-radius: 4px; padding: 2px 4px;
                       background: var(--pf-surface); color: var(--pf-muted); }
/* what it heard, in your own words. Quoted, so it reads as testimony. */
.pf-voice-said { margin: 10px 2px 0; font-family: var(--pf-disp); font-size: 15px; color: var(--pf-ink);
                 animation: pf-said-in .34s cubic-bezier(.2,.8,.25,1) both; }
.pf-voice-said::before { content: "“"; color: var(--pf-muted); }
.pf-voice-said::after  { content: "”"; color: var(--pf-muted); }
.pf-voice-said.is-sent { color: var(--pf-ok); }
@keyframes pf-said-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

.pf-braincard { display: flex; align-items: center; gap: 20px; margin: 18px 0 6px;
                padding: 16px 20px; background: var(--pf-surface);
                border: 1px solid var(--pf-line); border-radius: var(--pf-radius);
                box-shadow: 0 14px 30px -26px rgba(28,30,33,.35); }
.pf-bc-id { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pf-bc-name { display: inline-flex; align-items: center; gap: 9px; text-decoration: none;
              font-family: var(--pf-disp); font-weight: 700; font-size: 17px;
              letter-spacing: -.01em; color: var(--pf-ink); }
a.pf-bc-name:hover { color: var(--pf-navy); }
.pf-bc-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--pf-muted);
             flex: none; transition: background-color .3s ease; }
.pf-bc-dot.is-working { background: var(--pf-ok);
                        box-shadow: 0 0 0 3px color-mix(in srgb, var(--pf-ok) 18%, transparent);
                        animation: pf-breathe 2.6s ease-in-out infinite; }
/* The ONE place the machine's state is written — the pulse owns it while idle
   or working, the voice UX borrows it while listening/transcribing. */
.pf-bc-state { font-size: 13px; color: var(--pf-muted);
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-bc-model { font-family: var(--pf-mono); font-size: 10.5px; letter-spacing: .1em;
               text-transform: uppercase; color: var(--pf-muted); }
.pf-braincard .pf-trace { margin-left: auto; }
.pf-bc-doors { display: flex; align-items: center; gap: 10px; flex: none; }
/* The doors are plain .pf-btn pills now — the box model, floors and focus
   ring come from THE BUTTON. (The 2026-07-30/31 "unequal twins" bug lived in
   exactly the bespoke rules this replaced: two hand-rolled box models.) Only
   the voice pill's card dress remains: bare face, pill shape. */
.pf-braincard .pf-voice { margin: 0; flex-wrap: nowrap; }
.pf-braincard .pf-voice-btn { border-radius: 999px;
  background: none; box-shadow: none; border-color: var(--pf-line-2); }
/* While recording the label is empty (icon + timer only) — an empty span must
   not hold a flex gap open. */
.pf-braincard #voice-label:empty { display: none; }
/* Narration lives in the state line; the pill-side status only carries the
   short mechanics (Esc hint, errors), and never widens the card. */
.pf-braincard .pf-voice-status { max-width: 200px; }
/* What it heard, quoted inside the card: under the state line, clamped to two
   lines so a long take cannot unfold the whole card. The [hidden] rule matters:
   a stylesheet `display` would otherwise override the hidden attribute and
   paint a pair of empty quotes into the card. */
.pf-braincard .pf-voice-said { margin: 2px 0 0; font-size: 13px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.pf-braincard .pf-voice-said[hidden] { display: none; }

/* the trace. still when idle, alive when working — never the other way round. */
.pf-trace { display: flex; gap: 3px; align-items: center; height: 40px; }
.pf-trace i { width: 3px; border-radius: 2px; background: var(--pf-warn); opacity: .9; }
.pf-braincard.is-working .pf-trace i { animation: pf-pulse 1.5s ease-in-out infinite; }
/* :not(.is-hearing) — while it listens, the trace IS the microphone, and a
   flat idle bar would overrule the voice with an !important. */
.pf-braincard.is-idle:not(.is-hearing) .pf-trace i { background: var(--pf-line-2); height: 3px !important; }
@keyframes pf-pulse { 0%, 100% { transform: scaleY(.25) } 50% { transform: scaleY(1) } }

/* ---- the machine, listening ----
   The whole point: speaking does not light up a button, it makes the MACHINE
   react. While the mic is open, JS writes every bar's height from the live
   FFT — the trace on the card becomes your voice. No keyframe may compete
   with that, hence `animation: none` and the transform reset (the working
   pulse leaves a scaleY behind). */
.pf-braincard.is-hearing .pf-trace i {
  animation: none !important; transform: none !important;
  transition: height .06s linear, background-color .3s ease;
  will-change: height; }
.pf-braincard.is-listening .pf-trace i { background: var(--pf-warn); opacity: 1; }
/* transcribing: it stops hearing and starts thinking — cool, not warm, so the
   two states can never be confused at a glance. A sweep runs through the bars
   (also JS-driven) the way a scanner passes over what you just said. */
.pf-braincard.is-thinking .pf-trace i { background: var(--pf-mint); opacity: .85; }
/* it heard you: the machine flashes its own colour of "yes". */
.pf-braincard.is-heard .pf-trace i { background: var(--pf-mint); }
.pf-braincard.is-listening { border-color: color-mix(in srgb, var(--pf-warn) 60%, var(--pf-line));
                             box-shadow: 0 0 0 1px color-mix(in srgb, var(--pf-warn) 45%, transparent),
                                         0 14px 30px -26px rgba(28,30,33,.35); }
@media (prefers-reduced-motion: reduce) {
  .pf-braincard.is-hearing .pf-trace i { transition: none; }
  .pf-voice-btn.is-recording .pf-voice-halo { box-shadow: 0 0 0 3px color-mix(in srgb, var(--pf-warn) 30%, transparent); }
  .pf-voice-said { animation: none; }
}

/* On a phone the card stacks: identity, then the doors at thumb width. The
   trace goes — at 390px it is decoration fighting the doors for room. */
@media (max-width: 640px) {
  .pf-braincard { flex-wrap: wrap; gap: 12px; }
  .pf-braincard .pf-trace { display: none; }
  .pf-bc-doors { width: 100%; }
  .pf-bc-doors .pf-btn { flex: 1; }
  .pf-braincard .pf-voice { flex: 1; }
  .pf-braincard .pf-voice-btn { width: 100%; }
  .pf-braincard .pf-voice-status { display: none; }
}

/* ---- boards that carry content, not counts ---- */
.pf-board { display: block; text-decoration: none; color: inherit; background: var(--pf-surface);
            border: 1px solid var(--pf-line); border-radius: var(--pf-radius);
            padding: 22px 22px 6px; transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
.pf-board:hover { transform: translateY(-2px); border-color: var(--pf-line-2);
                  box-shadow: 0 20px 38px -22px rgba(28, 30, 33, .24); }
.pf-board:focus-visible { outline: 2px solid var(--pf-navy); outline-offset: 3px; }
.pf-board-h { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.pf-board-h h2 { font-family: var(--pf-disp); font-weight: 600; font-size: 24px; letter-spacing: -.01em; }
.pf-board-c { font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted); white-space: nowrap; }
.pf-board-c.is-wanted { color: var(--pf-warn); font-weight: 500; }
.pf-lines { margin-top: 14px; }
.pf-line { display: flex; gap: 11px; align-items: baseline; padding: 11px 0;
           border-top: 1px solid var(--pf-line); }
.pf-line-s { width: 6px; height: 6px; border-radius: 50%; background: var(--pf-line-2); flex: none;
             transform: translateY(-1px); }
.pf-line-s.is-doing { background: var(--pf-warn); box-shadow: 0 0 0 3px rgba(180, 115, 42, .16); }
.pf-line-s.is-done { background: var(--pf-ok); }
.pf-line p { font-size: 14.5px; color: var(--pf-muted); line-height: 1.35; flex: 1; }
.pf-line time { font-family: var(--pf-mono); font-size: 10.5px; color: var(--pf-muted); flex: none; }
.pf-board-more { display: block; padding: 13px 0; border-top: 1px solid var(--pf-line);
                 font-size: 13px; color: var(--pf-muted); font-weight: 500; }

/* ── staleness ───────────────────────────────────────────────────────────────
   A board is published by the Brain; this says so when the last publish is old
   enough to matter (or never happened). It wears --pf-warn, the same colour the
   rest of the system uses for "this wants a human" — never an error red: a
   stale board is not broken, it is unattended, and the rows below it may still
   be perfectly true. The point is that the reader knows which he is looking at.

   Deliberately quiet on the dashboard card (one line under the heading) and
   explicit on the board page itself (a banner above the content), because the
   card is a glance and the page is where somebody acts on what he sees. */
.pf-stale { display: flex; align-items: baseline; gap: 9px; margin: 0 0 20px;
            padding: 11px 14px; border: 1px solid var(--pf-line-2); border-radius: var(--pf-radius);
            background: rgba(180, 115, 42, .06); }
.pf-stale-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pf-warn);
                flex: none; transform: translateY(-1px); box-shadow: 0 0 0 3px rgba(180, 115, 42, .16); }
.pf-stale p { font-size: 13px; color: var(--pf-muted); line-height: 1.4; }
.pf-board-stale { display: flex; align-items: baseline; gap: 8px; margin-top: 6px; }
.pf-board-stale span { width: 5px; height: 5px; border-radius: 50%; background: var(--pf-warn);
                       flex: none; transform: translateY(-1px); }
.pf-board-stale p { font-family: var(--pf-mono); font-size: 10.5px; color: var(--pf-warn);
                    line-height: 1.35; }
.pf-board:hover .pf-board-more { color: var(--pf-navy); }

/* ---- apps: a shelf. places you go, not things that pile up. ---- */
.pf-shelf { background: var(--pf-surface); border: 1px solid var(--pf-line);
            border-radius: var(--pf-radius); overflow: hidden; }
.pf-app { display: flex; align-items: center; gap: 16px; padding: 15px 20px; text-decoration: none;
          color: inherit; border-bottom: 1px solid var(--pf-line); transition: background .14s ease, padding-left .14s ease; }
.pf-app:last-child { border-bottom: 0; }
.pf-app:hover { background: var(--pf-surface-2); padding-left: 24px; }
.pf-app:focus-visible { outline: 2px solid var(--pf-navy); outline-offset: -2px; }
/* The number the owner says out loud: "share app 2". A ledger line-number, not a
   chip — slim mono, tabular, never boxed. */
.pf-app-n { font-family: var(--pf-mono); font-variant-numeric: tabular-nums; font-size: 12px;
            color: var(--pf-muted); flex: none; width: 2.2em; text-align: right; }
.pf-app-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pf-ok); flex: none; }
.pf-app h3 { font-size: 15.5px; font-weight: 600; letter-spacing: -.01em; min-width: 148px; }
.pf-app-path { font-family: var(--pf-mono); font-size: 12.5px; color: var(--pf-muted); flex: 1; }
.pf-app-vis { font-family: var(--pf-sans); font-size: 10.5px; text-transform: uppercase; letter-spacing: .1em;
              color: var(--pf-muted); background: var(--pf-surface-2); border-radius: 99px;
              padding: 3px 10px; font-weight: 500; }
.pf-app-go { color: var(--pf-muted); }
.pf-app:hover .pf-app-go { color: var(--pf-navy); }

@media (prefers-reduced-motion: reduce) {
  .pf-bc-dot, .pf-trace i { animation: none !important; }
  .pf-board, .pf-app, .pf-btn { transition: none !important; }
}
@media (max-width: 900px) {
  .pf-trace { height: 30px; }
}
@media (max-width: 560px) {
  .pf-app { flex-wrap: wrap; gap: 10px; }
  .pf-app h3 { min-width: 0; }
  .pf-app-path { flex-basis: 100%; order: 3; }
}

/* ---- SIGNATURE: the meetings board ----
   A meeting is a record; an unanswered question is a job. So the question is the
   loud thing here, and the meeting is the quiet frame around it. */
.mt { background: var(--pf-surface); border: 1px solid var(--pf-line); border-radius: var(--pf-radius);
      padding: 22px 24px; margin-bottom: 16px; }
.mt-h { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.mt-h h2 { font-family: var(--pf-disp); font-weight: 600; font-size: 19px; letter-spacing: -.01em;
           color: var(--pf-ink); margin: 0; }
.mt-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 6px 0 0;
           font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); }
.mt-meta code { color: var(--pf-muted); }
.mt-status { text-transform: uppercase; letter-spacing: .1em; }
.mt-goal { margin: 12px 0 0; color: var(--pf-muted); font-size: 14px; line-height: 1.6; }

/* the people in the room */
.mt-people { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.mt-person { font-size: 12px; font-weight: 500; color: var(--pf-ink);
             background: var(--pf-surface-2); border: 1px solid var(--pf-line);
             border-radius: 999px; padding: 4px 10px; }

/* the questions */
.mt-qs { list-style: none; margin: 16px 0 0; padding: 0; }
.mt-qs > li + li { margin-top: 12px; }
.mt-q { border-left: 2px solid var(--pf-line-2); padding: 2px 0 2px 14px; }
.mt-q.is-open { border-left-color: var(--pf-warn); }
.mt-q-h { display: flex; align-items: baseline; gap: 8px; }
.mt-q-h p { margin: 0; font-size: 14.5px; font-weight: 600; color: var(--pf-ink); line-height: 1.5; }
.mt-q-mark, .mt-a-mark { font-family: var(--pf-mono); font-size: 10.5px; font-weight: 500;
                         color: var(--pf-muted); flex: none; }
.mt-q.is-open .mt-q-mark { color: var(--pf-warn); }
.mt-by { display: block; margin-top: 3px; font-family: var(--pf-mono); font-size: 11px;
         color: var(--pf-muted); }
.mt-as { list-style: none; margin: 8px 0 0; padding: 0; }
.mt-as > li + li { margin-top: 6px; }
.mt-a { display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: baseline; }
.mt-a p { margin: 0; font-size: 14px; color: var(--pf-muted); line-height: 1.6; }
.mt-a .mt-by { grid-column: 2; margin-top: 0; }

/* =====================================================================   12. THE KANBAN  —  the to-do board as three columns you can move cards across
   ----------------------------------------------------------------------------
   The board was three stacked read-only panels. It is now three side-by-side
   columns — To-do / In progress / Done — and a card can be dragged, or nudged
   with its buttons, from one to the next. Each move POSTs to /todo/move, which
   runs `foundry todo` on the box: the Brain is still the writer, the click just
   pulls the same lever. Buttons exist alongside drag because drag does not work
   on a phone, and this board is read on one.
   ========================================================================== */
.kanban { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: start; }

/* White, like every other board's panel. This was --pf-paper: identical to the
   page behind it, so the column had no edge and the done cards inside it (also
   --pf-paper) had no edge either. The done tone below now reads BECAUSE the
   column it sits on is white. */
.kan-col { background: var(--pf-surface); border: 1px solid var(--pf-line);
           border-radius: var(--pf-radius); padding: 10px; min-height: 120px;
           transition: background-color .14s ease, box-shadow .14s ease, border-color .14s ease; }
/* The live drop target while a card is held over it. */
.kan-col.is-drop { background: var(--pf-surface-2); border-color: var(--pf-navy);
                   box-shadow: inset 0 0 0 1px var(--pf-navy); }

.kan-col-head { display: flex; align-items: baseline; justify-content: space-between;
                gap: 8px; padding: 6px 8px 12px; }
.kan-col-title { font-family: var(--pf-sans); font-weight: 600; font-size: 13px;
                 letter-spacing: .04em; text-transform: uppercase; color: var(--pf-muted); }
.kan-col-n { font-family: var(--pf-mono); font-variant-numeric: tabular-nums;
             font-size: 12px; color: var(--pf-muted); }
/* The column's identity lives in one dot on its header, not a wash of colour. */
.kan-col-doing .kan-col-title { color: var(--pf-warn); }
.kan-col-done  .kan-col-title { color: var(--pf-ok); }

.kan-cards { display: flex; flex-direction: column; gap: 8px; min-height: 44px; }
/* The empty column still reads as a place a card can land. */
.kan-empty { border: 1px dashed var(--pf-line-2); border-radius: 9px; padding: 14px 12px;
             text-align: center; font-size: 12px; color: var(--pf-muted); }

.kan-card { background: var(--pf-surface); border: 1px solid var(--pf-line);
            border-radius: 10px; padding: 11px 12px 9px;
            box-shadow: 0 8px 20px -18px rgba(28,30,33,.5);
            cursor: grab; transition: box-shadow .12s ease, transform .12s ease, opacity .12s ease; }
.kan-card:hover { box-shadow: 0 12px 26px -18px rgba(28,30,33,.55); }
.kan-card.is-dragging { opacity: .4; cursor: grabbing; }
.kan-card:focus-visible { outline: 2px solid var(--pf-navy); outline-offset: 2px; }

.kan-card-top { display: flex; align-items: baseline; gap: 9px; }
.kan-card-text { flex: 1; min-width: 0; margin: 0; font-family: var(--pf-sans);
                 font-size: 15px; font-weight: 500; line-height: 1.32; color: var(--pf-ink);
                 overflow-wrap: anywhere; }
/* Same inversion as .ledger-done above: on a white column a warm fill ADVANCES.
   The strike, the muted text and a grey left edge carry "this is history". */
.kan-col-done .kan-card { box-shadow: none; background: var(--pf-surface);
                          border-left: 2px solid var(--pf-line-2); }
.kan-col-done .kan-card-text { font-weight: 400; color: var(--pf-muted); text-decoration: line-through;
                               text-decoration-color: var(--pf-line-2); }

.kan-card-foot { display: flex; align-items: center; justify-content: space-between;
                 gap: 8px; margin-top: 9px; }
.kan-card-time { font-family: var(--pf-mono); font-variant-numeric: tabular-nums;
                 font-size: 11px; color: var(--pf-muted); }
/* Move controls: one nudge each way. The label is set per-button in JS so it
   says where the card goes ("Start", "Done", "Reopen"), never a bare arrow. */
.kan-moves { display: flex; gap: 4px; }
.kan-move { font-family: var(--pf-sans); font-size: 11px; font-weight: 600;
            color: var(--pf-muted); background: var(--pf-surface-2);
            border: 1px solid transparent; border-radius: 7px; padding: 4px 9px;
            cursor: pointer; transition: background-color .12s ease, color .12s ease; }
.kan-move:hover { background: var(--pf-navy); color: #fff; }
.kan-move-done:hover { background: var(--pf-ok); }
.kan-move:disabled { opacity: .4; cursor: default; }
.kan-card.is-saving { opacity: .55; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .kan-col, .kan-card, .kan-move { transition: none !important; }
}
/* Below 900px three columns cannot sit side by side and stay legible (at 768
   each column is ~230px and every card wraps to three lines), so they stack —
   the same order, top to bottom. Drag across a stack is unreliable, so the
   move buttons carry the whole job here. */
@media (max-width: 900px) {
  .kanban { grid-template-columns: 1fr; gap: 12px; }
}
/* the meeting's handle — the number the owner says out loud ("close meeting 2") */
.mt-num { font-family: var(--pf-mono); font-size: 13px; font-weight: 500; color: var(--pf-muted);
          margin-right: 6px; }
/* a closed meeting is still the record, it just stops asking for anything */
/* The dashed border is what says "settled" here; the paper fill it used to
   carry now ADVANCES against the white sheet (see .ledger-done). */
.mt.is-done { background: var(--pf-surface); border-style: dashed; }
.mt.is-done h2 { color: var(--pf-muted); }
.mt.is-done .mt-q { border-left-color: var(--pf-line); }
.mt.is-done .mt-q-h p { font-weight: 500; color: var(--pf-muted); }
/* an archived meeting is off the active board but kept — quieter than done, and
   one click from restore */
.mt.is-archived { background: var(--pf-surface); border-style: dashed; opacity: .82; }
.mt.is-archived h2 { color: var(--pf-muted); }

/* further information attached after the fact — reads like a meeting answer */
.mt-notes { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--pf-line); }
.mt-notes-h { margin: 0 0 8px; font-family: var(--pf-mono); font-size: 11.5px; font-weight: 500;
              letter-spacing: .12em; text-transform: uppercase; color: var(--pf-muted); }
.mt-note + .mt-note { margin-top: 8px; }
.mt-note p { margin: 0; font-size: 14px; color: var(--pf-ink); line-height: 1.6; }
.mt-note .mt-by { margin-top: 2px; }

/* the card's controls: archive / restore, and the add-note fold */
.mt-actions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px;
              margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--pf-line); }
/* Closed, the fold is a button sitting beside Archive. OPEN, it takes the whole
   row: `.mt-actions` is a flex container, so a shut-tight `<details>` is a
   shrink-to-fit item, and a body declared `width: 100%` of a shrink-to-fit
   parent resolves against the SUMMARY's width — a 235px stub with the
   placeholder clipped mid-word ("Add note auf desktop/mobile … sieht kacke aus",
   owner 2026-08-12). `flex-basis: 100%` when open wraps it onto its own line and
   gives the field the card to work with. */
.mt-addnote { flex: 0 0 auto; }
.mt-addnote[open] { flex: 1 1 100%; }
.mt-addnote > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center;
                        padding: 6px 10px; font-size: 12px; color: var(--pf-muted);
                        border: 1px solid var(--pf-line); border-radius: var(--pf-radius-sm); }
.mt-addnote > summary::-webkit-details-marker { display: none; }
.mt-addnote > summary::after { content: "▾"; margin-left: 6px; font-size: 11px; }
.mt-addnote[open] > summary::after { content: "▴"; }
.mt-addnote > summary:hover { color: var(--pf-ink); border-color: var(--pf-faint); }
.mt-addnote-body { display: flex; flex-direction: column; gap: 10px; margin-top: 10px;
                   width: 100%; max-width: 720px; }
/* A note is prose, not a search box: it opens at ~6 lines so a paragraph is
   visible while it is being typed, and grows from there. Two classes deep on
   purpose — the mobile floor further down is `textarea.pf-input { min-height:
   44px }` and would otherwise squash this field back to one line on a phone,
   which is where a note is most likely to be dictated. */
.mt-addnote-body .mt-note-input { display: block; width: 100%; min-height: 148px;
                                  resize: vertical; line-height: 1.6; }
@media (pointer: coarse) {
  .mt-addnote-body .mt-note-input { font-size: 16px; }
}
.mt-addnote-body .pf-btn { align-self: flex-start; }


/* =====================================================================   13. THE RESEARCH LIBRARY  —  a shelf you browse, and a page you read on.
   ----------------------------------------------------------------------------
   The Research board used to print all 15 writeups, in full, one under the next:
   an 8,000px page of prose where every entry was cut off at 1,200 characters and
   there was nowhere to go for the rest. It answered no question a reader arrives
   with. "What has my Brain researched?" needs a SHELF. "What did it find out?"
   needs a PAGE. Those are two different objects, so this is two screens.

   The library (index.html) is a shelf: one line per writeup, its question, and
   the answer it reached. The reading room (entry.html) is the writeup itself,
   whole, with its own table of contents and its sources listed as receipts.

   Everything here is composed from the tokens in sections 1-9. The one new idea
   is EDITORIAL RESTRAINT: a research library should look like a well-set journal,
   so the display serif does the work, hairlines do the dividing, and nothing is
   coloured that does not have to be.
   ========================================================================== */

/* The library spans the shell — a shelf, not a column of prose. It no longer
   needs a rule of its own: since 2026-08-24 every board's frame spans the shell
   (section 10), so this used to be the exception and is now simply the rule. */

/* ---- the head: a headline, and the shelf's own vital signs ---- */
.lib-head { display: flex; align-items: flex-end; justify-content: space-between;
            gap: 30px; flex-wrap: wrap; }
.lib-head .pf-lead { max-width: 52ch; margin-bottom: 0; }

/* ---- the on-demand visualization affordance --------------------------------
   A page-level NOTE, not a button: it tells the owner a capability the board
   otherwise hides — that the Brain will build a bespoke visual view of any
   writeup on request and deploy it to his own server. Navy-tinted so it reads
   as guidance rather than a warning (the amber `.pf-stale` reads as trouble),
   and composed entirely from tokens: the tint is a `color-mix` off the accent
   navy, so it introduces no new hex. It spans the wide library head. */
.pf-viz-cta { display: flex; align-items: flex-start; gap: 12px; margin: 0 0 26px;
              padding: 14px 16px; border: 1px solid var(--pf-line-2); border-radius: var(--pf-radius);
              background: color-mix(in srgb, var(--pf-navy) 4%, transparent); }
.pf-viz-cta svg { flex: none; width: 18px; height: 18px; margin-top: 1px; color: var(--pf-navy); }
.pf-viz-cta p { margin: 0; font-size: 13px; line-height: 1.55; color: var(--pf-muted); max-width: 74ch; }
.pf-viz-cta strong { color: var(--pf-ink); font-weight: 600; }
.pf-viz-cta a { color: var(--pf-navy); font-weight: 600; text-decoration: none; }
.pf-viz-cta a:hover { text-decoration: underline; }


/* ---- THE BRIEFING — a front page, not a table --------------------------------
   The board is read in about twenty seconds by someone who did not commission the
   research. A flat list of fifteen equal titles spends those seconds making that
   person hunt for the one good thing; a front page spends them PROVING there is a
   good thing. So the newest writeup is promoted to a lead story carrying its
   actual finding — not its title, the finding — and the other fourteen fall into
   a tight index beneath it.

   The rail on the left is the other half of the fix. With search and filters
   sitting above a full-bleed list, the reading column stretched to the shell and
   preview lines ran to ~120 characters, which is what made the spacing feel
   wrong: the measure was broken, not the padding. Filters live in the rail now,
   and the reading column keeps a measure it can be read at. */
.brief { display: grid; grid-template-columns: 200px 1fr; gap: 40px; align-items: start; }

/* the rail: sticky, because a filter you have to scroll back up to reach is a
   filter nobody uses on the twelfth row */
.rail { position: sticky; top: 24px; }
.rail-h { font-family: var(--pf-sans); font-size: 10.5px; font-weight: 600; letter-spacing: .14em;
          text-transform: uppercase; color: var(--pf-muted); margin: 0 0 10px; }
/* `display: block` is load-bearing, not tidiness: this is a <label>, and an
   INLINE box's vertical margin does nothing at all — so the 22px below never
   applied and "Topics" sat welded to the search field (owner, 2026-08-12). */
.rail-search { display: block; position: relative; margin-bottom: 22px; }
.rail-search .pf-input { width: 100%; padding-left: 30px; }
.rail-search svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
                   width: 14px; height: 14px; color: var(--pf-muted); pointer-events: none; }
.rail-tags { display: flex; flex-direction: column; gap: 1px; }
.rail-tag { display: flex; align-items: center; justify-content: space-between; gap: 8px;
            padding: 6px 9px; border: 0; border-radius: 7px; background: none; cursor: pointer;
            font: 500 12.5px/1.4 var(--pf-sans); color: var(--pf-muted); text-align: left;
            transition: background-color .12s ease, color .12s ease; }
.rail-tag:hover { background: var(--pf-surface-2); color: var(--pf-ink); }
.rail-tag[aria-pressed="true"] { background: var(--pf-navy); color: #fff; }
.rail-tag[aria-pressed="true"] .rail-c { color: rgba(255,255,255,.7); }
/* A topic the current search has emptied: still there (the rail never shrinks
   underfoot), visibly out of play. */
.rail-tag--empty:not([aria-pressed="true"]) { opacity: .4; cursor: default; }
.rail-c { font-family: var(--pf-mono); font-size: 10.5px; color: var(--pf-muted);
          font-variant-numeric: tabular-nums; }

/* ---- the lead story ---- */
.lead-card { display: block; position: relative; background: var(--pf-surface);
             border: 1px solid var(--pf-line); border-radius: var(--pf-radius);
             padding: 30px 32px 26px; margin-bottom: 30px; text-decoration: none; color: inherit;
             box-shadow: 0 18px 40px -30px rgba(28,30,33,.4);
             transition: box-shadow .16s ease, border-color .16s ease; overflow: hidden; }
.lead-card::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
                    background: linear-gradient(90deg, var(--pf-navy),
                                color-mix(in srgb, var(--pf-navy) 25%, transparent)); }
.lead-card:hover { border-color: var(--pf-line-2); box-shadow: 0 26px 50px -30px rgba(28,30,33,.5); }
.lead-card:focus-visible { outline: 2px solid var(--pf-navy); outline-offset: 2px; }
.lead-card:hover .lead-title { color: var(--pf-navy); }
.lead-flag { display: inline-flex; align-items: center; gap: 7px; margin-bottom: 14px;
             font: 600 10px/1 var(--pf-sans); letter-spacing: .16em; text-transform: uppercase;
             color: var(--pf-navy); }
.lead-flag::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--pf-ok); }
/* The lead story's handle, on the flag's line. Same number the rows below show,
   so the eye learns one shape for "how I name this writeup". */
.lead-n { margin: 0 0 14px 10px; font-family: var(--pf-mono); font-size: 11.5px; font-weight: 500;
          color: var(--pf-muted); font-variant-numeric: tabular-nums; }
.lead-title { margin: 0 0 14px; font-family: var(--pf-disp); font-size: 30px; font-weight: 700;
              line-height: 1.14; letter-spacing: -.02em; max-width: 22ch;
              transition: color .16s ease; }
.lead-q { margin: 0 0 16px; font-family: var(--pf-disp); font-style: italic; font-size: 16px;
          line-height: 1.5; color: var(--pf-muted); max-width: 62ch; }
/* three lines of the FINDING. Not the filing note, not the methodology — the
   answer, which is the only part of a writeup that earns a click. */
.lead-body { margin: 0; font-size: 14px; line-height: 1.65; color: var(--pf-ink); max-width: 66ch;
             display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
             line-clamp: 3; overflow: hidden; }
.lead-foot { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--pf-line);
             display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
             font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); }
.lead-foot > .m + .m::before { content: "·"; margin-right: 8px; color: var(--pf-line-2); }
.lead-foot .m-src { color: var(--pf-navy); font-weight: 500; }
.lead-go { margin-left: auto; font-family: var(--pf-sans); font-weight: 600; font-size: 12.5px;
           color: var(--pf-navy); }
.lead-card:hover .lead-go { text-decoration: underline; }

/* ---- the index beneath: hairline rows, like a journal's table of contents.
   The whole row is the link. Fifteen boxes read as fifteen unrelated things;
   fifteen lines read as one body of work. (A card-per-writeup grid was built and
   shown alongside this and turned down — the same "sparse, floating" look that
   was rejected for the reading boards once before. It does not live here as dead
   CSS.) */
.idx-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
         margin: 0 0 12px; }
.idx-h h2 { margin: 0; font-family: var(--pf-disp); font-size: 15px; font-weight: 700;
            letter-spacing: -.01em; }
.idx-count { font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted); white-space: nowrap; }

/* ONE framed panel, divided by hairlines — never a .pf-card per row. A card per
   writeup gives each one a box and a shadow, and fifteen boxes read as fifteen
   unrelated things floating in space (the look that was tried here and turned
   down). The panel is what makes the index read as a single body of work.
   Pinned by tests/test_base_apps.py: the reading boards are one framed panel. */
.lib { background: var(--pf-surface); border: 1px solid var(--pf-line);
       border-radius: var(--pf-radius); box-shadow: 0 14px 30px -24px rgba(28,30,33,.35);
       overflow: hidden; }
.lib > * + * { border-top: 1px solid var(--pf-line); }

.idx-item { display: grid; grid-template-columns: 2.4rem 1fr auto; align-items: baseline;
            gap: 0 18px; padding: 17px 22px 16px;
            text-decoration: none; color: inherit; transition: background-color .14s ease; }
.idx-item:hover { background: var(--pf-surface-2); }
.idx-item:focus-visible { outline: 2px solid var(--pf-navy); outline-offset: -2px; }
.idx-item:hover .idx-title { color: var(--pf-navy); }
.idx-n { font-family: var(--pf-disp); font-weight: 700; font-size: 13px; color: var(--pf-muted);
         font-variant-numeric: tabular-nums; user-select: none; }
.idx-title { margin: 0 0 4px; font-family: var(--pf-disp); font-size: 17px; font-weight: 700;
             line-height: 1.25; letter-spacing: -.01em; max-width: 40ch;
             transition: color .14s ease; }
/* ONE line of preview, hard. A preview that runs to six lines is not a preview,
   it is the wall of text this board used to be. */
.idx-sub { margin: 0; font-size: 13px; line-height: 1.5; color: var(--pf-muted); max-width: 72ch;
           display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
           line-clamp: 1; overflow: hidden; }
.idx-sub.is-q { font-family: var(--pf-disp); font-style: italic; }
.idx-meta { margin-top: 7px; display: flex; gap: 8px; flex-wrap: wrap;
            font-family: var(--pf-mono); font-size: 10.5px; color: var(--pf-muted); }
.idx-meta > * + *::before { content: "·"; margin-right: 8px; color: var(--pf-line-2); }
.idx-meta .m-src { color: var(--pf-navy); }
.idx-right { text-align: right; align-self: center; }
.idx-date { font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted); white-space: nowrap; }

/* an in-progress writeup is still a writeup, but it says so */
.idx-wip, .lead-wip {
  display: inline-block; margin-top: 6px;
  font: 600 9.5px/1 var(--pf-sans); letter-spacing: .08em; text-transform: uppercase;
  color: var(--pf-warn); background: color-mix(in srgb, var(--pf-warn) 10%, transparent);
  padding: 3px 7px; border-radius: 999px;
}

/* nothing matched the filter — a dead end must still look like the board */
.idx-none { padding: 40px 22px; text-align: center; color: var(--pf-muted); }

/* the tag chips, still worn by the reading room's header (entry.html) */
.lib-chips { display: flex; gap: 5px; flex-wrap: wrap; margin-left: 4px; }
.lib-chip { font-family: var(--pf-sans); font-size: 10.5px; font-weight: 500; letter-spacing: .02em;
            color: var(--pf-muted); background: var(--pf-surface-2); border-radius: 4px;
            padding: 2px 6px; }
.lib-chips .lib-chip::before { content: none; }

/* ---- THE READING ROOM (entry.html) ----
   Prose on the left at a real measure, the writeup's own structure on the right.
   The sidebar is what turns a 2,000-word report into something you can stand in
   front of a CEO and navigate. */
.rd { display: grid; grid-template-columns: minmax(0, 1fr) 232px; gap: 44px; align-items: start; }
/* A grid item's default `min-width: auto` is its CONTENT's width, so the widest
   comparison table in the report set the floor for the whole column and pushed
   the page sideways on a phone — the table's own `overflow-x` never got a chance
   to do its job. This is the line that lets it. */
.rd > * { min-width: 0; }
.rd-back { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 500;
           color: var(--pf-muted); text-decoration: none; margin-bottom: 18px; }
.rd-back:hover { color: var(--pf-navy); }
.rd-back svg { transition: transform .12s ease; }
.rd-back:hover svg { transform: translateX(-3px); }

/* The writeup's handle, above its title: what the owner says to the Brain to
   mean THIS one. Mono and quiet as a label, but the number itself carries the
   page's ink colour — it is the part that gets read out loud. */
.rd-num { margin: 0 0 10px; font-family: var(--pf-mono); font-size: 11.5px; letter-spacing: .1em;
          text-transform: uppercase; color: var(--pf-muted); }
.rd-num span { color: var(--pf-ink); font-weight: 500; letter-spacing: .04em; }
.rd-title { font-family: var(--pf-disp); font-size: 34px; font-weight: 700; letter-spacing: -.02em;
            line-height: 1.12; margin: 0; color: var(--pf-ink); max-width: 22ch; }
.rd-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 0 8px; margin-top: 14px;
           font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); }
.rd-meta > * + *::before { content: "·"; margin-right: 8px; color: var(--pf-line-2); }
.rd-meta .lib-chips .lib-chip::before { content: none; }

/* the question, given the weight it deserves: this is the brief the Brain was
   handed, and the report below is the answer to exactly it. */
.rd-q { margin: 26px 0 0; padding: 16px 20px; border-left: 2px solid var(--pf-navy);
        background: var(--pf-surface); border-radius: 0 var(--pf-radius-sm) var(--pf-radius-sm) 0;
        font-family: var(--pf-disp); font-size: 17px; font-style: italic; line-height: 1.5;
        color: var(--pf-ink); }
.rd-q-k { display: block; font-family: var(--pf-sans); font-style: normal; font-size: 10.5px;
          font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
          color: var(--pf-navy); margin-bottom: 7px; }

/* The MEASURE applies to prose, not to everything.
   Holding the whole report to 68ch squeezed its comparison tables into a column
   half their natural width, where the browser hyphen-less-ly broke "bedingt"
   into "beding t" to make a cell fit. Prose gets the measure; the objects that
   are as wide as they are — tables, code — get the whole column and carry their
   own horizontal scroll. */
.rd-body { margin-top: 30px; }
.rd-body .prose > * { max-width: 68ch; }
.rd-body .prose > .table-scroll,
.rd-body .prose > pre.code,
.rd-body .prose > hr { max-width: 100%; }
/* The report is the page now, not a passenger in a scroll box. `.prose-scroll`
   (a 26rem inner scroller) is deliberately NOT used here: a scrollbar inside a
   scrolling page is how the old board hid 90% of every writeup. */
.rd-body .prose { font-size: 15px; line-height: 1.72; }
.rd-body .prose h3 { font-size: 21px; margin: 2.1rem 0 .5rem; scroll-margin-top: 90px; }
.rd-body .prose h4 { font-size: 16.5px; margin: 1.5rem 0 .35rem; scroll-margin-top: 90px; }
.rd-body .prose h5, .rd-body .prose h6 { font-size: 14.5px; scroll-margin-top: 90px; }
.rd-body .prose hr { border: 0; border-top: 1px solid var(--pf-line); margin: 2rem 0; }
.rd-body .prose blockquote { margin: 1rem 0; padding: 2px 0 2px 16px;
                             border-left: 2px solid var(--pf-line-2); color: var(--pf-muted); }
.rd-body .prose ol { margin: .6rem 0; padding-left: 1.35rem; }
.rd-body .prose pre.code { background: var(--pf-surface-2); border: 1px solid var(--pf-line);
                           border-radius: var(--pf-radius-sm); padding: 14px 16px; overflow-x: auto;
                           font-family: var(--pf-mono); font-size: 12.5px; line-height: 1.6; }
.rd-body .prose pre.code code { background: none; border: 0; padding: 0; font-size: inherit; }

/* A comparison table IS the answer in most of these writeups. Set it like a
   table in a journal: hairlines, tabular figures, a shy header, and its own
   horizontal scroll so a wide one never pushes the page sideways. */
.table-scroll { overflow-x: auto; margin: 1.1rem 0; border: 1px solid var(--pf-line);
                border-radius: var(--pf-radius-sm); }
.prose table { border-collapse: collapse; width: 100%; font-size: 13.5px;
               font-variant-numeric: tabular-nums; }
.prose thead th { position: sticky; top: 0; background: var(--pf-surface-2); text-align: left;
                  font-family: var(--pf-sans); font-size: 11px; font-weight: 600; letter-spacing: .08em;
                  text-transform: uppercase; color: var(--pf-muted); padding: 10px 14px;
                  white-space: nowrap; border-bottom: 1px solid var(--pf-line); }
.prose tbody td { padding: 11px 14px; border-top: 1px solid var(--pf-line);
                  color: var(--pf-ink); vertical-align: top; line-height: 1.5; }
/* Section 10 sets `.prose { overflow-wrap: anywhere }` so a URL can never widen
   the panel. Inside a table cell that rule is too eager: it breaks ordinary German
   words mid-syllable to save a pixel. Break long words only when there is no other
   way, and at a word boundary first. */
.prose th, .prose td { overflow-wrap: break-word; }
.prose tbody tr:first-child td { border-top: 0; }
.prose tbody tr:hover { background: var(--pf-paper); }

/* ---- the rail: contents, then the receipts ---- */
.rd-rail { position: sticky; top: 22px; display: flex; flex-direction: column; gap: 22px; }
.rd-rail-k { font-family: var(--pf-sans); font-size: 10.5px; font-weight: 600; letter-spacing: .16em;
             text-transform: uppercase; color: var(--pf-muted); margin: 0 0 10px; }

.rd-toc { list-style: none; margin: 0; padding: 0; border-left: 1px solid var(--pf-line); }
.rd-toc li { margin: 0; }
.rd-toc a { display: block; padding: 6px 0 6px 14px; margin-left: -1px;
            border-left: 2px solid transparent; font-size: 13px; line-height: 1.4;
            color: var(--pf-muted); text-decoration: none;
            transition: color .12s ease, border-color .12s ease; }
.rd-toc a:hover { color: var(--pf-ink); }
/* the reader's place in the report, tracked by an IntersectionObserver */
.rd-toc a.is-here { color: var(--pf-navy); font-weight: 600; border-left-color: var(--pf-navy); }
.rd-toc .is-sub a { padding-left: 26px; font-size: 12.5px; color: var(--pf-muted); }
.rd-toc .is-sub a:hover, .rd-toc .is-sub a.is-here { color: var(--pf-navy); }

/* the sources: the writeup's receipts. A research report a CEO cannot trace back
   to its sources is an opinion. */
.rd-src { list-style: none; margin: 0; padding: 0; }
.rd-src li { padding: 7px 0; border-top: 1px solid var(--pf-line); }
.rd-src li:first-child { border-top: 0; }
.rd-src a { font-size: 12.5px; line-height: 1.4; color: var(--pf-muted); text-decoration: none;
            display: flex; gap: 7px; }
.rd-src a:hover { color: var(--pf-navy); }
.rd-src-n { font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted); flex: none; }
.rd-src-host { overflow-wrap: anywhere; }

/* attachments: named, not linked. The decks live in the vault on the Brain and
   the board is served from the web server, so this says WHAT exists and does not
   pretend it can hand it over. A dead download button would be the lie. */
.rd-files { list-style: none; margin: 0; padding: 0; font-family: var(--pf-mono); font-size: 11.5px;
            color: var(--pf-muted); }
.rd-files li { padding: 5px 0; overflow-wrap: anywhere; }
.rd-foot { margin-top: 36px; padding-top: 14px; border-top: 1px solid var(--pf-line);
           font-family: var(--pf-mono); font-size: 11px; color: var(--pf-muted); }

@media (max-width: 980px) {
  /* The rail stops being a rail: contents above the report, sources below it —
     ordered by DOM, so no reflow trickery is needed. */
  .rd { grid-template-columns: 1fr; gap: 24px; }
  .rd-rail { position: static; }
  .rd-title { font-size: 27px; max-width: none; }
}
@media (max-width: 900px) {
  /* The rail stops being a rail: search and topics sit above the stories as a
     normal bar. Sticky is dropped with it — a sticky block in normal flow on a
     phone just eats the top of the screen. */
  .brief { grid-template-columns: 1fr; gap: 24px; }
  .rail { position: static; }
  .rail-tags { flex-direction: row; flex-wrap: wrap; }
  .rail-tag { border: 1px solid var(--pf-line); border-radius: 999px; }
  .lead-card { padding: 24px 20px 20px; }
  .lead-title { font-size: 24px; max-width: none; }
  .lead-q { font-size: 15px; }
}
@media (max-width: 760px) {
  /* The date column folds under the title rather than squeezing it: on a 390px
     screen a third column leaves the title about twelve characters. */
  .idx-item { grid-template-columns: 2rem 1fr; gap: 0 12px; padding: 16px 14px; }
  .idx-right { grid-column: 2; text-align: left; margin-top: 8px; }
  .idx-title { font-size: 16px; max-width: none; }
  .idx-wip { margin-top: 0; margin-left: 8px; }
}
@media (prefers-reduced-motion: reduce) {
  .idx-item, .lead-card, .rd-back svg, .rail-tag { transition: none !important; }
}

/* =====================================================================   14. THE CHAT
   Retired 2026-07. The Cockpit's chat page is now the Hub's Desk, ported
   verbatim into apps/shared/chat.css — same class names, same visual system,
   loaded only by that page.

   The old section that stood here (its own .chat-msg, .chat-thread, .chat-pick
   and a `.chat [hidden]` block) was not merely dead: it loaded FIRST and set
   `display: flex` on `.chat-msg`, which quietly beat the UA sheet's
   `[hidden] { display: none }` and left every non-matching bubble on screen
   during a search. Two stylesheets owning one class name is not a style
   question, it is a bug generator — so it is gone rather than overridden.
   ============================================================================ */

/* =====================================================================   15. THE MOBILE OVERHAUL — "Needs you" (2026-07)
   ----------------------------------------------------------------------------
   Direction A from the July mobile review. The dashboard stops opening on an
   explanation of itself and opens on the work: a slim status strip, one serif
   lede, and the two-to-four things that actually want the owner as cards with
   their action reachable. The bottom bar drops from six destinations to three —
   Today · Ask · Boards — with Ask, the thing he does most, as the fat pill in
   the thumb's home position. Boards opens the six destinations as a sheet.

   Everything here is composed from the tokens in sections 1-9: no new hex, no
   new radius, no new font. Contrast note: --pf-faint (2.91:1) is retired as a
   TEXT colour across this file; timestamps, labels and meta lines read at
   --pf-muted (6.31:1). Faint survives only on hairlines, dots and disabled fills.
   ========================================================================== */

/* ---- the lede: one serif line, the answer the owner opened the app for ---- */
.pf-lede { padding: 20px 0 14px; }
.pf-lede-h { font-family: var(--pf-disp); font-size: 28px; line-height: 1.14; margin: 0;
             font-weight: 700; letter-spacing: -.01em; color: var(--pf-ink); max-width: 24ch; }
.pf-lede-h em { font-style: normal; color: var(--pf-muted); }
.pf-lede-sub { margin: 7px 0 0; font-size: 13.5px; color: var(--pf-muted); }

/* ---- the needs-you stack: one card per thing that wants a human ---- */
.pf-needs { display: flex; flex-direction: column; gap: 12px; }
.need { background: var(--pf-surface); border: 1px solid var(--pf-line);
        border-radius: var(--pf-radius); padding: 15px 16px 13px;
        box-shadow: 0 14px 30px -26px rgba(28,30,33,.35); }
/* The lead card wears the ember edge — the one place ember appears as signal,
   never as text: the single most-wanting thing on the page. */
.need--lead { border-left: 3px solid var(--pf-ember); }
.need-kicker { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.need-kicker > span:first-child { font-family: var(--pf-mono); font-size: 10.5px;
                 letter-spacing: .12em; text-transform: uppercase; color: var(--pf-muted);
                 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.need-age { margin-left: auto; flex: none; font-family: var(--pf-mono); font-size: 10.5px;
            color: var(--pf-muted); }
.need-h { margin: 0; font-family: var(--pf-sans); font-size: 16px; line-height: 1.35;
          font-weight: 600; letter-spacing: -.005em; color: var(--pf-ink); overflow-wrap: anywhere; }
.need-h em { font-style: italic; }
.need-h code { font-family: var(--pf-mono); font-size: .9em; }
.need-p { margin: 6px 0 0; font-size: 13.5px; line-height: 1.45; color: var(--pf-muted);
          overflow-wrap: anywhere; }
/* actions: 44px minimum, reachable one-handed. */
.need-acts { display: flex; gap: 8px; margin-top: 12px; }
.need-btn { flex: 1; min-height: 44px; border-radius: 10px;
            background: var(--pf-surface); box-shadow: none;
            font-size: 14px; text-align: center; padding: 0 10px; line-height: 1.2; }
.need-btn--primary { background: var(--pf-navy); border-color: var(--pf-navy); color: var(--pf-night-ink); }
.need-btn--primary:hover { background: var(--pf-navy-2); border-color: var(--pf-navy-2); }
.need-btn--primary.is-done { background: var(--pf-ok); border-color: var(--pf-ok); }
.need-btn--ghost { flex: 0 0 auto; min-width: 84px; color: var(--pf-muted); }
.need-btn--ghost:hover { color: var(--pf-ink); border-color: var(--pf-muted); }
.pf-needs-more { margin: 12px 2px 0; font-family: var(--pf-mono); font-size: 11.5px;
                color: var(--pf-muted); }
.pf-needs-clear { border: 1px dashed var(--pf-line-2); border-radius: var(--pf-radius);
                  padding: 22px 18px; text-align: center; color: var(--pf-muted); font-size: 14px; }
.pf-needs-clear p { margin: 0; }

/* "running without you", the quiet secondary block, stood here. Removed with
   its markup on the owner's call (2026-08-12): the dashed box named work that
   wanted nothing from him. Its classes went with it rather than lingering as
   dead rules — nothing on any page carries them. */

/* =====================================================================   THE THREE-TAB BAR — Today · Ask · Boards
   ----------------------------------------------------------------------------
   The bar's display mechanics (hidden by default, shown below 760px, never on
   the Desk) are unchanged in section 5. What changes here is the CONTENT: three
   slots, not six, with Ask as a navy pill in the middle where the thumb rests.
   ========================================================================== */
.pf-tabbar-inner { display: grid; grid-template-columns: 1fr 1.3fr 1fr; align-items: center;
                   gap: 6px; }
/* Ask: the fat pill. It is a link to chat — the thing the owner does most, in
   the easiest place a thumb reaches. */
.pf-ask { min-height: 48px; margin: 0 4px; border-radius: 24px; background: var(--pf-navy);
          color: var(--pf-night-ink); display: flex; align-items: center; justify-content: center;
          gap: 8px; text-decoration: none; font: 600 14.5px/1 var(--pf-sans); }
.pf-ask:hover, .pf-ask:active { background: var(--pf-navy-2); }
.pf-ask svg { width: 19px; height: 19px; stroke: currentColor; fill: none; }

/* ---- the Boards sheet: the six destinations, one tap from the bar ---- */
.pf-sheet-backdrop { position: fixed; inset: 0; z-index: 45; background: rgba(12,33,56,.42);
                     opacity: 0; pointer-events: none; transition: opacity .2s ease; }
.pf-sheet-backdrop.is-open { opacity: 1; pointer-events: auto; }
.pf-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 46;
            background: var(--pf-surface); border-top-left-radius: 18px; border-top-right-radius: 18px;
            border-top: 1px solid var(--pf-line-2);
            padding: 8px 16px calc(20px + env(safe-area-inset-bottom));
            box-shadow: 0 -24px 48px -24px rgba(12,33,56,.5);
            transform: translateY(100%); transition: transform .24s cubic-bezier(.2,.8,.25,1); }
.pf-sheet.is-open { transform: translateY(0); }
.pf-sheet-grip { width: 40px; height: 4px; border-radius: 999px; background: var(--pf-line-2);
                 margin: 6px auto 12px; }
.pf-sheet-h { font-family: var(--pf-mono); font-size: 10.5px; letter-spacing: .14em;
              text-transform: uppercase; color: var(--pf-muted); margin: 0 4px 6px; }
.pf-sheet-row { display: flex; align-items: center; gap: 14px; min-height: 52px;
                padding: 0 6px; text-decoration: none; color: var(--pf-ink);
                border-top: 1px solid var(--pf-line); }
.pf-sheet-row:first-of-type { border-top: 0; }
.pf-sheet-ico { flex: none; width: 22px; height: 22px; color: var(--pf-muted); }
.pf-sheet-ico svg { width: 22px; height: 22px; stroke: currentColor; fill: none; display: block; }
.pf-sheet-label { flex: 1; font: 600 15.5px/1.2 var(--pf-sans); }
.pf-sheet-c { flex: none; font-family: var(--pf-mono); font-size: 11.5px; color: var(--pf-muted); }
.pf-sheet-c.is-wanted { color: var(--pf-warn); }
.pf-sheet-go { flex: none; color: var(--pf-muted); }

/* ---- tap targets: 44px on the two boards with the most actions ----
   Desktop keeps the compact controls — a mouse does not miss a 26px button —
   but on a phone every control the review flagged grows to 44px. */
@media (max-width: 760px) {
  .kan-move { min-height: 44px; padding: 8px 12px; font-size: 13px; }
  .kan-card-foot { flex-wrap: wrap; }
  .kan-moves { gap: 6px; }
  .entry-actions .pf-btn { min-height: 44px; padding: 10px 14px; }
}

/* ---- emails: one full-width primary action, the rest behind an overflow ---- */
.entry-actions .entry-primary { width: 100%; min-height: 44px; justify-content: center;
                                font-size: 13.5px; }
.entry-more { margin-top: 2px; }
.entry-more > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center;
                        min-height: 44px; padding: 0 2px; font: 600 12.5px/1 var(--pf-sans);
                        color: var(--pf-muted); }
.entry-more > summary::-webkit-details-marker { display: none; }
.entry-more > summary:hover { color: var(--pf-ink); }
.entry-more > summary::after { content: "▾"; margin-left: 6px; font-size: 11px; }
.entry-more[open] > summary::after { content: "▴"; }
.entry-more-row { display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 0 2px; }
@media (max-width: 760px) {
  .entry-more-row .pf-btn { min-height: 44px; padding: 10px 14px; }
}

/* ---- the Boards tab is a <button> (it toggles the sheet); reset its chrome ---- */
button.pf-tab { background: none; border: 0; cursor: pointer; font: inherit; }
/* A single dot on the Boards tab when something on a board wants the owner. */
.pf-tab-boards { position: relative; }
.pf-tab-wants .pf-tab-ico::after { content: ""; position: absolute; top: -3px; right: 50%;
                 margin-right: -13px; width: 8px; height: 8px; border-radius: 50%;
                 background: var(--pf-warn); border: 2px solid var(--pf-surface); }

/* =====================================================================   PHONE POLISH PASS (owner order, 2026-07-29: "Paddings und so weiter,
   das muss alles top-notch funktionieren")
   ----------------------------------------------------------------------------
   Two rules applied everywhere, measured on the live pages at 390px first:

   1. Every field a thumb can type into is at least 16px — below that iOS
      Safari zooms the whole viewport on focus and does not zoom back. The
      chat search had this bug (fixed in 1.8.1); the research search had it
      too (#q at 14px). This closes the class for every .pf-input on every
      page, present and future.

   2. Every control a thumb must hit clears ~44px. The audit found the bar
      actions at 36px, the voice button at 33px, the Brain-strip link at
      18px. Padding does the growing; negative margins keep the visual
      rhythm exactly where it was.

   Append-only, token-only, no frozen selector renamed or removed.
   ========================================================================== */
@media (max-width: 760px) {
  .pf-input, .pf-select, textarea.pf-input {
    font-size: 16px;
    min-height: 44px;
  }
  .pf-baract { min-height: 44px; }
  .pf-brand { min-height: 44px; }
  .pf-voice-btn { min-height: 44px; }
}

/* The board's Brain switcher. Present only when the account has more than one
   Brain — with a single Brain there is nothing to choose and the row is gone
   entirely, not merely empty. Skins THE BUTTON's pill; the box model, floors
   and type come from the base, so these chips are the same object as every
   other pill on the page. */
.board-brains { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 2px; }
.board-brains[hidden] { display: none; }
.board-brain { background: none; border-color: var(--pf-line); color: var(--pf-muted); }
.board-brain:hover { color: var(--pf-ink); border-color: var(--pf-ink); }
.board-brain--active { background: var(--pf-navy); border-color: var(--pf-navy); color: var(--pf-paper); }

/* ---- Touch targets (T1/T2/Y2, breakpoint-audit GUIDELINES.md) ----
   On coarse pointers every control gets a >=44px hit height. Dense-list
   buttons grow real padding (invisible hit areas would overlap their
   neighbours, T3); the brand link grows invisibly (nothing adjacent).
   Inputs hold 16px so iOS Safari does not zoom the page on focus. */
@media (pointer: coarse) {
  .pf-btn-sm, .kan-move, .rail-tag, .pf-brainctl, .chat-brain { min-height: 44px; }
  .kan-move { padding: 6px 12px; }
  .pf-brand { padding: 7px 8px; margin: -7px -8px; }
  .pf-input { font-size: 16px; min-height: 44px; }
  .pf-textlink { display: inline-block; padding: 14px 8px; margin: -14px -8px; }
}

/* =====================================================================   11. THE SEARCH PALETTE — one field for everything (search.js, 2026-09-03).

   ⌘K on a desktop, the magnifier in the bar everywhere. A <dialog>: a centred
   panel above 760px, the whole screen below it (M1 — never a desktop box on a
   phone). Token-only, like everything else here. The hit rows are 44px doors;
   the field is 16px so iOS does not zoom; the chips scroll sideways in one row
   rather than wrapping the panel taller.
   ============================================================================ */
/* The door in the bar: a quiet round target beside the account control. */
.pf-search-open { display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 40px; height: 40px; border: 0; border-radius: 999px; background: none; color: var(--pf-muted);
  cursor: pointer; margin-left: auto; }
.pf-topnav ~ .pf-search-open { margin-left: 0; }
.pf-search-open svg { width: 19px; height: 19px; stroke: currentColor; fill: none; }
.pf-search-open:hover { color: var(--pf-ink); background: var(--pf-surface-2); }

.pf-search { width: min(720px, calc(100vw - 32px)); max-height: min(78vh, 720px); margin: 8vh auto 0;
  padding: 0; border: 1px solid var(--pf-line-2); border-radius: 18px; background: var(--pf-surface);
  color: var(--pf-ink); box-shadow: 0 30px 80px -30px color-mix(in srgb, var(--pf-ink) 45%, transparent);
  display: none; flex-direction: column; overflow: hidden; }
.pf-search[open] { display: flex; }
.pf-search::backdrop { background: color-mix(in srgb, var(--pf-navy-2) 42%, transparent); }

.pf-search-form { display: flex; align-items: center; gap: 10px; padding: 10px 14px 10px 18px;
  border-bottom: 1px solid var(--pf-line); color: var(--pf-muted); }
.pf-search-ico { flex: none; display: inline-flex; }
.pf-search-ico svg { width: 18px; height: 18px; stroke: currentColor; fill: none; }
.pf-search-input { flex: 1; min-width: 0; min-height: 44px; border: 0; outline: 0; background: none;
  font: 400 16px/1.4 var(--pf-sans); color: var(--pf-ink); }
.pf-search-input::placeholder { color: var(--pf-muted); }
.pf-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.pf-search-kbd { flex: none; font: 500 10.5px/1 var(--pf-mono); letter-spacing: .06em; text-transform: uppercase;
  color: var(--pf-muted); border: 1px solid var(--pf-line-2); border-radius: 6px; padding: 5px 7px; }
.pf-search-close { display: none; flex: none; width: 44px; height: 44px; margin-left: -8px; border: 0; border-radius: 999px;
  background: none; color: var(--pf-ink); cursor: pointer; align-items: center; justify-content: center; }
.pf-search-close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; }

/* The chip rows: which Brainy, which source. One line each, scrolling sideways. */
.pf-search-chips { display: flex; gap: 6px; padding: 10px 18px 0; overflow-x: auto; scrollbar-width: none; flex: none; }
.pf-search-chips::-webkit-scrollbar { display: none; }
.pf-search-sources { padding-bottom: 10px; border-bottom: 1px solid var(--pf-line); }
.pf-search-chip { flex: none; min-height: 32px; padding: 6px 12px; border: 1px solid var(--pf-line-2); border-radius: 999px;
  background: var(--pf-surface); color: var(--pf-muted); font: 500 12.5px/1.2 var(--pf-sans); cursor: pointer;
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-search-chip:hover { color: var(--pf-ink); border-color: var(--pf-faint); }
.pf-search-chip.is-on { background: var(--pf-navy); border-color: var(--pf-navy); color: var(--pf-surface); }

.pf-search-status { margin: 0; padding: 8px 18px 0; font: 500 11px/1.4 var(--pf-mono); letter-spacing: .04em;
  text-transform: uppercase; color: var(--pf-muted); min-height: 12px; }
.pf-search-status:empty { display: none; }

.pf-search-body { flex: 1; min-height: 120px; overflow-y: auto; padding: 6px 8px 10px; }
.pf-search-h { margin: 10px 10px 4px; font: 600 11px/1.4 var(--pf-sans); letter-spacing: .16em; text-transform: uppercase; color: var(--pf-navy); }
.pf-search-group { display: flex; align-items: baseline; gap: 8px; margin: 12px 10px 4px; }
.pf-search-group-name { font: 700 13.5px/1.3 var(--pf-disp); color: var(--pf-ink); }
.pf-search-group-sub { font: 400 11px/1.3 var(--pf-mono); color: var(--pf-muted); }
.pf-search-group-count { margin-left: auto; font: 500 11px/1 var(--pf-mono); color: var(--pf-muted);
  border: 1px solid var(--pf-line); border-radius: 999px; padding: 3px 8px; }

/* A hit is a door: whole-row link, 44px floor, the active one (keyboard or
   pointer) on the raised surface. Marks are brass on paper — a highlight, not
   an alarm. */
.pf-search-hit { display: flex; align-items: flex-start; gap: 12px; min-height: 44px; padding: 9px 10px;
  border-radius: 10px; text-decoration: none; color: inherit; }
.pf-search-hit:hover, .pf-search-hit.is-active { background: var(--pf-surface-2); }
.pf-search-hit-ico { flex: none; width: 30px; height: 30px; border-radius: 8px; display: inline-flex; align-items: center;
  justify-content: center; background: var(--pf-paper); color: var(--pf-muted); margin-top: 1px; }
.pf-search-hit-ico svg { width: 16px; height: 16px; stroke: currentColor; fill: none; }
.pf-search-hit--brain .pf-search-hit-ico { color: var(--pf-navy); }
.pf-search-hit--permission .pf-search-hit-ico { color: var(--pf-warn); }
.pf-search-hit-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pf-search-hit-title { font: 600 14px/1.35 var(--pf-sans); color: var(--pf-ink); overflow-wrap: anywhere; }
.pf-search-hit-snip { font: 400 13px/1.45 var(--pf-sans); color: var(--pf-muted); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.pf-search-hit-meta { font: 400 11px/1.4 var(--pf-mono); color: var(--pf-muted); }
mark.pf-search-mark { background: color-mix(in srgb, var(--pf-brass) 28%, transparent); color: inherit;
  border-radius: 3px; padding: 0 1px; }
.pf-search-empty { margin: 0; padding: 26px 12px; font: 400 13px/1.5 var(--pf-sans); color: var(--pf-muted); }
.pf-search-recent { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 10px 4px; }
.pf-search-again { min-height: 32px; padding: 6px 12px; border: 1px solid var(--pf-line); border-radius: 999px;
  background: var(--pf-paper); color: var(--pf-ink); font: 400 13px/1.2 var(--pf-sans); cursor: pointer;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-search-again:hover { border-color: var(--pf-faint); }

.pf-search-foot { display: flex; align-items: center; gap: 4px; padding: 8px 12px; border-top: 1px solid var(--pf-line);
  background: var(--pf-paper); flex: none; }
.pf-search-sort { min-height: 32px; padding: 6px 10px; border: 0; border-radius: 8px; background: none;
  color: var(--pf-muted); font: 500 12px/1.2 var(--pf-sans); cursor: pointer; }
.pf-search-sort.is-on { color: var(--pf-navy); background: var(--pf-surface-2); }
.pf-search-sort[hidden] { display: none; }
.pf-search-hint { margin-left: auto; font: 400 11px/1.4 var(--pf-mono); color: var(--pf-muted); }

/* An item a search hit pointed at: lit once, then ordinary. */
.is-found { animation: pf-found 1.8s ease-out; }
@keyframes pf-found {
  0%, 35% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--pf-brass) 60%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pf-brass) 0%, transparent); }
}

/* Between the tab bar (760px) and the pinned pills (900px) the bar is already
   full: brand, six pills and the account chip come to ~820px, and the pills
   absorb the squeeze by wrapping. A 40px magnifier on top pushed the chip off
   the right edge at 768 (L1/L2 in the audit). In that band the pills give up
   4px of side padding each — six pills, 48px — which is the magnifier's width. */
@media (min-width: 761px) and (max-width: 899px) {
  .pf-topnav a { padding: 8px 9px; }
  .pf-search-open { width: 36px; height: 36px; }
}
/* Touch widths, tablets included: chips are targets (T1). */
@media (max-width: 900px) {
  .pf-search-chip, .pf-search-sort, .pf-search-again { min-height: 44px; }
}

@media (max-width: 760px) {
  /* The phone takes the whole screen (M1): the bar's ← is the way back, the
     keyboard hint and the esc chip mean nothing to a thumb. */
  .pf-search { width: 100vw; max-width: none; max-height: none; height: 100dvh; margin: 0; border: 0; border-radius: 0; }
  .pf-search-form { padding: 6px 12px 6px 8px; }
  .pf-search-close { display: inline-flex; margin-left: 0; }
  .pf-search-ico, .pf-search-kbd, .pf-search-hint { display: none; }
  .pf-search-chips { padding-left: 12px; padding-right: 12px; }
  .pf-search-chip, .pf-search-sort, .pf-search-again { min-height: 44px; }
  .pf-search-status { padding-left: 12px; }
  .pf-search-body { padding: 4px 4px 12px; }
  .pf-search-foot { padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
  .pf-search-open { width: 44px; height: 44px; }
}
