@font-face {
  font-family: "Poppins";
  src: url("../fonts/Poppins-Regular.48cbe3a935fb.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Poppins";
  src: url("../fonts/Poppins-SemiBold.1792e2773e6e.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Brand colors */
  --primary: #567c8d;
  --primary-soft: color-mix(in srgb, var(--primary) 18%, white);
  --accent: #b7c688;
  --navy: #2f4156;
  --muted: #6b7280;
  /* v2 visual design (2026-08-08): warm near-white page, pure white cards,
     soft shadow instead of a hard border. */
  --bg: #faf7f4;
  --surface: #ffffff;
  /* Recessed fields inside a white card (search input, avatar fallback). */
  --surface-sunken: #f7f4f1;
  --border: #ebe5df;
  --banner-bg: #e9eff3;
  --success: #16a34a;
  --warning: #d9a441;
  --error: #dc2626;

  /* Typography */
  --font: "Poppins", system-ui, -apple-system, sans-serif;

  /* Spacing scale */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;

  /* Radius */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 20px;

  /* Shadow — cards carry a soft lift rather than a visible border. */
  --shadow-sm: 0 1px 3px rgba(47, 65, 86, 0.05), 0 6px 16px rgba(47, 65, 86, 0.05);
  --shadow-md: 0 2px 6px rgba(47, 65, 86, 0.07), 0 12px 28px rgba(47, 65, 86, 0.08);
  --shadow-elevated: 0 8px 24px rgba(47, 65, 86, 0.12), 0 24px 64px rgba(47, 65, 86, 0.12);
}

* {
  box-sizing: border-box;
}

/* The hidden attribute must always win, even over a component's display rule
   (a class like `display:flex` otherwise beats [hidden]'s UA `display:none`). */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--navy);
  padding-bottom: var(--s-8);
}

/* Form controls do not inherit font-family: the browser forces its own UI font
   (Arial here), so every button and input rendered in Arial inside a Poppins
   design. It showed up as a <button> looking visibly smaller than an <a>
   styled to match it, at identical font-size, weight and line-height. This is
   the root fix; base.css already carried a dozen local `font-family: inherit`
   patches for the same cause, which this makes redundant. */
button,
input,
select,
textarea {
  font-family: inherit;
}

a {
  color: var(--primary);
}

/* Type scale — one consistent set of heading sizes across every page, so
   "similar sections" (page titles, card headings, sub-headings) line up
   instead of each bare <h1> falling back to the oversized browser default. */
h1,
h2,
h3 {
  color: var(--navy);
  font-weight: 600;
  line-height: 1.25;
}

h1 { font-size: 1.5rem; margin: 0 0 var(--s-4); }
h2 { font-size: 1.125rem; margin: 0 0 var(--s-3); }
h3 { font-size: 1rem; margin: 0 0 var(--s-2); }

.kp-container {
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 var(--s-4);
}

/* Card primitive */
.card {
  background: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--s-5);
  margin-bottom: var(--s-4);
}

/* Modifier for standalone forms (e.g. login) that shouldn't stretch to the
   full width of the wider .kp-container. */
.card-narrow {
  /* Used only by the login card. 28rem gives the logo and the pill inputs
     room to breathe without a two-field form sprawling across the viewport.

     width: 100% is load-bearing. .page-auth is a column flex container, and
     the auto margins below make this a shrink-to-fit flex item: without an
     explicit width it sizes to its content (267px) and never reaches
     max-width, so raising the cap alone changed nothing. */
  width: 100%;
  max-width: 28rem;
  margin-left: auto;
  margin-right: auto;
}

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s-3);
}

/* Inside a card head the heading sits inline with the badge — drop its margin
   and let the global type scale drive the size (title h1 > section h2/h3). */
.card-head h1,
.card-head h2,
.card-head h3 {
  margin: 0;
}

/* Groups a heading with an adjacent action (e.g. an inline-edit pencil) so
   card-head's space-between treats them as one unit, not a third column. */
.card-head-title {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* Modal shell — mobile-first bottom sheet, centered dialog ≥560px. Shared by
   the capture modal, the project-create modal and the assign modal (any future
   "small form in an overlay" reuses this rather than re-declaring it). */
.modal[hidden] { display: none; }   /* self-contained hide — no reliance on (cached) base.css */
.modal { position: fixed; inset: 0; z-index: 80; display: flex;
  align-items: flex-end; justify-content: center; padding: 0; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(20,25,45,.45); }
.modal-card { position: relative; width: 100%; max-width: 440px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 18px 18px 0 0; box-shadow: var(--shadow-elevated);
  padding: 1.1rem 1.2rem calc(1.2rem + env(safe-area-inset-bottom)); max-height: 92vh; overflow-y: auto; }
@media (min-width: 560px) {
  .modal { align-items: center; padding: 1rem; }
  .modal-card { border-radius: 18px; }
}
.modal-head { display: flex; justify-content: space-between; align-items: center; gap: .6rem; margin-bottom: .6rem; }
.modal-head strong { font-size: 1.05rem; color: var(--navy); }
.modal-x { border: 0; background: none; font-size: 1.5rem; line-height: 1; cursor: pointer;
  color: var(--muted); padding: 0 .2rem; box-shadow: none; }
.modal-x:hover { background: none; transform: none; color: var(--navy); }
/* "Are you sure?" (static/js/confirm-dialog.js): the modal shell with a
   question, what happens, and a button naming the action. A delete gets a
   solid red button; Abbrechen is always the quiet one. */
.confirm-modal .modal-card { max-width: 420px; }
.confirm-text { margin: 0 0 var(--s-5); color: var(--navy); line-height: 1.5; }
.confirm-actions { display: flex; justify-content: flex-end; gap: var(--s-2); flex-wrap: wrap; }
.confirm-ok-danger { background: var(--error); border-color: var(--error); color: #fff; }
.confirm-ok-danger:hover { background: color-mix(in srgb, var(--error) 85%, black); color: #fff; }
.rec-head-actions { display: inline-flex; gap: .4rem; align-items: center; }
.rec-fields { display: flex; flex-direction: column; gap: .7rem; margin-bottom: .9rem; }
.rec-fields .field { margin: 0; }
.rec-fields .field-label { display: block; font-size: .8rem; color: var(--muted); margin-bottom: .2rem; }
.rec-fields input,
.rec-fields select { width: 100%; padding: .6rem; border: 1px solid var(--border);
  border-radius: var(--radius-sm); font-size: 1rem; font-family: inherit;
  background: var(--surface); color: var(--navy); }
.rec-btn { padding: .55rem 1rem; font-size: .9rem; border-radius: 999px; border: 1px solid var(--border);
  background: transparent; color: var(--navy); cursor: pointer; box-shadow: none; }
.rec-btn:hover { transform: none; box-shadow: none; background: var(--bg); }
.rec-btn.rec-primary, .rec-btn.rec-primary:hover { background: var(--primary); color: #fff; border-color: var(--primary); }

/* Nav bar: brand left, tabs CENTRED, avatar right. The three-column grid is
   what keeps the tabs optically centred on the page regardless of how wide the
   tenant name or the avatar are (flex + space-between would let a long tenant
   name push the tabs off-centre). */
.kp-nav-bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s-4);
  border-bottom: 1px solid var(--border);
  padding: var(--s-4) 0;
  margin-bottom: var(--s-6);
}

.kp-nav-bar > :last-child {
  justify-self: end;
}

/* justify-self is load-bearing: .kp-nav-bar is a grid, and a grid item
   defaults to `stretch`, so without this the (blockified) anchor's box fills
   its whole 1fr column and the focus ring outlines a third of the viewport. */
.kp-brand {
  color: var(--navy);
  text-decoration: none;
  font-weight: 600;
  justify-self: start;
  border-radius: var(--radius-sm);
}

.kp-brand:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.kp-brand-logo {
  display: block;
  height: 32px;
  width: auto;
  /* Caps an unusually wide logo so it scales down instead of crowding the
     tabs. */
  max-width: 45vw;
  object-fit: contain;
}

.auth-logo {
  display: block;
  height: 44px;
  width: auto;
  max-width: 70%;
  object-fit: contain;
  margin-bottom: var(--s-4);
}

.btn-subtle {
  background: transparent;
  color: var(--navy);
  border: 1px solid var(--border);
  box-shadow: none;
  padding: var(--s-1) var(--s-4);
  font-size: 0.85rem;
}

.btn-subtle:hover {
  background: var(--surface);
  color: var(--primary);
  box-shadow: none;
  transform: none;
}

/* .btn-subtle sets only colour, border and padding; the radius and layout come
   from the `button` element rule, which an <a> never matches. Without this an
   <a class="btn-subtle"> renders as underlined inline text in a square box. */
a.btn-subtle {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  text-decoration: none;
  font-weight: 600;
}

/* Filled variant of .btn-subtle, sized and painted like an active filter chip
   (.chip / .chip.active): same 40px height and padding, same --primary fill as
   the Sprachaufnahme action button, so a page's emphasised controls read as one
   set. The :hover pair is required, otherwise .btn-subtle:hover repaints it
   back to the outlined look mid-interaction. */
.btn-subtle.btn-solid {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 40px;
  padding: var(--s-2) var(--s-4);
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
  font-weight: 600;
}

.btn-subtle.btn-solid:hover {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
  box-shadow: var(--shadow-sm);
}

/* Desktop tab nav — plain text links, underline marks the active tab */
.kp-tabs {
  display: flex;
  gap: var(--s-5);
  flex-wrap: wrap;
}

.kp-tab {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.9rem;
  padding-bottom: var(--s-2);
  border-bottom: 2px solid transparent;
}

.kp-tab:hover {
  color: var(--accent);
}

.kp-tab.active {
  color: var(--navy);
  font-weight: 600;
  border-bottom-color: var(--primary);
}

.kp-tab.disabled {
  color: var(--muted);
  opacity: 0.5;
  pointer-events: none;
}

/* Floating action button */
.kp-fab {
  position: fixed;
  right: var(--s-5);
  bottom: calc(var(--s-7) + var(--s-4));
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  border: none;
  box-shadow: var(--shadow-elevated);
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 20;
}

/* Forms */
/* display:block is load-bearing. .field is applied to both <div class="field">
   and <label class="field">, and a <label> is display:inline by default, where
   vertical margins do not affect layout. The margin below was therefore
   silently dropped on every label-based field, so the same class produced two
   different layouts depending on the tag (project_detail.html uses both and
   was inconsistent with itself). */
.field {
  display: block;
  margin-bottom: var(--s-4);
}

.field label {
  display: block;
  font-size: 0.875rem;
  margin-bottom: var(--s-1);
}

.field input {
  width: 100%;
  padding: var(--s-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 1rem;
}

.errors {
  color: var(--error);
  font-size: 0.875rem;
  margin-bottom: var(--s-4);
  list-style: none;
  padding: 0;
}

button {
  padding: var(--s-3) var(--s-5);
  background: var(--primary);
  color: #fff;
  border: none;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

button:hover {
  background: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

/* Status badge */
.badge {
  display: inline-block;
  padding: var(--s-1) var(--s-3);
  border-radius: 999px;
  font-size: 0.8rem;
  background: var(--border);
  color: var(--navy);
}

.badge.uploaded,
.badge.transcribing {
  background: var(--primary-soft);
  color: var(--primary);
}

.badge.done {
  background: #e6f6ea;
  color: var(--success);
}

.badge.error {
  background: #fde8e8;
  color: var(--error);
}

/* Recordings toolbar: search + status filter + sort (a plain GET form) */
.list-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  margin-bottom: var(--s-5);
}

.list-toolbar .toolbar-search {
  flex: 1 1 220px;
  min-width: 0;
}

.list-toolbar .toolbar-search,
.list-toolbar select {
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  font-family: inherit;
  color: var(--navy);
  background: var(--surface);
}

.list-toolbar select {
  cursor: pointer;
}

/* Group-by heading above a grid of that group's recordings */
.group-heading {
  margin: var(--s-6) 0 var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
}

.group-heading:first-of-type {
  margin-top: 0;
}

.group-count {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--muted);
}

/* Recording list: card-per-recording grid */
.entity-grid {
  margin-bottom: var(--s-4);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--s-4);
}

.entity-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}

.entity-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

/* Title spans the full tile width and clamps to two lines, so a long title
   never collides with the status badge (which now lives in the footer row).
   The reserved two-line height keeps every tile the same height. */
.entity-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.5em;
  margin: 0 0 var(--s-3);
  overflow-wrap: anywhere;
}

/* Footer pinned to the bottom of the tile: date on the left, badge on the
   right. Wraps the badge below the date only if the tile is very narrow. */
.entity-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-1) var(--s-2);
  margin-top: auto;
}

.entity-foot .meta {
  margin: 0;
}

.empty {
  color: var(--muted);
}

/* Hides an input visually while keeping it functional and accessible
   (used for the file input triggered by the mic-FAB label). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Detail page top bar: back link + destructive action */
.btn-danger {
  background: transparent;
  color: var(--error);
  border: 1px solid color-mix(in srgb, var(--error) 35%, var(--border));
  box-shadow: none;
  padding: var(--s-1) var(--s-4);
  font-size: 0.85rem;
}

.btn-danger:hover {
  background: color-mix(in srgb, var(--error) 10%, transparent);
  color: var(--error);
  box-shadow: none;
  transform: none;
}

/* Details card: view + edit of the AI fields (title/category/customer/address) */
.meta-list {
  margin: 0;
  display: grid;
  gap: var(--s-2);
}

.meta-list > div {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: var(--s-3);
  align-items: baseline;
}

.meta-list dt {
  color: var(--muted);
  font-size: 0.85rem;
}

.meta-list dd {
  margin: 0;
  color: var(--navy);
  font-size: 0.85rem;
}

@media (max-width: 480px) {
  .meta-list > div { grid-template-columns: 1fr; gap: 0; }
}

.meta-form .field-label {
  display: block;
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: var(--s-1);
}

.meta-form .field select {
  width: 100%;
  padding: var(--s-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 1rem;
  font-family: inherit;
  background: var(--surface);
  color: var(--navy);
}

/* A field label with an affordance beside it (e.g. the Kunde rename pencil).
   The <label> keeps the `for` association; the button is a sibling so that
   clicking it does not also activate the labelled control. */
.meta-form .field-label-row {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

/* A bare glyph sitting on the label baseline: no padding, no background, no
   button chrome. Anything more reads as a second control competing with the
   field itself. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  box-shadow: none;
  line-height: 1;
}

.icon-btn .icon {
  width: 0.9em;
  height: 0.9em;
  vertical-align: baseline;
}

.icon-btn:hover {
  background: transparent;
  color: var(--primary);
  box-shadow: none;
  transform: none;
}

.icon-btn[aria-expanded="true"] {
  color: var(--primary);
}

/* Löschen sits bottom-left, away from Speichern: destructive and constructive
   actions should not be adjacent targets. */
.meta-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--s-3);
  margin-top: var(--s-3);
}

.meta-form-actions .btn-danger {
  margin-right: auto;
}

/* ---- Address suggestions ---------------------------------------------------
   Our own listbox rather than a <datalist>, which filters its options against
   the input's value and so shows nothing once the field holds a composed line
   ("Schlossstraße 10, Lippetal"). Positioned in flow, not absolutely: the
   dialog scrolls, and an absolutely-positioned list detaches from its input
   the moment it does. */
.assign-suggestions {
  list-style: none;
  margin: 2px 0 0;
  padding: 0;
  max-height: 11rem;
  overflow-y: auto;
  border: 1px solid var(--banner-bg);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.assign-suggestion {
  display: block;
  width: 100%;
  padding: var(--s-2) var(--s-3);
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.85rem;
  color: var(--navy);
  text-align: left;
  cursor: pointer;
}

.assign-suggestion:hover,
.assign-suggestion:focus {
  background: var(--banner-bg);
  outline: none;
}

/* ---- Still-missing fields ------------------------------------------------
   Amber, never red: nothing is wrong, the form is simply not finished yet.
   The ring sits outside the border so it reads as an annotation on the field
   rather than a restyled field. */
.rec-fields .is-missing {
  border-color: var(--warning);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 18%, transparent);
}

.rec-fields .is-missing:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 30%, transparent);
}

/* A disagreement, not an error: same warning tint as the uncertainty line, and
   it never disables anything. */
.assign-hint-warn {
  margin: 0 0 var(--s-2);
  padding: 2px 0 2px var(--s-2);
  border-left: 2px solid var(--warning);
  color: var(--navy);
  font-size: 0.78rem;
  line-height: 1.4;
}

/* Names what the assignment still lacks. A note, never a block: the review
   screen saves either half of the job. */
.assign-missing {
  margin: 0 0 var(--s-2);
  color: var(--warning);
  font-size: 0.8rem;
  text-align: right;
}

/* A click that did not save. Red, unlike the amber note: here the user asked
   for something and did not get it. */
.decision-error {
  margin: var(--s-3) 0 0;
  color: var(--error);
  font-size: 0.875rem;
  font-weight: 600;
  text-align: right;
}

/* ---- Evidence, beside the field it explains --------------------------------
   Each hint sits directly under its control, so the transcript words and the
   value they produced are read together. Clicking one opens the full
   transcript at the highlighted span. */
.why-hint {
  display: block;
  width: 100%;
  margin: 1px 0 0;
  padding: 2px 0 2px var(--s-2);
  border: 0;
  border-left: 2px solid var(--banner-bg);
  background: none;
  font: inherit;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--muted);
  text-align: left;
  cursor: pointer;
}

.why-hint:hover {
  border-left-color: var(--primary);
  color: var(--navy);
}

/* ---- Provenance markers ----------------------------------------------------
   A filled dot for a value the recording announced, a hollow one for a value
   the model pieced together. Shape carries the meaning, not colour alone —
   the two must stay distinguishable to a colour-blind user and in print. */
/* A fixed box so the two glyphs occupy the same width and sit on the same
   axis, whatever their own metrics. */
.why-source {
  display: inline-block;
  /* rem, not em: the two glyphs run at different font-sizes, so an em box
     would be a different width for each and they would not line up. */
  width: 0.9rem;
  margin-right: 0.35em;
  text-align: center;
  line-height: 1;
}

/* A green dot for a value the recording announced, an orange triangle for one
   the model pieced together. The shapes carry the meaning as well as the
   colours — a dot and a triangle stay distinct to a red/green-colour-blind
   reader and in a black-and-white print, where two coloured dots would not. */
/* Sized to match each other, not to match each other's font-size: U+25CF is a
   much heavier glyph than U+25B2 at the same em, so the dot is set smaller to
   end up the same size on screen. */
.why-source[data-source="genannt"] {
  color: var(--success);
  /* U+25CF inks about 43% of its font-size, U+25B2 about 72%, so matching
     font-sizes would leave the dot half the triangle's size. Measured, not
     guessed: at these values both ink ~11px tall. */
  font-size: 1.55em;
}

.why-source[data-source="erschlossen"] {
  color: var(--warning);
  font-size: 0.95em;
}

.why-hint[data-source="erschlossen"] { border-left-color: var(--warning); }

/* Shown only when a marker is on screen, so it never explains an absent thing. */
.assign-legend {
  margin: 0 0 var(--s-2);
  color: var(--muted);
  font-size: 0.74rem;
}

.assign-legend .why-source { margin-left: 0.6em; }
.assign-legend .why-source:first-child { margin-left: 0; }

/* The one thing that still belongs above the form: it qualifies every field
   below it, so it cannot hang off a single one. */
.assign-warn {
  margin: 0 0 var(--s-2);
  padding: 2px 0 2px var(--s-2);
  border-left: 2px solid var(--warning);
  color: var(--navy);
  font-size: 0.78rem;
  line-height: 1.4;
}

.link-subtle {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.82rem;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}

.link-subtle:hover { color: var(--navy); }

/* Audio player */
.audio-player {
  width: 100%;
  margin: var(--s-4) 0 var(--s-2);
}

/* Playback-speed control under the audio player */
.audio-speed {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
}

.audio-speed-label {
  font-size: 0.8rem;
  color: var(--muted);
}

.audio-speed button {
  padding: var(--s-1) var(--s-3);
  font-size: 0.8rem;
  font-weight: 500;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--navy);
  box-shadow: none;
  cursor: pointer;
}

.audio-speed button:hover {
  transform: none;
  box-shadow: none;
  background: var(--surface);
}

.audio-speed button.active,
.audio-speed button.active:hover {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

/* Row of actions at the foot of a card (Kopieren + Neu generieren) */
.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  margin-top: var(--s-4);
}

/* "Transcription running" state with a live pulse */
.processing-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
}

.processing-hint {
  color: var(--muted);
  font-size: 0.9rem;
}

/* Transcript */
.transcript-details {
  margin-top: var(--s-4);
}

.transcript-details summary {
  cursor: pointer;
  color: var(--primary);
  font-size: 0.9rem;
}

.transcript {
  white-space: pre-wrap;
  line-height: 1.5;
  margin-top: var(--s-3);
}

.meta {
  color: var(--muted);
  font-size: 0.85rem;
  margin-bottom: var(--s-5);
}

.error-msg {
  color: var(--error);
}

/* Summary card — a calm, consistent surface like every other card (not a heavy
   dark block), with a thin left accent bar so it still reads as the primary
   insight without shouting. */
.insight-hero {
  border-left: 3px solid var(--primary);
}

.insight-hero #summary-text {
  font-size: 0.95rem;
  line-height: 1.6;
  margin-top: 0;
}

.insight-hero #summary-key-points {
  padding-left: var(--s-5);
}

.insight-hero #summary-key-points li {
  font-size: 0.95rem;
  line-height: 1.5;
}

/* Pulsing "generating" indicator */
.pulse-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.9rem;
}

.pulse-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  animation: pulse 1.2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

/* Extraction card: exactly three text levels — the card title (h2), section
   labels (h3), and one uniform body treatment for ALL content (the lead
   sentence, item lines, materials, open questions) so nothing drifts in size. */
.extraction-list h3 {
  margin: var(--s-4) 0 var(--s-2);
}

.extraction-list h3:first-child {
  margin-top: 0;
}

.extraction-list p,
.extraction-list li {
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--muted);
}

.extraction-list p {
  margin: 0 0 var(--s-2);
}

.extraction-lead {
  margin-bottom: var(--s-4);
}

.extraction-list ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.extraction-list li {
  padding: var(--s-1) 0;
}

/* Protocol (recording hero) — topic headings + bulleted notes, Granola-style.
   Kept distinct from .extraction-list so the protocol shows real bullets while
   the project extraction keeps its marker-less layout. */
.protocol-body h3 {
  margin: var(--s-4) 0 var(--s-2);
  color: var(--navy);
}
.protocol-body h3:first-child {
  margin-top: 0;
}
.protocol-body ul {
  list-style: disc;
  margin: 0 0 var(--s-3);
  padding-left: 1.35rem;
}
.protocol-body li {
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--navy);
  padding: var(--s-1) 0;
}

.hidden {
  display: none;
}

/* Toast notification */
.kp-toast {
  position: fixed;
  left: 50%;
  bottom: var(--s-6);
  transform: translateX(-50%);
  background: var(--navy);
  color: #fff;
  padding: var(--s-3) var(--s-5);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font-size: 0.85rem;
  z-index: 30;
}

/* ===========================================================================
   Capture UI — moved out of recordings/_capture_modal.html's inline <style>
   during the 2026-08-08 UI revamp (templates keep markup, styling lives here).
   =========================================================================== */

.upload-queue { position: fixed; right: 1rem; bottom: 5.5rem; width: 340px; max-width: 92vw;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; z-index: 60;
  box-shadow: var(--shadow-elevated); padding: .6rem .8rem; max-height: 60vh; overflow-y: auto; }
.upload-queue-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: .4rem; }
#upload-queue-close { border: 0; background: none; font-size: 1.2rem; line-height: 1; cursor: pointer; color: var(--muted); }
#upload-queue-close:hover { background: none; transform: none; box-shadow: none; }
.upload-item { margin: .5rem 0; font-size: .85rem; }
.upload-item .upload-name { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.upload-bar { height: 6px; background: var(--primary-soft); border-radius: 999px; overflow: hidden; margin: .25rem 0; }
.upload-bar-fill { height: 100%; width: 0; background: var(--primary); transition: width .12s ease; }
.upload-item.done .upload-bar-fill { background: var(--success); }
.upload-item.failed .upload-bar-fill { background: var(--error); }
.upload-state { color: var(--muted); font-size: .78rem; }

/* FAB row: mic + upload, same style, side by side */
.kp-fab-row { position: fixed; right: var(--s-5); bottom: calc(var(--s-7) + var(--s-4));
  display: flex; align-items: center; gap: .8rem; z-index: 20; }
.kp-fab-row .kp-fab { position: static; right: auto; bottom: auto; }
/* Mic glyph as text (emoji) — renders at font-size, can't be shrunk like an inline SVG */
#rec-open .mic-glyph { font-size: 1.7rem; line-height: 1; }

#rec-settings-btn { font-size: 1.15rem; }
.rec-mic-row { display: flex; gap: .6rem; align-items: flex-end; margin-bottom: .9rem; }
.rec-mic-row select { width: 100%; padding: .5rem; border: 1px solid var(--border);
  border-radius: var(--radius-sm); font-size: .95rem; font-family: inherit; background: var(--surface); color: var(--navy); }
.rec-meter { display: flex; align-items: center; gap: .5rem; margin: .2rem 0 .5rem; }
.rec-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--error);
  animation: pulse 1.2s ease-in-out infinite; }
.rec-dot.paused { animation: none; opacity: .45; }
.rec-time { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--navy); }
.rec-state { color: var(--muted); font-size: .85rem; }
.rec-level { height: 9px; background: var(--border); border-radius: 999px; overflow: hidden; margin: .1rem 0 .6rem; }
.rec-level-fill { height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg, #2f7d5d, #d9a441 65%, #c0392b); transition: width .06s linear; }
.rec-preview { width: 100%; margin: .2rem 0 .6rem; }
#rec-error { margin: .2rem 0 .6rem; font-size: .85rem; }
.rec-controls { display: flex; flex-wrap: wrap; gap: .5rem; }

/* Small helpers used by the modal field rows */
.field-hint { margin: 0; font-size: .85rem; color: var(--muted); }
/* Under a field in the recording's edit form, before the next label. */
#rec-edit-form .field-hint { margin: -2px 0 var(--s-3); }
.field-stacked { margin-top: .5rem; }

/* ===========================================================================
   Landing page
   =========================================================================== */

.landing-greeting { margin-bottom: var(--s-5); }

/* Capture + create actions. The upload control is a <label>, not a <button>,
   so the shared look lives on the class rather than on the element type. */
.action-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-bottom: var(--s-6);
}

.action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  min-height: 44px;
  background: var(--primary);
  color: #fff;
  border: none;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

/* The upload "+" is a <label>, so it never matched the global button:hover
   rule and stayed inert while its neighbours lifted. Hover lives on the class
   so every capture/create control behaves identically. */
.kp-fab:hover,
.action-btn:hover {
  background: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

/* Unassigned-recordings banner: same accent bar as the insight hero, so
   "this needs your attention" reads consistently across the app. */
.banner-unassigned { border-left: 3px solid var(--primary); }

.assign-list { list-style: none; margin: 0; padding: 0; }

.assign-list li {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--border);
}

.assign-list li:first-child { border-top: 0; }
.assign-list li a { flex: 1 1 auto; }
.assign-list li .meta { margin: 0; }
.assign-list li button { margin-left: auto; }

/* One-line version of the same nudge, on the projects page. */
.banner-line {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin: 0 0 var(--s-4);
  color: var(--muted);
  font-size: 0.9rem;
}

/* ===========================================================================
   Entity cards (recordings AND projects) + project pills
   =========================================================================== */

.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}

.pill {
  display: inline-block;
  padding: 2px var(--s-3);
  border-radius: 999px;
  font-size: 0.78rem;
  line-height: 1.6;
}

/* Customer filled, category outlined: the customer is the card's owner, the
   category a secondary tag. */
.pill-customer { background: var(--primary-soft); color: var(--primary); }
.pill-category { background: transparent; border: 1px solid var(--border); color: var(--muted); }

.entity-sub {
  margin: 0 0 var(--s-3);
  color: var(--muted);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

/* Per-card state: red for a failed recording, pulsing for one still running. */
.state-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.state-error { background: var(--error); }
.state-processing { background: var(--primary); animation: pulse 1.2s ease-in-out infinite; }

/* ===========================================================================
   Filter chips (rendered only when their count is above zero)
   =========================================================================== */

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
}

.chip {
  padding: var(--s-1) var(--s-4);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.82rem;
  color: var(--navy);
  background: var(--surface);
  text-decoration: none;
}

.chip:hover { border-color: var(--primary); }
.chip.active { background: var(--primary); border-color: var(--primary); color: #fff; }

.group-link { margin-left: auto; font-size: 0.85rem; font-weight: 400; }

.back-link {
  display: inline-block;
  margin-bottom: var(--s-4);
  font-size: 0.85rem;
}

/* ===========================================================================
   Flash messages (the non-member redirect explains itself through these)
   =========================================================================== */

.messages { list-style: none; margin: 0 0 var(--s-4); padding: 0; }

.message {
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-md);
  font-size: 0.9rem;
  margin-bottom: var(--s-2);
}

.message.error { background: #fde8e8; color: var(--error); }
.message.success { background: #e6f6ea; color: var(--success); }

/* An action inside a message ("Rückgängig"), on the message's own line. */
.message-action { display: inline; margin: 0 var(--s-2); }
.message-action button { padding: 2px var(--s-3); font-size: 0.85rem; }
.message a { color: inherit; }

/* ---- Row menu ("⋯" on a list row): the rare actions, out of the way ------ */
.row-menu { position: relative; flex: 0 0 auto; }
.row-menu > summary {
  list-style: none;
  cursor: pointer;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
}
.row-menu > summary::-webkit-details-marker { display: none; }
.row-menu > summary:hover,
.row-menu[open] > summary { background: var(--surface-sunken); color: var(--navy); }
.row-menu-items {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 20;
  min-width: 160px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--s-1);
}
.row-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  box-shadow: none;
  color: var(--navy);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}
.row-menu-item:hover { background: var(--surface-sunken); transform: none; box-shadow: none; }
.row-menu-danger,
.row-menu-danger:hover { color: var(--error); }
/* In a table cell (Vokabular) the menu sits inline beside Speichern. */
.vocab-actions .row-menu { display: inline-block; vertical-align: middle; }

/* ---- Papierkorb ----------------------------------------------------------- */
.unassigned-trash { margin: var(--s-2) 0 0; text-align: right; font-size: 0.85rem; }
.unassigned-trash a { color: var(--muted); }
.trash-retention {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin: var(--s-4) 0 var(--s-5);
}
.trash-retention label { color: var(--navy); font-size: 0.9rem; }
.trash-retention select { width: auto; }
.trash-list { display: flex; flex-direction: column; gap: var(--s-3); }
.trash-item .row-meta { display: block; }
.trash-purge { color: var(--warning); }

/* ===========================================================================
   Login page. It blanks the nav block, and the nav bar is what supplies the
   top spacing on every other page, so without this the card sits flush
   against the viewport edge.
   =========================================================================== */

.page-auth {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 80vh;
  padding: var(--s-7) 0;
}

/* Pill-style inputs, scoped to the login page only — .field is shared by
   every modal and the project/recording edit forms, which weren't asked
   for and shouldn't change incidentally. */
.page-auth .field input {
  border-radius: 999px;
  padding: var(--s-3) var(--s-5);
  min-height: 44px;
  border: 1px solid var(--border);
  background: var(--surface);
}

/* ===========================================================================
   Mobile. On-site capture is phone-in-hand, so every screen is checked at
   360px: single-column cards, full-width controls, no horizontal scroll.
   =========================================================================== */

@media (max-width: 520px) {
  .entity-grid { grid-template-columns: 1fr; }

  .action-row { flex-direction: column; align-items: stretch; }
  .action-row .action-btn { width: 100%; }

  .list-toolbar { flex-direction: column; align-items: stretch; }
  .list-toolbar .toolbar-search,
  .list-toolbar select,
  .list-toolbar button { width: 100%; min-height: 44px; }

  .assign-list li { flex-wrap: wrap; }
  .assign-list li button { margin-left: 0; }

  /* This block used to carry `.kp-nav-bar { flex-wrap: wrap }`, which was inert
     (.kp-nav-bar is a grid, not a flex container). The nav's real mobile reflow
     lives in the second max-width:520px block further down, which is the single
     owner of it: do not re-declare grid-template-columns here, it would only be
     overridden by that later block. */
  .kp-nav-bar { gap: var(--s-3); }
  .kp-tabs { gap: var(--s-4); }
  .kp-brand-logo { height: 24px; }
}

/* Even spacing on the project detail page: each section heading sits one
   card-gap below the block above it. The header card and the card grid share
   the same bottom margin, so dropping the heading's own top margin makes both
   gaps identical. (Was an inline <style> in project_detail.html.) */
.group-heading-flush { margin-top: 0; }

/* ===========================================================================
   v2 visual design (2026-08-08). Spec: the "Amendment: v2 visual design"
   section of docs/superpowers/specs/2026-08-08-ui-revamp-design.md
   =========================================================================== */

/* Inline SVG icons inherit the colour of the text they sit beside. */
.icon {
  width: 1.05em;
  height: 1.05em;
  flex: 0 0 auto;
  vertical-align: -0.15em;
}

/* ---- Account menu (replaces the top-bar Abmelden button) ---------------- */

.user-menu { position: relative; }

.user-avatar {
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  border: none;
  font-size: 0.95rem;
  font-weight: 600;
  box-shadow: none;
  cursor: pointer;
}

.user-avatar:hover {
  background: var(--primary);
  transform: none;
  box-shadow: var(--shadow-sm);
}

.user-menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + var(--s-2));
  min-width: 12rem;
  background: var(--surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--s-2);
  z-index: 70;
}

.user-menu-name {
  margin: 0;
  padding: var(--s-2) var(--s-3);
  font-size: 0.85rem;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}

.user-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--s-3);
  margin-top: var(--s-1);
  background: transparent;
  color: var(--navy);
  border: none;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  font-weight: 500;
  box-shadow: none;
  cursor: pointer;
}

.user-menu-item:hover {
  background: var(--surface-sunken);
  color: var(--navy);
  transform: none;
  box-shadow: none;
}

/* The menu holds both a link (Konto) and a submit button (Abmelden); the link
   needs the underline removed to read as the same row as the button. */
a.user-menu-item {
  text-decoration: none;
}

/* ---- Account page ------------------------------------------------------- */

/* Everything below is scoped to .account-page. The page stacks four cards of
   short forms, where the app-wide button size and the 0-top-margin heading
   rules read as cramped; other screens are wider and are left alone. */

/* Two columns on a wide viewport (Konto | Passwort, then Sicherheit |
   Richtlinie), one column below 900px. 900px rather than the sheet's usual
   520px because two 28rem cards plus the gap need ~920px before side-by-side
   stops being cramped. The grid drives the spacing, so the cards drop their
   own bottom margin. */
.account-page {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  /* Grid's default `stretch` is kept on purpose so both cards in a row share a
     height and the row reads as one band. The dead space that would otherwise
     leave in the shorter card is taken up by the rule below. */
}

.account-page .card {
  margin-bottom: 0;
  display: flex;
  flex-direction: column;
}

/* When a card ENDS in a form, that form absorbs the stretch and drops its
   action row to the card's bottom edge, so the buttons of both cards in a row
   sit on the same line instead of one floating mid-card. Fields stay put at
   the top: only .meta-form-actions moves.

   :last-child is the whole trick. The Konto card ends in a <p>, not a form, so
   its Speichern button is left where the content puts it; Passwort and
   Richtlinie do end in a form and so line up against it. */
.account-page .card > form:last-child {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.account-page .card > form:last-child .meta-form-actions {
  margin-top: auto;
  /* padding, not margin: `margin-top: auto` replaces the 16px margin, and when
     the card has no slack left auto resolves to 0 and the button ends up flush
     against the last input. Padding is immune to that, so this is the floor. */
  padding-top: var(--s-4);
}

/* Full-width above the grid rather than sitting in the first cell. */
.account-page .back-link {
  grid-column: 1 / -1;
}

@media (min-width: 900px) {
  .account-page {
    grid-template-columns: 1fr 1fr;
    /* Two 28rem cards plus the gap. Without this the grid fills .kp-container's
       72rem and the cards, still capped at 28rem and auto-centred, sit marooned
       with a gutter between them. */
    max-width: 60rem;
    margin-inline: auto;
  }

  .account-page .card-narrow {
    max-width: none;
  }
}

/* Smaller than the app-wide pill: these are settings-form actions, not the
   page's primary call to action, and the narrow card exaggerates their size. */
.account-page button,
.account-page a.btn-subtle {
  padding: var(--s-2) var(--s-4);
  font-size: 0.875rem;
  line-height: 1.25;
}

/* Two things made this link-button bigger than the <button>s beside it:
   btn-solid pins a 40px min-height for the toolbar rows it was built for, and
   .btn-subtle carries a 1px border where the `button` element rule sets
   border: none, so identical padding still rendered 2px taller and wider.
   btn-solid paints background and border the same --primary, so dropping the
   border changes the size and nothing else. */
.account-page a.btn-subtle.btn-solid {
  min-height: 0;
  border: none;
}

/* Binds the label to its own input: 1.25 line-height leaves visible leading
   under a 0.8rem label, which works against the 4px margin. */
.account-page .meta-form .field-label {
  line-height: 1.2;
}

/* The last .field's 16px bottom margin stacked with the action row's own top
   margin, so the save button sat in a 32px pocket, wider than any other gap on
   the card. Drop the field's margin and let one deliberate gap do the work. */
.account-page .meta-form .field:last-of-type {
  margin-bottom: 0;
}

.account-page .meta-form-actions {
  margin-top: var(--s-4);
}

/* Same double-margin below: 24px margin plus 16px padding put the rule 40px
   under the button and only 16px above the list it introduces, so it read as
   belonging to the button. Even it out. */
.account-page .account-readonly {
  margin-top: var(--s-5);
  padding-top: var(--s-5);
}

/* The h1 is 1.5rem; 12px under it is tighter than the 16px rhythm the fields
   below use, which made the heading look stuck to the first label. */
.account-page .card-head {
  margin-bottom: var(--s-4);
}

/* .account-note sets margin: top-only, because everywhere else it is the last
   thing in a card. In the policy card it introduces a form, so without a
   bottom gap the "Wirksam für ..." status line collides with the first field
   label. Scoped to .meta-form so the recovery-codes form, which is a bare
   <form> whose .meta-form-actions already supplies the gap, is not doubled. */
.account-page .account-note + .meta-form {
  margin-top: var(--s-4);
}

/* The global h2/h3 rules set margin-top: 0, which is right for a heading that
   opens a card and wrong for one that starts a new section partway down it.
   Give only the latter its own top space. */
.account-page .meta-form-actions + h3,
.account-page form + h3,
.account-page ul + h3,
.account-page p + h3 {
  margin-top: var(--s-6);
}

/* A subsection heading sitting directly under its card's h2 needs separation,
   but not a full section break. */
.account-page h2 + h3,
.account-page .codes-panel + h3 {
  margin-top: var(--s-4);
}

/* The last control in a card should not touch the card's bottom padding. */
.account-page .card > form:last-child .meta-form-actions {
  margin-bottom: 0;
}

/* Read-only identity (username, workspace), below the editable fields. The
   top border separates "what you can change" from "what you cannot". */
.account-readonly {
  /* The form above ends in a right-aligned pill button with no bottom margin,
     so without this margin the divider is drawn straight through it. */
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

/* Pills instead of label/value rows plus a footnote: the same three facts in
   one line rather than four, which is most of why this card used to tower over
   its row partner. */
.account-ids {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: 0;
}

/* The title plus both pills is wider than the ~400px of card interior, so the
   head wraps to a second line instead of overflowing. margin-left: auto keeps
   the pills right-aligned on the wider viewports where they do fit. */
.account-page .card-head {
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
}

.account-page .card-head .account-ids {
  margin-left: auto;
}

/* Stacked two-line pills: label above value. Narrower than the side-by-side
   form, which is what lets both sit on the title's line instead of wrapping
   under it. Scoped to .account-ids so the single-line .attr-chip used for
   materials on the project overview is untouched.

   cursor: help because the tooltip is now the only place the "admin changes
   these" note lives, so hovering has to look like it does something. */
.account-ids .attr-chip {
  position: relative;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  /* Roomier than the single-line chip: at two lines the 999px radius resolves
     to a stadium curve that would otherwise crowd the text at the corners. */
  padding: var(--s-2) var(--s-5);
  line-height: 1.3;
  text-align: center;
  cursor: help;
}

/* Tooltip. Below the pill rather than above: these sit at the top of the card,
   where anything drawn upward runs into the nav bar. */
.account-ids .attr-chip::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + var(--s-2));
  left: 50%;
  z-index: 20;
  width: max-content;
  max-width: 14rem;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-sm);
  background: var(--navy);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 400;
  line-height: 1.35;
  text-align: center;
  white-space: normal;
  transform: translateX(-50%);
  opacity: 0;
  /* Never intercept the pointer, or the tooltip covering the pill would make
     the hover flicker as focus bounces between the two. */
  pointer-events: none;
  transition: opacity 0.12s ease;
}

.account-ids .attr-chip:hover::after,
.account-ids .attr-chip:focus-visible::after {
  opacity: 1;
}

/* The label stays muted (inherited from .attr-chip); the value is the thing
   being read, so it gets the body colour and weight. */
.attr-chip-value {
  margin-left: var(--s-2);
  color: var(--navy);
  font-weight: 600;
}

/* Stacked, so the value sits under its label rather than beside it. */
.account-ids .attr-chip-value {
  margin-left: 0;
}

.account-note {
  margin: var(--s-2) 0 0;
  color: var(--muted);
  font-size: 0.8rem;
}

/* Confirmed second factors. Previously had no CSS at all, so it rendered as a
   default bulleted <ul> despite each row carrying a remove button. */
.device-list {
  list-style: none;
  margin: 0 0 var(--s-4);
  padding: 0;
}

.device-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
}

.device-list li:last-child {
  border-bottom: none;
}

.device-name {
  font-size: 0.9rem;
  font-weight: 600;
}

.device-detail {
  display: block;
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 400;
}

/* One-time recovery codes. Shown exactly once, so this is the only place the
   plaintext ever exists: it gets a highlighted panel rather than the red
   .errors styling it used to borrow. */
.codes-panel {
  margin-bottom: var(--s-5);
  padding: var(--s-4);
  border: 1px solid var(--success);
  border-radius: var(--radius-md);
  background: #e6f6ea;
}

.codes-panel-lead {
  margin: 0 0 var(--s-3);
  color: var(--success);
  font-size: 0.875rem;
  font-weight: 600;
}

.codes-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-2) var(--s-4);
  list-style: none;
  margin: 0;
  padding: 0;
}

.codes-list code {
  font-size: 0.9rem;
  letter-spacing: 0.05em;
}

/* ---- Page header (title + subtitle + one primary action) ---------------- */

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}

.page-head h1 { margin: 0; }

/* Variant for a head whose action is a control rather than a label. A 40px
   button makes the row taller than the 30px heading, so centring is what keeps
   the two optically aligned.

   Centring alone, though, splits the 10px difference and pushes the heading
   5px below where it sits on every page whose head has no action (compare
   Angebote and the landing greeting, both at the same offset). Pulling the row
   up by that half-difference puts the heading back on their line while leaving
   the button centred against it.

   The calc spells out the derivation rather than hiding a -5px: h1 is 1.5rem at
   the 1.25 body line-height, the action is .btn-subtle.btn-solid's 40px
   min-height. Update it if either changes. */
.page-head-inline {
  align-items: center;
  margin-top: calc((1.5rem * 1.25 - 40px) / 2);
  margin-bottom: var(--s-4);
}

.page-subtitle {
  margin: var(--s-1) 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

/* ---- Landing action row ------------------------------------------------- */

/* Equal columns on purpose: the primary action is emphasised by colour, not
   by being wider than its neighbours. */
.action-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

/* Icon sits left of the label on all three, so the row reads as one set. */
.action-btn {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 60px;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--navy);
  border: none;
  box-shadow: var(--shadow-sm);
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.action-btn .icon { width: 1.15rem; height: 1.15rem; }

/* Two-button variant of .action-row (e.g. project detail: mic + upload,
   no "Neues Projekt" since we're already inside one). */
.action-row-2 { grid-template-columns: repeat(2, 1fr); }

.action-btn-primary {
  background: var(--primary);
  color: #fff;
}

.action-btn:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
  background: var(--surface);
  color: var(--navy);
}

.action-btn-primary:hover {
  background: var(--primary);
  color: #fff;
}

/* ---- Unassigned-recordings banner --------------------------------------- */

.banner-unassigned {
  background: var(--banner-bg);
  border-radius: var(--radius-lg);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-6);
}

.banner-row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

/* The unassigned recordings live inside the banner, revealed by its Zuordnen
   button, so the summary and the thing you act on are one component instead of
   two sections repeating the same count. */
.banner-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid rgba(47, 65, 86, 0.12);
}

.banner-list .row-item {
  background: var(--surface);
  box-shadow: none;
  padding: var(--s-3) var(--s-4);
}

.banner-text { flex: 1 1 auto; }

.banner-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--navy);
}

.banner-sub {
  margin: 2px 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.banner-cta {
  background: var(--surface);
  color: var(--navy);
  border: none;
  border-radius: 999px;
  padding: var(--s-3) var(--s-5);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.banner-cta:hover {
  background: var(--surface);
  color: var(--primary);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

/* A standing instruction, not a failure: shown while recording on a device
   where the page cannot hold the screen awake itself. */
.rec-hint {
  margin: var(--s-2) 0 0;
  padding: var(--s-2) var(--s-3);
  border-left: 2px solid var(--warning);
  color: var(--navy);
  font-size: 0.8rem;
  line-height: 1.4;
}

/* An unassigned row with a suggestion attached wraps onto a second line: icon
   + title + Zuordnen stay on row one, the suggestion card takes the full row
   width on row two. */
.banner-list .row-item { flex-wrap: wrap; }

/* Sits with the filter above the list, not beside the banner's own button:
   it acts on the rows, not on the banner. */
.unassigned-analyse {
  margin: 0 0 var(--s-2);
}

/* ---- Unassigned-list filter ----------------------------------------------
   Same controls as the projects page, but inside the banner rather than as a
   card of its own: this list is already nested one level, and a second card
   around it would read as a third thing on the page. */
.unassigned-filter {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin: var(--s-3) 0 var(--s-2);
}

.unassigned-filter .search-field {
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: var(--s-1) var(--s-3);
}

.unassigned-filter .search-field input {
  width: 100%;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.88rem;
  padding: var(--s-1) 0;
}

.unassigned-filter .search-field input:focus { outline: none; }

.unassigned-filter .filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.unassigned-filter .filter-row select {
  flex: 1 1 8rem;
  min-width: 0;
  font-size: 0.82rem;
  padding: var(--s-1) var(--s-2);
}

/* Says how much is hidden — a filtered list silently showing three of six
   rows looks like three recordings. */
.filter-count {
  margin: 0;
  color: var(--muted);
  font-size: 0.78rem;
}

/* ---- Proposal-as-headline row (recordings/_unassigned_row.html) ---------
   The customer is what the eye looks for, so it gets the title slot and the
   weight; the project trails behind it, lighter, as the qualifier it is. The
   arrow separator is not a bullet on purpose: customer and project are not
   peers, one belongs to the other. */

.row-title .row-project {
  font-weight: 400;
  color: var(--muted);
}

/* "Neu:" is a qualifier, not the point. Small and tinted so it registers
   without competing with the name it qualifies. */
.row-title .row-new {
  font-size: 0.82em;
  font-weight: 700;
  color: var(--primary);
}

/* Says the headline is a guess. Uppercase and tiny: read once, then ignored
   on every subsequent row. */
.row-kind {
  margin-right: 0.5em;
  padding: 1px 6px;
  border-radius: var(--radius-sm, 6px);
  background: var(--banner-bg);
  color: var(--primary);
  font-size: 0.78em;
  font-weight: 700;
}

/* A hint that the proposal is less certain — plain italic text, never a
   pill: sitting beside a real button, a pill reads as clickable, and this is
   not a control. */
.row-uncertain {
  margin-left: 0.5em;
  color: var(--warning);
  font-style: italic;
  /* Matches .row-meta rather than the badge row it sits in: it is an aside
     about the proposal, and at the subtitle's 0.85rem it read as a heading. */
  font-size: 0.78rem;
}

/* The date line, below the badge row. Quietest thing in the row: it is
   provenance, not something to act on. */
.row-meta {
  display: block;
  color: var(--muted);
  font-size: 0.78rem;
}

/* The status row reads last, closest to the button it qualifies. */
.row-item .row-sub { margin-top: 3px; }

.tag-warn {
  background: color-mix(in srgb, var(--warning) 22%, white);
  color: var(--warning);
}

.btn-primary {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

.btn-primary:hover {
  background: var(--primary);
  color: #fff;
  opacity: 0.92;
}

/* ---- Section headings --------------------------------------------------- */

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin: var(--s-6) 0 var(--s-4);
}

.section-head h2 { margin: 0; }

.section-count {
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--muted);
  margin-left: var(--s-2);
}

/* ---- Filter card (search + chips) --------------------------------------- */

.filter-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--s-4);
  margin-bottom: var(--s-5);
}

.search-field {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  background: var(--surface-sunken);
  border-radius: 999px;
  padding: 0 var(--s-4);
  margin-bottom: var(--s-3);
}

.search-field .icon {
  color: var(--muted);
  width: 1.15rem;
  height: 1.15rem;
}

.search-field input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: transparent;
  padding: var(--s-3) 0;
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--navy);
  min-height: 44px;
}

.search-field input:focus { outline: none; }

.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

/* Native select carets sit hard against the right edge, which looks cramped
   inside a pill. Draw our own so its distance from the edge is ours to set. */
.filter-row select,
.trash-retention select {
  appearance: none;
  -webkit-appearance: none;
  padding: var(--s-2) var(--s-3);
  padding-right: var(--s-8);
  min-height: 40px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background-color: var(--surface);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right var(--s-4) center;
  color: var(--navy);
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.filter-row .btn-search { margin-left: auto; }

/* Wide screens: the Projekte filters on one line. A select is as wide as its
   longest option, so four of them wrapped onto a second line. Here each keeps
   its natural width and only shrinks when the row runs out of room, the widest
   (usually Kunde, with long names) the most; a long selected name is then cut
   with an ellipsis rather than pushing the row onto two lines. */
@media (min-width: 1100px) {
  .filter-card .filter-row { flex-wrap: nowrap; }
  .filter-card .filter-row select {
    /* As wide as the label it shows, not its longest option, so short
       labels are never cut to make room for a long name nobody chose.
       Chrome and Edge; elsewhere the shrinking below does the work. */
    field-sizing: content;
    flex: 0 1 auto;
    min-width: 7.5rem;
    text-overflow: ellipsis;
  }
  .filter-card .filter-row .chip,
  .filter-card .filter-row .btn-search { flex: 0 0 auto; }
  /* Kategorie and Sortieren have short, fixed labels: never cut them. Only
     Kunde and Ort, whose labels are names and towns, give way. */
  .filter-card .filter-row select[name="category"],
  .filter-card .filter-row select[name="sort"] { flex-shrink: 0; }
  /* Kunde names run longest, so Kunde gives way first; Ort only a little. */
  .filter-card .filter-row select[name="place"] { flex-shrink: 0.25; }
}

/* Chips: pill filters, the active one filled. */
.chip {
  display: inline-flex;
  align-items: center;
  padding: var(--s-2) var(--s-4);
  min-height: 40px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.85rem;
  color: var(--navy);
  background: var(--surface);
  text-decoration: none;
  white-space: nowrap;
}

.chip:hover { border-color: var(--primary); color: var(--navy); }

.chip.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
  font-weight: 600;
}

/* ---- Entity cards ------------------------------------------------------- */

.entity-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}

.entity-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--s-5);
  text-decoration: none;
  color: inherit;
}

.entity-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.entity-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}

.entity-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--navy);
  overflow-wrap: anywhere;
}

/* Category tag, top right of the card. */
.tag {
  flex: 0 0 auto;
  padding: var(--s-1) var(--s-3);
  border-radius: var(--radius-sm);
  background: var(--banner-bg);
  color: var(--primary);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Customer / address rows, each led by an icon. */
.entity-line {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0 0 var(--s-2);
  color: var(--muted);
  font-size: 0.92rem;
  overflow-wrap: anywhere;
}

.entity-line .icon { color: var(--muted); }
.entity-line.is-error { color: var(--error); }

.entity-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--border);
  font-size: 0.88rem;
}

.entity-count {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--primary);
  font-weight: 500;
}

.entity-when { color: var(--muted); }

/* Traffic-light state dot: red failed, amber running, green fine, grey empty.
   The mock only had green/grey; failures would then be invisible on the card. */
.state-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.state-error { background: var(--error); }
.state-processing { background: var(--warning); animation: pulse 1.2s ease-in-out infinite; }
.state-ok { background: var(--success); }
.state-empty { background: var(--border); }

/* ---- "In Bearbeitung": full-width rows, not grid cards ------------------ */

.row-list { display: flex; flex-direction: column; gap: var(--s-3); }

.row-item {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--s-4) var(--s-5);
  text-decoration: none;
  color: inherit;
}

.row-item:hover { box-shadow: var(--shadow-md); }

.row-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--surface-sunken);
  color: var(--primary);
}

.row-icon .icon { width: 1.3rem; height: 1.3rem; }

.row-body { flex: 1 1 auto; min-width: 0; }

.row-title {
  margin: 0;
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--navy);
  overflow-wrap: anywhere;
}

.row-sub { margin: 2px 0 0; font-size: 0.85rem; color: var(--muted); }
.row-sub.is-error { color: var(--error); }
.row-when { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }

/* ---- Unassigned list (landing banner detail rows) ----------------------- */

.assign-list li { border-top-color: var(--border); }

/* ---- Mobile ------------------------------------------------------------- */

@media (max-width: 520px) {
  .action-row { grid-template-columns: 1fr; }
  .action-row-2 { grid-template-columns: 1fr; }

  .page-head { flex-direction: column; }

  .banner-row { flex-wrap: wrap; }
  .banner-cta { width: 100%; }

  .filter-row select,
  .filter-row .btn-search { width: 100%; }
  .filter-row .btn-search { margin-left: 0; }

  .entity-grid { grid-template-columns: 1fr; }

  .kp-nav-bar {
    grid-template-columns: auto 1fr;
    row-gap: var(--s-3);
  }
  .kp-tabs { grid-column: 1 / -1; grid-row: 2; justify-content: center; }
}

/* Row items stack their title above the subtitle. The elements are spans (a
   row can be a link, and a <div> inside an <a> is invalid), so they need an
   explicit block display; a title that is itself a link must not pick up the
   default underline/blue. */
.row-title,
.row-sub {
  display: block;
}

a.row-title,
.row-title a {
  color: var(--navy);
  text-decoration: none;
}

a.row-title:hover,
.row-title a:hover {
  color: var(--primary);
}

/* ===========================================================================
   Recording detail: "Protokoll senden" dialog textarea. Moved out of
   recordings/detail.html's inline <style> — the one place left over from the
   2026-08-08 revamp that hadn't moved into base.css.
   =========================================================================== */

.send-text {
  width: 100%;
  box-sizing: border-box;
  padding: .6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: .95rem;
  line-height: 1.4;
  resize: vertical;
  background: var(--surface);
  color: var(--navy);
}

/* ===========================================================================
   Audit log. Moved out of audit/log.html's inline <style> — the last
   template left over from the 2026-08-08 revamp that hadn't moved its CSS
   into base.css. audit-head is dropped in favor of the existing .page-head.
   =========================================================================== */

.audit-flag { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.subtabs { display: flex; gap: var(--s-2); border-bottom: 1px solid var(--border); margin: 0 0 var(--s-5); }
.subtab { padding: 9px 16px; font-size: 14px; font-weight: 600; color: var(--muted);
  text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.subtab:hover { color: var(--navy); }
.subtab.active { color: var(--navy); border-bottom-color: var(--primary); }
.audit-warn { font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: #a86a12; background: #f8efd9; border-radius: 999px; padding: 3px 10px; }
.audit-sub { color: var(--muted); font-size: 14px; margin: 0 0 var(--s-5); max-width: 74ch; }
.audit-card { background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden; }
.audit-scroll { overflow-x: auto; }
table.audit { width: 100%; border-collapse: collapse; min-width: 720px; font-size: 13.5px; }
table.audit th { text-align: left; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; padding: 12px 16px;
  border-bottom: 1px solid var(--border); white-space: nowrap; }
table.audit td { padding: 11px 16px; vertical-align: top;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
table.audit tr:last-child td { border-bottom: none; }
.mono { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 12px; }
.a-time { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.a-label { font-weight: 600; color: var(--navy); }
.a-id { color: var(--muted); }
.a-link { color: var(--primary); text-decoration: none; }
.a-link:hover { text-decoration: underline; }
.a-via { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.a-actor { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.a-pid { color: var(--muted); }
.badge.user { color: #2b6a7f; background: #e3eef2; }
.badge.agent { color: #6d4bc7; background: #efe9fb; }
.badge.system { color: #4a5a6b; background: #e8edf1; }
.badge.admin { color: #a86a12; background: #f8efd9; }
.badge.anonymous { color: #6b7280; background: #eceef1; }
.diff { margin-top: 6px; font-size: 12px; color: var(--muted); }
.diff .fld { color: var(--navy); font-weight: 600; }
.diff .from { text-decoration: line-through; opacity: .75; }
.diff .to { color: #1f7a52; }
.audit-empty { padding: var(--s-7) var(--s-5); text-align: center; color: var(--muted); }
.audit-foot { color: var(--muted); font-size: 12.5px; margin-top: var(--s-4); }

/* ---- Matched quote cards (Feature 2) ------------------------------------ */

.quote-match {
  padding: var(--s-4) 0;
  border-top: 1px solid var(--border);
}

.quote-match:first-of-type { border-top: none; }

.quote-match-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
}

.quote-match-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--navy);
}

/* The similarity score is context, not the headline: the reference and the
   customer are what a human judges the suggestion by. */
.quote-match-score {
  flex: 0 0 auto;
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.quote-match-meta {
  margin: var(--s-1) 0 var(--s-2);
  font-size: 0.85rem;
  color: var(--muted);
}

.quote-match-no { font-weight: 600; color: var(--primary); }

.quote-match-items {
  margin: 0;
  padding-left: var(--s-5);
  font-size: 0.9rem;
  color: var(--navy);
}

.quote-match-items li { margin-bottom: var(--s-1); }

@media (max-width: 520px) {
  /* Score drops under the title rather than squeezing it. */
  .quote-match-head { flex-direction: column; align-items: flex-start; gap: var(--s-1); }
}

/* A disabled button must look disabled. "Passende Angebote finden" is disabled
   until a project has an extraction, but rendered identically to an enabled
   button: full colour, pointer cursor, hover lift. Clicking it did nothing and
   the only hint was a hover tooltip. The :hover pairs are needed so the lift
   and colour change do not fire on a dead control. */
button:disabled,
button[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}

button:disabled:hover,
button[disabled]:hover {
  box-shadow: none;
  transform: none;
  background: var(--primary);
}

.action-btn:disabled:hover,
.btn-subtle:disabled:hover {
  background: var(--surface);
}

/* ===========================================================================
   Quote/invoice document tables (Angebote)
   =========================================================================== */
.doc-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  margin-bottom: var(--s-5);
}
.doc-scroll { overflow-x: auto; }
table.doc { width: 100%; border-collapse: collapse; min-width: 640px; font-size: 13.5px; }
table.doc th {
  text-align: left; font-size: 13px; letter-spacing: .03em; text-transform: uppercase;
  color: var(--navy); font-weight: 700; padding: 12px 16px;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
table.doc td {
  padding: 11px 16px; vertical-align: top;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
table.doc tr:last-child td { border-bottom: none; }

/* Item tables (quote/invoice line items) get one extra column vs. the
   quotes list's table.doc, so scope the width hint to just this table --
   otherwise "Beschreibung" is squeezed into auto-layout's equal split while
   Menge/Einheit sit mostly empty. */
table.doc-items th:nth-child(3), table.doc-items td:nth-child(3) { width: 38%; }

/* On a phone the 6-column Angebote list can't fit -- table.doc's 640px
   min-width would force horizontal scrolling. Below 520px, collapse each row
   into a stacked label/value block (data-label supplies the field name that
   the hidden <thead> would have carried) so it reads top-to-bottom and never
   scrolls sideways. Scoped to .doc-list so the quote/invoice document tables
   (table.doc-items) keep their real columns. */
@media (max-width: 520px) {
  table.doc-list { min-width: 0; font-size: 14px; }
  table.doc-list thead { display: none; }
  table.doc-list tbody, table.doc-list tr, table.doc-list td { display: block; }
  table.doc-list tr {
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
  }
  table.doc-list tr:last-child { border-bottom: none; }
  table.doc-list td {
    border: none;
    padding: 3px 0;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    text-align: right;
  }
  table.doc-list td::before {
    content: attr(data-label);
    flex: none;
    text-align: left;
    color: var(--muted);
    font-weight: 600;
    font-size: 11px;
    letter-spacing: .03em;
    text-transform: uppercase;
  }
  /* Empty trailing "Rechnungen" cell adds nothing on mobile -- drop the row. */
  table.doc-list td:empty { display: none; }
  /* Lead the card with the Angebotsnummer. */
  table.doc-list td:first-child { font-weight: 700; font-size: 15px; padding-bottom: 4px; }
}

/* Line-item tables on the Angebot/Rechnung drill-down (7 columns, min-width
   640px) can't fit a phone either. Below 520px each position becomes a card:
   a "Pos N · Art" eyebrow, the Beschreibung as headline, then the money rows as
   label -> value with Gesamtpreis emphasised. Scoped to .doc-items. */
@media (max-width: 520px) {
  table.doc-items { min-width: 0; font-size: 14px; }
  table.doc-items thead { display: none; }
  table.doc-items tbody, table.doc-items tr, table.doc-items td { display: block; }
  table.doc-items tr {
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
  }
  table.doc-items tr:last-child { border-bottom: none; }
  table.doc-items td {
    border: none;
    padding: 2px 0;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    text-align: right;
  }
  table.doc-items td::before {
    content: attr(data-label);
    flex: none;
    text-align: left;
    color: var(--muted);
    font-weight: 600;
    font-size: 11px;
    letter-spacing: .03em;
    text-transform: uppercase;
  }
  /* Beschreibung is the line's headline: full width, no label, prominent. */
  table.doc-items td[data-label="Beschreibung"] {
    display: block;
    text-align: left;
    font-weight: 700;
    font-size: 15px;
    padding: 2px 0 6px;
  }
  table.doc-items td[data-label="Beschreibung"]::before { content: none; }
  /* Pos + Art read as one small muted eyebrow above the description. */
  table.doc-items td[data-label="Pos"],
  table.doc-items td[data-label="Art"] {
    display: inline-block;
    padding: 0 0 2px;
    color: var(--muted);
    font-size: 12px;
  }
  table.doc-items td[data-label="Art"]::before { content: none; }
  table.doc-items td[data-label="Pos"]::before {
    content: "Pos ";
    text-transform: none;
    letter-spacing: 0;
    font-size: 12px;
  }
  table.doc-items td[data-label="Pos"]::after { content: " ·"; }
  /* Gesamtpreis is the number that matters on each line. */
  table.doc-items td[data-label="Gesamtpreis"] { font-weight: 700; }
}

.pagination { display: flex; align-items: center; gap: var(--s-3); margin-top: var(--s-4); }
.pagination-status { color: var(--muted); font-size: 0.85rem; }
.pagination-link { font-size: 0.9rem; }

/* Angebote list: the running total sits on top and stays put while the list
   scrolls under it, so the count of matches is always in view. */
.list-count {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  padding: 10px 2px;
  margin-bottom: var(--s-3);
  border-bottom: 1px solid var(--border);
  color: var(--navy);
  font-size: 0.95rem;
}
.list-count strong { font-size: 1.15rem; font-weight: 700; }
.list-count-loaded { color: var(--muted); font-size: 0.85rem; }
/* Infinite-scroll "loading the next page" line. */
.list-loading { color: var(--muted); text-align: center; padding: var(--s-3); font-size: 0.9rem; }


.linked-invoices { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }

/* The one number on a quote/invoice card that should read as a headline,
   not another entity-line -- same idea as insight-hero's border accent. */
.doc-total {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--primary);
}

/* Gesamtsumme leads; Rabatt and MwSt sit beside it as secondary context, so
   the three numbers that describe one total are read together. Wraps rather
   than shrinking the headline on narrow screens. */
.doc-total-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  margin: var(--s-3) 0 0;
}

.doc-total-part {
  font-size: 0.85rem;
  color: var(--muted);
}

/* ---- Match feedback controls -------------------------------------------- */

.match-vote {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-3);
}

/* Deliberately quiet by default: the judgement is the point, but it should
   not shout louder than the quote it is about. State is carried by colour
   once given, so a card shows at a glance whether it was already answered. */
.vote-btn {
  padding: var(--s-1) var(--s-3);
  font-size: 0.8rem;
  font-weight: 600;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  box-shadow: none;
  cursor: pointer;
}

.vote-btn:hover {
  background: var(--surface-sunken);
  color: var(--navy);
  box-shadow: none;
  transform: none;
}

.vote-on, .vote-on:hover { background: var(--success); border-color: var(--success); color: #fff; }
.vote-off, .vote-off:hover { background: var(--error); border-color: var(--error); color: #fff; }
.vote-chosen, .vote-chosen:hover { background: var(--navy); border-color: var(--navy); color: #fff; }

.match-no-match {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

.match-no-match input[type="text"] {
  flex: 1 1 16rem;
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.85rem;
}

/* A checkbox that reads as part of a filter row rather than a stacked form
   field (the Angebote "Auch in Positionen suchen" opt-in). */
.check-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.85rem;
  color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
}

.check-inline input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--primary);
  cursor: pointer;
}

/* ---- Project page tabs + Todos ------------------------------------------ */

/* The tab strip is the page's section heading: each tab is set at .group-heading
   scale (h2) and the strip carries that heading's bottom border, so the three
   panels read as one heading you switch between rather than as chrome sitting
   above a second, repeated title. Panels therefore carry no h2 of their own. */
.tabbar {
  display: flex;
  align-items: baseline;
  gap: var(--s-5);
  border-bottom: 1px solid var(--border);
  margin: var(--s-6) 0 var(--s-4);
  padding-bottom: var(--s-2);
}

.tab {
  padding: 0 0 var(--s-2);
  margin-bottom: calc(-1 * var(--s-2) - 1px);   /* underline sits on the strip's border */
  font-size: 1.125rem;                          /* h2 */
  font-weight: 600;
  line-height: 1.3;
  color: var(--muted);
  /* The settings strip renders its tabs as <a>, the project strip as <button>.
     Without this the anchors carry the browser's default underline and the two
     strips look like different components. The active tab is marked by the
     border-bottom below, not by underlined text. */
  text-decoration: none;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  box-shadow: none;
  cursor: pointer;
}

.tab:hover {
  color: var(--navy);
  background: transparent;
  box-shadow: none;
  transform: none;
}

.tab-active, .tab-active:hover {
  color: var(--navy);
  border-bottom-color: var(--primary);
}

.tab-count {
  display: inline-block;
  min-width: 1.25rem;
  padding: 0 0.35rem;
  font-size: 0.75rem;
  line-height: 1.25rem;
  text-align: center;
  vertical-align: 0.15em;
  color: #fff;
  background: var(--primary);
  border-radius: 999px;
}

@media (max-width: 520px) {
  /* Three h2-sized labels do not fit a phone: shrink and tighten rather than
     wrap, so the strip stays one line. */
  .tabbar { gap: var(--s-3); }
  .tab { font-size: 0.95rem; }
}

/* The panel's own primary action (Neu erstellen / Passende Angebote finden),
   top of the card. Both panels use this so the two buttons are identical. */
.panel-action {
  margin-bottom: var(--s-4);
}

/* ---- Ueberblick: work items as quote-style line items -------------------- */

/* The extraction summary, in the register of a quote's Betreffzeile: the one
   sentence that says what this job is, set above everything it summarises. */
.overview-lead {
  margin: 0 0 var(--s-3);
  font-size: 1rem;
  line-height: 1.55;
  color: var(--navy);
}

/* Project materials, promoted out of the old trailing "Materialien: ..." line
   to sit with the summary they belong to. */
.overview-materials {
  margin: 0 0 var(--s-2);
}


/* Area label above the items it groups. Quieter than an h2 (the tab holds that
   rank) but clearly a divider. Also used for the Todos panel's KI subsection. */
.work-area {
  margin: var(--s-5) 0 var(--s-2);
  padding-bottom: var(--s-1);
  border-bottom: 1px solid var(--border);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.work-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Quantity column, then body. The fixed first column aligns the measures down
   the page the way a quote does. */
.work-item {
  display: grid;
  grid-template-columns: 5rem 1fr;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
}

.work-item:last-child { border-bottom: none; }

.work-item-qty {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.work-item-body {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  align-items: flex-start;
}

/* Normal weight: a full sentence in bold turned the list into a wall. */
.work-item-desc {
  color: var(--navy);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

@media (max-width: 520px) {
  .work-item { grid-template-columns: 4rem 1fr; gap: var(--s-2); }
}

/* Non-interactive chip: material, Maße, project materials. Same pill language
   as the filter chips, one step smaller and never clickable. */
.chip-row-static {
  margin-bottom: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* .meta's default bottom margin is sized for a card footer; inside a panel that
   holds only the status pill it leaves a hole. */
.meta-flush { margin-bottom: 0; }

.attr-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem var(--s-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
}

.todo-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) 0;
}

.todo-check {
  /* padding: 0 is load-bearing -- without it the global button padding
     (var(--s-3) var(--s-5)) indents every open todo away from the done ones,
     which are plain paragraphs. */
  padding: 0;
  width: 1.4rem;
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  font-size: 1.1rem;
  line-height: 1;
  text-align: left;
  cursor: pointer;
  color: var(--muted);
}

.todo-check:hover { color: var(--primary); box-shadow: none; transform: none; }

/* Same 1.4rem marker column as .todo-check, so open and done rows line up. */
.todo-done {
  display: flex;
  gap: var(--s-2);
  align-items: baseline;
  color: var(--muted);
  padding: var(--s-1) 0;
  margin: 0;
}

.todo-done::before {
  content: "☑";
  width: 1.4rem;
  flex: 0 0 auto;
}

.todo-add {
  display: flex;
  gap: var(--s-2);
  margin-top: var(--s-3);
}

.todo-add input[type="text"] {
  flex: 1 1 auto;
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.85rem;
}

.suggestion-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) 0;
  border-bottom: 1px dashed var(--border);
}

.suggestion-row .todo-text { flex: 1 1 auto; color: var(--muted); }

/* ===========================================================================
   Transcript correction (recordings/detail.html). The user edits the whole
   transcript in a textarea; the read-only view and the form swap visibility.
   .send-text is the sibling textarea style for the "Protokoll senden" dialog:
   kept separate because this one is taller and sits inside a <details>.
   =========================================================================== */

.transcript-input {
  width: 100%;
  box-sizing: border-box;
  margin-top: var(--s-1);
  padding: .6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: .95rem;
  line-height: 1.5;
  resize: vertical;
  background: var(--surface);
  color: var(--navy);
}

/* The transcript block runs right up to the button, so give it room to
   breathe rather than sitting flush against the last line of text. */
#transcript-edit-btn {
  margin-top: var(--s-4);
}

.transcript-hint {
  margin: var(--s-1) 0 0;
  font-size: .85rem;
  color: var(--muted);
}

.transcript-original {
  margin-top: var(--s-1);
}

.transcript-original > summary {
  font-size: .85rem;
  color: var(--muted);
  cursor: pointer;
}

/* Shown on the project Ueberblick tab when a recording was corrected after
   the extraction last ran, so the overview is knowably out of date. */
.stale-hint {
  margin: 0 0 var(--s-1);
  padding: var(--s-1) var(--s-2);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--surface-alt, var(--surface));
  font-size: .9rem;
  color: var(--navy);
}


/* ---- Transcript review -------------------------------------------------- */

.review-page .card { max-width: 52rem; margin-inline: auto; }

.review-lead { margin-bottom: var(--s-4); }

.review-hint {
  margin: 0 0 var(--s-3);
  color: var(--muted);
  font-size: 0.85rem;
}


/* The queue entry point on the landing page. */
.review-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-bottom: var(--s-5);
  padding: var(--s-4);
  border: 1px solid #e0a200;
  border-radius: var(--radius-md);
  background: #fdf0d5;
}

.review-banner p { margin: 0; font-size: 0.9rem; }

.term-list { list-style: none; margin: 0 0 var(--s-4); padding: 0; }

.term-item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.term-list li:last-child .term-item { border-bottom: none; }

.term-before { color: var(--muted); text-decoration: line-through; }
.term-arrow { color: var(--muted); }
.term-after { font-weight: 600; }

/* Same amber as .review-banner: "needs your attention" reads identically
   wherever it appears. */
.badge.pruefung,
.badge.zuordnung { color: #8a6400; background: #fdf0d5; }

/* Which recording the review screen is about, under the heading that names
   the job. Links to the recording's own page. */
.review-recording-name {
  margin: 0 0 var(--s-1);
  font-weight: 600;
}

/* The original transcript, laid onto the current blocks, with each change
   marked: the old words struck through, the new ones after them. */
.original-legend { color: var(--muted); font-size: .82rem; margin: var(--s-2) 0; }
.original-text {
  flex: 1 1 auto;
  margin: 0;
  padding: .3rem .5rem;
  font-size: .95rem;
  line-height: 1.6;
  color: var(--muted);
}
.transcript-original del {
  color: #9b2c2c;
  background: #fde8e8;
  text-decoration: line-through;
  border-radius: 3px;
  padding: 0 .15em;
}
.transcript-original ins {
  color: var(--navy);
  background: #ffe58f;
  text-decoration: none;
  border-radius: 3px;
  padding: 0 .15em;
}
.original-revert { margin: var(--s-2) 0 var(--s-1); }

/* ---- Speaker labels (Person 1 / Person 2) -------------------------------- */
.review-block-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.speaker-menu { position: relative; align-self: flex-start; margin: var(--s-2) 0 2px .5rem; }
.speaker-label {
  list-style: none;
  cursor: pointer;
  display: inline-block;
  font-size: .78rem;
  font-weight: 600;
  color: var(--primary);
  padding: 1px var(--s-2);
  border-radius: 999px;
  background: var(--primary-soft);
}
.speaker-label::-webkit-details-marker { display: none; }
.speaker-label-static { cursor: default; margin-right: var(--s-1); }
/* A continuing block: the chip shrinks to a faint handle that still opens the
   menu, so any block can be moved to another person. */
.speaker-continued { margin-top: 0; }
.speaker-continued > .speaker-label {
  font-size: 0;
  width: 22px;
  height: 6px;
  padding: 0;
  opacity: 0;
}
.review-block:hover .speaker-continued > .speaker-label,
.speaker-continued[open] > .speaker-label,
.speaker-continued > .speaker-label:focus-visible { opacity: .6; }
.speaker-menu-items {
  position: absolute;
  z-index: 25;
  top: calc(100% + 4px);
  left: 0;
  min-width: 280px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--s-3);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
/* The menu's two parts: "Wer ist Person 1?" (name for every block of the
   person, proposals grouped by source) and, behind a rule, "Nur dieser
   Abschnitt" (move one block) with small choice chips, so the two lists of
   names never read as one. */
.speaker-menu-head { display: flex; flex-direction: column; gap: 2px; }
.speaker-menu-title { margin: 0; font-size: .95rem; font-weight: 600; color: var(--navy); }
.speaker-menu-sub { margin: 0; font-size: .78rem; color: var(--muted); }
.speaker-proposal-group { display: flex; flex-direction: column; gap: 2px; }
.speaker-group-heading {
  margin: var(--s-1) 0 0; font-size: .7rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.speaker-rename { display: flex; align-items: center; gap: var(--s-1); }
.speaker-rename-input {
  flex: 1; min-width: 0;
  padding: var(--s-1) var(--s-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: .9rem;
  color: var(--navy);
}
.speaker-block-section {
  display: flex; flex-direction: column; gap: 2px;
  margin-top: var(--s-1); padding-top: var(--s-2); border-top: 1px solid var(--border);
}
.speaker-moves { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-1); }
.speaker-move {
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: none;
  color: var(--navy);
  padding: 4px 12px;
  font-size: .85rem;
  font-weight: 500;
}
.speaker-move:hover { background: var(--surface-sunken); transform: none; box-shadow: none; }
.speaker-text { margin: 0 0 var(--s-2) .5rem; line-height: 1.6; }
/* Name proposals: a full-width line per name, the fact or the words heard
   beneath it. */
.speaker-proposals { display: flex; flex-direction: column; gap: 2px; }
.speaker-proposal-row { display: flex; flex-direction: column; align-items: stretch; }
.speaker-proposal {
  width: 100%; min-height: 40px; padding: 6px 8px; border: 0; border-radius: var(--radius-sm);
  background: none; box-shadow: none; color: var(--navy); font-size: .9rem; font-weight: 600;
  text-align: left;
}
.speaker-proposal:hover, .speaker-proposal:focus-visible {
  background: var(--surface-sunken); transform: none; box-shadow: none;
}
.speaker-proposal-quote, .speaker-proposal-note {
  padding: 0 8px 4px; font-size: .78rem; color: var(--muted); text-align: left;
}
.speaker-proposal-quote {
  border: 0; border-radius: 0; background: none; box-shadow: none; min-height: 0;
  font-weight: 400; line-height: 1.35; cursor: pointer;
  text-decoration: underline dotted; text-underline-offset: 2px;
}
.speaker-proposal-quote:hover { transform: none; box-shadow: none; }
.speaker-link-note {
  margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1);
  font-size: .8rem; color: var(--muted);
}
.review-block.speaker-flash { background: var(--surface-sunken); transition: background .3s; }

/* Layout fix after the speaker labels (colleague feedback, 2026-10-24): the
   timecode is a <button>, so it inherited the global button shadow and pill
   (a white chip beside every block). The label sits on the same line as the
   timecode; a continuing block's handle floats over the text instead of
   taking a row, so its text lines up with its timecode and the gaps between
   blocks stay even. */
.review-seek,
.review-seek:hover {
  box-shadow: none;
  transform: none;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--primary);
}
.review-seek { padding-top: .3rem; line-height: 1.6; }
.review-block-body { position: relative; }
.speaker-menu { margin: .3rem 0 0 .5rem; }
.speaker-continued { position: absolute; top: 0; left: 0; margin: 0; }
.speaker-continued > .speaker-label { display: block; }

/* Phone: a narrower timecode column, the menu kept on screen (it was 280px
   wide from the label's left edge and ran off the right side), and the
   recording header allowed to wrap so Bearbeiten and ⋯ stay inside. */
@media (max-width: 520px) {
  .review-block { gap: var(--s-2); }
  .review-seek { flex-basis: 2.4rem; font-size: .75rem; }
  .speaker-menu-items {
    position: fixed;
    left: var(--s-4);
    right: var(--s-4);
    top: auto;
    min-width: 0;
    max-height: 70vh;
    overflow-y: auto;
  }
  .card-head { flex-wrap: wrap; gap: var(--s-2); }
  .card-head-title { min-width: 0; flex-wrap: wrap; }
}

/* ---- Protokoll bearbeiten ------------------------------------------------- */
.protocol-edit-section {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--s-3) var(--s-4);
  margin-bottom: var(--s-3);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.protocol-edit .field { display: flex; flex-direction: column; gap: var(--s-1); }
.protocol-edit input[type="text"],
.protocol-edit textarea {
  width: 100%;
  box-sizing: border-box;
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--navy);
  font-size: .95rem;
  line-height: 1.6;
}
.protocol-edit textarea { resize: vertical; overflow: hidden; min-height: 3.4em; }
.protocol-edit-remove { align-self: flex-end; font-size: 0.8rem; }
.protocol-edit-steps { margin-top: var(--s-4); }
.protocol-original-point { margin: 0 0 var(--s-1); }
.protocol-original del { background: #fde8e8; color: var(--error); }
.protocol-original ins { background: #ffe58f; text-decoration: none; }
.protocol-stale {
  margin: var(--s-3) 0 0;
  padding: var(--s-2) var(--s-3);
  border-left: 3px solid var(--warning);
  background: color-mix(in srgb, var(--warning) 10%, transparent);
  color: var(--navy);
  font-size: 0.85rem;
}
#protocol-history .meta { margin-top: var(--s-3); }

/* The review screen's two parts, Zuordnung and Transkript, each foldable.
   The summary is the section label; the browser's own triangle is replaced by
   a chevron that turns, so the control reads the same in every browser. */
.review-section { margin-top: var(--s-3); }

.review-section > summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  list-style: none;
  cursor: pointer;
  padding: var(--s-1) 0;
}

.review-section > summary::-webkit-details-marker { display: none; }

.review-section > summary::before {
  content: "";
  width: .5rem;
  height: .5rem;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform .15s ease;
  flex: 0 0 auto;
}

.review-section[open] > summary::before { transform: rotate(45deg); }

.review-section > summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* The folded Zuordnung still says what the recording would be filed under. */
.review-section-summary {
  color: var(--muted);
  font-size: .85rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The propose button closes the transcript section, where reading ends. */
.review-propose {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: var(--s-2) 0 var(--s-1);
}

.propose-status { color: var(--muted); font-size: .85rem; }

/* Sized as section labels, not second page titles: the h1 above already
   names the job. */
.review-section-title,
.review-transcript-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--navy);
}

@media (prefers-reduced-motion: reduce) {
  .review-section > summary::before { transition: none; }
}

.review-blocks { margin-bottom: var(--s-4); }

.review-block {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-2) 0;
}

/* Fixed width so the text column aligns whether or not a timecode is shown. */
.review-seek {
  flex: 0 0 3.2rem;
  padding: .15rem 0;
  border: 0;
  background: none;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  font-size: .82rem;
  text-align: right;
  cursor: pointer;
}

.review-seek:hover { text-decoration: underline; }
.review-seek-empty { cursor: default; }

.review-text {
  flex: 1 1 auto;
  box-sizing: border-box;
  padding: .3rem .5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  font-family: inherit;
  font-size: .95rem;
  line-height: 1.6;
  resize: none;
  overflow: hidden;
}

.review-text:hover { border-color: var(--border); }

/* Marker-pen yellow, as the Zuordnen dialog highlighted evidence. A click on
   a snippet selects the words behind it, so this is what that looks like. */
.review-text::selection { background: #ffe58f; color: inherit; }

.review-text:focus {
  border-color: var(--primary);
  background: var(--bg);
  outline: none;
}

/* The picker shown on the review page for a capture that arrived with no
   project (Telegram, Nextcloud). */
.review-assign {
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--border);
}

/* Says the fields below were filled in from the recording, so the reviewer
   reads them as a proposal to check rather than as their own earlier typing. */
.review-prefill-note {
  margin: 0 0 var(--s-2);
  color: var(--muted);
  font-size: 0.85rem;
}

/* Where a marker's words were found. A brief tint, not a permanent mark: it
   answers "where" and then gets out of the way. The phrase itself cannot be
   highlighted because the segment is a textarea. */
.review-block-found {
  border-radius: var(--radius-sm);
  background: var(--banner-bg);
  transition: background 0.4s ease;
}
@media (prefers-reduced-motion: reduce) {
  .review-block-found { transition: none; }
}

/* Shown on the Ueberblick tab when recordings are missing from the extraction
   because nobody has signed them off yet. Same amber as the review banner. */
.unconfirmed-hint {
  margin: 0 0 var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-sm);
  background: #fdf0d5;
  color: #8a6400;
  font-size: .9rem;
}

/* The unconfirmed dot on the recording detail page. Amber, not the green
   state-ok: a transcript nobody has signed off is not a success state. */
.state-review { background: #e0a200; }

/* The review banner reused inside a card (recording detail), where it sits
   under the meta block rather than at the top of a page. */
.review-banner-inline { margin: var(--s-4) 0 0; }


/* ---- Settings (vocabulary, prompts) ------------------------------------- */

.settings-page .card { max-width: 60rem; margin-inline: auto; }

/* Add a term by hand: one row on a wide screen, stacked on a narrow one. */
.vocab-add {
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin: var(--s-4) 0;
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--border);
}

.vocab-add .field { flex: 1 1 14rem; margin: 0; }

.vocab-table { width: 100%; border-collapse: collapse; }

.vocab-table th {
  padding: var(--s-2) var(--s-2) var(--s-2) 0;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: .8rem;
  font-weight: 600;
  text-align: left;
}

.vocab-table td {
  padding: var(--s-2) var(--s-2) var(--s-2) 0;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.vocab-input {
  width: 100%;
  box-sizing: border-box;
  padding: .3rem .5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  font-family: inherit;
  font-size: .9rem;
}

.vocab-input:hover { border-color: var(--border); }
.vocab-input:focus { border-color: var(--primary); background: var(--bg); outline: none; }

.vocab-source { color: var(--muted); font-size: .82rem; white-space: nowrap; }

.vocab-actions { white-space: nowrap; text-align: right; }

.vocab-rejected { margin-top: var(--s-5); }
.vocab-rejected > summary { cursor: pointer; color: var(--muted); font-size: .9rem; }

.vocab-rejected-list { list-style: none; margin: var(--s-3) 0 0; padding: 0; }

.vocab-rejected-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border);
}

/* ---- SharePoint settings and "Ordner zuordnen" ---------------------------- */
.sp-facts { display: grid; grid-template-columns: max-content 1fr; gap: var(--s-2) var(--s-4); margin: var(--s-4) 0; }
.sp-facts dt { color: var(--muted); font-size: .85rem; }
.sp-facts dd { margin: 0; overflow-wrap: anywhere; }
.sp-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.sp-consent { margin-top: var(--s-4); padding-top: var(--s-4); border-top: 1px solid var(--border); }
.sp-table select { width: 100%; max-width: 28rem; }
.sp-table td:first-child { width: 6rem; }
.sp-new-project { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
.sp-new-project input[type=text] { min-width: 14rem; }
@media (max-width: 40rem) {
  .sp-facts { grid-template-columns: 1fr; }
}

.prompt-block { margin-top: var(--s-5); }
.prompt-name { margin: 0; font-size: 1rem; }
.prompt-what { margin: var(--s-1) 0 var(--s-2); }

.prompt-body {
  margin: 0;
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-size: .82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-x: auto;
}

.prompt-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
}

.prompt-input {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: var(--s-2);
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .82rem;
  line-height: 1.5;
  resize: vertical;
}

.prompt-input:focus { border-color: var(--primary); outline: none; }

.prompt-locked-label {
  margin: 0 0 var(--s-1);
  color: var(--muted);
  font-size: .78rem;
}

/* The JSON contract. Visually recessed against the editable box above, so it
   is obvious at a glance which half you can change. */
.prompt-locked { opacity: .75; }

.prompt-preview {
  margin: var(--s-4) 0;
  padding-top: var(--s-3);
  border-top: 1px solid var(--border);
}

.prompt-preview-bar {
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.prompt-preview-bar .field { flex: 1 1 16rem; margin: 0; }

/* Side by side on a wide screen, stacked on a narrow one. */
.prompt-preview-panes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--s-3);
  margin-top: var(--s-3);
}

.prompt-preview-note {
  margin: var(--s-3) 0 var(--s-1);
  color: var(--muted);
  font-size: .82rem;
}

/* The changed prompt lines. Green for added, red for removed, so the cause of
   the difference below is readable at a glance. */
.prompt-preview-diff {
  margin: 0;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-size: .78rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-x: auto;
}

.prompt-no-target { margin: var(--s-3) 0 0; }

.prompt-howto {
  margin: var(--s-4) 0 var(--s-5);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  font-size: .88rem;
}

.prompt-howto p { margin: 0 0 var(--s-2); }
.prompt-howto ol { margin: 0; padding-left: 1.2rem; }
.prompt-howto li { margin-bottom: var(--s-1); }
.prompt-howto-note { margin: var(--s-2) 0 0; color: var(--muted); font-size: .82rem; }

.prompt-contract { margin: var(--s-2) 0 var(--s-3); }
.prompt-contract > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: .82rem;
}

/* "Wer spricht?" on a recording without speakers (speakers-offer.js). */
.speakers-offer {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  margin: 0 0 var(--s-2); font-size: .9rem;
}
.speakers-offer-question { font-weight: 600; }
.speakers-offer-status { color: var(--muted); }
.speakers-offer-pulse { color: var(--navy); }
@media (prefers-reduced-motion: reduce) {
  .speakers-offer-pulse { animation: none; }
}

/* ---- Project files: the "Dateien" card (project-files.js) ---------------- */
.pf-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); margin: 0 0 var(--s-3); font-size: .9rem; }
.pf-crumb { text-decoration: none; overflow-wrap: anywhere; }
.pf-crumb-sep { color: var(--muted); }
.pf-crumb-current { font-weight: 600; color: var(--navy); overflow-wrap: anywhere; }
.pf-actions { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
/* <label>/<a> styled as .btn-subtle: the `button` rule's radius and layout. */
.pf-actions label.btn-subtle,
.pf-actions a.btn-subtle {
  display: inline-flex; align-items: center; gap: var(--s-2); min-height: 44px;
  border-radius: 999px; font-weight: 600; cursor: pointer; text-decoration: none;
}
.pf-actions .icon { width: 1.1rem; height: 1.1rem; }
.pf-hint { margin: var(--s-2) 0 var(--s-3); }
.pf-list { list-style: none; margin: 0; padding: 0; }
.pf-row { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) 0; border-bottom: 1px solid var(--border); }
.pf-list:last-child .pf-row:last-child { border-bottom: 0; }
.pf-main { display: flex; align-items: center; gap: var(--s-3); flex: 1 1 auto; min-width: 0; min-height: 48px; color: inherit; text-decoration: none; }
.pf-icon { flex: 0 0 48px; width: 48px; height: 48px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); background: var(--surface-sunken); color: var(--muted); }
.pf-icon .icon { width: 1.4rem; height: 1.4rem; }
.pf-icon-folder { color: var(--primary); }
.pf-thumb { flex: 0 0 48px; width: 48px; height: 48px; object-fit: cover; border-radius: var(--radius-sm); background: var(--surface-sunken); }
.pf-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pf-name { font-weight: 600; color: var(--navy); overflow-wrap: anywhere; }
.pf-meta { color: var(--muted); font-size: .82rem; }
.pf-meta-error { color: var(--error); }
.pf-progress { width: 100%; max-width: 14rem; height: 6px; accent-color: var(--primary); }
.pf-upload-actions { display: flex; flex-wrap: wrap; gap: var(--s-2); flex: 0 0 auto; }
.pf-row .row-menu-items form { margin: 0; }
.pf-row a.row-menu-item { text-decoration: none; }

/* Full-screen photo viewer, with previous/next within the open folder. */
.pf-viewer[hidden] { display: none; }
.pf-viewer { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; background: rgba(10, 12, 20, .94); }
.pf-viewer img { max-width: 100%; max-height: calc(100% - 5rem); object-fit: contain; }
.pf-viewer button { position: absolute; width: 44px; height: 44px; padding: 0; border-radius: 50%; background: rgba(255, 255, 255, .14); color: #fff; font-size: 1.6rem; line-height: 1; box-shadow: none; }
.pf-viewer button:hover { background: rgba(255, 255, 255, .26); transform: none; box-shadow: none; }
.pf-viewer button[hidden] { display: none; }
.pf-viewer-close { top: var(--s-3); right: var(--s-3); }
.pf-viewer-prev { left: var(--s-3); top: 50%; margin-top: -22px; }
.pf-viewer-next { right: var(--s-3); top: 50%; margin-top: -22px; }
.pf-viewer-name { position: absolute; left: var(--s-4); right: var(--s-4); bottom: var(--s-3); margin: 0; color: #fff; text-align: center; font-size: .9rem; overflow-wrap: anywhere; }
