/*
 * Remote reading app — list-view chrome only. Reuses report-base.css's :root tokens (loaded before this
 * file in index.html) for the detail view's cards/dashboard/modals; this file only styles the app shell
 * that report-base.css has no equivalent for (the report list, header, error state).
 */
body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
}

/*
 * Page container for the list view (2026-09-07).
 *
 * Header, toolbar, error banner and list each carried their own `max-width: 900px` and no horizontal
 * margin at all — there was not a single `margin-inline: auto` in this file. A max-width without one
 * constrains the width but never moves the box, so on any screen wider than 900px the whole app sat
 * flush against the left edge with a growing empty gap on the right. The detail view never showed it
 * because it is styled by report-base.css, whose `.layout` does centre itself (`max-width: 1440px;
 * margin: 0 auto`) — which is why the bug only ever appeared on the list.
 *
 * One grid column instead of four separate max-widths: every band is bounded and centred by the
 * container, so they share one axis by construction and cannot drift apart when one of them is
 * edited later. `minmax(0, …)` keeps long unbreakable strings (report titles, file names) from
 * pushing the column past its bound.
 */
:root {
  --list-content-max: 900px;
}

#listView {
  display: grid;
  grid-template-columns: minmax(0, var(--list-content-max));
  justify-content: center;
  align-content: start;
}

/* F-XX/2026-09-06: the browser's UA stylesheet only sets `[hidden] { display: none }` — any AUTHOR
 * rule on the same element that also declares `display` (e.g. `.push-settings { display: flex }`,
 * `.update-banner { display: flex }`, `.remote-app-error { display: flex }`) wins over that UA default
 * regardless of selector specificity, because author styles always outrank user-agent styles in the
 * cascade. Every one of those three components toggles visibility purely via `el.hidden = true/false`
 * in remote-app.js — with no `[hidden]` override, the `hidden` attribute was cosmetically inert on all
 * three: the push-settings panel could never actually be dismissed (always `display:flex`, "close"
 * click did nothing visible), and the update banner rendered visible on every single load regardless
 * of whether a real update existed. One global, specificity-proof rule closes this for these three and
 * any future `hidden`-toggled component in this file. */
[hidden] {
  display: none !important;
}

.remote-app-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 20px 16px 8px;
}

.remote-app-header h1 {
  margin: 0;
  font-size: 1.3rem;
  margin-right: auto;
}

.detail-backbar {
  position: sticky;
  /* F-62/2026-09-21: starts below the sticky banner strip (.app-banners) while a banner is showing — remote-app.js
     syncBannerOffset() writes its height here. */
  top: var(--app-banners-height, 0px);
  z-index: 6;
  display: flex;
  /* F-62/2026-09-21: the offline-queue badge and the "von einem anderen Gerät aktualisiert" banner are rows of this bar; at 390 px
     they do not fit next to Liste/Theme/Hilfe and wrap onto a second line. */
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* Same bound and padding as report-base.css's .layout below it, so the back button lines up with
     the report content instead of hugging the left edge on a wide screen (2026-09-07). */
  max-width: var(--layout-max);
  margin-inline: auto;
  padding: 10px clamp(16px, 3vw, 28px);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

/* Loading state between tapping a report and its spec arriving (2026-09-07) — same max-width and
   padding as .detail-backbar/.layout so the placeholder occupies the position the report will. */
.detail-loading {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  max-width: var(--layout-max);
  margin-inline: auto;
  padding: 28px clamp(16px, 3vw, 28px);
  color: var(--muted);
}

.detail-loading-spinner {
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: detail-loading-spin .7s linear infinite;
}

.detail-loading-title {
  font-weight: 600;
  color: var(--text);
}

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

/* A spinner that cannot spin still has to say "busy" — the border stays, only the rotation stops. */
@media (prefers-reduced-motion: reduce) {
  .detail-loading-spinner { animation: none; }
}

/* report-base.css's shared .floating-actions (Theme/Hilfe) is also used, unmodified, by every
   standalone locally-rendered report — only neutralize its own padding/border/background when it is
   nested inside this remote-app-only backbar, so "Liste"/"Theme"/"Hilfe" read as one line here without
   touching the shared class's default look everywhere else. */
.detail-backbar .floating-actions {
  padding: 0;
  border-bottom: none;
  background: transparent;
}

.detail-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: auto;
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--panel);
  font-weight: 600;
  text-decoration: none;
  transition: border-color .15s ease, background-color .15s ease;
}

.detail-back:hover,
.detail-back:focus-visible {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.offline-badge {
  font-size: .8rem;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--warn-bg, #4a3a12);
  color: var(--warn-text, #ffcf6b);
}

/* F-62/2026-09-21: the auto margin keeps the badge next to "Liste" and pushes Theme/Hilfe to the right edge. */
.detail-backbar .offline-badge {
  margin-right: auto;
}

/* E-05/2026-09-24: a report served from the Service Worker's offline cache fallback (sw.js
 * networkFirstWithCacheFallback()) instead of a live answer — informational, not a warning like
 * .offline-badge, so it keeps the info tone already used for .sev-badge.sev-info/.loop-marker. */
.cache-stale-badge {
  font-size: .8rem;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--info-soft);
  color: var(--info);
}

/* Same push-right trick as .offline-badge above — a hidden badge has no margin (display: none), so
 * whichever of the two is the last visible one before .floating-actions provides the gap. */
.detail-backbar .cache-stale-badge {
  margin-right: auto;
}

/* F-62/2026-09-21: the banner owns a full row of the (sticky) back bar, so it cannot scroll out of view. */
.detail-backbar .remote-update-banner {
  flex: 1 0 100%;
  margin: 0;
}

.push-settings {
  position: absolute;
  top: 100%;
  right: 16px;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .3);
  font-size: .85rem;
}

.push-settings label {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.push-settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-weight: 600;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 2px;
}

/* Same framed panel as the report's own toolbar (report-base.css `.toolbar`) — deliberately not
 * sticky/fixed and with no scroll-triggered hide/reveal (user request 2026-09-08, same reasoning as
 * report-base.css's `.toolbar` comment: the floating/auto-hide behaviour never read as intentional no
 * matter how it was tuned). Plain in-flow block: stays at the top of the document, scrolling past it is
 * the same as scrolling past any other section. `--panel` rather than `--bg` so the frame reads as a
 * panel distinct from the page background. */
#listToolbar {
  /* TOOLBAR-CARDS/2026-11-03: the toolbar itself is only the column; the frames are the cards inside (report-base.css .toolbar-cards).
     An id selector, so it also beats report-base.css's ≤720px `.toolbar` padding. */
  padding: 0;
  background: none;
  border: 0;
  /* SPACING-GUTTER/2026-10-03: same 16px side gutter as the header, the error banner and .remote-list. */
  margin: 0 16px 10px;
}

/* TOOLBAR-CARDS/2026-11-03: the filter is a card of its own (report-base.css .toolbar-card); no fold around it any more. */
.filter-panel { flex: 1 1 100%; width: 100%; }

/* The wizard card disappears with its button (the button stays hidden until the manifest has decidable codes). */
.wizard-card:has(> #homeWizardBtn[hidden]) { display: none; }

#globalSearch {
  flex: 1 1 100%;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--text);
  font: inherit;
}

.chip-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

/* Pill (matches .chip's pill footprint) around the bare native date widget — the two used to sit at
   different heights and read as stray, ungrouped controls next to the range chips. No visible "Von"/
   "Bis" label text (2026-09-07 user request: a single dash between the two inputs already says which
   side is which — kept as an aria-label on each input instead, so the fields stay accessible). */
.date-field {
  display: inline-flex;
  align-items: center;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel);
  font-size: .85rem;
  color: var(--muted);
}

.date-field input[type="date"] {
  padding: 4px 8px;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--panel-soft);
  color: var(--text);
  font: inherit;
}

/* Visible only in the mobile stacked layout below — desktop keeps the dash-only pairing from 2026-09-07
   (no redundant "Von"/"Bis" text next to two fields already sitting side by side). */
.date-field-label {
  display: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--muted);
}

.date-field-group {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 0;
  min-width: 0;
}

/* Belongs to the Zeitraum level and therefore sits directly under its chip row — it is the same filter
   expressed freely, not a level of its own (see LIST_LEVELS in remote-app.js: `range: 'custom'`). */
.filter-level .date-range-row {
  margin-top: 4px;
}

.date-range-row {
  display: flex;
  align-items: center;
  flex: 1 1 100%;
  gap: 8px;
}

.date-range-dash {
  color: var(--muted);
  font-size: .85rem;
  flex: 0 0 auto;
}

.date-range-row .date-field {
  flex: 1 1 0;
}

.date-range-row .date-field input[type="date"] {
  width: 100%;
  box-sizing: border-box;
}

/* Zeitraum level, mobile only (user report 2026-09-15: "date filter nebeneinander zu lang"). Native
   date inputs can't wrap or shrink like chips, so on a narrow phone "Von"/"Bis" need their own vertical
   stack with visible labels, since the dash no longer sits between them to say which is which. */
@media (max-width: 767px) {
  .date-range-row {
    flex-direction: column;
    align-items: stretch;
  }

  .date-range-dash { display: none; }

  .date-field-group {
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
  }

  .date-field-label { display: block; }
}

.btn-block {
  display: block;
  flex: 1 1 100%;
  width: 100%;
  box-sizing: border-box;
  text-align: center;
}

.remote-app-error {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 16px 8px;
  padding: 12px 14px;
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
  color: var(--danger);
}

/* F-XX/2026-09-07: a flex item's default `min-width: auto` locks its shrunk width to its own
 * min-content size — for this span, that means the longest unbreakable token in the error text (a
 * report filename with no spaces, e.g. "20260904-0011-CODE_REVIEW_...json", or a validateSpec message
 * quoting one). With the dismiss button pinned at `flex-shrink: 0` right below, that min-content floor
 * pushed the whole row past the viewport — the button ended up off-screen and undismissable, and the
 * page gained horizontal scroll (measured: 471px document width in a 390px viewport). Same bug class
 * as the `minmax(0, …)` fix on #listView's grid column above — `min-width: 0` lets this span shrink
 * below its min-content size so wrapping (`overflow-wrap`) can actually take effect instead of being
 * pre-empted by the flex algorithm. */
.remote-app-error span {
  min-width: 0;
  overflow-wrap: break-word;
}

.remote-app-error .btn { flex-shrink: 0; }

/* I5/2026-09-14: same shape as .remote-app-error above (icon+text + one action button), accent instead
 * of danger tint — this is a low-stakes "something changed elsewhere" nudge, not an error. Lives inside
 * #detailView (see showRemoteUpdateBanner() in remote-app.js), so a reload/navigation away removes it
 * automatically along with the rest of the detail DOM — no separate hide/dismiss handler needed. */
.remote-update-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 12px;
  padding: 10px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
}

.remote-update-banner span {
  min-width: 0;
  overflow-wrap: break-word;
}

.remote-update-banner .btn { flex-shrink: 0; }

.remote-list-divider {
  /* SPACING-GUTTER/2026-10-03: no side inset — the day heading starts on the same edge as the cards and the page title (was 4px in). */
  padding: 12px 0 2px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--muted);
}

.remote-list-divider:first-child { padding-top: 0; }

.remote-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 16px 32px;
}

.remote-list-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 12px; /* REPORT-UX/2026-10-14: tighter card */
  border: 1px solid var(--border);
  border-left-width: 1px;
  border-radius: var(--radius-sm);
  background: var(--panel);
  text-decoration: none;
  color: var(--text);
  box-shadow: var(--shadow);
  /* Base transition lives here, not on .is-updated (E-10/2026-09-08): the fade-out on class removal
     needs the transition declared on the element's resting style, not only on the temporary class
     that is gone by the time the transition is supposed to be running. color/box-shadow added
     2026-09-17 alongside .is-updated's own color/box-shadow — same reasoning, one property each. */
  transition: background 3s ease-out, color 3s ease-out, box-shadow 3s ease-out;
}

.remote-list-row:hover,
.remote-list-row:focus-visible,
.remote-list-row:focus-within {
  border-color: var(--accent);
}

/* F-28/2026-10-03: the row is a <div> (the copy button must not sit inside a link); only the title text is the <a>. The row
 * keeps the old link's look - pointer cursor, inherited colour, no underline - and remote-app.js forwards a click anywhere on
 * the row to that link. */
.remote-list-row { cursor: pointer; }

.remote-list-link {
  color: inherit;
  text-decoration: none;
}

/* Finished (every decidable code reached fixed/ignored/accepted) gets a colored left edge so it stands
   out from a pending row without reading the ✅/⏳ marker text — a pending row keeps the plain border,
   the hourglass marker already carries that state (user request 2026-09-08). */
.remote-list-row.is-complete {
  border-left-color: var(--ok);
  border-left-width: 4px;
}

/* E-10/2026-09-08, revised 2026-09-17 (user report: plain --text over the solid --accent background
 * was unreadable — dark text on a dark blue fill): brief highlight for a row whose codes changed
 * during a silent background list refresh (runListPoll()/runVisibilityRefresh() in remote-app.js).
 * Text now inverts to --accent-contrast (the same white already used wherever --accent is a solid fill
 * elsewhere, e.g. the primary button) while the flash is active. Both directions now animate: entering
 * uses this rule's own short transition (deliberately shorter than the 3s fade-out below, so the cue
 * still reads as an immediate "something changed" snap-to-attention, not a slow fade-in that could be
 * missed), and remote-app.js removes the class again after 4s, letting the base rule's longer
 * transition fade it back out. */
.remote-list-row.is-updated {
  background: var(--accent);
  color: var(--accent-contrast);
  box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow);
  transition: background 0.35s ease, color 0.35s ease, box-shadow 0.35s ease;
}

/* FLASH-INVERT/2026-09-24: the row's own colour inverted, but the code count (--text), the meta line (--muted) and
   every badge carry a colour of their own, so they stayed dark on the dark blue fill. Every descendant now inverts
   too; tinted pills keep their shape through a translucent contrast wash. `.is-updated-fading` (remote-app.js
   flashUpdated()) fades the descendants back together with the row's 3s background fade. */
.remote-list-row.is-updated * {
  color: var(--accent-contrast) !important;
  border-color: color-mix(in srgb, var(--accent-contrast) 45%, transparent) !important;
  transition: color 0.35s ease, background-color 0.35s ease, border-color 0.35s ease;
}

/* FLASH-BADGE/2026-11-03: .progress-group keeps its opaque panel fill; without the wash its inverted white text is unreadable. */
.remote-list-row.is-updated :is(.sev-badge, .loop-marker, .completion-badge, .progress-badge, .attention-badge, .progress-group) {
  background-color: color-mix(in srgb, var(--accent-contrast) 16%, transparent) !important;
}

.remote-list-row.is-updated-fading * {
  transition: color 3s ease-out, background-color 3s ease-out, border-color 3s ease-out;
}

/* REPORT-UX/2026-10-13: the icon button keeps its transparent border through the flash. The descendant rule above gave it a 45% outline
   box while the row was inverted, which read as a second, broken control. Hover/focus still show the border (report-base.css). */
.remote-list-row.is-updated .copy-title-btn,
.remote-list-row.is-updated-fading .copy-title-btn {
  border-color: transparent !important;
}

@media (prefers-reduced-motion: reduce) {
  .remote-list-row,
  .remote-list-row.is-updated,
  .remote-list-row.is-updated *,
  .remote-list-row.is-updated-fading * {
    transition: none;
  }
}

.remote-list-title {
  font-weight: 600;
  font-size: .93em; /* REPORT-UX/2026-10-14: title slightly smaller */
}

/* The ✅/⏳ marker sits in front of the title (user request 2026-09-07). Shape and colours come from
   report-base.css, which the app already loads for the detail view — only the inline spacing is
   list-specific, and the fixed width keeps every title in the column starting at the same x. */
/* F-29g/2026-09-21: the marker is icon + count ("⏳ 12"). As an inline-block of 1.3em width the two inline spans no longer fit
 * and the count wrapped below the icon, which split a row's title over three lines. An inline flex box does not wrap its
 * items; the width is a minimum that leaves room for two digits, so the titles still start at one x for every marker. */
.remote-list-title .completion-badge.is-marker {
  display: inline-flex;
  min-width: 2.5em;
  margin-right: 2px;
  white-space: nowrap;
  color: inherit;
}

.remote-list-meta {
  color: var(--muted);
  font-size: .9rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/* "total klar links": the code count is the one number that always matters, so it gets its own
   emphasized span on the left of the meta line instead of blending into the muted time text. */
.remote-list-total {
  font-weight: 600;
  color: var(--text);
}

/* Left-aligned, below a thin divider rule (2026-09-06 user feedback: was bottom-right, now left with
   an hr above it, matching the rest of the card's left-aligned text). */
.remote-list-divider-rule {
  width: 100%;
  border: none;
  border-top: 1px solid var(--border);
  margin: 6px 0 2px;
}

/* 2026-09-20 user request: renderPreviewCluster() emits TWO of these per row — `.remote-list-badges-severity`
   (severity counters + Positive count) and `.remote-list-badges-actions` (progress/flag counters, loop marker).
   Each is a full-width line of its own inside the column-flex row, so action icons never share a line with
   the severity counters. */
.remote-list-badges {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  width: 100%;
}

.sev-badge {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
}

.sev-badge.sev-krit { background: var(--danger-soft); color: var(--danger); }
/* F-02/2026-09-08: sev-high and sev-idea were missing entirely, so a 🟠 High finding and — far more
   visibly — every single E code (deriveCode() forces E to the "idea" severity, [D15]) rendered as an
   unstyled grey pill in the list while the report detail view coloured them correctly. The two rules
   below reuse the same --high/--idea tokens report-base.css already defines for .summary-count-row,
   so the list and the report header cannot drift apart on colour. */
.sev-badge.sev-high { background: var(--high-soft); color: var(--high); }
.sev-badge.sev-warn { background: var(--warn-soft); color: var(--warn); }
.sev-badge.sev-info { background: var(--info-soft); color: var(--info); }
.sev-badge.sev-ok { background: var(--ok-soft); color: var(--ok); }
.sev-badge.sev-idea { background: var(--idea-soft); color: var(--idea); }

/* Positive gets the same badge shape as the Ampel pills (just its own green tint, no fraction — a
   Positive has no "open" state to contrast against, so "nur total pos" per user feedback). */
.sev-badge.pos-count { background: var(--ok-soft); color: var(--ok); }

/* Same pill shape as .sev-badge (padding, radius, tabular alignment) instead of a bare floating emoji
   with no background (2026-09-14 user feedback: "sieht beschissen aus"). The 🛠/💬/❓ markers that used to
   sit here (.queued-marker, .refine-marker, .open-question-marker) are gone: the progress and attention
   badges from report-base.css replaced them 2026-09-19, with text instead of a bare icon. */
.loop-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: .8rem;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}

/* I7/I8, 2026-09-07 — list-row counterpart to the report header's loop-status badge (report-base.css
   .loop-status-badge); see renderPreviewCluster() in remote-app.js. Same info tint as that badge's
   default (non-auto-triggered) state, so the two stay visually paired. font-weight added 2026-09-17
   (user report: a bare icon among 4-5 other same-weight icon pills was too easy to miss) — this is the
   one marker meaning "something is actively happening right now", so it gets to stand out slightly. */
.loop-marker { background: var(--info-soft); color: var(--info); font-weight: 700; }

/* Auto-started counterpart to report-base.css's .loop-status-badge.is-auto-trigger — same accent tint,
   scaled down to this dense icon-only marker row (I1/2026-09-14). */
.loop-marker.is-auto-trigger {
  background: var(--accent-soft);
  color: var(--accent);
}

.remote-app-state {
  padding: 16px;
  color: var(--muted);
}

#detailView .layout {
  min-height: 100vh;
}

/* F-62/2026-09-21: the banners and the refresh spinner live in one strip that sticks to the TOP of the page. They used to be
   fixed to the bottom edge, where they sat on top of the dialog buttons and of the toast (which is anchored there). The strip
   is the first element of <body>, so it takes its own height in the flow and the page never starts underneath it. */
.app-banners {
  position: sticky;
  top: 0;
  z-index: 40;
}

/* Persistent (no auto-hide) — distinct from the transient #toast, since a stale-version notice must
   stay until the user acts on it, not disappear after 2.6s like a save-confirmation would. A single compact row (text and
   button side by side, wrapping to two lines on a narrow screen): a strip that stays on screen while the user scrolls must
   not eat a third of a phone, which the full-width stacked block layout of the bottom banner did. */
/* F-3/2026-09-27: matches .detail-backbar/.detail-loading's own bound and horizontal padding (max-width +
 * margin-inline: auto + the same clamp()) so "Neu laden" sits inset like the rest of the page content
 * instead of hugging the raw viewport edge on mobile — this strip is the only banner that used a fixed
 * 16px instead of that shared clamp(). box-sizing: border-box keeps the padding inside the border-bottom
 * width instead of adding to it. */
.update-banner {
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  max-width: var(--layout-max);
  margin-inline: auto;
  padding: 8px clamp(16px, 3vw, 28px);
  background: var(--panel);
  border-bottom: 2px solid var(--accent);
  box-shadow: 0 4px 16px rgba(0, 0, 0, .35);
  font-size: .95rem;
}

.update-banner > span {
  flex: 1 1 14rem;
  font-weight: 600;
}

/* E-09/2026-09-08: diagnostic-only cache version suffix, kept visually subordinate to the main
   "Neue Version verfügbar" message it's appended to. */
.update-banner-version {
  font-weight: 400;
  opacity: .65;
  font-size: .8em;
}

.update-banner-version:not(:empty)::before {
  content: " · ";
}

/* Shared by .update-banner and .install-banner (2026-09-07: install banner has two peer buttons —
   "Installieren"/"Schließen" — styled like the update banner's own single button). */
.banner-actions {
  display: flex;
  gap: 8px;
}

.banner-actions .btn {
  box-sizing: border-box;
  min-height: 40px;
  padding: 8px 16px;
  font-size: .95rem;
  text-align: center;
}

/* F-29e/2026-09-24: the install hint measured about 190 px at 390 px wide (a three-line sentence above two full-size buttons) and
   stays on screen while the list scrolls. Now one row: the text may shrink or wrap to two short lines, the buttons never wrap
   below it, and "Schließen" is a 40 px ✕. The buttons keep their 40 px tap height (F-29b), so the strip is about 48 px. */
.install-banner {
  flex-wrap: nowrap;
  padding: 4px 8px 4px 12px;
  font-size: .85rem;
}

.install-banner > span {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 500;
}

.install-banner .banner-actions {
  flex: none;
  gap: 4px;
}

.install-banner .banner-actions .btn {
  padding: 6px 12px;
  font-size: .85rem;
}

.install-banner #installBannerClose {
  min-width: 40px;
  padding: 6px;
}

/* F-62/2026-09-21: part of the sticky strip but out of its flow — absolutely placed just below it, so the spinner appearing
   for a moment (every background refresh) never moves the page. F-71/2026-09-21: the round icon is drawn in CSS like
   .detail-loading-spinner (the hourglass it replaces already means "N in Arbeit"). */
.lifecycle-spinner {
  position: absolute;
  top: calc(100% + 10px);
  right: 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: .8rem;
  box-shadow: var(--shadow);
}

.lifecycle-spinner-icon {
  width: 12px;
  height: 12px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: detail-loading-spin .7s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .lifecycle-spinner-icon { animation: none; }
}

#backToList {
  text-decoration: none;
}

/* ---------------------------------------------------------------- Login (F-14/2026-09-07)
   Session login replaced HTTP Basic Auth, so the app now needs a page of its own instead of the
   browser's credential dialog. Uses the same tokens as the rest of the app — no separate palette. */

.login-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  margin: 0;
  padding: 1.5rem;
  background: var(--bg);
}

.login-card {
  width: 100%;
  max-width: 22rem;
  padding: 1.75rem 1.5rem 2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px);
  background: var(--panel); /* F-103/2026-10-20: was the undefined token --surface; the app token is --panel */
  box-shadow: var(--shadow);
}

.login-card h1 {
  margin: 0 0 0.25rem;
  font-size: 1.4rem;
}

.login-sub {
  margin: 0 0 1.25rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.login-field {
  display: block;
  margin-bottom: 0.9rem;
}

.login-field span {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}

.login-field input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.6rem 0.7rem;
  font: inherit;
  color: var(--text); /* F-103/2026-10-20: was the undefined token --fg; the app token is --text */
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius, 10px);
}

.login-field input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.login-error {
  margin-bottom: 1rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius, 10px);
  border: 1px solid var(--sev-krit, #b3261e);
  background: color-mix(in srgb, var(--sev-krit, #b3261e) 12%, transparent);
  font-size: 0.88rem;
}

/* The logout control sits in the header next to the push button. */
#logoutForm {
  display: inline;
}

/* Inline comment-autosave indicator (remote-app.js wireCommentAutosave()) — sits directly under its
   .comment-box (report-base.css), never the global #toast, since autosave fires on every 700ms typing
   pause and a toast per keystroke pause would be noise. Empty/idle state renders nothing (no reserved
   height flash) until a save actually starts. */
.comment-autosave-status {
  display: block;
  margin-top: 6px;
  min-height: 16px;
  font-size: 12px;
  line-height: 16px;
}

.comment-autosave-status.is-saving,
.comment-autosave-status.is-offline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
}

.comment-autosave-status.is-saving::before {
  content: "";
  width: 11px;
  height: 11px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: detail-loading-spin .7s linear infinite;
}

.comment-autosave-status.is-saved {
  color: var(--ok);
}

/* I4/2026-09-14: the brief .is-saved flash (above) fades into this quiet, persistent state instead of
 * clearing to nothing — a user who looks away mid-flash and back later otherwise finds an empty line
 * with no way to tell whether the save actually happened. Stays until the next autosave cycle
 * overwrites it (see setCommentAutosaveIndicator() in remote-app.js). */
.comment-autosave-status.is-saved-persist {
  color: var(--muted);
}

.comment-autosave-status.is-error {
  color: var(--danger);
}

@media (prefers-reduced-motion: reduce) {
  .comment-autosave-status.is-saving::before { animation: none; }
}

/* F-STATE-CLOSE/2026-09-20 (D11/D25): the close controls for 🧾/📖/⏪ sit in a .reopen-actions group (same pill look
 * as the toggles below them); only the in-flight state is new — the write is one request, a second tap waits. */
.state-actions .chip-toggle:disabled {
  opacity: 0.55;
  cursor: progress;
}

/* F-29b/F-70/2026-09-20: tap targets below 40 px, measured at 390 px wide — back 38.8, logout/push 38.8, theme/
 * help 31, the push dialog's close 31, the push category rows 21.8 (13 px checkboxes), the date fields 31.8 and
 * the copy-title button 28x19. A thumb needs about 40 px; the padding is the only thing that changes, so the row
 * and header layouts keep their shape. The shared chips, `.rf-btn`, `.row-comment-flag`, the summary toggles and
 * the side-nav toggle live in report-base.css and are sized there. */
.remote-app-header .btn,
.detail-back,
.detail-backbar .btn,
.push-settings-head .btn,
#listToolbar .btn-block {
  box-sizing: border-box;
  min-height: 40px;
}

.push-settings-head .btn { min-width: 40px; }

/* The whole row is the label of its checkbox, so a tap anywhere on it toggles — the row is the target. */
.push-settings label { min-height: 40px; }

/* The pill's vertical padding moves into the input, so the pill keeps its height while the input — the element
 * that opens the picker — becomes 40 px tall. */
.date-field { padding: 0 6px; }

.date-field input[type="date"] { min-height: 40px; box-sizing: border-box; }

/* A hit slop instead of a bigger icon: padding grows the tappable area to about 40x41 px, the negative margin
 * cancels it again so the title line, and with it the row, stays exactly as tall as before. */
.remote-list-row .copy-title-btn {
  padding: 13px 11px;
  margin: -11px -6px -11px 0;
}

/* F-28/2026-09-21: a refusal toast carries the German sentence and, behind "Details", the untouched server line. report-base.css
 * makes every toast click-through (pointer-events: none); only a toast that has such a detail takes clicks, so the disclosure can
 * be opened — while it is open remote-app.js keeps the toast on screen. */
/* Only while it is showing: the faded-out toast keeps its class, and an invisible full-width box that eats taps at the bottom
 * of the screen would be worse than the problem this solves. */
.toast.has-detail.is-visible { pointer-events: auto; }

.toast-detail { margin-top: 2px; font-weight: 400; }

.toast-detail summary {
  display: block;
  padding: 6px 0;
  cursor: pointer;
  font-weight: 600;
  text-decoration: underline;
}

.toast-detail-text {
  display: block;
  padding-bottom: 4px;
  font-size: 12px;
  overflow-wrap: anywhere;
  user-select: text;
}

/* SCROLL-JUMP/2026-09-25: report-base.css sets `scroll-behavior: smooth` for the standalone report; in the app every scroll
   restore after a repaint must be instant, or the page visibly glides back after each decision (see jumpScrollTo()). */
html { scroll-behavior: auto; }

/* ATTACH/2026-09-25: screenshots attached to a code (remote-app.js, decorateAttachments()). The block sits under the comment field in
   the detail dialog; nothing here resizes on load (thumbnails have a fixed box) so an upload never moves the page. */
.attachments-block { margin-top: 12px; }

.attachments-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}

.attachments-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px;
  margin-top: 8px;
}

.attachments-grid:empty { display: none; }

.attachment-thumb {
  position: relative;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--accent-soft);
}

.attachment-open {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

.attachment-open img { display: block; width: 100%; height: 100%; object-fit: cover; }

.attachment-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 28px;
  min-height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  cursor: pointer;
  line-height: 1;
}

.attachments-hint { margin: 6px 0 0; font-size: 12px; color: var(--muted); }

.attachment-lightbox {
  max-width: 96vw;
  max-height: 96vh;
  padding: 8px;
  border: 0;
  border-radius: 10px;
  background: var(--bg, #fff);
}

.attachment-lightbox::backdrop { background: rgba(0, 0, 0, 0.8); }
.attachment-lightbox img { display: block; max-width: 92vw; max-height: 86vh; margin: 0 auto; cursor: zoom-out; }
.attachment-lightbox-bar { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding-bottom: 6px; font-size: 13px; }

/* LIST-QUIET/2026-09-25: the freshness line has a fixed height, so its text changing (every few seconds) never moves the list. */
.remote-list-fresh {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: .5rem;
  height: 2rem;
  /* SPACING-GUTTER/2026-10-03: the refresh button sat flush against the right viewport edge; same 16px gutter as the cards. */
  padding: 0 16px;
  box-sizing: border-box;
  font-size: .8rem;
  color: var(--muted);
}

/* F-01/2026-10-13: a code stored as fixed while a rest is open (decorateStateConflicts()) — the report still opens, the code says so. */
.state-conflict-warning {
  margin: 0 0 8px;
  padding: 6px 10px;
  border-left: 3px solid var(--warn, #c98a00);
  font-weight: 600;
}

/* FILTER-UNIFIED/2026-10-14: the start page's unified filter sits right under the level panel, full width like it. */
.list-filter-host { flex: 1 1 100%; width: 100%; }
.facet-hits {
  display: inline-block;
  margin-left: 6px;
  padding: 0 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: .78rem;
  font-weight: 700;
}

/* F-04/2026-10-13: "Ähnliche Findings" in the code dialog — one button per candidate, the whole row is the target. */
.similar-findings { margin-top: 14px; }
.similar-load-btn[hidden] { display: none; }
.similar-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.similar-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.similar-item:hover { border-color: var(--border-strong); }
.similar-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.similar-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.similar-report { color: var(--muted); font-size: .85rem; min-width: 0; overflow-wrap: anywhere; }
.similar-score { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }
.similar-summary { font-size: .9rem; overflow-wrap: anywhere; }
.similar-shared { color: var(--muted); font-size: .8rem; overflow-wrap: anywhere; }
.similar-error { color: var(--danger); }

/* REPORT-UX/2026-10-14 (user request): on mobile both badge rows (severity group, progress group) span the full card width. */
@media (max-width: 640px) {
  .remote-list-badges .progress-group { display: flex; flex: 1 1 100%; width: 100%; justify-content: space-between; box-sizing: border-box; }
}

/* REPORT-UX/2026-10-14 (user request): compact list bars — the severity bar never wraps, extra badges (loop marker) sit in their own line below. */
.remote-list-badges .progress-group { padding: 1px 8px; gap: 4px 6px; flex-wrap: nowrap; }
.remote-list-badges .progress-group .pg-part { font-size: 12px; }
.remote-list-badges-extra { gap: 4px; }
@media (max-width: 640px) {
  .remote-list-badges .progress-group { padding: 1px 6px; gap: 3px; }
  .remote-list-badges .progress-group .pg-part { font-size: 11.5px; }
}
