/* ============================================================
   zenpAI Arbeitsbereich — design system: tokens, reset, primitives and the
   per-component sections the app shell composes. A copy of the homepage
   stylesheet, not an include: the two are expected to diverge.
   No CDN, no stylesheet pulled in from elsewhere, no url() pointing off-origin,
   no external request of any kind. System font stacks only.

   The workspace is read for a whole working day, often by people whose
   contrast sensitivity is well past its peak, and AA's 4.5 assumes a 16px body
   that a dense technical surface does not have. So the rank that carries the
   facts is held to 7:1 — WCAG AAA — against every ground it is drawn on, in
   both themes, and nothing anywhere falls under 4.5.

     primary text   --eierschale   12.4 – 18.1   AAA
     stated facts   --text-2        7.0 – 11.5   AAA
     the accent     --orange        5.1 –  8.0   AA
     the loud one   --laut          6.0 – 10.7   AA in light, AAA in dark
     muted          --text-3        4.6 –  7.0   AA

   Rank two is where a label, a value, a metadata line and a tag are set, so
   every fact this product states is AAA. The three below it are AA and are
   argued rather than conceded: the light accent #A63C1E cannot reach 7:1 on
   any ground in this palette and still be the coral this product is built on,
   and the accent marks, links and indexes text that is set in rank two beside
   it. The loud colour is spent on one thing, and that thing also says what it
   is in words. Muted is separators, placeholders and the mark for a value that
   is not there.

   Functional text is never set below 12.5px, which the type scale enforces
   rather than the eye.
   ============================================================ */

/* ── 1. Colour tokens ─────────────────────────────────────────────────
   Every theme-dependent value is declared twice — once here for the dark
   ground, once in the `[data-theme="light"]` block — in the same order, so
   that a token present in one block and missing from the other is visible as
   a gap rather than as an invisible label on half the installations. A token
   whose value is the same in both themes, or which is only another name for
   one of these, belongs in section 2 and is declared once. ── */
:root{
  /* Surfaces, from the page ground outward. Each step is a real luminance
     change, so a panel reads as a panel before its border is drawn. */
  --kohle:#17171A;          /* page ground */
  --kohle-card:#212127;     /* panel, card, popover, dialog, code block */
  --kohle-card-2:#2B2B33;   /* raised: a control on a panel, a tag, a hover */
  --kohle-tief:#111114;     /* deep: a well sunk into the page */

  /* Text, three ranks. Rank two carries every fact; rank three carries none. */
  --eierschale:#F4F1EB;     /* primary — prose, headings, values */
  --text-2:#CFC9BF;         /* secondary — labels, metadata, stated facts */
  --text-3:#A39D94;         /* muted — separators, placeholders, markers */

  --orange:#F0906B;         /* accent — the coral this product is built on */
  --laut:#FFB454;           /* the one thing on screen that is not routine */

  /* Chips. A tag states a fact; a marked chip states an exceptional one. */
  --tag-bg:#2B2B33;
  --tag-fg:#CFC9BF;
  --marke-bg:#4A2418;
  --marke-fg:#FFC9B4;

  /* Two weights of line, and the difference is meaning. --linie divides
     things that belong together (rows of one ledger, blocks of one answer);
     --linie-struktur divides one region of the app from another (the sidebar
     from the transcript, a panel from the page, a table from its header). */
  --linie:rgba(244,241,235,0.10);
  --linie-struktur:rgba(244,241,235,0.20);

  --shadow-card:0 1px 2px rgba(0,0,0,0.34), 0 8px 20px rgba(0,0,0,0.26);
  --shadow-soft:0 14px 38px rgba(0,0,0,0.46);
}
[data-theme="light"]{
  --kohle:#F2EFE8;
  --kohle-card:#FBF9F5;
  --kohle-card-2:#FFFFFF;
  --kohle-tief:#E9E5DC;

  --eierschale:#14161A;
  --text-2:#464A52;
  --text-3:#62666E;

  --orange:#A63C1E;
  --laut:#7A4A00;

  --tag-bg:#E9E5DC;
  --tag-fg:#464A52;
  --marke-bg:#FBE3D8;
  --marke-fg:#7A2A10;

  --linie:rgba(20,22,26,0.10);
  --linie-struktur:rgba(20,22,26,0.20);

  --shadow-card:0 1px 2px rgba(20,22,26,0.08), 0 8px 20px rgba(20,22,26,0.07);
  --shadow-soft:0 14px 38px rgba(20,22,26,0.15);
}

/* ── 2. Values that do not flip, and names for the ones that do ───────
   An alias points at a theme-dependent token rather than repeating its value,
   so it cannot fall out of step with the theme it is named for. ── */
:root{
  --graphit:var(--eierschale);  /* primary text */
  --hellgrau:var(--text-2);     /* secondary text */
  --schiefer:var(--text-2);     /* a stated fact is secondary, never muted */
  --mittelgrau:var(--text-3);   /* muted */
  --blau:var(--orange);         /* accent alias used throughout the system */
  --kaolin:#F4F1EB;             /* always-light cream, both themes */
  --tinte:#14161A;              /* always-dark ink, both themes */

  /* The solid button carries white text in both themes, so its ground is one
     terracotta in both: 5.47:1 against white, 6.89:1 hovered. */
  --cta-bg:#AE4B2C;
  --cta-bg-hover:#983E22;

  --ease:cubic-bezier(0.4, 0, 0.2, 1);

  /* Free, privacy-clean system fonts — no external request, no web fonts. */
  --sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, "Helvetica Neue", Arial, sans-serif;
  --mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", Consolas, monospace;
}

/* Geometry and the type scale. Two radii carry almost everything: a control
   is 11px, a surface a control can sit on is 15px. --radius-xs exists for the
   handful of marks small enough that 11px would round them into circles. */
:root{
  --radius-xs:3px;
  --radius:11px;
  --radius-lg:15px;
  --radius-pill:999px;

  /* Two grounds, two sizes. Prose — an answer, a report body, a sentence the
     service wrote — is read at 16.5px. The shell around it speaks at 14px.
     --fs-min is the floor: nothing in the product is set smaller, including
     the trace ledger, which is the densest surface here and therefore the one
     that would tempt it. */
  --fs-ui:14px;
  --fs-label:13.5px;
  --fs-meta:13px;
  --fs-code:13px;
  --fs-min:12.5px;
}

/* ── 3. App tokens ────────────────────────────────────────────────── */
:root{
  --app-header-h:68px;
  --rail-w:400px;
  --sidebar-w:268px;
  --measure:74ch;
  --hatch: repeating-linear-gradient(135deg,
             var(--blau) 0 2px, transparent 2px 6px);
}

/* ── 4. Reset. `scroll-behavior:smooth` is deliberately absent from the
   html{} rule: inherited, it would animate every one of the ~60 programmatic
   scrolls per second the transcript autoscroll performs and turn it into a
   visible rubber-band. Smooth scrolling is opt-in per call (the "Zum Ende"
   pill only) and is set in Rust, never here. The one occurrence below sits
   inside the prefers-reduced-motion kill switch, restoring the non-smooth
   default. ── */
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
body{
  font-family:var(--sans);
  font-weight:400;
  font-size:16.5px;
  line-height:1.62;
  color:var(--eierschale);
  background:var(--kohle);
}
[data-theme="light"] body{background:var(--kohle);color:var(--eierschale)}
body{transition:background 380ms var(--ease), color 380ms var(--ease), border-color 380ms var(--ease)}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:0.01ms!important;animation-iteration-count:1!important;transition-duration:0.01ms!important;scroll-behavior:auto!important}
}

/* `.phase.phase--announced` is the same treatment applied to a live region
   rather than to a static string: a settled answer goes on announcing its
   completion to a screen reader while it stops drawing a line above text that
   already says so on screen. Compounded with `.phase`, because the phase line
   states its own height and margin further down and this has to outrank them. */
.sr-only,.phase.phase--announced{position:absolute;width:1px;height:1px;min-height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
/* 3px, not a hairline: the focus ring is the only thing on screen telling a
   keyboard reader where they are, and it has to win that job at arm's length. */
:focus-visible{outline:3px solid var(--blau);outline-offset:2px;border-radius:var(--radius-xs)}

/* .wrap — a centred max-width column for modal bodies and any other centred
   surface. The app shell itself is a header + CSS grid and does not need it. */
.wrap{max-width:1280px;margin:0 auto;padding:0 32px}
@media (max-width:720px){.wrap{padding:0 20px}}

button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
a{color:inherit;text-decoration:none}

/* ── 5. The label vocabulary ──────────────────────────────────────────
   Five ranks and one value treatment. Rank is carried by size, weight and
   colour — never by a typeface, a capitalisation or a tracking value, because
   those flatten five kinds of information into one texture and cost the
   reader the ranking they were meant to give.

     .label-section  a panel's own heading            14px   700  primary
     .label-field    the key in a key/value ledger    13.5px 600  secondary
     .label-meta     a quiet line of facts            13px   400  secondary
     .tag            one neutral fact, boxed          12.5px 500  tag colours
     .tag--marked    one exceptional fact, boxed      12.5px 650  marked colours
     .code           a value that is genuinely code   13px   mono secondary

   Monospace belongs to .code and to nothing else. It is the property of a
   value that has to be read character by character — a hash, a request id, a
   path, an alias — not a decorative label font. A label set in mono says
   "this string matters literally", which of a field name is untrue.

   The `.mono-label` hook the views carry resolves to .label-meta, and the
   modifiers those views compose with it (.fg, .accent, .warn, .hint) select
   the other ranks. A view that means the tag box names `.tag`. ── */
.label-section{
  font-family:var(--sans);font-size:var(--fs-ui);font-weight:700;
  letter-spacing:-0.005em;line-height:1.35;color:var(--eierschale);
}
.label-field{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;
  line-height:1.4;color:var(--text-2);
}
.label-meta,.mono-label{
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:400;
  line-height:1.5;color:var(--text-2);
}
/* The tag box, stated once. Every surface that boxes a single fact — the
   compliance chip and its categories, the classification beside a banner, the
   event kind in a chain row, the label over the service's own German — selects
   into this rule rather than describing the box again. */
.tag,
.chip,
.banner .banner-level{
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--sans);font-size:var(--fs-min);font-weight:500;
  line-height:1.35;padding:3px 9px;border-radius:var(--radius);
  background:var(--tag-bg);color:var(--tag-fg);
  border:1px solid transparent;
}
/* The marked chip. One box, one set of colours, and only for the facts that
   are genuinely exceptional: a raised classification, a compliance category
   that fired. A colour per level would say four things are exceptional. */
.tag--marked,
.chip.on,
.tag[data-level="vs-nfd"]{
  background:var(--marke-bg);color:var(--marke-fg);font-weight:650;
}
/* The classification ladder, stated once for every chip that carries a level.
   The banner over an answer and the chip on a source card read the same
   attribute, so the two surfaces cannot climb it differently: public and
   internal are plain tags, confidential lifts the word to the primary rank, and
   only VS-NfD takes the marked box above. */
.tag[data-level="intern"]{color:var(--eierschale)}
.tag[data-level="vertraulich"]{color:var(--eierschale);font-weight:600}

/* .code — a value read character by character: a hash, a request id, a path,
   an alias, a JSON key. The selectors beside it are the places the views
   already mark such a value, so the monospace idiom is stated once and the
   rest of the stylesheet never repeats it. */
.code,
.source-card-head > .mono-label.accent:first-child,
.trace-id code{
  font-family:var(--mono);font-size:var(--fs-code);line-height:1.5;
  color:var(--text-2);overflow-wrap:anywhere;
}

/* .code-block — the same value treatment given a surface of its own: an
   agent's output, a disclosed JSON meta, a discarded partial answer, a fenced
   block in an answer. A well sunk into the page, because its contents are not
   the product's words. */
.code-block,
.document-ledger pre,
.md pre{
  background:var(--kohle-tief);
  border:1px solid var(--linie-struktur);border-radius:var(--radius-lg);
  padding:14px 16px;overflow-x:auto;
}
.code-block,
.document-ledger pre{
  font-family:var(--mono);font-size:var(--fs-code);line-height:1.6;
  color:var(--text-2);white-space:pre-wrap;overflow-wrap:anywhere;
}

/* The modifiers the views compose onto `.mono-label`. Each is one rank, not a
   colour override: `.fg` promotes a metadata line to a field label and
   `.warn` raises one to the loud colour. */
.mono-label.fg,label.mono-label{font-size:var(--fs-label);font-weight:600;color:var(--text-2)}
.label-meta.accent,.mono-label.accent{color:var(--blau)}
.label-meta.warn,.mono-label.warn{color:var(--laut);font-weight:500}
.label-meta.hint,.mono-label.hint{color:var(--text-3)}
.num{font-variant-numeric:tabular-nums}
/* An instant holds its two halves together; the break between them is the one
   the format already draws, and `moment_view` is what puts it there. */
.moment{white-space:nowrap}

/* ── 6. Primitives ────────────────────────────────────────────────── */

/* .card — a raised surface. The elevation is the card: the shadow and the
   luminance step say "panel" on their own, and the hairline only finishes the
   edge. Hover lifts 4px and borders coral.
   Reveal is a CSS @keyframes animation with `backwards` fill, not a
   class-removal transition: that leaves elements stuck at opacity:0 when the
   class is gone before first paint. Leptos sets
   `style="animation-delay: …ms"` per item for the stagger. */
.card{
  background:var(--kohle-card);border:1px solid var(--linie);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);
  transition:transform 280ms var(--ease), border-color 280ms var(--ease), box-shadow 280ms var(--ease);
  animation:card-in 420ms var(--ease) backwards;
}
.card:hover{transform:translateY(-4px);border-color:var(--blau);box-shadow:var(--shadow-soft)}
@keyframes card-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* A source card's border flashes coral for 600ms when its [n] citation anchor
   is clicked. Applied as a class toggle the caller removes after the animation
   ends. */
@keyframes card-flash{from{box-shadow:inset 0 0 0 1px var(--blau);border-color:var(--blau)}to{box-shadow:inset 0 0 0 1px transparent}}
.card.flash{animation:card-flash 600ms var(--ease)}

/* .chip — the tag rank, given a pill and room for a dot. `.chip.on` is the
   marked chip: this is the compliance chip's whole distinction, and the
   header's replay indicator borrows it for the same reason. */
.chip{
  gap:8px;border-radius:var(--radius-pill);padding:5px 13px;
  transition:border-color 200ms var(--ease), color 200ms var(--ease), background 200ms var(--ease);
}
.chip.interactive{cursor:pointer}
.chip.interactive:hover{border-color:var(--blau);color:var(--blau)}
.chip .dot{width:6px;height:6px;border-radius:var(--radius-xs);background:currentColor}
/* `.chip.laut` — the one chip that is not routine: content the compliance layer
   kept out of an answer. It is the only place --laut is spent, and it keeps the
   raised surface as its ground rather than a tint of the loud colour, which is
   the pairing that clears 7:1 in both themes. Hovering it moves the ground, not
   the colour: a chip that goes coral on hover reads as having calmed down. */
.chip.laut{
  background:var(--kohle-card-2);color:var(--laut);
  border-color:var(--laut);font-weight:650;
}
.chip.laut.interactive:hover{background:var(--kohle-card);color:var(--laut);border-color:var(--laut)}

/* .ledger — a key/value listing. A <dl>: <dt> the field name, <dd> the value.
   Used by the trace panel's request ledger and the document panel's key/value
   listing. The rule between rows is decorative — the rows are one table — so
   it is the hairline and not the structural line. */
.ledger{display:grid;grid-template-columns:152px minmax(0,1fr);gap:0 14px;font-size:var(--fs-ui)}
.ledger dt{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;
  color:var(--text-2);padding:7px 0;border-top:1px solid var(--linie);line-height:1.4;
}
.ledger dd{
  padding:7px 0;border-top:1px solid var(--linie);overflow-wrap:anywhere;
  font-variant-numeric:tabular-nums;line-height:1.45;color:var(--eierschale);
}
.ledger dd.accent{color:var(--blau)}
/* Our word for a value that is a machine token, on the value's own line and
   behind it: the column content is what an auditor matches, the word is what
   everyone else reads, and the order says which is which. It is a rank rather
   than a place, so a table cell that states a token states it the same way. */
.ledger-word{
  font-family:var(--sans);font-size:var(--fs-meta);color:var(--text-2);
  line-height:1.45;margin-left:9px;
}
/* Two readings of one row — a moment and the hash it produced — divided the
   way the answer footer divides its own run of facts. */
.ledger .sep{margin:0 7px;color:var(--text-3)}
/* A note sits inside the value it qualifies, and a value may be code — but the
   note is a sentence, so it names its own family rather than inheriting one. */
.ledger .ledger-note{
  display:block;font-family:var(--sans);font-size:var(--fs-meta);color:var(--text-2);
  margin-top:3px;line-height:1.45;
}
@media (max-width:720px){.ledger{grid-template-columns:126px minmax(0,1fr)}}
/* The rail is 400px wide whatever the viewport is, so the ledgers standing in
   it get their own column split rather than the viewport's: at the shell's own
   152px a German field name and the value beside it both wrap, and a row that
   wraps on both sides of the gap is a row with no shape left. */
.rail-panes .ledger{grid-template-columns:minmax(0,128px) minmax(0,1fr);gap:0 12px}
/* A field name longer than that column breaks rather than running into the
   value beside it: a label touching its own value is two facts read as one. */
.rail-panes .ledger dt{overflow-wrap:break-word}
/* The handle an event contributed to the chain is nine characters and is
   compared to another nine by eye. It moves to the next line whole rather than
   breaking across two, which is the one thing that would make it uncomparable. */
.ledger .code.hash{white-space:nowrap}

/* .rule — the visual escalation ladder: ONE hue, THREE textures.
   No red/amber/green severity ramp.
     .rule            hairline  = normal
     .rule--classified solid 3px coral = classified
     .rule--blocked    hatched 3px coral = blocked / anomalous
   Used standalone (DiscardedPanel, DocumentPanel 404/451, chain-verify
   failure) and composed into .banner below. The plain rule marks a division
   between regions, so it takes the structural line rather than the hairline —
   a rule that reads as a smudge is not a rule. */
.rule{border-left:2px solid var(--linie-struktur);padding-left:15px}
.rule--classified{border-left:3px solid var(--blau);padding-left:16px}
.rule--blocked{border-left:3px solid transparent;border-image:var(--hatch) 1;padding-left:16px}

/* ── 7. Logo — 26×26 box, 3 bars (first coral, other two foreground),
   theme-flipping wordmark. Pure CSS, no image. ── */
.logo{display:flex;align-items:center;gap:10px}
.logo-mark{
  width:26px;height:26px;border:1.8px solid var(--graphit);border-radius:var(--radius-xs);
  display:flex;flex-direction:column;justify-content:space-between;padding:4px 3px;
  flex-shrink:0;
}
.logo-mark span{display:block;height:3px;width:100%;background:var(--graphit);border-radius:0.5px}
.logo-mark span:first-child{background:var(--blau)}
.logo-word{font-family:var(--sans);font-weight:800;font-size:19px;letter-spacing:-0.04em;color:var(--graphit)}
.logo-word .accent{color:var(--blau)}

/* ── 8. Buttons ────────────────────────────────────────────────────
   .btn         solid CTA (--cta-bg) — composer send, report "save as file".
   .btn--outline coral outline, no fill — the "Abbrechen" (stream abort) /
                secondary-action shape.
   .btn--ghost  borderless until hover — header icon buttons, rail tabs'
                sibling actions.
   All three are controls, so all three are 11px, and all three are set at the
   shell's own 14px: a button a reader has to lean in to read is a button they
   press by aim rather than by reading. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:var(--sans);font-weight:600;font-size:var(--fs-ui);letter-spacing:-0.005em;
  padding:11px 20px;border-radius:var(--radius);border:0;cursor:pointer;
  background:var(--cta-bg);color:#fff;
  transition:background 200ms var(--ease), color 200ms var(--ease), transform 200ms var(--ease), border-color 200ms var(--ease);
}
.btn:hover{background:var(--cta-bg-hover);color:#fff;transform:translateX(3px)}
.btn:disabled{opacity:0.5;cursor:not-allowed;transform:none}

.btn--outline{
  background:none;color:var(--blau);
  border:1px solid color-mix(in srgb, var(--blau) 55%, transparent);
}
.btn--outline:hover{background:none;border-color:var(--blau);color:var(--blau);transform:none}
.btn--outline:disabled{opacity:0.5;cursor:not-allowed}

.btn--ghost{
  background:none;color:var(--text-2);
  font-family:var(--sans);font-size:var(--fs-ui);font-weight:600;letter-spacing:-0.005em;
  padding:8px 13px;border:1px solid transparent;border-radius:var(--radius);
}
.btn--ghost:hover{color:var(--blau);border-color:var(--linie-struktur);transform:none}
.btn--ghost.outline{border-color:var(--linie-struktur);color:var(--graphit)}
.btn--ghost.outline:hover{border-color:var(--blau);color:var(--blau)}

/* ── 9. Form fields. A field is a control with an edge of its own: filled
   ground, structural border, 11px corners, coral on focus. An input drawn as
   a bottom rule alone is an editorial device — it tells a reader where to
   write only once they have guessed there is a field there. Used by the
   composer, the login form, the report dialog and the console filters. ── */
.field{display:flex;flex-direction:column;gap:7px}
.field label{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;color:var(--text-2);
}
.field input,.field select,.field textarea{
  background:var(--kohle-card);border:1px solid var(--linie-struktur);border-radius:var(--radius);
  padding:10px 12px;color:var(--graphit);font-family:var(--sans);font-size:15px;line-height:1.45;
  transition:border-color 200ms var(--ease), box-shadow 200ms var(--ease);
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;border-color:var(--blau);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--blau) 24%, transparent);
}
.field input::placeholder,.field textarea::placeholder{color:var(--text-3)}
.field textarea{resize:vertical;min-height:80px;font-family:var(--sans)}
.field input[type="date"],.field input[type="number"]{font-variant-numeric:tabular-nums}

/* .field-group — one bounded surface holding an input and the control that
   acts on it, so the pair reads as one thing and lights up together on
   focus-within rather than the input lighting up alone. The composer, the
   search form and the agent launch form all take this surface; each states its
   own internal layout on top of it, and the bounds of any field inside it
   belong to that layout rather than to the surface. */
.field-group{
  display:flex;align-items:flex-end;gap:14px;
  background:var(--kohle-card);
  border:1px solid var(--linie-struktur);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);
  padding:10px 12px 10px 16px;
  transition:border-color 200ms var(--ease), box-shadow 200ms var(--ease);
}
.field-group:focus-within{
  border-color:var(--blau);
  box-shadow:var(--shadow-card), 0 0 0 3px color-mix(in srgb, var(--blau) 24%, transparent);
}
.field-group textarea,.field-group input{
  flex:1;background:none;border:0;outline:0;resize:none;
  color:var(--graphit);font-family:var(--sans);font-size:16.5px;line-height:1.5;
  padding:6px 0 6px;
}
.field-group textarea::placeholder,.field-group input::placeholder{color:var(--text-3)}

/* The composer — `.field-group`'s surface laid out as a column: the question on
   top, and under it the one row carrying the disclosure of what the question
   will be sent with and the button that sends it. This is the shape every chat
   tool a reader has already used draws around its input, and the familiarity is
   the point — a bounded, filled, raised box says "type here" before a
   placeholder has been read, where a rule under a line of text says it only to
   someone who already guessed there was a field. */
.composer{
  /* What the question grows into, stated in lines and not in pixels so that it
     means the same at any type size: two lines standing empty, ten lines at
     full stretch, and the box scrolls past that. A ceiling has to exist —
     without one a long question eats the transcript it is being asked about —
     so it is named here, where it can be read and changed, instead of living as
     a pixel constant inside a measurement where nobody would find it. */
  --composer-lines-min:2;
  --composer-lines-max:10;
  --composer-input-pad:6px;

  flex-direction:column;align-items:stretch;gap:7px;
  padding:8px 12px 9px 16px;
}
/* `flex:0 0 auto` and not the surface's own `flex:1`: down a column the growth
   factor would let the box negotiate its height with the row beneath it, and
   this height is measured from the text, bounded above and below, and nothing
   else's to decide. */
.composer .composer-input{
  flex:0 0 auto;
  padding-block:var(--composer-input-pad);
  min-height:calc(var(--composer-lines-min) * 1lh + 2 * var(--composer-input-pad));
  max-height:calc(var(--composer-lines-max) * 1lh + 2 * var(--composer-input-pad));
  overflow-y:auto;
}

/* The row along the bottom of the container: the disclosure at the left, the
   send button at the right. An open disclosure takes the whole line and moves
   the button to the next one rather than prising the row apart — the same
   answer the routing rule's disclosure gives in section 10. */
.composer-tools{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}
/* No free space to claim while the hint beside it holds the room between the
   disclosure and the button; all of it at the widths where the hint is not
   drawn, which is what keeps the button at the right edge either way. */
.composer-tools .btn{margin-left:auto}

/* Enter and Shift+Enter are learned in the first minute and would be read for
   every hour after it, so the pair stands beside the button it describes while
   the composer holds focus and is quiet the rest of the time. It takes the room
   between the disclosure and that button and holds it whether it is drawn or
   not: a hint that appears by shoving the send button onto another line costs
   more attention than it repays. Faded rather than hidden, so it stays in the
   accessibility tree at the widths where there is room for it; at the width
   where shell.css takes it out of the layout altogether, the textarea's own
   `aria-describedby` is what goes on carrying it. Either way what it says
   arrives on the way into the field, however the reader got there. */
.composer-hint{
  flex:1 1 auto;min-width:0;
  text-align:right;white-space:nowrap;overflow:hidden;
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:400;line-height:1.4;
  color:var(--text-3);
  opacity:0;transition:opacity 200ms var(--ease);
}
.composer:focus-within .composer-hint{opacity:1}

/* A setting belongs on the control it changes, so what the next question will
   be sent with waits inside the container rather than beside it. Native
   <details>: a keyboard opens it without any script, and what it holds enters
   the tab order exactly while it is open. */
.composer-settings summary{
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:600;
  color:var(--text-2);cursor:pointer;
}
.composer-settings summary:hover{color:var(--blau)}
.composer-settings[open]{flex-basis:100%}
.composer-settings-body{
  display:flex;flex-wrap:wrap;align-items:flex-start;gap:12px 22px;
  margin:9px 0 3px;padding-top:11px;border-top:1px solid var(--linie);
}
.composer-setting{display:flex;flex-direction:column;gap:4px;min-width:0}
/* A control standing on a filled panel takes the raised ground: a select drawn
   in the panel's own colour is a select a reader finds by its border alone. */
.composer-settings select{
  background:var(--kohle-card-2);color:var(--graphit);
  border:1px solid var(--linie-struktur);border-radius:var(--radius);
  font-family:var(--sans);font-size:var(--fs-ui);line-height:1.4;
  padding:7px 10px;cursor:pointer;
  transition:border-color 200ms var(--ease);
}
.composer-settings select:hover,.composer-settings select:focus-visible{border-color:var(--blau)}
.composer-settings select:disabled{cursor:not-allowed;opacity:0.55}
/* The option list is drawn by the platform, which inherits no ground of its
   own — name it, so a light-on-light dropdown is impossible in either theme,
   and keep the platform from lettercasing a value stated verbatim. */
.composer-settings option{background:var(--kohle-card);color:var(--graphit);text-transform:none}

/* A meter — the sentence stating both numbers, and a track drawing the same
   reading beside it. The track is what keeps a bound from reading as one more
   line of metadata: a quantity a person is meant to feel approaching is drawn
   as well as spelled, and the drawing is what ranks these two readings above
   the quiet row they sit in. The accent fills the track; the loud colour takes
   it at the ceiling, which is the one state in a month that is not the
   ordinary course of one. */
.composer-meter{display:grid;grid-template-columns:minmax(0,1fr) 76px;gap:10px;align-items:center}
.composer-meter-value{
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:400;line-height:1.4;
  color:var(--text-2);font-variant-numeric:tabular-nums;
}
.composer-meter-track{
  display:block;height:4px;border-radius:var(--radius-xs);
  background:var(--linie-struktur);overflow:hidden;
}
.composer-meter-fill{
  display:block;height:100%;border-radius:inherit;background:var(--blau);
  transition:width 300ms var(--ease);
}
.composer-meter--full .composer-meter-value{color:var(--laut);font-weight:500}
.composer-meter--full .composer-meter-fill{background:var(--laut)}

/* ── 10. Classification banner — one row over an answer carrying the level,
   the routing it took and the question that opens the rule behind it.
   Escalates by TEXTURE, not by hue:
     null        no banner at all
     oeffentlich plain rule
     intern      plain rule
     vertraulich coral-tinted rule
     vs-nfd      solid 3px coral rule + faint coral tint
   The chip beside each of those climbs the shared `[data-level]` ladder in
   section 5, which the source cards climb too.
   The word "belegt"/"nachgewiesen" (the provable-claim wording) is a
   content rule enforced by `banner.rs`'s truth table, not by CSS — it
   renders into the same `.banner-note` slot as every other note below,
   styled identically. */
.banner{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px;
  padding:11px 0 11px 15px;margin-bottom:14px;
  border-left:2px solid var(--linie-struktur);
}
.banner .banner-note{font-family:var(--sans);font-size:var(--fs-meta);color:var(--text-2);line-height:1.5}
.banner .banner-note a{color:var(--blau);border-bottom:1px solid color-mix(in srgb, var(--blau) 55%, transparent)}

/* The rule the routing followed, in the policy engine's own syntax, behind the
   question a reader would ask. The row itself keeps stating the routing — the
   classification, the tier, the model — so what waits here is the expression
   and nothing else. Open, it takes a line of its own rather than prising the
   tags apart, and it is a native <details> so a keyboard reaches it inside a
   scrolling transcript without any script. */
.banner-why summary{
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:600;
  color:var(--blau);cursor:pointer;
}
.banner-why[open]{flex-basis:100%}
.banner-why .code{display:block;margin-top:6px}

.banner[data-level="vertraulich"]{border-left-color:color-mix(in srgb, var(--blau) 60%, transparent)}

.banner[data-level="vs-nfd"]{
  border-left:3px solid var(--blau);padding-left:16px;
  background:linear-gradient(90deg, color-mix(in srgb, var(--blau) 10%, transparent), transparent 62%);
}

/* The third texture — hatched — is not a classification level, it is the
   anomaly state: Missing trace, 404/451 document, a broken audit chain.
   Compounded as `.banner.banner--blocked` (0,2,0), not a bare `.banner--blocked`
   (0,1,0): the `Missing` state keeps `data-level` set (so the label still names
   the real classification) and needs the hatched rule to win over that level's
   own `[data-level="…"]` rule (also 0,2,0); a tie resolves by source order, and
   this block comes after every one of them. */
.banner.banner--blocked{
  border-left:3px solid transparent;border-image:var(--hatch) 1;padding-left:16px;
}
.banner.banner--blocked .banner-level{background:var(--tag-bg);color:var(--eierschale);font-weight:600}

/* ── 11. Transcript — no chat bubbles, the transcript is a ledger.
   Questions: a metadata line + the set question, coral rule on the left.
   Answers: body text on the page ground, separated by hairlines. */
.qa-question{padding-left:16px;border-left:3px solid var(--blau);margin:8px 0 30px}
.qa-question .mono-label{margin-bottom:8px;display:block}
.qa-question p{font-size:20px;font-weight:600;letter-spacing:-0.015em;line-height:1.38;max-width:60ch}
.qa-answer{padding-bottom:56px;border-bottom:1px solid var(--linie-struktur);margin-bottom:44px}
.qa-answer:last-child{border-bottom:0;padding-bottom:20px;margin-bottom:0}

/* Phase line — the live status above a streaming answer. */
.phase{
  display:flex;align-items:center;gap:10px;
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:500;color:var(--text-2);
  margin-bottom:16px;min-height:20px;
}
.phase .cursor{width:8px;height:15px;background:var(--blau);animation:phase-blink 1.05s steps(1) infinite}
@keyframes phase-blink{50%{opacity:0}}
/* The counters tick while the answer streams, so they are tabular: digits that
   change width make the line jitter and the reader re-find it every frame. */
.phase .nums{color:var(--text-2);font-variant-numeric:tabular-nums}

/* Answer footer — three ranks in one row. The model that answered is a fact and
   wears a tag; the token counts, the duration and the hash are one quiet
   sentence; »Prüfpfad öffnen« and »Kopieren« read as the controls they are. The
   row's 18px column gap separates the three ranks, and `.answer-facts` holds
   its own run together inside that gap so the numbers read as one statement
   rather than as three fields of equal standing. */
.answer-footer{
  display:flex;flex-wrap:wrap;gap:8px 18px;align-items:center;
  margin-top:26px;padding-top:14px;border-top:1px solid var(--linie);
}
/* `display:inline` and not a flex box: these spans hold a label, a space and a
   value as separate text nodes, and a flex box drops a whitespace-only item —
   the sentence would render as "8421ms". */
.answer-footer .answer-facts{display:inline}
.answer-footer .k{
  display:inline;font-size:var(--fs-meta);font-weight:400;
  color:var(--text-2);font-variant-numeric:tabular-nums;
}
.answer-footer .sep{margin:0 7px;color:var(--text-3)}
.answer-footer button{
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:600;color:var(--text-2);
  padding:2px 0;border-bottom:1px solid var(--linie-struktur);
  transition:color 200ms var(--ease), border-color 200ms var(--ease);
}
.answer-footer button:hover{color:var(--blau);border-bottom-color:var(--blau)}

/* Discarded-answer panel — mid-stream truncation. Hatched-rule container; both
   disclosures carry `.code-block`, so the withheld text and the operator string
   read as the machine values they are and neither is styled as an answer. */
.discarded{border-left:3px solid transparent;border-image:var(--hatch) 1;padding-left:16px;margin-top:18px}
.discarded h4{font-size:16px;font-weight:700;margin-bottom:6px}
.discarded details{margin-top:10px}
.discarded summary{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;color:var(--blau);cursor:pointer;
}
.discarded .code-block{margin-top:10px}
/* The withheld tokens are not an answer and are not for lifting out of this
   panel. The operator string beside them names the fix and exists to be
   applied, so the refusal to select belongs to the text alone. */
.discarded-text{user-select:none}

/* "Zum Ende" jump-to-latest pill — opacity and transform together, both eased,
   both killed under prefers-reduced-motion by the global switch above. */
.jump-pill{
  position:absolute;left:50%;bottom:calc(100% + 14px);transform:translate(-50%,6px);
  display:inline-flex;align-items:center;gap:9px;background:var(--kohle-card-2);
  border:1px solid var(--linie-struktur);border-radius:var(--radius-pill);padding:8px 17px;
  font-family:var(--sans);font-size:var(--fs-meta);font-weight:600;
  color:var(--graphit);box-shadow:var(--shadow-soft);opacity:0;pointer-events:none;
  transition:opacity 220ms var(--ease), transform 220ms var(--ease);
  z-index:20;
}
.jump-pill.show{opacity:1;transform:translate(-50%,0);pointer-events:auto}
.jump-pill:hover{border-color:var(--blau);color:var(--blau)}
.jump-pill .dot{width:7px;height:7px;border-radius:50%;background:var(--blau);opacity:0}
.jump-pill.new .dot{opacity:1}

/* ── 12. Markdown body — the ONLY high-frequency region, and the one region
   that is not shell: `MarkdownView` renders an answer in the transcript and
   the body of the Auditbericht in the report dialog, so everything below has
   to keep reading as a document. Nothing here is a chip, a tab or a pill.
   Class names are load-bearing: `.md`, `.md-block` and `.md-tail` are
   referenced verbatim from the Leptos view, and `a.cite` is the exact anchor
   `md/cite.rs` emits as raw HTML
   (`<a class="cite" href="#src-N" data-n="N">[N]</a>`). Do not rename
   without updating those call sites. */
/* The measure a line of prose is read at. The report dialog holds it for the
   prose and lifts it off the tables, in section 17 — a chat answer's table is
   as wide as the answer, and a compliance document's is as wide as the
   document. */
.md{max-width:var(--measure)}
.md-block + .md-block{margin-top:1.05em}
.md-tail:empty{display:none}
.md p{line-height:1.62}
.md h1{font-size:23px}
.md h2{font-size:20px}
.md h3{font-size:18px}
.md h1,.md h2,.md h3{font-weight:700;letter-spacing:-0.02em;line-height:1.25;margin-top:0.45em}
.md ul,.md ol{padding-left:1.15em}
.md li{margin:0.22em 0}
.md li::marker{color:var(--text-3)}
.md strong{font-weight:700}
.md code{
  font-family:var(--mono);font-size:max(0.88em, var(--fs-min));
  background:var(--tag-bg);color:var(--tag-fg);
  border:0;border-radius:var(--radius-xs);padding:2px 6px;
}
.md pre code{background:none;border:0;padding:0;font-size:var(--fs-code);line-height:1.62;color:var(--text-2)}
.md table{border-collapse:collapse;width:100%;font-size:15px}
.md .table-wrap{overflow-x:auto;border:1px solid var(--linie-struktur);border-radius:var(--radius-lg)}
/* A table header is a row of field labels, and the rule under it divides the
   header from the body — a structural division, not a row separator. */
.md th{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:700;
  color:var(--eierschale);text-align:left;padding:11px 14px;
  border-bottom:1px solid var(--linie-struktur);background:var(--kohle-card-2);
}
.md td{padding:10px 14px;border-bottom:1px solid var(--linie);font-variant-numeric:tabular-nums}
.md tr:last-child td{border-bottom:0}
/* Both of these size themselves against the text they sit in, and both are
   clamped at --fs-min: inside a table header the em-relative size alone would
   fall under the floor.
   The citation marker stays monospace: it is a reference to a numbered thing,
   read as a token rather than as a word, and mono keeps [3] and [12] the same
   optical weight in a sentence. */
.md a.cite{
  display:inline-block;font-family:var(--mono);font-size:max(0.85em, var(--fs-min));font-weight:600;
  color:var(--blau);vertical-align:0.1em;padding:0 3px;
  border-bottom:1px solid transparent;transition:border-color 150ms var(--ease);
}
.md a.cite:hover{border-bottom-color:var(--blau)}

/* ── 13. Trace panel — the chain spine. A vertical line with one 10px
   rounded-square node per chained event (the logo mark's geometry — not
   circles). Nodes fill solid coral over 240ms as their event lands. */
.spine{position:relative;padding-left:22px;margin-top:4px}
.spine::before{
  content:"";position:absolute;left:4px;top:6px;bottom:6px;width:2px;
  background:color-mix(in srgb, var(--blau) 45%, transparent);
}
.spine-event{position:relative;padding-bottom:22px}
.spine-event::before{
  content:"";position:absolute;left:-22px;top:4px;width:10px;height:10px;border-radius:var(--radius-xs);
  border:1px solid var(--linie-struktur);background:var(--kohle);
  transition:background 240ms var(--ease), border-color 240ms var(--ease);
}
.spine-event.on::before{background:var(--blau);border-color:var(--blau)}
.spine-event.pending::before{border-style:dashed}
.spine-event .spine-event-head{display:flex;justify-content:space-between;align-items:baseline;gap:10px;margin-bottom:9px}
.spine-event .spine-event-title{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;color:var(--text-2);
}
.spine-event.on .spine-event-title{color:var(--eierschale);font-weight:700}
.spine-event .spine-event-time{
  font-family:var(--sans);font-size:var(--fs-meta);color:var(--text-2);font-variant-numeric:tabular-nums;
}

/* The »Kondensierung« node's disclosure. Reuses `.spine-event`'s own node/title
   styling; the head is a plain button here (the other two nodes have none —
   they fill automatically) so it needs its chrome reset and a pointer cursor.
   It stands outside `.spine` — the condensation call is a separate request from
   the one the chain above it records — and carries that spine's own indent
   itself, so its node sits on the same vertical as the two above rather than
   half off the edge of the pane. */
.spine-event.condense{margin-left:22px}
.spine-event.condense .spine-event-head{
  width:100%;background:none;border:0;padding:0;cursor:pointer;text-align:left;
}
.spine-event.condense .spine-event-head:hover .spine-event-title{color:var(--blau)}

/* Request-id header above the spine, and the chain-verify result bar. The
   handle is named: eight characters of a uuid standing alone at the top of a
   pane are debris until a word says what they are a handle for. */
.trace-id{
  display:flex;align-items:center;gap:8px;justify-content:space-between;margin-bottom:16px;
  padding-bottom:12px;border-bottom:1px solid var(--linie-struktur);
}
.trace-id-name{display:flex;align-items:baseline;gap:8px;min-width:0}
.trace-id-acts{display:inline-flex;align-items:center;gap:6px;flex-shrink:0}
.trace-id code{color:var(--blau)}
/* The whole chain's recompute, under the one request the pane is about. It
   answers a different question from everything above it, so it stands behind a
   division rather than as one more block in the column. */
.rail-panes .trace-verify{
  display:flex;flex-direction:column;align-items:flex-start;gap:10px;
  margin-top:22px;padding-top:16px;border-top:1px solid var(--linie);
}
.verify-bar{
  margin-top:6px;padding:13px 14px;
  background:var(--kohle-card);border:1px solid var(--linie-struktur);border-radius:var(--radius-lg);
  display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
}
.verify-bar .status{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;color:var(--text-2);
}
.verify-bar .status.ok{color:var(--blau)}

/* ── 14. Source cards. Composes the `.card` primitive
   (background/border/radius/hover-lift/flash) with the app's own internal
   layout: a head row (citation number, always coral; classification, right, a
   chip on the shared `[data-level]` ladder the banner climbs), a sans title
   that falls back to `source_path` when `title` is null, a doc_ref/revision
   line, the folders the document stands in and the source_path itself (a
   `.code` value, so monospace, and it wraps anywhere) — the same two readings
   of a location the document panel gives it, so one document is not spelled
   two ways one click apart — and, only when `revision_note` is present, a
   coral-ruled note.
   A card whose document the endpoint would not hand over states that instead,
   in the hatched `.rule--blocked` rule the panel states its own two refusals
   in, and carries no classification chip: there is no level to climb where
   there is no document.
   `.hovered`/`.selected` are the reactive half of the [n] <-> card sync — NOT
   `:hover`, because the reader may be hovering the `[n]` anchor in the
   transcript rather than the card itself; `sources.rs` drives both
   directions. */
.sources{display:flex;flex-direction:column;gap:14px}
/* What the retrieval did, above the cards it produced: the condensed query —
   shown only when it differs from the typed question, because a rewritten query
   the reader cannot see is retrieval theatre — and the compliance layer's own
   line. The query is a field label over prose the service wrote, so the value
   is set as prose and not as one more run of metadata. Withholding nothing is
   the ordinary outcome and it is stated here, one click from the answer,
   instead of on a line above every answer in the transcript. */
.sources-meta{display:flex;flex-direction:column;gap:7px;margin:-2px 0 0}
.retrieval-query{display:flex;flex-direction:column;gap:2px}
.retrieval-query-text{
  font-family:var(--sans);font-size:var(--fs-ui);line-height:1.5;
  color:var(--eierschale);overflow-wrap:anywhere;
}

.source-card{
  display:flex;flex-direction:column;gap:7px;width:100%;text-align:left;
  padding:16px 17px 15px;cursor:pointer;
}
.source-card-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
/* The citation number — the head's first child — is a reference token: mono
   keeps [3] and [12] at the same optical weight, and the accent says it is the
   anchor the card answers. The classification beside it is a word, not a
   token, so it stays in the label face. */
.source-card-head > .mono-label.accent:first-child{font-size:var(--fs-label);font-weight:700;color:var(--blau)}
.source-card-title{font-family:var(--sans);font-size:15.5px;font-weight:600;line-height:1.4}
.source-card-ref{margin-top:-2px}
.source-card-note{
  margin-top:2px;padding-left:11px;border-left:2px solid var(--blau);
  font-family:var(--sans);font-size:var(--fs-meta);line-height:1.5;color:var(--text-2);
}
.source-card.hovered,.source-card.selected{border-color:var(--blau)}
.source-card.selected{box-shadow:inset 0 0 0 2px var(--blau)}
/* The card is a flex column and spaces its own children; the blocked rule is
   one child of it and spaces its own in turn, or its lines would sit closer
   together than the lines of a card that resolved. The pane's sentence for a
   lookup still in flight drops the standing it has as a whole pane's empty
   state — inside a card it is one line among the card's lines. */
.source-card .rule--blocked{display:flex;flex-direction:column;gap:7px}
.source-card .empty{padding:0}

/* The reverse half of the same sync — the `[n]` anchor lighting up when its
   card is hovered/selected. Classes only: the anchors are raw `inner_html` and
   are toggled directly by `sources.rs`'s `Effect`, never by Leptos. */
.md a.cite.hovered,.md a.cite.selected{
  background:var(--marke-bg);color:var(--marke-fg);border-radius:var(--radius-xs);
}

/* ── 15. Compliance chip. The `.chip.laut` primitive IS the chip: it appears in
   an exchange only when something was withheld, which is the one state that
   warrants the loud colour. This section adds the category/count disclosure
   beneath it. Counts and category names only — `Compliance` carries nothing
   else, so there is nothing else this markup could show even by accident (see
   `compliance.rs`). */
.compliance{display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin-bottom:14px}
.compliance-categories{list-style:none;display:flex;flex-direction:column;gap:6px}
/* One category and the count beside it. The gap between the two is what a
   separator would otherwise have to be — a value carries no punctuation that is
   not its own — and the row hugs its content, because a chip stretched across
   the answer would read as a bar over it rather than as one fact in a list. */
.compliance-categories li{display:flex;align-items:center;gap:9px}
.compliance-categories .num{
  font-family:var(--sans);font-size:var(--fs-meta);color:var(--text-2);
}

/* ── 16. Document panel, at two levels.
   The panel is what a person reading a citation needs in order to know they
   have the right document — its name, its classification on the shared
   `[data-level]` ladder, the folders it stands in, the file itself as the value
   it is, and the one act a metadata panel can offer for a file it does not
   serve. The record — every field the endpoint returned, in the service's own
   words, and the raw `meta` behind a second door inside it — waits in
   `.document-record`, which takes `.ledger` stacked for the width it stands
   in.
   404/451 reuse the standalone `.rule--blocked` hatched rule with a heading and
   explanatory prose — the same weight as the success state, because both
   failure states ARE the demonstration, not error handling bolted on beside
   it. */
.document-panel{display:flex;flex-direction:column;gap:12px}
.document-panel h3{font-family:var(--sans);font-size:16px;font-weight:700;margin-bottom:6px}
.document-panel .rule--blocked p{margin-top:8px;line-height:1.55}
/* The name of the document, then the marks it carries. The name is the first
   thing read here, so nothing shares its line; what it is classified as and
   which revision it is stand together under it. */
.document-title{margin-bottom:0;overflow-wrap:anywhere;line-height:1.35}
.document-head{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;margin-top:-4px}
.document-where{display:flex;flex-direction:column;gap:4px}
/* The folders, with the separator drawn rather than typed as punctuation of the
   path's own. A source card reads a location the same way, so it selects into
   this rule rather than describing the same line again. */
.document-trail,.source-card-trail{overflow-wrap:anywhere}
.document-file{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
/* Where the classification came from, in the one case where that is not the
   ordinary course of an ingest: a marking in the document itself, or a level a
   model assigned. The coral rule is the same escalation the banner over an
   answer draws, and the marking under the sentence is a value. */
.document-evidence{
  font-family:var(--sans);font-size:var(--fs-ui);line-height:1.55;color:var(--text-2);
}
.document-evidence .code{display:block;margin-top:5px}
.document-record{margin-top:2px}
.document-ledger details{margin-top:14px}
.document-record > summary,
.document-ledger summary{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;color:var(--blau);cursor:pointer;
}
.document-record .document-ledger{margin-top:14px}
/* Inside the record the ledger stacks. »Verarbeitungsstatus« and a full share
   path are both long, and side by side in a 400px rail each of them breaks
   mid-word; standing under its own name, the value has the whole width and
   breaks where it has to and not before. The rule divides one field from the
   next, which is what the rule between rows was always for — so the value
   carries none, or it would divide a name from what it names. */
.document-record .ledger{display:block}
.document-record .ledger dt{padding:9px 0 0}
.document-record .ledger dd{border-top:0;padding:2px 0 9px}
.document-ledger pre{margin-top:10px}

/* ── 17. Report dialog. A native <dialog>: `report.rs` calls
   `show_modal()`/`close()`, never an `open` attribute toggle — only the
   imperative API gives the UA's own backdrop, focus trap and Escape-to-close
   for free. The UA default dialog chrome (white canvas, 1em border) is reset
   below to the card surface. The chain-broken state reuses
   `.banner.banner--blocked` — the same hatched composition `trace.rs`'s
   `Kette prüfen` uses for a broken chain — rather than a second "this audit
   chain is broken" treatment, and the intact state reuses
   `.verify-bar .status.ok` for the identical reason. */
/* The UA-default reset and the card paint must stay in ONE rule: split apart,
   the reset (0-1-1) outweighs the paint (0-1-0) and the modal renders
   transparent.
   The width is the document's rather than a dialog's: the Auditbericht carries
   tables of eight columns — a deletion states its instant, its kind, its
   operator, its subject, its basis and a count per store — and every one of
   those columns is a fact the export exists to show. */
dialog.report-dialog{
  border:1px solid var(--linie-struktur);padding:0;color:inherit;max-width:none;max-height:82vh;
  background:var(--kohle-card);border-radius:var(--radius-lg);
  width:min(920px,100%);box-shadow:var(--shadow-soft);
}
dialog.report-dialog::backdrop{background:rgba(0,0,0,0.55)}
.report-dialog-body{display:flex;flex-direction:column;max-height:82vh}
.report-dialog-head{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:20px 24px;border-bottom:1px solid var(--linie-struktur);
}
.report-dialog-head h3{font-family:var(--sans);font-size:20px;font-weight:700;letter-spacing:-0.02em}
/* The "Auditorensicht" chip + subline, stacked under the title rather than
   beside it: the chip alone reuses `.chip.on` from the header's replay
   indicator, so the subline is what actually explains the label. */
.report-dialog-title-group{display:flex;flex-direction:column;gap:6px;min-width:0}
.report-dialog-auditor{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.report-dialog-auditor-subline{
  font-family:var(--sans);font-size:var(--fs-meta);color:var(--text-2);line-height:1.5;
}
.report-dialog-content{padding:20px 24px 26px;overflow-y:auto;flex:1;min-height:0}
.report-body{display:flex;flex-direction:column;gap:14px}
.report-body.rule--blocked{padding:2px 0 2px 16px}
.report-body h4{font-family:var(--sans);font-size:16px;font-weight:700}
/* Down a column every item stretches to the column's width, which of a chip and
   of a bar holding one word is not what either of them is. Both state their own
   width instead, and what stretches here is the document. */
.report-body > .tag,
.report-body > .verify-bar{align-self:flex-start}
/* `.empty` carries the padding a rail pane's own empty state needs, and neither
   the sentence a loading export stands under nor the purpose form's hint is
   standing in a rail pane. */
.report-body .empty{padding:0}
/* The body is a compliance document, and a document's tables are as much of it
   as its prose. Prose keeps the measure a line is read at; a table takes the
   width it has, because a column cut off at the measure is a number the reader
   cannot see at all. */
.report-body .md{max-width:none}
.report-body .md-block{max-width:var(--measure)}
.report-body .md-block:has(> table){max-width:none}
.report-dialog-foot{
  display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  padding:16px 24px;border-top:1px solid var(--linie-struktur);
}
/* The window and the act on it hold the left of the row; what leaves the
   building holds the right. */
.report-dialog-foot .spacer{flex:1}
.report-dialog-foot input[type="date"]{
  background:var(--kohle-card-2);border:1px solid var(--linie-struktur);border-radius:var(--radius);
  color:inherit;font-family:var(--sans);font-size:var(--fs-ui);
  font-variant-numeric:tabular-nums;padding:8px 10px;
  transition:border-color 200ms var(--ease), box-shadow 200ms var(--ease);
}
.report-dialog-foot input[type="date"]:focus{
  outline:0;border-color:var(--blau);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--blau) 24%, transparent);
}
.report-dialog-foot input[type="date"]:disabled{opacity:0.5}
@media (max-width:600px){.report-dialog{width:100%;max-height:100%;border-radius:0}}

/* ── 18. Purpose dialog. A form fragment rather than a native `<dialog>` of
   its own — `purpose.rs` raises none — so it states its own measure, heading
   spacing and action-row layout and takes everything else from the primitives:
   the textarea reuses `.field`'s control treatment exactly as the login form's
   inputs do, and the rejected-attempt error line reuses `.rule--blocked`, the
   same treatment `LoginScreen`'s own error slot uses, rather than a second
   "this was refused" texture. It stands at two mount points — inside the report
   dialog's `.report-dialog-content` and inside the console's own events panel
   — and the rules below hold at both. */
/* One typed sentence, so one measure at both mount points: a field the width of
   a 1080px panel invites a paragraph the chain does not want. */
.purpose-form{display:flex;flex-direction:column;gap:14px;max-width:var(--measure)}
.purpose-form h4{font-family:var(--sans);font-size:16px;font-weight:700}
/* This form stands in a dialog and in the console's own panel, and in neither
   is it a rail pane — so `.empty`'s pane padding goes, and the refused
   attempt's hatched rule is restated with the indent that padding takes with
   it. Without the indent the sentence stands ON its own rule. */
.purpose-form .empty{padding:0}
.purpose-form .rule--blocked{padding:2px 0 2px 16px}
.purpose-form-actions{display:flex;justify-content:flex-end;gap:12px;flex-wrap:wrap}
@media (max-width:420px){.purpose-form-actions{justify-content:stretch}
  .purpose-form-actions .btn,.purpose-form-actions .btn--ghost{flex:1}}

/* ── 19. Governance console. Built out of the primitives above wherever one
   fits: `.ledger` for a scope and a deletion record, `.chip.interactive` for
   the drill-down into a trace, `.field` for the filter bar, `.tag` and `.code`
   for the values in a chain row, `.empty` and `.banner--blocked` for the states
   a read can land in. What this section adds is what a report needs and a
   conversation does not — the card, the metric, the table, the bounded axis and
   the heading a population stands under — because the console is the one
   surface in this product that reads as a report. */
/* Each card is as tall as what it holds. Stretched to its neighbour's height,
   a card carrying three lines beside one carrying eighteen rows is mostly an
   empty box, and an empty box reads as something that failed to load. */
.gov-cards{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:18px;
  align-items:start;
}
.gov-card{
  border:1px solid var(--linie);border-radius:var(--radius-lg);
  background:var(--kohle-card);box-shadow:var(--shadow-card);padding:18px 20px;
  display:flex;flex-direction:column;gap:12px;min-width:0;
}
.gov-card h3{font-family:var(--sans);font-size:16px;font-weight:700}
.gov-card .trace-verify{display:flex;flex-direction:column;gap:10px;align-items:flex-start}
/* `.empty` carries a rail pane's own padding, and a card is not a rail pane. */
.gov-card .empty{padding:0}
/* Two records under one heading — the last sweep and the last erasure — are two
   acts and not one list of fields about one act. The structural line divides
   them, and the second record's own first hairline steps aside for it so the
   division is one line rather than two. */
.gov-card .ledger + .ledger{
  margin-top:2px;padding-top:12px;border-top:1px solid var(--linie-struktur);
}
.gov-card .ledger + .ledger > dt:first-child,
.gov-card .ledger + .ledger > dt:first-child + dd{border-top:0}
/* The degradation banner is a shell bar above the views and a note inside a
   card: same register, same label before the service's own German, but the
   card supplies the padding and the rule below it would read as a stray
   divider. */
.gov-card .degraded{padding:0;border-bottom:0;background:none}

/* A flag on a filter bar: the box and the words for it are one control, so the
   label carries the input rather than pointing at it. */
.gov-check{
  display:flex;align-items:center;gap:8px;
  font-family:var(--sans);font-size:var(--fs-label);color:var(--text-2);cursor:pointer;
}

.gov-nav{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  border-bottom:1px solid var(--linie-struktur);padding-bottom:12px;
}
.gov-nav .spacer{flex:1}
.gov-nav .btn--ghost.on{color:var(--blau);border-color:var(--blau)}

.gov-panel{display:flex;flex-direction:column;gap:18px}
/* The heading a whole population of the panel stands under. It sits on the
   panel rather than inside a card, so it names its rank here: a heading a
   browser sizes for us ranks against nothing else on the page. */
.gov-section{
  font-family:var(--sans);font-size:18px;font-weight:700;
  letter-spacing:-0.015em;line-height:1.3;margin-top:10px;
}
/* The filter bar, what it says about itself and the occasion every read below
   carries are three things, and the panel's own rhythm divides its children
   rather than what one of them holds. */
.gov-events-head{display:flex;flex-direction:column;gap:14px}
.gov-filters{display:flex;gap:14px 18px;flex-wrap:wrap;align-items:flex-end}
.gov-filters .field{min-width:150px}
/* A list of Ablagepfade is a column of paths and not a field beside two
   others: it takes the bar's own width and wraps the row after it. */
.gov-filters .field--wide{flex-basis:100%}
/* Which act is showing. A row of chips with one marked, and deliberately not a
   second `.gov-nav`: the console's own section nav stands directly above this
   panel, and two identical bars stacked read as one control that grew. */
.akt-choice{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.gov-filters input[type="date"],.gov-filters input[type="number"],
.gov-filters input[type="text"],.gov-filters textarea{
  font-family:var(--sans);font-size:var(--fs-ui);font-variant-numeric:tabular-nums;
}
.gov-note{color:var(--text-2);font-size:var(--fs-ui);line-height:1.6;max-width:74ch}
/* A key/value record here keeps the measure the sentences beside it keep. A
   ledger drawn the full width of a 1080px report is five hairlines the length
   of the page with a value at the left end of each. */
.gov-panel .ledger{max-width:var(--measure)}
.gov-metrics{display:flex;gap:30px;flex-wrap:wrap}
.gov-metric{display:flex;flex-direction:column;gap:3px}
.gov-metric .n{font-size:28px;font-weight:700;font-variant-numeric:tabular-nums;line-height:1.2}
.gov-walk{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.gov-scope{display:flex;flex-direction:column;gap:8px}
.gov-drill{
  border:1px solid var(--linie);border-radius:var(--radius-lg);
  background:var(--kohle-card);box-shadow:var(--shadow-card);
  padding:18px 20px;display:flex;flex-direction:column;gap:14px;align-items:flex-start;
}
.gov-drill h3{font-family:var(--sans);font-size:16px;font-weight:700}

/* A wide table scrolls INSIDE its block rather than widening the view, the
   same treatment `.md .table-wrap` gives an answer's table. */
.gov-panel .table-wrap,.gov-card .table-wrap{overflow-x:auto}

/* One axis of the report, bounded. Eight of them stand under each other on this
   page — five over requests, three over runs — separated by the sentences that
   qualify them, and a reader counting down a column has to see where one axis
   ends and the next begins. The surface is the card's, because that is what
   this product draws around a thing to be read as one thing.
   The measure is stated at the table's own size, so an axis is exactly as wide
   as the notes beside it: a key at one edge of the view and its count at the
   other is a pair the eye has to carry across the whole width. */
.gov-axis{
  font-size:var(--fs-ui);max-width:var(--measure);
  background:var(--kohle-card);
  border:1px solid var(--linie);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);
}
.gov-axis .gov-table th,.gov-axis .gov-table td{padding-left:18px}
.gov-axis .gov-table th{padding-top:12px}
.gov-axis .gov-table tr:last-child td{padding-bottom:12px}

.gov-table{width:100%;border-collapse:collapse;font-size:var(--fs-ui)}
.gov-table th{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:700;
  color:var(--eierschale);text-align:left;
  padding:8px 14px 8px 0;border-bottom:1px solid var(--linie-struktur);white-space:nowrap;
}
.gov-table td{
  padding:8px 14px 8px 0;border-bottom:1px solid var(--linie);
  vertical-align:top;overflow-wrap:anywhere;font-variant-numeric:tabular-nums;
}
.gov-table th.num,.gov-table td.num{text-align:right;padding-right:18px}
.gov-table tr:last-child td{border-bottom:0}
/* A cell breaks anywhere, which is what a share path and a directory name need
   and what a boxed token cannot survive: `agent_run_ended` split across two
   lines inside its own box is not the word the chain wrote. A box holds
   together and the table scrolls instead — the answer this section already
   gives a table too wide for the view. */
.gov-table td .tag,.gov-table td .chip{white-space:nowrap}

/* ── 20. The switcher. A native <dialog>, shown with `show_modal()` for the
   reason the report dialog is: only the imperative API gives the UA's own
   backdrop, focus containment and Escape-to-close, which the `open` attribute
   does not. The shape is the one every chat product a reader has already used
   draws for the same act — a field at the top of a panel near the top of the
   screen, a scrolling list of rows under it — and the familiarity is the
   point: nothing here has to be learned before it can be used.

   The width is a list's rather than a document's. Every row is a title, two
   short quotations and a date; the measure that reads well for those is closer
   to a column than to a page, and a 900px row puts a date at one edge of the
   view and the title it belongs to at the other. */
/* The UA-default reset and the card paint stay in ONE rule, as the report
   dialog's do: split apart, the reset (0-1-1) outweighs the paint (0-1-0) and
   the modal renders transparent. */
dialog.switcher{
  border:1px solid var(--linie-struktur);padding:0;color:inherit;max-width:none;
  background:var(--kohle-card);border-radius:var(--radius-lg);
  width:min(620px,100%);box-shadow:var(--shadow-soft);
  /* Near the top rather than centred: the list grows downward as a reader
     types, and a box centred on its own height would slide under them. */
  margin:11vh auto auto;
}
dialog.switcher::backdrop{background:rgba(0,0,0,0.55)}
.switcher-body{display:flex;flex-direction:column;max-height:72vh}

/* The one input, and the glyph that says what it is for before the placeholder
   has been read. The row IS the field's edge — the input inside it carries
   none of its own, so the pair lights up together rather than one box drawing
   itself inside another. */
.switcher-field{
  display:flex;align-items:center;gap:12px;
  padding:14px 18px;border-bottom:1px solid var(--linie-struktur);
  transition:border-color 200ms var(--ease);
}
.switcher-field:focus-within{border-bottom-color:var(--blau)}
.switcher-field-glyph{flex:0 0 auto;color:var(--text-3);font-size:19px;line-height:1}
.switcher-field input{
  flex:1;min-width:0;background:none;border:0;outline:0;
  color:var(--graphit);font-family:var(--sans);font-size:16.5px;line-height:1.5;padding:2px 0;
}
.switcher-field input::placeholder{color:var(--text-3)}

.switcher-list{flex:1;min-height:0;overflow-y:auto;padding:6px 14px 16px}
.switcher-list h3{padding:16px 4px 8px;margin:0}
.switcher-list > h3:first-child{padding-top:8px}
.switcher-list .empty{padding:10px 4px 0;font-size:var(--fs-meta)}
.switcher-group{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}

/* A row is an object, the way a row of the sidebar is one: its own fill one
   step above the ground it sits on, its own edge, its own corners. The list is
   read by counting boundaries, and two lines of bare text separated by a margin
   give the eye nothing to count. */
.switcher-row{
  display:flex;flex-direction:column;gap:5px;
  width:100%;text-align:left;padding:9px 11px;cursor:pointer;
  background:var(--kohle-card-2);
  border:1px solid var(--linie);border-radius:var(--radius);
  transition:border-color 160ms var(--ease);
}
.switcher-row:hover{border-color:var(--linie-struktur)}
/* The row the cursor names, drawn from the moment the row set stands rather
   than only once a keypress moves the browser's own focus onto it: Enter
   acts on this row before the arrows have ever touched it, so it has to read
   as the target from the start. A class alongside the roving tabindex, never
   a focus steal — the accent border and inset ring are the same pair
   `.source-card.selected` wears, so a cursor reads as the same kind of fact
   wherever it stands. The global focus ring still marks whichever row
   actually holds focus once the arrows have moved it there; the two coexist
   without conflict, one accent doing both jobs. */
.switcher-row--cursor{
  border-color:var(--blau);
  box-shadow:inset 0 0 0 2px var(--blau);
}
.switcher-row-head{display:flex;align-items:center;gap:8px;min-width:0}
.switcher-row-title{
  flex:1 1 auto;min-width:0;
  font-family:var(--sans);font-size:var(--fs-ui);font-weight:500;line-height:1.4;
  color:var(--graphit);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.switcher-row-meta{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* One half of the matched turn, quoted: which half it is, and the words. The
   label holds its width and the quotation takes the rest, so the two halves of
   a match line up down the row and a reader compares like with like. */
.switcher-snippet{display:flex;gap:9px;align-items:baseline;min-width:0}
/* The label holds the secondary rank rather than the muted one: it names which
   voice the quotation beside it is, which is a fact and not a separator. */
.switcher-snippet .label-field{flex:0 0 auto}
.switcher-snippet-text{
  min-width:0;font-family:var(--sans);font-size:var(--fs-meta);line-height:1.55;
  color:var(--text-2);overflow-wrap:anywhere;
}
/* The words that matched, in the box the product already uses for one
   exceptional fact. A snippet is prose and is read at the prose contrast, so
   the mark takes the marked chip's own pair rather than a highlighter yellow
   that would sit under the floor in either theme. */
.switcher-snippet-text mark{
  background:var(--marke-bg);color:var(--marke-fg);
  border-radius:var(--radius-xs);padding:0 2px;
}

.switcher-foot{
  padding:12px 18px;border-top:1px solid var(--linie-struktur);
  font-family:var(--sans);font-size:var(--fs-meta);line-height:1.5;color:var(--text-2);
}

/* On a phone the dialog is the screen: a 620px panel floating 11vh down a
   390px viewport is a panel with nowhere to float. */
@media (max-width:600px){
  dialog.switcher{width:100%;max-height:100%;margin:0;border-radius:0}
  .switcher-body{max-height:100vh}
}

/* ── 21. Paper. A rendered letter — a draft on the letterhead its run pins, or
   the sample letter through a letterhead being written. The fragment inside is
   `zenpai_paper`'s own HTML and carries its own class names; this section is
   the only place that styles them, so the crate stays a renderer and this
   stays a stylesheet.

   The sheet is light in BOTH themes and its ink is dark in both, because it is
   depicting paper: a letter that inverted with the app's theme would be
   showing a page nobody can print. Both fixed values are AAA against each
   other (16.8:1), which is what a page of prose is read at.

   It is not a facsimile. There is no page shadow, no A4 aspect ratio and no
   pagination: HTML does not paginate, and a sheet pretending to be one page
   would be claiming a page break this preview cannot know. What it shows is
   the content and the letterhead. */
.paper-sheet{
  background:var(--kaolin);color:var(--tinte);
  border:1px solid var(--linie-struktur);border-radius:var(--radius);
  padding:26px 30px;overflow-x:auto;
}
.paper{display:flex;flex-direction:column;gap:14px;line-height:1.5}
/* The marking, at the head of the page and again in the footer — the two
   places `paper/` puts it. It is the one exceptional fact on the sheet, so it
   takes the marked chip's own pair rather than a colour of its own, and it is
   spaced out because that is how a marking is set on German paper. */
.paper-marking{
  align-self:flex-start;
  background:var(--marke-bg);color:var(--marke-fg);
  border-radius:var(--radius-xs);padding:2px 8px;
  font-weight:700;letter-spacing:0.08em;text-transform:uppercase;
}
.paper-letterhead{display:flex;align-items:flex-start;gap:18px;flex-wrap:wrap}
/* The logo keeps its own proportions and is bounded by the band the letter
   reserves for it — never stretched, and never allowed to push the sender
   block off the line. */
.paper-logo{max-height:64px;max-width:240px;object-fit:contain}
.paper-sender{white-space:pre-wrap;flex:1 1 240px;min-width:0}
.paper-head{display:flex;flex-direction:column;gap:8px}
.paper-field{display:flex;flex-direction:column;gap:2px}
/* The slot's own label sits over its value in the muted rank the page has:
   these are the letter's field names, not the app's. */
.paper-label{opacity:0.72;font-weight:600}
.paper-value{white-space:pre-wrap;overflow-wrap:anywhere}
.paper-body{display:flex;flex-direction:column;gap:10px}
.paper-body h1,.paper-body h2,.paper-body h3{font-weight:700;line-height:1.3}
.paper-body p{white-space:pre-wrap;overflow-wrap:anywhere}
.paper-list{padding-left:1.3em;display:flex;flex-direction:column;gap:4px}
.paper-table{width:100%;border-collapse:collapse}
.paper-table th,.paper-table td{
  padding:5px 9px;border-bottom:1px solid rgba(20,22,26,0.18);
  vertical-align:top;overflow-wrap:anywhere;
}
.paper-table th{font-weight:700;border-bottom-width:2px}
/* A page break is a break in the paper, and on a screen it is the only honest
   way to draw one: a rule where the next page would start. */
.paper-page-break{border-top:1px dashed rgba(20,22,26,0.34);margin:6px 0}
.paper-footer{
  border-top:1px solid rgba(20,22,26,0.18);padding-top:10px;
  display:flex;flex-direction:column;gap:8px;
}
.paper-footer-text{white-space:pre-wrap}

/* The draft pane's document branch: the same rule and label the markdown one
   wears, with the sheet under it. */
.turn-draft-paper .paper-sheet{margin-top:4px}
/* What can be done with the draft above, on the line under it. The act is
   named once and its three doors stand beside it — never three verbs, which
   would read as three different acts. */
.draft-actions{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:12px;
}

/* ── 22. The letterheads, in the console. Built out of the governance
   console's own primitives — the card, the table, the note — plus what a form
   needs that a report does not: a wizard's screen, its actions, and the four
   slots of a letter head. */
.template-acts{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.template-removed > summary{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:600;
  color:var(--blau);cursor:pointer;
}
.template-removed-list{
  list-style:none;display:flex;flex-direction:column;gap:6px;margin-top:10px;
}
.template-removed-list li{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.template-wizard{gap:16px}
.wizard-screen{display:flex;flex-direction:column;gap:14px}
.wizard-screen .field{max-width:var(--measure)}
.wizard-actions{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  border-top:1px solid var(--linie-struktur);padding-top:14px;
}
/* One slot of the letter head: the key it is filed under, what it is called,
   and whether a letter may leave it empty. The key leads, because that is the
   name the model states it by. */
.template-slot{
  display:flex;flex-direction:column;gap:8px;
  border-top:1px solid var(--linie);padding-top:12px;
}
.template-required{
  display:flex;align-items:center;gap:8px;
  font-family:var(--sans);font-size:var(--fs-label);color:var(--text-2);cursor:pointer;
}
.template-logo-held{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
/* The picked image, at the size the letter's own band gives it — so what the
   wizard shows and what the sheet under it shows are the same picture. */
.template-logo-preview{
  max-height:64px;max-width:240px;object-fit:contain;
  background:var(--kaolin);border:1px solid var(--linie);
  border-radius:var(--radius-xs);padding:4px;
}
