/*
 * M2.1 — Themis shared UI primitives (frozen COMPONENT_LIBRARY §1/§6/§17/§18).
 *
 * The reusable component layer for the frozen Dashboard v2.0 design. Built ENTIRELY on the M1
 * semantic token foundation (themis-tokens.css) — never hard-codes hex on surfaces/text/borders,
 * so light / dark / system all follow from `data-theme`. Every selector is namespaced `.thc-*`
 * (Themis Component) so this file is INERT for existing screens: it can only style markup that
 * opts in by using a `.thc-*` class. Presentation only — no layout, workflow, permission, route,
 * schema, or status-code change (storage codes are mapped to labels/icons in the Blade layer,
 * never renumbered).
 *
 * Load AFTER themis-tokens.css.
 */

/* ─────────────────────────────  Buttons (§1)  ───────────────────────────── */
.thc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 32px;                 /* compact (default) */
  padding: 0 14px;
  border-radius: 5px;           /* frozen radius/sm — buttons/inputs/selects (DESIGN_SYSTEM §8) */
  border: 1px solid transparent;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: filter .12s ease, background-color .12s ease, border-color .12s ease;
}
.thc-btn--comfortable { height: 40px; padding: 0 18px; font-size: 14px; }
.thc-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* Primary — navy fill + white (cyan retired for fills/text per TH-UI-020). */
.thc-btn--primary { background: var(--btn-dark-bg); color: var(--text-on-accent); }
.thc-btn--primary:hover { filter: brightness(1.12); }
.thc-btn--primary:active { filter: brightness(.92); }

/* Secondary — surface + border. */
.thc-btn--secondary { background: var(--surface); color: var(--text-body); border-color: var(--border-strong); }
.thc-btn--secondary:hover { background: var(--hover); }
.thc-btn--secondary:active { filter: brightness(.97); }

/* Tertiary / ghost — transparent, low emphasis. */
.thc-btn--tertiary { background: transparent; color: var(--link); }
.thc-btn--tertiary:hover { background: var(--hover); }

/* Destructive — danger OUTLINE (token-safe in both themes; never conflated with restore). */
.thc-btn--destructive { background: transparent; color: var(--red-fg); border-color: var(--red-border); }
.thc-btn--destructive:hover { background: var(--red-bg); }

/* Restorative — success outline, visually/verbally opposite to destructive. */
.thc-btn--restorative { background: transparent; color: var(--green-fg); border-color: var(--green-border); }
.thc-btn--restorative:hover { background: var(--green-bg); }

/* Icon button — 44px accessible target (WCAG 2.2 §2.5.8). */
.thc-btn--icon {
  height: 44px; width: 44px; padding: 0; border-radius: 5px;   /* frozen radius/sm */
  background: transparent; color: var(--text-secondary);
}
.thc-btn--icon:hover { background: var(--hover); }

/* Row actions promoted OUT of the „…" menu sit inline beside it. Layout only — the 44x44 hit area
   and every visual state still come from .thc-btn--icon, so a promoted action and a menu trigger
   remain the same control at the same size. */
.thc-row-inline-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}

/* TH-UI-024 survives promotion. Restore is RESTORATIVE, never danger and never neutral — that held
   while it was a menu item (.thc-menu__item--restorative) and must still hold now that it is an
   inline icon. Same green tokens, applied to the glyph rather than to a bordered button, so the
   promoted control keeps the icon geometry and the restorative meaning at once. */
.thc-btn--icon.thc-btn--restorative {
  color: var(--green-fg);
  border-color: transparent;
}
.thc-btn--icon.thc-btn--restorative:hover { background: var(--green-bg); }

/* Disabled + pending (shared) — no duplicate submit; announced via aria-disabled / aria-busy. */
.thc-btn[disabled], .thc-btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; filter: none; }
.thc-btn[aria-busy="true"] { cursor: progress; }

/* ─────────────────────────────  Chips / badges (§6)  ─────────────────────── */
.thc-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px;
  border-radius: 3px;             /* frozen radius/xs — badges/chips/tags (DESIGN_SYSTEM §8) */
  border: 1px solid transparent;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}
.thc-chip .thc-ico { flex: 0 0 auto; }
.thc-chip__sym { font-weight: 800; letter-spacing: -.5px; }   /* ! / !! priority symbol */

/* Semantic tint sets (text + fill + border together — icon+text+color, never color alone). */
.thc-chip--success { color: var(--green-fg);  background: var(--green-bg);      border-color: var(--green-border); }
.thc-chip--warning { color: var(--yellow-fg); background: var(--yellow-bg);     border-color: var(--yellow-border); }
.thc-chip--danger  { color: var(--red-fg);    background: var(--red-bg);        border-color: var(--red-border); }
.thc-chip--danger-strong { color: var(--red-fg); background: var(--red-bg-strong); border-color: var(--red-border); }
.thc-chip--info    { color: var(--brand-ink); background: var(--info-bg);       border-color: var(--info-border); }
.thc-chip--neutral { color: var(--text-body);  background: var(--chip-bg);      border-color: var(--border); }
.thc-chip--muted   { color: var(--text-muted); background: var(--chip-bg);      border-color: var(--border); }

/* ─────────────────────────────  Loading (§17)  ───────────────────────────── */
.thc-skeleton {
  display: block;
  background: linear-gradient(90deg, var(--surface-sub) 25%, var(--hover) 37%, var(--surface-sub) 63%);
  background-size: 400% 100%;
  border-radius: 5px;             /* frozen radius/sm (was a non-frozen 6px) */
  animation: thc-shimmer 1.2s ease-in-out infinite;
}
.thc-skeleton--line { height: 12px; margin: 8px 0; }
.thc-skeleton--line.is-short { width: 45%; }
.thc-skeleton--block { height: 96px; }
@keyframes thc-shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }

.thc-spinner {
  display: inline-block;
  width: 1em; height: 1em;
  vertical-align: -.125em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: thc-spin .7s linear infinite;
}
.thc-spinner--lg { width: 28px; height: 28px; border-width: 3px; }
@keyframes thc-spin { to { transform: rotate(360deg); } }

/* Reduced motion — STOP animation (frozen motion rule: instant/opacity-only, not merely slowed).
   The skeleton becomes a flat block; the spinner becomes a static, complete, dimmed ring that still
   reads as "busy" (its role="status" text carries the meaning for assistive tech). WCAG 2.3.3. */
@media (prefers-reduced-motion: reduce) {
  .thc-skeleton { animation: none; background: var(--surface-sub); }
  .thc-spinner { animation: none; border-right-color: currentColor; opacity: .55; }
}

/* ────────────────────  State blocks — empty / error / etc. (§18)  ───────── */
.thc-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  max-width: 460px;
  margin: 0 auto;
  padding: 40px 24px;
  color: var(--text-body);
}
.thc-state__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: var(--surface-sub);
  border: 1px solid var(--border);
  color: var(--text-secondary);
}
.thc-state__icon .thc-ico { width: 28px; height: 28px; }
.thc-state__title { font-size: 17px; font-weight: 700; color: var(--text-primary); margin: 4px 0 0; }
.thc-state__msg { font-size: 14px; color: var(--text-muted); margin: 0; }
.thc-state__action { margin-top: 8px; }

/* State-block accent by intent (icon ring only — copy still carries the meaning). */
.thc-state--error .thc-state__icon,
.thc-state--offline .thc-state__icon { color: var(--red-fg); border-color: var(--red-border); background: var(--red-bg); }
.thc-state--denied .thc-state__icon,
.thc-state--notfound .thc-state__icon { color: var(--yellow-fg); border-color: var(--yellow-border); background: var(--yellow-bg); }
.thc-state--success .thc-state__icon { color: var(--green-fg); border-color: var(--green-border); background: var(--green-bg); }

/* ─────────────  Standalone error-page shell (403 / 404 / 500 surfaces)  ──── */
/* The error views render their own document, so the shell owns its reset: no browser default margin
   or serif, the frozen system-font stack (DESIGN_SYSTEM §3.1), and border-box everywhere. */
.thc-doc {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  color: var(--text-body);
}
.thc-doc, .thc-doc *, .thc-doc *::before, .thc-doc *::after { box-sizing: border-box; }

.thc-errorpage {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--page-bg);
}
.thc-errorpage__card {
  width: 100%;
  max-width: 520px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;             /* frozen radius/md — cards/dialogs (DESIGN_SYSTEM §8) */
  padding: 12px 20px 28px;
}
/* Frozen STATE_LIBRARY: a LARGE code numeral leading the error shell, in --text-muted
   (AA ≥4.5:1; never the decorative --text-faint). Title/body follow it in the state block. */
.thc-errorpage__code {
  text-align: center;
  font-size: 44px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--text-muted);
  margin: 16px 0 0;
}

/* Inline icon sizing default. */
.thc-ico { width: 16px; height: 16px; display: inline-block; vertical-align: -.15em; }

/* Visually-hidden helper for accessible names. */
.thc-sr-only {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   M2.2 — DASHBOARD SHELL (frozen COMPONENT_LIBRARY §7a/§7b/§7c, SCREEN_SPECIFICATIONS B1)
   Sidebar, top bar and account menu. Token-driven; still namespaced so it cannot touch feature
   screens. The sidebar is a dark shell in BOTH themes, so it inherits the sidebar --focus-ring
   override from themis-tokens.css (PFC-001).
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── Sidebar ───────────────────────────────────────────────────────────────── */
.thc-shell-sidebar { display: flex; flex-direction: column; }

.thc-shell-brand {
  display: flex; align-items: center; gap: 10px;
  padding: 16px 18px;
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}
.thc-shell-brand__mark { border-radius: 5px; box-shadow: 0 0 0 1px rgba(255, 255, 255, .14); }
.thc-shell-brand__word {
  font-size: 13px; font-weight: 700; letter-spacing: .10em;
  color: var(--text-on-accent);
}

.thc-shell-nav__list { list-style: none; margin: 0; padding: 10px 8px; }
.thc-shell-nav__item { margin: 0 0 2px; }
.thc-shell-nav__link {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 11px;
  border-radius: 5px;                 /* frozen radius/sm */
  font-size: 13.5px; font-weight: 500;
  color: var(--text-faint);
  text-decoration: none;
}
.thc-shell-nav__link:hover { background: rgba(255, 255, 255, .07); color: var(--text-on-accent); }
.thc-shell-nav__link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-shell-nav__link.is-active {
  background: rgba(43, 168, 219, .16);
  color: var(--text-on-accent);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--brand);
}
.thc-shell-nav__icon { width: 20px; height: 20px; flex: 0 0 auto; }

/* ── Sidebar footer + appearance quick-toggle ──────────────────────────────── */
.thc-shell-sidebar__footer {
  margin-top: auto;
  padding: 12px 10px;
  border-top: 1px solid rgba(255, 255, 255, .10);
}
.thc-shell-identity { margin: 0 0 8px; padding: 0 4px; }
.thc-shell-identity__name { font-size: 13px; font-weight: 600; color: var(--text-on-accent); }

.thc-shell-quicktoggle {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  min-height: 44px;                   /* WCAG 2.2 target size */
  padding: 0 11px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 5px;
  background: transparent;
  color: var(--text-faint);
  font: inherit; font-size: 13px;
  cursor: pointer;
}
.thc-shell-quicktoggle:hover { background: rgba(255, 255, 255, .07); color: var(--text-on-accent); }
.thc-shell-quicktoggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ── Legacy-cascade neutralisation ─────────────────────────────────────────────
   The vendored Paper Dashboard + custom.css paint a full-size `.sidebar::after`
   (#F6F6F6, z-index 3) and fix the sidebar at 260px, translating it off-canvas below
   992px. The frozen shell owns this geometry, so those layers and sizes are reset here
   rather than left to fight the token surfaces (contrast) and the toggle (interaction). */
.thc-shell-sidebar::before,
.thc-shell-sidebar::after,
.thc-shell-sidebar .sidebar-wrapper::before,
.thc-shell-sidebar .sidebar-wrapper::after { content: none !important; display: none !important; }

.thc-shell-sidebar {
  position: relative;
  z-index: 1040;
  width: 248px;                        /* frozen >=1280 expanded width */
  height: 100vh;
  transform: none;                     /* defeat the legacy off-canvas translate */
  transition: width .16s ease, transform .16s ease;
}
/* At least as specific as the vendored `.sidebar .sidebar-wrapper{width:260px;height:calc(100vh - 75px);
   padding-bottom:100px}` so the inner wrapper follows the shell width instead of the legacy 260px. */
.thc-shell-sidebar .sidebar-wrapper,
.sidebar.thc-shell-sidebar .sidebar-wrapper {
  position: relative;
  z-index: 1;
  width: 100% !important;
  height: auto !important;
  min-height: 0;
  padding-bottom: 0 !important;
  overflow-x: hidden;
  overflow-y: auto;
  flex: 1 1 auto;
}

/* The app frame: top bar beside the sidebar, content below it (frozen DESIGN §5). DOM order is
   top bar -> sidebar -> main so the keyboard order is skip -> top bar -> sidebar -> main. */
/* Frozen UI family (DESIGN_SYSTEM §3.1): the system-native stack — zero-download, and it matches the
   iOS app's San Francisco voice on Apple hardware. Paper Dashboard sets Montserrat on `body`, which
   every shell element would otherwise inherit; the frozen package explicitly forbids a downloaded
   display face as the UI family. Scoped to the shell so unmigrated legacy screens are untouched. */
/* Applied to the SHELL CHROME ONLY — never to `.thc-shell-frame` itself, which encloses the yielded
   feature screen: `font-family` inherits, so styling the frame would restyle every unmigrated screen
   (and only partly, since Paper Dashboard sets Montserrat explicitly on form controls). Legacy screens
   keep their existing font until their own approved migration. */
.thc-shell-header,
.thc-shell-sidebar,
.thc-skip-link {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

.thc-shell-frame {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

/* ── Nav section headers (frozen `overline` token: 11 / 14, 600, +0.04em, uppercase) ─────────── */
.thc-shell-nav__group + .thc-shell-nav__group { margin-top: 18px; }
.thc-shell-nav__grouplabel {
  margin: 0 0 6px;
  padding: 0 12px;
  font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-faint);
}
.thc-shell-frame > .thc-shell-sidebar { grid-column: 1; grid-row: 1 / -1; }
.thc-shell-frame > .thc-shell-header { grid-column: 2; grid-row: 1; }
.thc-shell-frame > .main-panel {
  grid-column: 2; grid-row: 2;
  width: auto !important;              /* the legacy 260px-based width is replaced by the grid */
  float: none !important;
  /* `!important` is required to beat Paper Dashboard's `.ps-container{overflow:hidden !important}`,
     which still applies to any screen that re-adds that class. `min-height:0` lets a grid item
     shrink below its content so the panel — not the page — scrolls. */
  min-height: 0;
  overflow: auto !important;
}

/* Skip link — first focusable element (frozen keyboard model). */
.thc-skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 2000;
  padding: 10px 14px; border-radius: 5px;
  background: var(--surface); color: var(--link);
  font-size: 13px; font-weight: 600; text-decoration: none;
}
.thc-skip-link:focus { top: 8px; outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* Collapsed icon rail: the visible label hides but the ACCESSIBLE NAME remains (sr-only text),
   and each item keeps a tooltip. Destinations are never removed (TH-UI-023). */
.thc-shell-sidebar.is-collapsed { width: 64px; }
.thc-shell-sidebar.is-collapsed .thc-shell-nav__label,
.thc-shell-sidebar.is-collapsed .thc-shell-nav__grouplabel,
.thc-shell-sidebar.is-collapsed .thc-shell-brand__word,
.thc-shell-sidebar.is-collapsed .thc-shell-identity,
.thc-shell-sidebar.is-collapsed .thc-shell-quicktoggle__label {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.thc-shell-sidebar.is-collapsed .thc-shell-nav__link,
.thc-shell-sidebar.is-collapsed .thc-shell-quicktoggle { justify-content: center; }

/* Off-canvas drawer scrim (<=1023). */
.thc-shell-scrim {
  position: fixed; inset: 0; z-index: 1030;
  background: var(--overlay);
  border: 0; padding: 0; margin: 0;
}
.thc-shell-scrim[hidden] { display: none; }

/* ── Frozen breakpoints (RESPONSIVE_SPECIFICATION §2) ──────────────────────── */

/* 1024–1279: collapsed icon rail by DEFAULT, expandable as an overlay. */
@media (max-width: 1279px) and (min-width: 1024px) {
  /* Pin the sidebar column to the rail width. When `.is-expanded` becomes position:fixed it
     leaves grid sizing, and an `auto` track would collapse to 0 — shifting the header and main
     content 64px left. The overlay must cover the page, not move it. */
  .thc-shell-frame { grid-template-columns: 64px 1fr; }
  .thc-shell-sidebar { width: 64px; }
  .thc-shell-sidebar .thc-shell-nav__label,
  .thc-shell-sidebar .thc-shell-nav__grouplabel,
  .thc-shell-sidebar .thc-shell-brand__word,
  .thc-shell-sidebar .thc-shell-identity,
  .thc-shell-sidebar .thc-shell-quicktoggle__label {
    position: absolute !important; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
  }
  .thc-shell-sidebar .thc-shell-nav__link { justify-content: center; }
  /* Expanded on demand: overlays the content instead of reflowing it. */
  .thc-shell-sidebar.is-expanded { position: fixed; left: 0; top: 0; width: 248px; }
  .thc-shell-sidebar.is-expanded .thc-shell-nav__label,
  .thc-shell-sidebar.is-expanded .thc-shell-nav__grouplabel,
  .thc-shell-sidebar.is-expanded .thc-shell-brand__word,
  .thc-shell-sidebar.is-expanded .thc-shell-identity,
  .thc-shell-sidebar.is-expanded .thc-shell-quicktoggle__label {
    position: static !important; width: auto; height: auto;
    margin: 0; overflow: visible; clip: auto; white-space: normal;
  }
  .thc-shell-sidebar.is-expanded .thc-shell-nav__link { justify-content: flex-start; }
}

/* <=1023: off-canvas drawer opened by the named toggle. */
@media (max-width: 1023px) {
  .thc-shell-frame { grid-template-columns: 1fr; }
  .thc-shell-frame > .thc-shell-header { grid-column: 1; grid-row: 1; }
  .thc-shell-frame > .main-panel { grid-column: 1; grid-row: 2; }
  .thc-shell-sidebar {
    position: fixed; left: 0; top: 0; bottom: 0;
    width: 248px;
    transform: translateX(-100%);
    /* A CLOSED drawer must not be reachable by keyboard or AT — translating it offscreen alone
       would leave its links tabbable. `visibility:hidden` removes it from the tab order. */
    visibility: hidden;
  }
  .thc-shell-sidebar.is-open { transform: translateX(0); visibility: visible; }
  /* The rail collapse is meaningless in drawer mode — the drawer is always the full panel. */
  .thc-shell-sidebar.is-collapsed { width: 248px; }
  .thc-shell-sidebar.is-collapsed .thc-shell-nav__label,
.thc-shell-sidebar.is-collapsed .thc-shell-nav__grouplabel,
  .thc-shell-sidebar.is-collapsed .thc-shell-brand__word,
  .thc-shell-sidebar.is-collapsed .thc-shell-identity,
  .thc-shell-sidebar.is-collapsed .thc-shell-quicktoggle__label {
    position: static !important; width: auto; height: auto;
    margin: 0; overflow: visible; clip: auto; white-space: normal;
  }
}

/* Content gutter per breakpoint (frozen §2). */
/* Paper Dashboard ships `.main-panel>.content{padding:0 30px 30px;min-height:calc(100vh - 123px);
   margin-top:93px}` — that 93px assumed a legacy fixed navbar this shell no longer has, so overriding
   only `padding` would leave a 93px gap under the 56px top bar and a viewport-overflowing min-height.
   Both legacy values are reset here explicitly, at winning specificity. */
.thc-shell-frame > .main-panel > .content {
  padding: 24px;
  margin-top: 0;
  min-height: 0;
}
@media (max-width: 1023px) { .thc-shell-frame > .main-panel > .content { padding: 16px; } }

@media (prefers-reduced-motion: reduce) {
  .thc-shell-sidebar { transition: none; }
}

/* ── Top bar ───────────────────────────────────────────────────────────────── */
/* Frozen frame: the top bar is a fixed 56px (DESIGN_SYSTEM §5) and never grows with its controls;
   the 44px targets sit inside it rather than expanding it. */
.thc-shell-header {
  display: flex; align-items: center; gap: 14px;
  height: 56px;
  padding: 0 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.thc-shell-header .thc-btn--icon { height: 44px; width: 44px; }

/* DR-007 compression: below the desktop frame the account name drops, leaving the avatar and the
   h1 as the last elements standing — neither ever clips. */
@media (max-width: 1023px) {
  .thc-shell-account__name { display: none; }
}
.thc-shell-header__left { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1 1 auto; }
.thc-shell-header__right { display: flex; align-items: center; gap: 10px; margin-left: auto; }

/* DR-007: the h1 truncates before it wraps or pushes the right cluster off-frame. */
/* Frozen `h1` type token (DESIGN_SYSTEM §3.2): 22 / 28, weight 600. */
.thc-shell-title {
  margin: 0;
  min-width: 0; flex: 1 1 auto;
  font-size: 22px; font-weight: 600; line-height: 28px;
  color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Account menu ──────────────────────────────────────────────────────────── */
.thc-shell-account { position: relative; }
.thc-shell-account__button {
  display: flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 10px;
  border: 1px solid transparent; border-radius: 5px;
  background: transparent; color: var(--text-body);
  font: inherit; font-size: 13px; cursor: pointer;
}
.thc-shell-account__button:hover { background: var(--hover); }
.thc-shell-account__button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-shell-account__avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--btn-dark-bg); color: var(--text-on-accent);
  font-size: 11px; font-weight: 700;
  /* The Benutzerfoto is square (ProfilePhotoService crops it); the circle comes from this
     container, so the image has to be clipped by it rather than rounded on its own. */
  overflow: hidden;
}

/* No new class: the photo is an <img> inside the existing avatar, so `CssContractTest` still
   sees only class names this stylesheet already defines. `cover` keeps a square crop filling the
   circle without distortion, and `block` removes the inline-baseline gap that would otherwise
   push the image a pixel off-centre. */
.thc-shell-account__avatar img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

.thc-shell-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 1000;
  min-width: 248px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;                 /* frozen radius/md */
  padding: 8px;
  box-shadow: 0 10px 28px rgba(16, 41, 58, .16);
}
.thc-shell-menu[hidden] { display: none; }
.thc-shell-menu__identity { margin: 0 0 6px; padding: 6px 10px; display: flex; flex-direction: column; }
.thc-shell-menu__name { font-size: 13px; font-weight: 700; color: var(--text-primary); }
.thc-shell-menu__role { font-size: 12px; color: var(--text-muted); }
.thc-shell-menu__group { border-top: 1px solid var(--divider); padding-top: 6px; margin-top: 4px; }
.thc-shell-menu__grouplabel {
  margin: 2px 0 4px; padding: 0 10px;
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  color: var(--text-muted);
}
.thc-shell-menu__item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 44px; padding: 0 10px;   /* WCAG 2.2 target size */
  border: 0; border-radius: 5px;
  background: transparent; color: var(--text-body);
  font: inherit; font-size: 13.5px; text-align: left; cursor: pointer;
}
.thc-shell-menu__item:hover { background: var(--hover); }
/* Frozen contract (INTERACTION_GUIDELINES): 2px ring + 2px OFFSET, never inset. The menu is
   padded so the offset ring is not clipped. */
.thc-shell-menu__item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-shell-menu__item[aria-checked="true"] { background: var(--info-bg); color: var(--brand-ink); font-weight: 600; }
/* Frozen: the active option carries a FILLED indicator, not tint alone. */
.thc-shell-menu__item[aria-checked="true"]::after {
  content: ""; margin-left: auto;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--brand-ink);
}
.thc-shell-menu__itemtext { display: flex; flex-direction: column; }
.thc-shell-menu__hint { font-size: 11.5px; font-weight: 400; color: var(--text-muted); }
.thc-shell-menu__form { margin: 6px 0 0; border-top: 1px solid var(--divider); padding-top: 6px; }

/* ═══════════  M2.3 — Auth surfaces (frozen SCREEN_SPECIFICATIONS A1/A3)  ═══════════ */
/* The login renders its own document, so these build on `.thc-doc` exactly as the error shell does.
   Everything is namespaced `.thc-auth*` / `.thc-field*` and therefore inert for other screens. */

.thc-auth {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  background: var(--login-bg);
  /* This container deliberately does NOT clip. Clipping here would affect BOTH axes, so a tall
     panel (reauth banner + validation errors at 375, or a zoomed/short viewport) could not be
     scrolled to and its submit button became unreachable. The horizontal overflow is fixed at its
     SOURCE instead: the decorative layers sit in their own clipped, out-of-flow wrapper below, so
     the document keeps scrolling vertically as normal. */
}
/* Decorative calm wash + architectural line art (frozen A1). Never carry meaning.
   The wrapper is out of flow and clips its own contents, so the 900px glow can never widen the
   document — without trapping the page's vertical scrolling. */
.thc-auth__decor {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.thc-auth__glow {
  position: absolute;
  top: 50%; left: 50%;
  width: min(900px, 140vw);
  height: min(900px, 140vw);
  transform: translate(-50%, -50%);
  background: var(--login-glow);
  pointer-events: none;
}
.thc-auth__lineart {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}

/* SINGLE CENTRED PANEL — replaces the old fixed 50/50 col-6 split (TH-UI-009).
   RESPONSIVE_SPECIFICATION §3.1: "One calm panel (max-width ~412px) centred". */
.thc-auth__panel {
  position: relative;                /* above the decorative layers */
  z-index: 1;
  width: 100%;
  max-width: 412px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;                /* frozen radius/md — cards (DESIGN_SYSTEM §8) */
  padding: 40px 38px 32px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.thc-auth__mark {
  align-self: center;
  width: 76px; height: 76px;
  border-radius: 5px;                /* frozen radius/sm — same treatment as the sidebar brand mark */
  /* On dark surfaces the navy tile needs a hairline edge to read (DESIGN_SYSTEM §7.2). */
  box-shadow: 0 0 0 1px rgba(255,255,255,0.14);
}

/* Frozen `display` type token — DESIGN_SYSTEM §3.2 names 28/34 weight 600 the login heading. */
.thc-auth__title {
  margin: 16px 0 4px;
  font-size: 28px; line-height: 34px; font-weight: 600;
  text-align: center;
  color: var(--text-primary);
}
.thc-auth__subtitle {
  margin: 0 0 20px;
  font-size: 14px; line-height: 20px;
  text-align: center;
  color: var(--text-muted);
}

/* ── Notices: reauthentication (A3), error summary (A1), genuine success ──── */
.thc-auth__notice,
.thc-auth__error,
.thc-auth__success {
  display: flex;
  gap: 10px;
  padding: 10px 12px;
  margin: 0 0 16px;
  border-radius: 5px;                /* frozen radius/sm */
  border: 1px solid;
  font-size: 13px; line-height: 19px;
  text-align: left;
}
.thc-auth__notice {
  color: var(--brand-ink);
  background: var(--info-bg);
  border-color: var(--info-border);
}
/* Errors use the danger tokens and NEVER the success tokens (IMPLEMENTATION_GUIDE §171). */
.thc-auth__error {
  color: var(--red-fg);
  background: var(--red-bg);
  border-color: var(--red-border);
}
.thc-auth__error:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* A genuine success keeps truthful success semantics. */
.thc-auth__success {
  color: var(--green-fg);
  background: var(--green-bg);
  border-color: var(--green-border);
}
.thc-auth__notice-icon,
.thc-auth__error-icon { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; }
.thc-auth__notice-text,
.thc-auth__error-text { margin: 0; }
.thc-auth__error-body { min-width: 0; }
.thc-auth__error-list { margin: 4px 0 0; padding-left: 18px; }
.thc-auth__error-list a { color: inherit; }
/* Every interactive element carries the frozen indicator explicitly — a native outline is neither
   token-controlled nor guaranteed to meet contrast on the danger surface in both themes. */
.thc-auth__error-list a:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ── Workspace SSO (frozen A1: primary method, brand-white surface) ───────── */
/* RESPONSIVE §3.1: comfortable 46–50px controls on the auth surface. */
.thc-auth__sso {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  height: 48px;
  border-radius: 5px;                /* frozen radius/sm */
  border: 1px solid var(--border-strong);
  /* Google's guidelines keep the button on its own white surface in both themes. */
  background: #FFFFFF;
  color: #1F1F1F;
  font-size: 14px; font-weight: 600;
  text-decoration: none;
}
.thc-auth__sso:hover { filter: brightness(.97); }
.thc-auth__sso:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* The frozen prototype's four-quadrant mark — deliberately NOT a reproduction of the Google logo. */
.thc-auth__sso-mark {
  width: 18px; height: 18px; border-radius: 50%;
  background: conic-gradient(#4285F4 0deg 90deg,#34A853 90deg 180deg,#FBBC05 180deg 270deg,#EA4335 270deg 360deg);
}

/* Disclosure for the transitional password method (frozen prototype „Andere Anmeldemethode"). */
.thc-auth__disclosure {
  align-self: center;
  margin-top: 14px;
  min-height: 44px;                  /* touch target */
  padding: 6px 8px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 13.5px; font-weight: 600;
  color: var(--link);
}
.thc-auth__disclosure:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.thc-auth__divider {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 0;
  /* Visible text, so it needs the AA-capable --text-muted, never the decorative --text-faint
     (~2.5:1) — the same correction PFC-002 applied to the error-shell numeral. */
  color: var(--text-muted);
  font-size: 12px;
}
.thc-auth__divider::before,
.thc-auth__divider::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--border-soft);
}

/* ── Form fields ──────────────────────────────────────────────────────────── */
.thc-auth__form { display: flex; flex-direction: column; gap: 14px; }

.thc-field { display: flex; flex-direction: column; gap: 6px; }
.thc-field__label {
  font-size: 13px; line-height: 18px; font-weight: 500;   /* frozen `label` token */
  color: var(--text-body);
}
.thc-field__control { position: relative; display: flex; }
.thc-field__input {
  height: 48px;                      /* RESPONSIVE §3.1 comfortable control */
  padding: 0 12px;
  border-radius: 5px;                /* frozen radius/sm — inputs */
  border: 1px solid var(--border-strong);
  background: var(--input-field);
  color: var(--text-body);
  font-family: inherit;
  font-size: 14px; line-height: 20px;
  width: 100%;
}
.thc-field__input::placeholder { color: var(--text-faint); }
.thc-field__input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-field__input.is-invalid { border-color: var(--red-border); }
.thc-field__error { margin: 0; font-size: 12px; line-height: 16px; color: var(--red-fg); }

/* Password reveal — created by themis-auth.js, so it never exists without JS. */
.thc-field__control .thc-field__input { padding-right: 92px; }
.thc-field__reveal {
  position: absolute;
  right: 6px; top: 50%; transform: translateY(-50%);
  min-height: 44px;                  /* 44x44 touch minimum; fits inside the 48px field */
  padding: 0 10px;
  background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: 13px; font-weight: 600;
  color: var(--link);
}
.thc-field__reveal:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.thc-auth__submit { width: 100%; height: 48px; margin-top: 4px; }
/* Frozen prototype hover/pressed colours for the login action. The shared button primitive uses a
   brightness filter; on this surface the frozen design names explicit tokens, so they are applied
   here — LOGIN-SCOPED, leaving the shared primitive untouched for every other screen. */
.thc-auth__submit.thc-btn--primary:hover { background: var(--btn-dark-hover); filter: none; }
.thc-auth__submit.thc-btn--primary:active { background: var(--btn-dark-active); filter: none; }

/* Product identity, OUTSIDE the panel (frozen prototype). */
.thc-auth__footer {
  position: relative; z-index: 1;
  margin-top: 26px;
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; line-height: 16px;
  /* The footer sits OUTSIDE the panel, on the page gradient — a lighter surface than the card, so
     --text-muted measured only 3.81:1 there. Visible text needs AA, so it takes --text-body.
     (PFC-002 class: the frozen palette's own --foot-text is likewise decorative-weight.) */
  color: var(--text-body);
}
.thc-auth__footer-name { font-weight: 600; letter-spacing: .04em; }

/* ── Narrow mobile (frozen A1: operable and single-column 1440 → 375) ─────── */
@media (max-width: 420px) {
  .thc-auth { padding: 20px 12px; }
  .thc-auth__panel { padding: 26px 20px 22px; }
  .thc-auth__title { font-size: 24px; line-height: 30px; }
  .thc-auth__mark { width: 64px; height: 64px; }
}

/* ═══════════  M2.4 — Shared table & interaction composites  ═══════════ */
/* Frozen COMPONENT_LIBRARY §3 (tables), §5 (dialogs), §11 (menus), §12 (notifications).
   Namespaced `.thc-table*` / `.thc-menu*` / `.thc-dialog*` / `.thc-lightbox*` / `.thc-toast*` /
   `.thc-banner*`, so this section is inert for any markup that does not opt in. */

/* `hidden` MUST win over these components' own `display` rules. The UA stylesheet's
   `[hidden] { display: none }` loses to any author `display:` declaration, so a dialog, toast,
   banner or lightbox frame would render OPEN on load despite carrying the attribute. Every
   composite below that sets `display` therefore declares its own hidden guard. */
.thc-dialog__backdrop[hidden],
.thc-toast[hidden],
.thc-banner[hidden],
.thc-lightbox__item[hidden],
.thc-menu[hidden],
.thc-table__overflow-note[hidden] { display: none !important; }

/* ── Data table (§3) ──────────────────────────────────────────────────────── */
.thc-table {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;                /* frozen radius/md — cards/containers */
}
.thc-table__toolbar {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.thc-table__heading { min-width: 0; }
.thc-table__title {
  margin: 0;
  font-size: 16px; line-height: 22px; font-weight: 600;   /* frozen h3 token */
  color: var(--text-primary);
}
/* Scope + count are mandatory (TH-UI-029) and must stay legible: AA-capable token, never faint. */
.thc-table__summary {
  margin: 2px 0 0;
  font-size: 12px; line-height: 16px;
  color: var(--text-muted);
  display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
}
.thc-table__scope { font-weight: 600; }
.thc-table__qualifier { font-style: normal; }
.thc-table__controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.thc-table__scroll { overflow: auto; min-height: 0; flex: 1 1 auto; }
/* PFC-001 and the frozen responsive contract: 2px ring at +2px offset, never clipped. The offset
   draws OUTSIDE the scroll region, so the region is inset by 2px and the container no longer clips
   it — `.thc-table` keeps its rounded corners through the scroller's own radius instead. */
.thc-table__scroll { margin: 2px; border-radius: 0 0 5px 5px; }   /* frozen radius/sm, inset inside the 8px container */
.thc-table__scroll:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.thc-table__grid { width: 100%; border-collapse: collapse; font-size: 13px; }
.thc-table__head th {
  position: sticky; top: 0; z-index: 1;      /* header stays visible while the body scrolls */
  background: var(--surface-sub);
  border-bottom: 1px solid var(--border);
  padding: 8px 12px;
  text-align: left;
  font-size: 11px; line-height: 14px; font-weight: 600;   /* frozen overline token */
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
.thc-table__grid tbody td {
  height: 40px;                      /* frozen compact row */
  padding: 6px 12px;
  border-bottom: 1px solid var(--border-soft);
  color: var(--text-body);
  vertical-align: middle;
}
.thc-table__grid tbody tr:last-child td { border-bottom: 0; }
.thc-table__grid tbody tr:hover td { background: var(--hover); }
.thc-table__cell--primary { font-weight: 600; color: var(--text-primary); }
.thc-table__cell--meta { color: var(--text-muted); }
.thc-table__cell--stamp { font-variant-numeric: tabular-nums; color: var(--text-muted); }
.thc-table__cell--actions { text-align: right; white-space: nowrap; }
/* SERIAL — a compact record number ahead of the identity (owner ruling, 12 August 2026). It is
   deliberately quiet: the reader scans the NAME column, and the number is there for the one moment
   they need to quote a record. `width: 1%` with `nowrap` is the standard shrink-to-content trick —
   a table lays out `1%` as „as narrow as the content allows", so the column never steals width from
   the object name. Tabular figures keep the digits in a straight edge down the column.

   NOT PINNED at <=1023, unlike the identity beside it. Only one column can hold the left edge while
   the rest scrolls, and if the reader can keep exactly one value it must be the one that says WHICH
   building — so the serial scrolls away underneath the opaque primary cell. */
/* SPECIFICITY IS DELIBERATE HERE. `.thc-table__grid tbody td` sets `color: var(--text-body)` at
   (0,1,2); a bare `.thc-table__cell--serial` at (0,1,0) loses, and the muted colour silently does
   nothing while the rule sits in the sheet looking correct. Measured in a browser, not assumed —
   the declaration was already written and already dead before this selector was widened. */
.thc-table__cell--serial { width: 1%; white-space: nowrap; }
.thc-table__grid tbody td.thc-table__cell--serial,
.thc-table__grid thead th.thc-table__cell--serial {
  font-variant-numeric: tabular-nums; font-size: 12px; color: var(--text-muted);
}

/* JD-003 / DCR-11 — PERSISTENT row selection, opt-in via the table's `selectable` prop. Distinct
   from :hover, which is transient, and it must win where both apply: these rules carry the same
   specificity as the hover rule above and follow it, so source order decides. Nothing applies unless
   a row carries the class, so no existing consumer is affected. */
.thc-table__grid tbody tr.thc-table__row--selected td { background: var(--selected-bg); }
.thc-table__grid tbody tr.thc-table__row--selected td:first-child {
  box-shadow: inset 2px 0 0 0 var(--selected-indicator);
}

/* BADGE ISOLATION (owner composition rule). Row selection is container state; a semantic chip keeps
   its visual integrity regardless. Where the selected tint would merge with a chip's fill — in dark,
   --info-bg and --selected-bg are the SAME value, so a „Wird erstellt" chip would vanish — the chip
   is isolated on the standard surface token behind it. The chip's own tokens never change; only its
   ground is normalised, so it renders identically at rest, on hover and when selected. */
.thc-table__grid tbody tr.thc-table__row--selected .thc-chip { box-shadow: 0 0 0 3px var(--surface); }

/* Narrow widths (frozen RESPONSIVE §tables, 768): the scroll region must RETAIN ROW IDENTITY and
   keep every permitted action reachable. Scrolling sideways to a timestamp is useless if the row it
   belongs to has slid away, and actions parked at the far end are not "reachable" in any real sense.
   The identity column and the action cell therefore stay pinned, both with opaque backgrounds — a
   transparent sticky cell would let the scrolled content show through it. */
@media (max-width: 1023px) {
  .thc-table__grid th.thc-table__cell--primary,
  .thc-table__grid tbody td.thc-table__cell--primary { position: sticky; left: 0; z-index: 2; background: var(--surface); }
  /* Only a genuine ACTION column is pinned on the right — matched by the same class on the header
     and the cells. Pinning `th:last-child` would fix the final header of a table that has no row
     actions while its own body cells scrolled out from under it. */
  .thc-table__grid th.thc-table__cell--actions,
  .thc-table__grid tbody td.thc-table__cell--actions { position: sticky; right: 0; z-index: 2; background: var(--surface); }
  /* The header's own cells are already sticky vertically; the pinned corners are sticky on both axes
     and must sit above the rest of the header. */
  .thc-table__head th.thc-table__cell--primary,
  .thc-table__head th.thc-table__cell--actions { z-index: 3; background: var(--surface-sub); }
  /* Hover repaints the pinned cells too, so a hovered row does not break in half. */
  .thc-table__grid tbody tr:hover td.thc-table__cell--primary,
  .thc-table__grid tbody tr:hover td.thc-table__cell--actions { background: var(--hover); }
  /* Selection repaints the pinned cells for the same reason hover does — a selected row
     must not break in half where the sticky column meets the scrolled body. */
  .thc-table__grid tbody tr.thc-table__row--selected td.thc-table__cell--primary,
  .thc-table__grid tbody tr.thc-table__row--selected td.thc-table__cell--actions { background: var(--selected-bg); }
  /* A hairline marks where the pinned column ends, so the seam reads as intentional. */
  .thc-table__grid tbody td.thc-table__cell--primary { border-right: 1px solid var(--border-soft); }
  .thc-table__grid tbody td.thc-table__cell--actions { border-left: 1px solid var(--border-soft); }
}

/* Narrow mobile (frozen RESPONSIVE §tables, 375 read-safe): each row becomes a stacked RECORD CARD
   preserving primary identity, status, permitted actions and — through the unchanged toolbar — scope
   and count. A four-column grid at 375 is not read-safe however well it scrolls.

   Cells label themselves from `data-thc-label`, which the component APPLIES from the column each
   cell sits in — the caller declares columns once and never writes a label, so a label cannot drift
   from its header. The identity and action cells carry no label: the identity IS the record's name,
   and the actions are named by the controls inside them. The action cell stays with its row —
   never dropped (TH-UI-023). */
@media (max-width: 767px) {
  .thc-table__scroll { overflow: visible; }
  .thc-table__grid, .thc-table__grid tbody, .thc-table__grid tr, .thc-table__grid td { display: block; }
  /* The <=1023 pinning belongs to the horizontal-scroll strategy this presentation replaces: there
     is no sideways scroll left to pin against, and its seam hairlines would cut across the cards. */
  .thc-table__grid th, .thc-table__grid tbody td,
  .thc-table__grid tbody td.thc-table__cell--primary, .thc-table__grid tbody td.thc-table__cell--actions {
    position: static;
    border-right: 0; border-left: 0;
    background: transparent;
  }
  /* The header row is redundant once every cell carries its own label, and hiding it this way keeps
     the association markup intact for assistive tech that reads the table semantics. */
  .thc-table__head { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

  .thc-table__grid tbody tr {
    border: 1px solid var(--border);
    border-radius: 8px;              /* frozen radius/md */
    margin: 12px 16px;
    padding: 4px 0;
    background: var(--surface);
  }
  .thc-table__grid tbody tr:hover td { background: transparent; }

  .thc-table__grid tbody td {
    height: auto; min-height: 0;
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 7px 14px;
    border-bottom: 0;
    text-align: left;
    /* A long unbroken value — an email, a URL, a filename — must break rather than widen the card
       past the screen. There is no horizontal scroll left at this width to absorb it, and the
       overflow affordance is deliberately hidden. */
    white-space: normal;
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
  }
  .thc-table__grid tbody td > * { min-width: 0; max-width: 100%; }
  /* …except the action controls, which are fixed-size targets and must not be broken up. */
  .thc-table__grid tbody td.thc-table__cell--actions { overflow-wrap: normal; word-break: normal; }
  /* The label sits opposite its value, so a card reads as label/value pairs rather than a column of
     unexplained fragments. */
  .thc-table__grid tbody td[data-thc-label]::before {
    content: attr(data-thc-label);
    flex: 0 0 auto;
    font-size: 11px; line-height: 14px; font-weight: 600;
    letter-spacing: .04em; text-transform: uppercase;
    color: var(--text-muted);
  }
  /* Identity leads the card at full width; the actions close it, still reachable. The MARKED cell,
     not the first one — a checkbox column would otherwise be promoted as the record's name. */
  .thc-table__grid tbody td.thc-table__cell--primary {
    display: block;
    order: -1;
    font-size: 14px; line-height: 20px;
    padding-top: 11px;
  }
  .thc-table__grid tbody tr { display: flex; flex-direction: column; }
  .thc-table__grid tbody td.thc-table__cell--actions {
    justify-content: flex-end;
    padding-top: 4px; padding-bottom: 8px;
  }
  /* The 40px compact-row height is a GRID rule; a stacked cell sizes to its own content. */
  .thc-table__grid tbody td { height: auto; }
  /* The serial's `width: 1%` is a GRID instruction too — it means „shrink to content" only inside a
     table layout. On a stacked card the cell is a flex row, where 1% is simply 1% of the card and
     the number would be crushed against its own label. It reverts to an ordinary labelled row and
     keeps only its tabular figures; the card's `order: -1` on the primary cell already guarantees
     the name is read first, whatever the DOM order. */
  .thc-table__grid tbody td.thc-table__cell--serial { width: auto; }
  /* No horizontal scrolling is left to announce. */
  .thc-table__overflow-note { display: none; }
}

.thc-table__state { padding: 8px 16px 20px; }
/* Loading placeholders take the ROW rhythm — same 40px compact row and hairline as real rows — so
   the pending list is the shape of the list that will replace it. */
.thc-table__state--loading { padding: 0; }
.thc-table__skeleton-row {
  display: block;
  box-sizing: border-box;            /* 40px TOTAL, matching a real compact row */
  height: 40px; padding: 12px 16px;
  border-bottom: 1px solid var(--border-soft);
}
.thc-table__state--loading .thc-table__skeleton-row:last-child { border-bottom: 0; }
.thc-table__overflow-note {
  margin: 0; padding: 6px 16px;
  border-top: 1px solid var(--border-soft);
  font-size: 12px; color: var(--text-muted);
}

/* ── Menus (§11) ──────────────────────────────────────────────────────────── */
.thc-menu-root { position: relative; display: inline-flex; }
/* VIEWPORT-positioned, not absolute: a row-actions menu lives inside `.thc-table__scroll`, and a
   scroll container clips its absolutely-positioned descendants whatever their z-index — the last
   row's menu would be cut off while still holding focus. `position: fixed` escapes that clip;
   themis-composites.js writes top/left from the trigger's rectangle and flips or clamps at the
   viewport edges. The static offsets below are only the pre-measurement resting place. */
.thc-menu {
  /* Above the shell chrome: the sidebar is 1040 and its drawer scrim 1030, so the previous 60 put
     a row-actions panel UNDER the sidebar. The skip link stays highest at 2000. */
  position: fixed; top: 0; left: 0; z-index: 1050;
  min-width: 208px; max-width: calc(100vw - 16px); padding: 6px;
  overflow-y: auto;                  /* a menu taller than the screen scrolls, it never overflows */
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;                /* frozen radius/md */
  box-shadow: 0 4px 12px rgba(16,24,32,0.10);   /* frozen elevation e2 */
}

.thc-menu__item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  min-height: 40px; padding: 6px 10px;   /* compact pointer default */
  background: none; border: none; cursor: pointer;
  border-radius: 5px;                /* frozen radius/sm */
  font-family: inherit; font-size: 13px; text-align: left; text-decoration: none;
  color: var(--text-body);
}
.thc-menu__item:hover { background: var(--hover); }
.thc-menu__item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-menu__item[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }
.thc-menu__icon { flex: 0 0 auto; }

/* Destructive and restorative are visually AND semantically opposite (§1, §6b) — never conflated,
   and the label always carries the meaning so colour is not the only cue. */
.thc-menu__item--danger { color: var(--red-fg); }
.thc-menu__item--danger:hover { background: var(--red-bg); }
.thc-menu__item--restorative { color: var(--green-fg); }
.thc-menu__item--restorative:hover { background: var(--green-bg); }
/* A destructive item is separated from ordinary commands by a rule. */
.thc-menu__item--danger { margin-top: 4px; border-top: 1px solid var(--border-soft); padding-top: 10px; }

.thc-menu__section {
  margin: 6px 0 2px; padding: 0 10px;
  font-size: 11px; line-height: 14px; font-weight: 600;   /* frozen overline token */
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-muted);
}

/* ── Dialogs (§5) ─────────────────────────────────────────────────────────── */
.thc-dialog__backdrop {
  position: fixed; inset: 0; z-index: 1100;   /* a modal covers ALL shell chrome, sidebar included */
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: var(--overlay);        /* frozen scrim — theme-aware token, not a fixed RGBA */
}
.thc-dialog {
  width: 100%; max-width: 560px; max-height: 86vh;
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;                /* frozen radius/md */
  box-shadow: 0 12px 32px rgba(16,24,32,0.16);
}
.thc-dialog__header {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.thc-dialog__title {
  margin: 0; flex: 1 1 auto;
  font-size: 18px; line-height: 24px; font-weight: 600;   /* frozen h2 token */
  color: var(--text-primary);
}
.thc-dialog__body { padding: 16px; overflow-y: auto; color: var(--text-body); font-size: 14px; }
.thc-dialog__desc p { margin: 0 0 8px; }
.thc-dialog__desc p:last-child { margin-bottom: 0; }
.thc-dialog__object { font-weight: 600; color: var(--text-primary); }
.thc-dialog__recovery, .thc-dialog__destination { color: var(--text-muted); font-size: 13px; }
.thc-dialog__footer {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 12px 16px; border-top: 1px solid var(--border);
}
.thc-dialog__form { margin: 0; }

/* ── Lightbox (§5) ────────────────────────────────────────────────────────── */
/* Deliberately darker than `--overlay` in BOTH themes: a photo is judged against its surround, so
   the frozen §5 lightbox dims the screen far harder than a confirmation scrim does. Because the
   surface is dark in light theme too, the light theme's `--focus-ring` (#14536E) would land at
   ~1.7:1 on it — under the 3:1 an indicator needs. The whole subtree therefore adopts the
   dark-surface focus token, so every control inside inherits a compliant ring without special-casing. */
.thc-lightbox__backdrop {
  /* The theme's own scrim token, deepened by a flat layer on top: a photo is judged against its
     surround, so §5 dims far harder than a confirmation does — but the token still governs the
     colour (decision #51). Because the result is dark in BOTH themes, the light theme's
     `--focus-ring` (#14536E) would land at ~2.3:1 here, under the 3:1 an indicator needs, so the
     whole subtree adopts the dark-surface focus token and every control inside inherits it. */
  background: linear-gradient(rgba(10,16,22,0.72), rgba(10,16,22,0.72)), var(--overlay);
  padding: 0;
  --focus-ring: #8FD3EF;
}
.thc-lightbox {
  position: relative;
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
}
.thc-lightbox__bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 18px;
}
.thc-lightbox__meta { min-width: 0; }
.thc-lightbox__title {
  margin: 0; font-size: 13px; font-weight: 700; color: var(--text-on-accent);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.thc-lightbox__counter { margin: 1px 0 0; font-size: 12px; color: var(--text-on-accent); opacity: .78; }
.thc-lightbox__close, .thc-lightbox__nav {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;          /* 44x44 touch target */
  border-radius: 50%;                 /* frozen radius/full */
  border: 1px solid rgba(255,255,255,0.22);
  background: rgba(255,255,255,0.08);
  color: var(--text-on-accent);
  cursor: pointer;
}
.thc-lightbox__close:focus-visible,
.thc-lightbox__nav:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-lightbox__stage {
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 0 18px;
}
.thc-lightbox__item { margin: 0; display: flex; align-items: center; justify-content: center; min-width: 0; flex: 1 1 auto; height: 100%; }
/* Largest useful size, aspect ratio preserved — never cropped (frozen §5). */
.thc-lightbox__image { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.thc-lightbox__hint {
  margin: 0; padding: 14px 18px 18px;
  text-align: center; font-size: 12px; color: var(--text-on-accent); opacity: .72;
}

/* ── Toast & banner (§12) ─────────────────────────────────────────────────── */
.thc-toast {
  position: fixed; left: 24px; bottom: 24px; z-index: 1060;
  display: flex; align-items: flex-start; gap: 10px;
  max-width: min(420px, calc(100vw - 48px));
  padding: 11px 14px;
  border-radius: 8px;                /* frozen radius/md */
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: 0 4px 12px rgba(16,24,32,0.10);   /* frozen elevation e2 — same as the menu */
  font-size: 13px; line-height: 19px;
}
.thc-toast__text { margin: 0; flex: 1 1 auto; color: var(--text-body); }
.thc-toast__icon, .thc-banner__icon { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; }
/* 44x44 hit area (frozen DESIGN_SYSTEM icon-button target, WCAG 2.2 §2.5.8) without a 44px visual
   box: the glyph stays 28px and negative margins reclaim the surrounding space, so the notification
   keeps its compact rhythm while the target a thumb has to hit is the full size. */
.thc-toast__dismiss, .thc-banner__dismiss {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box;
  width: 44px; height: 44px; margin: -8px -8px -8px 0; padding: 0;
  background: none; border: none; cursor: pointer; border-radius: 5px;
  color: inherit;
}
.thc-toast__dismiss .thc-ico, .thc-banner__dismiss .thc-ico { width: 16px; height: 16px; }
.thc-toast__dismiss:focus-visible,
.thc-banner__dismiss:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-toast--success { color: var(--green-fg); background: var(--green-bg); border-color: var(--green-border); }
.thc-toast--error   { color: var(--red-fg);   background: var(--red-bg);   border-color: var(--red-border); }
.thc-toast--info    { color: var(--brand-ink); background: var(--info-bg); border-color: var(--info-border); }
.thc-toast--success .thc-toast__text,
.thc-toast--error .thc-toast__text,
.thc-toast--info .thc-toast__text { color: inherit; }

.thc-banner {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px;
  border-radius: 5px;                /* frozen radius/sm */
  border: 1px solid;
  font-size: 13px; line-height: 19px;
}
.thc-banner__body { flex: 1 1 auto; min-width: 0; }
.thc-banner__body p { margin: 0; }
.thc-banner--info    { color: var(--brand-ink); background: var(--info-bg);   border-color: var(--info-border); }
.thc-banner--warning { color: var(--yellow-fg); background: var(--yellow-bg); border-color: var(--yellow-border); }
.thc-banner--error   { color: var(--red-fg);    background: var(--red-bg);    border-color: var(--red-border); }
.thc-banner--success { color: var(--green-fg);  background: var(--green-bg);  border-color: var(--green-border); }

/* ── Narrow widths ────────────────────────────────────────────────────────── */
@media (max-width: 767px) {
  .thc-table__toolbar { flex-direction: column; align-items: stretch; }
  .thc-dialog__backdrop { padding: 12px; align-items: flex-end; }
  .thc-dialog { max-height: 92vh; }
  .thc-dialog__footer { flex-direction: column-reverse; align-items: stretch; }
  .thc-toast { left: 12px; right: 12px; bottom: 12px; max-width: none; }
}

/* A coarse pointer has no precision to spare: row-action commands take the frozen 44px target
   (WCAG 2.2 §2.5.8) wherever the primary input is touch, rather than only on narrow screens — a
   touchscreen laptop is as imprecise as a phone. */
@media (pointer: coarse) {
  .thc-menu__item { min-height: 44px; }
  .thc-lightbox__close, .thc-lightbox__nav { width: 44px; height: 44px; }
}

/* The reason an item is unavailable: read by assistive tech through aria-describedby, and shown as
   quiet secondary text so a sighted keyboard user sees it too. */
.thc-menu__reason { display: block; font-size: 12px; line-height: 16px; color: var(--text-muted); }
.thc-menu__item[aria-disabled="true"] { flex-wrap: wrap; }

/* ═══════════  M2.5 — list-screen chrome  ═══════════ */

/* LIST PAGE = FULL WORKSPACE (frozen COMPONENT_LIBRARY §3). When a table IS the page's primary
   content — D1, D9, E1, E9 and role-5 „Meine Objekte" — the container fills the available width AND
   height, the header stays visible via the sticky `<thead>` the component already has, and the body
   scrolls inside it. Without this a 7-row list reads as a small card marooned in empty space rather
   than the primary work surface.

   OPT-IN, per screen, via `class="thc-table--workspace"`. The stretch is applied through `:has()` so
   no layout file changes and no unmigrated page is touched: a browser without `:has()` simply keeps
   the shrink-to-fit card, which is the current behaviour, not a broken one. `.thc-table` is already
   a flex column with `min-height: 0` and a `flex: 1 1 auto` scroll region, so it needs only a
   definite height to fill. */
.thc-shell-frame > .main-panel > .content:has(> .thc-table--workspace) {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
.thc-shell-frame > .main-panel > .content > .thc-table--workspace { flex: 1 1 auto; min-height: 0; }

/* Below the table/record-card breakpoint the rows become cards in normal page flow, so a stretched
   container would only add empty space under a short list. */
@media (max-width: 767px) {
  .thc-shell-frame > .main-panel > .content:has(> .thc-table--workspace) { display: block; }
  .thc-shell-frame > .main-panel > .content > .thc-table--workspace { flex: none; }
}

/* Almost everything on a migrated list screen is a composite. What remains is the flash banner's
   spacing and the link inside the identity column — the page-level action moved into the table's
   own toolbar slot, which is where the frozen anatomy puts it, so no separate heading row exists. */
.thc-list-flash { margin: 0 0 16px; }

/* The identity cell's link. It inherits the cell's weight from `--primary`, so it only needs the
   colour and the focus ring the rest of the system uses. */
.thc-list-link { color: var(--link); text-decoration: none; }
.thc-list-link:hover { text-decoration: underline; }
.thc-list-link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 3px; }

/* ═══════════  M2.6 — form fields and the user-admin form  ═══════════ */
/* NOTE THE CLASS NAMES. M2.3's login already owns `.thc-field`, `.thc-field__label`,
   `.thc-field__control` (there a positioned WRAPPER around the password input, not the input
   itself) and `.thc-field__error`. Reusing those names put input chrome around the login password
   field and overrode its label and error styling — on an authentication surface this milestone is
   expressly forbidden to touch, and reached because all three shells load the same stylesheet.
   The M2.6 primitive therefore uses `thc-formfield*` throughout. `CssContractTest` pins both. */
/* Frozen COMPONENT_LIBRARY §2. One label bound to one control; the field owns its id so the label,
   the description and the validation summary's jump target cannot drift apart. */
.thc-formfield { display: flex; flex-direction: column; gap: 6px; min-width: 0; position: relative; }

/* ── Textvorlagen suggestions (Mangel · Massnahme · Anmerkung) ─────────────────────────────────
   ONE dropdown, moved to whichever field has focus, so `position: relative` above is what it
   anchors to. It is a sibling of the textarea inside `.thc-formfield`, which is why no coordinate
   maths exists in the script.

   The label is the short handle and the value the full canonical text — both are shown, because in
   587 of the 1,015 templates they differ and choosing between them is the reader's decision.
   `max-height` keeps a 26-suggestion point (3.01) from covering the row beneath it. */
.thc-suggest {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 40;
  max-height: 220px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 5px; box-shadow: 0 6px 18px rgb(0 0 0 / 12%);
}
.thc-suggest[hidden] { display: none; }
.thc-suggest__item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 7px 10px; cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.thc-suggest__item:last-child { border-bottom: 0; }
/* `--hover` is the system's own token for this state — a literal fallback would have rendered fine
   and quietly introduced a colour the theme does not own, which is what the token contract exists to
   catch. It did. */
.thc-suggest__item:hover,
.thc-suggest__item[data-themis-suggest-active="true"] { background: var(--hover); }
.thc-suggest__label { font-size: 12.5px; line-height: 17px; font-weight: 600; color: var(--text-primary); }
.thc-suggest__value { font-size: 12px; line-height: 16px; color: var(--text-muted); }
/* Touch: a tap target below ~40px is hard to hit on an iPad held one-handed. */
@media (pointer: coarse) {
  .thc-suggest__item { padding: 11px 12px; }
  .thc-suggest { max-height: 260px; }
}
.thc-formfield__label {
  font-size: 12.5px; line-height: 16px; font-weight: 600;
  color: var(--text-muted);
}
.thc-formfield__required { color: var(--red-fg); margin-left: 2px; }
.thc-formfield__hint { margin: 0; font-size: 12.5px; line-height: 17px; color: var(--text-muted); }
.thc-formfield__control {
  width: 100%; box-sizing: border-box;
  padding: 9px 12px;
  font: inherit; font-size: 13.5px; line-height: 20px;
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 5px;                 /* frozen radius/sm */
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.thc-formfield__control:hover { border-color: var(--border-strong, var(--border)); }
.thc-formfield__control:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-formfield--invalid .thc-formfield__control { border-color: var(--red-fg); }
.thc-formfield__error { margin: 0; font-size: 12.5px; line-height: 17px; color: var(--red-fg); }

/* Select: keep the native control (its keyboard behaviour is the accessible one) and only restate
   the chrome, with room for the platform arrow. */
select.thc-formfield__control { padding-right: 30px; appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 14px center, right 9px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.thc-form { display: block; }
.thc-form__grid {
  display: grid; gap: 18px 20px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 720px;
}
.thc-form__actions {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-top: 26px; padding-top: 18px;
  border-top: 1px solid var(--border);
  max-width: 720px;
}
.thc-form__summary { margin: 0 0 20px; max-width: 720px; }
.thc-form__summary-title { margin: 0 0 6px; font-weight: 600; }
.thc-form__summary-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.thc-form__summary-list a { color: inherit; font-weight: 600; }
.thc-form__summary-list a:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 3px; }

/* A jumped-to field must not land under the sticky top bar. */
.thc-formfield__control { scroll-margin-top: 84px; }

@media (max-width: 767px) {
  .thc-form__grid { grid-template-columns: minmax(0, 1fr); }
}

/* Section header for a form page (G2/G4). The shell owns the route-aware <h1>; this names the
   section beneath it, exactly as the table composite does on a list page. */
/* ITEM 12 — every primary page ends flush against the viewport bottom, so the last card or control
   sits on the edge with nothing under it. One rule on the page container rather than a per-screen
   hack, so a screen added later inherits it. */
.thc-page { display: block; max-width: 100%; padding-bottom: 56px; }
.thc-page__head { margin: 0 0 22px; }
.thc-page__title { margin: 0; font-size: 17px; line-height: 24px; font-weight: 600; color: var(--text-primary); }
.thc-page__lead { margin: 5px 0 0; font-size: 13px; line-height: 19px; color: var(--text-muted); }
/* A field the actor may see but not change here (INV-2 self-edit). Not a disabled input: a disabled
   control still looks like a control and implies its value would submit, and this one submits
   nothing at all. */
.thc-formfield__readonly {
  margin: 0; padding: 9px 12px;
  font-size: 13.5px; line-height: 20px; color: var(--text-primary);
  background: var(--surface-sub); border: 1px solid var(--border); border-radius: 5px;
}
.thc-formfield__readonly:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-form__summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ═══════════  M2.7 — building detail (D2) and assignment control  ═══════════ */
.thc-detail { margin-bottom: 20px; }
.thc-detail__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 18px;
}
.thc-detail__identity { min-width: 0; }
.thc-detail__meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.thc-detail__section { margin: 0 0 10px; font-size: 13px; line-height: 18px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.thc-detail__data {
  padding: 16px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px;   /* frozen radius/md */
}
/* Kenndaten/Prüfpflichten as native <details>/<summary> disclosures (Dashboard UI polish) — the
   same mechanism the D4 catalogue already uses (`thc-catalogue__summary`), so the browser keeps
   supplying the toggle, the keyboard model and the ARIA expanded state; no JS is added here either.
   The native ▶/▼ marker is left unstyled, exactly as the catalogue disclosure leaves it. */
.thc-detail__summary { cursor: pointer; }
.thc-detail__summary .thc-detail__section { margin: 0; }   /* the heading's own margin only applies when open, below */
.thc-detail__data[open] > .thc-detail__summary { margin: 0 0 10px; }
.thc-detail__summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: 8px; }

/* Objektdaten: <dt>/<dd> pairs, so label and value are associated semantically rather than by
   sitting next to each other in a grid. */
.thc-deflist { margin: 0; display: grid; gap: 12px 24px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.thc-deflist__pair { min-width: 0; }
.thc-deflist__label { margin: 0 0 2px; font-size: 12px; line-height: 16px; font-weight: 600; color: var(--text-muted); }
.thc-deflist__value { margin: 0; font-size: 13.5px; line-height: 19px; color: var(--text-primary); overflow-wrap: anywhere; }

.thc-assign { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.thc-assign__select { min-width: 180px; }
.thc-assign__select[aria-busy="true"] { opacity: .7; }
/* The per-row status region. Empty until something has happened, so it occupies no space and makes
   no claim; each state is text + colour, never colour alone (TH-UI-022). */
/* The badge variant holds a chip, which is sized by its own label — stretching it to the column's
   width would make a status badge as wide as the cell. The select variant still stretches. */
.thc-assign--badge { align-items: flex-start; }
.thc-assign__status { margin: 0; font-size: 12px; line-height: 16px; }
.thc-assign__status:empty { display: none; }
.thc-assign__status[data-state="pending"] { color: var(--text-muted); }
.thc-assign__status[data-state="success"] { color: var(--green-fg, var(--text-muted)); }
.thc-assign__status[data-state="error"]   { color: var(--red-fg); }

/* AN UNRESOLVABLE ASSIGNEE (owner ruling, 12 August 2026). A permanent condition of the row, not an
   event, so it is plain text rather than the live region above — announcing it on every render would
   read the same sentence aloud for every affected row on the page, and 645 entries are affected.
   Error ink, because „this record points at somebody who cannot hold it" is a defect an
   administrator has to resolve, not a neutral note. */
.thc-assign__invalid { margin: 4px 0 0; font-size: 12px; line-height: 16px; color: var(--red-fg); }

/* ── DIRECT ROW ACTIONS (owner ruling, 12 August 2026, item 10) ───────────────────────────────────
   The one or two actions a reader performs on nearly every row sit in the cell; the rest stay behind
   the overflow menu, which trails them. Right-aligned to match the actions column it lives in, and
   `nowrap` so a two-button row never breaks across lines and doubles the row height.

   AT <=767 the actions cell becomes the last row of a record card and is already `justify-content:
   flex-end`; the buttons wrap there rather than forcing the card wider, because there is no
   horizontal scroll left at that width to absorb them. */
.thc-row-direct {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 6px; flex-wrap: nowrap;
}
@media (max-width: 767px) { .thc-row-direct { flex-wrap: wrap; } }

@media (max-width: 1023px) { .thc-deflist { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 767px)  { .thc-deflist { grid-template-columns: minmax(0, 1fr); }
  .thc-detail__head { flex-direction: column; } }

/* ═══════════  M2.7 — sectioned forms (frozen COMPONENT_LIBRARY §15)  ═══════════ */
/* A real <fieldset>/<legend>: the legend names the group for assistive tech, which a styled <h3>
   floating above a <div> does not. */
.thc-fieldset { border: 0; margin: 0 0 26px; padding: 0; min-width: 0; }
.thc-fieldset__legend {
  padding: 0; margin: 0 0 12px;
  font-size: 13px; line-height: 18px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint);
}

/* ═══════════  M2.7 — creation forms (D4/D5/D8), frozen COMPONENT_LIBRARY §15  ═══════════ */

/* ITEM 1 + 15 — 72px was three visible lines by default for fields that usually hold one. The
   floor is now two lines; `resize: vertical` is untouched, so the reader still drags any field as
   tall as they like and each one keeps its OWN height (browser-native, per element — resizing one
   never moves its pair). */
.thc-formfield__control--multiline { min-height: 48px; resize: vertical; line-height: 20px; padding-top: 6px; }
/* ITEM 15 — a question is usually one line, so it starts at one and grows only if the reader drags
   it. It shares `resize: vertical` with the answer beside it and nothing else: two textareas in one
   row must size independently, which is what „independently resizable" means here. */
.thc-hinweis__question { min-height: 34px; }

/* ── INSPECTION-POINT FILTER (owner item 6) ───────────────────────────────────────────────────
   A single narrow control above the points it filters. Deliberately small and left-aligned: it is a
   view control, not a page action, and it must not read as something that changes the record. */
/* RIGHT-ALIGNED (owner ruling, 12 August 2026). `justify-content: flex-end` moves the control to
   the end of the row it already occupies — no toolbar restructuring, no spacing changes elsewhere.
   The note keeps its place BEFORE the select, so the pair reads „12 / 40 · X  [ filter ]" rather than
   the count drifting away from the control that produced it. */
.thc-pointfilter { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin: 0 0 8px; }
.thc-pointfilter__select { width: auto; min-width: 180px; height: 30px; padding: 4px 8px; font-size: 12px; }
.thc-pointfilter__note { margin: 0; font-size: 12px; line-height: 16px; color: var(--text-muted); }
/* No reserved line while nothing is filtered — the same rule as the photo status. */
.thc-pointfilter__note:empty { display: none; }

/* ── LIST FILTER (owner item 16) ──────────────────────────────────────────────────────────────
   Sits in the table toolbar beside the page action. Narrow and quiet: it filters a view, it does not
   act on the record set. */
.thc-table__search { display: flex; align-items: center; gap: 8px; }
.thc-table__search-input { width: auto; min-width: 220px; height: 32px; padding: 5px 10px; font-size: 13px; }
.thc-table__search-note { margin: 0; font-size: 12px; line-height: 16px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* No reserved line before the reader types — same rule as the photo status and the point filter. */
.thc-table__search-note:empty { display: none; }
@media (max-width: 767px) {
  .thc-table__search { width: 100%; }
  .thc-table__search-input { width: 100%; min-width: 0; }
}

/* A note that qualifies a whole section — what the route will do with it, or will not. */
.thc-form__note {
  margin: -4px 0 14px; max-width: 68ch;
  font-size: 13px; line-height: 19px; color: var(--text-muted);
}

/* The creation-consequence summary: stated BEFORE the submit, not discovered after it. */
/* Same measure as the validation summary above it: two stacked notices at different widths read as
   an accident, and full-bleed prose at 1440px is a worse line length than either. */
.thc-consequence { margin: 0 0 26px; max-width: 720px; }
.thc-consequence__title { margin: 0; }
.thc-consequence__list { margin: 6px 0 0; padding-left: 18px; }
.thc-consequence__list li { margin: 2px 0; }

/* ── Repeatable rows ───────────────────────────────────────────────────────────────────────── */
.thc-repeat { margin: 0 0 18px; }
.thc-repeat__rows { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
.thc-repeat__row {
  display: grid; gap: 10px 12px; align-items: end;
  padding: 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-sub);
}
/* Prüfung · Intervall · Regelung · remove — the fourth column is the action, sized to its content. */
.thc-repeat__row--obligation { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1.2fr) auto; }
.thc-repeat__row--hinweis { grid-template-columns: minmax(0, 1fr) auto; }
.thc-repeat__row-fields { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.thc-repeat__row-actions { display: flex; align-items: center; }
.thc-repeat__status { margin: 0; font-size: 12px; line-height: 16px; color: var(--text-muted); }
.thc-repeat__status:empty { display: none; }
/* Its own name, not the repeat vocabulary: the two regions report unrelated things. */
.thc-form__submit-status { margin: 0; align-self: center; font-size: 12px; line-height: 16px; color: var(--text-muted); }
.thc-form__submit-status:empty { display: none; }

/* Category block inside Prüfpflichten. The heading names the group the rows belong to. */
.thc-obligations__group { margin: 0 0 22px; }
.thc-obligations__group:last-child { margin-bottom: 0; }
.thc-obligations__title {
  margin: 0 0 8px; font-size: 14px; line-height: 20px; font-weight: 600; color: var(--text-primary);
}

@media (max-width: 1023px) {
  .thc-repeat__row--obligation { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .thc-repeat__row--obligation .thc-repeat__row-actions { grid-column: 1 / -1; justify-content: flex-end; }
}
@media (max-width: 767px) {
  .thc-repeat__row--obligation,
  .thc-repeat__row--hinweis { grid-template-columns: minmax(0, 1fr); }
  .thc-repeat__row--hinweis .thc-repeat__row-actions { justify-content: flex-end; }
}

/* ── Seeded catalogue disclosure ───────────────────────────────────────────────────────────── */
.thc-catalogue { display: flex; flex-direction: column; gap: 8px; }
.thc-catalogue__section { border: 1px solid var(--border); border-radius: 8px; background: var(--surface-sub); }
.thc-catalogue__summary {
  display: flex; align-items: baseline; gap: 8px; justify-content: space-between;
  padding: 10px 12px; cursor: pointer; font-size: 13.5px; line-height: 19px;
  font-weight: 600; color: var(--text-primary);
}
.thc-catalogue__summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: 8px; }
.thc-catalogue__count { font-weight: 400; font-size: 12.5px; color: var(--text-muted); }
/* D8: a rolled-over row inside a disclosure — key, its two fields, then its brief rows. */
.thc-catalogue__rows { display: flex; flex-direction: column; gap: 10px; padding: 0 12px 12px; }
.thc-catalogue__row {
  display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 4px 12px;
  padding-top: 10px; border-top: 1px solid var(--border-soft, var(--border));
}
.thc-catalogue__row:first-child { border-top: 0; padding-top: 0; }
.thc-catalogue__rowkey {
  margin: 6px 0 0; font-size: 12px; line-height: 16px; font-weight: 600; color: var(--text-muted);
}
.thc-catalogue__rowfields {
  grid-column: 2; display: grid; gap: 10px 12px;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}
.thc-catalogue__brief {
  grid-column: 2; display: grid; gap: 10px 12px; margin-top: 4px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding: 10px 12px; border-radius: 8px; background: var(--surface-inset, var(--surface));
}
@media (max-width: 1023px) {
  .thc-catalogue__rowfields,
  .thc-catalogue__brief { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 767px) {
  .thc-catalogue__row { grid-template-columns: minmax(0, 1fr); }
  .thc-catalogue__rowfields, .thc-catalogue__brief { grid-column: 1; }
}

.thc-catalogue__list {
  margin: 0; padding: 0 12px 12px 30px;
  font-size: 13px; line-height: 20px; color: var(--text-body);
}
.thc-catalogue__list li { margin: 1px 0; }

/* ── Media (cover photo / Lageplan) ────────────────────────────────────────────────────────── */
.thc-upload { display: flex; flex-direction: column; gap: 4px; }
.thc-upload__control { font-size: 13px; line-height: 19px; color: var(--text-body); }
.thc-upload__control:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 3px; }

/* ═══════════  M2.7 — D2 view-only media strip (frozen D2, DR-corr)  ═══════════ */
.thc-media {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin: 0 0 20px;
  /* M2.8: E2 renders this as a real <ul> of evidence photos, so the markers have to go. D2's
     media strip uses <figure> children and never showed them. */
  padding: 0; list-style: none;
}
.thc-media__item { margin: 0; min-width: 0; list-style: none; }
.thc-media__caption {
  margin: 0 0 6px; font-size: 12px; line-height: 16px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint);
}
.thc-media__frame {
  display: block; width: 100%; padding: 0; position: relative;
  /* Capped: two empty placeholders at half width would otherwise take ~300px of vertical space
     each, pushing the entries table — the working part of this screen — below the fold. */
  aspect-ratio: 16 / 9; max-height: 220px; overflow: hidden;
  background: var(--surface-sub); border: 1px solid var(--border); border-radius: 8px;
}
.thc-media__frame--action { cursor: pointer; }
.thc-media__frame--action:hover { border-color: var(--border-strong); }
.thc-media__frame--action:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-media__frame--empty { display: flex; align-items: center; justify-content: center; }
/* CONTAIN, NOT COVER (owner ruling, 14 August 2026). The frame is a fixed 16/9 box, so `cover`
   scaled every non-16/9 photo up and cropped the overflow — a cover shot lost its edges and a map
   lost its margins, while the lightbox (`.thc-lightbox__image`, already `contain`) showed the same
   file correctly. The source images were never the problem; the thumbnail rule was. Letterboxing
   falls on the frame's own `--surface-sub` background, which is what that background is for. */
.thc-media__image { width: 100%; height: 100%; object-fit: contain; display: block; }
.thc-media__empty { margin: 0; font-size: 13px; line-height: 19px; color: var(--text-muted); }
.thc-media__zoom {
  position: absolute; right: 8px; bottom: 8px;
  padding: 4px 8px; border-radius: 999px;
  font-size: 12px; line-height: 16px;
  background: var(--overlay); color: var(--text-on-accent);
}
@media (max-width: 767px) { .thc-media { grid-template-columns: minmax(0, 1fr); } }

/* ───────────────────  E2 read-only inspection detail (M2.8 slice 1)  ─────── */
.thc-entry-read { display: flex; flex-direction: column; gap: 20px; }

.thc-breadcrumb { margin: 0 0 4px; }
.thc-breadcrumb__list {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0; padding: 0; list-style: none;          /* an ordered list, not a numbered one */
  font-size: 12px; line-height: 16px; color: var(--text-muted);
}
.thc-breadcrumb__list li + li::before { content: '/'; margin-right: 6px; color: var(--text-faint); }
.thc-breadcrumb__list a { color: inherit; }
.thc-breadcrumb__list [aria-current="page"] { color: var(--text-body); font-weight: 600; }

/* „Nur Ansehen" — a statement about the screen, so it reads as information, not as a denial. */
.thc-readonly-marker {
  display: flex; align-items: center; gap: 8px; margin: 10px 0 0;
  padding: 8px 12px; border: 1px solid var(--border); border-radius: 3px;
  background: var(--surface-sub); color: var(--text-body);
  font-size: 13px; line-height: 19px;
}
.thc-readonly-marker .thc-ico { flex: 0 0 auto; color: var(--text-muted); }

.thc-catalogue-read__section { margin: 0 0 24px; }
.thc-catalogue-read__section:last-child { margin-bottom: 0; }
.thc-catalogue-read__heading {
  margin: 0 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border-soft);
  font-size: 15px; line-height: 21px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-primary);
}
.thc-catalogue-read__number { color: var(--text-muted); margin-right: 4px; }

.thc-point { padding: 8px 0; border-bottom: 1px solid var(--border-soft); }
.thc-point:last-child { border-bottom: 0; }
.thc-point__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin: 0 0 8px; font-size: 14px; line-height: 20px; font-weight: 600;
}
.thc-point__key { color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* `min-width: 0` as well as the wrap: this is a flex child, and a flex item's default
   `min-width: auto` refuses to shrink below its longest word, so the wrap rule alone would
   never get the chance to apply. The status chip beside it keeps its `margin-left: auto`. */
.thc-point__title { color: var(--text-primary); min-width: 0; overflow-wrap: anywhere; }
.thc-point__status { margin-left: auto; flex: 0 0 auto; }
.thc-point__mangel { display: flex; flex-direction: column; gap: 6px; }
.thc-point--scalar { opacity: .85; }
.thc-point__unreadable { margin: 0; font-size: 13px; line-height: 19px; color: var(--text-muted); }
.thc-point__raw { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text-body); }

/* A Mangel card. The tint follows the PRIORITY, so „Keine" never looks like a hazard.
   FULL-CARD, not an edge. Frozen DESIGN_SYSTEM §2.4 / TH-UI-022 specifies each Behebungspriorität
   as „tinted background + accent left rule/border + text label + symbol — never colour alone", and
   this rendered every card on the same `--surface-sub` with only the 3px left edge recoloured. The
   label and the symbol were already there (`[data-themis-priority-label]`); the background and the
   accent border were not, so at a glance every Mangel looked identical. Mode-C finding 3.

   THE COLOURS ARE §2.5's BACKGROUND AND BORDER MAPPING, not a bespoke palette. §2.5 („Semantic
   status/priority in dark") names each of these four states an fg/bg/border triplet of EXISTING
   tokens; §2.4 gives a different bespoke set for the same four, light only. The two frozen sections
   disagree, and only §2.5 states dark — so following §2.4 meant inventing dark values, which the
   freeze does not authorise. This takes §2.5's bg and border TOKENS and invents nothing.

   TWO LIMITS, stated rather than glossed. §2.5 prints numeric values for its light fg/bg and for
   the full dark triplet, but not for the light BORDER — that comes from the named token. And the
   FOREGROUND is not set by these modifiers. Where a label sits DIRECTLY on the card tint — E3's
   legend — it uses `--text-secondary`, because §2.5's own `--text-muted` measures 3.98:1 there and
   fails AA; the „!"/„!!" symbols keep §2.5's `--red-fg`/`--yellow-fg`. Where §2.5's fg and bg are
   applied as a PAIR — E2's `.thc-chip--*` — its foreground stands unchanged and clears AA on its
   own background (worst 4.71:1). Decision #78 states both halves.

   OWNER DECISION #78 (taken): frozen §2.5 supersedes §2.4 for these four states. */
/* ITEM 1 — the card is a compact inspection form, not a form panel. Padding and the gaps between
   its rows carried most of the height: five fields at 10px padding plus 12px grid gaps plus a 72px
   textarea floor is ~90px of whitespace before a single word is read. The textarea keeps
   `resize: vertical` — a shorter DEFAULT is not a shorter ceiling, and an inspector writing a long
   Anmerkung still drags it open. */
.thc-mangel {
  padding: 7px 10px; border: 1px solid var(--border); border-left-width: 3px;
  border-radius: 3px; background: var(--surface-sub);
}
.thc-mangel--warning {          /* Keine Behebungspriorität — yellow */
  background: var(--yellow-bg); border-color: var(--yellow-border);
}
.thc-mangel--danger {           /* Mangel mit Gefahr — muted light red */
  background: var(--red-bg); border-color: var(--red-border);
}
.thc-mangel--danger-strong {    /* Gefahr in Verzug — strong red */
  background: var(--red-bg-strong); border-color: var(--red-border);
}
.thc-mangel--muted {            /* Hinweis — grey */
  background: var(--chip-bg); border-color: var(--border);
}
/* SECONDARY, NOT MUTED, FOR EVERY LABEL ON THIS CARD. Tinting the background changed what the text
   sits on: `--text-muted` (#5C6B73) on „Gefahr in Verzug" (#F9D2CD) measures 3.98:1 in a browser —
   below AA — and the legend is where „Mangel 3 !! Gefahr in Verzug" is written, so the worst tint
   was carrying the most important label. `--text-secondary` is ≥5.69:1 on all four tints and
   ≥7.5:1 on the untinted surfaces these classes also appear on. Measured, not assumed.

   AND THE SHARED CLASSES TOO, which the first pass missed because it swept `.thc-mangel__*` by
   PREFIX. `.thc-formfield__label` and `.thc-photos__count` are used all over the product, are
   perfectly legible on white, and were still `--text-muted` inside the tinted card — the same
   3.98:1. A prefix is not a surface: what matters is what the card RENDERS, which is why the
   contract test now walks the rendered card instead of guessing at names.

   THREE CLASSES, AND ONLY TEXT ONES. `.thc-form__note` is the subtle one: it is the photo status
   line, EMPTY at render and filled by the JS („Foto hinzugefügt.") the moment the reader uploads, so
   it would have failed at 3.98:1 the first time it said anything and no static render would have
   shown it.

   `.thc-ico` and `.thc-media__thumb` were in an earlier version of this list on the reasoning that
   an allowlist is a place for the next offender to hide. That was wrong, and it broke something: the
   row's LIGHTBOX is rendered inside this fieldset, its close and prev/next glyphs sit on a dark
   scrim and deliberately take `--text-on-accent`, and `.thc-mangel .thc-ico` outranks
   `.thc-lightbox__close`. The icons went near-white to dark slate on near-black. Neither class
   carries text, so neither was ever the defect. The rule covers text; the guard against the next
   offender is the rendered-class test, not a wider selector. */
.thc-mangel .thc-formfield__label,
.thc-mangel .thc-photos__count,
.thc-mangel .thc-form__note { color: var(--text-secondary); }
.thc-mangel__head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 0 8px; font-size: 13px; line-height: 19px; font-weight: 600;
}
.thc-mangel__ordinal { color: var(--text-secondary); }
/* ITEM 1 — one compact row where the width allows it. `minmax(160px, 1fr)` fitted three columns in
   the editor's ~600px beside the QC panel, so five fields always wrapped to two rows and the card
   paid for a second row of labels and gaps. At 140px the same space takes four, and a wide editor
   (QC collapsed, or ≥1500px) takes all five.

   THE PRIORITY COLUMN IS SIZED TO ITS CONTENT, which is the other half of item 2: now that it holds
   „–", „i", „!" or „!!" rather than „Gefahr in Verzug (!!)", it has no business claiming an equal
   fraction of the row. */
.thc-mangel__fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 6px 10px; margin: 0;
}
.thc-mangel__pair:has(.thc-mangel__priority-select) { max-width: 96px; }
.thc-mangel__pair { min-width: 0; }
.thc-mangel__label {
  margin: 0; font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-secondary);
}
.thc-mangel__value {
  margin: 0; font-size: 13px; line-height: 19px; color: var(--text-body);
  overflow-wrap: anywhere;                     /* a long defect note must wrap, not widen the card */
}
.thc-mangel__extra {
  margin: 8px 0 0; font-size: 12px; line-height: 16px; color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.thc-mangel__unreadable { margin: 0; font-size: 13px; line-height: 19px; color: var(--text-secondary); }
.thc-mangel__photos { margin-top: 10px; }
.thc-mangel__photos-label {
  margin: 0 0 4px; font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-secondary);
}
/* The thumbnail is a button, so it needs the shared focus ring — it opens the lightbox. */
.thc-media__thumb {
  display: block; padding: 0; border: 1px solid var(--border); border-radius: 3px;
  background: none; cursor: pointer; overflow: hidden; line-height: 0;
}
/* CONTAIN for the same reason: `.thc-mangel__photos .thc-media__thumb` pins these to 4/3 (below),
   so a portrait or 16/9 Prüffoto was cropped into. An inspector uses the thumbnail to recognise
   which photo they took; cropping the subject out defeats it. */
.thc-media__thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.thc-media__thumb:hover { border-color: var(--text-muted); }
.thc-media__thumb:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.thc-obligations__heading {
  margin: 0 0 6px; font-size: 13px; line-height: 19px; font-weight: 700; color: var(--text-primary);
}
.thc-obligations__list { margin: 0; }
.thc-obligations__row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 4px 16px; padding: 6px 0; border-bottom: 1px solid var(--border-soft);
}
.thc-obligations__row:last-child { border-bottom: 0; }
.thc-obligations__cell { min-width: 0; }
.thc-obligations__cell dt {
  margin: 0; font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted);
}
.thc-obligations__cell dd {
  margin: 0; font-size: 13px; line-height: 19px; color: var(--text-body); overflow-wrap: anywhere;
}

.thc-hinweise-read { margin: 0; display: flex; flex-direction: column; gap: 10px; }
.thc-hinweise-read__pair { padding-bottom: 10px; border-bottom: 1px solid var(--border-soft); }
.thc-hinweise-read__pair:last-child { padding-bottom: 0; border-bottom: 0; }
.thc-hinweise-read__question {
  margin: 0 0 2px; font-size: 13px; line-height: 19px; font-weight: 600; color: var(--text-primary);
}
.thc-hinweise-read__answer {
  margin: 0; font-size: 13px; line-height: 19px; color: var(--text-body); overflow-wrap: anywhere;
}
.thc-hinweise-read__status { margin-left: 6px; font-size: 12px; color: var(--text-muted); }

/* Per-Mangel evidence previews: „large enough to recognise the defect at a glance" (frozen §16),
   but several to a row rather than D2's two full-width building slots. */
.thc-mangel__photos .thc-media { grid-template-columns: repeat(auto-fill, minmax(120px, 160px)); margin: 0; }
.thc-mangel__photos .thc-media__thumb { aspect-ratio: 4 / 3; }

@media (max-width: 767px) {
  /* The status chip drops under the title rather than being squeezed against it — the 375px
     collision M2.7 found in D2's status cell, prevented here rather than discovered later. */
  .thc-point__status { margin-left: 0; }
  .thc-mangel__fields { grid-template-columns: minmax(0, 1fr); }
  .thc-obligations__row { grid-template-columns: minmax(0, 1fr); }
}

/* ───────────────────  E3 inspection editor shell (M2.8 slice 2)  ──────────── */
.thc-entry-edit { display: flex; flex-direction: column; gap: 20px; }

/* M2.8 item 14 — the inspection metadata restored to the editor: Prüfdatum, Prüfung-Version and
   Dokument. A row on desktop and stacked below it, because the three are short and independent and a
   full-width stack for them would push the inspection itself below the fold. */
/* ── INSPECTION METADATA ROW (owner ruling, 12 August 2026, item 3) ───────────────────────────
   Three short, inspection-wide facts that sit in the page header above the category tabs.

   EXPLICIT WIDTHS, NOT `1fr`. The previous `repeat(auto-fit, minmax(180px, 1fr))` stretched all
   three across the whole editor column, so a two-character „v3" was given the same ~280px a full
   address would get. Width is a claim about how much a field can hold, and these hold ten
   characters, three and five. The ruling's shape — Prüfdatum moderate, Version and Dokument compact
   — is stated here as three measurements rather than left to a fraction of whatever is available.

   `justify-content: start` is the half that stops the stretch: without it the columns are placed but
   the TRACK still fills the row. */
.thc-entry-meta {
  display: grid; gap: 8px 12px;
  grid-template-columns: 148px 104px 124px;
  justify-content: start;
  margin: 0 0 12px;
}

/* LESS VISUAL WEIGHT, scoped to this row only — the same controls elsewhere in the editor keep the
   size they were designed at. A header fact is read, not filled in repeatedly. */
.thc-entry-meta .thc-formfield__control,
.thc-entry-meta .thc-formfield__readonly {
  height: 30px; min-height: 30px; padding: 4px 8px; font-size: 12px;
}
.thc-entry-meta .thc-formfield__label { font-size: 10px; line-height: 13px; }

/* STACKING, not squeezing. Below the point where three fixed columns plus their gaps stop fitting
   the narrowed editor, they become full-width rows — the fixed widths would otherwise overflow the
   column rather than reflow inside it. */
@media (max-width: 767px) {
  .thc-entry-meta { grid-template-columns: minmax(0, 1fr); }
}

/* The technical entry id in Building → Begehungen (owner ruling, 12 August 2026). It replaces
   `.thc-page__lead--meta`, which styled the editor lead this ruling removed — a rule kept alive for a
   class nothing renders is a rule nobody can tell is dead.

   TABULAR FIGURES so ids of different lengths line up down the column, and MUTED so the year stays
   the thing the eye lands on. It qualifies the year; it does not compete with it. */
.thc-list-id {
  margin-left: 6px; font-size: 12px; color: var(--text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* A definition field role 5 reads but does not author (item 8): the point's title and the Hinweise
   pair. Deliberately NOT `.thc-formfield__readonly` — that styles a disabled-looking CONTROL, and
   these are plain text with a hidden input carrying the value back unchanged, so they should read as
   the record rather than as an input somebody switched off. */
.thc-point__title-static,
.thc-formfield__static {
  margin: 0; padding: 9px 0;
  font-size: 13.5px; line-height: 20px; color: var(--text-primary);
  /* These render STORED text, so they wrap like every other surface that does. The read views
     (`.thc-deflist__value`, `.thc-mangel__value`, `.thc-obligations__cell dd`,
     `.thc-hinweise-read__answer`) already carried this rule; the editor's own static fields did not,
     so one long unbroken token — a URL in a Hinweis answer, a run-on point title — widened the
     editor instead of wrapping inside it. Same value, same reason, no new mechanism. */
  overflow-wrap: anywhere;
}

/* The ten-tab strip. It WRAPS rather than scrolls: a horizontally scrolled tablist hides sections
   behind a gesture, and the frozen responsive spec asks for „tabs wrap/scroll preserving section". */
.thc-tabs { margin: 0 0 4px; }
.thc-tabs__list {
  display: flex; flex-wrap: wrap; gap: 4px;
  padding: 0 0 8px; border-bottom: 1px solid var(--border);
}
.thc-tabs__tab {
  padding: 6px 12px; border: 1px solid transparent; border-radius: 3px;
  background: none; color: var(--text-muted); cursor: pointer;
  font-size: 12px; line-height: 16px; font-weight: 600;
  letter-spacing: .02em; white-space: nowrap;
}
.thc-tabs__tab:hover { color: var(--text-body); background: var(--hover); }
.thc-tabs__tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* Selection is carried by tint AND weight AND a border — never colour alone (DESIGN_SYSTEM §2.4). */
.thc-tabs__tab[aria-selected="true"] {
  color: var(--text-on-accent); background: var(--brand); border-color: var(--brand);
}
.thc-tabs__panel { padding-top: 4px; }

.thc-point--edit { padding: 14px 0; }
.thc-point__titlelabel {
  font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted);
}
.thc-point__title-input { max-width: 520px; margin-bottom: 10px; }

/* The result is a real fieldset: three radios under one legend is what a radio group IS. */
.thc-point__result { margin: 0 0 10px; padding: 0; border: 0; }
.thc-point__resultlegend {
  padding: 0; margin: 0 0 4px; font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted);
}
/* ITEM 5 — the result is the single most-read control on the screen and it was the weakest: a 13px
   label beside a default radio, three of them identical apart from a dot. Two things change.

   PROMINENCE: a larger control and a clearer unselected outline, so an unanswered point reads as
   unanswered rather than as a faint row.

   DISTINCTION: the SELECTED state is tinted by meaning — Mangel red, OK green, N.V. neutral — using
   the existing semantic tokens, not a new palette. „N.V." and „OK" previously differed only by which
   dot was filled, which is exactly the pair a reader scanning a long catalogue must tell apart.
   Colour is not the only signal: the label text and the checked dot both remain. */
.thc-radio { display: inline-flex; align-items: center; gap: 7px; margin-right: 14px; }
.thc-radio__input { margin: 0; width: 16px; height: 16px; accent-color: var(--text-secondary); }
.thc-radio__input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-radio__label { font-size: 13.5px; line-height: 19px; color: var(--text-body); cursor: pointer; }

/* The point's own result group: each option becomes a hit target with a visible resting outline. */
.thc-point__result .thc-radio {
  gap: 6px; margin-right: 8px; padding: 3px 9px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
}
.thc-point__result .thc-radio:hover { border-color: var(--border-strong, var(--border)); }
.thc-point__result .thc-radio__label {
  font-weight: 500;
  /* Match the 16px input box so the label's line box hugs its glyphs. With the base 19px
     line-height the element centered on the input, but the glyphs sit low in a 19px box, so the
     circle read as sitting HIGH against the text. A computed-style harness confirmed the element
     centers already coincided — only the optical line box was off. */
  line-height: 16px;
}
.thc-point__result .thc-radio:has(.thc-radio__input:checked) { border-width: 1.5px; padding: 2.5px 8.5px; }
.thc-point__result .thc-radio:has(.thc-radio__input[value="X"]:checked) {
  background: var(--red-bg); border-color: var(--red-border);
}
.thc-point__result .thc-radio:has(.thc-radio__input[value="OK"]:checked) {
  background: var(--green-bg, var(--chip-bg)); border-color: var(--green-border, var(--border));
}
.thc-point__result .thc-radio:has(.thc-radio__input:focus-visible) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* Mangel-layout fix: the wrapper that `hidden` toggles on and off exists so the JS has ONE element
   to flip per point — it carries no visual identity of its own. `:not([hidden])` matters: the native
   `[hidden] { display: none }` UA rule and this class rule are equal specificity, so without the
   guard, `display: contents` here (an author rule, applied after the UA stylesheet) would WIN and
   the „hidden" block would render anyway. When visible, `display: contents` makes the wrapper
   transparent to layout — `.thc-point__mangel`, the template and „Weiteren Mangel hinzufügen" sit in
   the flow exactly as they did before this wrapper existed, so introducing it changes no spacing. */
.thc-point__mangel-block:not([hidden]) { display: contents; }
.thc-point__unanswered {
  display: inline-block; margin: 4px 0 0; padding: 2px 8px; border-radius: 3px;
  background: var(--surface-sub); color: var(--text-muted); font-size: 12px; line-height: 16px;
}

/* NO `border` SHORTHAND HERE. `.thc-mangel` already sets the border and widens the LEFT edge to
   3px, and the priority tints colour that edge. A shorthand in this modifier — which is declared
   after them — resets both width and colour, so every editable card rendered a 1px neutral edge
   while carrying a `--danger-strong` class that did nothing. The class was present and inert, which
   is exactly what a test asserting class presence cannot see. Found in the Phase-1 browser sweep by
   comparing COMPUTED styles. Padding and radius are the base's too, so only the layout the edit
   variant genuinely needs is stated. */
.thc-mangel--edit {
  /* A native <legend> straddles the fieldset's top border — the border is drawn through its
     vertical centre, so „Mangel N" appeared to sit half OUTSIDE the card. Take the legend out of
     flow (position:absolute) rather than floating it: a float:100% legend shoves the
     `.thc-mangel__fields` GRID sibling sideways until it overflows. Absolute removes it from flow
     entirely, so the border stays continuous and the grid keeps full width; the extra top padding
     is the room the header row now occupies. Verified in a browser harness against the real CSS. */
  position: relative; padding: 30px 12px 10px;
}
.thc-mangel__legend {
  position: absolute; top: 9px; left: 12px; margin: 0;
  padding: 0; font-size: 12px; line-height: 16px; font-weight: 600; color: var(--text-secondary);
}
.thc-mangel__photos-note { margin: 8px 0 0; font-size: 12px; line-height: 16px; color: var(--text-secondary); }

.thc-hinweis {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--border-soft);
}
.thc-hinweis:last-child { border-bottom: 0; }

.thc-catalogue-read__empty { margin: 0; font-size: 13px; line-height: 19px; color: var(--text-muted); }

@media (max-width: 767px) {
  .thc-hinweis { grid-template-columns: minmax(0, 1fr); }
  .thc-point__title-input { max-width: none; }
}

/* ───────────────────  Per-Mangel photos: E8 (M2.8 slice 4)  ───────────────── */
/* ── PHOTO REGION (owner item 3) ──────────────────────────────────────────────────────────────
   Compacted, and made a visible drop target, without touching upload, delete, ordering or the
   counter — those are behaviour and this is the box they sit in. */
.thc-photos {
  margin-top: 8px;
  /* The dashed edge is the DROP TARGET made visible, and it is drawn permanently rather than only
     while dragging: a target that appears once you are already dragging is one nobody discovers.
     A transparent 1px border in the resting state would shift the layout by 2px the moment it
     turned solid, so the border exists at all times and only its colour changes. */
  border: 1px dashed transparent; border-radius: 5px;   /* frozen radius/sm — 6px is not in the set */
  /* 4px, not 6: the dashed edge needs enough room not to sit on the thumbnails, and every pixel
     beyond that is vertical height charged to every Mangel row in the inspection. The negative
     margins keep the content optically aligned with the fields above it. */
  margin-left: -4px; margin-right: -4px; padding: 4px;
  transition: border-color .12s ease, background-color .12s ease;
}
/* Discoverable at rest — a faint edge that reads as „things can be put here". */
.thc-mangel .thc-photos { border-color: var(--border-soft); }
/* …and unmistakable while a file is over it. Set by the delegated `dragenter`/`dragover` handlers,
   and ONLY when the row can actually accept the drop, so the highlight never promises an upload the
   cap or an in-flight request will refuse. */
.thc-photos[data-themis-photo-dragover] {
  border-color: var(--focus-ring); background: var(--hover);
}

.thc-photos__count {
  margin: 0 0 4px; font-size: 11px; line-height: 14px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted);
}
.thc-photos__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }

/* NO RESERVED HEIGHT FOR A STATUS NOBODY IS READING (owner item 3). The upload status is an
   `aria-live` region that is EMPTY until something happens, but an empty `<p>` in a flex row still
   claims its line-height plus the row's gap — dead vertical space on every Mangel row in the
   inspection, permanently, for a sentence shown for two seconds after an upload.

   `:empty` collapses it and nothing else changes: the element stays in the DOM and stays the same
   live region, so the announcement still works the instant text is written into it. Removing the
   node would have broken that; hiding it while empty does not. */
.thc-photos__actions [data-themis-photo-status]:empty { display: none; }
.thc-photos__actions .thc-form__note { margin: 0; }

/* THE EDIT GALLERY NEVER GOT THE READ CARD'S COMPACT TREATMENT. `.thc-mangel__photos .thc-media`
   (below) has carried `minmax(120px, 160px)` + a fixed `4 / 3` thumb ratio since M2.7; this editable
   gallery lived under plain `.thc-photos` and so fell through to the BASE `.thc-media` rule — D2's
   two-per-row, unbounded-height cover strip, meant for one or two building photos, not up to three
   per Mangel row. With no aspect-ratio on the thumb, an oversized source image (or, as here, a
   BROKEN one with only alt text to size against) had nothing to cap it, and the card grew to fit
   whatever the image happened to be — „dominates the Mangel card" is that, not a styling choice. The
   fix reuses the identical values the read card already proved, rather than inventing new ones. */
.thc-photos .thc-media { grid-template-columns: repeat(auto-fill, minmax(104px, 140px)); gap: 8px; margin: 0; }
.thc-photos .thc-media__thumb { aspect-ratio: 4 / 3; }
/* The remove control sits directly under its own thumbnail, not the gallery's block flow — so three
   photos read as three aligned columns instead of two thumbnails and an orphaned third button. */
.thc-photos .thc-media__item { display: flex; flex-direction: column; gap: 4px; }
.thc-photos .thc-media__item .thc-btn { align-self: stretch; text-align: center; }

/* „Foto hinzufügen" is a LABEL driving a visually-hidden file input — the only way to style a file
   picker consistently. It therefore has to carry the button's focus ring itself, because the ring
   belongs to the input the reader actually focuses. */
.thc-photos__add { cursor: pointer; margin: 0; }
.thc-photos__add[data-themis-disabled="true"] {
  opacity: .55; cursor: not-allowed; pointer-events: none;
}
.thc-photos__add:focus-within,
.thc-photo-input:focus-visible + .thc-photos__add { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* THE LABEL PRECEDES THE INPUT in the rendered markup, so `input + label` (input first) and
   `label:has(input)` (input nested) both match nothing — the file control is `.thc-sr-only`, and a
   focused control with no visible ring is a focused control the reader cannot find. Mode-A finding
   7. The general sibling combinator inside `:has()` is what the actual order needs. */
[data-themis-photo-add]:has(~ [data-themis-photo-input]:focus-visible),
[data-themis-photo-input]:focus-visible + .thc-photos__add,
[data-themis-photo-add]:has([data-themis-photo-input]:focus-visible) {
  outline: 2px solid var(--focus-ring); outline-offset: 2px;
}
.thc-photos__overcap { color: var(--yellow-fg); }

/* ───────────────────  Autosave indicator: E4 (M2.8 slice 5)  ─────────────── */
.thc-form__actions--sticky { flex-wrap: wrap; align-items: center; gap: 12px; }

/* State is carried by the WORDS first; the tint is an additional signal, never the only one
   (DESIGN_SYSTEM §2.4 — never colour alone). */
.thc-autosave {
  margin: 0; font-size: 12px; line-height: 16px; color: var(--text-muted);
}
[data-themis-autosave-state="dirty"] .thc-autosave { color: var(--yellow-fg); }
[data-themis-autosave-state="saving"] .thc-autosave { color: var(--text-body); }
[data-themis-autosave-state="saved"] .thc-autosave { color: var(--green-fg); }
[data-themis-autosave-state="failed"] .thc-autosave { color: var(--red-fg); font-weight: 600; }

/* ─────────────────  QC tool window: E5 (M2.8 slice 6)  ────────────────────
   DOCKED, NOT FLOATING. Frozen §3.6 rules out three treatments by name — floating/draggable, fixed
   non-resizable, full-screen modal — so the desktop panel is a GRID COLUMN. The editor column is
   narrowed by exactly the panel's width, which is what „never covers the field being corrected"
   means when it is a layout property rather than a z-index that happens to win today.
   ≤1024 is the one place it overlays, because a 280px column beside a 744px editor leaves neither
   usable; there it is a drawer with a scrim, and the dock returns to one column.                  */

.thc-qc-dock {
  display: block;
  /* DECLARED HERE, not only as a `var()` fallback: the documented default width is a property of
     the design, and the script overrides it per session rather than defining it. */
  --thc-qc-w: 360px;
}

@media (min-width: 1025px) {
  .thc-qc-dock[data-themis-qc-dock] {
    display: grid;
    /* `minmax(0, 1fr)` and not `1fr`: a grid item's automatic minimum size is its CONTENT, so a wide
       tab strip or a long German label would push the editor column past the track and the panel off
       the viewport. This is the whole overflow story on this screen. */
    grid-template-columns: minmax(0, 1fr) var(--thc-qc-w, 360px);
    gap: 24px;
    align-items: start;
  }
  .thc-qc-dock[data-themis-qc-dock]:has(.thc-qc[data-themis-qc-collapsed]) {
    grid-template-columns: minmax(0, 1fr) 48px;
  }
}
.thc-qc-dock__main { min-width: 0; }

.thc-qc {
  position: sticky;
  top: 16px;
  display: flex;
  max-height: calc(100dvh - 32px);
  border: 1px solid var(--divider);
  border-radius: 8px;
  background: var(--surface);
  overflow: hidden;
}
.thc-qc__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

/* THE GRIP is the left edge, and it is `pointer-events` only. Everything it can do is also reachable
   from the two discrete width buttons in the footer, so a keyboard user is never asked to drag. */
.thc-qc__grip {
  flex: 0 0 6px;
  cursor: col-resize;
  background: transparent;
  border-right: 1px solid var(--divider);
}
.thc-qc__grip:hover, .thc-qc[data-themis-qc-resizing] .thc-qc__grip { background: var(--divider); }

.thc-qc__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 16px;
  background: var(--surface-sub);
  border-bottom: 1px solid var(--divider);
}
.thc-qc__title { margin: 0; font-size: 1rem; font-weight: 700; }
.thc-qc__subtitle { margin: 2px 0 0; font-size: .8125rem; color: var(--text-muted); }
.thc-qc__collapse, .thc-qc__retry, .thc-qc__run, .thc-qc__reset, .thc-qc__width > button {
  border: 1px solid var(--divider); border-radius: 5px;
  background: var(--surface); color: inherit; cursor: pointer;
  padding: 4px 10px; font: inherit; font-size: .8125rem;
}

.thc-qc__summary { padding: 16px; border-bottom: 1px solid var(--divider); }
.thc-qc__pills { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; }
.thc-qc__pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px; border-radius: 999px;
  font-size: .8125rem; font-weight: 600; border: 1px solid transparent;
}
.thc-qc__pill--error   { background: var(--red-bg);    color: var(--red-fg);    border-color: var(--red-border); }
.thc-qc__pill--warning { background: var(--yellow-bg); color: var(--yellow-fg); border-color: var(--yellow-border); }
/* Frozen §11.1: muted at zero. A red pill reading „0" is a warning about nothing. */
.thc-qc__pill[data-themis-qc-zero="true"] {
  background: var(--surface-inset); color: var(--text-muted); border-color: var(--divider);
}
.thc-qc__note { margin: 0; font-size: .8125rem; color: var(--text-muted); }
.thc-qc[data-themis-qc-state="failed"] .thc-qc__note { color: var(--red-fg); font-weight: 600; }
.thc-qc[data-themis-qc-state="empty"]  .thc-qc__note { color: var(--green-fg); }
.thc-qc__blocking-note {
  margin: 8px 0 0; font-size: .8125rem; color: var(--text-muted);
}
.thc-qc__retry { margin-top: 8px; }
/* The manual run (owner ruling, 12 August 2026, item 1). Full width of the summary block so it reads
   as „act on this panel" rather than as one more inline chip among the count pills. */
.thc-qc__run { margin-top: 10px; width: 100%; }

.thc-qc__scroll { flex: 1 1 auto; overflow-y: auto; }
.thc-qc__group { padding: 12px 0 0; }
.thc-qc__group-title {
  margin: 0 0 4px; padding: 0 16px;
  font-size: .6875rem; letter-spacing: .06em; font-weight: 700;
  color: var(--text-muted);
}
.thc-qc__list { list-style: none; margin: 0; padding: 0; }
.thc-qc__item + .thc-qc__item { border-top: 1px solid var(--divider); }

/* COMPACT WORK-ITEM ROWS on transparent surfaces with a hover tint — frozen §11.1 forbids the
   full-width saturated banner this replaces. Severity is carried by the GLYPH and the group heading,
   never by colour alone. */
.thc-qc__issue {
  display: flex; gap: 12px; align-items: flex-start;
  width: 100%; text-align: left;
  padding: 12px 16px;
  border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer;
}
.thc-qc__issue--static { cursor: default; }
.thc-qc__issue:not(.thc-qc__issue--static):hover { background: var(--surface-inset); }
.thc-qc__glyph { flex: 0 0 18px; font-size: 1rem; line-height: 1.25; font-weight: 700; }
.thc-qc__item--error   .thc-qc__glyph { color: var(--red-fg); }
.thc-qc__item--warning .thc-qc__glyph { color: var(--yellow-fg); }
.thc-qc__issue-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.thc-qc__issue-label { font-size: .875rem; font-weight: 600; }
.thc-qc__issue-caption, .thc-qc__issue-note {
  font-size: .75rem; color: var(--text-muted);
}
.thc-qc__issue-note { font-style: italic; }
.thc-qc__empty { margin: 0; padding: 16px; font-size: .8125rem; color: var(--green-fg); }

.thc-qc__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--divider); background: var(--surface-sub);
}
.thc-qc__width { display: flex; gap: 4px; }

/* THE RAIL is the collapsed state of this same window — the list is hidden, never destroyed, so
   expanding restores it without another request. */
.thc-qc__rail {
  display: none;
  writing-mode: vertical-rl;
  padding: 16px 0;
  border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer;
  gap: 12px; align-items: center;
}
.thc-qc__rail-label { font-size: .6875rem; letter-spacing: .08em; font-weight: 700; }
.thc-qc__rail-counts { font-size: .75rem; font-weight: 600; color: var(--text-muted); }
.thc-qc[data-themis-qc-collapsed] .thc-qc__body,
.thc-qc[data-themis-qc-collapsed] .thc-qc__grip { display: none; }
.thc-qc[data-themis-qc-collapsed] .thc-qc__rail { display: flex; }

/* Owner ruling, 12 August 2026, item 1: the standalone Qualitätsprüfung button is gone from the top
   area wherever the panel is DOCKED and already on screen. Above 1024 the panel is a column beside
   the editor and the button restated a window the reader can see; below it, the panel is a drawer
   that starts closed and `.thc-qc__rail` is `display: none !important`, so this button is the only
   way in and removing it would strand the panel the same ruling says to keep.

   BOTH CLASSES IN THE SELECTOR, deliberately. `.thc-page__actions { display: flex }` is declared at
   the same (0,1,0) weight LATER in this sheet, so a lone modifier loses on source order and the
   button stays visible while the rule sits here looking correct — measured in a browser, which is
   the second time this sheet has hidden a dead declaration behind equal specificity. Doubling the
   class makes it (0,2,0) and independent of where either rule sits. */
.thc-page__actions.thc-page__actions--qc-drawer { display: none; }
@media (max-width: 1024px) {
  .thc-page__actions.thc-page__actions--qc-drawer { display: flex; }
}

/* ≤1024 — RIGHT DRAWER OVERLAY WITH A SCRIM (frozen §3.6). Closed by default at this width: an
   overlay that covers the editor without being asked for is the obstruction the docked design
   exists to avoid. */
@media (max-width: 1024px) {
  .thc-qc {
    position: fixed; inset: 0 0 0 auto;
    width: min(360px, 100vw); max-height: 100dvh; height: 100dvh;
    border-radius: 0; border-width: 0 0 0 1px;
    transform: translateX(100%);
    transition: transform 240ms ease-in-out;
    z-index: 60;
  }
  .thc-qc[data-themis-qc-open] { transform: translateX(0); }
  .thc-qc:not([data-themis-qc-open]) { visibility: hidden; }
  /* The rail and the grip are desktop affordances: at this width the drawer is opened and closed by
     the editor's own button, and there is nothing beside it to resize against. */
  .thc-qc__rail, .thc-qc__grip, .thc-qc__width { display: none !important; }
  .thc-qc[data-themis-qc-collapsed] .thc-qc__body { display: flex; }
  .thc-qc__scrim {
    position: fixed; inset: 0; z-index: 59;
    background: rgba(18, 24, 32, .44);
  }
}

@media (prefers-reduced-motion: reduce) {
  .thc-qc { transition: none; }
}

/* Rendered by the editor header and the panel; declared here so the class contract holds. */
.thc-page__actions { display: flex; gap: 8px; margin: 12px 0 0; }
.thc-qc__titles { min-width: 0; }
.thc-qc__item--unresolved .thc-qc__issue { opacity: .92; }

/* Per-tab QC counts (frozen Component §7e). Text, not colour: the glyph is the severity and the
   `aria-label` carries it for a reader who cannot see either. Hidden until QC has actually spoken. */
.thc-tabs__counts { margin-left: 6px; font-size: .6875rem; font-weight: 700; letter-spacing: .02em; }

/* The editable Mangel card carries the SAME priority signal as the read-only one (Mode-A finding 8):
   the tinted left edge above, plus the symbol and full label in the legend — so severity survives
   greyscale, colour blindness and a screen reader, and never depends on the tint alone. */
.thc-mangel__priority { margin-left: 10px; font-size: .75rem; font-weight: 600; color: var(--text-secondary); }
.thc-mangel__priority-symbol { margin-right: 4px; font-weight: 700; }
.thc-mangel--warning .thc-mangel__priority-symbol { color: var(--yellow-fg); }
.thc-mangel--danger .thc-mangel__priority-symbol,
.thc-mangel--danger-strong .thc-mangel__priority-symbol { color: var(--red-fg); }

/* ══════════════════════════════════════════════════════════════════════════════════════════════════
   M2.9 — Mission-Control dashboard (frozen SCREEN_SPECIFICATIONS §C, §C.1, §C.1a; prototype
   Dashboard.dc.html). Every value below is the prototype's own; every colour is a token, so dark is a
   pure swap and no surface, border or text is hard-coded (IMPLEMENTATION_HANDOVER §7).
   ══════════════════════════════════════════════════════════════════════════════════════════════════ */

.thc-dash { display: flex; flex-direction: column; gap: 14px; max-width: 1240px; }

/* ── Context line + quick-actions toolbar (§C.1a: small secondary buttons, never large CTAs) ─────── */
.thc-dash__context { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 2px; }
.thc-dash__caption { font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.thc-dash__quick { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
.thc-dash__quick .thc-btn { height: 32px; }

/* ── KPI cards ───────────────────────────────────────────────────────────────────────────────────
   `all` is the resting state and shows no badge (DR-019). The active treatment is a --page-bg fill
   plus a --brand border doubled by an inset ring, so the pressed state survives greyscale and is not
   carried by hue alone. */
.thc-dash__kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.thc-dash__kpis--personal { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.thc-kpi {
  display: block; width: 100%; text-align: left; font-family: inherit;
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px;
  padding: 12px 14px; cursor: pointer;
}
.thc-kpi--static { cursor: default; }
.thc-kpi:not(.thc-kpi--static):hover { box-shadow: 0 2px 8px rgba(16, 41, 58, .10); }
.thc-kpi:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-kpi.is-active { background: var(--page-bg); border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.thc-kpi__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.thc-kpi__label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.thc-kpi__badge {
  font-size: 11px; font-weight: 700; white-space: nowrap;
  color: var(--brand-ink); background: var(--info-bg); border-radius: 999px; padding: 2px 8px;
}
.thc-kpi__value { display: block; font-size: 23px; font-weight: 700; letter-spacing: -.02em; color: var(--text-primary); }

/* ── Operational alerts strip (§C.1a: compact chips, never large cards) ──────────────────────────── */
.thc-dash__alerts { display: flex; flex-wrap: wrap; gap: 8px; }
.thc-alert-chip {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; border-radius: 8px; padding: 6px 11px;
}
.thc-alert-chip__dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; flex-shrink: 0; }
.thc-alert-chip--warning { background: var(--yellow-bg); color: var(--yellow-fg); }
.thc-alert-chip--error { background: var(--red-bg); color: var(--red-fg); }

/* ── Shared panel shell for the queue and the map region ─────────────────────────────────────────── */
.thc-panel { background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px; overflow: hidden; }
.thc-panel__head {
  padding: 11px 16px; border-bottom: 1px solid var(--divider);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.thc-panel__title { font-size: 14px; font-weight: 700; color: var(--text-primary); margin: 0; }
.thc-panel__scope { font-size: 12px; color: var(--text-muted); }
.thc-panel__tools { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 1 1 0; justify-content: flex-end; min-width: 0; }

/* ── Operative Warteschlange ──────────────────────────────────────────────────────────────────────
   40px rows (DR-006) and a PINNED action column (DR-008), so the call-to-action never sits behind a
   horizontal scrollbar at the widths where the table has to scroll. */
.thc-queue__scroll { overflow-x: auto; }
.thc-queue__table { width: 100%; border-collapse: collapse; min-width: 760px; }
.thc-queue__table thead tr { background: var(--surface-sub); }
.thc-queue__table th {
  text-align: left; font-size: 11px; font-weight: 700; color: var(--text-muted);
  padding: 7px 14px; border-bottom: 1px solid var(--divider); white-space: nowrap;
}
.thc-queue__table td { padding: 0 14px; height: 40px; border-bottom: 1px solid var(--divider); }
.thc-queue__object { font-size: 13.5px; font-weight: 600; color: var(--link); white-space: nowrap; text-decoration: none; }
.thc-queue__object:hover { color: var(--link-hover); text-decoration: underline; }
.thc-queue__object:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-queue__assignee { font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
.thc-queue__time { font-size: 12.5px; color: var(--text-muted); white-space: nowrap; }
.thc-queue__action-cell { text-align: right; position: sticky; right: 0; background: var(--surface); }
.thc-queue__table th.thc-queue__action-cell { background: var(--surface-sub); }

/* ── Map region + synchronized object list ───────────────────────────────────────────────────────── */
.thc-map__search {
  height: 34px; flex: 1 1 0; min-width: 140px; max-width: 280px;   /* fluid, never a fixed 300px (TH-UI-023) */
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface-inset);
  display: flex; align-items: center; gap: 8px; padding: 0 11px;
}
.thc-map__search:focus-within { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.thc-map__search-icon { color: var(--text-faint); flex-shrink: 0; display: inline-flex; }
.thc-map__search-input {
  flex: 1 1 0; min-width: 0; border: 0; background: none; outline: none;
  font-size: 13px; color: var(--text-body); font-family: inherit;
}
.thc-map__count {
  font-size: 12px; font-weight: 600; color: var(--text-secondary);
  background: var(--chip-bg); border: 1px solid var(--border); border-radius: 999px;
  padding: 5px 11px; white-space: nowrap;
}
/* „Nicht zugewiesen" map filter (owner rulings, 17 September 2026) — a toggle beside the search, not a
   KPI card. The swatch is the reserved amber of its markers, so the control names its pins by colour
   AND by text; the pressed state is carried by aria-pressed and mirrored by .is-active. */
.thc-map__filter {
  height: 34px; display: inline-flex; align-items: center; gap: 7px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-body); white-space: nowrap; cursor: pointer;
}
.thc-map__filter:hover { background: var(--hover); }
.thc-map__filter:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.thc-map__filter.is-active {
  background: var(--page-bg); border-color: var(--map-marker-unassigned);
  box-shadow: inset 0 0 0 1px var(--map-marker-unassigned);
}
.thc-map__filter-swatch {
  width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
  background: var(--map-marker-unassigned); box-shadow: 0 0 0 2px var(--map-marker-halo), 0 0 0 3px var(--border);
}
.thc-map__filter-count { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text-muted); }

.thc-map__body { display: flex; align-items: stretch; }
.thc-map__canvas { flex: 3.2 1 0; position: relative; min-height: 520px; background: var(--map-bg); overflow: hidden; }
.thc-map__canvas > .thc-map__surface { position: absolute; inset: 0; width: 100%; height: 100%; }

/* Dark: a dark basemap is applied by the provider style; where the provider cannot deliver one the
   frozen fallback is a controlled LIGHT surface inside a --map-frame border — never a CSS inversion,
   which would render the cartography and its labels in false colours (HANDOVER §7). */
.thc-map__canvas.is-framed { border: 1px solid var(--map-frame); }

.thc-map__overlay {
  position: absolute; inset: 0; z-index: 4;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 24px; background: var(--surface-sub);
}
.thc-map__overlay--loading { background: var(--info-bg); }
.thc-map__overlay-title { font-size: 15px; font-weight: 700; color: var(--text-primary); }
.thc-map__overlay-msg { font-size: 13px; color: var(--text-muted); margin-top: 5px; max-width: 340px; }
.thc-map__overlay .thc-btn { margin-top: 12px; }

/* Partial failure: the map still works, so the note is inline and never covers it. */
.thc-map__note {
  position: absolute; left: 12px; right: 12px; top: 12px; z-index: 3;
  font-size: 12px; font-weight: 600; border-radius: 8px; padding: 7px 11px;
  background: var(--yellow-bg); color: var(--yellow-fg);
}

.thc-map__callout {
  position: absolute; left: 12px; bottom: 12px; z-index: 3; max-width: 70%;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 13px; box-shadow: 0 3px 10px rgba(16, 41, 58, .14);
}
.thc-map__callout-name { font-size: 13px; font-weight: 700; color: var(--text-primary); }
.thc-map__callout-addr { font-size: 12px; color: var(--text-muted); }

.thc-map__list { width: 312px; flex-shrink: 0; border-left: 1px solid var(--divider); display: flex; flex-direction: column; max-height: 520px; }
.thc-map__list-head {
  padding: 9px 14px; border-bottom: 1px solid var(--divider); flex-shrink: 0;
  font-size: 11px; font-weight: 700; letter-spacing: .05em; color: var(--text-muted);
}
.thc-map__list-scroll { overflow-y: auto; flex: 1 1 0; }
.thc-map__row {
  display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left;
  padding: 10px 12px; border-bottom: 1px solid var(--divider);
  border-left: 3px solid transparent; background: transparent;
  font-family: inherit; cursor: pointer;
}
.thc-map__row:hover { background: var(--hover); }
.thc-map__row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.thc-map__row.is-selected { border-left-color: var(--brand); background: var(--surface-sub); }
.thc-map__row-main { display: block; flex: 1 1 0; min-width: 0; }
.thc-map__row-name { display: block; font-size: 13.5px; font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.thc-map__row-addr { display: block; font-size: 12px; color: var(--text-muted); }
.thc-map__row-meta { display: block; font-size: 11.5px; color: var(--text-muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.thc-map__row-side { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
.thc-map__row-time { font-size: 11px; color: var(--text-faint); }
.thc-map__row-action { font-size: 12px; font-weight: 600; color: var(--link); }

/* ── Prüfjahr progress ───────────────────────────────────────────────────────────────────────────── */
.thc-year {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px;
  padding: 12px 18px; display: flex; gap: 26px; align-items: center; flex-wrap: wrap;
}
.thc-year__title { font-size: 13px; font-weight: 700; color: var(--text-primary); flex-shrink: 0; }
.thc-progress { flex: 1 1 0; min-width: 220px; }
.thc-progress__head { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; margin-bottom: 5px; }
.thc-progress__label { color: var(--text-secondary); }
.thc-progress__value { font-weight: 700; color: var(--text-primary); }
.thc-progress__track { height: 7px; background: var(--chip-bg); border-radius: 999px; overflow: hidden; }
.thc-progress__fill { height: 100%; border-radius: 999px; }
.thc-progress__fill--done { background: var(--green-fg); }
.thc-progress__fill--progress { background: var(--brand); }

/* ── Read-only „In der App" section (STATE_LIBRARY §1.5b) ────────────────────────────────────────
   Muted by design: these rows are the mobile app's, and the section exists to say so rather than to
   offer work. It carries a lock label and no action of any kind. */
.thc-inapp__title { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.thc-inapp__qualifier { font-size: 12px; font-weight: 400; color: var(--text-muted); }
.thc-inapp__note { font-size: 12.5px; color: var(--text-muted); padding: 11px 16px 0; margin: 0; }
.thc-inapp__list { list-style: none; margin: 0; padding: 8px 0 4px; }
.thc-inapp__row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 16px; border-top: 1px solid var(--divider);
}
.thc-inapp__row-main { display: block; flex: 1 1 0; min-width: 0; }
.thc-inapp__row-name { display: block; font-size: 13.5px; font-weight: 600; color: var(--text-primary); }
.thc-inapp__row-meta { display: block; font-size: 12px; color: var(--text-muted); }
.thc-inapp__lock { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--text-muted); }

/* ── Responsive (prototype `kpiGridStyle` / `mapWrapStyle`; narrow = vw <= 1024) ─────────────────── */
@media (max-width: 1024px) {
  .thc-dash__kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .thc-map__body { flex-direction: column; }
  .thc-map__canvas { flex: none; width: 100%; min-height: 380px; }
  .thc-map__list { width: 100%; max-height: 320px; border-left: 0; border-top: 1px solid var(--divider); }
}
@media (max-width: 640px) {
  .thc-dash__kpis,
  .thc-dash__kpis--personal { grid-template-columns: minmax(0, 1fr); }
  .thc-dash__quick { margin-left: 0; width: 100%; }
}

/* ── JD-003 · docked detail panel ──────────────────────────────────────────────────────
   A companion to a list, not a dialog: it PUSHES the content rather than covering it, so the row it
   describes stays visible. Entirely new class namespace — it shares nothing with `.thc-qc*`, the
   page-local QC tool window, so that surface is untouched. */
.thc-detail-panel {
  display: flex;
  flex-direction: column;
  width: 420px;
  flex: 0 0 420px;
  align-self: stretch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.thc-detail-panel[hidden] { display: none; }
.thc-detail-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  background: var(--surface-sub);
  border-bottom: 1px solid var(--border);
}
.thc-detail-panel__title { margin: 0; font-size: 16px; line-height: 22px; font-weight: 600; color: var(--text-primary); }
.thc-detail-panel__close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin: -6px -8px -6px 0;
  background: none; border: 0; border-radius: 5px;
  color: var(--text-muted); cursor: pointer;
}
.thc-detail-panel__close:hover { background: var(--hover); color: var(--text-body); }
.thc-detail-panel__body { flex: 1 1 auto; overflow-y: auto; padding: 16px; }
/* Pinned, so the actions stay reachable however long the history grows. */
.thc-detail-panel__actions {
  flex: 0 0 auto;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* <=1024: a right drawer over a scrim — the treatment frozen RESPONSIVE §3.6 already gives the QC
   tool window, reused rather than invented. */
@media (max-width: 1024px) {
  .thc-detail-panel {
    position: fixed; inset: 0 0 0 auto;
    width: min(420px, 100%); max-width: 100%;
    border-radius: 0; border-top: 0; border-right: 0; border-bottom: 0;
    z-index: 1040;
  }
  .thc-detail-panel::before {
    content: ""; position: fixed; inset: 0; right: min(420px, 100%);
    background: var(--overlay);
  }
}
