/* Reusable card + button + form-control primitives. Document new patterns in
 * DESIGN.md before adding them here. */

@layer components {
  /* Flat card: a hairline border alone gives enough separation on white —
     no shadow, so a page of cards reads as calm, not a stack of raised boxes.
     Floating surfaces (combobox/modal/toast) keep --shadow-1 for real lift. */
  .card {
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-md);
    padding: var(--sp-5);
    margin-bottom: var(--sp-4);
  }

  /* Neutral hint / empty-state / sub-label text — the app-wide muted, small
     line (empty states, form labels, chart figure strips, table sub-heads).
     Declared BEFORE .error-banner so `.note-meta.error-banner` (see
     partials/generic/error-note.html) still takes the danger colour via source order.
     Context rules only tweak margin (.form/.admin kill it) or shrink further
     (.mz-sheet → --fs-xs); they no longer re-declare colour/size. */
  .note-meta { font-size: var(--fs-sm); color: var(--c-text-muted); }

  /* Error/alert banner — the one danger-tinted card state (role="alert"), shared
     by every view. Replaces the per-entity copies .plan-error/.cf-error/.ma-error/
     .admin-error. Always combined with .card, so it just recolours the border. */
  .error-banner { color: var(--c-danger); border-color: var(--c-danger); }

  /* Warning note — the amber-tinted sibling of .error-banner, for a *precondition*
     that blocks a feature until the user acts ("Buchen ist noch nicht
     konfiguriert", kein Bexio-Benutzer verknüpft, Integration nicht eingerichtet).
     Distinct from .note-meta (a neutral empty-state / hint) and from .error-banner
     (a genuine failure). Carries role="alert" so a screen reader announces it, and
     leads with an <x-icon name="triangle-alert"> — bind x-text on the trailing
     <span>, never on the <p> itself (that would wipe the icon). See DESIGN.md →
     Warning note. */
  .warn-note {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    margin: var(--sp-3) 0;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-warn);
    border-radius: var(--radius-sm);
    background: var(--c-warn-tint);
    color: var(--c-warn);
    font-size: var(--fs-sm);
  }
  .warn-note > x-icon { margin-top: 0.1em; } /* optical align glyph to first line */
  /* Optionaler Nachweis-Knopf am Ende einer Warnung ("welche Rechnungen sind
     das?" in Debitoren). Hängt rechts am Rand und schrumpft nicht mit — der
     Warntext daneben umbricht stattdessen, wie ohne Knopf auch. */
  .warn-note__action { flex: none; margin-left: auto; align-self: flex-start; }

  .btn {
    /* Every button is exactly one control tall (--control-h), centred by
       inline-flex, so it can never render taller than the field/combobox beside
       it — the app-wide "buttons ≤ neighbours" rule. line-height:1 keeps the
       label from re-inflating the box; min-height (not height) lets a rare
       two-line label grow instead of clipping. Horizontal padding still shapes
       the width. The dense variants below (--small, --icon) opt out.
       See DESIGN.md → Control height. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* The one icon↔label distance. A flex container drops whitespace-only text
       nodes, so `<x-icon></x-icon><span>Label</span>` renders glued together
       without a gap — which is why half a dozen views had each re-spelled their
       own on a .btn-based class while the rest simply looked wrong. Owned here
       once; --icon (single child) is unaffected, --small tightens it below. */
    gap: var(--sp-2);
    min-height: var(--control-h);
    font: inherit;
    /* After `font: inherit` — the shorthand resets line-height, so it must be
       set afterwards or the inherited 1.55 re-inflates the box past --control-h. */
    line-height: 1;
    font-weight: var(--fw-medium);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    border: 1px solid var(--c-border);
    background: var(--c-surface-2);
    color: var(--c-text);
    cursor: pointer;
    /* Ein `<a class="btn">` ist ein Knopf, kein Link im Fliesstext: ohne das hier
       trägt genau er als einziger einen Unterstrich und liest sich neben seinen
       Nachbarn wie ein Fremdkörper. Betrifft die Datei-/Deep-Link-Knöpfe
       (Prüfblatt Briefpapier, „in bexio öffnen", PDF-Viewer). */
    text-decoration: none;
    transition: background var(--motion-fast) var(--ease-out);
  }
  .btn:hover:not(:disabled) { background: var(--c-border); }

  /* A disabled button must LOOK disabled — .btn--icon says so below, the text
     buttons never did. `.btn--primary:disabled` therefore rendered in full accent
     blue at full opacity: "Als Entwurf nach bexio" sat there as the loudest thing
     on the phone screen while being unclickable, with the amber note underneath
     explaining why. Same dimming as the icon variant, so both read alike. */
  .btn:disabled { opacity: 0.5; cursor: default; }

  /* Compact size for dense rows/cells (moved here from entities/cashflow.css so
     it sits with the other .btn primitives — it is used across views). Opts out
     of the shared control height (min-height:0) and stays as small as its
     padding — that is the whole point of the dense variant. */
  .btn--small {
    min-height: 0; padding: 2px var(--sp-2); font-size: var(--fs-sm);
    gap: var(--sp-1); /* dense box → tighter icon↔label distance than the full-size .btn */
  }

  .btn--primary {
    background: var(--c-accent);
    color: var(--c-accent-text);
    border-color: transparent;
  }
  /* `:not(:disabled)` is not decoration here, it is the specificity that makes
     this rule apply at all: the generic `.btn:hover:not(:disabled)` above scores
     (0,3,0), so a bare `.btn--primary:hover` (0,2,0) LOST to it — hovering a
     primary button swapped the accent fill for the grey --c-border while the
     white --c-accent-text stayed, i.e. white on light grey (~1.2:1). Matching the
     selector shape of the --danger sibling puts both at (0,3,0) and lets source
     order decide. Keep the `:not(:disabled)` if this selector is ever touched. */
  .btn--primary:hover:not(:disabled) { background: var(--c-accent-hover); }

  /* Filled destructive button — the confirm action in a delete/ignore dialog,
     where a plain .btn wouldn't signal irreversibility. Text sibling of
     .btn--icon-danger. */
  .btn--danger {
    background: var(--c-danger);
    color: var(--c-danger-text);
    border-color: transparent;
  }
  .btn--danger:hover:not(:disabled) { background: var(--c-danger-hover); }

  /* Icon-only button — square, equalized padding, centers the <x-icon>. The
     glyph inherits font-size (1em); the host owns the name via title/aria-label.
     Space-saving stand-in for a text button in tables and dense rows. */
  .btn--icon {
    /* Square at the shared control height — inherits min-height + inline-flex
       centring from .btn; min-width squares it off so it lines up with the
       text buttons and fields around it. */
    min-width: var(--control-h);
    padding: var(--sp-2);
  }
  .btn--icon:hover:not(:disabled) { color: var(--c-accent); }
  .btn--icon:disabled { opacity: 0.5; cursor: default; }
  .btn--icon-danger:hover:not(:disabled) {
    color: var(--c-danger); border-color: var(--c-danger);
  }

  /* Dichte Icon-Aktion IN EINER KARTE — der Modifier zu .btn--icon (nie allein),
     für eine Aktion, die in einem kartenbreiten Block sitzt statt in einer
     Rasterzeile: heute die Board-Karten (Lead-/Deal-Brett der Sales-Performanz).
     Die geteilte --control-h-Kachel ist dort dreimal 36 px in einer 15 rem breiten
     Karte — mehr Fläche als der Personen- oder Deal-Name, um den es geht —, und ein
     Rahmen 4 px innerhalb des Kartenrahmens liest als Rauschen statt als Aktion.
     Also: Box auf das Glyph plus Polster (≈27 px, über dem 24-px-Minimum von
     WCAG 2.2 SC 2.5.8), Füllung und Rahmen weg, Affordanz vom Glyph und dem
     Akzent-Hover (dasselbe, was die Touch-Regel unten mit jedem .btn--icon macht).
     Das GLYPH bleibt --icon-size-action wie überall — es driftet nie, nur die Box.
     NICHT für eine Zeilen-Aktion: die Ruhe-Optik einer Row-Action ist app-weit die
     gerahmte Kachel (DESIGN.md → Button). */
  .btn--icon-dense {
    min-height: 0;
    min-width: 0;
    padding: var(--sp-1);
    background: transparent;
    border-color: transparent;
    /* `flex: none` ist hier nicht Kosmetik, sondern die Gegenbuchung zu
       `min-width: 0`: das geerbte `min-width: var(--control-h)` war in der
       Flex-Titelzeile der Karte zugleich die Schrumpf-Untergrenze. Ohne den
       Ersatz staucht ein langer Name die drei Aktionen daneben — gemessen auf
       15.5 px, also UNTER das 17-px-Glyph, das sie tragen. Der Name bricht um
       (`overflow-wrap: anywhere` an der Karte), die Aktion nicht. Ausserhalb
       eines Flex-Containers ist die Zeile wirkungslos. */
    flex: none;
  }

  /* dot-brand focus ring: cyan border + soft glow.
   *
   * Der KONTRAST-Träger ist die Border, nicht der Schein: --c-accent-glow ist
   * rgba(0,150,200,0.12) und liegt über Weiss bei ~1.15:1 — als Indikator allein
   * unsichtbar. Der Wechsel der Border auf --c-accent trägt 3.39:1 gegen die
   * Fläche und erfüllt damit WCAG 1.4.11. Wer den Schein je als alleinigen
   * Indikator einsetzt, muss ihn zwangsläufig bis fast auf die volle Akzentfarbe
   * hochziehen: eine 12-%-Tönung KANN 3:1 rechnerisch nicht erreichen.
   *
   * `outline: 2px solid transparent` statt `outline: none`: im
   * Forced-Colors-Modus (Windows-Kontrastdesign) ersetzt das System sowohl
   * border-color als auch box-shadow durch eigene Farben — beide Hälften des
   * Rings fielen dort aus und der Fokus war schlicht unsichtbar. Ein
   * transparentes Outline ist normal nicht zu sehen, wird vom System aber
   * eingefärbt und bringt den Fokus dort zurück. Kostet visuell nichts. */
  .btn:focus-visible,
  .field:focus:not([readonly]) {
    outline: 2px solid transparent;
    outline-offset: 2px;
    border-color: var(--c-accent);
    box-shadow: 0 0 0 3px var(--c-accent-glow);
  }

  .field {
    display: block;
    width: 100%;
    font: inherit;
    /* Same shared control height as .btn — line-height:1 pulls the natural box
       below --control-h so the min-height governs and every single-line input,
       month picker and combobox stands exactly one control tall next to its
       button. The browser vertically centres the text in the taller box. */
    line-height: 1;
    min-height: var(--control-h);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    background: var(--c-surface);
    color: var(--c-text);
    margin-bottom: var(--sp-3);
  }
  /* Multi-line inputs are the exception: taller box, and restore the readable
     base line-height that .field flattened to 1 for the single-line case. */
  textarea.field { min-height: 5rem; line-height: var(--lh-base); resize: vertical; }

  /* GEFÜHRTES FELD — der Wert gehört einem fremden System und wird hier nur
     angezeigt (DESIGN.md → „Geführtes Feld"). Gedämpfte Fläche und Schrift, kein
     Fokusring (s. oben), Zeiger wie bei Text. `readonly` und NICHT `disabled`:
     der Wert soll lesbar, markierbar und kopierbar bleiben — ein disabled-Feld
     fällt aus der Tabreihenfolge und wird von manchen Screenreadern ganz
     übersprungen. Am Knopf gilt das Gegenteil, dort ist :disabled richtig. */
  .field[readonly] {
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    cursor: default;
  }

  /* Native checkbox / radio — brand-tint the mark (accent-color keeps the real
     control, so keyboard + screen-reader behaviour stays intact) and give it a
     legible, consistent box; the browser default is small and grey. margin:0 so
     it sits flush in the form grid / table cell it lives in. */
  input[type="checkbox"],
  input[type="radio"] {
    accent-color: var(--c-accent);
    width: 1.05rem;
    height: 1.05rem;
    margin: 0;
    cursor: pointer;
  }
  input[type="checkbox"]:disabled,
  input[type="radio"]:disabled { cursor: default; }

  /* x-collapse wrapper: keep it a block formatting context so the inner card's
     margin-bottom stays inside the animated height. Without this the plugin
     measures a start height that excludes the child margin, then snaps ~16px
     when overflow:hidden re-includes it. (The wrapper itself carries no padding/
     border/margin — see DESIGN.md "x-collapse".) The plugin toggles inline
     display, which correctly overrides this when hidden. */
  [x-collapse] { display: flow-root; }

  .row { display: flex; gap: var(--sp-2); align-items: center; }
  /* Horizontal form controls line up on a shared baseline — the field's
     block-flow bottom margin would otherwise offset it against sibling buttons. */
  .row > .field { margin-bottom: 0; }

  /* Native date/month: WebKit (iOS Safari — und damit auch Chrome/Firefox auf
     iOS, die dieselbe Engine fahren) rendert den Wert NICHT als zentrierten
     Text, sondern in einem eigenen ::-webkit-date-and-time-value mit eigenem
     Default-Rand, das oben in der Box sitzt und rechtsbündig ausrichtet. In
     einem Feld, dessen Höhe von --control-h kommt (44px am Telefon) statt vom
     Inhalt (~32px), heisst das: das Datum klebt oben und hängt am rechten Rand
     — der Rest der Zeile (Picker-Glyph, Nachbar-Button) steht mittig, das
     Datum nicht. Die Box selbst zum Flex-Container machen, dann zentriert
     `align-items` den Wert wie bei jedem anderen Feld; Rand und Ausrichtung
     des Pseudo-Elements zurücksetzen. Blink/Gecko auf dem Desktop kennen das
     Pseudo-Element nicht und legen ein einzeiliges Control als Flex-Container
     identisch aus — die Regel ist dort wirkungslos, aber nicht schädlich. */
  input.field[type="date"],
  input.field[type="month"] {
    display: flex;
    align-items: center;
  }
  input.field[type="date"]::-webkit-date-and-time-value,
  input.field[type="month"]::-webkit-date-and-time-value {
    margin: 0;
    text-align: left;
  }

  /* ── Touch (any width) ────────────────────────────────────────────────────
     Two rules that follow the POINTER, not the viewport. Both used to sit in
     the ≤640px block below, which meant a tablet — a finger-only device that is
     768–1366 CSS px wide — matched neither. See DESIGN.md → Breakpoints.

     Die Breiten-Bedingung steht daneben, nicht statt ihr — genau wie am
     --control-h-Token (tokens/spacing.css). Der Umzug auf die Zeigerart allein
     gewann das Tablet und verlor dabei das schmale Fenster OHNE Grobzeiger: die
     16px-Untergrenze unten galt vorher für jede Breite ≤640px, danach nur noch
     für Finger-Geräte. `tests/smoke/mobile-touch-targets.spec.js` misst genau
     das (360px, kein Touch) und stand auf 13.6px. Beide Hälften sind nötig. */
  @media (pointer: coarse), (max-width: 640px) {
    /* Safari auto-zooms the page when a focused text control renders below 16px.
       This is a WebKit-on-touch behaviour, not a phone one: iPadOS does it too,
       so on a tablet every tap into a dense grid cell yanked the page to a zoomed
       viewport it never zoomed back out of. The dense grids run at --fs-sm
       (~13.6px), so that was every cell/field input in the app. Floor any
       text-entry control at 1rem (16px); `max(1rem, 1em)` keeps larger inputs
       (e.g. the ⌘K search) at their own size. Checkboxes/radios are exempt (no
       text, no zoom). Lives in @layer components so it wins over the `font:
       inherit` on .field. */
    .field,
    input:not([type="checkbox"]):not([type="radio"]),
    textarea {
      font-size: max(1rem, 1em);
    }

    /* Icon-only buttons drop their fill + frame. --control-h grows to 44px on
       touch (spacing.css) so the tap target is right, but a *filled and
       bordered* 44px square weighs far more than the 17px glyph it carries: a
       toolbar cluster or a period-nav pair reads as grey tiles instead of
       actions. Keep the target, drop the box — the glyph plus the accent hover
       carry the affordance. Text buttons and the .btn--primary/--danger fills
       are unaffected; entities/accounts.css already does exactly this for the
       tree-row actions, and layout/base.css for the header chrome.
       Tied to the same query as the 44px token on purpose: the two must move
       together, or the tablet gets the tile wall this rule exists to prevent. */
    .btn--icon { background: transparent; border-color: transparent; }

    /* Und die dichte Karten-Variante gibt ihre Verkleinerung hier WIEDER AUF: auf
       einem Fingergerät (oder in einem schmalen Fenster) ist die Box das Tap-Ziel,
       und ein 27px-Knopf in einer Board-Karte läge unter dem 44px-Minimum, neben
       44px-Geschwistern. Sie fällt also auf die geteilte Kachelgrösse zurück —
       Füllung und Rahmen bleiben dabei weg, die nimmt die Regel darüber ohnehin.
       Bewusst dieselbe Query wie der --control-h-Token (tokens/spacing.css) und
       wie die Regel darüber: die drei müssen zusammen wandern, sonst bekommt das
       Tablet eine 27px-Aktion, die niemand trifft. Darum braucht diese Variante
       auch KEIN ::after-Hit-Pad (components/icon.css) — anders als ein Chevron
       oder ein Chip-Löschen hat sie in der Karte den Platz, ihre echte Box zu
       vergrössern. */
    .btn--icon-dense {
      min-height: var(--control-h);
      min-width: var(--control-h);
      padding: var(--sp-2);
    }
  }

  /* ── Phone (≤640px) ───────────────────────────────────────────────────────
     What is left here is genuinely about ROOM, not about fingers. */
  @media (max-width: 640px) {
    /* A native date/month input renders exactly one fixed-format value plus its
       picker glyph — its content width IS the control, there is nothing to fill
       with. Stretched to the full column of a one-column phone form it reads as
       a huge, mostly-empty box (~307px of frame around ~120px of date). Let the
       browser size it to that content instead: --control-h still holds the 44px
       tap target, only the width goes. `fit-content` rather than a guessed rem
       value, which would clip wherever the locale renders a longer format
       (mm/dd/yyyy vs. dd.mm.yyyy). Type selector so it outranks the
       `.form > .field` / `.form__group > .field` width rules in form.css, and
       `flex: 0 0 auto` so a wrapped .form__group line (the admin employment
       range) doesn't hand each half the whole row via its 12rem basis. */
    input.field[type="date"],
    input.field[type="month"] {
      width: fit-content;
      max-width: 100%;
      flex: 0 0 auto;
    }
  }
}
