*, ::before, ::after {
  --tw-border-spacing-x: 0;
  --tw-border-spacing-y: 0;
  --tw-translate-x: 0;
  --tw-translate-y: 0;
  --tw-rotate: 0;
  --tw-skew-x: 0;
  --tw-skew-y: 0;
  --tw-scale-x: 1;
  --tw-scale-y: 1;
  --tw-pan-x:  ;
  --tw-pan-y:  ;
  --tw-pinch-zoom:  ;
  --tw-scroll-snap-strictness: proximity;
  --tw-gradient-from-position:  ;
  --tw-gradient-via-position:  ;
  --tw-gradient-to-position:  ;
  --tw-ordinal:  ;
  --tw-slashed-zero:  ;
  --tw-numeric-figure:  ;
  --tw-numeric-spacing:  ;
  --tw-numeric-fraction:  ;
  --tw-ring-inset:  ;
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #fff;
  --tw-ring-color: rgb(59 130 246 / 0.5);
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 0 #0000;
  --tw-shadow-colored: 0 0 #0000;
  --tw-blur:  ;
  --tw-brightness:  ;
  --tw-contrast:  ;
  --tw-grayscale:  ;
  --tw-hue-rotate:  ;
  --tw-invert:  ;
  --tw-saturate:  ;
  --tw-sepia:  ;
  --tw-drop-shadow:  ;
  --tw-backdrop-blur:  ;
  --tw-backdrop-brightness:  ;
  --tw-backdrop-contrast:  ;
  --tw-backdrop-grayscale:  ;
  --tw-backdrop-hue-rotate:  ;
  --tw-backdrop-invert:  ;
  --tw-backdrop-opacity:  ;
  --tw-backdrop-saturate:  ;
  --tw-backdrop-sepia:  ;
  --tw-contain-size:  ;
  --tw-contain-layout:  ;
  --tw-contain-paint:  ;
  --tw-contain-style:  ;
}

::backdrop {
  --tw-border-spacing-x: 0;
  --tw-border-spacing-y: 0;
  --tw-translate-x: 0;
  --tw-translate-y: 0;
  --tw-rotate: 0;
  --tw-skew-x: 0;
  --tw-skew-y: 0;
  --tw-scale-x: 1;
  --tw-scale-y: 1;
  --tw-pan-x:  ;
  --tw-pan-y:  ;
  --tw-pinch-zoom:  ;
  --tw-scroll-snap-strictness: proximity;
  --tw-gradient-from-position:  ;
  --tw-gradient-via-position:  ;
  --tw-gradient-to-position:  ;
  --tw-ordinal:  ;
  --tw-slashed-zero:  ;
  --tw-numeric-figure:  ;
  --tw-numeric-spacing:  ;
  --tw-numeric-fraction:  ;
  --tw-ring-inset:  ;
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #fff;
  --tw-ring-color: rgb(59 130 246 / 0.5);
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 0 #0000;
  --tw-shadow-colored: 0 0 #0000;
  --tw-blur:  ;
  --tw-brightness:  ;
  --tw-contrast:  ;
  --tw-grayscale:  ;
  --tw-hue-rotate:  ;
  --tw-invert:  ;
  --tw-saturate:  ;
  --tw-sepia:  ;
  --tw-drop-shadow:  ;
  --tw-backdrop-blur:  ;
  --tw-backdrop-brightness:  ;
  --tw-backdrop-contrast:  ;
  --tw-backdrop-grayscale:  ;
  --tw-backdrop-hue-rotate:  ;
  --tw-backdrop-invert:  ;
  --tw-backdrop-opacity:  ;
  --tw-backdrop-saturate:  ;
  --tw-backdrop-sepia:  ;
  --tw-contain-size:  ;
  --tw-contain-layout:  ;
  --tw-contain-paint:  ;
  --tw-contain-style:  ;
}

/*
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
*/

/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/

*,
::before,
::after {
  box-sizing: border-box;
  /* 1 */
  border-width: 0;
  /* 2 */
  border-style: solid;
  /* 2 */
  border-color: #e5e7eb;
  /* 2 */
}

::before,
::after {
  --tw-content: '';
}

/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/

html,
:host {
  line-height: 1.5;
  /* 1 */
  -webkit-text-size-adjust: 100%;
  /* 2 */
  -moz-tab-size: 4;
  /* 3 */
  -o-tab-size: 4;
     tab-size: 4;
  /* 3 */
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  /* 4 */
  font-feature-settings: normal;
  /* 5 */
  font-variation-settings: normal;
  /* 6 */
  -webkit-tap-highlight-color: transparent;
  /* 7 */
}

/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/

body {
  margin: 0;
  /* 1 */
  line-height: inherit;
  /* 2 */
}

/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/

hr {
  height: 0;
  /* 1 */
  color: inherit;
  /* 2 */
  border-top-width: 1px;
  /* 3 */
}

/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/

abbr:where([title]) {
  -webkit-text-decoration: underline dotted;
          text-decoration: underline dotted;
}

/*
Remove the default font size and weight for headings.
*/

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: inherit;
  font-weight: inherit;
}

/*
Reset links to optimize for opt-in styling instead of opt-out.
*/

a {
  color: inherit;
  text-decoration: inherit;
}

/*
Add the correct font weight in Edge and Safari.
*/

b,
strong {
  font-weight: bolder;
}

/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/

code,
kbd,
samp,
pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  /* 1 */
  font-feature-settings: normal;
  /* 2 */
  font-variation-settings: normal;
  /* 3 */
  font-size: 1em;
  /* 4 */
}

/*
Add the correct font size in all browsers.
*/

small {
  font-size: 80%;
}

/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/

table {
  text-indent: 0;
  /* 1 */
  border-color: inherit;
  /* 2 */
  border-collapse: collapse;
  /* 3 */
}

/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/

button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  /* 1 */
  font-feature-settings: inherit;
  /* 1 */
  font-variation-settings: inherit;
  /* 1 */
  font-size: 100%;
  /* 1 */
  font-weight: inherit;
  /* 1 */
  line-height: inherit;
  /* 1 */
  letter-spacing: inherit;
  /* 1 */
  color: inherit;
  /* 1 */
  margin: 0;
  /* 2 */
  padding: 0;
  /* 3 */
}

/*
Remove the inheritance of text transform in Edge and Firefox.
*/

button,
select {
  text-transform: none;
}

/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/

button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
  -webkit-appearance: button;
  /* 1 */
  background-color: transparent;
  /* 2 */
  background-image: none;
  /* 2 */
}

/*
Use the modern Firefox focus style for all focusable elements.
*/

:-moz-focusring {
  outline: auto;
}

/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/

:-moz-ui-invalid {
  box-shadow: none;
}

/*
Add the correct vertical alignment in Chrome and Firefox.
*/

progress {
  vertical-align: baseline;
}

/*
Correct the cursor style of increment and decrement buttons in Safari.
*/

::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
  height: auto;
}

/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/

[type='search'] {
  -webkit-appearance: textfield;
  /* 1 */
  outline-offset: -2px;
  /* 2 */
}

/*
Remove the inner padding in Chrome and Safari on macOS.
*/

::-webkit-search-decoration {
  -webkit-appearance: none;
}

/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/

::-webkit-file-upload-button {
  -webkit-appearance: button;
  /* 1 */
  font: inherit;
  /* 2 */
}

/*
Add the correct display in Chrome and Safari.
*/

summary {
  display: list-item;
}

/*
Removes the default spacing and border for appropriate elements.
*/

blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
  margin: 0;
}

fieldset {
  margin: 0;
  padding: 0;
}

legend {
  padding: 0;
}

ol,
ul,
menu {
  list-style: none;
  margin: 0;
  padding: 0;
}

/*
Reset default styling for dialogs.
*/

dialog {
  padding: 0;
}

/*
Prevent resizing textareas horizontally by default.
*/

textarea {
  resize: vertical;
}

/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/

input::-moz-placeholder, textarea::-moz-placeholder {
  opacity: 1;
  /* 1 */
  color: #9ca3af;
  /* 2 */
}

input::placeholder,
textarea::placeholder {
  opacity: 1;
  /* 1 */
  color: #9ca3af;
  /* 2 */
}

/*
Set the default cursor for buttons.
*/

button,
[role="button"] {
  cursor: pointer;
}

/*
Make sure disabled buttons don't get the pointer cursor.
*/

:disabled {
  cursor: default;
}

/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
   This can trigger a poorly considered lint error in some tools but is included by design.
*/

img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
  display: block;
  /* 1 */
  vertical-align: middle;
  /* 2 */
}

/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/

img,
video {
  max-width: 100%;
  height: auto;
}

/* Make elements with the HTML hidden attribute stay hidden by default */

[hidden]:where(:not([hidden="until-found"])) {
  display: none;
}

:root {
  --space-2xs: 0.25rem;
  --space-xs: 0.5rem;
  --space-sm: 0.75rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --space-2xl: 3rem;
  --space-3xl: 4rem;
  /* Surfaces */
  --surface-base: oklch(1 0 0);
  --surface-raised: oklch(0.996 0.005 95);
  --surface-muted: oklch(1 0 0);
  --surface-soft: oklch(0.955 0.012 95);
  /* Lines */
  --line-soft: oklch(0.89 0.012 95);
  --line-softer: oklch(0.93 0.01 95);
  --line-strong: oklch(0.8 0.017 92);
  /* Ink */
  --ink-strong: oklch(0.28 0.02 70);
  --ink-base: oklch(0.4 0.017 72);
  --ink-soft: oklch(0.55 0.014 74);
  --ink-softest: oklch(0.7 0.012 74);
  --brand-color: oklch(0.55 0.014 74);
  /* Radii */
  --radius-sm: 0.5rem;
  --radius-md: 0.85rem;
  --radius-lg: 1.2rem;
  --radius-xl: 1.4rem;
  /* Transition timings */
  --t-fast: 120ms;
  --t-base: 180ms;
  --t-slow: 220ms;
  --ease: ease;
  /* Row padding token (used by aside/drawer rows) */
  --row-pad: 0.6rem;
  /* Density knob: vertical padding on each field row in the detail document.
       This is the single lever for the spacing between field entries — lower it
       to tighten the document, raise it to loosen. Default 12px. */
  --field-row-pad-y: 8px;
  /* Density knob: vertical space between field groups in the detail document.
       Single lever for the gap separating one group (e.g. Geometrie) from the
       next (Boden). Default 42px. */
  --field-group-gap: 28px;
  /* Eigenschaften-Bereich: paper-like surface tokens (Issue 1) */
  --eigen-bg: oklch(0.99 0.005 95);
  --eigen-field-bg: oklch(0.97 0.008 95);
  --eigen-field-border: oklch(0.88 0.015 95);
  --eigen-ink: oklch(0.28 0.02 74);
  /* Accent — olive green, matching the Inline-Dokument detail page
       (.app-shell-fixed) so the whole app shares one accent tone. */
  --accent: #435f2d;
  --accent-strong: #435f2d;
  --accent-soft: color-mix(in srgb, #435f2d 22%, white 78%);
  /* Semantic */
  --danger: oklch(0.6 0.16 26);
  --danger-soft: oklch(0.96 0.03 26);
  --warning: oklch(0.68 0.11 75);
  --warning-soft: oklch(0.97 0.03 80);
  --success: oklch(0.62 0.12 150);
  --success-soft: oklch(0.96 0.03 150);
  /* Semantic mixes — derived from base hues to keep drift in check */
  --accent-border: color-mix(in srgb, var(--accent) 24%, white 76%);
  --accent-border-soft: color-mix(in srgb, var(--accent) 18%, white 82%);
  --accent-border-strong: color-mix(in srgb, var(--accent) 58%, white 42%);
  --danger-ink: color-mix(in srgb, var(--danger) 72%, black 28%);
  --danger-ink-strong: color-mix(in srgb, var(--danger) 80%, black 20%);
  --danger-strong: color-mix(in srgb, var(--danger) 86%, black 14%);
  --danger-strongest: color-mix(in srgb, var(--danger) 92%, black 8%);
  --danger-tint: color-mix(in srgb, var(--danger) 24%, white 76%);
  --warning-ink: color-mix(in srgb, var(--warning) 60%, black 40%);
  --warning-tint: color-mix(in srgb, var(--warning) 30%, white 70%);
  --warning-bg: color-mix(in srgb, var(--warning) 42%, white 58%);
  --warning-border-strong: color-mix(in srgb, var(--warning) 58%, white 42%);
  /* Raw palette — used to compose rgba() variants below */
  --_sand: 184 169 144;
  /* warm sand — borders, dividers */
  --_sand-mid: 188 172 148;
  /* slightly darker sand — internal dividers */
  --_shadow-warm: 67 55 35;
  /* warm brown — standard shadows */
  --_shadow-deep: 61 48 28;
  /* deep brown — dialog/card shadows */
  --_white: 255 255 255;
  /* pure white — glassy backgrounds */
  --_cream: 253 251 247;
  /* warm off-white — dialog/form panels */
  --_warm-bg: 246 242 236;
  /* warm tint — meta blocks, table headers */
  --_sand-muted: 200 193 182;
  /* muted sand — button-muted */
  --_disabled-bg: 221 214 202;
  /* disabled button fill */
  --_disabled-text: 100 89 71;
  /* disabled button text */
  --_ink-paper: 42 39 34;
  /* paper ink #2a2722 — inline-document tints/lines/shadows */
  /* Composed tokens — primary opacities (most common usage) */
  --border-sand: rgba(var(--_sand) / 0.24);
  --border-sand-soft: rgba(var(--_sand) / 0.2);
  --border-divider: rgba(var(--_sand-mid) / 0.2);
  --border-divider-faint: rgba(var(--_sand-mid) / 0.14);
  --border-divider-medium: rgba(var(--_sand-mid) / 0.22);
  --bg-card: rgba(var(--_white) / 0.72);
  --bg-card-light: rgba(var(--_white) / 0.74);
  --bg-card-medium: rgba(var(--_white) / 0.78);
  --bg-card-strong: rgba(var(--_white) / 0.8);
  --bg-card-solid: rgba(var(--_white) / 0.98);
  --bg-cream: rgba(var(--_cream) / 0.98);
  --bg-warm: rgba(var(--_warm-bg) / 0.9);
  --bg-warm-table: rgba(var(--_warm-bg) / 0.92);
  --shadow-sm: 0 1px 2px rgba(var(--_shadow-warm) / 0.05), 0 8px 24px rgba(var(--_shadow-warm) / 0.04);
  --shadow-md: 0 1px 2px rgba(var(--_shadow-warm) / 0.05), 0 16px 40px rgba(var(--_shadow-warm) / 0.08);
  --font-body: "Helvetica Neue", Arial, sans-serif;
  --font-heading: "Linux Biolinum", "Optima", "Georgia", Arial, sans-serif;
  --font-prominent: "Linux Biolinum", "Optima", Georgia, "Times New Roman", serif;
  --font-prominent-weight: 500;
  --font-placeholder: "Helvetica Neue", Arial, sans-serif;
  --color-placeholder: var(--ink-soft);
}

html {
  background-color: var(--surface-base);
  color: var(--ink-strong);
}

body {
  min-height: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background:
      radial-gradient(circle at top left, rgba(151, 196, 216, 0.15), transparent 28%),
      radial-gradient(circle at top right, rgba(218, 198, 166, 0.16), transparent 24%),
      linear-gradient(180deg, rgba(255, 255, 255, 0.68), rgba(255, 255, 255, 0.2)),
      var(--surface-base);
  color: var(--ink-base);
  font-family: var(--font-body);
}

h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
  color: var(--ink-strong);
  font-family: var(--font-heading);
  font-weight: 600;
  letter-spacing: -0.02em;
}

h1 {
  font-size: 2rem;
}

h2 {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ink-base);
}

h3 {
  font-size: 1rem;
  font-weight: 500;
  color: var(--ink-soft);
}

a {
  transition: color var(--t-base) var(--ease), opacity var(--t-base) var(--ease), background-color var(--t-base) var(--ease), border-color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}

p {
  max-width: 72ch;
}

code,
  kbd,
  samp,
  pre {
  font-family: var(--font-body);
}

::-moz-selection {
  background: color-mix(in srgb, var(--accent-soft) 80%, white 20%);
}

::selection {
  background: color-mix(in srgb, var(--accent-soft) 80%, white 20%);
}

button,
  input,
  select,
  textarea {
  font: inherit;
}

/* :where() keeps these defaults at zero specificity so any component
     restyle (inline-document optic, pill/popover inputs) wins without
     selector arms races — the flattened Tailwind output has no @layer
     cascade to do that for us. */

input:where(:not([type="hidden"]):not([type="checkbox"]):not([type="radio"])),
  select,
  textarea {
  width: 100%;
  min-height: 2.4rem;
  border: .5px solid var(--line-soft);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--surface-raised) 88%, white 12%);
  color: var(--ink-strong);
  /* Vertical padding kept low so min-height drives the height */
  padding: 0.3rem 0.95rem;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  transition: border-color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), background-color var(--t-base) var(--ease);
}

textarea {
  min-height: 7rem;
}

input::-moz-placeholder, textarea::-moz-placeholder {
  font-family: var(--font-placeholder);
  color: var(--color-placeholder);
}

input::placeholder,
  textarea::placeholder {
  font-family: var(--font-placeholder);
  color: var(--color-placeholder);
}

input:where(:not([type="hidden"]):not([type="checkbox"]):not([type="radio"])):focus,
  select:focus,
  textarea:focus {
  outline: none;
  border-color: var(--accent-border-strong);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent-soft) 82%, white 18%);
  background: var(--surface-raised);
}

input:where(:not([type="hidden"]):not([type="checkbox"]):not([type="radio"])):disabled,
  select:disabled,
  textarea:disabled {
  background: var(--surface-soft);
  color: var(--ink-soft);
  cursor: not-allowed;
}

input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--accent-strong);
}

dialog {
  margin: auto;
}

dialog::backdrop {
  background: rgba(71, 56, 34, 0.22);
  -webkit-backdrop-filter: blur(5px);
          backdrop-filter: blur(5px);
}

.\!container {
  width: 100% !important;
}

.container {
  width: 100%;
}

@media (min-width: 640px) {
  .\!container {
    max-width: 640px !important;
  }

  .container {
    max-width: 640px;
  }
}

@media (min-width: 768px) {
  .\!container {
    max-width: 768px !important;
  }

  .container {
    max-width: 768px;
  }
}

@media (min-width: 1024px) {
  .\!container {
    max-width: 1024px !important;
  }

  .container {
    max-width: 1024px;
  }
}

@media (min-width: 1280px) {
  .\!container {
    max-width: 1280px !important;
  }

  .container {
    max-width: 1280px;
  }
}

@media (min-width: 1536px) {
  .\!container {
    max-width: 1536px !important;
  }

  .container {
    max-width: 1536px;
  }
}

/* ----------------------------------------------------------------------
     Shared property clusters (css-cleanup-04). Pulled out of N individual
     rules so the recurring decls live in one place. Placed at the top of the
     components layer so it sits *before* the consuming rules in source order
     (the flattened TW build resolves conflicts by source order, not @layer
     cascade) — instance-specific properties stay on the individual classes.
  ---------------------------------------------------------------------- */

/* Truncate: single-line text with ellipsis. */

.topbar-crumb-current,
  .detail-object-identifier,
  .detail-aside-item-label,
  .detail-aside-hero-badge,
  .tree-panel-link-id,
  .file-stage-meta-overlay .file-stage-meta-name,
  .datei-liste-identifier,
  .mosaik-caption,
  .hotspot-popover-name-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Ghost-button reset: strip native button chrome. `border` stays on the
     individual rules because it varies (none / 1px transparent / 1.5px solid). */

.collapsible-strip__rail,
  .collapsible-strip__rail-toggle,
  .detail-aside-toggle,
  .tree-panel-collapsed-strip,
  .tree-panel-strip-toggle,
  .tree-panel-toggle,
  .detail-file-aside-toggle,
  .detail-file-aside-tab,
  .datei-liste-header-toggle,
  .detail-drawer-toggle,
  .detail-drawer-tab,
  .detail-drawer-tab-new,
  .detail-drawer-collapse,
  .ms-checklist-box {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: transparent;
  cursor: pointer;
}

/* Icon centering: flex cross/main-axis centering. `display` stays on the
     individual rules because it varies (flex / inline-flex, and
     .file-stage-empty adds flex-direction: column). */

.topbar-focus-toggle,
  .topbar-record-nav-btn,
  .tile-icon,
  .inline-header-kebab-toggle,
  .detail-aside-mode-toggle,
  .detail-aside-hero-link,
  .detail-aside-nav,
  .detail-aside-thumb,
  .file-stage-empty,
  .detail-resize-handle,
  .detail-drawer-spinner,
  .field-helptext-trigger,
  .field-type-reset-trigger,
  .mosaik-icon,
  .hotspot-toolbar-icon-button,
  .raumbuch-pano-pin {
  align-items: center;
  justify-content: center;
}

/* ----------------------------------------------------------------------
     Collapsible-Strip: side column that toggles between a narrow 38px rail
     (with vertical label + toggle button) and an expanded content panel.
     See CONTEXT.md → UI patterns.

     Per-instance specifics (expanded width, border side) live on the
     consuming class (e.g. .detail-aside) — this block only owns the
     toggle mechanic and rail styling.

     Pre-hydration: each strip's inline script sets [data-pre-expanded] on
     its own element during the document-parse stream, so the first paint
     already matches the persisted state. detail-persistence.js then
     swaps to .is-expanded after wireDom.
  ---------------------------------------------------------------------- */

.collapsible-strip {
  --strip-expanded-width: 320px;
  flex: 0 0 38px;
  background: var(--surface-raised);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Animate the open/close only after hydration. Without this gate the
     first paint (or every hx-boost body swap) animates the flex-basis from
     the collapsed default to the persisted expanded state — the visible
     "erst zu, dann auf" flicker. detail-persistence.js stamps
     html.detail-hydrated once wireDom has run. */

html.detail-hydrated .collapsible-strip {
  transition: flex-basis var(--t-base) var(--ease);
}

.collapsible-strip.is-expanded,
  .collapsible-strip[data-pre-expanded] {
  flex: 0 0 var(--strip-expanded-width);
  overflow-y: auto;
}

.collapsible-strip__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border: none;
  width: 100%;
}

.collapsible-strip.is-expanded .collapsible-strip__rail,
  .collapsible-strip[data-pre-expanded] .collapsible-strip__rail {
  display: none;
}

.collapsible-strip:not(.is-expanded):not([data-pre-expanded]) .collapsible-strip__content {
  display: none;
}

.collapsible-strip__rail-toggle {
  border: none;
  font-size: 1.1rem;
  color: var(--ink-soft);
  padding: 0.2rem;
  line-height: 1;
}

.collapsible-strip__rail-label {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--ink-soft);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
}

.app-shell {
  min-height: 100%;
}

/* Footer-bearing admin pages: flex column so the footer sticks to the bottom
     of short pages. Scoped to this modifier so plain .app-shell pages and the
     editor's .app-shell-fixed keep their original block/fixed layout. */

.app-shell--with-footer {
  display: flex;
  flex-direction: column;
  /* Footer-bearing admin/list pages (login, project/class lists, roster,
       Datenschutz, …) carried the pure-white :root --surface-base while the
       editor pages sit on a warmer panel tone. Paint this shell with the same
       warm surface so the whole app reads as one tone. The .app-shell-fixed
       Inline-Dokument detail pages (standard/file class) are intentionally
       excluded — they keep their own paper palette. Same tone as the topbar. */
  min-height: 100dvh;
  background: #f7f5f0;
}

/* As a flex item, .page-shell would shrink to its content width and its
     mx-auto centering would collapse the layout. Restore full width and let it
     grow vertically so the footer is pushed down. */

.app-shell--with-footer > .page-shell {
  width: 100%;
  flex: 1 0 auto;
}

.app-shell-fixed {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
  /* Slice 07: Papier-Palette — scoped overrides of the shared surface/
       line/ink tokens so the Inline-Dokument detail page gets the
       paper-and-ink look without recoloring Login/Projektliste/Klassen-Editor,
       which share these tokens via :root. */
  --surface-base: #fffcf6;
  --surface-raised: #f8f6f1;
  --surface-soft: #eaeee1;
  --surface-overlay: #fffdf8;
  --eigen-bg: #fffcf6;
  --line-soft: rgba(var(--_ink-paper) / 0.13);
  --line-softer: rgba(var(--_ink-paper) / 0.13);
  --line-strong: rgba(var(--_ink-paper) / 0.20);
  --ink-strong: #2a2722;
  --ink-base: #5c574e;
  --ink-soft: #8c867a;
  --ink-softest: #b3ad9f;
  /* Accent (olive green) is now the shared :root tone — no scoped
       override needed here. Only surface/line/ink stay paper-specific. */
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: #f7f5f0;
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  /* needed for ::after gradient line */
  isolation: isolate;
}

.topbar::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
      90deg,
      rgba(199, 166, 115, 0.61) 0%,
      color-mix(in srgb, var(--accent) 18%, transparent) 50%,
      rgba(199, 166, 115, 0.61) 100%
    );
}

/* Soft 4px light glow spilling just below the separator line, brightest
     in the centre so it reads as a beam of light under the divider. */

.topbar::before {
  content: "";
  position: absolute;
  bottom: -1.2px;
  left: 0;
  right: 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(
      90deg,
      transparent 0%,
      rgba(140, 140, 140, 0.85) 50%,
      transparent 100%
    );
  filter: blur(2px);
}

.topbar-inner {
  display: flex;
  align-items: center;
  /* Fixed bar height across all pages: tall enough for the record-nav /
       focus-mode controls (30px), so pages without them match exactly. */
  min-height: 46px;
  gap: 0.4rem;
  padding-inline: clamp(1rem, 2.5vw, 2rem);
  padding-block: .5rem;
}

.topbar-project {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  text-decoration: none;
}

.topbar-project:hover {
  color: var(--ink-strong);
}

.topbar-breadcrumbs {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0.4rem;
  min-width: 0;
  overflow: hidden;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.topbar-crumb-link {
  color: rgb(104, 104, 104);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}

.topbar-crumb-link:hover {
  color: var(--ink-strong);
  font-weight: 700;
}

.topbar-crumb-current {
  color: var(--ink-base);
  font-weight: 700;
}

.topbar-crumb-separator {
  color: color-mix(in srgb, var(--line-strong) 68%, white 32%);
  font-size: 0.78rem;
}

.topbar-search {
  flex: 1 1 auto;
  display: flex;
  justify-content: center;
  min-width: 0;
}

.topbar-search[hidden] {
  display: none;
}

.topbar .topbar-search input[type="search"] {
  flex: 1 1 auto;
  width: 100%;
  max-width: 28rem;
  min-width: 0;
  height: 1.8rem;
  min-height: 0;
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--line-soft, #fcfcfc);
  border-radius: 2rem;
  /* Paper surface — matches the detail document area (.app-shell-fixed). */
  background: #fffcf6;
  box-shadow: none;
  font-size: 0.9rem;
  color: var(--ink);
  transition: none;
}

.topbar .topbar-search input[type="search"]:focus {
  outline: none;
  border-color: var(--line-soft, #ffffff);
  box-shadow: none;
  background: #fffcf6;
}

/* The native search ✕ only shows on focus/hover (unreachable on a tablet) —
     hide it and use the custom .topbar-search-clear button below instead. */

.topbar .topbar-search input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* Positioned wrapper so the custom clear button can overlay the input's
     right edge; carries the same max-width as the input so the bar stays
     centred. */

.topbar-search-field {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  max-width: 28rem;
}

/* Leave room for the clear button so long queries don't run under it. */

.topbar-search.has-query .topbar-search-input {
  padding-right: 2.4rem;
}

/* Mit Badge muss der Eingabetext auch am Badge vorbei, nicht nur am ✕.
     Die Badge-Breite haengt vom Klassennamen ab; sie ist aber auf 9rem
     gedeckelt (danach Ellipse), also wird hier fuer diesen Grenzfall
     reserviert. Lieber etwas Luft rechts als Text, der unter dem Badge
     verschwindet — CSS kann die tatsaechliche Breite nicht ausrechnen, und
     dafuer JS zu messen waere fuer eine Layout-Frage zu viel Maschinerie. */

.topbar-search-field:has(.topbar-search-class) .topbar-search-input {
  padding-right: 9.6rem;
}

/* ✕-Button + Klassen-Badge als ein rechtsbündiger Cluster IM Suchfeld.
     Der Cluster ist das Overlay (nicht mehr der ✕ allein), damit die
     natürliche Badge-Breite den ✕ nach links schiebt — ohne geratene Offsets.
     Zeiger-Events nur auf den Kindern, sonst blockiert der Cluster Klicks
     auf das darunterliegende Input. */

.topbar-search-adornments {
  position: absolute;
  top: 50%;
  right: 0.3rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 0.1rem;
  max-width: calc(100% - 2rem);
  pointer-events: none;
}

.topbar-search-adornments > * {
  pointer-events: auto;
}

/* Klassen-Badge: benennt die Klasse, innerhalb derer gefiltert wird — der
     Geltungsbereich der Suche ist sonst unsichtbar. Sitzt am rechten Innenrand
     des Suchfelds; die Feldhöhe bleibt unberührt, die Topbar wächst nicht. */

.topbar-search-class {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0.05rem 0.5rem;
  border-radius: 0.7rem;
  background: color-mix(in srgb, var(--accent) 12%, #fff);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--ink-muted, #6b6b6b);
  /* Nicht selektierbar (rein informativ), aber Zeiger-Events bleiben an:
       bei einem langen, per Ellipse gekürzten Klassennamen ist der title-
       Tooltip die einzige Möglichkeit, ihn vollständig zu lesen. */
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  cursor: default;
}

/* Custom clear button: hidden until there is a query, then always visible
     (no hover/focus gate) with a touch-sized hit area for the tablet. */

.topbar-search-clear {
  flex: 0 0 auto;
  display: none;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted, #6b6b6b);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.topbar-search.has-query .topbar-search-clear {
  display: inline-flex;
}

.topbar-search-clear:hover {
  color: var(--ink);
}

/* Feld-Chip-Suche (Slice S4). The chip bar + field-suggestion dropdown sit
     BELOW the search input as absolute overlays anchored to the search form, so
     the fixed 46px topbar height never changes (T1: „Topbar-Höhe bleibt
     konstant"). The form itself is the positioned ancestor. */

form[data-search-bar] {
  position: relative;
}

/* Suggestion dropdown: opens when the user types „/…" in the search input;
     lists the matching class fields. Centred under the input, same max-width. */

.search-field-suggest {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 50%;
  transform: translateX(-50%);
  width: min(28rem, 100%);
  max-height: 16rem;
  overflow-y: auto;
  z-index: 40;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  background: #fffcf6;
  border: 1px solid var(--line-soft, #e7e1d4);
  border-radius: 0.6rem;
  box-shadow: 0 6px 20px rgba(60, 50, 30, 0.14);
}

.search-field-suggest[hidden] {
  display: none;
}

.search-field-suggest-item {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  padding: 0.35rem 0.55rem;
  border: 0;
  border-radius: 0.4rem;
  background: transparent;
  font-size: 0.85rem;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.search-field-suggest-item:hover,
  .search-field-suggest-item[data-active] {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.search-field-suggest-type {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--ink-softest);
}

/* Chip bar: absolutely overlaid under the search input; visible only while at
     least one chip exists. Grows downward / wraps for multiple chips. */

.search-chip-bar {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 50%;
  transform: translateX(-50%);
  width: min(28rem, 100%);
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.4rem;
  background: #fffcf6;
  border: 1px solid var(--line-soft, #e7e1d4);
  border-radius: 0.7rem;
  box-shadow: 0 4px 14px rgba(60, 50, 30, 0.1);
}

.search-chip-bar[hidden],
  /* Collapsed: chips stay in the DOM (so ?f= serialisation is unaffected) but
     the bar is hidden so a tall filter stack stops covering the content and
     buttons beneath it. A click/focus on the search area re-expands it. */
  .search-chip-bar.is-collapsed {
  display: none;
}

.search-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.1rem 0.15rem 0.1rem 0.45rem;
  background: color-mix(in srgb, var(--accent) 10%, #fff);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 0.8rem;
  font-size: 0.8rem;
  color: var(--ink);
  white-space: nowrap;
}

.search-chip-field {
  font-weight: 600;
}

/* Operator select + value input(s) sit inline in the chip. */

.search-chip-op,
  .search-chip-value,
  .search-chip-value-to {
  height: 1.35rem;
  min-height: 0;
  padding: 0 0.25rem;
  border: 1px solid var(--line-soft, #e7e1d4);
  border-radius: 0.3rem;
  background: #fff;
  font-size: 0.78rem;
  color: var(--ink);
}

/* Operator select hugs its (short, symbolic) label instead of reserving the
     browser's wide default select width — trims the empty space after e.g. "><". */

.search-chip-op {
  width: auto;
  min-width: 0;
  padding-right: 1rem;
  text-align: center;
  -moz-text-align-last: center;
       text-align-last: center;
}

input.search-chip-value,
  .search-chip-value-to {
  width: 4rem;
  min-width: 0;
}

/* Select/multiselect value dropdown (S6): size to the chosen option rather
     than the fixed text-input width, but stay compact within the chip. */

select.search-chip-value {
  max-width: 9rem;
}

.search-chip-range-sep {
  color: var(--ink-softest);
  font-size: 0.78rem;
}

.search-chip-bool {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  font-size: 0.8rem;
  cursor: pointer;
}

.search-chip-remove,
  .search-chip-bar-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted, #6b6b6b);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.search-chip-remove:hover,
  .search-chip-bar-clear:hover {
  color: var(--ink);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}

/* The bar-wide clear (all chips) sits at the trailing edge, visually set off. */

.search-chip-bar-clear {
  margin-left: auto;
}

.brand-link {
  display: inline-flex;
  align-items: baseline;
  gap: 0.75rem;
  color: var(--brand-color);
  text-decoration: none;
}

.brand-mark {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 999px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 85%, white 15%), var(--warning-bg));
  box-shadow: 0 0 0 5px rgba(125, 159, 178, 0.11);
}

.brand-copy {
  display: grid;
  gap: 0.15rem;
}

.brand-title {
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
}

.topbar-meta {
  display: flex;
  align-items: center;
  gap: 1rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
  margin-inline-start: auto;
}

/* Fokus-Modus toggle (Slice 08): 30x30 ghost icon button, accent-tinted
     when active (aria-pressed=true). The hairline separator sits between it
     and the user menu. */

.topbar-focus-toggle {
  display: inline-flex;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease;
}

.topbar-focus-toggle:hover {
  background: color-mix(in srgb, var(--ink-soft) 10%, transparent);
  color: var(--ink-strong);
}

.topbar-focus-toggle[aria-pressed="true"] {
  background: color-mix(in srgb, var(--accent-soft) 80%, white 20%);
  color: var(--accent-strong);
}

.topbar-sep {
  width: 1px;
  align-self: stretch;
  margin-block: 0.35rem;
  background: var(--line-softer);
}

/* User menu (Topbar-Benutzermenü 02): circular avatar summary opening a
     popover that reuses the .inline-header-menu* styling. The avatar shares
     the kebab toggle's neutral #dcd7cc tone — one fill for everyone. */

.topbar-user {
  position: relative;
}

.topbar-user-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 9999px;
  background: #dcd7cc;
  color: var(--ink-base);
  font-size: 0.65rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

.topbar-user-avatar::-webkit-details-marker {
  display: none;
}

.topbar-user-avatar:hover {
  background: color-mix(in srgb, #dcd7cc 80%, black 12%);
}

/* Non-clickable identity header inside the popover. */

.topbar-user-identity {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.5rem 0.6rem;
}

.topbar-user-name {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-strong);
}

.topbar-user-email {
  font-size: 11.5px;
  color: var(--ink-softest);
}

/* Datensatz-Navigation ‹ › (Slice 09): two quiet 28x28 ghost chevrons with
     a hairline divider between them. Disabled position renders greyed and
     non-interactive (no wrap-around). */

.topbar-record-nav {
  display: inline-flex;
  align-items: center;
}

.topbar-record-nav-btn {
  display: inline-flex;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  transition: background-color 140ms ease, color 140ms ease;
}

.topbar-record-nav-btn:hover {
  background: color-mix(in srgb, var(--ink-soft) 10%, transparent);
  color: var(--ink-strong);
}

.topbar-record-nav-btn.is-disabled {
  color: var(--line-soft);
  pointer-events: none;
}

.topbar-record-nav-sep {
  width: 1px;
  align-self: stretch;
  margin-block: 0.4rem;
  background: var(--line-softer);
}

.page-shell {
  margin-left: auto;
  margin-right: auto;
  max-width: 80rem;
  padding-inline: clamp(1rem, 2.5vw, 2rem);
  padding-block: clamp(1.5rem, 2vw, 2.5rem);
}

.page-stack {
  display: grid;
  gap: clamp(1.5rem, 2vw, 2rem);
}

.page-section {
  display: grid;
  gap: clamp(1rem, 1.6vw, 1.5rem);
}

/* Template-definition tabs (classes/detail.html): a quiet tab strip that
     switches between the Felder / Beziehungen / IFC panels on one page. */

.template-tabs {
  display: flex;
  gap: 0.25rem;
  margin-top: 2rem;
  border-bottom: 1px solid var(--border-divider-medium);
}

.template-tab {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 0.6rem 1rem;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

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

.template-tab.is-active {
  color: var(--accent-strong);
  border-bottom-color: var(--accent-strong);
}

.template-tab-panel {
  margin-top: 0.5rem;
}

/* `.page-section { display: grid }` outranks the UA `[hidden]` rule, so the
     hidden panels would stay visible — restore the hide explicitly. */

.template-tab-panel[hidden] {
  display: none;
}

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-lg);
}

.page-heading {
  display: grid;
  gap: 0.45rem;
  flex: 1;
  min-width: 0;
}

.page-eyebrow {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.page-title {
  line-height: 1.08;
}

.page-subtitle {
  color: var(--ink-soft);
  font-size: 0.96rem;
  line-height: 1.6;
}

.page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.notice {
  display: grid;
  gap: 0.4rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line-soft);
  padding: .4rem 1rem;
  background: color-mix(in srgb, var(--surface-raised) 82%, white 18%);
}

/* The class display (grid) has the same specificity as the preflight
     [hidden] rule but comes later in the cascade — without this, dialogs
     toggling blocks via the hidden attribute never actually hide them. */

.notice[hidden],
  .field-stack[hidden] {
  display: none;
}

.notice-danger {
  border-color: var(--danger-tint);
  background: var(--danger-soft);
  color: var(--danger-ink);
}

.notice-warning {
  border-color: var(--warning-tint);
  background: var(--warning-soft);
  color: var(--warning-ink);
}

.notice-success {
  border-color: color-mix(in srgb, var(--success) 30%, white 70%);
  background: var(--success-soft);
  color: color-mix(in srgb, var(--success) 58%, black 42%);
}

.notice-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* Fixed host for HTMX server/network error banners (see base.html). Empty
     until a 5xx or send error fires, so it never affects normal layout. */

.app-error-toast:not(:empty) {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  width: min(40rem, calc(100% - 2rem));
}

.app-error-toast .notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  box-shadow: 0 10px 30px rgb(0 0 0 / 18%);
}

/* Transient success toast (oben mittig). Empty until a save redirect fires;
     fades + slides in, auto-dismisses via the script in base.html. */

.app-success-toast:not(:empty) {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translate(-50%, -0.5rem);
  z-index: 60;
  width: -moz-max-content;
  width: max-content;
  max-width: calc(100% - 2rem);
  opacity: 0;
  pointer-events: none;
  transition:
      opacity 0.18s ease,
      transform 0.18s ease;
}

.app-success-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

.app-success-toast .notice {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  box-shadow: 0 10px 30px rgb(0 0 0 / 18%);
}

@media (prefers-reduced-motion: reduce) {
  .app-success-toast:not(:empty) {
    transition: opacity 0.18s ease;
    transform: translate(-50%, 0);
  }
}

.surface {
  border: 1px solid var(--border-sand);
  border-radius: var(--radius-xl);
  background: transparent;
  box-shadow: none;
}

.surface-muted {
  background: rgba(249, 245, 239, 0.88);
}

.surface-pad {
  padding: clamp(0.1rem, 0vw, 0rem);
}

.surface-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  padding: 1.15rem 1.35rem;
  border-bottom: 1px solid var(--border-divider-medium);
}

.surface-body {
  display: grid;
  gap: var(--space-lg);
  padding: clamp(1.35rem, 2vw, 1.75rem);
}

.surface-title {
  font-size: 1.2rem;
  line-height: 1.15;
}

.tile-grid {
  display: grid;
  gap: clamp(1rem, 1.5vw, 1.4rem);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.tile-link,
  .tile-panel {
  display: grid;
  gap: 0.75rem;
  align-content: start;
  min-height: 100%;
  padding: 1.2rem;
  border: 1px solid var(--border-sand);
  border-radius: var(--radius-lg);
  background: var(--bg-card);
  box-shadow: 0 1px 2px rgba(var(--_shadow-warm) / 0.04);
}

.tile-link:hover {
  transform: translateY(-1px);
  border-color: var(--accent-border);
  box-shadow: var(--shadow-md);
}

.tile-panel {
  background: transparent;
}

/* Schablonen page only: dashed tile frames. Scoped via .tile-grid-dashed
     so the shared .tile-link/.tile-panel base stays unchanged everywhere else. */

.tile-grid-dashed .tile-link,
  .tile-grid-dashed .tile-panel {
  background: transparent;
  border: 1px  dashed;
  box-shadow: 2px 2px 2px rgba(var(--_shadow-warm) / 0.1);
}

.tile-head {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}

.tile-icon {
  display: inline-flex;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface-soft) 68%, white 32%);
  font-size: 1.2rem;
}

.tile-icon svg {
  width: 1.4rem;
  height: 1.4rem;
  color: var(--ink-base, currentColor);
}

.tile-copy {
  display: grid;
  gap: 0.3rem;
  min-width: 0;
}

.tile-title {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  line-height: 1.2;
}

.tile-subtitle {
  color: var(--ink-soft);
  font-size: 0.9rem;
  line-height: 1.55;
}

.mono {
  font-family: var(--font-body);
}

.project-meta-inline {
  margin-top: 0.75rem;
  font-size: 0.78rem;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.\!button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.\!button {
  min-height: 2.3rem !important;
  padding-inline: 1rem !important;
  border-radius: 13px !important;
  border: 1px solid transparent !important;
  font-size: 0.9rem !important;
  font-weight: 600 !important;
  white-space: nowrap !important;
}

.button {
  min-height: 2.3rem;
  padding-inline: 1rem;
  border-radius: 13px;
  border: 1px solid transparent;
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
}

.button-primary {
  background: color-mix(in srgb, var(--accent-strong) 88%, black 12%);
  color: white;
  box-shadow: 0 10px 24px color-mix(in srgb, var(--accent) 18%, transparent);
}

.button-primary:hover {
  background: color-mix(in srgb, var(--accent-strong) 94%, black 6%);
}

.button-secondary {
  border-color: rgba(var(--_sand) / 0.32);
  background: rgba(var(--_white) / 0.75);
  color: var(--ink-base);
}

.button-secondary:hover {
  border-color: var(--accent-border-soft);
  background: rgba(var(--_white) / 0.92);
  color: var(--ink-strong);
}

.button-start-empty {
  border-color: rgba(var(--_sand) / 0.32);
  background: transparent;
  color: var(--ink-soft);
  cursor: default;
}

.button-warning {
  border-color: var(--warning-tint);
  background: color-mix(in srgb, var(--warning-soft) 82%, white 18%);
  color: var(--warning-ink);
}

.button-warning:hover {
  background: color-mix(in srgb, var(--warning-soft) 62%, white 38%);
}

.button-danger {
  background: var(--danger-strong);
  color: white;
}

.button-danger:hover {
  background: var(--danger-strongest);
}

.button-muted {
  background: rgba(var(--_sand-muted) / 0.45);
  color: var(--ink-soft);
  border-color: rgba(var(--_sand) / 0.3);
}

.button-muted:hover {
  background: rgba(var(--_sand-muted) / 0.65);
  color: var(--ink-strong);
}

.button-ghost {
  background: transparent;
  color: var(--ink-soft);
  padding-inline: 0;
  min-height: auto;
}

.button-ghost:hover {
  color: var(--ink-strong);
}

.\!button:disabled {
  background: rgba(var(--_disabled-bg) / 0.75) !important;
  border-color: transparent !important;
  color: rgba(var(--_disabled-text) / 0.7) !important;
  box-shadow: none !important;
  cursor: not-allowed !important;
}

.button:disabled,
  .button-primary:disabled,
  .button-secondary:disabled,
  .button-warning:disabled,
  .button-danger:disabled {
  background: rgba(var(--_disabled-bg) / 0.75);
  border-color: transparent;
  color: rgba(var(--_disabled-text) / 0.7);
  box-shadow: none;
  cursor: not-allowed;
}

/* Animated dots for buttons during running submit requests.
     Aktiv, sobald raumbuchMarkFormPending() das Attribut setzt. */

.\!button[data-busy-dots]::after {
  content: "" !important;
  display: inline-block !important;
  width: 1.2em !important;
  text-align: left !important;
  animation: raumbuch-busy-dots 1.2s steps(4, end) infinite !important;
}

.button[data-busy-dots]::after {
  content: "";
  display: inline-block;
  width: 1.2em;
  text-align: left;
  animation: raumbuch-busy-dots 1.2s steps(4, end) infinite;
}

@keyframes raumbuch-busy-dots {
  0%   {
    content: "";
  }

  25%  {
    content: ".";
  }

  50%  {
    content: "..";
  }

  75%  {
    content: "...";
  }

  100% {
    content: "";
  }
}

@media (prefers-reduced-motion: reduce) {
  .\!button[data-busy-dots]::after {
    animation: none !important;
    content: "…" !important;
  }

  .button[data-busy-dots]::after {
    animation: none;
    content: "…";
  }
}

.\!badge {
  display: inline-flex;
  align-items: center;
}

.badge {
  display: inline-flex;
  align-items: center;
}

.\!badge {
  padding: 0.2rem 0.55rem !important;
  border-radius: 999px !important;
  font-size: 0.76rem !important;
  font-weight: 600 !important;
  letter-spacing: 0.02em !important;
}

.badge {
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.badge-neutral {
  background: rgba(240, 235, 228, 0.9);
  color: var(--ink-base);
}

.badge-warning {
  background: color-mix(in srgb, var(--warning-soft) 72%, white 28%);
  color: var(--warning-ink);
}

.badge-success {
  background: color-mix(in srgb, var(--success-soft) 72%, white 28%);
  color: color-mix(in srgb, var(--success) 58%, black 42%);
}

/* Soft-tinted chip variants for the PDF placeholder help, mirroring the
     badge-success recipe: light-blue for the "Allgemein" group, light-lilac
     for the relation chips. */

.badge-blue {
  background: color-mix(in srgb, oklch(0.95 0.03 250) 72%, white 28%);
  color: color-mix(in srgb, oklch(0.55 0.13 250) 58%, black 42%);
}

.badge-lilac {
  background: color-mix(in srgb, oklch(0.95 0.04 305) 72%, white 28%);
  color: color-mix(in srgb, oklch(0.55 0.15 305) 58%, black 42%);
}

/* Light-amber for the Projektparameter chips (ADR-0045). */

.badge-amber {
  background: color-mix(in srgb, oklch(0.95 0.04 80) 72%, white 28%);
  color: color-mix(in srgb, oklch(0.55 0.12 80) 58%, black 42%);
}

/* PDF-Textblock placeholder help: one chip row per field group. */

.pdf-placeholder-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.35rem;
}

.pdf-placeholder-chips .badge {
  cursor: pointer;
}

.pdf-placeholder-chips .\!badge {
  cursor: pointer !important;
}

/* PDF-Textblock placeholder help, grouped: each group is a titled block
     (name + reusable "?" help trigger) followed by its own chip row. */

.pdf-placeholder-group {
  margin-bottom: 0.9rem;
}

.pdf-placeholder-group:last-child {
  margin-bottom: 0;
}

.pdf-placeholder-group-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}

.pdf-placeholder-group-title {
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* --- PDF-Richtext editor (Lexical, ADR-0036 §9, Slice 05) --------------- */

#pdf-editor {
  position: relative;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md, 8px);
  background: var(--bg-base, #fff);
}

.pdf-ed-toolbar {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex-wrap: wrap;
  padding: 0.4rem 0.5rem;
  border-bottom: 1px solid var(--line-soft);
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg-warm-table, #f6f4ef);
  border-radius: var(--radius-md, 8px) var(--radius-md, 8px) 0 0;
}

.pdf-ed-toolbar button {
  min-width: 2.1rem;
  height: 2.1rem;
  padding: 0 0.5rem;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-base);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  font-size: 0.9rem;
}

.pdf-ed-toolbar button:hover {
  background: color-mix(in srgb, var(--ink-base) 8%, transparent);
}

.pdf-ed-toolbar button.on {
  background: var(--accent-soft, #dfe6d5);
  border-color: var(--accent-border, #c3d1b3);
  color: var(--accent, #435f2d);
}

.pdf-ed-toolbar button.\!on {
  background: var(--accent-soft, #dfe6d5) !important;
  border-color: var(--accent-border, #c3d1b3) !important;
  color: var(--accent, #435f2d) !important;
}

.pdf-ed-toolbar button:disabled {
  opacity: 0.35;
  cursor: default;
}

.pdf-ed-toolbar .pdf-ed-sep {
  width: 1px;
  height: 1.4rem;
  background: var(--line-soft);
  margin: 0 0.25rem;
}

.pdf-ed-toolbar .ico-b {
  font-weight: 700;
}

.pdf-ed-toolbar .ico-i {
  font-style: italic;
  font-weight: 600;
}

.pdf-ed-zoom {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: auto;
  font-size: 0.8rem;
  color: var(--ink-soft, #6f6a5f);
}

.pdf-ed-zoom select {
  font-size: 0.8rem;
  padding: 0.1rem 0.25rem;
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  background: transparent;
}

.pdf-ed-toolbar .swatch-preview {
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 4px;
  border: 1px solid rgba(0, 0, 0, 0.2);
  background: #dc2626;
}

/* The surface IS the paper: it carries the PDF text column's width, so a line
     breaks on screen exactly where WeasyPrint breaks it. Zoom uses `zoom`, which
     scales the sheet and its text together (Word-style) — the paper edge stays
     the paper edge at every step. A font-size multiplier would grow the text
     inside a fixed sheet instead, which is what it must not do.

     The three knobs below mirror the @page rule in pdf/nrw_blatt.html
     (`size: A4; margin: 15mm 5mm 15mm 20mm`): the sheet is the full A4 width and
     the margins are real padding, so the text column comes out at exactly
     210 − 20 − 5 = 185mm without any arithmetic of its own. Change them here when
     that @page rule changes; they are not read from the template (one Blatt
     template, one hardcoded @page rule — parsing CSS to recover three numbers
     would be a lot of machinery for values that have never moved).

     Typography mirrors `.textblock` in the same template. CSS pt is a fixed
     4/3 px — the same conversion WeasyPrint applies — so the pt the toolbar
     reports is the pt the PDF prints. */

#pdf-editor {
  --pdf-ed-paper: 210mm;
  --pdf-ed-margin-left: 20mm;
  --pdf-ed-margin-right: 5mm;
}

/* Grey desk around the sheet, and the viewport that owns both scrollbars once
     the zoomed sheet outgrows it. Standard canvas-app containment (Figma, Docs,
     Acrobat): the viewport is a fixed box, the sheet scrolls *inside* it, and
     neither zoom nor added content may change the box's size.

     `contain: inline-size` and the fixed `height` are what actually enforce it.
     Without them the sheet's layout width (CSS `zoom` is a layout property,
     unlike `transform`) propagates outward through the flex/grid parents and
     stretches the whole tab panel. */

.pdf-ed-desk {
  display: flex;
  /* Sheet pinned to the top of the desk, not vertically centred. */
  align-items: flex-start;
  overflow: auto;
  min-width: 0;
  height: 60vh;
  background: var(--bg-muted, #f3f2ef);
  padding: 0.75rem 0;
  /* Width is taken from the parent, never from the sheet: `inline-size` stops
       the zoomed sheet's intrinsic width from propagating outward (what widened
       the tab panel) without collapsing the desk when a parent happens to be
       content-sized, as plain `contain: size` would. Height is fixed above. */
  contain: inline-size layout paint;
  overscroll-behavior: contain;
}

.pdf-ed-surface {
  width: var(--pdf-ed-paper);
  box-sizing: border-box;
  /* Horizontal `auto` only: `margin: 0 auto` would clip the sheet's left edge
       out of reach once it is wider than the desk (a known overflow-centring
       bug), while auto margins on a flex item overflow symmetrically. Vertically
       the sheet stays at the top — a centred sheet drifts down as the desk grows,
       and the top edge is where authors read and type. The desk's own padding
       supplies the gap above. */
  margin: 0 auto auto;
  /* The sheet is 210mm, full stop. Without this the flex default
       `shrink: 1` narrows it whenever the desk is narrower, which would quietly
       undo the mm fidelity the surface exists to guarantee. */
  flex: 0 0 auto;
  min-height: 16rem;
  /* Left/right are the real @page margins, so the text column is exactly the
       printed one. Vertical padding only needs to look like a page edge. */
  padding: 8mm var(--pdf-ed-margin-right) 8mm var(--pdf-ed-margin-left);
  outline: none;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.16);
  font-family: Helvetica, "Helvetica Neue", Arial, sans-serif;
  font-size: 10pt;
  line-height: 1.42;
  -webkit-hyphens: auto;
          hyphens: auto;
  zoom: var(--pdf-ed-zoom, 1);
}

/* Keep the sheet's drop shadow while focused — a bare inset ring would drop
     the paper effect the moment the author clicks into the text. */

.pdf-ed-surface:focus {
  box-shadow:
      inset 0 0 0 2px var(--accent-soft, #dfe6d5),
      0 1px 4px rgba(0, 0, 0, 0.16);
}

/* The global `p { max-width: 72ch }` readability cap must not apply here: on
     the sheet the line length is the printed one (185mm), not a comfort choice.
     Left in, 72ch of 10pt Helvetica wraps at ~141mm and the preview lies about
     where lines break. */

.pdf-ed-surface p {
  margin: 0 0 0.6em;
  max-width: none;
}

.pdf-ed-bold {
  font-weight: 700;
}

.pdf-ed-italic {
  font-style: italic;
}

.pdf-ed-divider {
  border: none;
  border-top: 0.1mm solid #d9d3c6;
  margin: 0.6rem 0;
}

.pdf-ed-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  cursor: pointer;
  vertical-align: baseline;
  background: color-mix(in srgb, oklch(0.95 0.03 220) 72%, white 28%);
  color: color-mix(in srgb, oklch(0.5 0.12 220) 58%, black 42%);
  border: 1px dashed currentColor;
}

.pdf-ed-pill.rel {
  background: color-mix(in srgb, oklch(0.95 0.04 305) 72%, white 28%);
  color: color-mix(in srgb, oklch(0.55 0.15 305) 58%, black 42%);
}

.pdf-ed-pill .mod {
  opacity: 0.6;
  font-weight: 500;
}

.pdf-ed-popover {
  position: absolute;
  z-index: 50;
  background: var(--bg-base, #fff);
  border: 1px solid var(--line-strong, #d8cfbd);
  border-radius: 8px;
  padding: 0.6rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}

.pdf-ed-assist-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 0.5rem;
  padding: 0.3rem 0.45rem;
  border: 1px solid var(--line-strong, #d8cfbd);
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  outline: none;
}

.pdf-ed-assist-input:focus {
  border-color: var(--accent, #2563eb);
}

.pdf-ed-swatch-grid {
  display: grid;
  grid-template-columns: repeat(10, 1.25rem);
  gap: 0.3rem;
}

.pdf-ed-swatch {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 4px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  cursor: pointer;
  padding: 0;
}

.pdf-ed-swatch:hover {
  transform: scale(1.15);
}

.pdf-ed-swatch.none {
  background: #fff;
  position: relative;
}

.pdf-ed-size-presets {
  display: flex;
  flex-direction: column;
  max-height: 12rem;
  overflow-y: auto;
  margin-top: 0.4rem;
}

.pdf-ed-size-presets button {
  text-align: left;
  padding: 0.2rem 0.5rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 4px;
}

.pdf-ed-size-presets button:hover {
  background: var(--bg-warm-table, #f6f4ef);
}

.pdf-ed-size-field {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line-strong, #d8cfbd);
  border-radius: 6px;
  overflow: hidden;
  height: 2rem;
}

.pdf-ed-size-field input {
  border: 0;
  width: 3rem;
  text-align: right;
  padding: 0 0.3rem;
  outline: none;
  background: transparent;
}

.pdf-ed-size-field button {
  width: 1.6rem;
  height: 100%;
  border: 0;
  border-left: 1px solid var(--line-soft);
  background: var(--bg-warm-table, #f6f4ef);
  cursor: pointer;
}

.table-scroll {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
}

.data-table thead th {
  padding: 0.95rem 1.25rem;
  background: var(--bg-warm-table);
  color: var(--ink-soft);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: left;
  text-transform: uppercase;
}

.data-table thead th:last-child,
  .data-table tbody td:last-child {
  text-align: right;
}

.data-table tbody tr {
  transition: background-color var(--t-base) var(--ease);
}

.data-table tbody tr:hover {
  background: rgba(246, 250, 253, 0.75);
}

.data-table tbody td {
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--border-divider-faint);
  color: var(--ink-base);
  font-size: 0.92rem;
  vertical-align: top;
}

.table-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.75rem;
}

.form-stack {
  display: grid;
  gap: 1.4rem;
}

.field-stack {
  display: grid;
  gap: 0.5rem;
}

/* Password input with an overlaid show/hide eye toggle. The wrapper keeps
     the input full-width; the button floats over the trailing edge and the
     input reserves right padding so typed text never slides under the icon. */

.password-field {
  position: relative;
  display: grid;
}

.password-field > input {
  padding-right: 3rem;
}

.password-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 2.875rem;
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  border-radius: var(--radius-md);
  transition: color var(--t-base) var(--ease);
}

.password-toggle:hover {
  color: var(--ink-strong);
}

.password-toggle:focus-visible {
  outline: none;
  color: var(--ink-strong);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-soft) 82%, white 18%);
}

.password-toggle svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* Icon swap is driven purely by aria-pressed so it cannot be defeated by a
     global `svg { display: block }` reset overriding the [hidden] attribute.
     Default (pressed=false → password hidden): show the eye.
     Pressed=true (password visible): show the eye-slash. */

.password-toggle .password-toggle-hide {
  display: none;
}

.password-toggle[aria-pressed="true"] .password-toggle-show {
  display: none;
}

.password-toggle[aria-pressed="true"] .password-toggle-hide {
  display: block;
}

/* ---------------------------------------------------------------
     Object-Detail Standard Shell (Issue 1 redesign).

     Flex-column layout: topbar → breadcrumb → header → main-row → drawer.
     The main-row itself is a flex-row: tree | properties-scroller | aside.
  --------------------------------------------------------------- */

.detail-shell {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.detail-object-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 1.25rem;
  border-bottom: 1px solid var(--line-softer);
  background: var(--surface-raised);
  gap: 1rem;
}

.detail-class-tag {
  font-size: 0.5rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 0.1rem 0.5rem;
  background: white;
  color: var(--ink-strong);
  border: 1px solid transparent;
  text-transform: uppercase;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  flex-shrink: 0;
}

/* When a class color is provided via --class-color, render the tag as a
     translucent chip — same pattern as hotspot pins, but using color-mix
     with transparent so only the background is translucent (text stays
     sharp). Text color comes from --class-text-color (server-computed
     black/white contrast). */

.detail-class-tag[style*="--class-color"] {
  background-color: color-mix(in srgb, var(--class-color) 50%, transparent);
  color: var(--class-text-color, var(--ink-strong));
}

.detail-object-identifier {
  font-size: 1.4rem;
  color: var(--accent-strong);
  font-weight: 600;
  font-family: var(--font-prominent);
  letter-spacing: -0.005em;
  min-width: 0;
}

.detail-object-header-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* Compact, hierarchy-aware header buttons — only inside the detail header.
     Outside (dialogs, lists) the global .button pill (2.75rem) stays. */

.detail-object-header-actions .button {
  min-height: 2rem;
  padding: 0.4rem 1rem;
  font-size: 0.875rem;
  box-shadow: none;
}

.detail-object-header-actions .\!button {
  min-height: 2rem !important;
  padding: 0.4rem 1rem !important;
  font-size: 0.875rem !important;
  box-shadow: none !important;
}

.detail-object-header-actions .button-secondary {
  border-color: var(--line-soft);
  background: white;
  color: var(--ink-base);
}

.detail-object-header-actions .button-secondary:hover {
  border-color: color-mix(in srgb, var(--accent) 24%, var(--line-soft) 76%);
  background: white;
  color: var(--ink-strong);
}

.detail-object-header-actions .button-warning {
  border-color: var(--warning-bg);
  background: white;
  color: var(--warning-ink);
}

.detail-object-header-actions .button-warning:hover {
  border-color: var(--warning-border-strong);
  background: color-mix(in srgb, var(--warning-soft) 50%, white 50%);
}

/* Inline-Dokument · Slice 06 — Objekt-Header as a quiet title block.
     The standard detail header drops the chrome bar (border/raised bg) and
     aligns to the document measure (780px column, 44px side padding) so it sits
     flush with the spy nav + document below. File detail keeps the plain
     .detail-object-header bar (this scopes everything under .doc-header). */

.detail-object-header.doc-header {
  display: block;
  /* Match the scroller's outer 1.25rem gutter (.detail-properties-scroller)
       so the header's 780px measure centres on the same axis as the spy nav +
       document below it. Without the extra 1.25rem the header drifts left of
       the document once the main column narrows (e.g. the Anhänge aside is
       widened) and both boxes fall back to width:100%. */
  max-width: calc(780px + 2 * 1.25rem);
  margin: 0 auto;
  padding: 1.5rem calc(44px + 1.25rem) 0.75rem;
  width: 100%;
  background: var(--eigen-bg);
  border-bottom: none;
}

.doc-header-meta {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.25rem;
}

.doc-header-dates {
  font-size: 11.5px;
  color: var(--ink-softest);
}

.doc-header-titlebar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.doc-header-kennung {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--ink-strong);
  /* Override the inherited .detail-object-identifier serif/accent styles. */
  font-family: inherit;
  margin: 0;
  /* Baseline-align the trailing Bezeichnung. */
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.6rem;
  min-width: 0;
}

.doc-header-bezeichnung {
  font-size: 18px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-soft);
}

/* In the file-class centre column the header sits above a full-width toolbar
     + file stage, so it spans the column (left-aligned) instead of centring on
     the 780px document measure used by the standard detail document. */

.detail-file-center .detail-object-header.doc-header {
  max-width: none;
  margin: 0;
  padding: 1rem 0.75rem 0.75rem;
}

/* Speichern — filled accent button, 30px tall, radius 7. */

.inline-header-save {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 30px;
  min-height: 30px;
  padding: 0 0.85rem;
  border: none;
  border-radius: 12px;
  background: var(--accent-strong);
  color: white;
  font-size: 0.875rem;
  font-weight: 600;
  box-shadow: none;
  cursor: pointer;
}

.inline-header-save:hover {
  background: color-mix(in srgb, var(--accent-strong) 88%, black 12%);
}

.inline-header-save-icon {
  width: 1rem;
  height: 1rem;
}

/* Kebab — 30×30 tinted toggle bundling the rarer actions in a popover. */

.inline-header-kebab {
  position: relative;
}

.inline-header-kebab-toggle {
  display: inline-flex;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: #dcd7cc;
  color: var(--ink-base);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/* Hide the native disclosure triangle (WebKit + standards). */

.inline-header-kebab-toggle::-webkit-details-marker {
  display: none;
}

.inline-header-kebab-toggle:hover {
  background: color-mix(in srgb, #dcd7cc 80%, black 12%);
}

/* Secondary variant: a kebab styled like a .tile-link card — white
     background with a soft gray border (project header — replaces the former
     Mitglieder/Einstellungen/Archivieren row). */

.inline-header-kebab-toggle-secondary {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-sand);
  background: var(--bg-card);
  color: var(--ink-base);
  box-shadow: 0 1px 2px rgba(var(--_shadow-warm) / 0.04);
}

.inline-header-kebab-toggle-secondary:hover {
  border-color: var(--accent-border);
  background: var(--bg-card);
  box-shadow: var(--shadow-md);
}

.inline-header-menu {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 30;
  min-width: 248px;
  padding: 0.35rem;
  background: var(--surface-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  box-shadow: 0 12px 28px rgba(var(--_ink-paper) / 0.16);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.inline-header-menu-item {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-strong);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.inline-header-menu-item:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* A blocked action stays visible with its reason in the hint (e.g. Reihenfolge
     speichern while the table search filters the rows, ADR-0037 §2). */

.inline-header-menu-item:disabled {
  color: var(--ink-soft);
  cursor: not-allowed;
}

.inline-header-menu-icon {
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
  margin-top: 0.1rem;
  color: var(--ink-soft);
}

.inline-header-menu-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.inline-header-menu-label {
  font-size: 0.875rem;
  font-weight: 600;
}

.inline-header-menu-hint {
  font-size: 11.5px;
  color: var(--ink-softest);
}

.inline-header-menu-rule {
  margin: 0.3rem 0.2rem;
  border: none;
  border-top: 1px solid var(--line-softer);
}

.inline-header-menu-item-danger {
  color: var(--warning-ink);
}

.inline-header-menu-item-danger .inline-header-menu-icon {
  color: var(--warning-ink);
}

.inline-header-menu-item-danger:hover {
  background: color-mix(in srgb, var(--warning-soft) 55%, transparent);
}

.detail-main-row {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
}

.detail-properties-scroller {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  /* Stop scroll chaining: without this, overscrolling the document past its
       end bubbles to the root and the macOS rubber-band lifts the whole
       100dvh shell, exposing white page background below the drawer. */
  overscroll-behavior: contain;
  background: var(--eigen-bg);
  /* No top padding: the sticky spy nav must sit flush under the object
       header. A top gap here would be a transparent strip the document scrolls
       through, leaking field text above the links bar. */
  padding: 0 1.25rem 1.25rem;
  display: flex;
  flex-direction: column;
}

/* Only the document scroller scrolls; the header + spy nav above it stay
     fixed (like the standalone reference). The scroller must shrink within
     this flex column (min-height:0 on the scroller), otherwise .detail-main
     itself would scroll and carry the header out of view. */

.detail-main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--eigen-bg);
}

/* Anhänge-Aside: .collapsible-strip instance on standard objects.
     Per-instance specs only — strip mechanic comes from .collapsible-strip. */

.detail-aside {
  position: relative;
  --strip-expanded-width: var(--detail-aside-w, 296px);
  border-left: 1px solid var(--line-softer);
  /* Minimal lift off the detail area (shadow points toward the center). */
  box-shadow: -4px 0 4px -2px rgb(0 0 0 / 0.06);
}

/* Swap-Host des Anhänge-Fragments: nur ein Griff für den Tausch, kein
     Layout-Kasten — Schiene und Inhalt bleiben Flex-Kinder von
     .collapsible-strip (siehe detail_standard.html). */

.anhaenge-aside-host {
  display: contents;
}

/* Issue 8: Mobile breakpoints — flex-based, no grid-template-areas -------- */

@media (max-width: 780px) {
  .detail-main-row {
    flex-direction: column;
  }

  .detail-aside,
    .detail-aside.is-expanded {
    flex: 0 0 auto;
    width: 100%;
    border-left: none;
    border-top: 1px solid var(--line-softer);
  }
}

.detail-aside-header {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.4rem 0.4rem;
}

.detail-aside-header .badge {
  margin-left: auto;
}

.detail-aside-header .\!badge {
  margin-left: auto !important;
}

.detail-aside-toggle {
  border: none;
  font: inherit;
  color: var(--ink-strong);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.detail-aside-body {
  display: grid;
  gap: 0.75rem;
  padding-top: 0.6rem;
}

.detail-aside-card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.detail-aside-card {
  background: var(--surface-base);
  border: 0px solid var(--line-soft);
  border-radius: 0rem;
  padding: 0.5rem;
  display: grid;
  gap: 0.2rem;
}

/* The trailing controls (mode toggle, "+", "-") cluster on the right, and
     the title takes every bit of free space to push them there. NOT
     ``justify-content: space-between``: that spreads the free space between
     ALL children, so a header with three of them put the middle one in the
     middle of the aside. The single view hid that behind the mode toggle's
     ``margin-left: auto``; the multi view has no mode toggle (ADR-0051 -- no
     preview mode across a selection), so its "+" drifted to the centre the
     moment the card grew a "-" (which renders only once an attachment
     exists). Letting the title claim the space is the one rule that holds for
     every combination of controls. */

.detail-aside-card-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.detail-aside-card-header > .detail-aside-card-title,
  .detail-aside-card-header > .detail-aside-card-title-text {
  margin-right: auto;
}

.detail-aside-card-title {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: transparent;
  border: none;
  padding: 0;
  font-weight: 600;
  color: var(--ink-strong);
  font-size: 0.9rem;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
}

.detail-aside-card-title:hover {
  color: var(--ink-strong);
}

.detail-aside-card-caret {
  display: inline-block;
  font-size: 0.7rem;
  line-height: 1;
  color: var(--ink-soft);
  transition: transform var(--t-fast) var(--ease);
}

.detail-aside-card[data-collapsed="true"] .detail-aside-card-caret {
  transform: rotate(-90deg);
}

/* Collapse: hide everything in the card except its header. */

.detail-aside-card[data-collapsed="true"] > :not(.detail-aside-card-header) {
  display: none;
}

.detail-aside-card-add,
  .detail-aside-card-remove {
  padding: 0.1rem 0.45rem;
  font-size: 1rem;
  line-height: 1;
}

/* Lösen-Modal (ADR-0053): Vorschau-Kacheln mit Kennung, mehrere wählbar.
     Jede Kachel nennt ihre Folge; „choice" trägt zusätzlich die Wahl zwischen
     Trennen und Archivieren. */

.anhaenge-remove-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.5rem;
  max-height: 60vh;
  overflow-y: auto;
}

.anhaenge-remove-tile-label {
  display: grid;
  grid-template-columns: auto 3rem 1fr;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.anhaenge-remove-tile-image {
  width: 3rem;
  height: 3rem;
  -o-object-fit: cover;
     object-fit: cover;
  border-radius: var(--radius-sm);
}

.anhaenge-remove-tile-icon {
  text-align: center;
}

.anhaenge-remove-tile-note {
  grid-column: 2 / -1;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

/* Ausgegraut, solange „diesen Datensatz archivieren" gewählt ist: dieser
     Ausgang navigiert weg und verträgt sich mit keinem anderen. */

.anhaenge-remove-tile-check:disabled + .anhaenge-remove-tile-image,
  .anhaenge-remove-tile-check:disabled ~ .anhaenge-remove-tile-label-text,
  .anhaenge-remove-tile-check:disabled ~ .anhaenge-remove-tile-note {
  opacity: 0.5;
}

.anhaenge-remove-tile-choice {
  display: flex;
  gap: 0.75rem;
  padding: 0.15rem 0.4rem 0.4rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.anhaenge-remove-summary {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
}

/* Multi view (ADR-0051 §2): the "+" is a form POST, not a link, because the
     selection rides in the body. The wrapper must not become a layout of its
     own — the header is a flex row expecting the button itself. */

.detail-aside-card-add-form {
  display: contents;
}

.detail-aside-item-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}

.detail-aside-item {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.detail-aside-item .detail-aside-item-link {
  flex: 1;
  min-width: 0;
}

.detail-aside-item-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.4rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--ink-strong);
}

.detail-aside-item-link:hover {
  background: var(--surface-soft);
}

.detail-aside-item-thumb {
  width: 2rem;
  height: 2rem;
  -o-object-fit: cover;
     object-fit: cover;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.detail-aside-item-icon {
  width: 2rem;
  text-align: center;
}

.detail-aside-item-label {
  font-size: 0.85rem;
}

.detail-aside-card-empty {
  color: var(--ink-soft);
  font-size: 0.85rem;
  margin: 0;
}

/* Anhänge-Tab slice 06: the multi view's card title is a plain span, not the
     collapse button — a Gemeinsamkeits card has no per-card gestures at all
     (no collapse, no mode toggle, no "+"). Same weight and size as the
     button's label so both asides read alike. */

.detail-aside-card-header > .detail-aside-card-title-text {
  font-weight: 600;
  color: var(--ink-strong);
  font-size: 0.9rem;
}

/* Mode-toggle (Issue 06): single button that switches list <-> preview. */

.detail-aside-mode-toggle {
  display: inline-flex;
  background: transparent;
  border: 1px solid var(--line-softer);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.4rem;
  font-size: 0.85rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  margin-left: auto;
}

.detail-aside-mode-toggle:hover {
  background: var(--surface-soft);
  color: var(--ink-strong);
}

/* Mode-switching: show preview OR list view ---------------------- */

.detail-aside-card[data-aside-mode="preview"] .detail-aside-item-list {
  display: none;
}

.detail-aside-card[data-aside-mode="list"] .detail-aside-preview {
  display: none;
}

/* Preview mode (Issue 06) --------------------------------------- */

.detail-aside-preview {
  display: grid;
  gap: 0.4rem;
}

.detail-aside-hero {
  position: relative;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--surface-soft);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.detail-aside-hero-link {
  display: flex;
  width: 100%;
  height: 100%;
  text-decoration: none;
  color: inherit;
}

/* `contain`, not `cover`: portrait/panorama previews were cropped by the
     4/3 hero box. Showing the whole image with letterbox bands is preferred. */

.detail-aside-hero-image {
  width: 100%;
  height: 100%;
  -o-object-fit: contain;
     object-fit: contain;
}

.detail-aside-hero-generic {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  color: var(--ink-soft);
}

/* `display: flex` overrides the UA `[hidden] { display: none }` rule, so the
     carousel JS toggling `el.hidden` would leave the 📄 fallback visible behind
     a preview without this explicit override (mirrors the hotspot-toolbar and
     popover patterns above). */

.detail-aside-hero-generic[hidden] {
  display: none;
}

.detail-aside-hero-generic-icon {
  font-size: 2.2rem;
  line-height: 1;
}

.detail-aside-hero-generic-label {
  font-size: 0.85rem;
}

.detail-aside-hero-badge {
  position: absolute;
  top: 0.4rem;
  left: 0.4rem;
  background: rgba(0, 0, 0, 0.65);
  color: white;
  font-size: 0.75rem;
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-sm);
  max-width: calc(100% - 4rem);
}

.detail-aside-hero-indicator {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  background: rgba(0, 0, 0, 0.65);
  color: white;
  font-size: 0.75rem;
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-sm);
}

/*  from 1.6rem: the old circles were hard to hit on a tablet. */

.detail-aside-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  width: 1.8rem;
  height: 2.9rem;
  border-radius: 10%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  line-height: 1;
}

.detail-aside-nav:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.detail-aside-nav-prev {
  left: 0.3rem;
}

.detail-aside-nav-next {
  right: 0.3rem;
}

.detail-aside-thumb-strip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
  gap: 0.25rem;
}

.detail-aside-thumb {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 2px solid transparent;
  background: var(--surface-soft);
  cursor: pointer;
  display: flex;
}

.detail-aside-thumb.is-active {
  border-color: var(--ink-strong);
}

.detail-aside-thumb-image {
  width: 100%;
  height: 100%;
  -o-object-fit: cover;
     object-fit: cover;
}

.detail-aside-thumb-icon {
  font-size: 1rem;
  color: var(--ink-soft);
}

/* Tree-Panel ---------------------------------------------------- */

.tree-panel {
  position: relative;
  flex: 0 0 var(--detail-tree-w, 296px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface-raised);
  border-right: 1px solid var(--line-softer);
  padding: 0.6rem 0.75rem;
  /* Minimal lift off the detail area (shadow points toward the center). */
  box-shadow: 4px 0 4px -2px rgb(0 0 0 / 0.06);
}

/* See .collapsible-strip: gate the width animation behind hydration so the
     first paint / hx-boost swap snaps to the persisted state without a
     visible collapse→expand animation. */

html.detail-hydrated .tree-panel {
  transition: flex-basis var(--t-base) var(--ease);
}

.tree-panel.is-collapsed {
  flex: 0 0 38px;
  overflow: hidden;
  padding: 0.5rem 0;
}

.tree-panel-collapsed-strip {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0;
  border: none;
  width: 100%;
}

.tree-panel.is-collapsed .tree-panel-collapsed-strip {
  display: flex;
}

.tree-panel.is-collapsed .tree-panel-header,
  .tree-panel.is-collapsed .tree-panel-trail {
  display: none;
}

/* Pre-hydration marker (see base.html inline script): mirrors .is-collapsed
     so the first paint matches the persisted state — no JS-driven flicker. */

html.pre-tree-collapsed .tree-panel {
  flex: 0 0 38px;
  overflow: hidden;
  padding: 0.5rem 0;
}

html.pre-tree-collapsed .tree-panel .tree-panel-collapsed-strip {
  display: flex;
}

html.pre-tree-collapsed .tree-panel .tree-panel-header,
  html.pre-tree-collapsed .tree-panel .tree-panel-trail {
  display: none;
}

.tree-panel-strip-toggle {
  border: none;
  font-size: 1.1rem;
  color: var(--ink-soft);
  padding: 0.2rem;
  line-height: 1;
}

/* Fokus-Modus (Slice 08): hide all chrome so only the document remains. The
     class lives on <html> (persists across hx-boost). The properties scroller
     then spans the full width while .doc-column keeps its 780px centering. */

html.detail-focus-mode .tree-panel,
  html.detail-focus-mode .detail-drawer,
  html.detail-focus-mode .detail-aside,
  html.detail-focus-mode .detail-datei-liste-column,
  html.detail-focus-mode .detail-file-aside {
  display: none;
}

.tree-panel-strip-label {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--ink-soft);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.tree-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0rem 0.1rem;
}

.tree-panel-toggle {
  border: none;
  font: inherit;
  color: var(--ink-strong);
  font-weight: 600;
  width: 100%;
  text-align: left;
  padding: 0.25rem 0.1rem;
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.tree-panel-toggle [aria-hidden] {
  margin-left: auto;
}

.tree-panel-trail {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.1rem;
}

.tree-panel-step {
  padding-left: calc(var(--depth, 0) * 0.75rem);
}

.tree-panel-link {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.4rem;
  text-decoration: none;
  color: var(--ink-base);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
}

.tree-panel-link:hover {
  background: var(--surface-soft);
  color: var(--ink-strong);
}

.tree-panel-link-current {
  color: var(--ink-strong);
  font-weight: 600;
  background: var(--surface-soft);
}

/* Sammelbearbeitung (ADR-0046): entries in the ephemeral multi-selection. */

.tree-panel-link.is-multi-selected {
  background: var(--accent-soft);
  color: var(--ink-strong);
  outline: 1px solid var(--accent-border-strong);
  outline-offset: -1px;
}

.tree-panel-link-id {
  flex: 1;
  min-width: 0;
}

.tree-panel-class-tag {
  flex-shrink: 0;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  padding: 0.15rem 0.75em;
  border-radius: var(--radius-sm);
  background: var(--surface-soft);
  color: var(--ink-soft);
}

.tree-panel-class-tag[style*="--class-color"] {
  background-color: color-mix(in srgb, var(--class-color) 35%, transparent);
  color: var(--class-text-color, var(--ink-strong));
}

.tree-panel-count {
  margin-left: 0.3rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
  font-weight: 400;
}

.tree-panel-children {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tree-panel-children-collapsed {
  display: none;
}

/* ---------------------------------------------------------------
     Datei-Detail Shell (PRD 2, Issue 3 / redesign Issue 9).

     Datei-Liste links (fixed width), Stage + Eigenschaften Mitte,
     Drawer unten (full width).  Built on the flex-column .detail-shell
     from Issue 1: an inner flex-row wraps Liste + Main side by side.
  --------------------------------------------------------------- */

.detail-shell-file {
  /* inherits flex-direction: column from .detail-shell */
}

/* Datei-Liste-Column: .collapsible-strip instance on file-class objects.
     Per-instance specs only — strip mechanic comes from .collapsible-strip. */

.detail-datei-liste-column {
  position: relative;
  --strip-expanded-width: var(--detail-datei-liste-w, 16rem);
  min-width: 0;
  border-right: 1px solid var(--line-softer);
}

/* Center column: holds object header + file stage.
     The center itself does not scroll; instead the inner stage-wrap owns
     the remaining height. This lets the hotspot-viewer fill the space
     between topbar/header and the (collapsed) drawer in preview-mode.
     In mosaic-mode the gallery inside scrolls. */

.detail-file-center {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Mosaic mode: the toolbar belongs at the bottom edge like the viewer-owned
     one in preview mode. Its markup sits before the stage in the template (it
     is shared with the polling/non-visual states), so the order is flipped
     here rather than by moving the block. `:has(.detail-mosaic)` is the mode
     marker — the centre column carries none of its own. */

.detail-file-center:has(.detail-mosaic) > .file-detail-toolbar {
  order: 1;
}

.detail-file-stage-wrap {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Mosaic gallery scrolls inside the stage-wrap so the centre column
     stays anchored. */

.detail-file-stage-wrap > .detail-mosaic {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* In preview-mode the stage-wrap holds .file-stage > .file-stage-body >
     #file-widget > .file-filled > .hotspot-viewer. Propagate height through
     that chain so the viewer stage can fill the available room. Each link
     becomes a flex-column with `flex: 1 1 auto; min-height: 0` so a child
     can claim the leftover space. */

.detail-file-stage-wrap > .file-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.detail-file-stage-wrap .file-stage-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.detail-file-stage-wrap #file-widget {
  flex: 1 1 auto;
  min-height: 0;
  margin-bottom: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.detail-file-stage-wrap #file-widget > .file-filled {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  /* `position: relative` so the bottom-left meta overlay anchors to the
       stage frame, not the page. */
  position: relative;
}

.detail-file-stage-wrap .hotspot-viewer {
  flex: 1 1 auto;
  min-height: 0;
}

/* The toolbar is the viewer's last child and should sit flush on the bottom
     edge of the image area — no strip of page background between it and the
     drawer below. The gap that `.file-filled` sets for its other states would
     otherwise show as that seam, as would `.surface-pad`'s padding. */

.detail-file-stage-wrap #file-widget {
  padding-bottom: 0;
}

.detail-file-stage-wrap #file-widget > .file-filled:has(.hotspot-viewer) {
  gap: 0;
}

/* No-preview placeholder: dummy icon + hint centred on the canvas, no
     background fill or frame so it sits plainly on the stage. */

.file-stage-empty {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  text-align: center;
}

.file-stage-empty-icon {
  font-size: 2.5rem;
  line-height: 1;
  opacity: 0.5;
}

/* Both of these carried inline styles until the bottom seam under the viewer
     toolbar needed stylesheet rules to override them — inline styles outrank
     stylesheets, so `.detail-file-stage-wrap`'s `margin-bottom: 0` never took
     effect and left a 1.5rem strip under the toolbar. */

.file-widget {
  margin-bottom: 1.5rem;
}

.file-filled {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* 360° panorama viewer fills the stage like the 2D hotspot viewer.
     Overrides the standalone 480px fallback from the [data-panorama-viewer]
     rule below. Double selector matches the specificity boost used there so
     this rule wins inside the detail stage. */

.detail-file-stage-wrap [data-panorama-viewer][data-panorama-viewer] {
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
}

/* Filename + size overlay (UI cleanup 2026-05). Floats on the bottom-left
     of the file stage so it costs no vertical space. Semi-transparent
     background so the underlying preview stays readable. */

.file-stage-meta-overlay {
  position: absolute;
  left: 0.75rem;
  bottom: 0.75rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.6rem;
  background: rgba(17, 24, 39, 0.72);
  color: #f8fafc;
  border-radius: var(--radius-sm);
  font-size: 0.8rem;
  pointer-events: none;
  z-index: 5;
  max-width: calc(100% - 1.5rem);
}

/* `display: inline-flex` above outranks the global `[hidden] { display: none }`
     rule at equal specificity (later in the stylesheet wins), so this
     experiment (hiding the overlay entirely) needs an explicit override. */

.file-stage-meta-overlay[hidden] {
  display: none;
}

.file-stage-meta-overlay .file-stage-meta-name {
  color: #f8fafc;
}

.file-stage-meta-overlay .file-stage-meta-size {
  color: rgba(248, 250, 252, 0.78);
  font-size: 0.75rem;
}

/* Unified file-detail toolbar (UI cleanup 2026-05): one horizontal row
     containing the mode-toggle (left), the hotspot pin/zoom/filter/fit
     buttons (middle), and the per-file actions (right). The hotspot
     viewer template renders this element with both `.file-detail-toolbar`
     and `.hotspot-viewer-toolbar` so existing viewer-scoped JS keeps
     finding its buttons. `width: 100%` makes the row claim the full
     centre column — without it the row only stretches to fit its
     children and `margin-left:auto` on the right-cluster collapses. */

/* Floating-bar look: every file-detail toolbar stays in flow (the image /
     content starts below it, no overlap) but reads as a raised strip across
     the full width — a soft shadow gives it depth instead of sitting flat. */

.file-detail-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  width: 100%;
  box-sizing: border-box;
  /* Design test: the toolbar reads as an overlay strip like the hero badge
       that shows file name and size — dark and translucent instead of the
       raised paper surface. Applies to all three instances: the 2D viewer,
       the panorama viewer and the mosaic view. */
  background: rgba(0, 0, 0, 0.65);
  color: white;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}

/* No `backdrop-filter` on this element, deliberately: the kebab menu is a
     descendant that overhangs the toolbar into the image, and a filter here
     would make the toolbar its backdrop root — the menu's own blur would
     then sample the toolbar instead of the image behind it. */

.file-detail-toolbar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Kebab variant of the actions cluster: anchors the popover and hides the
     native <details> disclosure triangle so the summary reads as a round
     toolbar icon button. */

.file-detail-actions-menu {
  position: relative;
}

.file-detail-actions-toggle {
  cursor: pointer;
  list-style: none;
}

.file-detail-actions-toggle::-webkit-details-marker {
  display: none;
}

/* Visual breather between logical button clusters (pin controls / zoom)
     in the file-detail toolbar. Uses left margin instead of a bigger gap
     so it only widens at that one spot, not between every button. */

.file-detail-toolbar-divider {
  width: 0;
  align-self: stretch;
  margin-left: 0.5rem;
}

.detail-mosaic {
  padding: 0.75rem;
}

/* Eigenschaften-Aside: .collapsible-strip instance on file-class objects.
     Per-instance specs only — strip mechanic comes from .collapsible-strip. */

.detail-file-aside {
  position: relative;
  --strip-expanded-width: var(--detail-file-aside-w, 22rem);
  border-left: 1px solid var(--line-softer);
  /* Minimal lift off the detail area (shadow points toward the center). */
  box-shadow: -4px 0 4px -2px rgb(0 0 0 / 0.06);
}

.detail-file-aside-header {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.75rem 0.5rem 0.5rem;
  flex-shrink: 0;
}

.detail-file-aside-toggle {
  border: none;
  font: inherit;
  color: var(--ink-strong);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
}

/* ADR-0050 §3: "Eigenschaften · Anhänge" sits IN the head, not in a strip
     below it. With tabs present the collapse toggle shrinks to its chevron
     (it no longer carries the title), so the titles get the remaining row. */

.detail-file-aside-header:has(.detail-file-aside-tabs)
    .detail-file-aside-toggle {
  width: auto;
  flex: 0 0 auto;
}

/* Both aside swap hosts are pure slots: while a plan pin is selected,
     multi-edit.js parks the page's own head / Anhänge panel in a stash and
     drops the multi panel's own in here. ``display: contents`` keeps the
     adopted child laying out as if it were the header's (resp. the strip
     content's) direct child, so the slot itself never shows. The panel-side
     wrapper [data-multi-aside-head] needs it for the same reason: it survives
     the move, and without it the tab strip would be a flex item of the
     wrapper instead of the header, one nesting level too deep. */

[data-multi-aside-head-host],
  [data-multi-aside-panel-host],
  [data-multi-aside-page-panel-slot],
  [data-multi-aside-head] {
  display: contents;
}

.detail-file-aside-tabs {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-width: 0;
}

/* Pill tabs in the Beziehungs-Drawer idiom (.detail-drawer-tab): the two
     titles were only distinguished by ink weight, which read as decoration
     rather than as "this one is on". A filled, outlined pill makes the active
     tab unmistakable. */

.detail-file-aside-tab {
  border: 1px solid transparent;
  /* ``font`` is a shorthand and resets size/weight, so both are restated
       after it to match the aside's other titles. */
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--ink-soft);
  background: var(--surface-muted);
  padding: 0.2rem 0.7rem;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.detail-file-aside-tab:hover {
  color: var(--ink-strong);
  background: var(--surface-soft);
}

.detail-file-aside-tab.is-active {
  border-color: var(--line-strong);
  color: var(--ink-strong);
  background: var(--surface-soft);
  font-weight: 600;
}

.detail-file-aside-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: 1rem 1.1rem;
  gap: 0;
}

/* `display: flex` above outranks the global `[hidden] { display: none }`
     reset (equal specificity, later in the cascade), so the tab switch — which
     toggles `panel.hidden` — needs this to actually hide a panel. Same
     workaround as .detail-drawer-panel[hidden] and .notice[hidden]. */

.detail-file-aside-body[hidden] {
  display: none;
}

/* The Anhänge tab renders the card list from _aside_cards.html, which on a
     standard class sits flush in the full-width .detail-aside column. The
     1.1rem side padding above is meant for the Eigenschaften form; here it
     would inset the cards. Keep the vertical padding, drop the horizontal. */

.detail-file-aside-body.anhaenge-tab-panel {
  padding-left: 0;
  padding-right: 0;
}

.detail-file-aside-body .surface-footer {
  margin-top: auto;
  padding-left: 0;
  padding-right: 0;
  border-top: 1px solid var(--border-divider);
}

@media (max-width: 1024px) {
  /* Only override the expanded width — collapsed rail keeps its 38px
       basis from .collapsible-strip. Without the :is() guard this rule
       would inflate the collapsed rail to 18rem at narrow viewports. */

  .detail-file-aside.is-expanded,
    .detail-file-aside[data-pre-expanded] {
    flex: 0 0 18rem;
  }
}

@media (max-width: 780px) {
  .detail-file-center,
    .detail-file-aside {
    flex: 0 0 auto;
    width: 100%;
    border-left: none;
  }

  .detail-file-aside {
    border-top: 1px solid var(--line-softer);
  }

  .detail-datei-liste-column {
    border-right: none;
    border-bottom: 1px solid var(--line-softer);
  }
}

.datei-liste-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.4rem 0.5rem 0.4rem 0.75rem;
  flex-shrink: 0;
}

.datei-liste-header-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-strong);
}

.datei-liste-header-toggle {
  border: none;
  font: inherit;
  color: var(--ink-strong);
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: space-between;
  line-height: 1;
  padding: 0.2rem;
  width: 100%;
}

.detail-datei-liste {
  display: grid;
  gap: 0.4rem;
  padding: 0.6rem 0.75rem;
}

.datei-liste-items {
  display: grid;
  gap: 0.2rem;
}

.datei-liste-item {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr);
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.4rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: inherit;
}

.datei-liste-item:hover {
  background: var(--surface-hover, rgba(0, 0, 0, 0.04));
}

.datei-liste-item.is-active {
  background: var(--surface-active, rgba(0, 0, 0, 0.07));
  font-weight: 600;
}

/* Sammelbearbeitung (ADR-0046): entries in the ephemeral multi-selection,
     same visual language as .tree-panel-link.is-multi-selected. */

.datei-liste-item.is-multi-selected {
  background: var(--accent-soft);
  color: var(--ink-strong);
  outline: 1px solid var(--accent-border-strong);
  outline-offset: -1px;
}

.datei-liste-thumb {
  width: 2rem;
  height: 2rem;
  -o-object-fit: cover;
     object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--surface-muted, oklch(1 0 0));
}

.datei-liste-thumb-placeholder {
  display: inline-block;
}

.datei-liste-identifier {
  font-size: 0.85rem;
}

.datei-liste-more {
  padding-top: 0.4rem;
  display: grid;
}

/* Drawer -------------------------------------------------------- */

.detail-drawer {
  position: relative;
  flex-shrink: 0;
  border-top: 1px solid var(--line-softer);
  background: white;
  display: flex;
  flex-direction: column;
  /* Default = collapsed so server-rendered HTML matches the most common
       persisted state without a JS-driven post-render flicker. JS adds
       .is-expanded after reading localStorage. Tab switches don't resize
       the drawer; the panel area scrolls when content exceeds ~3 rows. */
  height: 36px;
  overflow: hidden;
  transition: height var(--t-slow) var(--ease);
  /* Minimal lift off the detail area (shadow points upward toward it). */
  box-shadow: 0 -4px 4px -2px rgb(0 0 0 / 0.06);
}

.detail-drawer.is-expanded {
  height: var(--detail-drawer-h, 224px);
}

.app-shell-fixed .detail-drawer {
  background: #f7f4ee;
}

/* Pre-hydration marker (see base.html inline script): mirrors .is-expanded
     so the first paint matches the persisted state — no JS-driven flicker. */

html.pre-drawer-expanded .detail-drawer {
  height: var(--detail-drawer-h, 224px);
}

.detail-drawer-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.85rem;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface-raised);
}

/* Expanded: the tab strip carries the title + collapse chevron itself, so
     the slim collapsed bar is hidden. The pre-hydration marker mirrors this
     so a persisted-expanded drawer paints without a doubled header. */

.detail-drawer.is-expanded .detail-drawer-header,
  html.pre-drawer-expanded .detail-drawer .detail-drawer-header {
  display: none;
}

/* ----------------------------------------------------------------------
     Panel-Resize-Griff (Slice 10): an invisible hit strip on the panel's
     open edge with a centred grip pill. The strip is 8px under a fine
     pointer, 24px on coarse (touch) pointers. The pill is invisible at rest
     (mouse), faint grey at rest (touch), and accent on hover / drag / the
     touch reveal flash. Edge variants position the strip + orient the grip.

     The strip hugs the *inside* of the panel edge (offset 0, not a negative
     straddle): the host panels scroll (overflow auto/hidden), so anything
     past the padding edge would be clipped away and become unhittable. The
     1px border sits just outside; visually the grip still reads as "on the
     edge".
  ---------------------------------------------------------------------- */

.detail-resize-handle {
  position: absolute;
  z-index: 5;
  background: transparent;
  touch-action: none;
  display: flex;
}

/* Right edge (tree): vertical strip on the panel's right border. */

.detail-resize-handle-right {
  top: 0;
  bottom: 0;
  right: 0;
  width: 8px;
  cursor: col-resize;
}

/* Left edge (aside): vertical strip on the panel's left border. */

.detail-resize-handle-left {
  top: 0;
  bottom: 0;
  left: 0;
  width: 8px;
  cursor: col-resize;
}

/* Top edge (drawer): horizontal strip on the panel's top border. Unlike the
     side panels, the drawer's top edge IS its interactive header (relation
     tabs left, "+ Verbinden" right). A full-width hit strip would sit on top
     of those controls and swallow taps — fatal on touch where the strip is
     24px tall. So the drawer strip is a narrow centred band over the empty gap
     between the tab row and the button; the grip pill stays centred inside it,
     leaving both ends of the header tappable. */

.detail-resize-handle-top {
  left: 50%;
  transform: translateX(-50%);
  top: 0;
  width: 120px;
  height: 8px;
  cursor: row-resize;
}

.detail-resize-grip {
  border-radius: 999px;
  background: var(--ink-soft);
  opacity: 0;
  transition:
      opacity var(--t-base) var(--ease),
      background var(--t-base) var(--ease);
}

/* Vertical edges → upright pill (4×42px, 5×60px while dragging). */

.detail-resize-handle-right .detail-resize-grip,
  .detail-resize-handle-left .detail-resize-grip {
  width: 4px;
  height: 42px;
}

/* Top edge → lying pill. */

.detail-resize-handle-top .detail-resize-grip {
  width: 42px;
  height: 4px;
}

.detail-resize-handle:hover .detail-resize-grip,
  .detail-resize-handle.is-dragging .detail-resize-grip,
  .detail-resize-handle.is-revealed .detail-resize-grip {
  opacity: 1;
  background: var(--accent);
}

.detail-resize-handle-right.is-dragging .detail-resize-grip,
  .detail-resize-handle-left.is-dragging .detail-resize-grip {
  width: 5px;
  height: 60px;
}

.detail-resize-handle-top.is-dragging .detail-resize-grip {
  width: 60px;
  height: 5px;
}

/* Coarse (touch) pointers: widen the hit strip to 24px and show a faint
     resting grip so the handle is discoverable on tablets. */

@media (pointer: coarse) {
  .detail-resize-handle-right,
    .detail-resize-handle-left {
    width: 24px;
  }

  .detail-resize-handle-top {
    height: 24px;
  }

  .detail-resize-grip {
    opacity: 0.35;
  }
}

/* Collapse interplay: a collapsed panel shows no handle, and its expanded
     size is restored automatically (the CSS var stays applied). */

.tree-panel.is-collapsed .detail-resize-handle,
  html.pre-tree-collapsed .tree-panel .detail-resize-handle,
  .detail-drawer:not(.is-expanded) .detail-resize-handle,
  .collapsible-strip:not(.is-expanded):not([data-pre-expanded]) .detail-resize-handle {
  display: none;
}

/* While dragging, freeze the collapse transitions so the panel tracks the
     pointer instantly instead of easing behind it. */

body.is-resizing-col .tree-panel,
  body.is-resizing-col .collapsible-strip,
  body.is-resizing-row .detail-drawer {
  transition: none;
}

/* Body-level cursor + selection lock during a drag (toggled by JS). */

body.is-resizing-col {
  cursor: col-resize;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

body.is-resizing-row {
  cursor: row-resize;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

.detail-drawer-toggle {
  border: none;
  font: inherit;
  color: var(--ink-strong);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  text-align: left;
}

.detail-drawer-title {
  font-size: 0.95rem;
}

/* Multi-select swap host around the drawer body (_drawer.html): must not
     introduce a box of its own, or the body stops being a flex child of the
     .detail-drawer section. */

[data-multi-drawer-host] {
  display: contents;
}

.detail-drawer-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
  overflow: hidden;
}

.detail-drawer-tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1rem 0.85rem;
  color: var(--ink-strong);
}

/* Inline title that opens the tab row (design "Inline-Dokument"). */

.detail-drawer-tabs .detail-drawer-title {
  font-weight: 500;
  color: var(--ink-strong);
}

.detail-drawer-tab {
  background: var(--surface-muted);
  font: inherit;
  color: var(--ink-soft);
  padding: 0.2rem 0.7rem;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.detail-drawer-tab:hover {
  color: var(--ink-strong);
  background: var(--surface-soft);
}

.detail-drawer-tab.is-active {
  border: 0.1px solid;
  color: var(--ink-strong);
  background: var(--surface-soft);
  font-weight: 500;
}

.detail-drawer-tab-arrow {
  color: var(--ink-softest);
  font-size: 0.85em;
  line-height: 1;
}

/* Connect button: pushed to the far right of the tab strip, quiet box
     rather than a loud pill — keeps its "+ Verbinden" wording. */

.detail-drawer-tab-new {
  margin-left: auto;
  background: var(--surface-soft);
  font: inherit;
  color: var(--ink-soft);
  border: 1px solid;
  border-radius: var(--radius-sm);
  padding: 0.2rem 0.5rem;
}

.detail-drawer-tab-new:hover {
  color: var(--ink-strong);
  background: var(--surface-soft);
  border-color: var(--line-strong);
}

/* Title + chevron form one collapse toggle at the head of the tab strip. */

.detail-drawer-collapse {
  font: inherit;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-right: 0.5rem;
  color: var(--ink-strong);
  border: none;
  padding: 0.2rem 0.35rem;
  border-radius: var(--radius-sm);
}

.detail-drawer-collapse:hover {
  background: var(--surface-soft);
}

.detail-drawer-collapse-chevron {
  font-size: 1.05rem;
  line-height: 1;
  color: var(--ink-soft);
}

.detail-drawer-panels {
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
  padding: 0.2rem 0.85rem;
}

.detail-drawer-panel[hidden] {
  display: none;
}

.detail-drawer-empty {
  padding: 0.85rem;
}

.detail-drawer-spinner {
  display: flex;
  padding: 1.5rem;
}

.detail-drawer-spinner-dot {
  width: 0.85rem;
  height: 0.85rem;
  border: 2px solid var(--line-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: raumbuch-spin 0.7s linear infinite;
}

@keyframes raumbuch-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Project import status page: centered card with a self-polling spinner that
     shows the async import is still running (see _import_status.html). */

.import-status-card {
  max-width: 32rem;
  margin: 0 auto;
  text-align: center;
}

.import-status {
  margin-top: 1.75rem;
}

.import-status-running {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}

.import-status-spinner {
  width: 1.1rem;
  height: 1.1rem;
  border: 2px solid var(--line-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: raumbuch-spin 0.7s linear infinite;
}

.import-status-hint {
  color: var(--text-muted);
}

.import-status-error {
  margin-top: 0.35rem;
  font-family: ui-monospace, monospace;
  font-size: 0.85rem;
  word-break: break-word;
}

/* PDF-export progress (ADR-0042): a bar for the one divisible phase, plus the
     cancel button below it. The fill width is the only inline style — it is the
     value itself. */

.export-progress-bar {
  background: var(--surface-soft);
  border-radius: var(--radius-md);
  height: 8px;
  overflow: hidden;
}

.export-progress-fill {
  height: 100%;
  background: var(--accent);
  transition: width 0.3s;
}

.export-cancel {
  margin-top: 1rem;
}

/* Save button in-flight feedback: swap the check icon for a spinner while the
     boosted form submit is running (HTMX toggles .htmx-request on the button
     via hx-indicator="this"). */

.save-button-spinner {
  display: none;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: raumbuch-spin 0.7s linear infinite;
}

.save-button.htmx-request {
  pointer-events: none;
  opacity: 0.85;
}

.save-button.htmx-request .save-button-icon {
  display: none;
}

.save-button.htmx-request .save-button-spinner {
  display: block;
}

/* Collapsed state — generic for any element using data-persist-body. */

[data-persist-body][hidden] {
  display: none !important;
}

/* Inline-Dokument · Slice 01 — Dokument-Layout + Scroll-Spy ------------- */

/* Scroll-spy nav row above the document: same column width / alignment as
     the document below it. One item per group + the drawer toggle on the
     right. */

.doc-spy {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  max-width: 780px;
  margin: 0 auto;
  padding: 0 44px;
  width: 100%;
}

/* Keep the group links pinned while the document scrolls underneath
     (scoped to standard detail; file detail nav scrolls with its aside). */

.detail-properties-scroller .doc-spy {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--eigen-bg);
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

/* Horizontally scrollable track for the group links. In the narrow aside
     more groups than fit are common; instead of clipping them silently the
     row can be swiped/scrolled sideways. min-width: 0 lets the flex item
     shrink below its content width so the overflow actually scrolls. */

.doc-spy-track {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.doc-spy-track::-webkit-scrollbar {
  display: none;
}

.doc-spy-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0;
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 13.5px;
  line-height: 1;
  cursor: pointer;
}

.doc-spy-item:hover {
  color: var(--ink-strong);
}

/* Accent bar 3×15px — only visible on the active group. */

.doc-spy-bar {
  width: 3px;
  height: 15px;
  border-radius: 2px;
  background: var(--accent-strong);
  opacity: 0;
}

.doc-spy-item.is-active {
  color: var(--ink-strong);
  font-weight: 600;
}

.doc-spy-item.is-active .doc-spy-bar {
  opacity: 1;
}

.doc-spy-count {
  font-size: 11px;
  color: var(--ink-softest);
}

.doc-spy-drawer-toggle {
  margin-left: auto;
  width: 30px;
  height: 30px;
  border: none;
  background: transparent;
  cursor: pointer;
  align-self: center;
}

/* Document column: centred paper, 780px, generous side padding. */

/* The inline-document form (.form-stack grid) sits as a flex item in the
     properties scroller (standard) / file aside body. Its implicit single
     `auto` grid-track would otherwise grow to the widest field's *unwrapped*
     content width and push the whole column past the panel edge. min-width:0
     lets the flex item shrink to the panel; minmax(0,1fr) caps the track at
     the container so field values wrap inside instead of bursting out. */

.detail-properties-scroller > form,
  .detail-file-aside-body > form {
  min-width: 0;
  grid-template-columns: minmax(0, 1fr);
}

.doc-column {
  max-width: 780px;
  margin: 0 auto;
  padding: 8px 44px 72px;
  width: 100%;
  /* Grid item: stay shrinkable so the track (and the textarea/inputs inside
       it, which size to content) can never widen the panel. */
  min-width: 0;
}

/* Clear the sticky spy nav so a scrolled-to group header is not hidden. */

.doc-group {
  scroll-margin-top: 52px;
}

/* Group spacing: var(--field-group-gap) between groups, 8px above the first. */

.doc-group + .doc-group {
  margin-top: var(--field-group-gap);
}

.doc-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid rgba(var(--_ink-paper) / 0.20);
  margin-bottom: 1.25rem;
}

.doc-group-name {
  font-family: var(--font-prominent);
  font-size: 20px;
  font-weight: 600;
  color: var(--ink-strong);
}

.doc-group-count {
  font-size: 11.5px;
  color: var(--ink-softest);
  white-space: nowrap;
}

/* Empty group: silent ghost hint instead of the old hint line. */

.doc-group-ghost {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-style: italic;
  color: var(--ink-softest);
  font-size: 0.9rem;
}

.doc-group-ghost-plus {
  font-style: normal;
  font-weight: 600;
}

/* Field rows inside a document group (Slice 02): each row pads 12px 16px
     with a -16px horizontal margin so the hover tint bleeds past the document
     edge while the label/value stay flush. Stacked layout: 4px label→value
     gap. Hover reveals a soft tint with an 8px radius — no layout jump. */

.doc-group .field-stack {
  gap: 4px;
  padding: var(--field-row-pad-y) 16px;
  margin: 0 -16px;
  border-radius: 8px;
  transition: background var(--t-fast) var(--ease);
}

.doc-group .field-stack:hover {
  background: rgba(var(--_ink-paper) / 0.035);
  border-radius: 8px;
}

.field-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

/* A checkbox cell (e.g. "Pflichtfeld") has no label above it, so in a grid
     row shared with a taller label+input cell it would otherwise sit at the
     top. Center it against its neighbour. */

.field-grid > .checkbox-row {
  align-self: center;
}

/* Kennungs-Schema knobs (Präfix + die drei Trennzeichen/Format-Felder) als
     eigene, optisch abgesetzte Gruppe im Schablonen-Editor. Die vier Felder
     liegen in zwei Spalten; die Beispiel-Kennung bleibt darunter. */

.kennung-group {
  border: 1px solid var(--border, #d9d2c4);
  border-radius: 0.5rem;
  padding: 1rem 1.1rem 1.1rem;
  margin: 1.25rem 0 0;
  /* <fieldset> defaults to min-inline-size: min-content, which lets long hint
       text blow the two-column grid out of the modal. Pin it to the box. */
  min-inline-size: 0;
  background: rgba(0, 0, 0, 0.012);
}

.kennung-group > .kennung-group-title {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  margin-bottom: 0.75rem;
}

/* Zwei Spalten. Jedes der vier Felder gliedert sich in vier Sub-Zeilen
     (Label · Übernehmen-Checkbox · Eingabe · Hinweis). Via Subgrid teilen sich
     beide Spalten dieselben Zeilen-Tracks, sodass Labels, Eingaben und Hinweise
     über die Spalten exakt auf einer Linie liegen und nicht verspringen — egal
     ob ein Feld die "Projekt-Standard übernehmen"-Checkbox hat oder nicht. */

.kennung-group-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Pro Logik-Zeile vier Sub-Tracks: Label · Checkbox · Eingabe · Hinweis. */
  grid-auto-rows: auto;
  -moz-column-gap: 1.25rem;
       column-gap: 1.25rem;
  row-gap: 0.4rem;
}

.kennung-group-fields > .field-stack {
  margin-top: 0;
  min-inline-size: 0;
  display: grid;
  /* Vier Sub-Zeilen pro Feld; Subgrid übernimmt sie vom Eltern-Grid. */
  grid-template-rows: subgrid;
  grid-row: span 4;
  align-content: start;
  row-gap: 0.4rem;
}

/* Felder ohne Übernehmen-Checkbox (Präfix) lassen die Checkbox-Zeile (2) leer,
     damit die Eingabe auf gleicher Höhe wie die Nachbarspalte beginnt. */

.kennung-group-fields > .field-stack[data-no-inherit] > input {
  grid-row: 3;
}

@media (max-width: 480px) {
  .kennung-group-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Datei-Anhang group reuses the bordered .kennung-group shell but stacks its
     controls in a single column (toggle + dependent fields) instead of the
     two-column subgrid used for the Kennungs-Schema. Each control gets a clear,
     uniform gap with a hairline divider so the sections don't visually merge;
     the .mt-* utilities on the markup are overridden here for one rhythm. */

.file-group > * {
  margin-top: 0;
}

.file-group > *:not(.kennung-group-title):not(.hidden) ~ *:not(.hidden) {
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--border, #e5ddcf);
}

/* Inline-Dokument · Slice 02 — the field label is a quiet stacked caption:
     small muted Sans set above the value (4px gap from the .field-stack grid),
     not a bold heading. App-wide so Create-Form and Klassen-Editor share it. */

.field-label {
  display: flex;
  gap: 0.35rem;
  align-items: baseline;
  font-size: 13.5px;
  font-weight: 400;
  color: var(--ink-soft);
}

/* Value + unit sit on one baseline; the unit is a quiet Sans suffix that
     trails the rendered value (Slice 02 — replaces the old `[m²]` in label). */

.field-value-row {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.field-unit {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--ink-soft);
}

/* While the value is being edited the unit steps aside: focusing the input
     lifts it onto the editing surface, and the trailing unit would otherwise
     sit awkwardly on that raised plane. It returns the moment focus leaves. */

.doc-group .field-value-row:focus-within .field-unit {
  display: none;
}

/* Inline-Dokument · Slice 03 — inline-editing optic.
     Scoped to .doc-group so the Create-form and Klassen-Editor keep their
     boxed inputs. The inputs never leave the DOM (CSS-only): at rest they
     read as the gesetzter --font-prominent 17px value (Slice 02). Their own padding
     is compensated by a negative margin so the value stays flush with the
     stacked label above it.
     Direct-child only: nested inputs (boolean checkbox-row, pill/popover
     Sonstiges slots) keep their own styling. The template-mode value input
     sits one level deeper and opts in explicitly. */

.doc-group .field-value-row > input,
  .doc-group .field-value-row > textarea,
  .doc-group .field-value-row input.select-template-value {
  min-height: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  border-radius: 5px;
  padding: 2px 6px;
  margin: -2px -6px;
  font-family: var(--font-prominent);
  font-size: 17px;
  font-weight: var(--font-prominent-weight);
  line-height: 1.35;
  color: var(--ink-strong);
  transition:
      background var(--t-fast) var(--ease),
      box-shadow var(--t-fast) var(--ease);
}

/* Boolean "Ja" reads as an inline value, so it matches the typeset value
     font of the text/select fields instead of the quiet Sans field label. */

.doc-group .field-value-row .checkbox-row .field-label {
  font-family: var(--font-prominent);
  font-size: 17px;
  font-weight: var(--font-prominent-weight);
  color: var(--ink-strong);
}

/* Multiselect rendered as a checkbox matrix: option labels read as typeset
     values, matching the prominent font of the pills/select variants. */

.ms-matrix-label {
  font-family: var(--font-prominent);
  font-weight: var(--font-prominent-weight);
  color: var(--ink-strong);
}

.ms-matrix-label-all {
  color: var(--ink-muted);
}

/* The inline value sizes to its content instead of stretching the row:
     the field grows and shrinks with what's typed so the trailing unit
     follows the value's right edge. field-sizing keeps an empty field wide
     enough for its "Hinzufügen" hint (it sizes to the placeholder), so no
     explicit min-width floor is needed.
     Flex item: min-width:0 is required so a long single-line value can shrink
     to the row's max-width. Without it the default min-width:auto lets
     field-sizing's intrinsic content width win and the value overflows the
     column (same fix as the textarea below). */

.doc-group .field-value-row > input {
  field-sizing: content;
  /* The global input base sets `width: 100%` (Tailwind w-full); an explicit
       width overrides field-sizing's intrinsic sizing, so reset it to auto. */
  width: auto;
  min-width: 0;
  max-width: 100%;
}

.doc-group .field-value-row > textarea {
  field-sizing: content;
  width: 100%;
  /* Flex item: without min-width:0 the default min-width:auto lets
       field-sizing's intrinsic content width override width:100%, so a long
       value overflows the (narrow) column instead of wrapping. */
  min-width: 0;
  max-width: 100%;
}

/* Hover: a soft tint with a bottom hairline (inset box-shadow) signals the
     value is editable; the text cursor reinforces it. Disabled inputs are
     excluded so a readonly/archived value reads as final. */

.doc-group .field-value-row > input:not(:disabled):hover,
  .doc-group .field-value-row > textarea:not(:disabled):hover,
  .doc-group .field-value-row input.select-template-value:not(:disabled):hover {
  background: rgba(var(--_ink-paper) / 0.06);
  box-shadow: inset 0 -1px 0 rgba(var(--_ink-paper) / 0.13);
  cursor: text;
}

/* Focus: lift onto a lighter editing surface with a 2px accent ring and a
     soft shadow — the value "raises" itself to be edited. */

.doc-group .field-value-row > input:focus,
  .doc-group .field-value-row > textarea:focus,
  .doc-group .field-value-row input.select-template-value:focus {
  outline: none;
  background: #fffdf8;
  box-shadow:
      0 0 0 2px var(--accent),
      0 6px 18px rgba(var(--_ink-paper) / 0.1);
}

/* Empty: an italic "Hinzufügen" hint (markup placeholder attribute) in the
     muted ink reads as a quiet invitation, not a boxed empty field. */

.doc-group .field-value-row > input::-moz-placeholder, .doc-group .field-value-row > textarea::-moz-placeholder, .doc-group .field-value-row input.select-template-value::-moz-placeholder {
  font-family: var(--font-prominent);
  font-weight: var(--font-prominent-weight);
  font-style: italic;
  color: var(--ink-softest);
}

.doc-group .field-value-row > input::placeholder,
  .doc-group .field-value-row > textarea::placeholder,
  .doc-group .field-value-row input.select-template-value::placeholder {
  font-family: var(--font-prominent);
  font-weight: var(--font-prominent-weight);
  font-style: italic;
  color: var(--ink-softest);
}

/* Longtext shares the inline states but stays multi-line with an open 1.62
     leading and a vertical resize handle. */

.doc-group .field-value-row > textarea {
  line-height: 1.62;
  resize: vertical;
}

/* Disabled (readonly/archived): plain typeset text, no edit affordance —
     overrides the global :disabled box and keeps the resting look. */

.doc-group .field-value-row > input:disabled,
  .doc-group .field-value-row > textarea:disabled,
  .doc-group .field-value-row input.select-template-value:disabled {
  background: transparent;
  color: var(--ink-strong);
  cursor: default;
  resize: none;
}

/* A readonly/archived empty field must not invite editing — hide the
     "Hinzufügen" placeholder so it reads as a final, empty value. */

.doc-group .field-value-row > input:disabled::-moz-placeholder, .doc-group .field-value-row > textarea:disabled::-moz-placeholder, .doc-group .field-value-row input.select-template-value:disabled::-moz-placeholder {
  color: transparent;
}

.doc-group .field-value-row > input:disabled::placeholder,
  .doc-group .field-value-row > textarea:disabled::placeholder,
  .doc-group .field-value-row input.select-template-value:disabled::placeholder {
  color: transparent;
}

/* Number fields: hide the browser spinner so the resting state is text-
     identical (Firefox via appearance:textfield, WebKit via the spin button). */

.doc-group .field-value-row > input[type="number"] {
  -webkit-appearance: textfield;
     -moz-appearance: textfield;
          appearance: textfield;
}

.doc-group .field-value-row > input[type="number"]::-webkit-inner-spin-button,
  .doc-group .field-value-row > input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
          appearance: none;
  margin: 0;
}

/* Hinweistext: small "?" badge next to a field label that toggles a
     popover with the field's free-text help. Uses the native Popover API
     (popovertarget / [popover]) — no JS needed. */

/* Slice 02: the help "?" is a 14px hairline circle that stays nearly
     invisible at rest (opacity .5), lifts to .72 when its row is hovered, and
     reaches full opacity on its own hover/open. */

.field-helptext-trigger {
  display: inline-flex;
  width: 14px;
  height: 14px;
  box-sizing: border-box;
  border: 1px solid rgba(var(--_ink-paper) / 0.30);
  border-radius: 9999px;
  background: transparent;
  color: var(--ink-soft);
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  opacity: 0.5;
  transition:
      opacity 0.14s var(--ease),
      background var(--t-fast) var(--ease),
      border-color var(--t-fast) var(--ease),
      color var(--t-fast) var(--ease);
}

.doc-group .field-stack:hover .field-helptext-trigger {
  opacity: 0.72;
}

.field-helptext-trigger:hover {
  opacity: 1;
  background: rgba(var(--_ink-paper) / 0.06);
  border-color: var(--ink-softest);
  color: var(--ink-strong);
}

/* Slice 02: a typeset 252px help card — small Sans copy, soft shadow. */

.field-helptext-popover {
  width: 252px;
  margin: 0;
  padding: 11px 13px;
  border: none;
  border-radius: 9px;
  background: var(--surface-overlay, #fffdf8);
  color: var(--ink-base);
  font-size: 12.5px;
  line-height: 1.5;
  box-shadow:
      0 12px 36px rgba(var(--_ink-paper) / 0.18),
      0 0 0 0.5px rgba(var(--_ink-paper) / 0.20);
  white-space: pre-line;
  /* The per-field anchor name is set inline; the popover sits just below
       and to the right of its "?" trigger. Browsers without CSS anchor
       positioning fall back to the viewport-centred [popover] default. */
  position-area: bottom span-right;
  margin-top: 0.4rem;
}

/* Typ-Objekt 04 (ADR-0013): on an occurrence with a Typ-Beziehung a field is
     gebunden (its value still matches the type seed) or abweichend (locally
     adjusted). gebunden values render italic; abweichend stay upright and carry
     a "vom Typ übernehmen" refresh control styled like the help "?" badge. */

.field-row--gebunden .field-value-row input,
  .field-row--gebunden .field-value-row textarea {
  font-style: italic;
}

.field-type-reset-trigger {
  display: inline-flex;
  width: 14px;
  height: 14px;
  box-sizing: border-box;
  border: 1px solid rgba(var(--_ink-paper) / 0.30);
  border-radius: 9999px;
  background: transparent;
  color: var(--ink-soft);
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.5;
  transition:
      opacity 0.14s var(--ease),
      background var(--t-fast) var(--ease),
      border-color var(--t-fast) var(--ease),
      color var(--t-fast) var(--ease);
}

/* The refresh glyph is set via CSS so the button markup stays empty. */

.field-type-reset-trigger::before {
  content: "↻";
}

.doc-group .field-stack:hover .field-type-reset-trigger {
  opacity: 0.72;
}

.field-type-reset-trigger:hover {
  opacity: 1;
  background: rgba(var(--_ink-paper) / 0.06);
  border-color: var(--ink-softest);
  color: var(--ink-strong);
}

.field-hint {
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.field-hint-error {
  color: var(--danger-ink);
}

.field-hint-success {
  color: color-mix(in srgb, var(--success) 58%, black 42%);
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.4rem;
}

.copy-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* Projektparameter rows in the project create/edit modals (ADR-0045):
     narrow name, flexible value, trailing remove button. */

.param-rows {
  display: grid;
  gap: 0.5rem;
}

.param-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.param-row input[name='param_name'] {
  width: 11rem;
  flex-shrink: 0;
}

.param-row input[name='param_wert'] {
  flex: 1 1 0%;
  min-width: 0;
}

.copy-row input {
  flex: 1 1 0%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.75rem;
  padding-top: 1.75rem;
}

.dialog-panel .form-actions {
  position: sticky;
  bottom: -1.35rem;
  margin-inline: -1.35rem;
  margin-bottom: -1.35rem;
  padding: 1rem 1.35rem 1.35rem;
  background: var(--bg-cream);
  border-top: 1px solid rgba(var(--_sand) / 0.14);
}

/* Hide the "Exportieren" button while a package is being prepared so a running
     build cannot be re-triggered (the status fragment carries data-export-pending). */

.dialog-panel:has(.export-status[data-export-pending]) .export-submit {
  display: none;
}

/* Export stages: each radio choice, with the prepared-package info docked
     directly under the selected stage. */

.export-stages {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.export-status-hint {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--ink-soft);
  padding-left: 1.9rem;
}

.export-status-hint-error {
  color: var(--danger-ink);
}

/* Prepared package row: name + date/size on the left, icon actions on the
     right, indented to line up under the radio's label. */

.export-package {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-left: 1.9rem;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border-sand);
  border-radius: var(--radius-md);
  background: rgba(var(--_cream) / 0.7);
}

.export-package-info {
  display: flex;
  min-width: 0px;
  flex-direction: column;
  gap: 0.125rem;
}

.export-package-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--ink-base);
}

.export-package-meta {
  font-size: 0.75rem;
  line-height: 1rem;
  color: var(--ink-soft);
}

.export-package-actions {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.25rem;
}

.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: var(--radius-md);
  color: var(--ink-soft);
  border: 1px solid var(--border-sand);
  background: var(--bg-cream);
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.icon-button:hover {
  background: rgba(var(--_sand) / 0.16);
  color: var(--ink-base);
}

.icon-button-danger:hover {
  color: var(--danger-ink);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
}

.empty-state {
  display: grid;
  place-items: center;
  gap: 0.55rem;
  min-height: 10rem;
  padding: 2rem;
  border: 1px dashed rgba(var(--_sand) / 0.45);
  border-radius: var(--radius-lg);
  background: rgba(252, 249, 245, 0.86);
  text-align: center;
  color: var(--ink-soft);
}

.empty-state-title {
  color: var(--ink-base);
  font-size: 1rem;
  font-weight: 600;
}

.history-list {
  display: grid;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.history-entry {
  padding: 0.85rem 1rem;
  border: 1px solid var(--border-sand);
  border-radius: var(--radius-sm);
  background: var(--bg-card-light);
}

.history-entry-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

.history-entry-action {
  font-weight: 600;
  color: var(--ink-base);
}

.history-entry-meta {
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.history-entry-changes {
  display: grid;
  gap: 0.35rem;
  margin: 0.6rem 0 0;
}

.history-change {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.history-change-label {
  font-weight: 600;
  color: var(--ink-soft);
}

.history-change-value {
  margin: 0;
  color: var(--ink-base);
}

/* Tabellenansicht (ADR-0022) — read-only class grid, horizontal scroll. */

.class-table-scroll {
  overflow-x: auto;
}

.class-table {
  border-collapse: collapse;
  width: -moz-max-content;
  width: max-content;
  min-width: 100%;
  font-size: 0.9rem;
}

.class-table-header {
  position: sticky;
  top: 0;
  z-index: 2;
  text-align: left;
  background: var(--surface-base);
  border-bottom: 2px solid var(--border-sand);
  white-space: nowrap;
}

/* Kennung (first column) stays visible during horizontal scroll; its header
     cell pins to both axes and sits above the rest (corner). Opaque background
     required so body cells don't show through the sticky column. */

.class-table-header[data-column="identifier"] {
  left: 0;
  z-index: 3;
}

.class-table-cell[data-cell="identifier"] {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface-base);
  border-right: 1px solid var(--border-sand);
}

.class-table-row:hover .class-table-cell[data-cell="identifier"] {
  background: var(--surface-soft);
}

.class-table-row.is-selected .class-table-cell[data-cell="identifier"] {
  background: var(--accent-soft);
}

/* Speichern column pins to the right edge so the (conditionally shown) save
     button stays reachable during horizontal scroll. Same opaque-background /
     row-state handling as the Kennung column. */

.class-table-header.class-table-action {
  right: 0;
  z-index: 3;
}

.class-table-cell.class-table-action {
  position: sticky;
  right: 0;
  z-index: 1;
  background: var(--surface-base);
  border-left: 1px solid var(--border-sand);
}

.class-table-row:hover .class-table-cell.class-table-action {
  background: var(--surface-soft);
}

.class-table-row.is-selected .class-table-cell.class-table-action {
  background: var(--accent-soft);
}

.class-table-sort {
  all: unset;
  cursor: pointer;
  display: block;
  padding: 0.5rem 0.75rem;
  font-weight: 600;
  color: var(--ink-soft);
}

/* Row height is pinned to the editable-input height (2.4rem, see the global
     input rule) instead of being decided by the tallest child. Without it a
     class whose columns are mostly read-only (Datei-Klasse: Originaldateiname
     & co.) renders visibly shorter rows than a standard class, and the
     conditionally shown Speichern button — taller than plain text, shorter
     than an input — makes those rows jump as it appears and disappears. */

.class-table-cell {
  padding: 0.4rem 0.75rem;
  border-bottom: 1px solid var(--border-sand);
  white-space: nowrap;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On a <td> `height` acts as a minimum the table layout honours, so this is
     the floor every cell kind shares: 2.4rem input + 2 x 0.4rem padding. */

.class-table-row .class-table-cell {
  height: 3.2rem;
}

.class-table-row:hover {
  background: var(--bg-card-light);
}

.class-table-row.is-selected {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 0 var(--accent-strong);
}

.class-table-link {
  color: var(--ink-base);
  text-decoration: none;
}

.class-table-link:hover {
  text-decoration: underline;
}

/* Editable cells keep their inputs compact so rows stay dense (§3). */

.class-table-cell--edit input,
  .class-table-cell--edit select,
  .class-table-cell--edit textarea {
  width: 100%;
  min-width: 8rem;
}

/* Flat by default: in a grid of structured data one boxed control per cell
     reads as noise — every value carries a frame, so the frames stop
     distinguishing anything. At rest a cell shows only its text (a select keeps
     its native dropdown arrow, which is the affordance), and the input chrome
     returns on row hover and on focus, so "what is editable here" is still one
     mouse move away. Padding is kept from the global rule so nothing shifts
     horizontally when the frame appears. */

.class-table-cell--edit input:where(:not([type="hidden"]):not([type="checkbox"]):not([type="radio"])),
  .class-table-cell--edit select,
  .class-table-cell--edit textarea {
  border-color: transparent;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* The hover outline sits on the *cell*, not on the input: the dense
     longtext/multiselect cells have no input to carry it and already outline
     the whole td (see ``.class-table-cell--dense[data-cell-modal]:hover``), so
     drawing a rounded pill around the input would give one hovered row two
     different box shapes. One square cell outline for every column instead. */

.class-table-row:hover .class-table-cell--edit {
  box-shadow: inset 0 0 0 1px var(--border-sand);
  background: color-mix(in srgb, var(--surface-raised) 88%, white 12%);
}

/* The global :focus rule is written at low specificity on purpose, so it
     would lose against the hover rule above (which is what a focused cell in a
     hovered row is). Restate it here at matching weight — squared off, so the
     focused cell keeps the same footprint as the hovered one. */

.class-table-cell--edit input:where(:not([type="hidden"]):not([type="checkbox"]):not([type="radio"])):focus,
  .class-table-cell--edit select:focus,
  .class-table-cell--edit textarea:focus {
  border-color: var(--accent-border-strong);
  border-radius: 0;
  background: var(--surface-raised);
  box-shadow: none;
}

/* Dense cells (longtext): a fixed-width preview clipped at the column edge,
     opened in the shared modal by a click (dichte Zellen 03). The clipping is
     pixel-based on purpose — ``WWWW`` and ``iiii`` are equally many characters
     but not equally wide, so a character count would cut ragged. */

.class-table-cell--dense {
  width: 16rem;
  min-width: 16rem;
  max-width: 16rem;
}

.class-table-cell--dense[data-cell-modal] {
  cursor: pointer;
}

.class-table-cell--dense[data-cell-modal]:hover {
  box-shadow: inset 0 0 0 1px var(--border-sand);
}

/* A multiselect cell's summary: the value list clips at the column edge, the
     counter never does. "One value or four?" is exactly the question when
     scanning a column, so the +n must survive the ellipsis — hence its own
     element, and a flex row that shrinks only the text. */

.class-table-summary {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
}

.class-table-summary-text {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.class-table-summary-more {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 0.8rem;
}

/* The modal body: a textarea with room to actually read the text. */

.cell-modal-textarea {
  width: 100%;
  min-height: 16rem;
  resize: vertical;
}

/* The multiselect body: one checkbox per option, scrollable for long lists. */

.cell-modal-options {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-height: 24rem;
  overflow-y: auto;
}

.cell-modal-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* The allow_other free-text field, below the (scrolling) option list — set
     off by a rule so it reads as "add one", not as another option. */

.cell-modal-other-row {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-divider);
  color: var(--text-muted);
  font-size: 0.8rem;
}

.cell-modal-other {
  width: 100%;
}

/* Outweighs the row-hover outline above, which would otherwise wipe the red
     marking exactly while the user is reaching for the cell to fix it. */

.class-table-row .class-table-cell--edit.is-invalid,
  .class-table-row:hover .class-table-cell--edit.is-invalid {
  background: var(--danger-soft, #fdecec);
  box-shadow: inset 0 0 0 1px var(--danger-strong, #c0392b);
}

/* Per-row Speichern: hidden until the row is dirty or carries an error (§4). */

.class-table-save {
  visibility: hidden;
  padding: 0.25rem 0.6rem;
  font-size: 0.8rem;
}

.class-table-row.is-dirty .class-table-save,
  .class-table-row.has-error .class-table-save {
  visibility: visible;
}

/* Collapse the whole Speichern column while no row shows a Save button, so an
     idle table wastes no horizontal space on an empty column. It reappears as
     soon as any row goes dirty or carries a validation error. */

.class-table:not(:has(.class-table-row.is-dirty, .class-table-row.has-error))
    .class-table-action {
  display: none;
}

.section-stack {
  display: grid;
  gap: 1rem;
}

.record-list {
  overflow: hidden;
  border: 1px solid var(--border-sand);
  border-radius: var(--radius-sm);
  background: var(--bg-card-light);
}

.record-list > li {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0.5rem;
}

.record-list > li + li {
  border-top: 1px solid rgba(var(--_sand-mid) / 0.16);
}

/* IFC pset/qto list: ~30% taller rows than the default record-list. */

.pset-list > li {
  padding-top: 0.78rem;
  padding-bottom: 0.78rem;
}

.drag-handle {
  cursor: grab;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  color: var(--ink-soft);
  flex-shrink: 0;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0 0.25rem;
}

.drag-handle:active {
  cursor: grabbing;
}

.drag-active {
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
  background: rgba(var(--_white) / 0.95);
  opacity: 0.95;
}

.drag-placeholder {
  background: rgba(var(--_sand-mid) / 0.18);
  border: 1px dashed rgba(var(--_sand-mid) / 0.5);
  border-radius: var(--radius-sm);
  list-style: none;
}

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

.field-row {
  display: grid;
  grid-template-columns:
      1.5rem
      minmax(0, 2.5fr)
      minmax(0, 1.5fr)
      minmax(0, 0.8fr)
      minmax(0, 1fr)
      minmax(11rem, auto);
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  border-top: 1px solid rgba(var(--_sand-mid) / 0.16);
}

/* Relation-type table: handle + name + source→target + kind + cardinality + auto-show + actions */

.field-row--relation-type {
  display: grid;
  grid-template-columns:
      1.5rem
      minmax(0, 1.8fr)
      minmax(0, 2fr)
      minmax(0, 1.1fr)
      minmax(0, 0.9fr)
      minmax(0, 1fr)
      minmax(9rem, auto);
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  border-top: 1px solid rgba(var(--_sand-mid) / 0.16);
}

/* Read-only variant: no handle, no actions column */

.field-row--relation-type-ro {
  display: grid;
  grid-template-columns:
      minmax(0, 1.8fr)
      minmax(0, 2fr)
      minmax(0, 1.1fr)
      minmax(0, 0.9fr)
      minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  border-top: 1px solid rgba(var(--_sand-mid) / 0.16);
}

.field-row-head {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  background: rgba(var(--_sand-mid) / 0.06);
}

.field-cell {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.field-cell-rt-show {
  text-align: center;
}

.field-cell-rt-show form {
  text-align: center;
}

.field-cell-actions {
  display: flex;
  gap: 0.25rem;
  justify-content: flex-end;
  flex-wrap: wrap;
  overflow: visible;
}

.field-cell-actions .button {
  padding-inline: 0.6rem;
  white-space: nowrap;
}

.field-cell-actions .\!button {
  padding-inline: 0.6rem !important;
  white-space: nowrap !important;
}

@media (max-width: 900px) {
  .field-row {
    grid-template-columns:
        1.5rem
        minmax(0, 2fr)
        minmax(0, 1.2fr)
        minmax(9.5rem, auto);
  }

  .field-cell-req,
    .field-cell-unit {
    display: none;
  }

  /* Relation-type: collapse kind + cardinality on tablet */

  .field-row--relation-type {
    grid-template-columns:
        1.5rem
        minmax(0, 1.6fr)
        minmax(0, 1.8fr)
        minmax(0, 1fr)
        minmax(8rem, auto);
  }

  .field-row--relation-type-ro {
    grid-template-columns:
        minmax(0, 1.6fr)
        minmax(0, 1.8fr)
        minmax(0, 1fr)
        minmax(0, 1fr);
  }

  .field-cell-rt-card,
    .field-cell-rt-show {
    display: none;
  }
}

@media (max-width: 600px) {
  .field-row {
    grid-template-columns: 1.5rem minmax(0, 1fr) minmax(8rem, auto);
    row-gap: 0.25rem;
    padding: 0.5rem 0.75rem;
  }

  .field-cell-type {
    grid-column: 2 / 3;
    font-size: 0.75rem;
  }

  .field-row-head .field-cell-type {
    display: none;
  }

  .field-cell-actions {
    grid-row: 1 / 3;
    grid-column: 3 / 4;
    align-self: center;
  }

  /* Relation-type: mobile — only name + target + actions */

  .field-row--relation-type {
    grid-template-columns: 1.5rem minmax(0, 1fr) minmax(7rem, auto);
    row-gap: 0.25rem;
    padding: 0.5rem 0.75rem;
  }

  .field-row--relation-type-ro {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0.2rem;
    padding: 0.5rem 0.75rem;
  }

  .field-cell-rt-target {
    grid-column: 2 / 3;
    font-size: 0.8rem;
    color: var(--ink-soft);
  }

  .field-row--relation-type .field-cell-actions {
    grid-row: 1 / 3;
    grid-column: 3 / 4;
    align-self: center;
  }

  .field-cell-rt-kind {
    display: none;
  }
}

.record-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.record-link {
  color: var(--ink-base);
  font-weight: 600;
}

.record-link:hover {
  color: var(--ink-strong);
}

.record-meta {
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.dialog-shell {
  width: min(100% - 1.5rem, 42rem);
  max-height: min(90svh, 56rem);
  border: 0;
  border-radius: var(--radius-xl);
  background: transparent;
  padding: 0;
  overflow: hidden;
  box-shadow: none;
}

.dialog-shell.dialog-wide {
  width: min(100% - 1.5rem, 52rem);
}

.dialog-panel {
  display: flex;
  flex-direction: column;
  max-height: min(90svh, 56rem);
  border-radius: var(--radius-xl);
  border: 1px solid rgba(var(--_sand) / 0.22);
  background: var(--bg-cream);
  box-shadow: 0 24px 80px rgba(var(--_shadow-deep) / 0.18);
  padding: 1.35rem;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.dialog-header {
  display: grid;
  gap: 0.4rem;
  margin-bottom: 1rem;
}

.dialog-title {
  font-size: 1.22rem;
  line-height: 1.15;
}

.dialog-copy {
  color: var(--ink-soft);
  font-size: 0.9rem;
  line-height: 1.55;
}

.auth-shell {
  display: flex;
  min-height: 100%;
  align-items: center;
  justify-content: center;
  padding: clamp(1.5rem, 4vw, 3rem);
  /* Login overrides ``block body`` and never gets .app-shell--with-footer,
       so it carried the pure-white :root surface. Match the warm panel tone
       used across the rest of the app (same as the topbar). */
  min-height: 100dvh;
  background: #f7f5f0;
}

.auth-card {
  width: min(100%, 28rem);
  border-radius: var(--radius-xl);
  border: 1px solid var(--border-sand-soft);
  background: var(--bg-card-strong);
  box-shadow: 0 28px 80px rgba(var(--_shadow-deep) / 0.14);
  padding: clamp(1.4rem, 3vw, 2rem);
}

.auth-copy {
  display: grid;
  gap: 0.45rem;
  margin-bottom: 1.35rem;
  text-align: left;
}

.auth-kicker {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 700;
}

/* Footer on administrative pages only (opt-in via show_footer). Editor pages
     stay chrome-free, so this never renders there. */

.app-footer {
  flex-shrink: 0;
  padding: 1rem clamp(1rem, 4vw, 2rem);
  text-align: center;
}

.auth-footer {
  margin-top: .5rem;
  padding-top: .1rem;
  text-align: center;
}

.app-footer-link {
  font-size: 0.75rem;
  color: var(--ink-soft);
  text-decoration: none;
}

.app-footer-link:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* Static legal pages (Datenschutz): readable single-column prose. */

.legal-prose {
  max-width: 44rem;
  display: grid;
  gap: 0.75rem;
}

.legal-prose h2 {
  margin-top: 1rem;
  font-size: 1.05rem;
  font-weight: 600;
}

.legal-prose p {
  color: var(--ink-soft);
  line-height: 1.65;
}

.toggle-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.toggle-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
  padding-inline: 0.95rem;
  border-radius: 999px;
  border: 1px solid rgba(var(--_sand) / 0.26);
  background: var(--bg-card);
  color: var(--ink-base);
  font-size: 0.9rem;
  font-weight: 600;
}

.toggle-chip:hover {
  color: var(--ink-strong);
}

.toggle-chip.is-active {
  border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent-soft) 82%, white 18%);
  color: var(--accent-strong);
}

.autocomplete-list:empty {
  display: none;
}

.autocomplete-list {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + 0.35rem);
  z-index: 10;
  overflow-y: auto;
  max-height: 14rem;
  border: 1px solid rgba(var(--_sand) / 0.28);
  border-radius: var(--radius-md);
  background: var(--bg-card-solid);
  box-shadow: 0 18px 40px rgba(var(--_shadow-warm) / 0.14);
}

.autocomplete-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 0.8rem 0.95rem;
  cursor: pointer;
}

.autocomplete-option:hover {
  background: color-mix(in srgb, var(--accent-soft) 55%, white 45%);
}

.autocomplete-empty {
  padding: 0.8rem 0.95rem;
  color: var(--ink-soft);
  font-size: 0.875rem;
  font-style: italic;
}

.req-star {
  color: #a8694a;
  font-weight: bold;
}

.field-error {
  color: var(--danger-ink);
}

.button-ghost-danger {
  background: transparent;
  color: var(--danger-ink-strong);
  padding-inline: 0;
  min-height: auto;
}

.button-ghost-danger:hover {
  color: var(--danger-strongest);
}

.mosaik-grid {
  /* Zoom steps (toolbar +/- in mosaic mode, mosaic-active.js) set
       data-mosaik-zoom; the tile minimum drives an auto-fill grid, so the
       column count follows the available width — no per-breakpoint column
       rules needed. Step 3 is the default (~ the former 6-column layout). */
  --mosaik-tile-min: 13rem;
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--mosaik-tile-min), 100%), 1fr));
}

.mosaik-grid[data-mosaik-zoom="1"] {
  --mosaik-tile-min: 5rem;
}

.mosaik-grid[data-mosaik-zoom="2"] {
  --mosaik-tile-min: 8rem;
}

.mosaik-grid[data-mosaik-zoom="4"] {
  --mosaik-tile-min: 21rem;
}

.mosaik-grid[data-mosaik-zoom="5"] {
  --mosaik-tile-min: 34rem;
}

.mosaik-tile {
  display: block;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-sand);
  background: var(--bg-card);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: box-shadow var(--t-fast), transform var(--t-fast);
}

.mosaik-tile:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--accent-border);
}

.mosaik-tile.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-soft) 80%, white 20%);
}

/* Sammelbearbeitung (ADR-0046): tiles in the ephemeral multi-selection.
     multi-select.js paints .is-multi-selected on the tile link; the visual
     lives on the tile frame so the whole card reads as selected. */

.mosaik-tile:has(> .mosaik-tile-link.is-multi-selected) {
  border-color: var(--accent-border-strong);
  background: var(--accent-soft);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Sammelbearbeitung Slice 03: the multi-edit form partial. The panel root
     is transparent (display: contents) so its header + scroller lay out as
     direct children of the host — .detail-main's flex column (standard) or
     the .detail-file-aside-body (file classes). */

.multi-edit-panel {
  display: contents;
}

/* File page: the panel header moved into the centre column (multi-edit.js),
     the scroller stays in the aside and must look like the single-view form
     there — no own paper background, no extra gutter (the aside body already
     pads), and no second scroll container (the strip content scrolls). */

.detail-file-aside-body .detail-properties-scroller {
  background: transparent;
  padding: 0;
  overflow-y: visible;
}

/* Keep the header and the group links pinned while the properties scroll
     underneath, mirroring the standard detail. The sticky containing block is
     the .collapsible-strip (the only scrolling ancestor — the enclosing
     .detail-properties-scroller is overflow-y: visible above), so both stick
     against the strip and need its background to stay opaque. */

.detail-file-aside-header {
  position: sticky;
  top: 0;
  z-index: 6;
  background: var(--surface-raised);
}

.detail-file-aside-body .detail-properties-scroller .doc-spy {
  position: sticky;
  /* Sits directly below the sticky header, which shares the same
       containing block — hence an explicit offset instead of top: 0. */
  top: var(--detail-file-aside-header-h, 2.4rem);
  z-index: 5;
  background: var(--surface-raised);
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

.multi-edit-count {
  font-size: 1.15rem;
}

/* Slice 04: touched fields carry the established accent Rand-Markierung
     (same outline style as .is-multi-selected) — the change preview for
     "wird auf alle N geschrieben" (ADR-0046 §4). The row already rounds at
     8px inside .doc-group; the outline follows it. */

.multi-edit-panel .field-stack.is-dirty {
  outline: 1px solid var(--accent-border-strong);
  outline-offset: -1px;
  background: var(--accent-soft);
}

/* ⟨abweichend⟩: purely visual mixed marker next to the field label —
     muted, never bold, clearly not a value. */

.multi-mixed-hint {
  color: var(--ink-muted);
  font-weight: 400;
  font-style: italic;
  font-size: 0.78rem;
  margin-left: 0.35rem;
}

/* Sammelbearbeitung Stufe 2 (Slice 07): the multi-view Beziehungs-Drawer.
     multi-edit.js moves it into the .detail-drawer section (forced open), so
     it reuses the drawer's tab/panel classes; only the multi-specific bits
     live here. */

.multi-drawer .detail-drawer-panel {
  overflow-y: auto;
  padding: 0.35rem 0.85rem 0.6rem;
}

/* ⟨abweichend⟩ per tab: passive hint, same muted look as the field marker. */

.multi-drawer-divergent {
  color: var(--ink-muted);
  font-style: italic;
  font-size: 0.78rem;
}

/* Shown by multi-edit.js while a drawer action is blocked by dirty
     field edits ("Erst Feldänderungen speichern oder verwerfen"). */

.multi-drawer-locked-hint {
  margin: 0.25rem 0.85rem 0;
  color: var(--ink-strong);
  background: var(--accent-soft);
  border: 1px solid var(--accent-border-strong);
  border-radius: var(--radius-md);
  padding: 0.35rem 0.6rem;
  font-size: 0.82rem;
}

/* Sammelbearbeitung, file page preview mode: multi-edit.js swaps the
     single-file stage (viewer + per-file toolbar) for this grid of the
     selected files' thumbnails. The wrapper is transparent so the swap does
     not change the centre-column layout; the tiles echo the mosaic look but
     stay deliberately non-interactive (no hover lift, no pointer). */

.multi-edit-stage-region {
  display: contents;
}

/* The grid is a direct flex child of .detail-file-center (the wrapper is
     display: contents), so it takes the stage's room and scrolls itself —
     same behaviour as .detail-mosaic inside the stage-wrap. Tile sizing
     mirrors the mosaic default (~13rem auto-fill). */

.multi-stage-grid {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr));
  align-content: start;
  padding-top: 0.75rem;
}

.multi-stage-tile {
  margin: 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--accent-border-strong);
  background: var(--accent-soft);
  overflow: hidden;
}

.multi-stage-thumb {
  aspect-ratio: 1;
  width: 100%;
  -o-object-fit: cover;
     object-fit: cover;
  display: block;
  background: var(--surface-soft);
}

.multi-stage-thumb-placeholder {
  display: block;
}

.multi-stage-caption {
  display: block;
  padding: 0.35rem 0.5rem;
  font-size: 0.78rem;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mosaik-thumb {
  aspect-ratio: 1;
  width: 100%;
  -o-object-fit: cover;
     object-fit: cover;
  display: block;
  background: var(--surface-soft);
}

.mosaik-icon {
  aspect-ratio: 1;
  width: 100%;
  display: flex;
  font-size: 2.5rem;
  background: var(--surface-soft);
}

.mosaik-caption {
  padding: 0.4rem 0.5rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
  /* Small zoom steps make long identifiers overflow; the full text is
       already available via the title attribute. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 2D hotspot viewer (Issue #5) */

/* No gap: the dark toolbar reads as part of the image it sits under, so it
     butts straight against the stage. The only other children are the
     absolutely positioned popovers and the <template>s, so this gap was the
     stage-to-toolbar seam and nothing else. */

.hotspot-viewer {
  display: flex;
  flex-direction: column;
  gap: 0;
  position: relative;
}

.hotspot-viewer-toolbar {
  display: flex;
  gap: 0.5rem;
}

/* Design test: the kebab popover follows its dark toolbar instead of the
     shared light card. Scoped to the file toolbar — `.inline-header-menu`
     is shared with the user menu and the table headers, which stay light.
     Same alpha as the toolbar so both read as one material; the blur is what
     keeps the labels legible over the image behind them. Downward shadow is
     the mosaic default; the viewer rule below flips it. */

.file-detail-toolbar .file-detail-actions-menu .inline-header-menu {
  background: rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);
}

/* The viewer-owned toolbar is the last thing on the page, so a menu opening
     downwards would fall out of view. Anchor it above the kebab instead, and
     point the shadow up to match. Equal specificity with the colour rule
     above (both 3 classes), so this must stay *after* it to win. */

.hotspot-viewer-toolbar .file-detail-actions-menu .inline-header-menu {
  top: auto;
  bottom: calc(100% + 0.4rem);
  box-shadow: 0 -12px 28px rgba(0, 0, 0, 0.4);
}

/* Design test: the toolbar's buttons invert with it. `.button-secondary`
     is a near-white fill meant for the paper surface — on the dark strip it
     read as bright tiles, so the same idea is flipped: a faint white veil
     that lets the strip show through, brightening on hover. */

.file-detail-toolbar .button-secondary {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.2);
  color: rgba(255, 255, 255, 0.92);
}

/* `:not([aria-pressed="true"])` keeps this off the active toggles — the
     accent rule below outranks it anyway, which would otherwise leave a
     pressed button with no hover feedback at all. */

.file-detail-toolbar
    .button-secondary:hover:not([aria-pressed="true"], [aria-expanded="true"]) {
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(255, 255, 255, 0.35);
  color: #fff;
}

/* Active toggles: `--accent-soft` is the accent cut with 78% white, which
     on the dark strip turned back into the bright tile we just removed. Use
     the full-strength accent instead — it still reads as "on" against the
     translucent idle buttons, and carries the white icon properly. */

.file-detail-toolbar
    .hotspot-toolbar-icon-button[aria-pressed="true"],
  .file-detail-toolbar
    .hotspot-toolbar-icon-button[aria-expanded="true"] {
  background: var(--accent-strong);
  border-color: color-mix(in srgb, var(--accent-strong) 60%, white 40%);
  color: #fff;
}

.file-detail-toolbar
    .hotspot-toolbar-icon-button[aria-pressed="true"]:hover,
  .file-detail-toolbar
    .hotspot-toolbar-icon-button[aria-expanded="true"]:hover {
  background: color-mix(in srgb, var(--accent-strong) 82%, white 18%);
}

/* The idle pill's outline and label are `--line-strong` / `--ink-strong`,
     both tuned for paper. The active pill keeps its `--schablone-color`
     fill — that colour carries meaning, not decoration. */

.file-detail-toolbar .hotspot-schablone-picker-pill:not([data-active]) {
  border-color: rgba(255, 255, 255, 0.35);
  color: rgba(255, 255, 255, 0.92);
}

.file-detail-toolbar .inline-header-menu-item {
  color: rgba(255, 255, 255, 0.95);
}

.file-detail-toolbar .inline-header-menu-icon {
  color: rgba(255, 255, 255, 0.7);
}

.file-detail-toolbar .inline-header-menu-hint {
  color: rgba(255, 255, 255, 0.55);
}

.file-detail-toolbar .inline-header-menu-item:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
}

.file-detail-toolbar .inline-header-menu-item:disabled {
  color: rgba(255, 255, 255, 0.4);
}

.file-detail-toolbar .inline-header-menu-rule {
  border-top-color: rgba(255, 255, 255, 0.12);
}

/* `--warning-ink` is the warning colour cut with 40% black — legible on the
     light card, near-invisible on this one. Lighten it instead of dropping
     the danger signal. */

.file-detail-toolbar .inline-header-menu-item-danger,
  .file-detail-toolbar .inline-header-menu-item-danger
    .inline-header-menu-icon {
  color: color-mix(in srgb, var(--warning) 75%, white 25%);
}

.file-detail-toolbar .inline-header-menu-item-danger:hover {
  background: color-mix(in srgb, var(--warning) 28%, transparent);
}

/* Icon-only toolbar buttons (Issue 09).
     The shared `.hotspot-toolbar-icon-button` hook gives every toolbar
     trigger a 34x34 px tap target (WCAG 2.5.5 / Apple HIG) and resets the
     pill's horizontal padding so the icon centres in the square. The
     active state for toggle buttons is driven by [aria-pressed="true"] /
     [aria-expanded="true"] so we don't need a separate class. */

.hotspot-toolbar-icon-button {
  min-width: 28px;
  min-height: 28px;
  padding: 0;
  display: inline-flex;
}

/* `display: inline-flex` overrides the UA `[hidden] { display: none }` rule,
     so setting `el.hidden = true` in JS would have no visual effect without
     this explicit override. The filter button relies on it to disappear when
     the pin toggle is off. */

.hotspot-toolbar-icon-button[hidden] {
  display: none;
}

/* Slice 03: a setting tool with no active Schablone has nothing to write
     into. It stays visible (so the gap does not move) but reads as inert. */

.hotspot-toolbar-icon-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.hotspot-toolbar-icon-button[aria-pressed="true"],
  .hotspot-toolbar-icon-button[aria-expanded="true"] {
  background: var(--accent-soft);
  color: #fff;
  border-color: var(--accent-strong);
}

.hotspot-toolbar-icon {
  width: 20px;
  height: 20px;
  display: block;
}

/* The +/− zoom glyphs read heavier than the outline icons next to them;
     shrink just those two so they sit lighter in the toolbar. */

.hotspot-toolbar-icon-button[data-hotspot-zoom-in] .hotspot-toolbar-icon,
  .hotspot-toolbar-icon-button[data-hotspot-zoom-out] .hotspot-toolbar-icon,
  .hotspot-toolbar-icon-button[data-mosaik-zoom-in] .hotspot-toolbar-icon,
  .hotspot-toolbar-icon-button[data-mosaik-zoom-out] .hotspot-toolbar-icon {
  width: 12px;
  height: 12px;
}

.hotspot-viewer-stage {
  position: relative;
  overflow: hidden;
  flex: 1 1 auto;
  /* `min-height` would be a hard floor: in a short container the stage
       refuses to shrink and pushes the toolbar below it past the clipping
       `.file-filled` edge. `flex-basis` keeps the same roomy default while
       letting the stage give way to the toolbar when space runs out. */
  flex-basis: 20rem;
  min-height: 0;
  touch-action: none;
  cursor: grab;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

.hotspot-viewer-canvas {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.hotspot-viewer-image {
  display: block;
  max-width: none;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  -webkit-user-drag: none;
}

.hotspot-viewer-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Hint banner shown while a mode runs on a 2D image — the edge, polygon and
     move tools paint it themselves, the facade's position mode ("Pin setzen",
     verschieben, duplizieren) gets it as `positionHintClass`. Mirrors the
     Pannellum overlay (`.pano-add-mode-hint`) so both viewers give the same
     affordance during pin placement. */

.hotspot-viewer-hint {
  position: absolute;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 25;
  background: rgba(0, 0, 0, 0.75);
  color: white;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  pointer-events: none;
}

/* Hint bars that also host inline buttons (position-mode "Abbrechen";
     vertex-edit "Fertig"/"Abbrechen"): lay label + buttons out with a gap,
     and opt each button back into pointer-events — .hotspot-viewer-hint
     above is pointer-events: none so idle label space still passes clicks
     through to the canvas, and buttons must explicitly re-enable it. */

.hotspot-position-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.hotspot-position-bar button {
  pointer-events: auto;
  flex: none;
  padding: 0.25rem 0.65rem;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.12);
  color: white;
  font-size: 0.8rem;
  cursor: pointer;
  white-space: nowrap;
}

.hotspot-position-bar button:hover {
  background: rgba(255, 255, 255, 0.22);
}

/* Transient info messages that need no interaction (e.g. "Keine definierte
     Beziehung …") share the hint's dark top-centre pill, then fade out — so
     they land where the "Ersten Pin wählen …" banner sat. */

.hotspot-viewer-toast {
  position: absolute;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 26;
  background: rgba(0, 0, 0, 0.75);
  color: white;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  pointer-events: none;
  animation: hotspot-toast-fade 6s ease forwards;
}

@keyframes hotspot-toast-fade {
  0% {
    opacity: 0;
  }

  6% {
    opacity: 1;
  }

  85% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

/* 360° panorama viewer (Issue 2) — Pannellum needs an explicit height.
     Pannellum injects `.pnlm-container` onto our wrapper at runtime, whose
     own rule (`height: 100%`) ties with `[data-panorama-viewer]` on
     specificity and wins by source order. The dual selector raises ours
     above the tie so 480px sticks. */

[data-panorama-viewer][data-panorama-viewer] {
  width: 100%;
  height: 480px;
  overflow: hidden;
  background: var(--surface-soft);
}

/* Slice 07: pin-placement crosshair. Pannellum re-applies its `pnlm-grab`
     class (cursor: url(...)) on every mouseup, so the override has to be
     marked !important to outweigh the inline cursor URL. */

.pano-pin-placement-mode,
  .pano-pin-placement-mode * {
  cursor: crosshair !important;
}

/* Hint banner shown while in add- or move-mode. */

.pano-add-mode-hint {
  position: absolute;
  top: 3.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 25;
  background: rgba(0, 0, 0, 0.75);
  color: white;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  pointer-events: none;
}

/* Slice 06–08: panorama pin marker. Pannellum's `.pnlm-hotspot-base` is
     position:absolute + visibility hidden until the renderer projects it
     into view; we layer our own marker on top inside createTooltipFunc.
     The class lets the marker have a fixed size and round shape. */

.raumbuch-pano-pin {
  /* Pannellum's stylesheet forces `box-sizing: content-box` on every
       descendant of `.pnlm-container` — override back so the 2px border
       does not inflate the box and skew the offset-based centering below. */
  box-sizing: border-box;
  width: 14px;
  height: 14px;
  /* No negative margins for centering: Pannellum already centres the
       hotspot element on yaw/pitch by subtracting half its offsetWidth/
       Height in the transform it sets each frame. Adding margins would
       shift the marker by half its size, which becomes very visible the
       further the pin sits from the canvas centre. */
  border-radius: 999px;
  border: 2px solid white;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  display: flex;
  color: white;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.raumbuch-pano-pin-icon {
  pointer-events: none;
}

/* Slice 09: "Pins ausblenden" toggle. Hides every Pannellum hotspot via
     CSS without removing it from the viewer — rotating/zooming keeps the
     state because Pannellum still owns the underlying hotspot entries. */

.pano-pins-hidden .raumbuch-pano-pin {
  display: none;
}

.hotspot-pin {
  pointer-events: auto;
  transform-origin: center center;
}

/* Edge tool: the first picked pin is ringed while the user chooses the
     second. outline (not box-shadow) so it survives the pin's inline
     box-shadow set by the viewer JS. */

.hotspot-pin-edge-selected {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

/* Selection ground state (ADR-0049 §3): the same visual language as
     .tree-panel-link.is-multi-selected, but as outline — the pin's inline
     box-shadow (viewer JS, class colour) would beat any box-shadow here.
     Thicker and in the accent tone so it stays apart from the blue 2px
     edge-tool ring above; the two can never appear together, but they must
     not be confusable either. */

.hotspot-pin-selected {
  outline: 3px solid var(--accent-border-strong);
  outline-offset: 2px;
}

.hotspot-popover {
  background: var(--surface-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 0.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 9rem;
  max-width: 14rem;
  font-size: 0.8rem;
}

/* Defense-in-depth: the panorama wrapper is a flex column, so a popover
     without explicit positioning would push the Pannellum canvas down and
     shift hotspot projections. JS sets left/top per click; this rule keeps
     the popover out of flow even if a future caller forgets. */

.hotspot-popover.pano-active {
  position: absolute;
  z-index: 30;
}

.hotspot-popover-row {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

/* Name acts as the primary "Öffnen" action — full button affordance with
     icon, label and an arrow hint. Flex-1 so it absorbs the available width
     and the ⋮ menu hugs the right edge in the same row. */

.hotspot-popover-name {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
  text-decoration: none;
  text-align: left;
  justify-content: flex-start;
  font-weight: 600;
  padding: 0.2rem 0.45rem;
  padding-inline: 0.45rem;
  min-height: 1.75rem;
  height: 1.75rem;
  font-size: 0.8rem;
  line-height: 1.2;
}

.hotspot-popover-name-text {
  flex: 1 1 auto;
  min-width: 0;
}

.hotspot-popover-open-hint {
  flex: 0 0 auto;
  opacity: 0.8;
  font-weight: 400;
}

/* Square, and exactly as tall as the name button next to it — .button's
     own min-height/padding-inline would otherwise make it taller and wider. */

.hotspot-popover-menu-btn {
  flex: 0 0 auto;
  border-radius: 8px;
  width: 1.75rem;
  height: 1.75rem;
  min-height: 1.75rem;
  padding: 0;
  padding-inline: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* Kebab panel: action list + icon row, revealed together under the name
     row. Column layout so the icon row sits right below the list. */

.hotspot-popover-panel {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.hotspot-popover-panel[hidden] {
  display: none;
}

.hotspot-popover-menu {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.hotspot-popover-menu .button {
  width: 100%;
  justify-content: flex-start;
  min-height: 1.75rem;
  padding: 0.2rem 0.4rem;
  padding-inline: 0.4rem;
  font-size: 0.8rem;
  line-height: 1.2;
}

.hotspot-popover-menu .\!button {
  width: 100% !important;
  justify-content: flex-start !important;
  min-height: 1.75rem !important;
  padding: 0.2rem 0.4rem !important;
  padding-inline: 0.4rem !important;
  font-size: 0.8rem !important;
  line-height: 1.2 !important;
}

/* Hover feedback in the .detail-drawer-tab idiom: soft surface + strong
     ink, so the flat entries read as targets under the cursor. */

.hotspot-popover-menu .button:hover,
  .hotspot-popover-actions .button:hover {
  background: var(--surface-soft);
  color: var(--ink-strong);
  box-shadow: var(--shadow-sm);
}

.hotspot-popover-menu .\!button:hover,
  .hotspot-popover-actions .\!button:hover {
  background: var(--surface-soft) !important;
  color: var(--ink-strong) !important;
  box-shadow: var(--shadow-sm) !important;
}

.hotspot-popover-actions .button {
  border-radius: 8px;
  width: 1.75rem;
  height: 1.75rem;
  min-height: 1.75rem;
  padding: 0;
  padding-inline: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.hotspot-popover-actions .\!button {
  border-radius: 8px !important;
  width: 1.75rem !important;
  height: 1.75rem !important;
  min-height: 1.75rem !important;
  padding: 0 !important;
  padding-inline: 0 !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
}

.hotspot-popover-actions .hotspot-toolbar-icon {
  width: 1rem;
  height: 1rem;
}

/* Pin-Klassen-Filter (Issue 01 of pin-filter PRD).
     Popover anchors absolutely above the toolbar; the toolbar button toggles
     its hidden attribute. The toolbar is the viewer's last child, so the menu
     hangs off the bottom edge — `bottom` tracks the toolbar height instead of
     the magic `top: 3rem` that assumed a toolbar above the stage. */

.hotspot-viewer-filter-popover {
  position: absolute;
  bottom: 3.25rem;
  left: 0;
  z-index: 25;
  background: var(--surface-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 0.5rem 0.75rem;
  min-width: 14rem;
  max-width: 22rem;
}

/* "Alle"-Toggle: three-state checkbox-styled trigger above the pills.
     The visible box reflects aria-checked: filled = all on, hollow = all off,
     half-filled bar = mixed. Painted via CSS only — JS just flips the attr.
     `min-height` reset overrides the global `button{min-height:2.75rem}`,
     and explicit `:hover`/`:active`/`:focus` rules keep the accent visible
     when the user interacts (otherwise UA defaults darken the button). */

.hotspot-viewer-filter-all-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent !important;
  border: none;
  padding: 0.25rem 0;
  margin-bottom: 0.4rem;
  min-height: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-strong);
  cursor: pointer;
}

.hotspot-viewer-filter-all-toggle:hover,
  .hotspot-viewer-filter-all-toggle:focus,
  .hotspot-viewer-filter-all-toggle:active {
  background: transparent !important;
  color: var(--ink-strong);
  outline: none;
}

.hotspot-viewer-filter-all-toggle:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
}

.hotspot-viewer-filter-all-toggle-box {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border-radius: 3px;
  border: 1.5px solid var(--line-strong);
  background: var(--surface-raised);
  position: relative;
  flex-shrink: 0;
}

.hotspot-viewer-filter-all-toggle[aria-checked="true"] .hotspot-viewer-filter-all-toggle-box,
  .hotspot-viewer-filter-all-toggle[aria-checked="mixed"] .hotspot-viewer-filter-all-toggle-box {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

.hotspot-viewer-filter-all-toggle[aria-checked="true"] .hotspot-viewer-filter-all-toggle-box::after {
  content: "";
  position: absolute;
  inset: 2px 3px;
  border: solid white;
  border-width: 0 0 2px 2px;
  transform: rotate(-45deg) translateY(-2px);
}

.hotspot-viewer-filter-all-toggle[aria-checked="mixed"] .hotspot-viewer-filter-all-toggle-box::after {
  content: "";
  position: absolute;
  left: 2px;
  right: 2px;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: white;
}

/* "Linien anzeigen"-Toggle (Issue 07 of pin-filter PRD): two-state, painted
     identically to the "Alle"-toggle's filled/empty box. JS flips aria-checked
     and saveFilterState persists it alongside the class filter. */

.hotspot-viewer-filter-lines-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent !important;
  border: none;
  padding: 0.25rem 0;
  margin-top: 0.4rem;
  min-height: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-strong);
  cursor: pointer;
}

.hotspot-viewer-filter-lines-toggle:hover,
  .hotspot-viewer-filter-lines-toggle:focus,
  .hotspot-viewer-filter-lines-toggle:active {
  background: transparent !important;
  color: var(--ink-strong);
  outline: none;
}

.hotspot-viewer-filter-lines-toggle:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
}

.hotspot-viewer-filter-lines-toggle-box {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border-radius: 3px;
  border: 1.5px solid var(--line-strong);
  background: var(--surface-raised);
  position: relative;
  flex-shrink: 0;
}

.hotspot-viewer-filter-lines-toggle[aria-checked="true"] .hotspot-viewer-filter-lines-toggle-box {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

.hotspot-viewer-filter-lines-toggle[aria-checked="true"] .hotspot-viewer-filter-lines-toggle-box::after {
  content: "";
  position: absolute;
  inset: 2px 3px;
  border: solid white;
  border-width: 0 0 2px 2px;
  transform: rotate(-45deg) translateY(-2px);
}

/* Hierarchy lines SVG layer (Issue 07): spans the image bounds so its
     viewBox coordinates align with the pin overlay's percentage positions.
     pointer-events: none keeps clicks reaching pins below. */

.hotspot-viewer-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

/* Polygon area layer (ADR-0025): the lowest visual layer, filled room
     outlines in class colour. The SVG itself ignores clicks; each <polygon>
     opts back in (pointer-events: auto set in JS) so a click on the fill
     opens the pin popover while empty regions still pan the canvas. */

.hotspot-viewer-areas,
  .hotspot-viewer-polygon-draft {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

.hotspot-viewer-areas polygon {
  cursor: pointer;
  transition: fill-opacity 0.12s ease;
}

/* Accent indicator on the Anzeigeoptionen toolbar button (no number).
     Marks "showing less than everything". Visible iff JS removes the
     `hidden` attribute. */

.hotspot-viewer-filter-button {
  position: relative;
}

.hotspot-viewer-filter-indicator {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-strong);
  box-shadow: 0 0 0 1.5px var(--surface-raised);
  pointer-events: none;
}

/* --- Schablonenwahl (ADR-0054) ---------------------------------------
     The CAD-style layer row: closed it shows the active Schablone as a pill,
     open it lists every project Schablone with an eye per row.

     Fixed width for both the handle and the menu pills (decision 15), so
     switching Schablone never reflows the toolbar. The handle keeps the
     height of the icon buttons beside it (decision 12), which is why it
     resets the global `button { min-height: 2.75rem }`. */

.hotspot-schablone-picker {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  min-height: 28px;
  height: 28px;
  padding: 0 0.3rem;
}

.hotspot-schablone-picker[hidden] {
  display: none;
}

.hotspot-schablone-picker-chevron {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* Shared geometry of the pills — the handle's and the menu rows'. One
     width, one ellipsis rule, so the two can never read as different things.
     The width stays uniform on purpose: chips sized to their own label made
     the menu restless, and the handle must not reflow when the active
     Schablone changes. The menu hugs them via its own `fit-content`. */

.hotspot-schablone-picker-pill,
  .hotspot-schablone-pill {
  display: inline-block;
  width: 8.5rem;
  max-width: 8.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  border-radius: 999px;
  padding: 2px 8px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--ink-strong);
}

/* Painted = this is the active Schablone. Same fill as the active row in
     the menu, so the handle is a literal echo of the choice. */

.hotspot-schablone-picker-pill[data-active],
  .hotspot-schablone-pill[aria-pressed="true"] {
  background: var(--schablone-color, var(--accent-strong));
  border-color: transparent;
  color: #fff;
}

/* The menu hangs under its own handle. The toolbar is the positioning
     context (see .hotspot-viewer-filter-popover), and the handle's offset
     depends on how many buttons precede it and how wide the pill is, so JS
     measures it and sets --schablone-menu-left rather than a magic number. */

/* Width follows the chips instead of a fixed 15rem: the rows are the only
     content, so `fit-content` shrinks the box to the uniform pill width plus
     the eye and the padding — a small margin around the chips rather than the
     stretch the fixed 15rem left. `min-width` only guards the header row. */

.hotspot-schablone-popover {
  left: var(--schablone-menu-left, 0px);
  width: -moz-fit-content;
  width: fit-content;
  min-width: 9rem;
}

.hotspot-schablone-rows {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.hotspot-schablone-row {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.hotspot-schablone-eye {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  min-height: 0;
  padding: 0;
  flex-shrink: 0;
  background: transparent !important;
  border: none;
  color: var(--ink-muted);
  cursor: pointer;
}

.hotspot-schablone-eye:hover:not(:disabled) {
  color: var(--ink-strong);
}

.hotspot-schablone-eye:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 1px;
}

/* A Schablone with no hotspot on this sheet has nothing to show or hide
     (decision 16) — the eye stays, greyed, and its pill stays selectable. */

.hotspot-schablone-eye:disabled {
  opacity: 0.35;
  cursor: default;
}

.hotspot-schablone-eye svg {
  width: 1rem;
  height: 1rem;
  display: block;
}

/* aria-pressed drives the icon swap, the mechanic the password toggle uses
     — but under own class names, so that delegation never adopts these rows.
     pressed = this Schablone is visible, so the open eye shows; closed shows
     the struck-through one. */

.hotspot-schablone-eye .hotspot-schablone-eye-hide {
  display: block;
}

.hotspot-schablone-eye .hotspot-schablone-eye-show {
  display: none;
}

.hotspot-schablone-eye[aria-pressed="true"] .hotspot-schablone-eye-show {
  display: block;
}

.hotspot-schablone-eye[aria-pressed="true"] .hotspot-schablone-eye-hide {
  display: none;
}

.hotspot-schablone-pill {
  cursor: pointer;
  border-color: var(--schablone-color, var(--line-strong));
  color: var(--schablone-color, var(--ink-strong));
}

.hotspot-schablone-pill:disabled {
  cursor: default;
}

.hotspot-schablone-pill:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
}

/* The two toolbar popovers — Anzeigeoptionen and Schablonenwahl — follow
     the kebab menu onto the dark material. `.hotspot-schablone-popover` only
     adds position and width on top of `.hotspot-viewer-filter-popover`, so
     styling the base covers both. */

/* Alpha low enough to read as translucent *without* the blur: in the 2D
     viewer `backdrop-filter` samples the <img> behind it, but on the
     panorama the backdrop is Pannellum's WebGL canvas, which does not
     composite into the backdrop root — the blur there resolves to nothing
     and 0.65 black collapsed to flat opaque. Carrying the transparency in
     the colour itself makes both viewers land on the same material. */

.hotspot-viewer-filter-popover {
  background: rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 -12px 28px rgba(0, 0, 0, 0.4);
}

.hotspot-viewer-filter-all-toggle,
  .hotspot-viewer-filter-all-toggle:hover,
  .hotspot-viewer-filter-all-toggle:focus,
  .hotspot-viewer-filter-all-toggle:active,
  .hotspot-viewer-filter-lines-toggle,
  .hotspot-viewer-filter-lines-toggle:hover,
  .hotspot-viewer-filter-lines-toggle:focus,
  .hotspot-viewer-filter-lines-toggle:active {
  color: rgba(255, 255, 255, 0.95);
}

/* The unchecked box is `--surface-raised` (near-white paper) — on the dark
     popover that reads as a filled box, inverting the control's meaning.
     Empty must look empty, so hollow it out. */

.hotspot-viewer-filter-all-toggle-box,
  .hotspot-viewer-filter-lines-toggle-box {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.45);
}

.hotspot-schablone-eye {
  color: rgba(255, 255, 255, 0.6);
}

.hotspot-schablone-eye:hover:not(:disabled) {
  color: #fff;
}

/* Pills keep their `--schablone-color`  */

.hotspot-schablone-pill {
  background: color-mix(
      in srgb,
      var(--schablone-color, transparent) 20%,
      rgba(255, 255, 255, 0.25)
    );
  border-color:  rgba(255, 255, 255, 0.11);
  color: #fff;
}

/* The selected pill  */

.hotspot-schablone-pill[aria-pressed="true"] {
  background: var(--schablone-color, var(--accent-strong));
  border-color: #fff;
  color: #fff;
}

/* Pill-Chip select/multiselect variant (Issue 01).
     Used by select_pills + multiselect_pills macros. The native
     radio/checkbox input lives inside the <label> but is visually hidden;
     the pill itself flips state via the input's `:checked` state,
     surfaced through CSS `:has(:checked)`. */

.pill-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.pill-chip-stack {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.pill-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.34rem 0.78rem;
  border: 1px solid var(--line-soft, var(--border-sand, #d8c9a8));
  background: transparent;
  color: var(--ink-soft);
  border-radius: 999px;
  font-family: var(--font-prominent);
  font-size: 0.7969rem;
  /* 12.75px */
  font-weight: 600;
  line-height: 1.1;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
      color var(--t-fast) var(--ease);
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/* Ghost (unselected) hover lifts slightly. */

.pill-chip:hover {
  border-color: color-mix(in srgb, var(--accent) 24%, transparent);
  color: var(--ink-strong);
}

/* Selected: accent-tinted fill, accent border, accent ink — color carries
     the state, plus a 5px accent dot on the left. `.is-on` is the class-driven
     alias for pills without a checkable input (multiselect custom values). */

.pill-chip:has(.pill-chip-input:checked),
  .pill-chip.is-on {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  color: var(--accent-strong);
}

/* Visually hidden but focusable. Anchored absolutely to its .pill-chip
     (which is position:relative) — NOT to the scroll container. That keeps the
     input out of the pill's flex flow (so it consumes no slot + gap, label
     stays centered) while giving Chromium a local focus-scroll target inside
     the pill, instead of the bottom of .detail-properties-scroller where it
     would jump into the over-scroll region (Chrome/Edge only; WebKit fine). */

.pill-chip-input {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* 5px accent dot — only visible while the pill is selected. */

.pill-chip-dot {
  display: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-strong);
  flex-shrink: 0;
}

.pill-chip:has(.pill-chip-input:checked) .pill-chip-dot,
  .pill-chip.is-on .pill-chip-dot {
  display: block;
}

/* "Alle wählen" / "Auswahl aufheben" ghost-chip below multi-select pills */

.pill-chip-toggle-all {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.78rem;
  border: 1px solid var(--line-soft, var(--border-sand, #d8c9a8));
  background: transparent;
  color: var(--ink-soft);
  border-radius: 999px;
  font-family: var(--font-prominent);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
}

/* Same anchored-to-its-chip recipe as .pill-chip-input — out of flex flow
     (label centered) and focus-scrolls within the chip, not the scroll
     container. */

.pill-chip-toggle-all-input {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Multiselect free-text "other" slot (always-visible input below pills). */

.pill-chip-other-input {
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  outline: none;
  min-width: 8rem;
}

/* Sonstiges pill (single-select): a tristate group that flips between a
     dashed trigger, an inline input, and a selected custom-value pill. JS
     toggles is-empty / is-input / is-custom on the [data-pill-other] wrapper.
     The multiselect pills variant reuses the same classes per custom pill
     ([data-pill-ms-pill]) and for its repeatable add group ([data-pill-ms-add]). */

.pill-chip-other {
  display: inline-flex;
}

.pill-chip-other-trigger {
  border-style: dashed;
  color: var(--ink-soft);
  background: transparent;
}

.pill-chip-other-trigger:hover {
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--accent-strong);
}

/* The inline editing input replaces the trigger; accent inset ring. */

.pill-chip-other.is-input .pill-chip-other-input {
  display: inline-block;
  width: 150px;
  min-width: 0;
  padding: 0.34rem 0.7rem;
  border-radius: 999px;
  background: var(--surface-raised, #fff);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* Hide whichever facets don't match the current state. */

.pill-chip-other .pill-chip-other-input {
  display: none;
}

.pill-chip-other.is-input .pill-chip-other-trigger,
  .pill-chip-other.is-input .pill-chip-other-value,
  .pill-chip-other.is-custom .pill-chip-other-trigger,
  .pill-chip-other.is-custom .pill-chip-other-input,
  .pill-chip-other.is-empty .pill-chip-other-value {
  display: none;
}

/* Custom-value pill: a <label> wrapping the __other__ sentinel radio, so
     the shared .pill-chip :has(:checked) rules drive selected vs. ghost —
     no bespoke accent override. It only carries the remove-×. */

/* Click-to-edit affordance: while the custom value is selected its text
     edits in place (ghost pills re-select on click instead). */

.pill-chip-other-value:has(.pill-chip-other-radio:checked) .pill-chip-label,
  .pill-chip-other-value.is-on .pill-chip-label {
  cursor: text;
}

.pill-chip-remove {
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: 1;
  padding: 0;
  margin-left: 0.1rem;
  cursor: pointer;
  opacity: 0.7;
}

.pill-chip-remove:hover {
  opacity: 1;
}

/* "— (keine Auswahl)" chip in optional single-select pill groups. */

.pill-chip-none {
  color: var(--ink-soft);
  font-style: italic;
  font-weight: 500;
}

.pill-chip[aria-disabled="true"],
  .pill-chip-input:disabled + .pill-chip {
  cursor: default;
  opacity: 0.6;
}

/* ===================================================================== */

/* Multiselect dropdown: Pills + Checklist-Popover (Inline-Dokument 05)   */

/* ===================================================================== */

.ms-checklist {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.ms-pill-row {
  display: contents;
}

/* Selected-value pill: accent tint, accent dot, remove-×. */

.ms-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.34rem 0.6rem 0.34rem 0.78rem;
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent-strong, var(--accent));
  border-radius: 999px;
  font-family: var(--font-prominent);
  font-size: 0.7969rem;
  /* 12.75px */
  font-weight: 600;
  line-height: 1.1;
}

.ms-pill .pill-chip-dot {
  display: block;
}

.ms-pill-remove {
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  opacity: 0.7;
}

.ms-pill-remove:hover {
  opacity: 1;
}

/* Dashed "+ Auswählen" / "+ Hinzufügen" trigger pill with a chevron. */

.ms-checklist-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.34rem 0.7rem;
  border: 1px dashed var(--line-soft);
  background: transparent;
  color: var(--ink-soft);
  border-radius: 999px;
  font-size: 0.7969rem;
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.ms-checklist-trigger:hover {
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--accent-strong, var(--accent));
}

.ms-checklist-chevron,
  .select-popover-chevron {
  width: 0.8rem;
  height: 0.8rem;
  flex-shrink: 0;
  transition: transform var(--t-fast) var(--ease);
}

.ms-checklist.is-open .ms-checklist-chevron,
  .select-popover.is-open .select-popover-chevron {
  transform: rotate(180deg);
}

/* Checklist popover card: white, soft shadow + 0.5px hairline ring. */

.ms-checklist-popover,
  .select-popover-panel {
  position: absolute;
  inset-inline-start: 0;
  top: calc(100% + 0.35rem);
  z-index: 30;
  min-width: 230px;
  max-height: 300px;
  overflow-y: auto;
  padding: 0.35rem;
  background: var(--surface-raised, #fff);
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 0.5px var(--line-soft), var(--shadow-md);
  animation: select-popover-in 120ms var(--ease);
}

.ms-checklist-popover[hidden],
  .select-popover-panel[hidden] {
  display: none;
}

@keyframes select-popover-in {
  from {
    opacity: 0;
    transform: translateY(-3px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ms-checklist-all,
  .ms-checklist-option,
  .select-popover-option {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  padding: 0.42rem 0.55rem;
  border: none;
  background: transparent;
  color: var(--ink-base);
  border-radius: 0.5rem;
  font: inherit;
  font-size: 0.85rem;
  text-align: start;
  cursor: pointer;
}

.ms-checklist-all {
  border-bottom: 1px solid var(--line-softer);
  border-radius: 0;
  font-style: italic;
  color: var(--ink-soft);
}

.ms-checklist-option:hover,
  .select-popover-option:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* 16px checkbox, 5px radius, accent fill + white check when on. */

.ms-checklist-box {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin: 0;
  border: 1.5px solid var(--line-strong);
  border-radius: 5px;
  position: relative;
}

.ms-checklist-box:checked {
  border-color: currentColor;
}

/* CSS-drawn check in the row's text color — readable on the light popover
     without depending on an accent fill behind a white glyph. */

.ms-checklist-box:checked::after {
  content: "";
  position: absolute;
  left: 4.5px;
  top: 1.5px;
  width: 4.5px;
  height: 8px;
  border: solid currentColor;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

.ms-checklist-other,
  .select-popover-other {
  margin-top: 0.25rem;
  padding: 0.35rem 0.55rem 0.2rem;
  border-top: 1px solid var(--line-softer);
}

/* Repeatable "+ Sonstiges": dashed trigger toggles into an inline input. */

.ms-checklist-other-trigger {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.7rem;
  border: 1px dashed var(--line-soft);
  background: transparent;
  color: var(--ink-soft);
  border-radius: 999px;
  font-size: 0.7969rem;
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.ms-checklist-other-trigger:hover {
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--accent-strong, var(--accent));
}

.ms-checklist-other-trigger[hidden] {
  display: none;
}

.ms-checklist-other-input {
  width: 100%;
  padding: 0.34rem 0.7rem;
  border-radius: 999px;
  border: none;
  background: var(--surface-raised, #fff);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent);
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  outline: none;
}

.ms-checklist-other-input[hidden] {
  display: none;
}

.select-popover-other-input {
  width: 100%;
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  outline: none;
}

/* ===================================================================== */

/* Single-select dropdown: quiet custom popover (Inline-Dokument 05)      */

/* ===================================================================== */

.select-popover {
  position: relative;
}

.select-popover-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  min-width: 12rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--line-soft);
  background: color-mix(in srgb, var(--surface-raised) 88%, white 12%);
  color: var(--ink-strong);
  border-radius: var(--radius-md);
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

.select-popover.is-open .select-popover-trigger {
  border-color: var(--accent-border-strong);
}

.select-popover-value {
  font-family: var(--font-prominent);
  font-size: 0.95rem;
  font-weight: 500;
}

.select-popover-value.is-placeholder,
  .select-popover-option-label.is-placeholder {
  color: var(--ink-soft);
  font-style: italic;
}

/* Accent check on the active option, pushed to the row's right edge. */

.select-popover-check {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  margin-inline-start: auto;
}

.select-popover-option[data-active] .select-popover-check {
  background:
      no-repeat center/0.85rem
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%234f7a28' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='2 6.5 5 9 10 3'/%3E%3C/svg%3E");
}

.select-popover-option[data-active] {
  color: var(--accent-strong, var(--accent));
}

/* Inline-Dokument: inside the document the trigger reads as the typeset
     value itself — no box at rest, hover tint like the text inputs (Slice 03),
     --font-prominent 17px. Elsewhere (Create-Form, Klassen-Editor) the boxed trigger
     above stays. */

.doc-group .select-popover-trigger {
  min-width: 0;
  border: none;
  background: transparent;
  padding: 2px 6px;
  margin: -2px -6px;
  border-radius: 5px;
  gap: 0.45rem;
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

.doc-group .select-popover-trigger:hover {
  background: rgba(var(--_ink-paper) / 0.06);
  box-shadow: inset 0 -1px 0 rgba(var(--_ink-paper) / 0.13);
}

.doc-group .select-popover-value {
  font-size: 17px;
  font-weight: var(--font-prominent-weight);
  line-height: 1.35;
  color: var(--ink-strong);
}

.doc-group .select-popover-value.is-placeholder {
  color: var(--ink-soft);
}

.doc-group .select-popover-chevron {
  color: var(--ink-soft);
}

/* Template mode: the editable typeset value (the real submit input, inline
     optic via Slice 03 inside .doc-group) plus a quiet chevron trigger that
     opens the shared select-popover panel with the template options. */

.select-template {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  /* Flex item of .field-value-row: allow it to shrink so a long template
       value wraps within the row instead of pushing the column wider. */
  min-width: 0;
}

/* Like the plain text fields, the template value sizes to its content so the
     chevron trails the typed text instead of being pushed to the row's right
     edge. No grow; min-width:0 lets a long value shrink inside the row's
     max-width (field-sizing keeps the empty placeholder readable). */

.select-template-value {
  flex: 0 1 auto;
  field-sizing: content;
  width: auto;
  min-width: 0;
  max-width: 100%;
}

.select-template-trigger {
  display: inline-flex;
  border: none;
  background: transparent;
  padding: 0.2rem;
  border-radius: 4px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.select-template-trigger:hover {
  color: var(--ink-strong);
  background: rgba(var(--_ink-paper) / 0.06);
}

.select-template.is-open .select-popover-chevron {
  transform: rotate(180deg);
}

/* Pin-create popover: class pills + preloaded object list. Mirrors the
     "Tag setzen" popover from prototype/Repertum_latest.html — pill chips
     with a class-color dot, then a scrollable list filterable in-place. */

.hotspot-create-popover {
  background: var(--surface-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 0.6rem 0.7rem 0.55rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  width: 17rem;
  max-width: calc(100% - 1rem);
  /* Safety net for the class-picker step if the JS clamp can't fully fit it
       on a very small viewer: never taller than the viewport, scroll instead
       of clipping. The new-object form uses the dedicated modal below. */
  max-height: calc(100vh - 1.5rem);
  overflow-y: auto;
}

/* Slice 06: primary "create new related object" action in the selection list. */

.hotspot-related-create-new {
  display: block;
  width: 100%;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--accent-strong);
  border-radius: var(--radius-sm);
  background: var(--accent-strong);
  color: white;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  line-height: 1.2;
}

.hotspot-related-create-new:hover {
  background: color-mix(in srgb, var(--accent-strong) 88%, black);
}

/* Inline "Neues Objekt anlegen" Kennung prompt (blank-area pin-create).
     A single identifier field + actions rendered inside the object step,
     mirroring the relation drawer's "+ Neu" flow — no separate modal. */

.hotspot-related-new {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.hotspot-related-identifier {
  width: 100%;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  background: var(--surface-base);
  color: var(--ink-base);
  font-size: 0.85rem;
}

.hotspot-related-new .hotspot-related-cancel {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  background: var(--surface-base);
  color: var(--ink-base);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  line-height: 1.2;
}

.hotspot-create-new-feedback {
  margin: 0;
  font-size: 0.8rem;
  color: var(--danger-strong, #b91c1c);
}

/* Slice 03: the popover no longer asks for the Schablone, it names the
     active one. A quiet caption, not a control — nothing here is clickable. */

.hotspot-create-schablone {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: 0.78rem;
  color: var(--ink-soft, #475569);
  line-height: 1.2;
}

.hotspot-create-schablone__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  flex-shrink: 0;
  background: var(--ink-softer, #94a3b8);
}

.hotspot-create-schablone__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hotspot-create-schablone__text span {
  font-weight: 600;
  color: var(--ink-base);
}

.hotspot-create-results {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 13rem;
  overflow-y: auto;
  border: 1px solid var(--line-softer);
  border-radius: var(--radius-sm);
}

.hotspot-create-result {
  padding: 0.35rem 0.55rem;
  font-size: 0.82rem;
  cursor: pointer;
  border-bottom: 1px solid var(--line-softer);
}

.hotspot-create-result:last-child {
  border-bottom: none;
}

.hotspot-create-result:hover {
  background: color-mix(in srgb, var(--accent-strong) 6%, white);
}

.hotspot-create-result--hint {
  font-style: italic;
  color: var(--ink-soft);
  cursor: default;
}

.hotspot-create-result--hint:hover {
  background: transparent;
}

/* Values input + "Hinzufügen" side by side, so the button sits in the
     input's extension instead of stacking below it (easy to miss). */

.select-values-add {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
}

.select-values-add input {
  flex: 1 1 auto;
  min-width: 0;
}

.select-values-add button {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Signal that the box holds unsaved values "Speichern" will still adopt. */

.select-values-add input.has-pending {
  border-color: var(--warning-border-strong);
  background: var(--warning-soft);
}

.select-values-pending-hint {
  margin-top: 0.35rem;
  font-size: 0.8125rem;
  color: var(--warning-ink);
}

.topbar-meta {
  align-self: center;
}

.surface-header,
    .surface-footer,
    .page-header {
  align-items: flex-start;
}

.data-table thead th,
    .data-table tbody td {
  padding-inline: 0.9rem;
}

/* Grouped archive view: cascade groups with lazily loaded child rows. */

.archive-search {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.archive-search-input {
  max-width: 26rem;
}

.archive-group-cell {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.archive-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--ink-soft);
  transition: transform 120ms ease;
}

.archive-toggle[aria-expanded="true"] {
  transform: rotate(90deg);
}

.archive-children-row > td {
  background: rgba(240, 235, 228, 0.35);
}

.archive-children-table {
  width: 100%;
}

.archive-children-table td {
  border: 0;
  padding: 0.3rem 0.9rem;
  padding-left: 2.4rem;
}

.archive-children-table td + td {
  padding-left: 0.9rem;
}

.archive-child-match > td {
  background: color-mix(in srgb, var(--warning-soft) 55%, transparent);
}

/* Upload page (Anhänge Slice 07): "Kamera" and "Verknüpfen" sit side by side,
     each an icon plus its label. The buttons keep their normal pill look. */

.upload-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.upload-action-row .button {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.upload-action-row .\!button {
  display: inline-flex !important;
  align-items: center !important;
  gap: 0.45rem !important;
}

.upload-action-icon {
  width: 18px;
  height: 18px;
  flex: none;
}

/* The upload landing field: left-aligned with the rest of the form, wide
     enough to be an obvious drop target but not a full-width banner. */

#upload-dropzone {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
  max-width: 30rem;
  padding: 1.1rem 1.25rem;
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface-soft);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

#upload-dropzone:hover,
  #upload-dropzone:focus-visible {
  border-color: var(--accent);
}

#upload-dropzone.is-dragover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-soft));
}

.file-dropzone-icon {
  width: 32px;
  height: 32px;
  flex: none;
  color: var(--ink-soft);
}

.file-dropzone-text {
  min-width: 0;
}

.file-dropzone-title {
  margin: 0;
  font-weight: 600;
}

.file-dropzone-hint {
  margin: 0.15rem 0 0 0;
  color: var(--ink-soft);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

.\!visible {
  visibility: visible !important;
}

.visible {
  visibility: visible;
}

.invisible {
  visibility: hidden;
}

.collapse {
  visibility: collapse;
}

.static {
  position: static;
}

.fixed {
  position: fixed;
}

.absolute {
  position: absolute;
}

.relative {
  position: relative;
}

.sticky {
  position: sticky;
}

.mt-12 {
  margin-top: 3rem;
}

.mt-2 {
  margin-top: 0.5rem;
}

.mt-4 {
  margin-top: 1rem;
}

.mt-6 {
  margin-top: 1.5rem;
}

.mt-8 {
  margin-top: 2rem;
}

.line-clamp-2 {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.block {
  display: block;
}

.inline {
  display: inline;
}

.flex {
  display: flex;
}

.inline-flex {
  display: inline-flex;
}

.\!table {
  display: table !important;
}

.table {
  display: table;
}

.\!grid {
  display: grid !important;
}

.grid {
  display: grid;
}

.contents {
  display: contents;
}

.\!hidden {
  display: none !important;
}

.hidden {
  display: none;
}

.h-full {
  height: 100%;
}

.w-full {
  width: 100%;
}

.shrink {
  flex-shrink: 1;
}

.border-collapse {
  border-collapse: collapse;
}

.transform {
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.select-all {
  -webkit-user-select: all;
     -moz-user-select: all;
          user-select: all;
}

.resize {
  resize: both;
}

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.border {
  border-width: 1px;
}

.text-sm {
  font-size: 0.875rem;
  line-height: 1.25rem;
}

.uppercase {
  text-transform: uppercase;
}

.italic {
  font-style: italic;
}

.text-gray-600 {
  --tw-text-opacity: 1;
  color: rgb(75 85 99 / var(--tw-text-opacity, 1));
}

.shadow {
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.outline {
  outline-style: solid;
}

.ring {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.blur {
  --tw-blur: blur(8px);
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.filter {
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.transition {
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

/* Self-hosted Linux Biolinum (free Optima alternative, OFL/GPL+FE).
   Full glyph coverage incl. German umlauts/ß. Source TTFs live next to
   the woff2 in /static/fonts/biolinum/. */

@font-face {
  font-family: "Linux Biolinum";

  src: url("/static/fonts/biolinum/LinBiolinum_Rah.woff2") format("woff2");

  font-weight: 400;

  font-style: normal;

  font-display: swap;
}

@font-face {
  font-family: "Linux Biolinum";

  src: url("/static/fonts/biolinum/LinBiolinum_RBah.woff2") format("woff2");

  font-weight: 600 700;

  font-style: normal;

  font-display: swap;
}

@font-face {
  font-family: "Linux Biolinum";

  src: url("/static/fonts/biolinum/LinBiolinum_RIah.woff2") format("woff2");

  font-weight: 400;

  font-style: italic;

  font-display: swap;
}

@media (max-width: 640px) {
}
