/* ─────────────────────────────────────────────────────────────────────────
   Urlaubskasse — "The metal card on the table"
   One consolidated token system (dark-first, considered light variant).
   Warm ink + champagne gold. Three rules carry the whole look:

   1. ONE TYPEFACE, TWO ROLES. The platform sans does everything: a display
      stack (--font-display) for the balance figure and the one title line
      per screen, the text stack for the interface. A self-hosted display
      serif was tried here and removed — at ledger sizes its stroke contrast
      read as thin and fussy, and legibility of the figure people read all
      day beats character. What carries the ledger feel instead is the
      figures themselves: money is ALWAYS tabular and lining, everywhere.
   2. THE HERO IS ALWAYS DARK. The balance plate keeps the dark palette in
      light mode too, so it reads as one physical object you own rather
      than as a themed box (and it is guaranteed 10:1+ contrast either way).
   3. RED MEANS TROUBLE, NOT "EXPENSE". Nine of ten rows in a holiday fund
      are expenses; painting them all red turned the ledger into an alarm
      ticker and drained the color of meaning. Expenses are ink, deposits
      are jade, and red is reserved for cancellations, deletions and a
      critical balance. The sign ("+" / "−") is what carries positive vs.
      negative — never color alone (see §Accessibility).

   Elevation has one rule: every raised surface gets a top light edge
   (inset 0 1px 0 white α), every recessed one an inner shadow.
   ────────────────────────────────────────────────────────────────────── */

:root {
  /* Surfaces — warm ink, not milk chocolate; five steps of "lift" so cards,
     insets, medallions and dialogs stack legibly on top of each other. */
  --bg: #0d0b08;
  --bg-alt: #151210;
  --card: #1d1812;
  --card-2: #262019;
  --card-3: #302820;
  --text: #f4eee2;
  --text-muted: #b2a491;
  /* Tertiary line (foreign-currency figures). Cleared against --card (5.4:1)
     and --bg — deliberately NOT used on --card-2, where it would fail AA. */
  --text-faint: #9a8c7a;
  --border: rgba(236, 222, 196, 0.09);
  --border-strong: rgba(236, 222, 196, 0.2);
  /* Hairline with a gold cast — medallions, pills, the plate's inner rule. */
  --border-gold: rgba(230, 182, 77, 0.32);

  /* The balance plate. These four never change between themes: the hero is
     the same dark metal object in light mode as in dark. */
  --plate: #15110c;
  --plate-2: #241c12;
  --plate-3: #0f0c09;
  --plate-text: #f7f1e5;

  /* Gold — the one accent color, spent deliberately, but not timidly: a
     vivid, saturated champagne rather than a dusty desaturated tan — it
     should read as metal catching light, not as beige. Contrast-checked at
     9.4:1 against --card (see contrast notes in the light block). The glow
     is deliberately weaker than it once was: at 0.5 it pooled under the
     primary button as an orange smudge instead of reading as sheen. */
  --gold: #e6b64d;
  --gold-light: #f6dc95;
  --gold-dark: #a8772a;
  --gold-tint: rgba(230, 182, 77, 0.14);
  --gold-glow: rgba(230, 182, 77, 0.32);
  --on-gold: #241a04;
  /* Gold text sitting ON --gold-tint needs its own step: plain --gold over
     that tint measures 4.05:1 (dark) / 4.07:1 (light) — just under AA. One
     step lighter here / darker in the light block clears it at 9:1+. */
  --gold-on-tint: #f6dc95;

  /* Gold-on-a-surface (buttons, active chips, the outbox banner) is a
     separate role from gold-as-text. In dark mode the two happen to share
     the same vivid value, but the light block below only ever redefines
     --gold/--gold-light/--gold-dark (text-safe, AA-checked against light
     surfaces) — never these. That keeps every gold *surface* just as vivid
     ("brushed metal") in light mode as in dark, while --on-gold-surface
     (#241a04 on the lightest stop) still clears AA: 5.12:1 at the darkest
     gradient stop, 9:1+ at the lightest. See the light block for the
     text-token rationale. */
  --gold-surface-light: #f6dc95;
  --gold-surface: #e6b64d;
  --gold-surface-dark: #b8822a;
  --on-gold-surface: #241a04;

  --primary: var(--gold);
  --primary-tint: var(--gold-tint);

  /* Status — always paired with a sign, an icon and/or text, never color
     alone (see .status-positive / .status-negative / .balance-warning /
     .balance-critical below and the aria-hidden icons that ride with them).
     Jade and terracotta instead of mint and salmon: both sit inside the
     gold/ink family instead of clashing with it into khaki. */
  --green: #8fdcb6;
  --green-tint: rgba(143, 220, 182, 0.12);
  --green-fill: #167a5c; /* darker — white text on top needs this, not --green (fails AA) */
  --red: #f3a48c;
  --red-tint: rgba(243, 164, 140, 0.12);
  --red-fill: #b8503e; /* darker — white text on top needs this, not --red (fails AA) */
  --warning: #eab04a;
  --warning-tint: rgba(234, 176, 74, 0.16);

  /* Type — the platform sans in both roles (rule 1 at the top). It costs
     nothing to load, never flashes, and already ships excellent tabular
     figures; the display stack differs only in picking the tighter
     display optical cut where the OS offers one. */
  --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --edge-lit: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --edge-sunk: inset 0 1px 2px rgba(0, 0, 0, 0.25);
  --shadow: var(--edge-lit), 0 20px 40px -24px rgba(0, 0, 0, 0.85), 0 2px 6px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 28px 80px -30px rgba(0, 0, 0, 0.92), 0 4px 16px rgba(0, 0, 0, 0.26);
  /* The hero plate: depth, a gold hairline and a contained glow in one. */
  --shadow-plate: 0 30px 60px -30px rgba(0, 0, 0, 0.95), 0 0 48px -20px var(--gold-glow);
  /* Stepped radii: outer shell → inner container → pill. */
  --radius: 20px;
  --radius-sm: 12px;

  /* Spacing — one rhythm used everywhere instead of ad-hoc inline margins.
     Slightly more generous than a typical utility app: a page handling real
     money reads calmer, more deliberate, with room to breathe. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* Focus */
  --focus-ring: var(--gold-light);
  --tap-min: 44px;
}

/* Considered light variant — same warm-neutral + gold language, inverted.
   Gold, green and red are all shifted several steps darker than their dark-
   mode counterparts (verified against WCAG AA, not eyeballed — see the
   contrast notes below each block); paired with white/near-white text they
   fall short otherwise. */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #f3ede2;
    --bg-alt: #e9e1d1;
    --card: #fffdf8;
    --card-2: #f7f2e8;
    --card-3: #efe8db;
    --text: #1e1912;
    --text-muted: #5b5145;
    /* card 4.9:1 — as above, never place this on --card-2 or darker. */
    --text-faint: #736858;
    --border: rgba(30, 25, 18, 0.1);
    --border-strong: rgba(30, 25, 18, 0.22);
    --border-gold: rgba(109, 77, 22, 0.28);

    /* A light page can't carry a *bright* gold as text (physics: AA needs
       ~4.5:1, a vivid gold light enough to glow is nowhere near dark
       enough) — so this block only redefines the text/icon/border role.
       --gold-surface* (defined once in :root, never overridden here) stays
       the vivid dark-mode gradient, so buttons/active chips/the outbox
       banner are a genuine gold surface in light mode too, not a second,
       darker gold. This --gold text token is a deep, warm amber-brown,
       darkened specifically against --bg-alt (the balance card's own
       background, previously untested and the one real AA failure):
       gold/bg-alt 5.94:1, gold/bg 6.67:1, gold/card 7.40:1,
       white/gold 7.51:1. --gold-light is for hover states on the plain
       --bg surface (gold-light/bg 4.95:1) — never on --gold-tint, which
       needs the darker --gold (6.7:1 on the tint; gold-light there is
       only 4.17:1 and fails AA). */
    --gold: #6d4d16;
    --gold-light: #8a6018;
    --gold-dark: #4a350f;
    --gold-tint: rgba(109, 77, 22, 0.12);
    --gold-glow: rgba(184, 130, 42, 0.18);
    --on-gold: #ffffff;
    --gold-on-tint: #4a350f;

    /* green/card 5.6:1, red/card 6.4:1 — both pass, and both are the deep
       end of the same jade/terracotta pair the dark block uses. */
    --green: #0e7550;
    --green-tint: rgba(14, 117, 80, 0.1);
    --green-fill: var(--green);
    --red: #a6382a;
    --red-tint: rgba(166, 56, 42, 0.1);
    --red-fill: var(--red);
    --warning: #8a5a12;
    --warning-tint: rgba(138, 90, 18, 0.12);

    --shadow-sm: 0 1px 2px rgba(33, 28, 20, 0.06);
      --edge-lit: inset 0 1px 0 rgba(255, 255, 255, 0.65);
    --edge-sunk: inset 0 1px 2px rgba(50, 38, 20, 0.06);
    --shadow: var(--edge-lit), 0 18px 40px -28px rgba(50, 38, 20, 0.35), 0 1px 3px rgba(50, 38, 20, 0.08);
    --shadow-lg: 0 24px 60px -24px rgba(33, 28, 20, 0.32), 0 6px 16px -6px rgba(33, 28, 20, 0.2);
    /* The plate keeps its own dark shadow language even on paper. */
    --shadow-plate: 0 26px 50px -28px rgba(40, 30, 14, 0.6), 0 2px 6px rgba(40, 30, 14, 0.14);
  }
}

/* Money is always tabular and lining — columns of euros must line up to the
   cent, and that alignment is what actually makes a list of amounts read as
   a ledger. */
.amount, .tx-amount, .balance-badge, .overview-total, .tx-split-row strong, .money {
  font-feature-settings: "tnum" 1, "lnum" 1;
  font-variant-numeric: tabular-nums lining-nums;
}
/* The two display figures — balance plate and the admin's total — take the
   display stack; everything else inherits the text stack. */
.amount, .overview-total {
  font-family: var(--font-display);
}

/* ── Reset / base ─────────────────────────────────────────────────────── */

* { box-sizing: border-box; }
[hidden] { display: none !important; }
h1, h2, h3, p, figure { margin: 0; }

@media (prefers-reduced-motion: no-preference) {
  @keyframes riseIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
  @keyframes popIn { from { opacity: 0; transform: scale(0.96) translateY(6px); } to { opacity: 1; transform: scale(1) translateY(0); } }
  @keyframes sheetUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
  @keyframes rippleAnim { to { transform: scale(1); opacity: 0; } }
  @keyframes sheen { from { background-position: -140% 0; } to { background-position: 240% 0; } }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

html { background: var(--bg); }

body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom);
  line-height: 1.45;
  background-color: var(--bg);
  /* A hint of light in the top-right corner, not a puddle: at the old
     680px/0.16 the gold radial mixed with the page brown into a muddy
     olive across the whole header. */
  background-image:
    radial-gradient(420px 280px at 88% -12%, rgba(230, 182, 77, 0.07), transparent 62%),
    radial-gradient(120% 80% at 50% 0%, transparent 58%, rgba(0, 0, 0, 0.35) 100%);
  background-repeat: no-repeat;
  background-attachment: fixed;
  position: relative;
}
/* Material, not moiré. The old 3px/7px scanline grid read as a rendering
   artefact at any zoom; fine turbulence at 4.5% reads as leather/paper and
   is what stops the large flat surfaces from looking like plastic. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.045;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
@media (prefers-color-scheme: light) {
  body {
    background-image:
      radial-gradient(420px 280px at 88% -12%, rgba(184, 130, 42, 0.09), transparent 62%),
      radial-gradient(120% 80% at 50% 0%, transparent 62%, rgba(60, 45, 25, 0.06) 100%);
  }
  body::before { opacity: 0.06; mix-blend-mode: multiply; }
}

::selection { background: var(--gold); color: var(--on-gold); }

/* ── Accessibility utilities ──────────────────────────────────────────── */

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* One consistent ring, everywhere. Buttons/links/chips/custom toggles get an
   outline (they're irregular shapes); text inputs get a border-glow instead
   since they already read as "a box" — different shape, same gold language,
   same guarantee: nothing focuses invisibly. */
:focus { outline: none; }
:focus-visible {
  outline: 2.5px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: 6px;
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--gold-tint), inset 0 0 0 1px var(--gold);
  border-radius: var(--radius-sm);
}

/* Real, uniform 44×44 minimum touch target on every tappable control —
   applied once here instead of patched in per-component. */
.icon-btn, .tx-action, .hint-toggle-btn, .hint-dismiss, .link-btn,
.amount-type-btn, .user-admin-action, button.btn.small {
  min-width: var(--tap-min);
  min-height: var(--tap-min);
}
.md-switch, .back-link, .tx-header { min-height: var(--tap-min); }

.icon {
  display: inline-block;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.85;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -3px;
}
.icon[aria-hidden] { pointer-events: none; }

/* ── App shell ────────────────────────────────────────────────────────── */

.app {
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
  /* 16px gutters, not 20 + 22 of card padding on top: on a 390px phone every
     pixel of chrome here comes straight out of the ledger's text column. */
  padding: 26px 16px 48px;
  flex: 1;
  animation: riseIn 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (min-width: 641px) {
  .app { padding: 30px 20px 56px; }
}

header.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: 26px;
  min-height: 38px;
  padding: 0;
}
/* 500, not 700: a heavy Palatino-ish headline was the single biggest reason
   the screen read "newspaper" instead of "private bank". */
header.topbar h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.8rem;
  line-height: 1.05;
  letter-spacing: -0.02em;
}

/* Every section label is preceded by a short gold rule — the recurring
   ornament that ties the screens together, and the reason the old faint
   gold hairline across the top of each card is gone: it tried to say the
   same thing where nobody looked. */
.eyebrow, .section-label, .balance-card .label {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--gold);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  line-height: 1.3;
  text-transform: uppercase;
}
.eyebrow::before, .section-label::before, .balance-card .label::before {
  content: "";
  flex-shrink: 0;
  width: 18px;
  height: 1px;
  background: currentColor;
  opacity: 0.75;
}
.center-screen .eyebrow { justify-content: center; }
.section-label.with-gap { margin-bottom: var(--space-3); }
.ledger-card .section-label { margin-bottom: 14px; }

/* ── Buttons ──────────────────────────────────────────────────────────── */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: none;
  border: 1px solid transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  padding: 8px 12px;
  border-radius: 8px;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  position: relative;
  overflow: hidden;
}
.icon-btn:hover { background: var(--gold-tint); color: var(--gold); border-color: var(--gold-tint); }
.icon-btn:active { transform: scale(0.97); }
/* Secondary by rank, so the roster toolbar isn't three golds in one line
   (eyebrow, this link, the primary button) under a gold tab and a gold
   plate. Gold on hover keeps it identifiable as the accent family. */
.toolbar-action { color: var(--text-muted); white-space: nowrap; }
.toolbar-action:hover { color: var(--gold); }

button.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: none;
  border-radius: var(--radius-sm);
  min-height: 52px;
  padding: 13px 20px;
  font: inherit;
  font-size: 1rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  cursor: pointer;
  /* A dimensional gold, like brushed metal catching a light source, not a
     flat swatch — three stops so it reads as curved/lit rather than a
     two-tone stripe. */
  background: linear-gradient(155deg, var(--gold-surface-light) 0%, var(--gold-surface) 55%, var(--gold-surface-dark) 100%);
  color: var(--on-gold-surface);
  /* Tighter glow than before: at -12px it pooled under the button as an
     orange smudge on the page instead of reading as the button's own sheen. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), 0 8px 20px -14px var(--gold-glow), 0 6px 14px -12px rgba(0, 0, 0, 0.7);
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.15s ease;
  position: relative;
  overflow: hidden;
}
button.btn:hover { filter: brightness(1.08); transform: translateY(-1px); }
button.btn:active { transform: scale(0.98); }
button.btn.secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border-strong);
  box-shadow: none;
}
button.btn.secondary:hover { background: var(--card-2); border-color: var(--gold-dark); }
button.btn.danger { background: var(--red-fill); color: #fff; }
button.btn.danger:hover { background: var(--red); filter: none; }
button.btn.small { padding: 9px 14px; font-size: 0.85rem; border-radius: 7px; }
/* Nicht das Gold ausbleichen (halbtransparent wirkte wie ein Fehler), sondern
   die Fläche neutral machen: der Button ist dann sichtbar noch nicht dran. */
button.btn:disabled {
  /* Heller als die Eingabefelder (--bg-alt), sonst wird der inaktive Button
     mit einem ausfüllbaren Feld verwechselt — und nicht halbtransparentes
     Gold, das wie ein Darstellungsfehler aussieht. */
  background: var(--card-2);
  color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--border);
  cursor: not-allowed;
  transform: none;
  filter: none;
}
.full-width { width: 100%; }

/* "+ User" / "+ Neu" / "+ Buchung" — icon + label pairing, not a bare glyph */
.btn-plus-icon { margin-right: -2px; }

/* ── Form fields ──────────────────────────────────────────────────────── */

form.stack { display: flex; flex-direction: column; gap: 12px; }
label { display: block; margin-bottom: 6px; font-size: 0.78rem; letter-spacing: 0.04em; color: var(--text-muted); font-weight: 600; }

input[type="text"], input[type="password"], input[type="number"], input[type="date"], select {
  width: 100%;
  min-height: 48px;
  padding: 13px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-alt);
  /* Recessed half of the elevation rule — a field should read as cut into
     the surface, not painted onto it. */
  box-shadow: var(--edge-sunk);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
  font-weight: 500;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input::placeholder { color: var(--text-muted); opacity: 0.85; }

/* Money fields keep the custom field chrome — the native browser spinner
   is an unstyled white box that breaks it on every platform that draws one. */
input[type="number"] { -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.error { color: var(--red); font-size: 0.88rem; margin-top: 2px; font-weight: 600; }
.notice-success { color: var(--green) !important; }
.hint { color: var(--text-muted); font-size: 0.8rem; margin-top: 10px; text-align: center; line-height: 1.4; }

/* ── Cards ────────────────────────────────────────────────────────────── */

.card {
  position: relative;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* --shadow carries the top light edge of the elevation rule. */
  box-shadow: var(--shadow);
  padding: 20px 14px;
  margin-bottom: var(--space-4);
}
@media (min-width: 641px) {
  .card { padding: 24px 22px; }
}

/* ── Balance hero — the signature moment, and the one element that does NOT
   follow the theme: a dark metal plate in light mode too (rule 2 at the
   top). In dark mode it is the deepest surface on screen, in light mode the
   only dark one — either way the eye lands there first, and the amount sits
   at 10:1+ contrast in both.

   The old treatment failed on two counts worth keeping named: a solid 1px
   gold border read as an alert box rather than as metal, and a 186px
   min-height left the card half empty — density is what reads as expensive,
   emptiness reads as unfinished. The border is now a gradient hairline that
   is bright at the top-left and dark at the bottom-right, i.e. an edge
   catching a light source; the height is whatever the content needs. ── */
.balance-card {
  /* The plate carries its own palette. Because it stays dark in light mode,
     every token its children resolve — label gold, muted hints, the status
     colours — has to be the dark-mode value, or e.g. the light theme's deep
     brown gold (#6d4d16) lands on near-black at ~2.5:1. Rebinding the tokens
     here once makes that structural instead of a list of per-element
     overrides someone has to remember to extend. */
  --text: var(--plate-text);
  --text-muted: #b2a491;
  --text-faint: #9a8c7a;
  --gold: #e6b64d;
  --gold-light: #f6dc95;
  --gold-tint: rgba(230, 182, 77, 0.14);
  --gold-on-tint: #f6dc95;
  --border: rgba(236, 222, 196, 0.09);
  --border-strong: rgba(236, 222, 196, 0.2);
  --border-gold: rgba(230, 182, 77, 0.32);
  --green: #8fdcb6;
  --red: #f3a48c;
  --warning: #eab04a;

  display: flex;
  flex-direction: column;
  text-align: left;
  padding: 24px 22px 20px;
  border-radius: 22px;
  border: 1px solid transparent;
  color: var(--plate-text);
  background:
    /* padding-box fill … */
    linear-gradient(155deg, var(--plate-2) 0%, var(--plate) 48%, var(--plate-3) 100%) padding-box,
    /* … and the lit edge as a border-box gradient underneath it. */
    linear-gradient(135deg, rgba(246, 220, 149, 0.85) 0%, rgba(230, 182, 77, 0.35) 35%, rgba(168, 119, 42, 0.25) 70%, rgba(246, 220, 149, 0.6) 100%) border-box;
  box-shadow: var(--shadow-plate);
}
/* Brushed-metal grain + the corner light, layered over the plate fill. */
.balance-card::before {
  content: "";
  display: block;
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    /* Sampled at 0.22 the corner light composited to (69,56,31) — olive, not
       gold. A lighter, weaker stop reads as a reflection instead of a stain. */
    radial-gradient(70% 90% at 100% 0%, rgba(246, 220, 149, 0.14), transparent 60%),
    repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.025) 0 1px, transparent 1px 3px);
}
/* Inner rule — a second, quieter line inside the edge, the way a struck
   plate has a bevel. */
.balance-card::after {
  content: "";
  position: absolute;
  inset: 7px;
  border: 1px solid rgba(230, 182, 77, 0.12);
  border-radius: 15px;
  pointer-events: none;
}
/* One pass of light travelling diagonally across the whole plate on load —
   a single deliberate flourish, not a loop. */
.balance-card .balance-sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 40%, rgba(246, 220, 149, 0.1) 50%, transparent 60%);
  background-size: 300% 100%;
  animation: sheen 1.6s ease-out 0.35s 1;
}
/* The brand chevrons as a watermark, cropped by the plate's own overflow —
   fills the lower right without inventing information. */
.balance-card .balance-watermark {
  position: absolute;
  right: -10px;
  bottom: -14px;
  width: 132px;
  height: 92px;
  opacity: 0.11;
  pointer-events: none;
}
.balance-card .balance-watermark span,
.balance-card .balance-watermark i,
.balance-card .balance-watermark b {
  position: absolute;
  display: block;
  border: 2px solid var(--gold-light);
  transform: rotate(-30deg) skewX(19deg);
}
.balance-card .balance-watermark span { left: 4px; bottom: 8px; width: 88px; height: 58px; border-width: 2px 0 0 2px; }
.balance-card .balance-watermark i { left: 30px; bottom: 19px; width: 77px; height: 50px; border-width: 2px 0 0 2px; }
.balance-card .balance-watermark b { left: 56px; bottom: 30px; width: 66px; height: 42px; border-width: 2px 0 0 2px; }

.balance-card .label { position: relative; z-index: 1; margin: 0 0 14px; color: var(--gold); }
.balance-card .amount {
  position: relative;
  z-index: 1;
  font-size: clamp(2.6rem, 13.5vw, 4.4rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  text-rendering: optimizeLegibility;
}
/* .positive / .negative resolve against the plate's own rebound tokens
   above, so no per-state colours are needed here. */
.balance-card .amount.zero { color: var(--plate-text); }

/* Positive/negative/zero are never color-only: every amount is rendered by
   formatSignedAmount() (categories.js), which prefixes a literal "+" on
   credits — the sign itself is the non-color signal, on top of color. */
.positive, .status-positive { color: var(--green); }
.negative, .status-negative { color: var(--red); }
.zero { color: var(--text); }

/* Cancelled/stornoed transactions: struck through, dimmed, still legible —
   never simply removed from the list (auditability). */
.tx-cancelled { opacity: 0.6; }
.tx-cancelled .tx-reason, .tx-cancelled .tx-amount { text-decoration: line-through; }
.badge-cancelled {
  background: var(--card-2);
  color: var(--text-muted);
  padding: 1px 6px;
  border-radius: 6px;
  font-size: 0.75rem;
  font-weight: 700;
  margin-left: 6px;
  text-decoration: none !important;
  display: inline-block;
}
.storno-note {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--red-tint);
  color: var(--red);
  padding: 6px 10px;
  border-radius: 8px;
  margin-bottom: 8px;
  font-weight: 600;
  font-size: 0.85rem;
}

/* Warning/critical hero state (requirement 1, promoted): the card's own
   border switches off gold, and .balance-status below carries an icon +
   full status sentence — not just the numeral's color and sign. */
.card-status-warning { position: relative; border-color: var(--warning) !important; }
.card-status-warning.balance-card::after { border-color: var(--warning-tint) !important; }
.card-status-critical { position: relative; border-color: var(--red) !important; }
.card-status-critical.balance-card::after { border-color: var(--red-tint) !important; }

.balance-status {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  font-size: 0.85rem;
  font-weight: 700;
}
.card-status-warning .balance-status { color: var(--warning); }
.card-status-critical .balance-status { color: var(--red); }
.balance-status .icon { width: 16px; height: 16px; }

/* The admin's "Kasse gesamt" is the same plate object as the participant
   hero — .overview-card carries .balance-card in the markup. */
.overview-card { padding: 26px 24px 24px; }
.overview-card .overview-total {
  position: relative;
  z-index: 1;
  margin-top: 10px;
  font-size: clamp(2.3rem, 10vw, 3.1rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
}
.overview-actions { position: relative; z-index: 1; display: flex; margin-top: 20px; }

/* ── Transaction ledger ───────────────────────────────────────────────── */

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

/* .tx-item is shared by two genuinely different layouts: the participant
   accordion (#tx-list — a header button stacked above a collapsible body,
   column is correct) and the admin flat row (#detail-tx-list and
   #all-tx-list, the outbox dialog's .outbox-item — icon/info/amount/edit/
   delete as five siblings, which needs a row). Column is the base case; the
   flat-row contexts opt back into row layout explicitly instead of
   inheriting a stack. */
.tx-item { display: flex; flex-direction: column; padding: 16px 4px; margin: 0; border-bottom: 1px solid var(--border); }
.tx-item:last-child { border-bottom: none; }
/* In the participant ledger the rule starts at the text edge (medallion 44
   + gap 14), leaving the icon column running unbroken down the page the way
   a bank statement does. The admin's flat rows keep the full-width rule. */
#tx-list .tx-item { border-bottom-color: transparent; position: relative; }
#tx-list .tx-item::after {
  content: "";
  position: absolute;
  left: 55px; right: 0; bottom: 0;
  height: 1px;
  background: var(--border);
}
#tx-list .tx-item:last-child::after { display: none; }
#detail-tx-list .tx-item, #all-tx-list .tx-item, .tx-item.outbox-item {
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
}

/* Admin detail rows carry up to three 44px tap-target action buttons
   alongside the amount — too much for icon+reason+meta to share a single
   line on a narrow phone. .tx-main (icon+info) and .tx-side (amount+
   actions) let the row wrap onto two lines at narrow widths (see the
   max-width: 500px block) without losing the single-line desktop layout. */
#detail-tx-list .tx-main, #all-tx-list .tx-main { display: flex; align-items: center; gap: 13px; flex: 1; min-width: 0; }
#detail-tx-list .tx-side, #all-tx-list .tx-side { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
.tx-actions { display: flex; align-items: center; gap: 2px; }

.tx-header {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  border: none;
  background: none;
  /* Negative margin + matching padding so the hover surface extends past
     the text to the card's inner edge instead of stopping at the glyphs. */
  padding: 4px 10px;
  margin: -4px -10px;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: 14px;
  transition: background 0.15s ease;
}
.tx-header:hover { background: var(--card-2); }
.tx-header:active { background: var(--card-3); }

/* Participant ledger row geometry.
   In a single flex band [medallion | text | amount | chevron] the fixed
   furniture eats two thirds of a 390px phone (measured: 44+82+26 plus three
   14px gaps = 194 of 292px), leaving the text 98px — which is why the reason
   wrapped onto three lines and "Aktivität · 26.08.26" broke in half.
   A grid fixes it without touching the markup or the reading order: the
   amount shares the first line with the reason (statement convention), and
   every following line — category/date, the foreign-currency figure, the
   split marker — spans the full width beside the medallion instead of being
   squeezed into a narrow column. .tx-info becomes display: contents so its
   children join the same grid; it is a plain wrapper div, so nothing
   semantic is lost. Scoped to #tx-list: the admin's flat rows carry action
   buttons and keep their own flex layout. */
#tx-list .tx-header {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  column-gap: 9px;
  row-gap: 4px;
  align-items: center;
}
/* A ceiling, not a crop. Rows should scan as a column, so there is a limit —
   but at three lines it is only ever reached by genuinely long text, whereas
   two lines silently ate the end of ordinary reasons ("Bargeld-Abhebung
   Geldautomat" became "Bargeld-Abhebung…"). Losing what the user typed is
   the worse failure of the two. */
#tx-list .tx-reason {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  /* German compounds ("Flughafentransfer", "Bargeld-Abhebung Geldautomat")
     are routinely longer than the column. Without these the word neither
     wraps nor hyphenates and is simply sliced off mid-glyph; with them the
     page's lang="de" gets a proper hyphenation break, and only genuinely
     over-long text ever reaches the ellipsis. */
  -webkit-hyphens: auto;
  hyphens: auto;
  overflow-wrap: break-word;
}
#tx-list .tx-info { display: contents; }
/* Das Medaillon steht bewusst in Zeile 1, auf der Höhe des Grundes — nicht über
   den ganzen Block zentriert: bei zwei- und dreizeiligen Gründen wandert es sonst
   neben die Meta-Zeile und die Icon-Spalte franst über die Liste hinweg aus.
   (Ein früheres `grid-row: 1 / -1` war hier ohnehin wirkungslos: ohne explizit
   definierte Zeilen zeigt -1 auf Linie 1.) */
#tx-list .tx-icon { grid-column: 1; grid-row: 1; align-self: center; }
#tx-list .tx-reason { grid-column: 2; grid-row: 1; }
#tx-list .tx-amount { grid-column: 3; grid-row: 1; }
#tx-list .tx-chevron { grid-column: 4; grid-row: 1; }
#tx-list .tx-meta, #tx-list .tx-meta-split { grid-column: 2 / -1; }

.tx-accordion-body {
  margin-top: 12px;
  padding: 14px 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--edge-sunk);
  font-size: 0.9375rem;
  line-height: 1.45;
}
@media (prefers-color-scheme: light) {
  .tx-accordion-body { background: var(--card-2); }
}
@media (prefers-reduced-motion: no-preference) {
  .tx-accordion-body { animation: riseIn 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
}
.tx-accordion-row { display: flex; justify-content: space-between; gap: var(--space-3); }
.tx-accordion-row + .tx-accordion-row { margin-top: 6px; }
.tx-accordion-row span:first-child { color: var(--text-muted); font-weight: 500; }
.tx-accordion-row strong { font-weight: 600; }
.tx-split-detail {
  background: var(--bg-alt);
  padding: 12px 14px;
  border-radius: 10px;
  border: 1px solid var(--border-gold);
  margin-bottom: 10px;
}
.tx-split-detail-title {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 6px;
}
.tx-split-detail-partners { color: var(--text-muted); font-size: 0.8125rem; margin-top: 4px; }
/* Per-person breakdown inside the split accordion: name left, amount right,
   tabular figures so the euro columns line up, hairlines between people so
   it reads as a statement rather than as a stack of lines. */
.tx-split-breakdown { margin-top: 10px; display: flex; flex-direction: column; }
.tx-split-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: 0.9375rem;
  padding: 7px 0;
  border-top: 1px solid var(--border);
}
.tx-split-row strong { font-size: 0.9375rem; font-weight: 650; }
.tx-split-row-muted { color: var(--text-muted); }
.tx-split-row-muted strong { font-weight: 500; }

/* An embossed medallion, not a disabled button: a lit top edge, a dark
   bottom one and a gold hairline lift it off the card instead of letting it
   sink into the same tone as the search field next to it. */
.tx-icon {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: linear-gradient(160deg, var(--card-3), var(--card-2));
  border: 1px solid var(--border-gold);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), inset 0 -1px 0 rgba(0, 0, 0, 0.25);
  color: var(--gold);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  flex-shrink: 0;
}
@media (prefers-color-scheme: light) {
  .tx-icon {
    background: linear-gradient(160deg, #fffdf8, var(--card-3));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), inset 0 -1px 0 rgba(50, 38, 20, 0.06);
  }
}

.category-icon { display: block; width: 1.25rem; height: 1.25rem; }
.tx-icon .category-icon { width: 1.4rem; height: 1.4rem; }
.category-name { display: flex; align-items: center; gap: 8px; }

.category-icon-select { display: flex; align-items: center; gap: 10px; }
.category-icon-select select { flex: 1; min-width: 0; }
.category-icon-preview {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-alt);
  flex-shrink: 0;
}
.category-icon-preview .category-icon { width: 1.4rem; height: 1.4rem; }

.tx-info { flex: 1; min-width: 0; }
/* The participant ledger is read at arm's length on a phone — 17/14/19 is
   the scale that makes it comfortable, up from 15/12.5/14.7. */
.tx-reason { font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.012em; line-height: 1.28; }
.tx-meta { color: var(--text-muted); font-size: 0.875rem; font-weight: 500; line-height: 1.35; margin-top: 4px; }
/* Third line (the exact foreign-currency figure) steps back once more. The
   :not() matters: the split marker is also a .tx-meta and would otherwise
   inherit the faint colour from here at higher specificity than its own
   rule, landing at 4.05:1 on its tinted background (measured). */
.tx-meta + .tx-meta:not(.tx-meta-split) { font-size: 0.8125rem; color: var(--text-faint); }
/* The split marker is a pill, not bold gold body copy: as running text it
   wrapped onto two lines and shouted louder than the amount itself. */
.tx-meta-split {
  display: inline-flex;
  /* Shrink-to-fit inside its full-width grid track, so the marker stays a
     tag around its own text rather than a bar across the row. */
  justify-self: start;
  align-items: flex-start;
  gap: 5px;
  margin-top: 6px;
  padding: 4px 10px 4px 8px;
  /* A soft rect rather than a full pill: the label ("Aufteilung · gleich
     geteilt · Rechnung 100,00 €") is long enough to need two lines in the
     text column of a narrow phone, and a 999px radius on a two-line block
     reads as a mistake. Truncating it instead would hide the bill total,
     which is the whole point of the marker. */
  border-radius: 10px;
  background: var(--gold-tint);
  border: 1px solid var(--border-gold);
  color: var(--gold-on-tint);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.35;
  max-width: 100%;
}
.tx-meta .icon, .tx-split-detail-title .icon { width: 12px; height: 12px; vertical-align: -1.5px; }
.tx-meta-split .icon { width: 12px; height: 12px; flex-shrink: 0; margin-top: 2px; }
/* Expenses are ink, deposits are jade (rule 3 at the top): the sign, not the
   colour, is what separates them — .positive/.negative still apply, but only
   .positive now repaints, so a page of ordinary spending reads calm. */
/* No reserved min-width: the amount is nowrap, so it already claims exactly
   what it needs — a fixed column here stole ~90px from the reason and forced
   three-line wraps on a 390px phone, which is the opposite of the larger,
   calmer ledger this scale exists for. */
.tx-amount {
  /* Still well above the 0.92rem this replaced — the ledger figure was the
     point of the larger scale. In the sans it needs a touch more weight and
     no negative tracking to read as solidly as the serif did. */
  font-size: 1.0625rem;
  font-weight: 650;
  line-height: 1;
  white-space: nowrap;
  text-align: right;
  flex-shrink: 0;
}
#tx-list .tx-amount.negative { color: var(--text); }

/* Bare glyph at rest, a gold disc once open: the disc is worth ~14px of
   the reason's column, and it only has to read as a control in the state
   where it actually means something. */
.tx-chevron {
  color: var(--text-muted);
  width: 20px;
  height: 20px;
  padding: 3px;
  flex-shrink: 0;
  border-radius: 999px;
  background: transparent;
  border: 1px solid transparent;
  box-sizing: border-box;
  transition: transform 0.2s ease, background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.tx-header[aria-expanded="true"] .tx-chevron {
  transform: rotate(180deg);
  color: var(--gold);
  background: var(--gold-tint);
  border-color: var(--border-gold);
}

.tx-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 8px;
  transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
  position: relative;
  overflow: hidden;
}
.tx-action:hover { background: var(--bg-alt); color: var(--text); }
.tx-action:active { transform: scale(0.92); }
.tx-action-delete:hover { background: var(--red-tint); color: var(--red); }

/* ── User roster (admin) ──────────────────────────────────────────────── */

.user-row { display: flex; align-items: center; padding: 15px 0; border-bottom: 1px solid var(--border); position: relative; }
.user-row:last-child { border-bottom: none; }
/* A permanent, quiet gold tick per member that brightens on hover — the
   same ornament language as the section labels' rule. */
/* Absolutely positioned, not a flex item: on a phone .user-row wraps its
   action buttons onto a second line, and as an in-flow item the tick was
   dragged along and — sitting flush against the name's first letter at 30%
   — read as a text cursor rather than as an ornament. */
.user-row::before {
  content: "";
  position: absolute;
  left: -8px;
  top: 18px;
  width: 2px;
  height: 24px;
  border-radius: 1px;
  background: var(--gold);
  opacity: 0.5;
  transition: opacity 0.16s ease;
}
.user-row:hover::before { opacity: 1; }

/* Real-time search + category filter chips above the transaction list. */
.search-filter-box {
  margin-bottom: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The magnifier is a bundled SVG in the field's own background, not a 🔍 in
   the placeholder string: that emoji renders as a different object on every
   platform (and in colour), which is exactly the kind of detail that makes
   an otherwise considered screen look cheap. */
.search-filter-box input[type="text"] {
  min-height: 48px;
  padding: 10px 14px 10px 42px;
  border-radius: 12px;
  border-color: var(--border-strong);
  box-shadow: var(--edge-sunk);
  /* Not 0.9rem: iOS Safari auto-zooms the whole page in whenever a focused
     form control has font-size < 16px, and there's no way to opt out without
     disabling pinch-zoom app-wide (an a11y no-go). 16px is Apple's fixed
     threshold — every focusable input/select here must stay at or above it,
     so a slightly smaller search field isn't worth the zoom jump. */
  font-size: 16px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b2a491' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 14px 50%;
  background-size: 18px 18px;
}
@media (prefers-color-scheme: light) {
  .search-filter-box input[type="text"] {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b5145' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
  }
}

/* Wraps instead of scrolling sideways — a growing category list (admins can
   add their own) must never need a horizontal swipe to find one. */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* Transparent at rest: two rows of filled pills read as a carpet of
     buttons and compete with the ledger below them. */
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  padding: 5px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.15s ease;
  flex-shrink: 0;
  /* A row of scannable filter toggles, not a primary action — the full
     44px --tap-min used elsewhere here would nearly triple each chip's
     height for no visual content, forcing a growing category list into
     far more wrap rows than it needs. 32px still comfortably exceeds the
     WCAG 24px minimum target size. */
  min-height: 32px;
}
.filter-chip .icon, .filter-chip .category-icon { width: 14px; height: 14px; }

.filter-chip:hover { border-color: var(--gold); color: var(--text); }
/* Tint plus a full-gold border, not a solid gold fill: "Alle" is active by
   default, so a solid pill would put the brightest object on the page under
   the hero on every screen, permanently, competing with the plate. */
.filter-chip.active {
  background: var(--gold-tint);
  border-color: var(--gold);
  color: var(--gold-on-tint);
}

.user-open-btn {
  flex: 1;
  min-width: 0;
  min-height: var(--tap-min);
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: 6px;
}
.user-name { font-weight: 750; letter-spacing: -0.01em; }
.user-id { color: var(--text-muted); font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; margin-top: 3px; font-variant-numeric: tabular-nums; }

.balance-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 1.02rem; font-weight: 650; }
.balance-badge .icon { width: 15px; height: 15px; }
.balance-badge.balance-warning { color: var(--warning); background: var(--warning-tint); border: 1px solid rgba(234, 176, 74, 0.35); padding: 4px 9px; border-radius: 8px; }
.balance-badge.balance-critical { color: var(--red); background: var(--red-tint); border: 1px solid rgba(240, 152, 127, 0.35); padding: 4px 9px; border-radius: 8px; }

.user-actions { display: flex; align-items: center; gap: 2px; margin-left: 8px; }
.user-admin-action { font-size: 0.8rem; letter-spacing: -0.01em; }
.danger-action:hover { color: var(--red); background: var(--red-tint); border-color: rgba(240, 152, 127, 0.28); }

/* Ausgabe/Einnahme toggle */
/* Same 15px rhythm .dialog-body uses between its own direct children —
   #single-amount-block groups the toggle + amount field as one visual unit,
   so it needs its own matching gap instead of relying on the parent's. */
#single-amount-block { display: flex; flex-direction: column; gap: 15px; }
/* Segmented Control: vertiefte Bahn, erhabene Pille für die Auswahl — dieselbe
   Elevation-Regel wie überall (Lichtkante oben für Erhabenes, Innenschatten
   für Vertieftes). Die Auswahl trägt damit eine Fläche, keine Linie. */
.amount-type-toggle {
  display: flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-alt);
  box-shadow: var(--edge-sunk);
}
.amount-type-btn {
  /* Breite folgt dem Label, nicht umgekehrt: "flex: 1" teilte die Bahn in
     starre gleiche Anteile, und in die passte "Einnahmen" auf schmalen
     Handys schlicht nicht — bei 320px 82px Text auf 75px Anteil, gemessen,
     also ein abgeschnittenes Wort. Mit auto-Basis bekommt jedes Segment
     erst seinen Text und teilt sich den Rest der Bahn; die Segmente sind
     dann um die Labeldifferenz unterschiedlich breit, was hier der
     ehrlichere Kompromiss ist als eine Schrift, die für den engsten Fall
     immer weiter schrumpft.
     min-width: 0 samt overflow/text-overflow bleibt als Netz: das Wort
     ellipsiert im Notfall, statt wie früher unsichtbar ins Nachbarsegment
     zu bluten (in Chromium bei 375px bestätigt, kein reiner Safari-Bug). */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  border: none;
  background: none;
  border-radius: 9px;
  color: var(--text-muted);
  font: inherit;
  font-weight: 650;
  font-size: 0.9rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
/* Ein Aussehen für die Auswahl, drei Bedeutungen über die Textfarbe. Vorher
   trug jede Variante eine eigene getönte Fläche plus farbigen Unterstrich —
   drei Signale für dieselbe Sache, und bei "Ausgabe" ausgerechnet Rot, obwohl
   Rot in dieser App Storno und kritischen Stand bedeutet, nicht Ausgabe. */
.amount-type-btn.active {
  /* Im Dark Mode hebt --card sich zu wenig von der Bahn ab, im Light Mode ist
     das Karten-Weiß dagegen genau die knackige Pille — daher eine Stufe
     höher im Dunklen (siehe Light-Override darunter). */
  background: var(--card-3);
  color: var(--text);
  box-shadow: var(--edge-lit), 0 1px 3px rgba(0, 0, 0, 0.18);
}
@media (prefers-color-scheme: light) {
  .amount-type-btn.active { background: var(--card); }
}
/* Einnahme trägt Jade — dieselbe Zuordnung wie in der Buchungsliste. Die
   Klassen-Variante deckt den Filter über der Liste ab, die Wert-Variante den
   Umschalter im Buchungsdialog (renderSeg vergibt keine Klassen). */
.amount-type-btn.income.active,
.amount-type-btn.active[data-seg-value="income"] { color: var(--green); }
.amount-type-btn.all.active { color: var(--gold); }

/* ── Transaction list: advanced filters (Zeitraum, Buchungsart) ─────────── */

.filter-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-top: var(--space-2); }
.filter-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--on-gold);
  font-size: 0.68rem;
  font-weight: 800;
}
.tx-filter-box {
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Stacked by default (mobile-first — see CLAUDE.md), not a 2-column grid
   relying on min-width: 0 to shrink the cells: that works in Chromium, but
   real Mobile Safari has a known bug where a native form control (here
   <input type="date">) ignores min-width: 0 in a grid/flex item and keeps
   its own intrinsic content width regardless — confirmed live on a real
   iPhone after this passed every Chromium-based Playwright check (same root
   cause as the QR-link-row fix). Two date inputs side by side only get
   enough room to never need to shrink at the same breakpoint
   .split-reason-date-row already uses for its own two fields. */
.tx-date-range-row { display: grid; grid-template-columns: 1fr; gap: 8px; }
.tx-date-range-row > div { min-width: 0; }
@media (min-width: 641px) {
  .tx-date-range-row { grid-template-columns: 1fr 1fr; }
}

/* Material switch (the "Teilen" toggle) */
.md-switch { display: flex; align-items: center; gap: 12px; cursor: pointer; user-select: none; }
.md-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.md-switch-track {
  flex-shrink: 0;
  width: 44px;
  height: 26px;
  border-radius: 999px;
  background: var(--bg-alt);
  border: 1px solid var(--border-strong);
  position: relative;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.md-switch-thumb {
  position: absolute;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--text-muted);
  transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), background 0.15s ease;
}
.md-switch input:checked ~ .md-switch-track { background: var(--gold-tint); border-color: var(--gold); }
.md-switch input:checked ~ .md-switch-track .md-switch-thumb { transform: translateX(18px); background: var(--gold); }
.md-switch input:focus-visible ~ .md-switch-track { outline: 2.5px solid var(--focus-ring); outline-offset: 3px; }
.md-switch-label { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: 0.92rem; color: var(--text); }

/* ── Login / center screens ───────────────────────────────────────────── */

.center-screen {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* The page's own light sits in the top-right corner, far outside the card
     — which left the app's first screen as the only flat one. */
  background: radial-gradient(60% 45% at 50% 48%, var(--gold-glow), transparent 70%);
}
/* Same lit-edge treatment as the plate, at lower intensity: the login card
   is the first object of the app and should already feel made of something. */
.center-screen .card {
  width: 100%;
  max-width: 390px;
  padding: 32px 26px 26px;
  border-color: transparent;
  background:
    linear-gradient(var(--card), var(--card)) padding-box,
    linear-gradient(135deg, rgba(246, 220, 149, 0.5) 0%, rgba(230, 182, 77, 0.12) 40%, rgba(168, 119, 42, 0.1) 70%, rgba(246, 220, 149, 0.3) 100%) border-box;
  box-shadow: var(--shadow-lg);
}
@media (prefers-reduced-motion: no-preference) {
  .center-screen .card { animation: popIn 0.4s cubic-bezier(0.16, 1, 0.3, 1); }
}

.brand-mark { position: relative; width: 72px; height: 50px; margin: 0 auto 22px; }
.brand-mark span, .brand-mark i, .brand-mark b { position: absolute; display: block; border: 1px solid var(--gold); transform: rotate(-30deg) skewX(19deg); }
.brand-mark span { left: 2px; bottom: 5px; width: 49px; height: 33px; border-width: 1.5px 0 0 1.5px; }
.brand-mark i { left: 17px; bottom: 11px; width: 43px; height: 28px; opacity: 0.72; border-width: 1.5px 0 0 1.5px; }
.brand-mark b { left: 33px; bottom: 17px; width: 37px; height: 24px; opacity: 0.42; border-width: 1.5px 0 0 1.5px; }

/* Centred eyebrow: keep the flex layout (a `display: block` here would
   collapse the gold rule, since an inline ::before ignores width/height)
   and mirror the rule on both sides, which is what a centred label wants. */
.center-screen .eyebrow { justify-content: center; margin-bottom: 9px; }
.center-screen .eyebrow::after {
  content: "";
  flex-shrink: 0;
  width: 18px;
  height: 1px;
  background: currentColor;
  opacity: 0.75;
}
.title { font-family: var(--font-display); text-align: center; font-size: 2.2rem; line-height: 1.02; font-weight: 500; letter-spacing: -0.02em; margin-bottom: 9px; }
.subtitle { max-width: 260px; margin: 0 auto 25px; text-align: center; color: var(--text-muted); font-size: 0.92rem; line-height: 1.45; }

.back-link { display: inline-flex; align-items: center; gap: 5px; color: var(--gold); text-decoration: none; font-size: 0.85rem; font-weight: 600; transition: color 0.15s ease; }
.back-link:hover { color: var(--gold-light); text-decoration: underline; }

/* ── Dialogs ──────────────────────────────────────────────────────────── */

dialog {
  border: none;
  border-radius: 14px;
  border: 1px solid var(--border-strong);
  padding: 0;
  width: min(420px, 92vw);
  max-height: min(640px, 86dvh);
  overflow: hidden;
  background: var(--card);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}
@media (prefers-reduced-motion: no-preference) {
  dialog { animation: popIn 0.22s cubic-bezier(0.16, 1, 0.3, 1); }
}
dialog::backdrop { background: rgba(4, 9, 16, 0.76); backdrop-filter: blur(4px); }
/* The split/booking dialog carries far more fields (amount, fx, category,
   participant rows) than every other dialog in the app — widening only this
   one (not the shared `dialog` rule) lets its content reflow into fewer
   rows instead of scrolling, without changing the compact look of the
   simple user/category/confirm dialogs. Scoped to the desktop breakpoint:
   below 640px every dialog becomes a full-width bottom sheet regardless
   (see the media query further down), so this must not fight that. */
@media (min-width: 641px) {
  #split-dialog { width: min(600px, 94vw); }
}
dialog > .card { box-shadow: none; margin: 0; border: none; background: transparent; max-height: inherit; overflow-y: auto; }

.dialog-shell { display: flex; flex-direction: column; max-height: min(640px, 86dvh); }
.dialog-header { flex-shrink: 0; padding: 24px 24px 16px; }
.dialog-title { font-family: var(--font-display); font-weight: 500; font-size: 1.55rem; letter-spacing: -0.02em; margin-bottom: 0; }
.dialog-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 24px 20px;
  display: flex;
  flex-direction: column;
  gap: 0; /* Blöcke bringen ihren eigenen, symmetrischen Abstand mit (siehe .book-block) */
  scroll-padding-block-end: 8px;
}
/* Overlay scrollbars are hidden at rest on iOS/macOS, so a clipped form
   otherwise gives zero cue that anything exists below the fold — the cut
   just looks like a rendering bug. wireDialogScrollFade() (admin.js) toggles
   this class based on actual remaining scroll distance, so it only appears
   when there truly is more below, and clears itself once you reach the
   end — never a permanent, misleading fade on a dialog that already fits. */
.dialog-body.has-more-below {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent 100%);
}
.dialog-footer {
  flex-shrink: 0;
  padding: 15px 24px 20px;
  padding-bottom: calc(20px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--card);
  box-shadow: 0 -10px 18px -14px rgba(0, 0, 0, 0.4);
  position: relative;
  z-index: 1;
}

.row-between { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); }
.row-between.tight { margin-bottom: var(--space-3); }
.roster-actions { display: flex; align-items: center; gap: 6px; }

/* Admin start screen: Mitreisende / Alle Buchungen tabs — the two panels
   below sit as separate cards, only one visible ([hidden]) at a time. */
.admin-tabs {
  display: flex;
  gap: 6px;
  margin: var(--space-3) 0;
  border-bottom: 1px solid var(--border);
}
.admin-tab {
  flex: 1;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font: inherit;
  font-weight: 700;
  font-size: 0.92rem;
  padding: 10px 4px;
  cursor: pointer;
  min-height: var(--tap-min);
}
.admin-tab.active { color: var(--gold); border-bottom-color: var(--gold); }
.admin-tab:hover { color: var(--text); }

/* Confirm dialog */
.confirm-card { text-align: center; padding: 26px 22px 22px; }
.confirm-icon { font-size: 2.2rem; margin-bottom: 10px; }
.confirm-title { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.01em; margin-bottom: 8px; }
.confirm-message { color: var(--text-muted); font-size: 0.92rem; line-height: 1.5; margin-bottom: 20px; }
.confirm-actions { display: flex; gap: 10px; }
.confirm-actions .btn { flex: 1; margin: 0; }

/* Per-booking action sheet (the single "⋯" button opens this) — a stacked,
   left-aligned list of actions, full width like the mobile bottom sheets it
   sits in. The title is left-aligned too (not centred like a confirm) so it
   reads as a menu header, and it truncates rather than wrapping a long
   booking reason across lines. */
.tx-action-sheet { text-align: left; }
.tx-action-sheet .confirm-title {
  font-size: 1.15rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tx-action-sheet-buttons { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.tx-action-sheet-btn {
  justify-content: flex-start;
  gap: 12px;
  font-size: 1rem;
}
.tx-action-sheet-btn .icon { width: 20px; height: 20px; }

/* Split dialog helpers */
/* Sublabel also used by the transaction-list date-range filters (Von/Bis). */
.split-equal-sublabel { font-size: 0.75rem; margin-bottom: 4px; display: block; }

/* Non-split "Für wen?": opened from one person's own page there is nothing
   to pick, so it reads as their name, plainly. */
/* Steht die Person bereits fest (Buchung von ihrer eigenen Seite), ist das
   eine gesetzte Angabe und keine Auswahl — also in der Größe der Listenzeile
   gesetzt statt als fetter Kleintext, der wie ein Label aussieht. */
.split-participant-fixed { font-weight: 650; color: var(--text); font-size: 1.0625rem; letter-spacing: -0.01em; padding: 0; }

/* flex-wrap so .split-row-fx-preview (flex-basis: 100%, hidden unless a row
   is in FX mode) drops to its own line under the name/inputs instead of
   fighting them for width — the common (no fx) case never wraps. */
.split-participant-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 10px 4px; border-bottom: 1px solid var(--border); }
.split-participant-row:last-child { border-bottom: none; }
.split-participant-name {
  flex: 1;
  min-height: var(--tap-min);
  display: flex;
  align-items: center;
  font-weight: 600;
  color: var(--text);
  font-size: 0.95rem;
}

.split-shares-input { width: 46px !important; min-width: 0 !important; min-height: var(--tap-min) !important; padding: 10px 4px !important; text-align: center; }

/* Per-person amount cell wrapper (custom rows). */
.split-amount-fx-wrap { display: flex; align-items: center; gap: 4px; }

/* Weight stepper always drops onto its own full-width line under the name +
   computed € — consistent at every width, never the ragged half-in/half-out
   wrap a shrink-reliant inline layout produced at some widths. */
.split-shares-box { display: flex; align-items: center; gap: 6px; flex-basis: 100%; }
.split-shares-suffix { color: var(--text-muted); font-size: 0.8rem; }

/* Steppers (ghost headcount, per-person weight) — big enough taps, >=16px. */
.split-stepper-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.split-stepper-label { font-weight: 600; }
.split-stepper { display: flex; align-items: center; gap: 8px; }
.split-stepper-btn {
  width: var(--tap-min); height: var(--tap-min); min-width: var(--tap-min);
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-alt);
  font-size: 20px; line-height: 1; color: var(--text); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.split-stepper-btn:disabled { opacity: 0.4; cursor: default; }
#ghost-count { min-width: 1.5em; text-align: center; font-variant-numeric: tabular-nums; font-weight: 700; }

/* Custom rows: name grows, one amount field (explicit width, Safari-safe). */
.split-custom-row .split-participant-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.split-custom-row input[type="number"] { width: 120px; min-height: var(--tap-min); padding: 10px 12px; text-align: right; font-variant-numeric: tabular-nums; font-size: 16px; }
#custom-total-input { font-size: 16px; }

/* Balance / reconciliation line */
.split-balance { font-size: 0.9rem; color: var(--text-muted); font-variant-numeric: tabular-nums; min-height: 1.2em; }
.split-balance-danger { color: var(--danger, #dc2626); font-weight: 600; }
.split-balance:not(.split-balance-danger) { color: var(--text); font-weight: 600; }

/* ── Buchungsdialog v2: Blöcke, Chips, Betrag+Währung, €-Zeile ────────── */
/* Ein einziger Abstands-Mechanismus: die dialog-body führt keinen eigenen
   Flex-Gap mehr (der stapelte sich mit padding+margin der Blöcke zu ~47px
   wildem Zwischenraum), stattdessen trennt jeder Block über Trennlinie +
   symmetrisches padding/margin (=32px). Innerhalb eines Blocks sorgt der
   Flex-Gap für gleichmäßigen Feld-Rhythmus. */
.book-block { display: flex; flex-direction: column; gap: 14px; padding-bottom: 16px; margin-bottom: 16px; border-bottom: 1px solid var(--border); }
.book-block:last-of-type { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
.book-block-head { font-size: 0.78rem; letter-spacing: 0.04em; color: var(--text-muted); font-weight: 700; text-transform: uppercase; margin-bottom: 0; }
/* Direkte Block-Hinweise (z.B. Ghost-Hint): der Block-Gap trägt den Abstand,
   kein zusätzliches margin-top oben drauf. */
.book-block > .hint { margin-top: 0; }
/* Fehlerzeile hat ohne Flex-Gap der dialog-body sonst keinen Abstand zum
   letzten Block. */
#split-error:not(:empty) { margin-top: 16px; }

/* Für-wen-Chips — Anzahl gewählter Chips = Modus (1 = Einzelbuchung, 2+ = Split). */
.who-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.who-chip {
  min-height: var(--tap-min);
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--bg-alt);
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}
/* Gewählt = gefülltes Gold, wie der Buchen-Button — nie das Fallback-Blau. */
.who-chip.active { background: var(--gold-surface); border-color: var(--gold-surface); color: var(--on-gold-surface); font-weight: 700; }

/* Große Gruppe: Gewählt-Liste (entfernbar) + Hinzufügen-/Umbuchen-Select. */
.who-selected { display: flex; flex-direction: column; }
.who-selected-row { display: flex; align-items: center; gap: 10px; min-height: var(--tap-min); padding: 4px 2px; border-bottom: 1px solid var(--border); }
.who-selected-row:last-child { border-bottom: none; }
.who-selected-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: 0.95rem; color: var(--text); }
.who-remove {
  flex: 0 0 auto; width: var(--tap-min); height: var(--tap-min);
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-alt);
  color: var(--text-muted); font-size: 20px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.who-remove:hover { color: var(--red-fill); border-color: var(--red-fill); }
.who-add { width: 100%; font-size: 16px; }
/* Modus-Zeile — macht Einzel vs. Aufteilung lesbar, ohne Chips zu zählen. */
.who-mode { font-size: 0.85rem; color: var(--text); font-weight: 600; line-height: 1.4; }
.who-mode.who-mode-muted { color: var(--text-muted); font-weight: 500; }
.who-mode strong { color: var(--gold); font-weight: 700; }

/* Betrag + Währung in einer Zeile: Input wächst, Select feste Breite (nie auf
   Form-Control-Shrink von Mobile Safari verlassen — siehe CLAUDE.md). */
.amount-row { display: flex; align-items: center; gap: 8px; }
.amount-row > input[type="number"] { flex: 1 1 auto; min-width: 0; font-size: 16px; }
.currency-select { flex: 0 0 96px; width: 96px; min-height: var(--tap-min); font-size: 16px; padding: 10px 8px; }
#currency-other { margin-top: 8px; font-size: 16px; }
.eur-row { margin-top: 8px; }
.eur-row .eur-eq { flex: 0 0 auto; color: var(--text-muted); font-weight: 700; }
.eur-row .eur-suffix { flex: 0 0 auto; color: var(--text-muted); font-weight: 700; }

#amount-block > .amount-type-toggle { width: 100%; margin-bottom: 0; }
#amount-block > .amount-type-toggle .amount-type-btn { font-size: 16px; }
#basis-seg { margin-top: 8px; width: 100%; }
#basis-seg .amount-type-btn { font-size: 16px; }
#amount-label, #fx-eur-label, #custom-eur-label, #custom-total-label { display: block; }
#shares-toggle, #date-collapsed .link-btn { display: inline-block; margin-top: 8px; }

/* Einzeln-Zeilen: Name links, Betrag rechts mit fester Breite (Safari-robust). */
.split-custom-row { justify-content: space-between; }
.split-custom-row .split-participant-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-custom-row .split-amount-fx-wrap { flex: 0 0 auto; }
.split-custom-row .split-amount-fx-wrap input { width: 118px; font-size: 16px; }

/* Grund + Buchungsdatum side by side instead of stacked — one fewer field
   height in a dialog that's often taller than the viewport. Wraps back to a
   single column on narrow phones (bottom-sheet breakpoint, see below).
   min-width: 0 on the cell keeps the native date input (see
   .tx-date-range-row above) from forcing this row wider than the dialog. */
.split-reason-date-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.split-reason-date-row > div { min-width: 0; }
@media (max-width: 640px) {
  .split-reason-date-row { grid-template-columns: 1fr; }
}

.hint-toggle-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: 8px;
}
.hint-toggle-btn:hover { color: var(--gold); background: var(--gold-tint); }
.static-note { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); font-size: 0.8rem; font-weight: 600; }
/* Stacked on narrow screens — at ~372px content width (the shared 420px
   dialog, and every bottom-sheet phone width) 2-3 of these items wrap to
   two lines and read as a ragged pair when left to sit side by side. The
   split dialog is wider on desktop (see #split-dialog above) specifically
   so these can run as one left-aligned, wrapping row there instead of
   costing a full line each. */
.split-note-row { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
@media (min-width: 641px) {
  .split-note-row { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px 16px; }
}

.link-btn {
  background: none;
  border: none;
  color: var(--gold);
  font: inherit;
  font-weight: 700;
  font-size: 0.82rem;
  cursor: pointer;
  padding: 4px 2px;
  display: inline-flex;
  align-items: center;
}
.link-btn:hover { text-decoration: underline; }

/* Dismissible hints */
.dismissible-hint {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  text-align: left;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 10px 10px 12px;
  margin-top: 10px;
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1.4;
}
.dismissible-hint span { flex: 1; }
.hint-dismiss {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 6px;
}
.hint-dismiss:hover { background: var(--border-strong); color: var(--text); }

/* ── Offline outbox banner ────────────────────────────────────────────── */

.outbox-banner {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 16px;
  padding-top: calc(10px + env(safe-area-inset-top));
  background: var(--gold-surface);
  color: var(--on-gold-surface);
  font-size: 0.85rem;
  font-weight: 700;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
}
#outbox-banner-text {
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  padding: 6px 2px;
  min-height: var(--tap-min);
  display: inline-flex;
  align-items: center;
}
.outbox-banner button#outbox-banner-sync {
  background: rgba(0, 0, 0, 0.16);
  border: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  font-size: 0.78rem;
  padding: 6px 12px;
  min-height: 36px;
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.outbox-banner button#outbox-banner-sync:active { background: rgba(0, 0, 0, 0.28); }
.outbox-banner.outbox-banner-error { background: var(--red-fill); color: #fff; }

/* ── QR dialog ────────────────────────────────────────────────────────── */

.qr-card { text-align: center; padding: 26px 22px 22px; }
.qr-name { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.01em; }
.qr-id { margin-top: 0; margin-bottom: 14px; font-variant-numeric: tabular-nums; }
.qr-code-container { display: flex; justify-content: center; margin-bottom: 16px; }
.qr-code-container svg { width: 230px; height: 230px; background: #fff; border-radius: 14px; padding: 14px; box-shadow: var(--shadow-sm); }

/* Stacked by default (mobile-first — see CLAUDE.md), not a flex row relying
   on the input shrinking: `flex:1; min-width:0` shrinks a text input
   correctly in Chromium, but real Mobile Safari has a long-standing bug
   where a form control in a flex row ignores min-width:0 and keeps its own
   intrinsic content width, overflowing the row anyway — this is exactly
   what a real-device report caught after this passed every Chromium-based
   Playwright check. Both elements get an explicit width instead (100% each,
   full-bleed stacked), the same "explicit width, not shrink math" approach
   already used for .split-shares-input below. Only becomes a single row
   once there's comfortably enough width for both at their natural size. */
.qr-link-row { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.qr-link-row input {
  width: 100%;
  /* 16px, not 0.82rem: below 16px iOS Safari auto-zooms the page in on focus
     (see .search-filter-box note above). The link is longer than the field,
     but that's fine — a text input scrolls its own content horizontally, so
     the readonly link just starts at its beginning and the layout stays put. */
  font-size: 16px;
  text-align: left;
  padding: 10px 12px;
  min-height: auto;
}
.qr-link-row .btn { width: 100%; min-height: auto; white-space: nowrap; }
@media (min-width: 480px) {
  .qr-link-row { flex-direction: row; }
  .qr-link-row input { width: 1px; flex: 1 1 auto; }
  .qr-link-row .btn { width: auto; flex-shrink: 0; }
}

/* ── Outbox item rows (reuse .tx-item skin inside the dialog) ───────────── */
.outbox-item { background: var(--bg); padding: 10px 12px; border-radius: 10px; border-bottom: none; }

/* ── Ripple — a small authentic tap-feedback touch ───────────────────── */
/* A gold-tinted flash, not a white Material splash — an opaque grey disc
   on a dark card reads as leftover Material chrome, tonally the opposite
   of "calm, restrained, premium". */
.ripple { position: absolute; border-radius: 50%; background: var(--gold-tint); transform: scale(0); animation: rippleAnim 0.55s ease-out; pointer-events: none; }
button.btn .ripple { background: rgba(0, 0, 0, 0.16); }

/* ── Custom scrollbar, webkit only — subtle ──────────────────────────── */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }

/* ── Mobile ───────────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  header.topbar { margin-bottom: 22px; }
  /* .app / .card / .balance-card paddings are mobile-first in their own base
     rules now; the overrides that used to live here (including a
     min-height: 174px on the plate, i.e. exactly the half-empty card the
     redesign set out to fix) were silently undoing them. */

  /* Dialogs behave like an iOS action/bottom sheet — full width, flush to
     the bottom, rounded only on top. Desktop keeps the centered floating
     card (the default `dialog` rules above). */
  dialog {
    position: fixed;
    left: 0; right: 0; bottom: 0; top: auto;
    margin: 0;
    width: 100%;
    max-width: 100%;
    max-height: 88dvh;
    border-radius: 16px 16px 0 0;
  }
  @media (prefers-reduced-motion: no-preference) {
    dialog { animation: sheetUp 0.28s cubic-bezier(0.16, 1, 0.3, 1); }
  }
  .dialog-shell { max-height: 88dvh; }
  .dialog-header { padding: 22px 20px 14px; }
  .dialog-body { padding: 0 20px 18px; }
  .dialog-footer { padding: 14px 20px calc(16px + env(safe-area-inset-bottom)); }
}

@media (max-width: 500px) {
  .user-row { flex-wrap: wrap; gap: 0; }
  .user-open-btn { display: flex; flex-direction: column; justify-content: center; }
  /* No second hairline: with the row divider below, every member was framed
     by two lines and the roster's rhythm fell apart. */
  .user-actions { width: 100%; justify-content: flex-end; margin: 2px 0 0; padding-top: 0; }
  .user-admin-action { min-width: 62px; }

  #detail-tx-list .tx-item, #all-tx-list .tx-item { flex-wrap: wrap; row-gap: 6px; }
  /* flex-wrap so the amount + action buttons drop the buttons onto their own
     line rather than overflowing when both together exceed the row: a single
     booking now carries four fixed-width tap targets (duplicate/edit/cancel/
     delete) alongside a nowrap euro amount, which for a large amount can top
     the ~250px available at a 320px viewport. Normal small amounts still fit
     on one line, so this only engages in the tight case — the robust
     mobile-first fallback rather than trusting every amount to fit. */
  #detail-tx-list .tx-side, #all-tx-list .tx-side { flex-basis: 100%; flex-wrap: wrap; justify-content: flex-end; }
}

/* ── Narrow phones (≤360px) ──────────────────────────────────────────────
   The larger ledger scale is the point of this design, so it is kept; what
   gives way instead is the fixed furniture around it — the medallion, the
   gaps and the amount's reserved column. Verified at 320px, the narrowest
   viewport the app targets (see CLAUDE.md). */
@media (max-width: 360px) {
  .app { padding: 22px 12px 40px; }
  .card { padding: 18px 12px; }
  .balance-card, .overview-card { padding: 22px 18px 20px; }
  .tx-icon { width: 36px; height: 36px; border-radius: 10px; }
  .tx-icon .category-icon { width: 1.25rem; height: 1.25rem; }
  .tx-chevron { width: 18px; height: 18px; padding: 2px; }
  /* Smaller here than the 390px base, never larger, and no reserved column:
     an earlier revision of this block had the amount at 1.125rem with a
     76px min-width — bigger on the narrowest phone than on a roomier one,
     and re-introducing exactly the fixed column the base rule removes.
     Every pixel taken off the furniture here goes to the reason: at 320px
     it has ~100px, which is the difference between "Einzahlung" fitting on
     its line and being broken mid-word. */
  .tx-amount { font-size: 0.95rem; }
  #tx-list .tx-header { column-gap: 8px; }
  #tx-list .tx-reason { font-size: 0.95rem; letter-spacing: -0.015em; }
  #tx-list .tx-meta { font-size: 0.8125rem; }
  #tx-list .tx-item::after { left: 48px; }
}
