/* ===========================================================================
   PMA Ops — "JUNTA"                  backend/static/css/components.css
   Layout-Primitive, App-Shell und alle Komponenten. Vier Flächen, ein Satz.
   Markup-Vertrag je Komponente: docs/DESIGN-SYSTEM.md §6.
   Klassennamen: block, block__element, block--modifier. Utilities: u-*.
   =========================================================================== */

@layer layout {

  /* Die EINE Stelle, an der die Seitenrinne gesetzt wird. Vertikal immer
     padding-block, nie eine padding-Kurzform (die würde die Rinne nullen). */
  .shell {
    max-inline-size: 46rem;
    margin-inline: auto;
    padding-inline: var(--gutter);
    padding-block: var(--sp-5) var(--sp-8);
  }
  /* Liegt eine Daumenleiste auf der Fläche, muss die letzte Ledger-Zeile
     freibleiben — 56px Knopf + Leistenpolster + Atemluft. */
  .shell--thumbbar { padding-block-end: calc(var(--tap-lg) + var(--sp-8)); }
  .shell--wide { max-inline-size: 72rem; }
  .shell--full { max-inline-size: none; }

  /* Geschwister werden mit gap gesetzt, nie mit Einzelmargins. */
  .stack        { display: grid; gap: var(--sp-5); }
  .stack--tight { display: grid; gap: var(--sp-3); }
  .stack--loose { display: grid; gap: var(--sp-7); }

  /* Zeile, die umbrechen DARF. Spanisch läuft ~20 % länger als Englisch;
     nichts in diesem System bekommt eine feste Breite. */
  .cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
  }
  .cluster--baseline { align-items: baseline; }
  .cluster--between  { justify-content: space-between; }

  /* Vollbreite Bänder (Fotostreifen, Terrazzo) brechen aus der Shell aus. */
  .bleed {
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }

  /* Ein Wrapper kann sich nicht selbst abfragen → dieser existiert dafür. */
  .cq { container-type: inline-size; }
}

@layer components {

  /* =========================================================================
     1 · APP-SHELL
     ========================================================================= */

  /* Kopfleiste. Sticky, aber nie über der Systemleiste des Telefons. */
  .appbar {
    position: sticky;
    top: env(safe-area-inset-top, 0px);
    z-index: var(--z-header);
    --focus-halo: var(--tile);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-3) var(--gutter);
    background: var(--tile);
    border-block-end: var(--joint) solid var(--grout);
  }
  .appbar__brand {
    font-family: var(--f-display);
    font-weight: 800;
    font-size: var(--step-1);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
    text-transform: uppercase;
    margin-inline-end: auto;
  }
  .appbar__brand a { color: inherit; text-decoration: none; }
  /* Lizenz und Ort: prüfbare Angaben → Mono (Invariante 4). */
  .appbar__lic {
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
  }

  /* Hauptnavigation der öffentlichen Website / Ops-Konsole. */
  .nav { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
  .nav__link {
    display: grid;
    place-items: center;
    min-block-size: var(--tap);
    padding-inline: var(--sp-3);
    color: var(--ink);
    text-decoration: none;
    font-weight: 600;
    border-block-end: var(--joint) solid transparent;
  }
  .nav__link:hover { border-block-end-color: var(--grout-deep); }
  .nav__link[aria-current] {
    border-block-end-color: var(--accent);
    color: var(--accent);
  }

  /* Daumenleiste: eine Primäraktion + ein leiser Ausgang. ~96 % Trefferquote
     im unteren Band gegenüber ~61 % oben. */
  .thumbbar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: var(--z-thumbbar);
    --focus-halo: var(--tile);
    background: var(--tile);
    border-block-start: var(--joint) solid var(--ink);
    padding: var(--sp-3) var(--gutter) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
  }
  .thumbbar__in {
    max-inline-size: 46rem;
    margin-inline: auto;
    display: grid;
    gap: var(--sp-2);
  }
  @media (min-width: 30rem) {
    .thumbbar__in--pair { grid-template-columns: 1fr auto; align-items: center; }
  }
  .thumbbar .btn--primary { min-block-size: var(--tap-lg); }
  /* Ein Ausgang, der nicht der Knopf ist. */
  .thumbbar__note {
    font-size: var(--step--2);
    color: var(--ink-2);
    text-align: center;
    max-inline-size: none;
  }

  /* Untere Tab-Navigation (nur /owner). Labels dürfen zweizeilig werden. */
  .bottomnav {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: var(--z-thumbbar);
    --focus-halo: var(--tile);
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    background: var(--tile);
    border-block-start: var(--joint) solid var(--ink);
    padding-block-end: env(safe-area-inset-bottom, 0px);
  }
  .bottomnav__item {
    display: grid;
    place-items: center;
    gap: 2px;
    min-block-size: var(--tap-lg);
    padding: var(--sp-2) var(--sp-1);
    color: var(--ink-2);
    text-decoration: none;
    font-size: var(--step--2);
    font-weight: 600;
    text-align: center;
    border-block-start: 4px solid transparent;
  }
  .bottomnav__item[aria-current] { color: var(--accent); border-block-start-color: var(--accent); }

  /* =========================================================================
     2 · PANEL — eine gesetzte Fliese
     ========================================================================= */
  .panel {
    background: var(--tile);
    border: var(--joint) solid var(--grout);
    border-radius: var(--r-edge);
    padding: var(--sp-4);
  }
  @media (min-width: 40rem) { .panel { padding: var(--sp-5); } }

  /* Erhebung wird ausgegeben, nicht gestempelt: MAX. ZWEI pro Screen.
     /p → „Ahora“. /owner → „Esta semana“. Website → Lizenz-Streifen. */
  .panel--lift { box-shadow: var(--lift); }

  /* Die EINZIGE getönte Fläche im System: „Tu parte“. Trägt Ton + 6px-Schiene
     und bekommt deshalb bewusst KEINEN Lift — der harte Versatz markiert das
     eine Element, das „was passiert gerade“ beantwortet.
     Diese Exklusivität ist tragend: adoptiert ein zweiter Abschnitt
     --accent-soft, kollabiert die Hierarchie und „Tu parte“ ist nicht mehr
     der eine Ort mit Knöpfen. Das ist eine Regel, keine Konvention. */
  .panel--mine {
    background: var(--accent-soft);
    border-color: var(--accent);
    border-inline-start-width: 6px;
    --focus-halo: var(--accent-soft);
  }

  .panel--sunk { background: var(--tile-sunk); border-color: var(--grout-deep); }
  .panel--flush { padding: 0; }

  /* =========================================================================
     3 · LABELS
     .eyebrow    = allgemeines Abschnittslabel, --f-body (Invariante 4)
     .data-label = betitelt unmittelbar eine Zahl → --f-data
     ========================================================================= */
  .eyebrow {
    font-family: var(--f-body);
    font-weight: 600;
    font-size: var(--step--2);
    line-height: 1.25;
    text-transform: uppercase;
    letter-spacing: var(--track-label);
    color: var(--ink-2);
    max-inline-size: none;
  }
  .data-label {
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: var(--track-cap);
    color: var(--ink-2);
    max-inline-size: none;
  }

  /* Abschnittsregel unter einer Überschrift. */
  .rule {
    block-size: var(--joint);
    background: var(--accent-line);
    border: 0;
    margin: 0;
  }

  /* =========================================================================
     4 · STATUS-CHIP — die Pille bedeutet AUSSCHLIESSLICH Zustand
     Drei unabhängige Kanäle: Glyph (clip-path), Wort, Farbe. Liest damit auf
     einem ausgewaschenen Display und für farbfehlsichtige Nutzer.
     Das Dreieck ist für --crit RESERVIERT; „estimado“ bekommt einen neutralen
     Balken, damit neben „Semana del 6 de octubre“ kein Warnzeichen steht.
     Inhaltsbreit, umbruchfähig: „en espera de la asociación“ darf umbrechen.
     ========================================================================= */
  .chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    min-block-size: 28px;
    padding: 2px 12px;
    border: var(--joint) solid currentColor;
    border-radius: var(--r-chip);
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    line-height: 1.25;
    letter-spacing: 0.02em;
    white-space: normal;
  }
  .chip::before {
    content: "";
    inline-size: 8px;
    block-size: 8px;
    flex: none;
    background: currentColor;
  }
  .chip--firm { color: var(--ok);   background: var(--ok-fill); }
  .chip--firm::before { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }   /* Raute */

  .chip--est  { color: var(--warn); background: var(--warn-fill); }
  .chip--est::before  { clip-path: polygon(0 28%, 100% 28%, 100% 72%, 0 72%); }   /* Balken */

  .chip--wait { color: var(--wait); background: var(--wait-fill); }
  .chip--wait::before { border-radius: var(--r-chip); }                            /* Punkt */

  .chip--crit { color: var(--crit); background: var(--crit-fill); }
  .chip--crit::before { clip-path: polygon(50% 0, 100% 100%, 0 100%); }            /* Dreieck */

  .chip--neutral { color: var(--ink-2); background: var(--tile-sunk); }
  .chip--neutral::before { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }   /* Quadrat */

  /* =========================================================================
     5 · EIGENTÜMER-BADGE — „wer ist dran“
     Quadratisch, NIE eine Pille: es ist kein Zustand, sondern eine Zuordnung.
     Dasselbe Bauteil in /p und /owner, damit Kunde und Contractor ein
     Vokabular teilen. Vollständiger Satz, nicht dekorativ:
     Tú · PMA · La ciudad · HOA · Proveedor.
     Das ist das billigste Vertrauensmittel im Produkt: es macht aus
     „es passiert nichts“ ein „ich bin nicht dran“.
     ========================================================================= */
  .whose {
    display: inline-block;
    padding: 2px 8px;
    border: var(--joint) solid currentColor;
    border-radius: var(--r-edge);
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: var(--track-cap);
    color: var(--ink-2);
    white-space: nowrap;
  }
  /* „Tú“ trägt den Akzent, NICHT --crit. Die Aufgabe der Kundin ist eine
     Einladung, kein Fehler. Rot auf ihrer eigenen To-do-Zeile liest sich
     als Schuldzuweisung und verbrennt zugleich die crit-Semantik. */
  .whose--you    { color: var(--accent); border-color: var(--accent); }
  .whose--pma    { color: var(--ink-2); }
  .whose--city,
  .whose--hoa,
  .whose--vendor { color: var(--wait); border-color: var(--wait); }

  /* =========================================================================
     6 · BUTTONS
     `.btn` ist per Default die LEISE Variante. Die gefüllte Primäraktion ist
     eine bewusste Entscheidung — pro Viewport genau eine, sonst stapelt sich
     der dunkle Grünspan zu einer Platte (Website-Hero, §9.8).
     Geometrie: grid + place-items + min-block-size, nie feste Breite.
     „Aprobar presupuesto“ ist 42 % länger als „Approve quote“ und muss
     umbrechen, nicht abgeschnitten werden.
     ========================================================================= */
  .btn {
    display: grid;
    place-items: center;
    text-align: center;
    text-wrap: balance;
    min-block-size: var(--tap);
    padding: var(--sp-2) var(--sp-5);
    background: var(--tile);
    color: var(--ink);
    border: var(--joint) solid var(--line-control);
    border-radius: var(--r-edge);
    font-family: var(--f-display);
    font-weight: 800;              /* Archivo existiert nur in 800. */
    font-size: var(--step-0);
    line-height: 1.2;
    letter-spacing: 0.005em;
    text-decoration: none;
    cursor: pointer;
    position: relative;
    transition: transform var(--dur-press) ease, box-shadow var(--dur-press) ease,
                background-color var(--dur-press) ease;
  }
  /* Die eine Mikrointeraktion im System: der Knopf hebt sich in seinen Lift
     und setzt sich beim Drücken in den Estrich. */
  .btn:hover  { box-shadow: var(--lift); transform: translate(-1px, -1px); }
  .btn:active { box-shadow: none;        transform: translate(1px, 1px); }

  .btn--primary {
    background: var(--accent);
    color: var(--accent-ink);
    border-color: var(--accent);
  }
  .btn--danger {
    background: var(--crit);
    color: var(--crit-ink);
    border-color: var(--crit);
  }
  /* Ghost: kein Rahmen, nur Text — ausschließlich innerhalb eines Blocks,
     der bereits eine Grenze hat (Tabellenzelle, Karte). */
  .btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--accent);
    padding-inline: var(--sp-3);
  }
  .btn--ghost:hover { background: var(--tile-sunk); box-shadow: none; transform: none; }

  .btn--sm  { min-block-size: 40px; padding: var(--sp-1) var(--sp-3); font-size: var(--step--1); }
  .btn--lg  { min-block-size: var(--tap-lg); font-size: var(--step-1); }
  .btn--block { inline-size: 100%; }

  .btn[disabled],
  .btn[aria-disabled="true"] {
    opacity: .55;
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
  }
  /* Ladezustand. hx-disabled-elt="this" setzt disabled, aria-busy kommt aus
     dem Template. Doppelte Freigabe von $350.00 auf schlechtem LTE ist ein
     echter Bug — jedes mutierende Control trägt hx-disabled-elt. */
  .btn[aria-busy="true"] { cursor: progress; opacity: .8; pointer-events: none; }
  .btn[aria-busy="true"]::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    block-size: 3px;
    background: currentColor;
    opacity: .5;
    transform-origin: left center;
    animation: junta-progress 1.1s linear infinite;
  }
  @keyframes junta-progress {
    0%   { transform: scaleX(.05); }
    50%  { transform: scaleX(.7); }
    100% { transform: scaleX(1); }
  }

  /* Kleine Icon-Controls erreichen 48px, ohne die sichtbare Box aufzublähen. */
  .tap--icon { position: relative; }
  .tap--icon::before { content: ""; position: absolute; inset: -12px; }

  /* Sprachumschalter ES/EN — ein echtes Bedienelement, kein Link. */
  .langswitch { display: flex; border: var(--joint) solid var(--line-control); border-radius: var(--r-edge); }
  .langswitch button {
    min-block-size: var(--tap);
    min-inline-size: var(--tap);
    padding-inline: var(--sp-3);
    border: 0;
    background: var(--tile);
    color: var(--ink-2);
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--1);
    cursor: pointer;
  }
  .langswitch button + button { border-inline-start: var(--joint) solid var(--line-control); }
  .langswitch button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }

  /* Sonnenmodus-Schalter. Sitzt dauerhaft im /owner-Kopf, postet per HTMX,
     der Server setzt ein Cookie und Jinja2 stempelt data-mode auf <html>. */
  .sunbtn {
    display: grid;
    place-items: center;
    min-block-size: var(--tap);
    min-inline-size: var(--tap);
    padding-inline: var(--sp-3);
    background: var(--tile);
    color: var(--ink);
    border: var(--joint) solid var(--line-control);
    border-radius: var(--r-edge);
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    text-transform: uppercase;
    letter-spacing: var(--track-cap);
    cursor: pointer;
  }
  .sunbtn[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

  /* =========================================================================
     7 · FORMULARE
     Markup: .field > (label.field__label + .input + .field__hint + .field__err)
     Fehler werden über aria-invalid="true" + aria-describedby gesteuert;
     :user-invalid ist nur progressive Verbesserung.
     ========================================================================= */
  .form  { display: grid; gap: var(--sp-5); }
  .field { display: grid; gap: var(--sp-2); }

  .field__label { font-weight: 600; line-height: var(--lh-tight); }
  .field__label .req { color: var(--crit); font-weight: 600; }

  .field__hint { font-size: var(--step--1); color: var(--ink-2); max-inline-size: var(--measure); }

  .field__err {
    display: none;
    font-size: var(--step--1);
    font-weight: 600;
    color: var(--crit);
    max-inline-size: var(--measure);
  }
  .field:has([aria-invalid="true"]) .field__err { display: block; }

  .input {
    inline-size: 100%;
    min-block-size: var(--tap);
    padding: var(--sp-2) var(--sp-3);
    background: var(--tile-sunk);
    color: var(--ink);
    border: var(--joint) solid var(--line-control);
    border-radius: var(--r-edge);
    font-family: var(--f-body);
    font-size: var(--step-0);
    line-height: var(--lh-tight);
  }
  .input::placeholder { color: var(--ink-2); opacity: 1; }
  .input:hover { border-color: var(--ink-2); }
  textarea.input { min-block-size: calc(var(--tap) * 2.5); line-height: var(--lh-body); }

  /* Beträge, Telefonnummern, Permit-Nummern: Daten → Mono. */
  .input--data { font-family: var(--f-data); font-variant-numeric: tabular-nums; }

  .input--select {
    appearance: none;
    padding-inline-end: var(--sp-6);
    background-image:
      linear-gradient(45deg, transparent 50%, currentColor 50%),
      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position:
      right var(--sp-4) center,
      calc(100% - var(--sp-4) + 6px) center;
    background-size: 6px 6px, 6px 6px;
    background-repeat: no-repeat;
  }

  .input[aria-invalid="true"] { border-color: var(--crit); border-width: 3px; }
  .input:user-invalid          { border-color: var(--crit); border-width: 3px; }
  .input[data-state="ok"]      { border-color: var(--ok); }

  /* Honeypot des Kontaktformulars (§9.8). Nie display:none — Bots lesen das. */
  .hp { position: absolute; inset-inline-start: -9999px; inline-size: 1px; block-size: 1px; overflow: hidden; }

  /* Auswahl-Chips: „Todo del cliente“ / „Todo mío“ / „Dividir“,
     Art der Arbeit im Kontaktformular. Das Label ist das Control. */
  .optset { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
  .opt {
    display: grid;
    place-items: center;
    min-block-size: var(--tap);
    padding: var(--sp-2) var(--sp-4);
    background: var(--tile);
    border: var(--joint) solid var(--line-control);
    border-radius: var(--r-edge);
    font-weight: 600;
    cursor: pointer;
    text-align: center;
  }
  .opt input { position: absolute; opacity: 0; pointer-events: none; }
  .opt:has(input:checked) { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
  .opt:has(input:focus-visible) {
    outline: 3px solid var(--focus);
    outline-offset: 2px;
    box-shadow: 0 0 0 6px var(--focus-halo);
  }
  /* Ohne :has()-Unterstützung bleibt der Chip sichtbar und bedienbar;
     der Zustand liest dann über den nativen Radio-Fokus. */

  /* Checkbox/Radio in Listen (Punch-Liste, Sichtbarkeits-Toggles). */
  .check {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    min-block-size: var(--tap);
    padding-block: var(--sp-2);
    cursor: pointer;
  }
  .check input { inline-size: 24px; block-size: 24px; flex: none; margin-block-start: 2px; }

  /* =========================================================================
     8 · DINERO — KONTOAUSZUG, KEIN DASHBOARD
     Kein <table>: bei 375px müsste eine vierspaltige Tabelle seitwärts
     scrollen, und ein Kontoauszug, den man auf dem Telefon schieben muss,
     ist keiner. Rasterzeilen mit grid-template-areas: die Quellzeile
     („08 sep · PMA pagó con la tarjeta de la empresa · recibo“) läuft unter
     der Zeile über die volle Breite, Betrag und laufender Saldo bleiben in
     eigenen Spalten baseline-ausgerichtet und können nie vom Rand fallen.
     Ein Auszug hat Endlinien (2px --ink-2 oben und unten); eine Karte nicht.
     Markup: <ol class="stmt"> <li class="stmt__row stmt__row--out"> …
     ========================================================================= */
  .stmt {
    display: grid;
    border-block-start: var(--joint) solid var(--ink-2);
    font-variant-numeric: tabular-nums;
  }
  .stmt__row {
    display: grid;
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "what amt bal"
      "meta meta meta";
    column-gap: var(--sp-3);
    align-items: baseline;
    row-gap: var(--sp-1);
    padding-block: var(--sp-3);
    padding-inline-start: var(--sp-3);
    border-block-end: 1px solid var(--grout);
    border-inline-start: 4px solid transparent;
    max-inline-size: none;
  }
  .stmt__row:last-child { border-block-end: var(--joint) solid var(--ink-2); }
  /* Richtung trägt Vorzeichen UND Streifen — nie Farbton allein. */
  .stmt__row--in  { border-inline-start-color: var(--ok); }
  .stmt__row--out { border-inline-start-color: var(--grout-deep); }

  .stmt__what { grid-area: what; font-weight: 600; overflow-wrap: anywhere; }
  /* Die Methodenzeile sagt in WORTEN, was passiert ist. Ein blankes
     Minuszeichen darf nie die einzige Erklärung dafür sein, dass Geld der
     Kundin das Konto verlassen hat. */
  .stmt__meta {
    grid-area: meta;
    font-family: var(--f-data);
    font-size: var(--step--1);
    color: var(--ink-2);
    line-height: var(--lh-data);
  }
  .stmt__amt {
    grid-area: amt;
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step-0);
    text-align: end;
    white-space: nowrap;
  }
  .stmt__row--in .stmt__amt { color: var(--ok); }
  .stmt__bal {
    grid-area: bal;
    font-family: var(--f-data);
    font-size: var(--step--1);
    color: var(--ink-2);
    text-align: end;
    white-space: nowrap;
    min-inline-size: 6ch;
  }
  /* Beleg-Link: 12px Mono wäre sonst ein 16px-Ziel. */
  .stmt__receipt {
    display: inline-block;
    position: relative;
    margin-block-start: var(--sp-1);
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    color: var(--accent);
  }
  .stmt__receipt::before { content: ""; position: absolute; inset: -14px -12px; }

  .stmt__total {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2) var(--sp-4);
    padding-block: var(--sp-4);
    border-block-end: var(--joint) solid var(--ink-2);
    font-size: var(--step-1);
    font-weight: 600;
  }
  .stmt__total b { font-family: var(--f-data); font-variant-numeric: tabular-nums; }

  /* Ab 40rem darf die Quellzeile in eine eigene Spalte — mehr Platz, gleiche
     Bedeutung. Kein neues Bauteil, nur eine Spaltenzuweisung. */
  @media (min-width: 40rem) {
    .stmt__row {
      grid-template-columns: 8ch 1fr auto auto;
      grid-template-areas: "meta what amt bal";
      align-items: baseline;
    }
    .stmt__meta { font-size: var(--step--1); }
  }

  /* --- Kennzahlenstreifen: Contrato / Pagado / Pendiente / Depósito ------- */
  .stats {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--joint);
    background: var(--grout);
    border: var(--joint) solid var(--grout);
    border-radius: var(--r-edge);
  }
  @media (min-width: 40rem) { .stats { grid-template-columns: repeat(4, 1fr); } }
  .stat { background: var(--tile); padding: var(--sp-3); display: grid; gap: 2px; }
  .stat__l {
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    text-transform: uppercase;
    letter-spacing: var(--track-cap);
    color: var(--ink-2);
  }
  .stat__v {
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step-1);
    line-height: var(--lh-tight);
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
  }

  /* --- Saldo-Block: die Antwort auf „wie viel von meinem Geld ist noch da“
     in EINEM Objekt. Zahl in Mono, daneben der Satz, der die nächste
     Forderung und ihr Datum benennt. Bei negativem Deposit steht hier
     „PMA adelantó $420.00 por ti“ — nie „−$420“ (SYSTEM-DESIGN §6.1.5). */
  .balance {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-4);
    background: var(--tile-sunk);
    border-inline-start: 6px solid var(--accent);
    border-radius: 0 var(--r-edge) var(--r-edge) 0;
  }
  .balance--advanced { border-inline-start-color: var(--warn); }
  .balance__fig {
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step-2);
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
  }
  .balance__note { font-size: var(--step--1); color: var(--ink-2); }

  /* =========================================================================
     9 · MEILENSTEINE UND DRIFT — die Schnurgerüst-Schiene
     Ein Fliesenleger schlägt eine Linie und markiert die Reihen, bevor er
     legt. Meilensteinliste und Drift-Log laufen gegen eine 2px-Vertikale mit
     14px-Tick je Eintrag. Es ist die einzige Textur im System und sie misst
     etwas Reales: ein Tick = ein protokollierter Planwechsel.
     ========================================================================= */
  .rail {
    display: grid;
    gap: var(--sp-4);
    position: relative;
    padding-inline-start: var(--sp-6);
    margin: 0;
  }
  .rail::before {
    content: "";
    position: absolute;
    inset-block: 6px;
    inset-inline-start: 7px;
    inline-size: var(--joint);
    background: var(--grout-deep);
  }
  .rail > li {
    position: relative;
    display: grid;
    gap: var(--sp-1);
    justify-items: start;
    max-inline-size: none;
  }
  .rail > li::before {
    content: "";
    position: absolute;
    inset-block-start: 0.72em;
    inset-inline-start: calc(-1 * var(--sp-6) + 1px);
    inline-size: 14px;
    block-size: var(--joint);
    background: var(--accent-line);
  }

  /* Meilensteinliste: Wochenfenster + Chip + Abhängigkeit als Satz. */
  .mile__when {
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step-0);
    letter-spacing: -0.01em;
  }
  .mile__name { font-weight: 600; }

  /* Drift-Delta. Gute Nachricht ist nie gelb: --warn nur bei Verzug. */
  .drift__delta {
    font-family: var(--f-data);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--ink-2);
  }
  .drift__delta[data-dir="up"]   { color: var(--warn); }   /* +1 semana   */
  .drift__delta[data-dir="down"] { color: var(--ok); }     /* −2 días     */
  .drift__delta[data-dir="none"] { color: var(--ink-2); }  /* sin cambio  */
  .drift__when { font-family: var(--f-data); font-size: var(--step--1); color: var(--ink-2); }

  /* =========================================================================
     10 · PROJEKTZEILE — ein Bauteil, drei Flächen (Container-Query)
     Der Statusstreifen ist NIE allein: jede Zeile trägt zusätzlich einen
     .chip mit dem Wort (bloqueado / en espera / en marcha) und einen
     .whose-Badge. Auf /owner ist das der Zwei-Sekunden-Blick.
     ========================================================================= */
  .row {
    display: grid;
    gap: var(--sp-2);
    min-block-size: var(--tap);
    padding: var(--sp-3);
    background: var(--tile);
    border-block-end: var(--joint) solid var(--grout);
    border-inline-start: 8px solid var(--sev, var(--grout-deep));
    color: inherit;
    text-decoration: none;
    /* Schriftgröße folgt der KARTE, nicht dem Viewport. */
    font-size: clamp(1rem, 0.62rem + 1.9cqi, 1.1875rem);
  }
  .row[data-sev="crit"] { --sev: var(--crit); }
  .row[data-sev="warn"] { --sev: var(--warn); }
  .row[data-sev="ok"]   { --sev: var(--ok); }
  .row[data-sev="wait"] { --sev: var(--wait); }
  .row:hover { background: var(--tile-sunk); }

  .row__title { font-weight: 600; line-height: var(--lh-tight); }
  .row__meta  { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
  .row__when  { font-family: var(--f-data); font-size: var(--step--1); color: var(--ink-2); }

  @container (min-width: 34rem) {
    .row { grid-template-columns: 1fr auto; align-items: center; column-gap: var(--sp-4); }
  }

  /* =========================================================================
     11 · „LO QUE ANOTÉ HOY“ — die Gegenkontrolle zum autonomen Buchen
     Rückwärts-chronologisch. Je Zeile: Uhrzeit (Mono), Projekt, Werkzeug in
     Klartext, gebuchte Zahlen (Mono), Resolver-Score, Ein-Tap-Deshacer.
     Nach 24 h ist Deshacer grau mit dem Grund „pasaron 24 h“.
     SYSTEM-DESIGN §9.3/§9.7 — ohne diese Liste darf das autonome Modell
     nicht live gehen.
     ========================================================================= */
  .today { display: grid; }
  .today__row {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "time what"
      "time nums"
      "time act";
    column-gap: var(--sp-3);
    row-gap: var(--sp-1);
    padding-block: var(--sp-3);
    padding-inline-start: var(--sp-3);
    border-block-end: var(--joint) solid var(--grout);
    border-inline-start: 4px solid var(--accent-line);
    max-inline-size: none;
  }
  .today__row[data-undone="true"] { border-inline-start-color: var(--grout-deep); opacity: .7; }
  .today__time {
    grid-area: time;
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--1);
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
  }
  .today__what { grid-area: what; font-weight: 600; }
  .today__nums { grid-area: nums; font-family: var(--f-data); font-size: var(--step--1); }
  .today__act  { grid-area: act; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
  /* Resolver-Score / Beleg-Konfidenz: prüfbare Zahl → Mono. */
  .today__score { font-family: var(--f-data); font-size: var(--step--2); color: var(--ink-2); }
  .today__reason { font-size: var(--step--2); color: var(--ink-2); }
  @media (min-width: 40rem) {
    .today__row {
      grid-template-columns: auto 1fr auto;
      grid-template-areas:
        "time what act"
        "time nums act";
      align-items: start;
    }
  }

  /* =========================================================================
     12 · TABS — inhaltsbreite Pillen? NEIN. Die Pille ist Status.
     Tabs sind rechteckige Ziele mit einer 4px-Unterkante. Horizontal
     scrollbar mit Scroll-Snap, nie ein 7-Spalten-Raster: „Presupuesto“,
     „Materiales“ und „Fotos“ haben sehr unterschiedliche Längen.
     ========================================================================= */
  .tabs {
    display: flex;
    gap: 0;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-inline: contain;
    scrollbar-width: thin;
    background: var(--tile);
    border-block-end: var(--joint) solid var(--grout);
    position: sticky;
    top: calc(env(safe-area-inset-top, 0px) + var(--tap));
    z-index: var(--z-raised);
  }
  .tabs__tab {
    flex: none;
    scroll-snap-align: start;
    display: grid;
    place-items: center;
    min-block-size: var(--tap);
    padding-inline: var(--sp-4);
    background: transparent;
    border: 0;
    border-block-end: 4px solid transparent;
    color: var(--ink-2);
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
  }
  .tabs__tab[aria-selected="true"],
  .tabs__tab[aria-current="page"] { color: var(--ink); border-block-end-color: var(--accent); }

  /* =========================================================================
     13 · TABELLEN — nur /ops. Dichte ist dort der Zweck, deshalb ist das die
     einzige Stelle im System, an der 1px-Linien erlaubt sind.
     ========================================================================= */
  .table-scroll { overflow-x: auto; overscroll-behavior-inline: contain; }
  .table {
    inline-size: 100%;
    border-collapse: collapse;
    font-size: var(--step--1);
    font-variant-numeric: tabular-nums;
  }
  .table th {
    position: sticky;
    top: 0;
    z-index: var(--z-raised);
    text-align: start;
    padding: var(--sp-2) var(--sp-3);
    background: var(--tile);
    font-family: var(--f-data);
    font-weight: 600;
    font-size: var(--step--2);
    text-transform: uppercase;
    letter-spacing: var(--track-cap);
    color: var(--ink-2);
    border-block-end: var(--joint) solid var(--ink-2);
    white-space: nowrap;
  }
  .table td {
    padding: var(--sp-2) var(--sp-3);
    border-block-end: 1px solid var(--grout);
    vertical-align: top;
  }
  .table tbody tr:hover { background: var(--tile-sunk); }
  .table .num { text-align: end; font-family: var(--f-data); white-space: nowrap; }
  .table .mono { font-family: var(--f-data); }
  .table--dense td { padding-block: var(--sp-1); }

  /* =========================================================================
     14 · FOTOS — ein Foto ist ein Rechteck als Beweis, nie gerundet.
     Vorher/Nachher: die Trennlinie IST eine Fugenlinie. Begriff und
     Mechanismus sind dasselbe Objekt.
     ========================================================================= */
  .pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--joint);
    background: var(--grout-deep);
    border: var(--joint) solid var(--grout-deep);
  }
  .pair figure { margin: 0; background: var(--tile); }
  .pair img { aspect-ratio: 4 / 3; object-fit: cover; inline-size: 100%; }
  /* Bildunterschrift steht UNTER dem Bild, nie darauf. Und sie ist kein
     Datum und keine Zahl → --f-body, nicht Mono (Invariante 4). */
  .pair figcaption {
    padding: var(--sp-2) var(--sp-3);
    font-weight: 600;
    font-size: var(--step--2);
    text-transform: uppercase;
    letter-spacing: var(--track-label);
    color: var(--ink-2);
  }

  .shots {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--joint);
    background: var(--grout-deep);
    border: var(--joint) solid var(--grout-deep);
  }
  .shots figure { margin: 0; background: var(--tile); }
  .shots img { aspect-ratio: 1; object-fit: cover; inline-size: 100%; }

  /* LQIP: ein Terrazzo-Feld statt eines grauen Kastens. Null Bytes, null
     Requests. REGEL: nur als Platzhalter unter einem echten <img>, nie als
     Ruhezustand einer Galerie. Hat ein Projekt weniger als zwei echte
     Vorher/Nachher-Paare, wird GAR KEINE Galerie gerendert — ein Platzhalter,
     den man zweimal sieht, liest sich als kaputtes Produkt. */
  .shot-lqip {
    aspect-ratio: 4 / 3;
    max-inline-size: 100%;
    background:
      radial-gradient(circle at 18% 28%, var(--terrazzo-chip) 0 5px, transparent 6px),
      radial-gradient(circle at 62% 14%, var(--terrazzo-tint) 0 4px, transparent 5px),
      radial-gradient(circle at 81% 63%, var(--terrazzo-chip) 0 6px, transparent 7px),
      radial-gradient(circle at 34% 78%, var(--terrazzo-tint) 0 3px, transparent 4px),
      radial-gradient(circle at  8% 88%, var(--terrazzo-chip) 0 4px, transparent 5px),
      var(--terrazzo-base);
    background-size: 78px 72px, 78px 72px, 78px 72px, 78px 72px, 78px 72px, auto;
  }

  /* Text über einem Foto bekommt IMMER ein deckendes Panel, nie einen
     transluzenten Verlauf — sonst bricht der Kontrast still. */
  .photo-band { position: relative; display: grid; }
  .photo-band > img { grid-area: 1 / 1; inline-size: 100%; object-fit: cover; }
  .photo-band__panel {
    grid-area: 1 / 1;
    align-self: end;
    justify-self: start;
    margin: var(--sp-4);
    padding: var(--sp-4);
    background: var(--tile);
    border: var(--joint) solid var(--grout);
    max-inline-size: 34rem;
  }

  /* =========================================================================
     15 · RÜCKMELDUNG — Toast, Sheet, Skeleton, HTMX
     ========================================================================= */
  .toasts {
    position: fixed;
    inset-inline: 0;
    bottom: calc(var(--tap-lg) + var(--sp-5) + env(safe-area-inset-bottom, 0px));
    z-index: var(--z-toast);
    display: grid;
    gap: var(--sp-2);
    justify-items: center;
    padding-inline: var(--gutter);
    pointer-events: none;
  }
  .toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    inline-size: 100%;
    max-inline-size: 34rem;
    padding: var(--sp-3) var(--sp-4);
    background: var(--tile);
    color: var(--ink);
    border: var(--joint) solid var(--ink);
    border-inline-start-width: 6px;
    border-radius: var(--r-edge);
    box-shadow: var(--lift);
  }
  .toast--ok   { border-inline-start-color: var(--ok); }
  .toast--warn { border-inline-start-color: var(--warn); }
  .toast--crit { border-inline-start-color: var(--crit); }

  /* Sheet / Modal auf <dialog>. Kommt von unten (Daumenzone), nie mittig
     eingeblendet. Kein Blur im Backdrop. */
  .sheet {
    margin: 0;
    margin-block-start: auto;
    inline-size: 100%;
    max-inline-size: 46rem;
    max-block-size: 90dvh;
    padding: 0;
    background: var(--tile);
    color: var(--ink);
    border: var(--joint) solid var(--ink);
    border-radius: var(--r-edge) var(--r-edge) 0 0;
  }
  @media (min-width: 46rem) { .sheet { margin: auto; border-radius: var(--r-edge); } }
  .sheet::backdrop { background: rgb(0 0 0 / .55); }
  .sheet__panel {
    display: grid;
    gap: var(--sp-4);
    padding: var(--sp-5) var(--gutter) calc(var(--sp-5) + env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
  }
  .sheet__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-3);
  }
  @media (prefers-reduced-motion: no-preference) {
    .sheet[open] { animation: junta-rise var(--dur-ui) var(--ease); }
    @keyframes junta-rise { from { transform: translateY(12px); } to { transform: none; } }
  }

  /* Skeleton: zwei Töne, kein Blur, keine Transluzenz. */
  .skel {
    background: var(--tile-sunk);
    border-radius: var(--r-edge);
    min-block-size: 1em;
  }
  .skel--line   { block-size: 1em; margin-block: 0.27em; }
  .skel--line-2 { inline-size: 72%; }
  .skel--line-3 { inline-size: 48%; }
  .skel--block  { block-size: 6rem; }
  @media (prefers-reduced-motion: no-preference) {
    .skel {
      background-image: linear-gradient(90deg, transparent 0 30%, var(--grout) 50%, transparent 70% 100%);
      background-size: 240% 100%;
      animation: junta-sweep 1.4s linear infinite;
    }
    @keyframes junta-sweep { from { background-position: 120% 0; } to { background-position: -120% 0; } }
  }

  /* HTMX. Unter 180ms blitzt gar nichts — ein Spinner, der 60ms aufleuchtet,
     liest sich als Ruckeln, nicht als Tempo. */
  .htmx-indicator { opacity: 0; transition: opacity 120ms ease 180ms; }
  .htmx-request .htmx-indicator,
  .htmx-request.htmx-indicator { opacity: 1; transition-delay: 0ms; }
  [aria-busy="true"] { cursor: progress; }
  /* Swap-Ziel während des Austauschs: nur Deckkraft, keine Bewegung. */
  .htmx-swapping { opacity: .55; }

  /* Leerzustände. Terrazzo genau einmal pro Fläche. */
  .empty {
    display: grid;
    gap: var(--sp-3);
    justify-items: start;
    padding: var(--sp-5) var(--sp-4);
    background: var(--tile-sunk);
    border: var(--joint) solid var(--grout);
    border-radius: var(--r-edge);
  }
}

@layer utilities {
  .u-data    { font-family: var(--f-data); font-variant-numeric: tabular-nums; }
  .u-muted   { color: var(--ink-2); }
  .u-nowrap  { white-space: nowrap; }
  .u-measure { max-inline-size: var(--measure); }
  .u-full    { inline-size: 100%; }
  .u-start   { justify-self: start; }
  .u-end     { justify-self: end; }
  .u-hide    { display: none !important; }
  .u-sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  /* Sichtbar nur bei Tastaturfokus — der Skip-Link im Seitenkopf. */
  .u-skip:not(:focus) {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .u-skip {
    position: absolute;
    z-index: var(--z-toast);
    margin: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    background: var(--tile);
    border: var(--joint) solid var(--ink);
  }
}
