/* ============================================================
   DS atomic catalog — styles for Components/Ds/* and the
   /_dev/ds gallery page.

   Loaded via a plain <link> in index.html (not Blazor scoped CSS)
   because scoped CSS emission in this WASM project has been
   unreliable — see PersonBalanceRow.razor.css comment.
   ============================================================ */


/* ---------- EtButton ---------- */
.et-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    border: 0;
    border-radius: 999px;
    font-family: var(--et-font-family-base);
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    transition: transform .14s, filter .14s, box-shadow .14s;
}
/* The Href form (EtButton): a link in the button's dress. */
a.et-btn { text-decoration: none; }
.et-btn:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Design stateMap: disabled drops every shadow (incl. outline/danger inset rings). */
.et-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    box-shadow: none;
}
/* box-sizing is load-bearing here and nowhere else on .et-btn: this project
   has no global border-box (see app.css), and a <button> is border-box by UA
   default while an <a> is not. EtButton's Href form is an <a>, and a block one
   would otherwise render 100% + padding wide and overflow its container. */
.et-btn--block { display: flex; width: 100%; box-sizing: border-box; }

/* Sizes — md padding is per-variant in the dc map:
   tonal/outline 11px 18px, ghost 10px 14px, all others 12px 20px.
   sm is always 9px 14px at 12.5px. */
.et-btn--md { padding: 12px 20px; font-size: 14px; }
.et-btn--sm { padding: 9px 14px;  font-size: 12.5px; }
.et-btn--md.et-btn--tonal   { padding: 11px 18px; }
.et-btn--md.et-btn--outline { padding: 11px 18px; }
.et-btn--md.et-btn--ghost   { padding: 10px 14px; }

.et-btn-icon { font-size: 15px; line-height: 1; }

/* Primary — plum CTA gradient; token pair matches the design's rest/hover shadows. */
.et-btn--primary {
    background: var(--et-cta-gradient);
    color: #fff;
    box-shadow: var(--et-cta-shadow);
}
.et-btn--primary:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: var(--et-cta-shadow-hover);
}
.et-btn--primary:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: none;
}

.et-btn--secondary {
    background: var(--et-color-surface-2);
    color: var(--et-color-ink);
}
.et-btn--secondary:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}
.et-btn--secondary:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: none;
}

.et-btn--success {
    background: var(--et-color-owed);
    color: #fff;
    box-shadow: 0 4px 14px rgba(21, 118, 74, 0.30);
}
.et-btn--success:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(21, 118, 74, 0.38);
}
.et-btn--success:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: none;
}

/* Tonal — translucent white overlay fill (design literal, no token). */
.et-btn--tonal {
    background: rgba(255, 255, 255, 0.75);
    color: var(--et-color-ink);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.et-btn--tonal:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}
.et-btn--tonal:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: none;
}

.et-btn--outline {
    background: transparent;
    color: var(--et-color-accent);
    box-shadow: inset 0 0 0 1.5px var(--et-color-accent);
}
.et-btn--outline:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: inset 0 0 0 1.5px var(--et-color-accent), 0 3px 10px rgba(0, 0, 0, 0.10);
}
.et-btn--outline:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: inset 0 0 0 1.5px var(--et-color-accent);
}

.et-btn--ghost {
    background: transparent;
    color: var(--et-color-ink-2);
}
.et-btn--ghost:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
}
.et-btn--ghost:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
}

/* Dark — ink fill with surface text so the variant inverts (stays high-contrast)
   in dark mode; design literals are #1A1917 / #fff. */
.et-btn--dark {
    background: var(--et-color-ink);
    color: var(--et-color-surface);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
}
.et-btn--dark:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.26);
}
.et-btn--dark:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: none;
}

/* Danger — outlined destructive (surface fill + owe ring/text). */
.et-btn--danger {
    background: var(--et-color-surface);
    color: var(--et-color-owe);
    box-shadow: inset 0 0 0 1.5px var(--et-color-owe);
}
.et-btn--danger:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: inset 0 0 0 1.5px var(--et-color-owe), 0 3px 10px rgba(196, 57, 45, 0.12);
}
.et-btn--danger:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: inset 0 0 0 1.5px var(--et-color-owe);
}

/* Danger-solid — hover shadow is the dc script's neutral fallthrough
   (0 3px 10px black), not a red one; replicated faithfully. */
.et-btn--danger-solid {
    background: var(--et-color-owe);
    color: #fff;
    box-shadow: 0 6px 16px rgba(196, 57, 45, 0.30);
}
.et-btn--danger-solid:hover:not(:disabled) {
    filter: brightness(1.03);
    transform: translateY(-1px);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}
.et-btn--danger-solid:active:not(:disabled) {
    filter: brightness(0.94);
    transform: translateY(0);
    box-shadow: none;
}


/* ---------- EtIconButton ---------- */
/* `font: inherit` is the same fix already applied to .et-group-switcher,
   .et-group-settings-back, .et-group-settings-transfer-row and .et-page-back
   in app.css: a <button> does not inherit font-family from the page the way
   a <div>/<span> does, so without this the emoji glyph in .et-icon-btn-emoji
   fell back to each platform's native UI font instead of the app's Plus
   Jakarta Sans stack — the bell rendered at a different size/baseline on
   mobile vs desktop while emoji-in-a-div elements (e.g. EtGroupCard's
   .et-group-card-icon) never had the problem. */
.et-icon-btn {
    position: relative;
    display: grid;
    place-items: center;
    border: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--et-color-surface-2);
    /* The "list" and "bell" SVGs draw with stroke/fill: currentColor (F-19) so they
       follow theme text color instead of a hard-coded hex that vanished in dark mode. */
    color: var(--et-color-ink-2);
    cursor: pointer;
    transition: background .14s, transform .14s;
    flex-shrink: 0;
    font: inherit;
}
/* Hover/active disc tints are design literals — no hover/active surface token exists yet. */
.et-icon-btn:hover:not(:disabled) { background: #E6E2D8; }
.et-icon-btn:active:not(:disabled) {
    background: #DCD7CC;
    transform: scale(0.95);
}
.et-icon-btn:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Destructive variant (e.g. the events toolbar's bulk-delete trash icon,
   GroupSettings.razor): same tint EtCrudRow's own delete glyph uses
   (.et-crud-row-delete), so an icon-only danger action reads as destructive
   without needing a text label. */
.et-icon-btn--danger { color: var(--et-color-owe); }
.et-icon-btn--danger:hover:not(:disabled) { background: #F9E5E3; } /* dc literal error tint, matches .et-crud-row-delete:hover */
/* Primary variant: the CTA disc the bottom bar's + wears, for an icon-only main action
   (the one-time kassza's name add). The base hover/active tints would grey it out. */
.et-icon-btn--primary { background: var(--et-cta-gradient); color: #fff; }
.et-icon-btn--primary:hover:not(:disabled) { background: var(--et-cta-gradient); filter: brightness(1.03); }
.et-icon-btn--primary:active:not(:disabled) { background: var(--et-cta-gradient); filter: brightness(0.94); }
.et-icon-btn-emoji { line-height: 1; }
.et-icon-btn-badge {
    position: absolute;
    top: -1px;
    right: -1px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--et-color-owe);
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    display: grid;
    place-items: center;
    /* Design says 2px solid #fff — mapped to surface so the ring tracks dark mode. */
    border: 2px solid var(--et-color-surface);
    line-height: 1;
    font-family: var(--et-font-family-base);
}


/* ---------- EtSurface (design: Card) ---------- */
/* Root class stays .et-surface — .et-card is owned by legacy app.css. */
.et-surface {
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-md);
    box-shadow: var(--et-shadow-card);
    border: 1px solid transparent;
    padding: 12px;
    transition: box-shadow .18s;
    font-family: var(--et-font-family-base);
    /* Query container for the rows inside. Atoms branch on the width of the
       card holding them rather than on the viewport, so the same row is
       correct on a phone, in a docked panel and in the group view's narrow
       right column — and the gallery specimen can show the branch instead of
       only asserting it. Inline-size containment only: the height still
       follows the content. */
    container-type: inline-size;
}
/* The padding is the card's whole inner edge: a last child's own bottom margin
   (a form block's 20px) would stack on it and leave the bottom twice the top. */
.et-surface > :last-child { margin-bottom: 0; }
/* Form variant — radius 13 kept literal (design says 13, no token). */
.et-surface--form {
    border-radius: 13px;
    box-shadow: none;
    border: 1px solid var(--et-color-border);
    padding: 4px 15px;
}
.et-surface-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 6px 8px;
}
.et-surface--form .et-surface-header { padding: 10px 0 6px; }
.et-surface-title {
    font-size: 15px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
}
.et-surface-count {
    font-size: 12px;
    font-weight: 600;
    color: var(--et-color-ink-3);
}
.et-surface-link {
    border: 0;
    background: none;
    padding: 0;
    font-family: var(--et-font-family-base);
    font-size: 12.5px;
    font-weight: 700;
    color: var(--et-color-accent);
    cursor: pointer;
}
.et-surface-link:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtAvatar ---------- */
.et-avatar {
    border-radius: 50%;
    color: #fff;
    display: grid;
    place-items: center;
    font-family: var(--et-font-family-base);
    font-weight: 600;
    flex-shrink: 0;
    position: relative;
    overflow: hidden;
}
/* Uploaded profile picture overlay (spec §2.2) — sits above the initial so
   the initial stays as the fallback while the image loads or on error. */
.et-avatar-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
}
/* Tone gradients are fixed identity colors (dc values verbatim) — kept
   literal so avatars stay recognizable across palettes and dark mode. */
.et-avatar--plum    { background: linear-gradient(135deg, #9c6dab, #7A4E7A); }
.et-avatar--rose    { background: linear-gradient(135deg, #e088b8, #c94f95); }
.et-avatar--green   { background: linear-gradient(135deg, #5cb985, #2e8b5c); }
.et-avatar--blue    { background: linear-gradient(135deg, #5b9bd5, #2e6db5); }
.et-avatar--mustard { background: linear-gradient(135deg, #f0b362, #d68f2e); }
/* Sand — flat neutral for unbound gift recipients. Identity tone like the
   gradients above, so bg #ECEAE3 / fg #8F8C83 stay literal (dc values). */
.et-avatar--sand    { background: #ECEAE3; color: #8F8C83; }


/* ---------- V6 entitlement — shared "PRO" token (Task 6, spec §2.4) ----------
   One background/text pair for every PRO-branded surface: the header chip
   below (.et-pill--pro), the groups-card entitlement label
   (.et-group-card-entitlement further down) and the in-button CTA badge
   (.et-pro-badge in app.css). Before this round each surface picked its own
   color by urgency (red/amber/grey); the spec's 2026-08-24 revision replaced
   that with one fixed look everywhere: "one CSS token pair, no per-surface
   colors."

   Fixed literals, not swapped per [data-theme]/[data-palette] — like the
   avatar identity gradients above, "PRO" is a brand mark, not a semantic
   status a theme should be allowed to recolor. --et-color-pro-bg is the
   bell emoji's gold (🔔), picked by the owner from a reference screenshot
   (Wave 3 spec §6.1) — it no longer relates to the mustard avatar tone.
   --et-color-pro-ink is unchanged: the same fixed near-black already used
   for on-band identity text (.et-group-header-title). Contrast, computed
   from the two hex values via the WCAG relative-luminance formula
   (L = 0.2126R + 0.7152G + 0.0722B on linearized sRGB channels):
   L(#1A1917) ≈ 0.00977, L(#F6BC2F) ≈ 0.55765, ratio (0.55765+0.05) /
   (0.00977+0.05) ≈ 10.17:1 for black-on-gold — up from ~6.5:1 with the old
   #D68F2E, comfortably above the AA (4.5:1) and AAA (7:1) text thresholds. */
:root {
    --et-color-pro-bg: #F6BC2F;
    --et-color-pro-ink: #1A1917;
}

/* Wave 3 spec §6.2 wordmark split — shared by every PRO surface via
   Components/Ds/EtProLabel.razor, which wraps the literal "PRO" prefix in
   .et-pro-mark and everything after it in .et-pro-rest. The mark stays
   heavy and slightly tracked out so it reads as a badge; the rest inherits
   its container's (lighter) weight so the status text around it reads as
   plain copy, not more badge. Declared once here rather than repeated
   inside .et-pill--pro / .et-group-card-entitlement / .et-pro-badge. */
.et-pro-mark {
    font-weight: 800;
    letter-spacing: 0.04em;
}
.et-pro-rest {
    font-weight: 600;
}


/* ---------- EtPill ---------- */
.et-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border-radius: 999px;
    font-family: var(--et-font-family-base);
    white-space: nowrap;
}
.et-pill-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}
/* owed/owe backgrounds stay literal — no status-bg token for them yet. */
.et-pill--group   { background: var(--et-color-surface-2);   color: var(--et-color-ink-2);  font-weight: 600; padding: 4px 10px; font-size: 11px; }
.et-pill--owed    { background: #e7f4ec;                     color: var(--et-color-owed);   font-weight: 700; padding: 4px 11px; font-size: 12px; }
.et-pill--owe     { background: #fbebe8;                     color: var(--et-color-owe);    font-weight: 700; padding: 4px 11px; font-size: 12px; }
.et-pill--pending { background: var(--et-status-bg-pending); color: var(--et-color-pend);   font-weight: 700; padding: 4px 11px; font-size: 12px; }
.et-pill--sent    { background: var(--et-status-bg-sent);    color: var(--et-color-sent);   font-weight: 700; padding: 4px 11px; font-size: 12px; }
.et-pill--count   { background: transparent;                 color: var(--et-color-ink-3);  font-weight: 500; padding: 0;        font-size: 12px; }
.et-pill--neutral { background: var(--et-status-bg-logged);  color: var(--et-color-neutral);font-weight: 700; padding: 4px 11px; font-size: 12px; }
.et-pill--primary { background: var(--et-color-accent);      color: #fff;                   font-weight: 700; padding: 4px 11px; font-size: 12px; }
/* font-weight here is the baseline for .et-pro-rest (spec §6.2); the "PRO"
   prefix itself is bumped to 800 by .et-pro-mark, rendered via EtProLabel. */
.et-pill--pro     { background: var(--et-color-pro-bg);      color: var(--et-color-pro-ink);font-weight: 600; padding: 4px 11px; font-size: 12px; }
/* Leading check (ShowCheck) — as heavy as the PRO wordmark it sits next to. */
.et-pill-check    { font-weight: 800; line-height: 1; flex-shrink: 0; }


/* ---------- EtSectionHeader ---------- */
.et-section-header {
    display: flex;
    align-items: baseline;
    /* Wraps rather than clips/overflows when a trailing button (§5.4) plus a
       long title can't both fit on one line — the button then lands
       right-aligned on its own row below the title. */
    flex-wrap: wrap;
    row-gap: 8px;
    gap: 10px;
    margin-bottom: 12px;
    font-family: var(--et-font-family-base);
    /* No container-type here on purpose. Unlike EtGroupHeader (which owns its
       band and can safely become a container), a section header is a bare
       title row that callers drop straight into a page column — it does not
       control the box around it. It shares an ancestor with EtActivityRow:
       .et-surface (and the gallery's .ds-specimen-desktop/-phone) already set
       container-type: inline-size, so the narrow-link branch below rides
       that. A header with no such ancestor simply never matches the query
       and keeps the full label — the right default for an unknown box. */
}
.et-section-header-title {
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--et-color-ink);
}
.et-section-header-count {
    font-size: 12px;
    font-weight: 500;
    color: var(--et-color-ink-3);
}
.et-section-header-link {
    margin-left: auto;
    color: var(--et-color-accent);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    /* The whole point of the short form: once it can win, the arrow must
       never be the one glyph that wraps onto its own line. */
    white-space: nowrap;
}
/* Hover color stays literal — dc says #5B3E6E (deep plum), no token. */
.et-section-header-link:hover { color: #5B3E6E; }
.et-section-header-link:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Both labels are in the DOM; exactly one is display:none at any container
   width, so the hidden one is genuinely gone — no tab stop, no accessibility
   node, no screen reader reading "Korábbi költségek → Korábbiak →" back to
   back. Full is the one shown by default (see the no-container-type note
   above): a header outside any @container ancestor must still read in full. */
.et-section-header-link-short { display: none; }
/* Same 480px threshold as EtGroupHeader/EtActivityRow — one narrow-box
   breakpoint for the whole DS rather than three that drift apart. */
@container (max-width: 480px) {
    .et-section-header-link-full { display: none; }
    .et-section-header-link-short { display: inline; }
}
/* Trailing action slot (§5.4) — same right-anchoring as .et-section-header-link,
   but for a real control (EtButton) rather than a text link. align-items on
   the header is `baseline`, which would sink a button below the title text;
   `center` here keeps it vertically centered against the title regardless. */
.et-section-header-trailing {
    margin-left: auto;
    display: flex;
    align-items: center;
    /* Two actions can share the slot (events: bulk delete + create); on a phone
       the pair wraps instead of pushing the header sideways. */
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
}


/* ---------- EtEmptyState ---------- */
.et-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 34px 26px;
    font-family: var(--et-font-family-base);
}
/* Circle fill + inset ring are decorative — dc literals kept on purpose. */
.et-empty-circle {
    position: relative;
    width: 74px;
    height: 74px;
    margin-bottom: 16px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 40%, #F3E8F3, #EDE6EF);
    box-shadow: inset 0 0 0 1px rgba(122, 78, 122, 0.10);
}
.et-empty-emoji { font-size: 34px; line-height: 1; }
.et-empty-title {
    font-size: 16px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
    margin-bottom: 5px;
}
.et-empty-message {
    font-size: 13px;
    color: var(--et-color-ink-2);
    font-weight: 500;
    max-width: 300px;
    line-height: 1.5;
}
.et-empty-cta {
    padding: 11px 20px;
    border: 0;
    border-radius: 12px;
    background: var(--et-cta-gradient);
    color: #fff;
    font-family: var(--et-font-family-base);
    font-weight: 700;
    font-size: 13px;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(var(--et-cta-shadow-rgb), 0.32);
    transition: transform .14s, filter .14s;
}
.et-empty-cta:hover { filter: brightness(1.06); transform: translateY(-1px); }
.et-empty-cta:active { filter: brightness(0.93); transform: translateY(0); }
.et-empty-cta:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}

/* ---------- EtEmptyState — Card variant ----------
   The in-card form: full-bleed illustration left, text right. The Hero above
   spends 34px of padding and a 74px circle on its own, which is ~207px of
   surface for a two-word "Minden rendezve" — and the phone canvas pays that
   several times per scroll. This lands on 172px, the height of the EtTodoCard
   it sits beside on the desktop Home row, and drops to content height on a
   phone where nothing is beside it to match.

   Reach for it whenever the empty state sits inside a section the user is
   scrolling PAST; keep the Hero when the empty state IS the screen. */
.et-empty-text { display: contents; }
.et-empty--card {
    flex-direction: row;
    align-items: stretch;
    text-align: left;
    padding: 0;
    /* Own radius + clip so the art can bleed into the corners. A caller that
       gives its EtSurface Pad="0" gets a true full-bleed card (the two radii
       coincide); one that leaves the surface padded gets an inset tile, which
       is the right read when the empty state shares the card with other
       content. */
    border-radius: var(--et-card-radius-md);
    overflow: hidden;
    min-height: 172px;
    /* Wave 3 spec §5.5: the inset-tile form used to have no boundary of its
       own — a white block inside the (also white, shadow-only) EtSurface, so
       only the illustration's left edge read as an edge. This border makes
       the card self-evident on all four sides regardless of the caller's
       Pad. When Pad="0" the card fills the surface exactly, so this border
       sits at the same edge the surface's own shadow already implies —
       crisper, not doubled. */
    border: 1px solid var(--et-color-border);
}
.et-empty--card .et-empty-art {
    flex: 0 0 40%;
    /* The library art is 240x160; cover crops rather than letterboxes it, and
       the compositions all keep their subject centred so the crop is safe. */
    position: relative;
    min-width: 0;
}
.et-empty--card .et-empty-art img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.et-empty--card .et-empty-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    /* Flex items floor at min-content; without this a long title refuses to
       shrink and pushes the row wider than the card. */
    min-width: 0;
    padding: 16px 18px;
    gap: 2px;
}
.et-empty--card .et-empty-title { margin-bottom: 0; }
.et-empty--card .et-empty-message { max-width: none; }
/* The message div always renders so the Hero form's spacing is untouched;
   here an empty one would still spend the column gap. */
.et-empty--card .et-empty-message:empty { display: none; }
/* Every card CTA spans the text column, so a short label and a long one read as the
   same button instead of one hugging its text and the other wrapping (owner,
   2026-10-08). */
.et-empty--card .et-empty-cta { margin-top: 12px; align-self: stretch; }

/* Phone: nothing sits beside the card, so the todo-card height it was
   matching stops being a reason to be tall. The art keeps a slightly larger
   share because the whole card is narrower. */
@media (max-width: 767.98px) {
    .et-empty--card { min-height: 116px; }
    .et-empty--card .et-empty-art { flex-basis: 38%; }
    .et-empty--card .et-empty-text { padding: 14px 16px; }
}


/* ---------- EtChip ---------- */
.et-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
    border: 0;
    background: none;
    padding: 0;
    font-family: var(--et-font-family-base);
    cursor: pointer;
}
.et-chip-emoji { font-size: 13px; line-height: 1; }
.et-chip-remove { font-size: 10px; line-height: 1; opacity: .8; }
.et-chip--quickadd {
    background: var(--et-color-surface-2);
    border-radius: 999px;
    padding: 5px 11px;
    font-size: 12px;
    font-weight: 600;
    color: var(--et-color-ink);
    transition: background-color .15s;
}
.et-chip--quickadd:hover { background-color: #E6E2D8; } /* no token — cf. EtIconButton hover */
.et-chip--link {
    color: var(--et-color-accent);
    font-size: 12px;
    font-weight: 600;
    padding: 5px 6px;
}
.et-chip--suggest {
    border: 1.5px solid var(--et-color-accent);
    color: var(--et-color-accent);
    border-radius: 999px;
    padding: 6px 13px;
    font-size: 12.5px;
    font-weight: 700;
    transition: background-color .15s;
}
.et-chip--suggest:hover { background-color: rgba(122, 78, 122, 0.07); }
.et-chip--filter {
    border: 1.5px solid var(--et-color-accent);
    color: var(--et-color-accent);
    border-radius: 999px;
    padding: 7px 15px;
    font-size: 12.5px;
    font-weight: 700;
    transition: background-color .15s;
}
.et-chip--filter:hover { background-color: rgba(122, 78, 122, 0.07); }
.et-chip--filter.et-chip--active {
    background: var(--et-color-accent);
    color: #fff;
    box-shadow: 0 3px 10px rgba(122, 78, 122, 0.28);
}
.et-chip--filter.et-chip--active:hover { background-color: var(--et-color-accent); }
.et-chip:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtInfoBanner ----------
   Severity palettes are design literals (light-authored banner set,
   no semantic tokens for the tinted fg/border pairs yet). */
.et-info-banner {
    --etib-bg: #EDF2F7;
    --etib-border: #CBD9E6;
    --etib-fg: #44607A;
    background: var(--etib-bg);
    border: 1px solid var(--etib-border);
    border-radius: 14px;
    padding: 14px 16px;
    font-family: var(--et-font-family-base);
}
.et-info-banner--success { --etib-bg: #E7F4EC; --etib-border: #BFE0CC; --etib-fg: #15764A; }
.et-info-banner--warning { --etib-bg: #FBEFD7; --etib-border: #E8C877; --etib-fg: #6B5A2E; }
.et-info-banner--error   { --etib-bg: #F9E5E3; --etib-border: #E5B0A8; --etib-fg: #A63528; }
.et-info-banner-text {
    font-size: 13px;
    color: var(--etib-fg);
    line-height: 1.5;
}
/* Row so a second action (e.g. the /invite copy-link escape hatch) sits
   beside the primary one and wraps on narrow banners. */
.et-info-banner-actions {
    margin-top: 10px;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.et-info-banner-action {
    display: inline-flex;
    align-items: center;
    background: rgba(255, 255, 255, 0.65);
    border: 1px solid var(--etib-border);
    color: var(--etib-fg);
    border-radius: 999px;
    padding: 7px 14px;
    font-size: 12.5px;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    transition: background-color .15s;
}
.et-info-banner-action:hover { background-color: rgba(255, 255, 255, 0.95); }
.et-info-banner-action:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Dismissible form (OnDismiss): a round ✕ in the top-right corner, in the banner's own
   ink, and the text kept clear of it. */
.et-info-banner--dismissible {
    position: relative;
    padding-right: 44px;
}
.et-info-banner-dismiss {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 30px;
    height: 30px;
    border: 0;
    padding: 0;
    border-radius: 50%;
    background: transparent;
    display: grid;
    place-items: center;
    font-family: inherit;
    font-size: 13px;
    color: var(--etib-fg);
    cursor: pointer;
    transition: background-color .15s;
}
.et-info-banner-dismiss:hover { background-color: rgba(255, 255, 255, 0.65); }
.et-info-banner-dismiss:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Disclosure content (Wave 3 spec §1.1) — same top gap as .et-info-banner-actions
   above it, so a revealed bullet list (or GroupNew's price rows) sits at the same
   rhythm whether or not an action row is also present. */
.et-info-banner-content { margin-top: 10px; }
/* Disclosure form (Expanded set, 2026-09-26) — the content folds instead of
   popping in and out. The row goes 0fr → 1fr: a grid track interpolates
   between the two, so the height animates to the content's real size without
   JS measuring it. The clip's min-height:0 + overflow:hidden is what lets the
   0fr row reach zero; the content's 10px top margin sits inside the clip, so
   a closed banner is exactly as tall as it was when the content was not
   rendered at all.

   Closed means hidden, not just short: visibility takes the content out of
   the tab order and the accessibility tree. It flips at the END of the fold
   (a 0s transition delayed by the fold's duration) and at the START of the
   unfold, so the content stays visible for as long as any of it shows. The
   duration follows the destination, as CSS transitions do: unfolding runs the
   enter pair, folding the shorter exit. Without motion (data-motion="off",
   reduced motion through tokens.css) the same states just switch. */
.et-info-banner-disclosure {
    display: grid;
    grid-template-rows: 0fr;
}
.et-info-banner-disclosure--open { grid-template-rows: 1fr; }
.et-info-banner-disclosure-clip {
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
}
.et-info-banner-disclosure--open > .et-info-banner-disclosure-clip {
    opacity: 1;
    visibility: visible;
}
.et-info-banner-chevron {
    width: 12px;
    height: 12px;
    margin-left: 6px;
    flex-shrink: 0;
}
.et-info-banner-action[aria-expanded="true"] .et-info-banner-chevron { transform: rotate(180deg); }
html:not([data-motion="off"]) .et-info-banner-disclosure {
    transition: grid-template-rows var(--et-motion-exit) var(--et-ease-standard);
}
html:not([data-motion="off"]) .et-info-banner-disclosure--open {
    transition: grid-template-rows var(--et-motion-enter) var(--et-ease-standard);
}
html:not([data-motion="off"]) .et-info-banner-disclosure-clip {
    transition: opacity var(--et-motion-exit) var(--et-ease-exit),
                visibility 0s linear var(--et-motion-exit);
}
html:not([data-motion="off"]) .et-info-banner-disclosure--open > .et-info-banner-disclosure-clip {
    transition: opacity var(--et-motion-enter) var(--et-ease-enter),
                visibility 0s;
}
html:not([data-motion="off"]) .et-info-banner-chevron {
    transition: transform var(--et-motion-exit) var(--et-ease-standard);
}
html:not([data-motion="off"]) .et-info-banner-action[aria-expanded="true"] .et-info-banner-chevron {
    transition: transform var(--et-motion-enter) var(--et-ease-standard);
}


/* ---------- EtToast ----------
   A pill: 52px on one line (10px around the 32px close button), radius 26 so
   one line is a capsule and two lines a rounded card. Three looks, one class
   each, picked by the et:toast-style pilot (ThemeService):
     --ink   DEFAULT. #1F1F1F, the CTA gradient's charcoal end stop that every
             palette shares, with white text — the camera island's own black,
             so the phone entry below has no seam. The same in both themes.
     --card  The app's surface card: --et-color-surface, ink text, a 1px ring.
     --fill  The whole pill in the severity tone, white text.
   Severity reads from a 28px disc with a drawn glyph. The tones are literal
   (or the CTA start stop for info): they carry a white glyph in both themes,
   which the dark theme's lighter --et-color-owed/-owe/-pend would not. */
.et-toast {
    --et-toast-tone: rgb(var(--et-cta-shadow-rgb));
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    box-sizing: border-box;
    min-height: 52px;
    max-width: 420px;
    padding: 10px 10px 10px 12px;
    border-radius: 26px;
    font-family: var(--et-font-family-base);
}
.et-toast--success { --et-toast-tone: #15764A; }
.et-toast--warning { --et-toast-tone: #B7791F; }
.et-toast--error   { --et-toast-tone: #C4392D; }

.et-toast--ink {
    background: #1F1F1F;
    color: #fff;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28);
}
/* On a dark page the charcoal needs an edge to stand off the background. */
[data-theme="dark"] .et-toast--ink {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.10), 0 10px 28px rgba(0, 0, 0, 0.5);
}
.et-toast--card {
    background: var(--et-color-surface);
    color: var(--et-color-ink);
    box-shadow: 0 0 0 1px var(--et-color-border), 0 10px 28px rgba(0, 0, 0, 0.14);
}
.et-toast--fill {
    background: var(--et-toast-tone);
    color: #fff;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.22);
}

.et-toast-icon {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--et-toast-tone);
    color: #fff;
}
/* The pill already is the tone — the disc turns into a white wash on it. */
.et-toast--fill .et-toast-icon { background: rgba(255, 255, 255, 0.2); }
.et-toast-icon svg,
.et-toast-close svg { display: block; }
.et-toast-text {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.35;
    overflow-wrap: anywhere;
}
/* 32px visible, 44px to the finger (the ::after). */
.et-toast-close {
    position: relative;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.12);
    color: inherit;
    cursor: pointer;
}
.et-toast-close::after {
    content: "";
    position: absolute;
    inset: -6px;
}
.et-toast-close:hover { background: rgba(255, 255, 255, 0.2); }
.et-toast--card .et-toast-close {
    background: var(--et-color-surface-2);
    color: var(--et-color-ink-2);
}
.et-toast--card .et-toast-close:hover { background: var(--et-color-border); }
.et-toast-close:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
}

/* The countdown, drawn: a 2px line just inside the pill's bottom edge (on the
   edge itself it would read as a border) that runs out from both ends over the
   toast's dwell (--et-toast-ms, set inline by EtToast from ToastState). Held —
   a finger or the pointer on the toast — it pauses with the countdown. Not
   under the et:motion gate: it is the only place the remaining time shows.
   transform only, like all phone motion. */
@keyframes et-toast-drain { to { transform: scaleX(0); } }
.et-toast-timer {
    position: absolute;
    left: 26px;
    right: 26px;
    bottom: 4px;
    height: 2px;
    border-radius: 1px;
    background: currentColor;
    opacity: 0.3;
    pointer-events: none;
    animation: et-toast-drain var(--et-toast-ms, 3000ms) linear forwards;
}
.et-toast--held .et-toast-timer { animation-play-state: paused; }

/* Toast host (Components/Ui/EtToastHost.razor) — what MudSnackbarProvider's
   container used to be. Top-center below 768px, bottom-right from there up.
   z-index sits above .et-modal-overlay (1500) so a toast fired from a dialog
   is still visible. pointer-events are off on the strip and back on for the
   pill, so the empty gutter never eats a click on the page underneath.
   ToastState shows one toast at a time; the one it replaces, and any toast
   dismissed, stays for its exit as --leaving, taken out of the flow so it
   plays in place while the next one takes the slot. ToastState.LeaveMs keeps
   it rendered a little longer than the 220ms --leaving animations below.
   var() before env(), the chain every inset in app.css reads: in the Android
   shell Capacitor SystemBars publishes --safe-area-inset-* itself (see
   capacitor.config.ts); iOS and the PWA fall through to env(). */
.et-toast-host {
    --et-toast-inset: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
    position: fixed;
    z-index: 1600;
    top: calc(12px + var(--et-toast-inset));
    left: 16px;
    right: 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none;
}
.et-toast-host > .et-toast { pointer-events: auto; }
.et-toast-host > .et-toast--leaving {
    position: absolute;
    pointer-events: none;
}
html[data-motion="off"] .et-toast-host > .et-toast--leaving { display: none; }

/* Phone: the pill grows out of the camera island. It starts as a 0.32 × 0.7
   capsule — 115–127 × 36px on 390–430px phones, inside the island's 126 × 37pt
   — at max(12px, inset − 48px) from the top: 12–14pt on every Dynamic Island
   iPhone (inset 59/62pt), 12px everywhere else. That is half behind the notch
   on a notched iPhone (inset 44–50), over the camera hole of most Android
   phones, and where it rests anyway in a browser tab (inset 0, so it only
   grows — and never reaches the y=4 point WebKit samples the status-bar colour
   from). From there one spring carries it down to its resting place
   under the status bar; the content fades in once the pill has room for it.
   It rests BELOW the status bar, not over it: iOS draws the clock and battery
   on top of the WebView, and only the system can hide them. The exit runs the
   same path back. The swipe-up dismiss (app-init.js) drags it with `translate`,
   which composes with these `transform` keyframes. */
@keyframes et-toast-island-in {
    from { transform: translateY(var(--et-toast-from-y)) scale(0.32, 0.7); }
}
/* Opaque until it is nearly back under the island, for the same reason the
   entry does not fade. The opacity stop leaves the transform one curve. */
@keyframes et-toast-island-out {
    70% { opacity: 1; }
    to { transform: translateY(var(--et-toast-from-y)) scale(0.32, 0.7); opacity: 0; }
}
@media (max-width: 767.98px) {
    .et-toast-host {
        --et-toast-from-y: calc(max(12px, var(--et-toast-inset) - 48px) - 12px - var(--et-toast-inset));
    }
    .et-toast-host > .et-toast {
        width: 100%;
        transform-origin: 50% 0;
        touch-action: none;
        user-select: none;
        -webkit-user-select: none;
    }
    .et-toast-host > .et-toast--leaving {
        top: 0;
        left: 0;
        right: 0;
        margin: 0 auto;
    }
    /* No fade on the pill itself: it starts hidden behind the island, and a
       half-transparent one coming out from under it reads as grey. */
    html:not([data-motion="off"]) .et-toast-host > .et-toast {
        animation: et-toast-island-in 420ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
    }
    html:not([data-motion="off"]) .et-toast-host > .et-toast :is(.et-toast-icon, .et-toast-text, .et-toast-close) {
        animation: et-fade 200ms 140ms var(--et-ease-standard) both;
    }
    html:not([data-motion="off"]) .et-toast-host > .et-toast--leaving {
        animation: et-toast-island-out 220ms var(--et-ease-exit) forwards;
    }
    html:not([data-motion="off"]) .et-toast-host > .et-toast--leaving :is(.et-toast-icon, .et-toast-text, .et-toast-close) {
        animation: et-fade-out 100ms linear forwards;
    }
}

/* Desktop: bottom-right, sized to its text. The host is as wide as the widest
   pill so a leaving one, out of the flow, keeps the width it had. The pill
   rises 12px into place and sinks back out. */
@keyframes et-toast-rise-in {
    from { transform: translateY(12px) scale(0.96); opacity: 0; }
}
@keyframes et-toast-rise-out {
    to { transform: translateY(8px) scale(0.98); opacity: 0; }
}
@media (min-width: 768px) {
    .et-toast-host {
        top: auto;
        left: auto;
        right: 24px;
        bottom: 24px;
        width: min(420px, calc(100vw - 48px));
        align-items: flex-end;
    }
    .et-toast-host > .et-toast--leaving {
        right: 0;
        bottom: 0;
    }
    html:not([data-motion="off"]) .et-toast-host > .et-toast {
        animation: et-toast-rise-in var(--et-motion-enter) var(--et-ease-enter) both;
    }
    html:not([data-motion="off"]) .et-toast-host > .et-toast--leaving {
        animation: et-toast-rise-out 220ms var(--et-ease-exit) forwards;
    }
}


/* ---------- EtSkeleton ---------- */
@keyframes et-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .45; }
}
.et-skeleton {
    background: #E9E5DC; /* design literal (sand); darker than surface-2 by intent */
    animation: et-pulse 1.5s ease-in-out infinite;
}
.et-skeleton--text   { border-radius: 6px; }
.et-skeleton--rect   { border-radius: 13px; }
.et-skeleton--circle { border-radius: 50%; }


/* ---------- EtFieldRow ---------- */
.et-field { font-family: var(--et-font-family-base); }
.et-field-attach {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 13px;
    border: 1.5px dashed #D8D4CA; /* dc literal, in-family with border-strong */
    border-radius: 13px;
    color: var(--et-color-ink-2);
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
    transition: background-color .15s;
}
.et-field-attach:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-field-attach-emoji { font-size: 16px; }
.et-field-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 13px 15px;
    background: var(--et-color-surface);
    border-radius: 13px;
    border: 1px solid var(--et-color-border);
    cursor: pointer;
    transition: box-shadow .15s, border-color .15s;
}
.et-field-card--with-avatar { padding: 12px 14px; }
.et-field-card--input { cursor: text; }
.et-field-card--locked {
    background: var(--et-color-surface-2);
    border-color: transparent;
    cursor: default;
}
.et-field-card--focus {
    border: 1.5px solid var(--et-color-accent);
    box-shadow: 0 0 0 3px rgba(122, 78, 122, 0.15);
}
.et-field-card--error {
    border: 1.5px solid var(--et-color-owe);
    box-shadow: 0 0 0 3px rgba(196, 57, 45, 0.10);
}
.et-field-card--picker:not(.et-field-card--focus):not(.et-field-card--error):hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.et-field-body { flex: 1; min-width: 0; }
/* Block: on input rows this is a <label for> (#488), inline by default, and
   its line box made every input card 8px taller than the div it replaced. */
.et-field-label {
    display: block;
    font-size: 11px;
    color: var(--et-color-ink-2);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.et-field-value {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    display: flex;
    align-items: center;
    gap: 7px;
}
.et-field-label + .et-field-value { margin-top: 2px; }
.et-field-value--empty {
    color: var(--et-color-ink-3);
    font-weight: 500;
}
.et-field-image {
    width: 22px;
    height: 22px;
    border-radius: 6px;
    overflow: hidden;
    flex-shrink: 0;
}
.et-field-image img { width: 100%; height: 100%; object-fit: cover; }
.et-field-emoji { font-size: 15px; }
.et-field-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}
.et-field-value-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-field-chevron {
    color: var(--et-color-ink-3);
    font-size: 18px;
    flex-shrink: 0;
}
/* Dropdown-trigger chevron (▾/▴) — the triangle glyphs read heavier than
   the navigation ›, so they step down a size. */
.et-field-chevron--dd { font-size: 13px; }
.et-field-lock {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--et-color-ink-3);
    font-weight: 600;
    flex-shrink: 0;
}
.et-field-error {
    font-size: 11.5px;
    color: var(--et-color-owe);
    font-weight: 600;
    margin: 6px 2px 0;
}
/* Editable rows compose .et-field-card markup with a real input styled
   like the value text (used by the expense form's input/date fields). */
.et-field-input {
    width: 100%;
    border: 0;
    background: none;
    padding: 0;
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    outline: none;
}
.et-field-input::placeholder {
    color: var(--et-color-ink-3);
    font-weight: 500;
}


/* ---------- EtMiniInput ---------- */
.et-mini-input {
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: var(--et-font-family-base);
}
.et-mini-input-label {
    font-size: 12px;
    color: var(--et-color-ink-2);
    font-weight: 600;
}
.et-mini-input-box {
    display: flex;
    align-items: baseline;
    gap: 4px;
    background: var(--et-color-bg);
    border: 1.5px solid var(--et-color-border-strong);
    border-radius: 9px;
    padding: 7px 12px;
    cursor: text;
}
.et-mini-input-field {
    min-width: 2ch;
    border: 0;
    background: none;
    outline: none;
    padding: 0;
    font-family: inherit;
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
    text-align: center;
}
.et-mini-input-suffix {
    font-size: 12.5px;
    color: var(--et-color-ink-2);
}
.et-mini-input-remove {
    border: 0;
    background: none;
    padding: 0;
    color: var(--et-color-ink-3);
    font-family: inherit;
    font-size: 14px;
    cursor: pointer;
}
.et-mini-input-remove:hover { color: var(--et-color-owe); }
.et-mini-input-remove:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtCheckRow ---------- */
.et-check-row {
    display: flex;
    align-items: center;
    gap: 9px;
    font-family: var(--et-font-family-base);
}
/* Mud's bare `button { justify-content:center }` matches this too — the
   centering is wanted here, but declare it so the atom doesn't depend on
   the Mud sheet surviving the migration. */
.et-check-row-box {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    border-radius: 7px;
    border: 1.6px solid var(--et-color-border-strong);
    background: var(--et-color-surface);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    font-family: inherit;
    font-size: 13px;
    line-height: 1;
    color: #fff;
    cursor: pointer;
}
.et-check-row-box--on {
    background: var(--et-color-accent);
    border-color: var(--et-color-accent);
}
.et-check-row-box:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-check-row-label {
    font-size: 13px;
    font-weight: 700;
    color: var(--et-color-ink);
    cursor: pointer;
}
.et-check-row-hint {
    font-size: 12px;
    color: var(--et-color-ink-3);
}
/* A required tick missed on submit — the .et-field-card--error look on the box. */
.et-check-row--error .et-check-row-box {
    border-color: var(--et-color-owe);
    box-shadow: 0 0 0 3px rgba(196, 57, 45, 0.10);
}


/* ---------- EtAmountInput ---------- */
.et-amount-input {
    text-align: center;
    font-family: var(--et-font-family-base);
}
/* A <label for> since #488, which is inline by default: without `block` it
   sat on the same line as the inline-flex box, left of the number, and its
   margin-bottom did nothing. */
.et-amount-input-label {
    display: block;
    font-size: 12px;
    color: var(--et-color-ink-2);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 6px;
}
.et-amount-input-box {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    border-bottom: 2.5px solid var(--et-color-accent);
    /* The side padding is the only symmetric handle on how far the underline
       runs past the number — the suffix and its hidden mirror are fixed by the
       currency string. 20px → 12px on owner request (2026-09-17): each side's
       overhang goes 50px → 42px, the line still clears the "Ft" comfortably. */
    padding: 4px 12px 9px;
    cursor: text;
}
/* R4-06 — the field sizes itself from its own text.
   .et-amount-input-sizer is a hidden copy of the value and the only thing in
   flow, so the field is exactly as wide as the text renders; the input is laid
   over it. The width used to be computed in C# from a per-character estimate,
   which could not be right: `1ch` is 0.70em (the advance of "0") while a digit
   here takes 0.56em once tabular-nums and letter-spacing: -0.04em have had
   their say, so every digit left ~0.14em of dead space inside the box — all of
   it on the left, since the digits are right-aligned. This has no constant to
   drift.
   The input is out of flow, not a second item in a shared grid cell: that is
   how R4-06 first shipped, and the cell then took the input's own intrinsic
   width as a floor. Even at size="1" that measured 73px here, against 27px for
   a digit, so a 1–2 digit amount, the empty "0" included, carried ~46px of
   dead space left of the number and the underline ran that much further left
   than right (owner 2026-09-28: "nagyon széles").
   The two MUST keep identical type settings, hence the shared rule below. */
.et-amount-input-field {
    position: relative;
    display: inline-block;
}
.et-amount-input-sizer {
    visibility: hidden;
    /* The value carries U+202F group separators; collapsing them would make the
       sizer narrower than the input's own text. */
    white-space: pre;
    pointer-events: none;
}
.et-amount-input-num,
.et-amount-input-sizer,
.et-amount-input-value {
    font-family: inherit;
    font-size: 48px;
    font-weight: 800;
    letter-spacing: -0.04em;
    font-variant-numeric: tabular-nums;
}
.et-amount-input-num {
    /* Covers the sizer exactly. `width`/`height` are explicit because an
       absolutely positioned input does not stretch to its insets — it keeps
       its intrinsic size. The sizer never renders empty (it falls back to the
       placeholder "0"), so there is always a box to cover. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    background: none;
    padding: 0;
    color: var(--et-color-ink);
    text-align: right;
    outline: none;
}
/* The suffix mirrored on the left of the number, hidden. `visibility` and not
   `display:none`, because it has to keep taking its width — that width is the
   whole point: it is what puts the number, rather than "number + Ft", in the
   middle of the underline. */
.et-amount-input-ghost { visibility: hidden; }
.et-amount-input-num::placeholder { color: #C4C0B8; } /* dc literal — muted zero */
/* In flow, between the field and the suffix. It does take width on one side of
   the number only, so the empty state's placeholder "0" sits a little off the
   underline's centre — but it is the only state where that happens (a typed
   amount never renders a caret) and the alternative is worse: taken out of flow
   it has nothing but the box's 6px gap to occupy and the bar lands on the "Ft".
   Tried and reverted 2026-09-17. */
.et-amount-input-caret {
    font-size: 46px;
    font-weight: 200;
    color: var(--et-color-accent);
    margin: 0 -2px;
    animation: et-caret-blink 1.1s steps(1) infinite;
}
@keyframes et-caret-blink {
    0%, 49% { opacity: 1; }
    50%, 100% { opacity: 0; }
}
.et-amount-input-currency {
    font-size: 24px;
    color: var(--et-color-ink-3);
    font-weight: 700;
}
/* ReadOnly — a computed amount. The digits keep their ink and size (it is still THE number
   of the form); the underline goes neutral so it stops reading as a field to tap. */
.et-amount-input-value {
    color: var(--et-color-ink);
    white-space: pre;
}
.et-amount-input--readonly .et-amount-input-box {
    border-bottom-color: var(--et-color-border-strong);
    cursor: default;
}


/* ---------- EtSegmentedControl ---------- */
.et-segmented {
    /* The thumb's containing block — see .et-segmented-thumb below. */
    position: relative;
    display: flex;
    background: var(--et-color-surface-2);
    border-radius: 12px;
    padding: 4px;
    gap: 2px;
    font-family: var(--et-font-family-base);
}
.et-segmented-item {
    /* Positioned so the labels paint ABOVE the thumb: both are positioned
       with z-index:auto, so DOM order decides, and the thumb comes first. */
    position: relative;
    flex: 1;
    text-align: center;
    padding: 8px;
    border: 0;
    background: none;
    border-radius: 9px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--et-color-ink-2);
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s, color .15s;
}
.et-segmented-item--active {
    background: var(--et-color-surface);
    color: var(--et-color-ink);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.et-segmented-item:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Sliding selection (2026-09-26, EtSegmentedControl.razor has the why). The
   active option's own background above stays as the fallback: until
   etDs.syncSegmented has measured the row and stamped data-thumb, the thumb
   is display:none and nothing changes — the screen dumps and bUnit render
   exactly that. Once stamped, the thumb carries the same surface + shadow
   and the active option goes transparent over it. The four custom
   properties are written by ds.js in px, relative to the padding box. */
.et-segmented-thumb {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    width: var(--et-seg-w, 0px);
    height: var(--et-seg-h, 0px);
    transform: translate(var(--et-seg-x, 0px), var(--et-seg-y, 0px));
    border-radius: 9px;
    background: var(--et-color-surface);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    pointer-events: none;
}
.et-segmented[data-thumb] > .et-segmented-thumb { display: block; }
.et-segmented[data-thumb] > .et-segmented-item--active {
    background: none;
    box-shadow: none;
}
/* The glide itself — under the et:motion pilot, like the sheet's exit and the
   page transitions. data-motion="off" leaves the thumb jumping; reduced
   motion is covered by tokens.css's blanket transition-duration rule. Width
   moves too, because options are not always equally wide. */
html:not([data-motion="off"]) .et-segmented-thumb {
    transition: transform var(--et-motion-enter) var(--et-ease-standard),
                width var(--et-motion-enter) var(--et-ease-standard);
}
/* Full/short label swap (group-settings tabs, spec 2026-08-24 §4) — CSS-only,
   no matchMedia/JS: both spans render, the stylesheet picks one per the DS's
   single 640px phone breakpoint. Absent on every caller that doesn't pass
   ShortOptions (split-method, group-type), where the button renders a plain
   text node instead of these spans. */
.et-segmented-item-label-short { display: none; }
@media (max-width: 640px) {
    .et-segmented-item-label-full  { display: none; }
    .et-segmented-item-label-short { display: inline; }
}


/* ---------- EtCategoryPicker ---------- */
.et-category-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    font-family: var(--et-font-family-base);
}
.et-category-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 13px;
    border: 0;
    border-radius: 999px;
    background: var(--et-color-surface-2);
    color: var(--et-color-ink-2);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s, color .15s, box-shadow .15s;
}
.et-category-chip:hover:not(.et-category-chip--selected) { background: #E6E2D8; }
.et-category-chip--selected {
    background: var(--et-color-accent);
    color: #fff;
    font-weight: 700;
    box-shadow: 0 2px 8px rgba(122, 78, 122, 0.30);
}
.et-category-chip-emoji { font-size: 14px; line-height: 1; }
.et-category-chip:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* The last tile (spec 2026-10-02-kassza-categories-design.md §6): same pill size and
   shape as every other chip, dashed instead of filled so it reads as "add", not as a
   category someone could already be on. */
.et-category-chip--add {
    background: transparent;
    border: 1px dashed var(--et-color-ink-3);
    color: var(--et-color-ink-2);
}
.et-category-chip--add:hover { background: var(--et-color-surface-2); }
/* ReadOnly: the selected chip as text — same fill, nothing to tap. */
.et-category-chip--readonly { cursor: default; }


/* ---------- EtPayMethodPicker ---------- */
.et-paymethod {
    display: flex;
    gap: 8px;
    font-family: var(--et-font-family-base);
}
.et-paymethod-item {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 11px;
    border-radius: 11px;
    background: var(--et-color-surface);
    border: 1px solid var(--et-color-border);
    color: var(--et-color-ink-2);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s, color .15s;
}
.et-paymethod-item--active {
    /* dc: #1A1917 / #fff — ink/surface tokens so dark mode inverts
       (same mapping as .et-btn--dark) */
    background: var(--et-color-ink);
    border-color: transparent;
    color: var(--et-color-surface);
}
.et-paymethod-item:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtEmojiGrid ---------- */
/* auto-fill sizes the column count off the box itself instead of a fixed 8,
   with a 44px minmax floor for touch size. A fixed 8-column grid forced a
   44px min-height onto cells that had shrunk to ~36-38px of width on a
   narrow surface (~330-350px track at 390px), which read as squished
   rectangles rather than squares. aspect-ratio now keeps every cell square
   at whatever width auto-fill lands on, so the min-height crutch is gone. */
.et-emoji-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
    gap: 8px;
    font-family: var(--et-font-family-base);
}
.et-emoji-cell {
    width: 100%;
    aspect-ratio: 1 / 1;
    border: 0;
    padding: 0;
    border-radius: 11px;
    display: grid;
    place-items: center;
    font-size: 20px;
    background: var(--et-color-surface);
    box-shadow: inset 0 0 0 1px var(--et-color-border);
    cursor: pointer;
    transition: background .15s, box-shadow .15s;
}
.et-emoji-cell:hover:not(.et-emoji-cell--selected) { background: #F5F2EB; }
.et-emoji-cell--selected {
    background: var(--et-color-accent);
    box-shadow: 0 2px 8px rgba(122, 78, 122, 0.30);
}
.et-emoji-cell:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtColorSwatches ---------- */
.et-swatches {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    font-family: var(--et-font-family-base);
}
.et-swatch {
    width: 44px;
    height: 44px;
    border: 0;
    padding: 0;
    border-radius: 50%;
    cursor: pointer;
    box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.08);
    transition: box-shadow .15s;
}
.et-swatch--selected {
    box-shadow: inset 0 0 0 3px var(--et-color-surface), 0 0 0 3px var(--et-color-ink);
}
.et-swatch:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-swatch-custom {
    background: conic-gradient(from 210deg, #E5484D, #FFB224, #F5D90A, #46A758, #0EA5E9, #7C3AED, #D6409F, #E5484D);
    display: grid;
    place-items: center;
    position: relative;
}
.et-swatch-custom-dot {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    position: relative;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
/* Two CSS bars instead of a text "+" glyph: a font's "+" is drawn off-centre
   relative to its own glyph box (metrics vary per weight/font), which read
   as visibly lopsided in the 20px dot. Bars centred with translate(-50%,
   -50%) are pixel-exact regardless of font. Only rendered while no custom
   colour is active — CustomActive swaps the dot to a plain filled circle. */
.et-swatch-custom-dot--plus::before,
.et-swatch-custom-dot--plus::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    background: #1A1917; /* dc literal — plus glyph on the white core */
    transform: translate(-50%, -50%);
}
.et-swatch-custom-dot--plus::before {
    width: 10px;
    height: 2px;
}
.et-swatch-custom-dot--plus::after {
    width: 2px;
    height: 10px;
}
.et-swatch-custom-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}


/* ---------- EtGroupPicker ---------- */
.et-group-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    font-family: var(--et-font-family-base);
}
.et-group-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--et-color-surface-2);
    border: 1.5px solid transparent;
    border-radius: 999px;
    color: var(--et-color-ink-2);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: border-color .15s, color .15s, background .15s;
}
.et-group-chip:hover:not(.et-group-chip--selected) { background: #E6E2D8; }
.et-group-chip--selected {
    background: var(--et-color-accent);
    border-color: transparent;
    color: #fff;
    font-weight: 700;
    box-shadow: 0 2px 8px rgba(122, 78, 122, 0.30);
}
.et-group-chip-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}
/* On the accent fill the dot needs its own edge to read against colors close
   to the accent itself — same purpose as EtSwatch's selected ring, just
   thinner since this dot is tiny. */
.et-group-chip--selected .et-group-chip-dot {
    box-shadow: 0 0 0 1.5px #fff;
}
.et-group-chip:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtDatePicker ---------- */
/* Two presentations, one component: the anchored month grid above the sheet
   breakpoint, the plain native input below it. Same @media cut EtDropdown's
   own phone sheet uses (see EtDropdownSection's gallery note) — a native
   <input type="date"> is a viewport-level OS affordance, not something a
   container query should be deciding. */
.et-datepicker-native { display: none; }
@media (max-width: 640px) {
    .et-datepicker-desktop { display: none; }
    .et-datepicker-native { display: block; }
}
.et-datepicker-cal {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 4px;
    font-family: var(--et-font-family-base);
}
.et-datepicker-cal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 2px 4px 6px;
}
.et-datepicker-cal-title {
    font-size: 14px;
    font-weight: 800;
    color: var(--et-color-ink);
    letter-spacing: -0.01em;
}
.et-datepicker-nav {
    width: 28px;
    height: 28px;
    border: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--et-color-surface-2);
    color: var(--et-color-ink-2);
    font-family: inherit;
    font-size: 14px;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background-color .15s;
}
.et-datepicker-nav:hover { background-color: #E6E2D8; }
.et-datepicker-nav:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-datepicker-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    text-align: center;
    font-size: 11px;
    font-weight: 700;
    color: var(--et-color-ink-3);
    text-transform: uppercase;
    padding: 0 2px;
}
.et-datepicker-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
    padding: 0 2px;
}
.et-datepicker-cell {
    width: 100%;
    aspect-ratio: 1 / 1;
    border: 0;
    padding: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--et-color-ink);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background-color .15s, color .15s;
}
.et-datepicker-cell:hover:not(:disabled):not(.et-datepicker-cell--selected) { background: #E6E2D8; }
.et-datepicker-cell--outside { color: var(--et-color-ink-3); font-weight: 500; }
.et-datepicker-cell--today { box-shadow: inset 0 0 0 1.5px var(--et-color-accent); }
.et-datepicker-cell--selected {
    background: var(--et-color-accent);
    color: #fff;
    font-weight: 800;
}
.et-datepicker-cell:disabled { color: var(--et-color-ink-3); opacity: .4; cursor: default; }
.et-datepicker-cell:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- Mobile picker/grid carousel (spec 2026-08-24 §3.1/§3.2) ------
   Opt-in class, passed via each component's own `Class` parameter (already
   there for page-level overrides — no new parameter needed): EtGroupPicker,
   EtCategoryPicker and EtColorSwatches turn a wrapped chip/swatch row into a
   single scrollable row below the DS's 640px phone breakpoint; desktop is
   untouched. Same hidden-scrollbar convention as the Home shelves
   (.et-card-strip / .nac-strip) — this row is too short/variable-width for
   their paddles or edge-fade masks to earn their keep, so it skips both.
   Placed AFTER the three components' own rules above so the cascade's
   source-order tie-break (equal specificity throughout) lands the nowrap
   override — moving this block earlier in the file would silently lose
   that fight to .et-category-picker's `flex-wrap: wrap` (and, further down,
   to .et-group-picker's).

   The padding and its cancelling negative margin (Wave 3 spec §1.3) keep a
   selected item's ring from being clipped: a non-inset box-shadow only
   paints where there is room INSIDE the scroller, and while a middle item
   borrows that room from the gap to its neighbour, the first and last have
   none. 8px — the row's own gap — covers both the swatch ring (3px) and the
   emoji cell's selected glow (8px blur); the negative margin cancels it so
   the row's visible edges still line up with the label above instead of the
   whole strip shifting inward. Vertically the same clipping applies even
   though only overflow-x is set: per the CSS overflow spec, setting one axis
   to a value other than `visible` forces the other axis's *used* value to
   `auto` too, so the browser clips top/bottom against the padding box just
   like it clips left/right — the original 2px vertical padding was thinner
   than the 3px ring, cutting it off top and bottom. Matching the vertical
   padding to the same 8px (with a matching negative vertical margin so the
   strip's height in the flow is unchanged) gives the ring and the glow the
   same clipping headroom on every side. */
@media (max-width: 640px) {
    .et-picker-strip {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        margin: -8px;
        padding: 8px;
    }
    .et-picker-strip::-webkit-scrollbar { display: none; }
    /* Flex items default to flex-shrink:1 — without this, nowrap alone would
       squeeze every chip/swatch to fit the track instead of overflowing it,
       and there'd be nothing to scroll. Same fix as .et-card-strip > *. */
    .et-picker-strip > * {
        flex: 0 0 auto;
    }

    /* EtEmojiGrid is a CSS grid, not flex — auto-fill columns don't have a
       "don't wrap" switch, so the carousel form swaps it to a flex row
       instead and pins each cell back to its normal 44px square (the base
       rule sizes cells off the grid track via width:100%, which would
       otherwise stretch every cell to the full row width). */
    .et-emoji-grid.et-picker-strip {
        display: flex;
    }
    .et-emoji-grid.et-picker-strip .et-emoji-cell {
        width: 44px;
        flex: 0 0 44px;
    }
}


/* ---------- EtAmountDisplay ---------- */
.et-amount-display {
    text-align: center;
    font-family: var(--et-font-family-base);
}
.et-amount-display-label {
    font-size: 12px;
    color: var(--et-color-ink-2);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 6px;
}
.et-amount-display-num {
    font-size: 46px;
    font-weight: 800;
    letter-spacing: -0.04em;
    color: var(--et-color-ink);
}
.et-amount-display-num--md { font-size: 40px; }
.et-amount-display-num--green { color: var(--et-color-owed); }
.et-amount-display-num--red { color: var(--et-color-owe); }
.et-amount-display-currency {
    font-size: 22px;
    color: var(--et-color-ink-3);
}


/* ---------- EtMetaItem ---------- */
.et-meta-item {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--et-color-ink-2);
    font-family: var(--et-font-family-base);
    white-space: nowrap;
}
/* Clickable variant (F-30: the event chip opens the event summary panel).
   A <button> instead of a <span>, so the browser defaults have to be reset
   back to the chip's own look; only the pointer and the hover tint are new. */
.et-meta-item--action {
    border: 0;
    padding: 0;
    background: none;
    font: inherit;
    color: var(--et-color-ink-2);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: var(--et-color-border);
    text-underline-offset: 2px;
}
.et-meta-item--action:hover { color: var(--et-color-ink); }
.et-meta-item-image {
    width: 18px;
    height: 18px;
    border-radius: 4px;
    object-fit: cover;
}
.et-meta-item-emoji {
    font-size: 13px;
    line-height: 1;
}


/* ---------- EtDonutChart ---------- */
.et-donut-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    font-family: var(--et-font-family-base);
}
.et-donut {
    border-radius: 50%;
    display: grid;
    place-items: center;
}
.et-donut-hole {
    width: 62%;
    height: 62%;
    border-radius: 50%;
    background: var(--et-color-bg);
    display: grid;
    place-items: center;
    text-align: center;
}
.et-donut-center-label {
    font-size: 9px;
    color: var(--et-color-ink-3);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.et-donut-center-value {
    font-size: 15px;
    font-weight: 800;
    color: var(--et-color-ink);
}
.et-donut-caption {
    font-size: 12px;
    color: var(--et-color-ink-2);
    font-weight: 600;
}

/* ---------- EtCategoryBreakdown ----------
   The Saját kassza's two-month category table (spec 2026-11-03-personal-kassza-design.md D30).
   One three-column grid — name, this month, last month — that every row joins through subgrid,
   so the amounts line up down the table without a <table>: the amount columns size to their
   widest content ("10 000 000 Ft", "12 345,67 CHF") and the name column truncates instead. */
.et-category-breakdown {
    display: grid;
    grid-template-columns: minmax(0, 1fr) max-content max-content;
    column-gap: 12px;
    font-family: var(--et-font-family-base);
}
.et-category-breakdown-empty {
    grid-column: 1 / -1;
    padding: 12px 14px;
    font-size: 13px;
    font-weight: 600;
    color: var(--et-color-ink-2);
}
.et-category-breakdown-head,
.et-category-breakdown-total,
.et-category-breakdown-row {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: baseline;
    padding: 10px 14px;
}
.et-category-breakdown-head {
    padding-bottom: 4px;
    font-size: 11px;
    font-weight: 600;
    color: var(--et-color-ink-3);
}
.et-category-breakdown-total {
    font-size: 14px;
    font-weight: 800;
    color: var(--et-color-ink);
    box-shadow: inset 0 -1px 0 var(--et-color-border);
}
.et-category-breakdown-row {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
}
.et-category-breakdown-row + .et-category-breakdown-row { box-shadow: inset 0 1px 0 var(--et-color-border); }
.et-category-breakdown-name {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
}
.et-category-breakdown-icon { flex-shrink: 0; line-height: 1; }
.et-category-breakdown-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-category-breakdown-amount {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.et-category-breakdown-amount--last { color: var(--et-color-ink-2); }


/* ---------- EtLinkCard ---------- */
.et-link-card {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 10px 13px;
    background: var(--et-color-surface);
    border: 1px solid var(--et-color-border);
    border-radius: 12px;
    font-family: var(--et-font-family-base);
    cursor: pointer;
    text-align: left;
    transition: box-shadow .15s;
}
.et-link-card:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); }
.et-link-card-image {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    overflow: hidden;
    flex-shrink: 0;
}
.et-link-card-image img { width: 100%; height: 100%; object-fit: cover; }
.et-link-card-emoji {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background: var(--et-color-surface-2);
    display: grid;
    place-items: center;
    font-size: 15px;
    flex-shrink: 0;
}
.et-link-card-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--et-color-ink);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-link-card-kind {
    font-size: 11px;
    color: var(--et-color-ink-3);
    flex-shrink: 0;
}
.et-link-card:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtTimelineStep ----------
   Disc fills are the dc's 8-digit-hex alpha tints of the semantic colors
   (no alpha tokens exist) — fg uses the matching semantic token. */
.et-timeline-step {
    display: flex;
    gap: 12px;
    font-family: var(--et-font-family-base);
}
.et-timeline-rail {
    display: flex;
    flex-direction: column;
    align-items: center;
}
.et-timeline-disc {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: #6b68601f;
    color: var(--et-color-ink-2);
    display: grid;
    place-items: center;
    font-size: 14px;
    flex-shrink: 0;
}
.et-timeline-disc--blue  { background: #1B5CFF1f; color: var(--et-color-sent); }
.et-timeline-disc--green { background: #15764A1f; color: var(--et-color-owed); }
.et-timeline-disc--red   { background: #C4392D1a; color: var(--et-color-owe); }
.et-timeline-line {
    width: 2px;
    flex: 1;
    background: var(--et-color-border);
    margin: 2px 0;
}
.et-timeline-body {
    padding-bottom: 18px;
    flex: 1;
}
.et-timeline-body--last { padding-bottom: 0; }
.et-timeline-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--et-color-ink);
}
.et-timeline-time {
    font-size: 11px;
    color: var(--et-color-ink-3);
    margin-top: 2px;
}


/* ---------- EtMemberRow ---------- */
.et-member-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-radius: 12px;
    font-family: var(--et-font-family-base);
    transition: background-color .15s;
}
/* Only a row with somewhere to go looks tappable (owner, 2026-10-02). */
.et-member-row--link { cursor: pointer; }
.et-member-row--link:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-member-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
.et-member-row-body { flex: 1 1 auto; min-width: 0; }
.et-member-row-name-line {
    display: flex;
    align-items: center;
    gap: 8px;
}
.et-member-row-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex-shrink: 1;
    min-width: 0;
}
.et-member-row-admin {
    flex-shrink: 0;
    font-size: 12.5px;
    font-weight: 500;
    color: #9C998F; /* dc literal */
}
.et-member-row-sub {
    font-size: 12px;
    color: var(--et-color-ink-2);
    margin-top: 1px;
}
.et-member-row-you {
    flex-shrink: 0;
    font-size: 11px;
    font-weight: 700;
    padding: 4px 11px;
    border-radius: 999px;
    border: 1.5px solid var(--et-color-accent);
    color: var(--et-color-accent);
}
.et-member-row-menu {
    border: 0;
    background: none;
    padding: 0;
    color: var(--et-color-ink-3);
    font-family: inherit;
    font-size: 18px;
    flex-shrink: 0;
    width: 20px;
    text-align: center;
    cursor: pointer;
}
.et-member-row-menu--inert {
    cursor: default;
    opacity: 0.35;
}
.et-member-row-edit {
    border: 0;
    background: none;
    padding: 0;
    color: var(--et-color-ink-3);
    font-family: inherit;
    font-size: 15px;
    flex-shrink: 0;
    width: 20px;
    text-align: center;
    cursor: pointer;
}
.et-member-row-chevron {
    color: #C8C5BD; /* dc literal chevron grey */
    font-size: 20px;
    line-height: 1;
    flex-shrink: 0;
    margin-left: 4px;
}


/* ---------- EtBalanceRow ---------- */
.et-balance-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-radius: 12px;
    cursor: pointer;
    font-family: var(--et-font-family-base);
    transition: background-color .15s;
}
.et-balance-row:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-balance-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
.et-balance-row-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.et-balance-row-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-balance-row-sub {
    font-size: 11px;
    color: var(--et-color-ink-2);
    font-weight: 500;
    line-height: 1.3;
}
.et-balance-row-amount-wrap {
    flex-shrink: 0;
    text-align: right;
}
.et-balance-row-amount {
    font-size: 15px;
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.et-balance-row-amount--owed { color: var(--et-color-owed); }
.et-balance-row-amount--owe { color: var(--et-color-owe); }
.et-balance-row-amount--settled { color: var(--et-color-ink-3); }
.et-balance-row-sign { margin-right: 1px; }
.et-balance-row-chevron {
    color: #C8C5BD;
    font-size: 20px;
    line-height: 1;
    flex-shrink: 0;
    margin-left: 4px;
}


/* ---------- EtDebtRow ---------- */
/* Same rhythm as .et-balance-row, minus every interactive affordance: no
   cursor, no hover tint, no chevron. The row is a statement, not a target. */
.et-debt-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-radius: 12px;
    font-family: var(--et-font-family-base);
}
.et-debt-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
.et-debt-row-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.et-debt-row-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-debt-row-sub {
    font-size: 11px;
    color: var(--et-color-ink-2);
    font-weight: 500;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Neutral ink, not owe/owed red-green: the row describes someone else's debt,
   so a viewer-relative tone would be meaningless here. */
.et-debt-row-amount {
    flex-shrink: 0;
    text-align: right;
    font-size: 15px;
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    color: var(--et-color-ink);
}


/* ---------- EtCounterpartyRow ---------- */
.et-cparty-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-radius: 12px;
    cursor: pointer;
    font-family: var(--et-font-family-base);
    transition: background-color .15s;
}
.et-cparty-row:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-cparty-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
.et-cparty-row-avatar {
    position: relative;
    flex-shrink: 0;
}
.et-cparty-row-medal {
    position: absolute;
    bottom: -3px;
    right: -3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid var(--et-color-surface);
    display: grid;
    place-items: center;
    font-size: 10px;
}
.et-cparty-row-body { flex: 1 1 auto; min-width: 0; }
.et-cparty-row-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-cparty-row-count {
    font-size: 11.5px;
    color: var(--et-color-ink-2);
    font-weight: 500;
}
.et-cparty-row-chevron {
    color: #C8C5BD;
    font-size: 20px;
    line-height: 1;
    flex-shrink: 0;
    margin-left: 4px;
}


/* ---------- EtShareRow ---------- */
.et-share-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 0;
    font-family: var(--et-font-family-base);
}
.et-share-row--divider { border-bottom: 1px solid var(--et-color-border); }
/* Current user's own row — a soft accent fill, same tint language as the chip
   hover. -9px of the list surface's 15px inset leaves the block 6px off the card
   edge, so it reads as a contained row rather than a full-bleed band. Mixed off
   --et-color-accent rather than the hardcoded rgba(122, 78, 122, …) used
   elsewhere: that literal is plum, and it disappears on the dark ground and on
   the non-plum palettes. The divider goes — a hairline running into a rounded
   fill reads as a mistake. */
.et-share-row--me {
    margin: 0 -9px;
    padding: 12px 9px;
    border-radius: 10px;
    background: color-mix(in srgb, var(--et-color-accent) 7%, transparent);
    border-bottom-color: transparent;
}
.et-share-row--me .et-share-row-name { font-weight: 700; }
.et-share-row-name {
    flex: 1;
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-share-row-you {
    color: var(--et-color-ink-3);
    font-weight: 500;
}
/* Caption set: the name and its one-line caption stack in the slot the name alone fills
   otherwise. min-width:0 lets both ellipsise instead of pushing the amount off the row. */
.et-share-row-main {
    flex: 1;
    min-width: 0;
}
.et-share-row-caption {
    margin-top: 2px;
    font-size: 12px;
    font-weight: 500;
    color: var(--et-color-ink-3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-share-row-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
    flex-shrink: 0;
}
.et-share-row-amount {
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
    white-space: nowrap;
}
.et-share-row-menu {
    border: 0;
    background: none;
    padding: 0;
    color: var(--et-color-ink-3);
    font-family: inherit;
    font-size: 18px;
    width: 20px;
    text-align: center;
    flex-shrink: 0;
    cursor: pointer;
}
.et-share-row-menu--inert {
    cursor: default;
    opacity: 0.35;
}


/* ---------- EtSplitRow ---------- */
.et-split-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 0;
    font-family: var(--et-font-family-base);
}
.et-split-row--divider { border-bottom: 1px solid var(--et-color-border); }
.et-split-row-name {
    flex: 1;
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Locked shares the computed-amount look: same weight, nothing to edit. */
.et-split-row-eq-amount,
.et-split-row-locked-amount {
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
}
.et-split-row-eq-amount--off { color: var(--et-color-ink-3); }
.et-split-row-check {
    width: 24px;
    height: 24px;
    border: 0;
    padding: 0;
    border-radius: 7px;
    background: var(--et-color-surface);
    box-shadow: inset 0 0 0 1.5px var(--et-color-border-strong);
    color: #fff;
    font-family: inherit;
    display: grid;
    place-items: center;
    font-size: 13px;
    font-weight: 700;
    flex-shrink: 0;
    cursor: pointer;
}
.et-split-row-check--on {
    background: var(--et-color-accent);
    box-shadow: none;
}
.et-split-row-check:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-split-row-box {
    display: flex;
    align-items: baseline;
    gap: 4px;
    background: var(--et-color-bg);
    border: 1.5px solid var(--et-color-border-strong);
    border-radius: 9px;
    padding: 7px 12px;
    cursor: text;
}
.et-split-row-percent-wrap .et-split-row-box { gap: 3px; padding: 7px 11px; }
.et-split-row-box-value {
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
}
.et-split-row-box-input {
    border: 0;
    background: none;
    padding: 0;
    min-width: 1ch;
    font-family: inherit;
    text-align: right;
    outline: none;
}
.et-split-row-box-unit {
    font-size: 11px;
    color: var(--et-color-ink-3);
    font-weight: 700;
}
.et-split-row-percent-wrap {
    display: flex;
    align-items: center;
    gap: 9px;
}
.et-split-row-percent-amount {
    font-size: 12px;
    color: var(--et-color-ink-2);
    font-weight: 700;
    width: 52px;
    text-align: right;
}


/* ---------- EtActivityRow ----------
   Pastel thumb fills and micro-avatar gradients are dc literals
   (identity colors, palette-independent). */
.et-activity-row { font-family: var(--et-font-family-base); }
.et-activity-row-main {
    display: flex;
    align-items: center;
    gap: 13px;
    padding: 12px 10px;
    border-radius: 12px;
    cursor: pointer;
    transition: background .14s;
}
.et-activity-row-main:hover {
    background: var(--et-color-bg);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.et-activity-row-main:active {
    background: var(--et-color-surface-2);
    box-shadow: none;
}
.et-activity-row-thumb {
    width: 46px;
    height: 46px;
    border-radius: 13px;
    display: grid;
    place-items: center;
    font-size: 20px;
    flex-shrink: 0;
}
.et-activity-row-thumb--rose     { background: #FDECEC; }
.et-activity-row-thumb--blue     { background: #E9F1FE; }
.et-activity-row-thumb--green    { background: #E7F5EC; }
.et-activity-row-thumb--mustard  { background: #FBF1DC; }
.et-activity-row-thumb--lavender { background: #F3E9FB; }
.et-activity-row-thumb--peach    { background: #FBEDE6; }
/* Event-linked row (F-31 / D16): the same pastel square, cut along the
   bottom-left → top-right diagonal. Top-left half keeps the category emoji,
   bottom-right half carries the event's icon over a slightly deeper wash of
   the SAME pastel — a translucent black veil rather than six more literals,
   so every tone stays one shade apart from itself and the cut edge reads.
   overflow:hidden is load-bearing: clip-path ignores the parent's radius, so
   without it the halves would square off the thumb's rounded corners.
   The fixed 15px paddings are the triangles' centroids on the thumb's fixed
   46px box (⅓ and ⅔ of 46), i.e. each emoji sits optically in its own half. */
.et-activity-row-thumb--split { position: relative; overflow: hidden; }
.et-activity-row-thumb-half {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    line-height: 1;
}
.et-activity-row-thumb-half--category {
    clip-path: polygon(0 0, 100% 0, 0 100%);
    padding: 0 15px 15px 0;
}
.et-activity-row-thumb-half--event {
    clip-path: polygon(100% 0, 100% 100%, 0 100%);
    padding: 15px 0 0 15px;
    background: rgba(0, 0, 0, .06);
}
.et-activity-row-body { flex: 1; min-width: 0; }
.et-activity-row-title-line {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 2px;
}
/* min-width:0 + the ellipsis trio, or a long expense name wraps to a second
   line and the row collides with its neighbour. A flex child defaults to
   min-width:auto, which refuses to shrink below its content. */
.et-activity-row-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-activity-row-group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    font-weight: 600;
    color: var(--et-color-ink-2);
    background: var(--et-color-surface-2);
    padding: 2px 8px;
    border-radius: 999px;
    flex-shrink: 0;
    white-space: nowrap;
}
/* Narrow: the group marker disappears entirely — no pill, no dot. Decision
   2026-08-08 (designer): the filter panel and the surrounding context (a
   group's own activity list, the group view's right column) already carry
   the group, so a phone row spends nothing on it. Diverges from the mobile
   canvas on purpose — the canvas still draws the dot ahead of the title and
   the payer avatar inline in the text line; revisit the canvas if this
   sticks.

   @container, not @media: the row should collapse whenever the BOX it sits in
   is narrow — a phone, but equally a docked side panel or the group view's
   right column. It also makes the branch visible in the gallery specimen,
   which a viewport query cannot be. The container is .et-surface (see the
   note there); 480px is the width below which the row needs to shed weight. */
@container (max-width: 480px) {
    .et-activity-row-group { display: none; }

    /* The payer micro-avatar moves off the text line and onto the category
       thumb's corner as a badge, so the payer line's TEXT starts flush under
       the title instead of carrying an 18px indent. Fixed pixel offsets, not
       percentages — they track .et-activity-row-thumb's fixed 46px box and
       .et-activity-row-main's 12px/10px padding, not the row's width, so
       they hold regardless of how wide the narrow container actually is. */
    .et-activity-row-main { position: relative; }
    .et-activity-row-payer-avatar {
        position: absolute;
        left: 56px; /* 10px padding-left + 46px thumb width */
        top: 58px;  /* 12px padding-top + 46px thumb height */
        transform: translate(-50%, -50%);
        box-shadow: 0 0 0 2px var(--et-color-surface);
    }
}
.et-activity-row-group-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
}
.et-activity-row-payer-line {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    color: var(--et-color-ink-2);
}
.et-activity-row-payer-avatar {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    color: #fff;
    display: grid;
    place-items: center;
    font-size: 8px;
    font-weight: 600;
    flex-shrink: 0;
}
.et-activity-row-payer-avatar--plum    { background: linear-gradient(135deg, #9c6dab, #7A4E7A); }
.et-activity-row-payer-avatar--rose    { background: linear-gradient(135deg, #e088b8, #c94f95); }
.et-activity-row-payer-avatar--green   { background: linear-gradient(135deg, #5cb985, #2e8b5c); }
.et-activity-row-payer-avatar--blue    { background: linear-gradient(135deg, #5b9bd5, #2e6db5); }
.et-activity-row-payer-avatar--mustard { background: linear-gradient(135deg, #f0b362, #d68f2e); }
.et-activity-row-payer-avatar--sand    { background: #ECEAE3; color: #8F8C83; }
.et-activity-row-payer-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-activity-row-payer-text b {
    font-weight: 600;
    color: var(--et-color-ink);
}
.et-activity-row-right {
    text-align: right;
    flex-shrink: 0;
    margin-left: 6px;
}
.et-activity-row-amount {
    font-size: 15px;
    font-weight: 700;
    color: var(--et-color-ink);
    white-space: nowrap;
}
.et-activity-row-share {
    font-size: 10.5px;
    font-weight: 600;
    white-space: nowrap;
}
.et-activity-row-share--red   { color: var(--et-color-owe); }
.et-activity-row-share--green { color: var(--et-color-owed); }
.et-activity-row-share--muted { color: var(--et-color-ink-3); }
.et-activity-row-divider {
    height: 1px;
    background: var(--et-color-border);
    margin: 2px 12px 0;
}

/* New-expense one-shot highlight (spec 2026-08-24 §3.3). GroupDetail.razor
   applies this via EtActivityRow's `Class` param to the row matching
   ?highlightExpenseId=; a C# timer clears it ~1.6s later regardless of
   which branch below ran, so it always ends up gone. Targets -main (not
   the row root) to reuse its existing 12px-radius padded box instead of
   painting a square-cornered band behind the divider too.
   Same accent tint as .et-share-row--me's "this row is different" fill
   (color-mix off --et-color-accent, no new color), just stronger — that
   one is a resting state, this is a few-second flash. */
.et-activity-row--new .et-activity-row-main {
    animation: et-activity-row-new-fade 1.5s ease-out forwards;
}
@keyframes et-activity-row-new-fade {
    from { background-color: color-mix(in srgb, var(--et-color-accent) 14%, transparent); }
    to   { background-color: transparent; }
}
/* Reduced motion: the tokens.css blanket rule (animation-duration:0.001ms)
   would turn the fade above into an imperceptible flash — the row needs to
   actually READ as highlighted while it's up, just without the motion. So
   the animation is disabled outright here and the tint held as a static
   background; the same C# timeout removes the class rather than an
   animationend. */
@media (prefers-reduced-motion: reduce) {
    .et-activity-row--new .et-activity-row-main {
        animation: none;
        background-color: color-mix(in srgb, var(--et-color-accent) 14%, transparent);
    }
}


/* ---------- EtNotifRow ---------- */
/* Top-aligned: a row can grow to three lines (title / meta+pill / reason
   quote), and the avatar, amount and unread dot must stay pinned to the
   first line rather than drift to the vertical middle. */
.et-notif-row {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 11px 16px;
    cursor: pointer;
    font-family: var(--et-font-family-base);
    transition: background-color .15s;
}
.et-notif-row:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-notif-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
.et-notif-row-avatar {
    position: relative;
    flex-shrink: 0;
}
/* Status marker on the avatar. Ring drawn in the surface colour so it reads as
   a cut-out over the avatar in both themes.

   This is the narrow row's status channel: in a narrow container the text pill
   is hidden (see the @container at the end of this block) and the marker
   carries the state alone.
   Glyphs are text, not emoji — an emoji would ignore `color` and muddy the disc. */
.et-notif-row-marker {
    position: absolute;
    right: -3px;
    bottom: -3px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    color: #fff;
    border: 2px solid var(--et-color-surface);
    display: grid;
    place-items: center;
    font-size: 9.5px;
    font-weight: 800;
    line-height: 1;
}
.et-notif-row-marker--received     { background: var(--et-color-pend); }
.et-notif-row-marker--sent         { background: var(--et-color-neutral); }
.et-notif-row-marker--confirmed    { background: var(--et-color-owed); }
.et-notif-row-marker--disputed     { background: var(--et-color-owe); }
.et-notif-row-marker--acknowledged { background: var(--et-color-neutral); }
.et-notif-row-marker--waived       { background: var(--et-color-owed); }
.et-notif-row-marker--deleted      { background: var(--et-color-neutral); }

/* Own icon set rather than text glyphs or emoji: one stroke weight, round caps,
   same optical size — so the seven markers read as a family and match the app's
   soft, rounded shapes. Emoji would ignore `color` and muddy a 16px disc. */
.et-notif-row-marker-icon {
    width: 11px;
    height: 11px;
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-width: 2.1;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.et-notif-row-marker-fill { fill: currentColor; stroke: none; }

/* Narrow-only: the status marker buys the room the pill gives up — the two
   swap in the same @container at the end of this block. Hidden is the DEFAULT
   rather than the branch, on purpose: a row rendered with no query container
   above it then falls back to the WIDE form (pill visible, marker off), which
   is the safe half. The inverse (`@container (min-width:…) { display:none }`)
   would give a container-less row the narrow form with no way to see it.
   The dispute marker has no --phone modifier, so it stays at every width. */
.et-notif-row-marker--phone { display: none; }
.et-notif-row-body { flex: 1; min-width: 0; padding-top: 1px; }
.et-notif-row-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-notif-row-title--unread { font-weight: 700; }
.et-notif-row-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin-top: 2px;
}
.et-notif-row-sub {
    font-size: 12px;
    color: var(--et-color-ink-2);
}
/* Status pill. Colours reuse the same semantic tokens as EtPill so all six
   themes stay in sync: pend = amber, owed = green, owe = red, neutral = grey. */
.et-notif-row-status {
    display: inline-flex;
    align-items: center;
    font-size: 10px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    white-space: nowrap;
}
.et-notif-row-status--amber   { background: var(--et-status-bg-pending); color: var(--et-color-pend); }
.et-notif-row-status--green   { background: #e7f4ec;                    color: var(--et-color-owed); }
.et-notif-row-status--red     { background: #fbebe8;                    color: var(--et-color-owe); }
.et-notif-row-status--neutral { background: var(--et-status-bg-logged);  color: var(--et-color-neutral); }
/* Narrow: title + sub + pill + amount does not fit. The avatar marker already
   says the same thing, so the pill goes, the marker comes in and the sub line
   gets the room back.

   @container, not @media: this row's two live homes are boxes, not screens —
   the notifications page card (.et-surface) and the bell dropdown's 340px menu
   (.et-notif-menu, given container-type for exactly this). The old
   min-width:960px / max-width:959.98px pair keyed off the viewport, which got
   the bell wrong in both directions: on a 1440px desktop it kept the pill
   inside a 340px menu, and it hid the pill on a 900px laptop where the
   notifications card is ~850px wide and has room to spare.

   480px, the same threshold EtActivityRow uses. Both are "an avatar + two text
   lines + a trailing amount, plus one optional pill" and start colliding at the
   same width, so the DS keeps ONE narrow-row breakpoint instead of two that
   drift. It also lands the two real containers on opposite sides: the bell menu
   (340px) is always narrow, the notifications card is narrow only on a phone. */
@container (max-width: 480px) {
    .et-notif-row-status { display: none; }
    .et-notif-row-marker--phone { display: grid; }
}
/* Reason quote — italic block with a 2px rule on the left. */
.et-notif-row-reason {
    margin-top: 6px;
    padding: 5px 10px;
    border-left: 2px solid var(--et-color-border);
    background: var(--et-color-surface-2);
    border-radius: 0 7px 7px 0;
    font-size: 11.5px;
    color: var(--et-color-ink-2);
    font-style: italic;
    line-height: 1.35;
}
.et-notif-row-amount {
    font-size: 13.5px;
    font-weight: 700;
    color: var(--et-color-ink);
    white-space: nowrap;
    flex-shrink: 0;
    padding-top: 1px;
}
.et-notif-row-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--et-color-accent);
    flex-shrink: 0;
    margin-top: 6px;
}


/* ---------- EtSettleItemRow ---------- */
.et-settle-row {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 12px 0;
    cursor: pointer;
    font-family: var(--et-font-family-base);
}
.et-settle-row--divider { border-bottom: 1px solid var(--et-color-border); }
/* The row the caller navigated from (expense panel → "Elszámolás X felé").
   Same soft-fill language as .et-share-row--me. */
.et-settle-row--source {
    margin: 0 -9px;
    padding: 12px 9px;
    border-radius: 10px;
    background: color-mix(in srgb, var(--et-color-accent) 7%, transparent);
    border-bottom-color: transparent;
}
.et-settle-row--source .et-settle-row-title { font-weight: 700; }
.et-settle-row-arrow {
    font-size: 16px;
    font-weight: 700;
    width: 14px;
    text-align: center;
    flex-shrink: 0;
}
.et-settle-row-arrow--in { color: var(--et-color-owed); }
.et-settle-row-arrow--out { color: var(--et-color-owe); }
.et-settle-row-image {
    width: 40px;
    height: 40px;
    border-radius: 11px;
    overflow: hidden;
    flex-shrink: 0;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.et-settle-row-image img { width: 100%; height: 100%; object-fit: cover; }
.et-settle-row-body { flex: 1; min-width: 0; }
.et-settle-row-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-settle-row-sub {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--et-color-ink-2);
    margin-top: 1px;
}
.et-settle-row-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}
.et-settle-row-amount {
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
    white-space: nowrap;
    flex-shrink: 0;
}
.et-settle-row-check {
    width: 24px;
    height: 24px;
    border-radius: 7px;
    background: var(--et-color-accent);
    color: #fff;
    display: grid;
    place-items: center;
    font-size: 13px;
    font-weight: 700;
    flex-shrink: 0;
}
/* The ⓘ disc. EtFieldRow's and EtSettingsRow's info buttons wear the same
   one, so their selectors join these two rules rather than copy them. */
.et-settle-row-info,
.et-field-info,
.et-settings-row-info {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--et-color-surface-2);
    display: grid;
    place-items: center;
    font-size: 13px;
    color: var(--et-color-ink-2);
    flex-shrink: 0;
}
/* Unselected check state (Checked=false) — empty outline, dimmed content. */
.et-settle-row-check--off {
    background: transparent;
    color: transparent;
    box-shadow: inset 0 0 0 1.5px var(--et-color-border);
}
.et-settle-row--off .et-settle-row-title,
.et-settle-row--off .et-settle-row-amount { color: var(--et-color-ink-3); }
/* Secondary ⓘ as a real button next to the check (Mud's bare button rule
   would restyle it — declare everything). */
.et-settle-row-info--btn,
.et-field-info,
.et-settings-row-info {
    border: 0;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
    justify-content: center;
}
/* The field and settings ⓘ sit on phone rows, beside the row's own tap
   target (the input, the picker), where a 24px disc is small. A transparent
   4px ring makes it 32px, and the negative margins shrink its footprint to
   24px across (the disc's own) and 16px down: a settings row's content is
   only 23px tall, so even the bare disc would add a pixel to its height. */
.et-field-info,
.et-settings-row-info {
    box-sizing: content-box;
    padding: 4px;
    margin: -8px -4px;
    background-clip: content-box;
}


/* ---------- EtSettingsRow ---------- */
.et-settings-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 15px 18px;
    cursor: pointer;
    font-family: var(--et-font-family-base);
    transition: background-color .15s;
}
.et-settings-row:hover { background-color: rgba(0, 0, 0, 0.02); }
.et-settings-row--locked {
    cursor: default;
}
.et-settings-row--locked:hover { background-color: transparent; }
.et-settings-row--divider { border-bottom: 1px solid var(--et-color-border); }
.et-settings-row-label {
    flex: 1;
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-settings-row-label--danger { color: var(--et-color-owe); }
/* With an ⓘ the label takes only its text's width, so the button sits right
   after the words, and its auto margin keeps value + chevron at the end. */
.et-settings-row--info .et-settings-row-label { flex: 0 1 auto; }
.et-settings-row-info { margin-right: auto; }
/* Colour dot ahead of the value (dc: `dot`) — the profile's Színstílus row
   shows the selected palette's accent so the row reads without opening. */
.et-settings-row-dot {
    width: 11px;
    height: 11px;
    border-radius: 50%;
    flex-shrink: 0;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
}
.et-settings-row-value {
    font-size: 13px;
    color: var(--et-color-ink-3);
    font-weight: 500;
    max-width: 55%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-settings-row-chevron {
    color: #C8C5BD;
    font-size: 18px;
    flex-shrink: 0;
}


/* ---------- EtToggleRow ---------- */
.et-toggle-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px;
    font-family: var(--et-font-family-base);
}
.et-toggle-row--disabled { opacity: 0.45; }
.et-toggle-row--divider { border-bottom: 1px solid var(--et-color-border); }
.et-toggle-row-body { flex: 1; min-width: 0; }
.et-toggle-row-label {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
}
.et-toggle-row-hint {
    font-size: 12px;
    color: var(--et-color-ink-2);
    margin-top: 1px;
    line-height: 1.4;
}
.et-toggle-row-track {
    width: 44px;
    height: 26px;
    border: 0;
    padding: 0;
    border-radius: 999px;
    background: #D8D4CA; /* dc literal off-track */
    position: relative;
    cursor: pointer;
    flex-shrink: 0;
    transition: background .15s;
}
.et-toggle-row-track--on { background: var(--et-color-owed); }
.et-toggle-row-track:disabled { cursor: default; }
.et-toggle-row-knob {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
    transition: transform .15s;
}
.et-toggle-row-track--on .et-toggle-row-knob { transform: translateX(18px); }
.et-toggle-row-track:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Second switch disabled on its own (the row stays bright). */
.et-toggle-row-track--disabled { opacity: 0.45; }
/* Column heads above two-switch rows: the label column is empty, each head
   sits over its 44px track, with the row's 12px gap and 18px side padding. */
.et-toggle-row-columns {
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    padding: 12px 18px 0;
    font-family: var(--et-font-family-base);
    font-size: 11px;
    font-weight: 600;
    color: var(--et-color-ink-2);
}
.et-toggle-row-columns > span {
    width: 44px;
    text-align: center;
}


/* ---------- EtCrudRow ---------- */
.et-crud-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 8px;
    border-radius: 12px;
    font-family: var(--et-font-family-base);
    transition: background-color .15s;
}
.et-crud-row:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-crud-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
/* Pick row (Checked set): the whole row is the tap target. The tick box sits
   where the ✎/🗑 pair would, at the same 8px right inset. */
.et-crud-row--pick {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.et-crud-row-tile {
    width: 38px;
    height: 38px;
    border-radius: 11px;
    background: var(--et-color-surface-2);
    display: grid;
    place-items: center;
    font-size: 17px;
    flex-shrink: 0;
}
.et-crud-row-body { flex: 1; min-width: 0; }
.et-crud-row-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-crud-row-sub {
    font-size: 12px;
    color: var(--et-color-ink-2);
    margin-top: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-crud-row-sub--muted { color: var(--et-color-ink-3); }
.et-crud-row-actions {
    display: flex;
    gap: 2px;
    flex-shrink: 0;
}
/* 40×40 (44 on a phone) rather than the original 30×30: at 30 the pencil was
   below every touch-target guideline and readers reported missing it. The tap
   area grows, the ROW does not — the negative vertical margin hands the extra
   height back to the flow, the same compensation .et-picker-strip makes for
   its own padding, so a 38px lead tile still sets the row's height. */
.et-crud-row-edit,
.et-crud-row-delete {
    width: 40px;
    height: 40px;
    margin-block: -1px;
    border: 0;
    padding: 0;
    background: none;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-family: inherit;
    font-size: 13.5px;
    cursor: pointer;
    transition: background-color .15s;
}
/* One box for both icons, so they are the same size and sit on one line. */
.et-crud-row-glyph {
    width: 19px;
    height: 19px;
    display: block;
}
.et-crud-row-edit { color: var(--et-color-ink-2); }
.et-crud-row-edit:hover { background-color: var(--et-color-surface-2); }
.et-crud-row-delete { color: var(--et-color-owe); }
.et-crud-row-delete:hover { background-color: #F9E5E3; } /* dc literal error tint */
.et-crud-row-edit:focus-visible,
.et-crud-row-delete:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
@media (max-width: 640px) {
    .et-crud-row-edit,
    .et-crud-row-delete {
        width: 44px;
        height: 44px;
        margin-block: -3px;
    }
}


/* ---------- EtDangerZone ---------- */
.et-danger-zone {
    background: rgba(192, 57, 43, 0.05);
    border: 1px solid var(--et-color-owe);
    border-radius: 16px;
    padding: 18px 20px;
    font-family: var(--et-font-family-base);
}
.et-danger-zone-title {
    font-size: 15px;
    font-weight: 800;
    color: var(--et-color-owe);
    margin-bottom: 6px;
}
.et-danger-zone-text {
    font-size: 13px;
    color: var(--et-color-ink-2);
    line-height: 1.5;
    margin-bottom: 14px;
    max-width: 760px;
}
.et-danger-zone-actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}


/* ---------- EtExpenseSummary ---------- */
.et-expense-summary {
    display: flex;
    align-items: center;
    gap: 12px;
    font-family: var(--et-font-family-base);
}
.et-expense-summary-body { flex: 1; min-width: 0; }
.et-expense-summary-title {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-expense-summary-sub {
    font-size: 12px;
    color: var(--et-color-ink-2);
}
.et-expense-summary-amount {
    font-size: 24px;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--et-color-ink);
    white-space: nowrap;
}


/* ---------- EtPartyCard ---------- */
.et-party-card {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 8px;
    background: var(--et-color-surface);
    border: 1px solid var(--et-color-border);
    border-radius: 14px;
    padding: 16px;
    font-family: var(--et-font-family-base);
}
.et-party-card-party {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}
.et-party-card-name {
    font-size: 12px;
    font-weight: 600;
    color: var(--et-color-ink);
}
.et-party-card-role {
    font-size: 10px;
    color: var(--et-color-ink-3);
}
.et-party-card-arrow {
    color: var(--et-color-accent);
    font-size: 20px;
}


/* ---------- EtEventCard ---------- */
.et-event-card {
    width: 100%;
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-lg);
    box-shadow: var(--et-shadow-card);
    overflow: hidden;
    font-family: var(--et-font-family-base);
    transition: transform .16s, box-shadow .16s;
}
@media (hover: hover) {
    .et-event-card:hover {
        transform: translateY(-3px);
        box-shadow: 0 10px 24px rgba(0, 0, 0, 0.10);
    }
}
/* Already happened. Same treatment the legacy carousel gives .uec-past —
   receded, not disabled: recording the expense afterwards is the normal
   case, so the CTA stays live and hover restores full contrast. */
.et-event-card--past {
    opacity: 0.7;
    filter: saturate(0.85);
}
@media (hover: hover) {
    .et-event-card--past:hover {
        opacity: 1;
        filter: none;
    }
}
/* Height follows the width instead of a fixed 150px: the card is narrower on
   a phone now (see .et-home-event / .et-group-view-event in app.css), and a
   frozen media height would have kept the whole shelf tall.

   1:1, not the 3:2 the compaction shipped (owner, 2026-09-24 — spec §1). At
   3:2 this card measured 218x190 against the pre-compaction card's 220x260:
   the CTA row went away and the media never got the height back, so the card
   read as a squashed version of itself rather than a shorter one. A square
   media puts it at 218x265 — the original proportion — with every pixel it
   takes back going to the illustration instead of to a button that already
   shouts from the FAB and from the event panel. The illustrations follow: the
   card/ set is the same drawing scaled up into a 240x240 canvas, bottom edge
   kept (tools/screens/derive-illustrations.mjs), so object-fit crops nothing
   here.

   The two pills are a flex row on the media's own box, and the image is the
   absolutely positioned layer under them (owner, 2026-09-26). They used to be
   two absolutes pinned to opposite corners, which knew nothing of each other:
   a long group name ran straight under the countdown. In one row the countdown
   keeps its width, the group pill takes what is left and ellipsises — at any
   card width, with no guess at how wide "Lejárt" or "12 nap" is. The pills
   stay position:relative so they paint above the positioned image. */
.et-event-card-media {
    position: relative;
    aspect-ratio: 1 / 1;
    height: auto;
    overflow: hidden;
    display: flex;
    align-items: flex-start;
    gap: 6px;
    padding: 10px;
    box-sizing: border-box;
}
.et-event-card-media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
/* margin-left:auto parks it on the right whether or not a group pill is in
   the row; the DOM order (badge first) is left alone, `order` puts the group
   pill ahead of it. */
.et-event-card-badge {
    position: relative;
    flex: none;
    order: 1;
    margin-left: auto;
    padding: 4px 11px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    backdrop-filter: blur(6px);
    background: rgba(255, 255, 255, 0.92);
    color: #1A1917; /* on white blur chip — fixed */
}
.et-event-card-badge--urgent {
    background: #C4392D;
    color: #fff;
}
/* A block, not the flex row it was: the name is a bare text node, and a flex
   container would wrap it in an anonymous item that text-overflow cannot
   reach. As a block the ellipsis applies to the pill's own line, and the dot
   rides in it as an inline-block. min-width:0 lets the flex row shrink it. */
.et-event-card-group {
    position: relative;
    display: block;
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: rgba(255, 255, 255, 0.92);
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 600;
    color: #1A1917;
    backdrop-filter: blur(6px);
}
.et-event-card-group-dot {
    display: inline-block;
    vertical-align: middle;
    margin-right: 5px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
}
/* Media + title/date open the event summary panel (F-38). Only the two
   regions the card hands an OnOpen to carry this class, so a card without
   the callback keeps the old, inert cursor. */
.et-event-card-open { cursor: pointer; }
.et-event-card-open:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: -2px;
}
/* One row: title/date on the left, the pencil on the right. It used to be two
   stacked rows with the add-expense CTA underneath — dropping that row and
   pulling the pencil up beside the title is most of the height this card gave
   back (2026-09-19 compaction spec §1). */
.et-event-card-body {
    padding: 12px 14px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
/* min-width:0 so the title's ellipsis can actually fire — a flex item's
   automatic minimum would otherwise push the pencil out of the card. */
.et-event-card-head { min-width: 0; }
.et-event-card-title {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin-bottom: 2px;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-event-card-date {
    font-size: 11px;
    color: var(--et-color-ink-2);
    font-weight: 500;
    margin-bottom: 0;
}


/* ---------- EtEventCardLarge (pilot) ----------
   ── pilot toggle (temporary, removed once the owner decides) ──
   The EtEventCard block above as it stood at 6813021a, the last commit before
   the 2026-09-19 compaction (#450), under its own `et-event-card-lg*` root so
   the two variants never fight over a selector. Picked by
   ThemeService.EventCard == "large" at the HomePage / GroupDetail call sites;
   owner request 2026-09-22, deleted whole when the A/B ends.

   Everything here is a verbatim copy of that commit apart from the class names
   — including the fixed 150px media height the compacted card replaced with a
   3:2 aspect-ratio. That height IS the variable under test. */
.et-event-card-lg {
    width: 100%;
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-lg);
    box-shadow: var(--et-shadow-card);
    overflow: hidden;
    font-family: var(--et-font-family-base);
    transition: transform .16s, box-shadow .16s;
}
@media (hover: hover) {
    .et-event-card-lg:hover {
        transform: translateY(-3px);
        box-shadow: 0 10px 24px rgba(0, 0, 0, 0.10);
    }
}
/* Already happened. Same treatment the legacy carousel gives .uec-past —
   receded, not disabled: recording the expense afterwards is the normal
   case, so the CTA stays live and hover restores full contrast. */
.et-event-card-lg--past {
    opacity: 0.7;
    filter: saturate(0.85);
}
@media (hover: hover) {
    .et-event-card-lg--past:hover {
        opacity: 1;
        filter: none;
    }
}
/* The one change from the verbatim copy that is not a class name (2026-09-26):
   the pills are the same flex row the compacted card uses — see the note on
   .et-event-card-media — because the long-group-name overlap is a bug in both
   variants, not part of what the A/B compares. The 150px box is untouched. */
.et-event-card-lg-media {
    position: relative;
    height: 150px;
    overflow: hidden;
    display: flex;
    align-items: flex-start;
    gap: 6px;
    padding: 10px;
    box-sizing: border-box;
}
.et-event-card-lg-media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.et-event-card-lg-badge {
    position: relative;
    flex: none;
    order: 1;
    margin-left: auto;
    padding: 4px 11px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    backdrop-filter: blur(6px);
    background: rgba(255, 255, 255, 0.92);
    color: #1A1917; /* on white blur chip — fixed */
}
.et-event-card-lg-badge--urgent {
    background: #C4392D;
    color: #fff;
}
.et-event-card-lg-group {
    position: relative;
    display: block;
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: rgba(255, 255, 255, 0.92);
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 600;
    color: #1A1917;
    backdrop-filter: blur(6px);
}
.et-event-card-lg-group-dot {
    display: inline-block;
    vertical-align: middle;
    margin-right: 5px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
}
/* Media + title/date open the event summary panel (F-38). Only the two
   regions the card hands an OnOpen to carry this class, so a card without
   the callback keeps the old, inert cursor. */
.et-event-card-lg-open { cursor: pointer; }
.et-event-card-lg-open:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: -2px;
}
.et-event-card-lg-body { padding: 13px 15px 15px; }
.et-event-card-lg-title {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin-bottom: 2px;
    color: var(--et-color-ink);
}
.et-event-card-lg-date {
    font-size: 11px;
    color: var(--et-color-ink-2);
    font-weight: 500;
    margin-bottom: 11px;
}
.et-event-card-lg-actions {
    display: flex;
    gap: 7px;
    align-items: center;
}
.et-event-card-lg-cta {
    flex: 1;
    min-width: 0;
}
/* R5-07's create tile is a strip child like any card, and its min-height is a
   floor set just under the shortest real card so it never decides the shelf's
   height. The large variant's cards are ~240px, so the compacted floor (160px)
   would make the tile the odd short one out — this restores the floor the
   shelf had before #450, for the large variant only. */
html[data-event-card="large"] .et-event-add-card { min-height: 240px; }
/* ---------- end EtEventCardLarge (pilot) ---------- */


/* ---------- EtEventAddCard ----------
   R5-07: the "Új esemény" affordance, moved out of the section header onto the
   end of the events shelf. It is a strip child like any card, so height comes
   from the flex row's stretch — the min-height only guards the case where it
   would be the tallest thing in it. Dashed border + no surface fill is what
   separates it from the real cards beside it; hover/focus pulls the border and
   the disc to the accent. */
.et-event-add-card {
    width: 100%;
    height: 100%;
    /* Below the shortest real card, so it is still only a floor and never the
       thing that sets the shelf's height — the compaction spec §1/6 asks for
       one height across the shelf. 250px since the media went 1:1 (a 200px
       wide card now measures ~247px); it was 160 while the media was 3:2. */
    min-height: 250px;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Explicit, per the DS button rule — a global reset must not decide it. */
    justify-content: center;
    gap: 11px;
    padding: 16px;
    background: transparent;
    border: 1.5px dashed var(--et-color-border-strong);
    border-radius: var(--et-card-radius-lg);
    color: var(--et-color-ink-2);
    font-family: var(--et-font-family-base);
    cursor: pointer;
    transition: border-color .16s, color .16s, background .16s;
}
.et-event-add-card-disc {
    width: 44px;
    height: 44px;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--et-color-accent) 10%, transparent);
    color: var(--et-color-accent);
    transition: background .16s;
}
.et-event-add-card-label {
    font-size: 13px;
    font-weight: 600;
    letter-spacing: -0.01em;
    text-align: center;
}
.et-event-add-card:hover,
.et-event-add-card:focus-visible {
    border-color: var(--et-color-accent);
    color: var(--et-color-ink);
    background: color-mix(in srgb, var(--et-color-accent) 4%, transparent);
}
.et-event-add-card:hover .et-event-add-card-disc,
.et-event-add-card:focus-visible .et-event-add-card-disc {
    background: color-mix(in srgb, var(--et-color-accent) 18%, transparent);
}
.et-event-add-card:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: -2px;
}


/* ---------- EtCardStrip ----------
   Horizontal shelf for fixed-width cards. The min-width:0 is belt-and-braces
   only: a scroll container already contributes zero to its ancestors'
   min-content sizing, so the shelf does not blow out a grid track on its own
   (measured — removing it changes nothing). It is here so a future caller
   that wraps the strip in a flex/grid item of its own can't reintroduce an
   automatic minimum. */
.et-card-strip-wrap {
    position: relative;
    min-width: 0;
}
.et-card-strip {
    display: flex;
    gap: 14px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-behavior: smooth;
    /* Breathing room for the card shadow, which the hidden scrollbar no
       longer reserves. The paddles' top offset compensates for it. */
    padding-bottom: 10px;
    /* No visible scrollbar — the paddles and the partially-shown card at the
       edge carry the affordance. Both properties are needed: scrollbar-width
       covers Firefox and Chromium's overlay-scrollbar mode, where the
       pseudo-element rule alone is ignored. */
    scrollbar-width: none;
}
.et-card-strip::-webkit-scrollbar { display: none; }
.et-card-strip > * { flex: 0 0 auto; }

/* Edge fade — the same 28px mask the legacy .uec-carousel / .nac-strip use,
   ported so the shelf reads identically at every width. With the scrollbar
   hidden and the paddles only on hover, this is the one affordance that says
   "more cards this way" BEFORE the user touches anything. It follows
   data-can-scroll-*, so an edge the user has already reached is not faded.
   The paddles live on the wrap, outside the masked box, so they stay crisp.

   The LEFT fade additionally waits for data-user-scrolled="true" (set by
   etDs.attachStrip on a horizontal drag / wheel / paddle click). The events
   shelf opens anchored on the next event still ahead, so past cards sit
   off-screen to the left from the very first paint — a fade there would tell
   a first-time reader "you've already scrolled past something" when they
   haven't touched anything, and it's the only shelf on the page that would
   do so (the todo shelf below always opens on its first card). Once the user
   has moved the strip themselves — in either direction — both edges fade
   normally. The paddles are deliberately NOT gated on this: ‹ has to be
   clickable before there's any way to have scrolled. */
.et-card-strip-wrap[data-can-scroll-right="true"] .et-card-strip {
    -webkit-mask-image: linear-gradient(to right,
        black 0, black calc(100% - 28px), transparent 100%);
            mask-image: linear-gradient(to right,
        black 0, black calc(100% - 28px), transparent 100%);
}
.et-card-strip-wrap[data-user-scrolled="true"][data-can-scroll-left="true"] .et-card-strip {
    -webkit-mask-image: linear-gradient(to right,
        transparent 0, black 28px, black 100%);
            mask-image: linear-gradient(to right,
        transparent 0, black 28px, black 100%);
}
.et-card-strip-wrap[data-user-scrolled="true"][data-can-scroll-left="true"][data-can-scroll-right="true"] .et-card-strip {
    -webkit-mask-image: linear-gradient(to right,
        transparent 0, black 28px,
        black calc(100% - 28px), transparent 100%);
            mask-image: linear-gradient(to right,
        transparent 0, black 28px,
        black calc(100% - 28px), transparent 100%);
}

.et-card-strip-nav {
    position: absolute;
    /* Half the strip's bottom padding, so the paddle centres on the cards
       rather than on the card+scrollbar box. */
    top: calc(50% - 5px);
    transform: translateY(-50%);
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: 1px solid var(--et-color-border);
    background: var(--et-color-surface);
    color: var(--et-color-ink);
    font-size: 19px;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s;
    z-index: 2;
}
.et-card-strip-nav--prev { left: 6px; }
.et-card-strip-nav--next { right: 6px; }
/* Revealed on hover, and only on the side that still has cards to reach.
   Keyboard users get them on focus — pointer-events:none doesn't block
   tab focus, only the cursor. */
@media (hover: hover) {
    .et-card-strip-wrap[data-can-scroll-left="true"]:hover .et-card-strip-nav--prev,
    .et-card-strip-wrap[data-can-scroll-right="true"]:hover .et-card-strip-nav--next {
        opacity: 1;
        pointer-events: auto;
    }
}
.et-card-strip-nav:focus-visible {
    opacity: 1;
    pointer-events: auto;
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtTodoCard ---------- */
.et-todo-card {
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-md);
    box-shadow: var(--et-shadow-card);
    /* Row form (mobile compaction §2): 12/14 is EtBalanceRow's padding, so
       a todo row and a balance row sit on the same rhythm. */
    padding: 12px 14px;
    font-family: var(--et-font-family-base);
    min-width: 0;
    transition: transform .16s, box-shadow .16s;
}
/* The pointer cursor and the hover lift are gated on --clickable: they used
   to sit on the base class, so an ack-only card (no navigation target)
   invited a click that did nothing. Only a card wired to OnCardClick
   advertises itself as pressable now. */
.et-todo-card--clickable { cursor: pointer; }
/* Lifts only under a real pointer. A touch screen has no hover, but iOS WebKit
   keeps :hover on whatever sits under the last tap, so an ungated lift left the
   first to-do card raised and shadowed as if selected on app open (owner,
   2026-10-08). The other card lifts and the past-event restore below take the
   same gate. */
@media (hover: hover) {
    .et-todo-card--clickable:hover {
        transform: translateY(-3px);
        box-shadow: 0 10px 24px rgba(0, 0, 0, 0.10);
    }
}
.et-todo-card-head {
    display: flex;
    align-items: center;
    gap: 12px;
}
.et-todo-card-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.et-todo-card-person {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The status and its optional text action share one line; space-between
   parks the action at the body's right edge without a spacer element. */
.et-todo-card-subrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-width: 0;
}
.et-todo-card-amount {
    font-size: 17px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    flex-shrink: 0;
}
/* Same glyph and tint as .et-balance-row-chevron — the two row families
   must not disagree about what "this opens something" looks like. */
.et-todo-card-chevron {
    color: #C8C5BD;
    font-size: 20px;
    line-height: 1;
    flex-shrink: 0;
    margin-left: 4px;
}
.et-todo-card-status {
    font-size: 12px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-todo-card-status--amber { color: var(--et-color-pend); }
.et-todo-card-status--red   { color: var(--et-color-owe); }
.et-todo-card-status--green { color: var(--et-color-owed); }
/* Buttons are Sm and NOT Block: two half-width blocks read as a dialog's
   footer, which is too much weight for a row inside a shelf. */
/* The CTAs line up with the NAME, not with the card edge: starting them under
   the avatar read as a second, unrelated column. 52px = the 40px avatar plus
   .et-todo-card-head's 12px gap, so the buttons share the text column's left
   edge exactly. */
.et-todo-card-actions {
    display: flex;
    gap: 8px;
    margin-top: 10px;
    margin-left: 52px;
}

/* ── Compact form (spec 2026-09-24 §4) ──
   The same row in the events shelf's 220px footprint, so the two shelves that
   sit under one another on the phone canvas read as one system. Everything
   here is a modifier on the row above rather than a second component: the DOM
   is identical and only the flow changes, which is a CSS job. The variant is
   reachable through the et:todo-card pilot ("compact"); the row stays the
   default until the owner decides on a device.

   Since 2026-09-29 the amount sits under the avatar, the way EtTodoCardLarge
   has it (owner: "az összeg lehetne egy sorral lentebb hasonlóan az eredeti
   tennivaló kártyához és kicsit nagyobban"), and the name is the balance
   rows' 14/600 — at 700 it outweighed the names in "Akik neked tartoznak":

       [avatar 36]  Jay            name 14/600, beside the avatar
       4 500 Ft                    amount 20/800 (15 before, the big card 24)
       Megerősítésre vár           status, from the card's left edge
       [ Megkaptam ] [ Vitatás ]   only under et:todo-actions = shown

   The head becomes a grid and the body `display: contents`, so the name, the
   status line and the amount — three siblings in the row's DOM — land in
   their own areas without a second markup. (The first cut, #469, wrapped a
   flex head instead: the amount right-aligned on a line of its own and the
   CTAs stacked full width — 175px tall where the grid was 128.) */
.et-todo-card--compact { padding: 12px; }
.et-todo-card--compact .et-todo-card-head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "avatar person"
        "amount amount"
        "status status";
    /* Rows first: the row's 12px gap would otherwise open between the lines;
       each line sets its own distance below. */
    gap: 0 10px;
    align-items: center;
}
.et-todo-card--compact .et-todo-card-head > .et-avatar { grid-area: avatar; }
.et-todo-card--compact .et-todo-card-body { display: contents; }
.et-todo-card--compact .et-todo-card-person { grid-area: person; }
/* EtTodoCardLarge's rhythm: 8px under the head, 2px to the status. */
.et-todo-card--compact .et-todo-card-amount {
    grid-area: amount;
    margin-top: 8px;
    font-size: 20px;
    letter-spacing: -0.02em;
    line-height: 1.2;
}
/* A full row of its own, so it may take two lines rather than ellipsise
   "Visszautasította a fizetésedet" at the narrow pilot sizes. */
.et-todo-card--compact .et-todo-card-subrow {
    grid-area: status;
    margin-top: 2px;
}
.et-todo-card--compact .et-todo-card-status { white-space: normal; }
/* The whole card is the target at this width, and the shelf has no room for a
   glyph that only repeats what the cursor already says. */
.et-todo-card--compact .et-todo-card-chevron { display: none; }
/* Side by side, like the big card's pair: halves, flex:1 on a zero basis.
   8px sides instead of Sm's 14 — the big card's own halves squeeze its
   "Megkaptam" to ~7px a side anyway, and at 8 the pair stays on one line at
   every compact size, mini (~159px inside) included. The max-content floor is
   what the big card lacks: a label is never clipped — a longer one (English
   "I received it") takes its own width and the other button the rest, and
   where even that cannot fit, the second button wraps under the first. The
   avatar indent the row uses goes: at this width it would cost a third of the
   row. */
.et-todo-card--compact .et-todo-card-actions {
    flex-wrap: wrap;
    margin-left: 0;
    margin-top: 10px;
}
.et-todo-card--compact .et-todo-card-actions > .et-btn {
    flex: 1 1 0;
    min-width: max-content;
    padding-left: 8px;
    padding-right: 8px;
}


/* ---------- EtTodoCardLarge (pilot) ----------
   ── pilot toggle (temporary, removed once the owner decides) ──
   The EtTodoCard block above as it stood at 1a4d6dd0, the last commit before
   #452 turned the card into a row, under its own `et-todo-card-lg*` root so
   the two variants never fight over a selector. Picked by
   ThemeService.TodoCard == "card" in NeedsAttentionCarousel; owner request
   2026-09-22, deleted whole when the A/B ends.

   Verbatim from that commit apart from the class names — including the 16/18px
   padding, the 24px amount and the stacked block CTAs, which together are the
   ~173px height the row variant traded away. That height IS the variable under
   test. */
.et-todo-card-lg {
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-md);
    box-shadow: var(--et-shadow-card);
    padding: 16px 18px;
    font-family: var(--et-font-family-base);
    min-width: 0;
    transition: transform .16s, box-shadow .16s;
}
/* The pointer cursor and the hover lift are gated on --clickable: they used
   to sit on the base class, so an ack-only card (no navigation target)
   invited a click that did nothing. Only a card wired to OnCardClick
   advertises itself as pressable now. */
.et-todo-card-lg--clickable { cursor: pointer; }
@media (hover: hover) {
    .et-todo-card-lg--clickable:hover {
        transform: translateY(-3px);
        box-shadow: 0 10px 24px rgba(0, 0, 0, 0.10);
    }
}
.et-todo-card-lg-head {
    display: flex;
    align-items: center;
    gap: 11px;
    margin-bottom: 8px;
}
.et-todo-card-lg-person {
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-todo-card-lg-amount {
    font-size: 24px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--et-color-ink);
    margin-bottom: 2px;
}
.et-todo-card-lg-status {
    font-size: 12px;
    font-weight: 600;
    margin-bottom: 12px;
}
/* No CTA row under it (et:todo-actions = hidden): the gap it kept for the
   buttons would be dead space at the card's foot. */
.et-todo-card-lg-status:last-child { margin-bottom: 0; }
.et-todo-card-lg-status--amber { color: var(--et-color-pend); }
.et-todo-card-lg-status--red   { color: var(--et-color-owe); }
.et-todo-card-lg-status--green { color: var(--et-color-owed); }
.et-todo-card-lg-actions {
    display: flex;
    gap: 8px;
}
.et-todo-card-lg-cta {
    flex: 1;
    min-width: 0;
}
/* ---------- end EtTodoCardLarge (pilot) ---------- */


/* ---------- EtGroupCard ---------- */
/* v3 (owner feedback 2026-09-25): a small EtGroupHeader over a status strip.
   The band carries the group's identity — name, avatars, "N tag" — on its own
   tint with its picture on the right, exactly the header's arrangement; the
   white strip under it says where you stand: balance, PRO status and, in a
   group's first week, the early-bird offer. See the component's header
   comment for the owner's words. */
.et-group-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    background: var(--et-color-surface);
    /* 18, not the dc's 20: at half the card's old height a 20px corner read as
       a pill rather than as a card. */
    border-radius: var(--et-card-radius-lg);
    box-shadow: var(--et-shadow-card);
    font-family: var(--et-font-family-base);
    color: var(--et-color-ink);
    cursor: pointer;
    min-width: 0;
    transition: transform .16s, box-shadow .16s;
}
@media (hover: hover) {
    .et-group-card:hover {
        transform: translateY(-2px);
        box-shadow: var(--et-shadow-card-hover);
    }
}
/* The band. Its height is its two lines of text plus padding (~78px), the
   header's rule — so the picture, bound to that height, scales with it. */
.et-group-card-cover {
    position: relative;
    display: block;
    flex-shrink: 0;
    overflow: hidden;
}
/* Above the picture, which is absolutely positioned before it. */
.et-group-card-head {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px 16px 15px;
    min-width: 0;
}
/* The band illustration, sized exactly like EtGroupHeader's: bound to the
   band's HEIGHT and parked right-bottom inside the same 58% slot, so the 5:2
   motif scales with the band, never crops, and shrinks rather than runs under
   the name on a narrow card. */
.et-group-card-illu {
    position: absolute;
    inset: 0 0 0 auto;
    width: 58%;
    display: flex;
    justify-content: flex-end;
    align-items: flex-end;
    pointer-events: none;
}
.et-group-card-illu img {
    height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    object-position: right bottom;
    display: block;
}
/* R3-08/D6, owner correction 2026-09-16 — the group photo on the band's right
   side. A STRAIGHT vertical fade, no clip-path: on a band this short a
   diagonal cut is too short to read as a designed edge. Everything here is
   scoped to a photo being present, which is what keeps a photo-less band
   pixel-identical.

   v3 put the name and the meta line on this band, so the photo now fades IN
   with a mask instead of under a band-coloured overlay. v2's overlay was a
   flat colour (the band's END colour) painted over a gradient band: it met the
   band at 40% with a visible vertical seam, and with only 4% of it solid the
   grey meta text ran onto a half-revealed dark photo. A mask has no colour of
   its own, so the photo dissolves into whatever the band gradient is at that
   point — no seam — and it can start late: invisible to ~58% of the card,
   fully itself from ~76%. */
.et-group-card-photo {
    position: absolute;
    inset: 0 0 0 auto;
    width: 60%;
    height: 100%;
    object-fit: cover;
    display: block;
    -webkit-mask-image: linear-gradient(90deg, transparent 30%, #000 60%);
    mask-image: linear-gradient(90deg, transparent 30%, #000 60%);
}
/* Over a photo the text ends a little past where the photo starts to show,
   and ellipsises there rather than run onto the picture. */
.et-group-card-cover--imaged .et-group-card-head { max-width: 64%; }
/* Title row: glyph + name, the name ellipsises. Colours are the header's
   fixed on-band inks — the band tints stay light in both themes. */
.et-group-card-title {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
}
/* No overflow:hidden on the glyph: an emoji paints past its line box, and a
   clip at line-height 1 shaves its bottom off. */
.et-group-card-icon {
    font-size: 17px;
    line-height: 1;
    flex-shrink: 0;
}
.et-group-card-name {
    min-width: 0;
    font-size: 18px;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.2;
    color: #1A1917; /* on the light band — fixed, as .et-group-header-title */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* The ellipsis needs overflow:hidden, which clips at the padding box — and
       Plus Jakarta's g/j/p/y descenders and the Ő/Á accents reach past a 1.2
       line box. The padding gives the clip that room; the matching negative
       margin keeps the row exactly as tall as before. */
    padding-block: 0.12em;
    margin-block: -0.12em;
}
.et-group-card-meta-line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.et-group-card-avatars {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}
/* The white ring is what makes the stack read as overlapping rather than as
   one blurred blob; the negative margin is skipped on the first face. */
.et-group-card-face {
    border: 1.5px solid #fff;
    margin-left: -6px;
}
.et-group-card-face:first-child { margin-left: 0; }
.et-group-card-face--more {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.8);
    color: #6B6860;
    font-size: 9.5px;
    font-weight: 700;
    flex-shrink: 0;
}
.et-group-card-meta {
    min-width: 0;
    font-size: 12px;
    font-weight: 500;
    color: #6B6860; /* on the light band — fixed, as .et-group-header-meta */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-group-card-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    flex: 1;
    min-width: 0;
    padding: 12px 16px 14px;
}
/* Balance left, PRO status right; the balance gives way first. */
.et-group-card-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-width: 0;
}
.et-group-card-balance {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    white-space: nowrap;
}
.et-group-card-balance-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--et-color-ink-2);
}
.et-group-card-balance-amount {
    font-size: 15px;
    font-weight: 800;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
}
.et-group-card-balance--owe .et-group-card-balance-amount { color: var(--et-color-owe); }
.et-group-card-balance--owed .et-group-card-balance-amount { color: var(--et-color-owed); }
.et-group-card-balance--settled .et-group-card-balance-label { color: var(--et-color-ink-3); }
.et-group-card-balance--spend .et-group-card-balance-amount { color: var(--et-color-ink); }
/* V6 entitlement label (spec §2.4). Shares the fixed PRO token
   (--et-color-pro-bg/-ink, declared above the EtPill block) with the header
   chip and the CTA badge — GroupsList.razor passes --et-color-pro-ink as
   EntitlementColor so the text/dot inherit it too. font-weight is the
   .et-pro-rest baseline (Wave 3 spec §6.2) — the "PRO" prefix renders through
   EtProLabel at .et-pro-mark's heavier weight. */
.et-group-card-entitlement {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 999px;
    background: var(--et-color-pro-bg);
    font-size: 10.5px;
    font-weight: 600;
    white-space: nowrap;
}
.et-group-card-entitlement-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
}
/* A temporary or one-time kassza's countdown in the same slot (StatusLabel, spec
   2026-10-09-ai-badges-design.md §3): the PRO pill's box in the neutral chip's
   colours (.et-pill--neutral), so gold keeps meaning "paid". */
.et-group-card-entitlement--neutral {
    background: var(--et-status-bg-logged);
    color: var(--et-color-neutral);
}
.et-group-card-entitlement--neutral .et-group-card-entitlement-dot { background: currentColor; }
/* The early-bird offer (owner pick 2026-09-25: a button that opens the
   paywall). The PRO gold at a soft mix rather than solid, so the one real
   button on the card does not out-shout the PRO pill it sits under; the ink
   is the PRO ink. Mixed into white, not into --et-color-surface: the PRO ink
   is a fixed near-black, and a mix into the dark theme's surface would put it
   on a near-black fill. Like the PRO pill, the button is light in both
   themes. Full width, left-aligned text, arrow at the far end. */
.et-group-card-offer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 9px 12px;
    border: 1px solid color-mix(in srgb, var(--et-color-pro-bg) 70%, transparent);
    border-radius: 11px;
    background: color-mix(in srgb, var(--et-color-pro-bg) 22%, #fff);
    color: var(--et-color-pro-ink);
    font-family: inherit;
    font-size: 13px;
    font-weight: 700;
    text-align: left;
    cursor: pointer;
    transition: background .16s;
}
.et-group-card-offer:hover {
    background: color-mix(in srgb, var(--et-color-pro-bg) 38%, #fff);
}
.et-group-card-offer:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-group-card-offer-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-group-card-offer-arrow { flex-shrink: 0; }


/* ---------- EtAllocationBanner ---------- */
.et-allocation-banner {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 11px 14px;
    background: #E7F4EC; /* dc literal owed-tint (cf. EtPill owed bg) */
    border-radius: 11px;
    font-size: 13px;
    font-weight: 700;
    color: var(--et-color-owed);
    font-family: var(--et-font-family-base);
}
.et-allocation-banner--warn {
    background: var(--et-status-bg-pending);
    color: var(--et-color-pend);
}


/* ---------- EtPanelHeader ---------- */
.et-panel-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 20px 14px;
    border-bottom: 1px solid var(--et-color-border);
    font-family: var(--et-font-family-base);
}
/* min-width: 0 lets the title/subtitle actually shrink and ellipsize instead
   of pushing the close button out of the row — a flex child defaults to
   min-width:auto, which refuses to shrink below its content (same trap as
   .et-activity-row-title). Both this panel header and every name-bearing
   title/subtitle it hosts (settle-up, expense/payment/split detail) can
   carry an arbitrarily long display name. */
.et-panel-header-body { flex: 1; min-width: 0; }
.et-panel-header-title {
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-panel-header-subtitle {
    font-size: 12px;
    color: var(--et-color-ink-2);
    margin-top: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* TitleContent: a caller's control in the title slot. The text slot's clip has
   to go — a dropdown anchored here hangs its menu below the title on desktop,
   and overflow:hidden on an ancestor of its containing block would cut it off. */
.et-panel-header-title--custom { overflow: visible; }
/* A button that reads as the title: the slot's font, size and colour, no
   chrome — only the chevron says it opens something. */
.et-panel-header-title-btn {
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    max-width: 100%;
    border: 0;
    padding: 0;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}
/* The EtFieldRow dropdown triangle (▾/▴), bare, in the accent colour. A bare grey glyph read
   as decoration, not as "this opens a menu" (owner, 2026-10-05); the accent discs that
   followed shouted (feedback 2026-10-18 F10) — the owner's call is the simplest form: no disc,
   only the colour says it is tappable. The accent follows the palette, dark theme included. */
.et-panel-header-title-chevron {
    flex: none;
    font-size: 13px;
    line-height: 1;
    color: var(--et-color-accent);
}
.et-panel-header-title-btn:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
    border-radius: 6px;
}
/* Back shares the close chrome — a round button leading the header (left of
   the title), rendered only when the panel sits on another panel (ShowBack).
   The header's own 12px gap spaces it; no extra margin needed. */
.et-panel-header-close,
.et-panel-header-back {
    width: 34px;
    height: 34px;
    border: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--et-color-surface-2);
    display: grid;
    place-items: center;
    font-family: inherit;
    font-size: 15px;
    color: var(--et-color-ink-2);
    cursor: pointer;
    flex-shrink: 0;
    transition: background-color .15s;
}
.et-panel-header-back {
    /* The ‹ glyph rides low and small at the shared 15px — nudge it. */
    font-size: 20px;
    line-height: 1;
    padding-bottom: 3px;
}
.et-panel-header-close:hover,
.et-panel-header-back:hover { background-color: #E6E2D8; }
.et-panel-header-close:focus-visible,
.et-panel-header-back:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtPanel ----------
   One shell, two forms: the right-hand 460px side panel on a wide box, the
   design's 90%-height bottom sheet on a narrow one. The sheet used to be its
   own component (EtSheetFrame); it had no production caller and the two
   shells had already drifted a few pixels apart, so it is folded in here and
   the SHEET's values won — those are the ones drawn in the mobile design.

   The dc frames the shell with absolute positioning inside the canvas;
   in-app it is a fixed full-viewport layer.

   Why this atom may branch on @container when EtTopBar / EtFilterPanel may
   not: container-type brings layout containment, which makes the element the
   containing block for position:fixed descendants — and this panel really
   does host fixed things (EtDropdown's backdrop and its phone sheet, opened
   from ExpenseNew / GroupNew / RecipientEditPanel). Two facts make it safe:

   1. Overlay mode: .et-panel is already `position: fixed; inset: 0`, so its
      box IS the viewport. Trapping a fixed descendant inside a box that
      already equals the viewport is a no-op, and the query answers the same
      width a media query would. Previewability for free.
   2. Docked mode: the panel fills PanelOutlet's ~460px rail, where the same
      containment WOULD trap those overlays into the rail. So docked gets
      `container-type: normal` — no containment introduced at all. That costs
      nothing, because PanelOutlet only renders its rail from 960px up (it
      answers that breakpoint itself now that MudHidden is gone): a docked
      panel is desktop-only and never needs the sheet.

   Belt and braces, the sheet rules are additionally scoped
   `:not(.et-panel--docked)`, so even if some future ancestor becomes a
   container, a docked panel still cannot morph into a bottom sheet. */
.et-panel {
    position: fixed;
    inset: 0;
    z-index: 1400;
    container-type: inline-size;
    font-family: var(--et-font-family-base);
}
.et-panel--inline { position: absolute; } /* gallery/demo containment */
/* Docked mode (PanelOutlet host): the outlet owns the fixed right rail and
   the slide animation; the panel just fills it. No dimming overlay — the
   background page stays live. container-type off, see (2) above. */
.et-panel--docked {
    position: static;
    inset: auto;
    z-index: auto;
    height: 100%;
    container-type: normal;
}
.et-panel--docked .et-panel-shell {
    position: relative;
    top: auto;
    right: auto;
    bottom: auto;
    width: 100%;
    height: 100%;
}
/* No body scroll lock while the overlay panel is open — a position:fixed
   body breaks iOS keyboard avoidance (focusing an input pans the visual
   viewport and the pan sticks after the keyboard closes: taps land offset
   from the pixels). Instead the background is shielded gesture-side: the
   overlay swallows touch pans (taps still click, so tap-to-close works),
   and the panel's scroll area refuses to chain its overscroll into the
   page. etSheetDrag covers the not-scrollable-content case in JS. */
.et-panel-overlay { touch-action: none; }
.et-panel-scroll { overscroll-behavior-y: contain; }
.et-panel-overlay {
    position: absolute;
    inset: 0;
    background: rgba(26, 25, 23, 0.32);
    backdrop-filter: blur(2px);
    cursor: pointer;
}
.et-panel-shell {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: var(--etp-width, 460px);
    max-width: 100vw;
    background: var(--et-color-bg);
    box-shadow: -12px 0 40px rgba(0, 0, 0, 0.18);
    display: flex;
    flex-direction: column;
}
/* The overlay form takes focus when it opens (EtPanel, etDs.focusIn); same as .et-dialog. */
.et-panel-shell:focus { outline: none; }
.et-panel-scroll {
    flex: 1;
    overflow-y: auto;
    scrollbar-width: none;
}
/* 16px under the header's rule: the same gap the pages keep under the top bar
   (owner, 2026-10-02), so a first block of any kind needs no top spacing of
   its own. */
.et-panel-body { padding: 16px 20px 20px; }
.et-panel-footer {
    padding: 14px 20px;
    border-top: 1px solid var(--et-color-border);
    background: rgba(250, 248, 243, 0.92); /* dc literal — bg tint at 92% */
    backdrop-filter: blur(8px);
    flex-shrink: 0;
}
.et-panel-footer-row {
    display: flex;
    gap: 10px;
}
.et-panel-footer-row--stacked {
    flex-direction: column;
    gap: 8px;
}
.et-panel-footer-cta {
    flex: 1;
    min-width: 0;
}
.et-panel-footer-note {
    text-align: center;
    font-size: 11px;
    color: var(--et-color-ink-3);
    margin-top: 8px;
}
/* FooterMenu (owner 2026-10-01): a zero-height strip on the body/footer seam,
   inset like the footer's padding so a stretched menu is exactly as wide as
   the footer button. The menu rises from it (bottom, not top) on desktop; the
   phone sheet below 640px is the dropdown's own position:fixed form, which is
   why the strip sits outside the footer — its backdrop-filter would contain a
   fixed descendant. */
.et-panel-footer-menu {
    position: relative;
    height: 0;
    margin: 0 20px;
    flex-shrink: 0;
}
@media (min-width: 641px) {
    .et-panel-footer-menu .et-dropdown-menu { top: auto; bottom: 6px; }
}

/* Shared sheet keyframes — kept OUTSIDE the branch below because they have
   two users: this panel's sheet form and .et-dropdown-menu's phone sheet
   (et-sheet-rise); et-fade is used by the sheet overlay here only.
   (app.css used to declare a duplicate et-sheet-rise for the retired
   BottomSheet component; that went with the component in the E round.) */
@keyframes et-sheet-rise {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
}
@keyframes et-fade {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* ── pilot toggle (temporary): et:motion — the sheet's exit ──
   The way out mirrors the way in: EtPanel.CloseAsync stamps .et-panel--leaving
   (through etMotion.leave, app-init.js) and waits for this slide before it
   reports the close, because the caller unmounts the panel the moment it
   hears about it. Sheet form only — the 640+ side panel has no entry
   animation either, so it keeps closing in place. `forwards` holds the sheet
   off-screen for the frame or two until the unmount lands. Specificity (0,4,1)
   outranks the rise below. */
@keyframes et-sheet-drop {
    from { transform: translateY(0); }
    to { transform: translateY(100%); }
}
@keyframes et-fade-out {
    from { opacity: 1; }
    to { opacity: 0; }
}
@container (max-width: 640px) {
    html:not([data-motion="off"]) .et-panel--leaving:not(.et-panel--docked) .et-panel-shell {
        animation: et-sheet-drop var(--et-motion-exit) var(--et-ease-exit) forwards;
    }
    html:not([data-motion="off"]) .et-panel--leaving:not(.et-panel--docked) .et-panel-overlay {
        animation: et-fade-out var(--et-motion-exit) var(--et-ease-exit) forwards;
    }
}

/* Sheet form (narrow box): the side panel becomes the design's BottomSheet90
   with a grabber. Padding and overlay tint come from the old EtSheetFrame —
   the panel's own phone branch had drifted to 0.32 dim and 20px gutters. */
@container (max-width: 640px) {
    .et-panel:not(.et-panel--docked) .et-panel-overlay {
        background: rgba(26, 25, 23, 0.4);
        animation: et-fade 0.2s ease-out;
    }
    .et-panel:not(.et-panel--docked) .et-panel-shell {
        top: auto;
        left: 0;
        width: 100%;
        height: 90%;
        border-top-left-radius: 24px;
        border-top-right-radius: 24px;
        box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.22);
        animation: et-sheet-rise 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    /* Grabber as a pseudo-element. EtSheetFrame shipped a real
       .et-sheet-grabber div; identical pixels, one DOM node cheaper. */
    .et-panel:not(.et-panel--docked) .et-panel-shell::before {
        content: "";
        width: 40px;
        height: 4px;
        background: #D8D4CA; /* dc literal grabber */
        border-radius: 2px;
        margin: 12px auto 0;
        flex-shrink: 0;
    }
    /* Locked (EtPanel.Locked): it never swipes away, so no grabber promising it does. */
    .et-panel--locked:not(.et-panel--docked) .et-panel-shell::before { display: none; }
    .et-panel:not(.et-panel--docked) .et-panel-body { padding: 16px 16px 20px; }
    /* No bounce at the scroll area's edges in the sheet form (owner, 2026-10-14). A pull
       down from the top drags the sheet (etSheetDrag), and the browser's own overscroll —
       iOS's rubber band, Android's stretch — moved the content inside it at the same time,
       so the content slid down with the sheet. `contain` above only stops the chaining to
       the page behind; `none` also drops the effect itself. */
    .et-panel:not(.et-panel--docked) .et-panel-scroll { overscroll-behavior-y: none; }
    /* The sheet is bottom-anchored (height:90% off `bottom: 0`), so its last
       row sits on the screen edge — on iOS that is where the home indicator
       lives. Only the footer needs the inset: growing the shell instead would
       either open a gap to the dimmed page or push content off the top.
       var() before env(), as everywhere (app.css): Capacitor SystemBars
       injects --safe-area-inset-* because Android WebView < 140 answers env()
       with 0. */
    .et-panel:not(.et-panel--docked) .et-panel-footer {
        padding: 12px 16px calc(18px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
    }
    /* ── pilot toggle (temporary): et:motion — a panel→panel hop ──
       Tartozás rendezése → a split row → the expense, and ‹ back: PanelOutlet
       re-mounts the page for every panel path, so the new EtPanel used to rise
       from the bottom again as if the sheet had only just opened. The outlet
       marks it --swapped (EtPanelEntrance) and the sheet stays where it is; the
       content's push/pop is a View Transition (app.css). Deliberately NOT gated
       on et:motion: with motion off, and where there are no View Transitions,
       an instant swap inside a still sheet is exactly right — a second rise
       never was. Same specificity as the rise above, and later. */
    .et-panel--swapped:not(.et-panel--docked) .et-panel-overlay,
    .et-panel--swapped:not(.et-panel--docked) .et-panel-shell { animation: none; }
}

/* Peek (spec 2026-10-18-sheet-peek-design.md): a sheet with unsaved input,
   swiped down, collapses into a bar on the screen edge and stays mounted. The
   sheet is held out of view (no overlay, no pointer events), so the page
   underneath scrolls and clicks; only the bar takes touches. Sheet form only —
   the peek can only start from the swipe, and a phone rotated wide while
   peeked shows the ordinary panel. The bar rises and drops on transforms
   (and the drop's fade), like the sheet itself. shell.css lifts it above the
   app's bottom bar and moves the FAB out of its way. */
.et-panel-peek { display: none; }
@keyframes et-peek-drop {
    to { transform: translateY(100%); opacity: 0; }
}
/* The root and its siblings are no descendants of the root's container, so
   these two sit in a media query (same 640 boundary). A peeked panel hides the
   panels stacked after it — its own sub-sheets (ExpenseNew's receipt sheet) —
   so only one bar shows; they come back with it. */
@media (max-width: 640px) {
    .et-panel--peek:not(.et-panel--docked) { pointer-events: none; }
    .et-panel--peek ~ .et-panel { display: none; }
}
@container (max-width: 640px) {
    .et-panel--peek:not(.et-panel--docked) .et-panel-overlay { display: none; }
    /* Out of view, not unmounted. Animation none here means the rise replays
       once the class goes — reopening is the sheet rising again. */
    .et-panel--peek:not(.et-panel--docked) .et-panel-shell {
        visibility: hidden;
        transform: translateY(100%);
        animation: none;
    }
    .et-panel--peek .et-panel-peek {
        /* The safe area sits INSIDE the bar on the screen edge (shell.css zeroes
           it where the bar sits on the app's bottom bar instead). */
        --etp-peek-inset: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        box-sizing: border-box;
        height: calc(var(--et-panel-peek-height) + var(--etp-peek-inset));
        padding: 20px 16px var(--etp-peek-inset);
        display: flex;
        align-items: center;
        gap: 12px;
        background: var(--et-color-bg);
        border-top-left-radius: 24px;
        border-top-right-radius: 24px;
        box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.18);
        pointer-events: auto;
        touch-action: none;
        animation: et-sheet-rise var(--et-motion-enter) var(--et-ease-enter);
    }
    /* The sheet's grabber, on the bar. */
    .et-panel--peek .et-panel-peek::before {
        content: "";
        position: absolute;
        top: 8px;
        left: 50%;
        width: 40px;
        height: 4px;
        margin-left: -20px;
        background: #D8D4CA; /* dc literal grabber, as on the sheet */
        border-radius: 2px;
    }
    html:not([data-motion="off"]) .et-panel--leaving .et-panel-peek {
        animation: et-peek-drop var(--et-motion-exit) var(--et-ease-exit) forwards;
    }
}
.et-panel-peek-open {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 2px;
    border: 0;
    padding: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}
.et-panel-peek-title,
.et-panel-peek-note {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-panel-peek-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--et-color-ink);
}
.et-panel-peek-note {
    font-size: 12px;
    color: var(--et-color-ink-3);
}


/* ---------- EtDialog ---------- */
/* The card carries an inline width (EtDialog.Width, default 440px). Inside the
   grid overlay a percentage max-width cannot clamp it — the auto track sizes to
   the 440px contribution before the percentage can resolve — so the clamp must
   be viewport-based (32px = 2 × the overlay's 16px padding). Column flex + a
   scrolling body keep tall dialogs (member/group pickers) on-screen with the
   head and footer pinned. */
.et-dialog {
    background: var(--et-color-surface);
    border-radius: 20px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 32px);
    max-height: calc(100dvh - 32px);
    display: flex;
    flex-direction: column;
    font-family: var(--et-font-family-base);
}
/* The card takes focus when it opens (EtDialog, etDs.focusIn): a container, not a
   control — its buttons and fields carry the rings. */
.et-dialog:focus { outline: none; }
.et-dialog-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 20px 22px 0;
    flex-shrink: 0;
}
.et-dialog-title {
    flex: 1;
    font-size: 17px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
}
.et-dialog-title--danger { color: var(--et-color-owe); }
.et-dialog-close {
    width: 30px;
    height: 30px;
    border: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--et-color-surface-2);
    display: grid;
    place-items: center;
    font-family: inherit;
    font-size: 13px;
    color: var(--et-color-ink-2);
    cursor: pointer;
    flex-shrink: 0;
    transition: background-color .15s;
}
.et-dialog-close:hover { background-color: #E6E2D8; }
.et-dialog-close:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-dialog-body { padding: 14px 22px 6px; overflow-y: auto; }
.et-dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 12px 22px 20px;
    flex-shrink: 0;
}
/* Centering overlay for EtDialog hosts (same dim as EtPanel's overlay). */
.et-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 1500;
    background: rgba(26, 25, 23, 0.32);
    backdrop-filter: blur(2px);
    display: grid;
    place-items: center;
    padding: 16px;
}
/* ── pilot toggle (temporary): et:motion — the dialog's way in and out ──
   In: the overlay fades up and the card settles from 96%. Out: the caller has
   already unmounted the overlay; app-init.js puts it back for one animation,
   marked --leaving and inert (see "The dialog's exit" there), and this plays
   it: the overlay fades while the card shrinks back to 96%. The card animates
   its transform only — it sits in the fading overlay, so its opacity follows.
   Every .et-modal-overlay host gets the overlay half, the attachment lightbox
   included; only EtDialog's card scales. */
@keyframes et-dialog-in  { from { transform: scale(0.96); } }
@keyframes et-dialog-out { to   { transform: scale(0.96); } }
html:not([data-motion="off"]) .et-modal-overlay {
    animation: et-fade var(--et-motion-exit) var(--et-ease-standard);
}
html:not([data-motion="off"]) .et-modal-overlay .et-dialog {
    animation: et-dialog-in var(--et-motion-enter) var(--et-ease-enter);
}
.et-modal-overlay--leaving { pointer-events: none; }
html:not([data-motion="off"]) .et-modal-overlay--leaving {
    animation: et-fade-out var(--et-motion-exit) var(--et-ease-exit) forwards;
}
html:not([data-motion="off"]) .et-modal-overlay--leaving .et-dialog {
    animation: et-dialog-out var(--et-motion-exit) var(--et-ease-exit) forwards;
}


/* ---------- EtBannerCropper ---------- */
/* Hosted in an EtDialog, so the card chrome is EtDialog's — this block is only
   the frame and its hint. `touch-action: none` is load-bearing: without it the
   browser claims a horizontal drag for its own scroll/refresh gesture and the
   pointermove stream stops mid-crop on every phone. The image is a background
   rather than an <img> so `cover` + a percentage `background-position` do the
   pan; that pairing is exactly the crop prepareBanner performs, so preview and
   result cannot drift. */
.et-banner-cropper-frame {
    width: 100%;
    border-radius: 14px;
    background-color: var(--et-color-surface-2);
    background-repeat: no-repeat;
    background-size: cover;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-user-drag: none;
    overflow: hidden;
}
.et-banner-cropper-hint {
    margin-top: 10px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--et-color-ink-3);
    text-align: center;
}
/* The frame is only useful as wide as it can get, so on a phone the card takes
   the overlay's full width instead of EtDialog's 480px design width. */
@media (max-width: 640px) {
    .et-banner-cropper { width: 100% !important; }
}


/* ---------- EtAvatarCropper ---------- */
/* Same EtDialog host as the banner cropper, and the same reason for
   `touch-action: none` — here it also keeps a two-finger pinch for the frame
   instead of zooming the page. The img starts as a centred cover, which is
   exactly the state etDs.attachAvatarCropper starts from; once the picture has
   loaded the script sizes it in px and moves it by transform alone. The ring is
   the frame's ::after: the circle EtAvatar cuts out, with the rest of the square
   dimmed around it. Width comes from EtDialog (400px, clamped to the viewport
   on a phone), so the frame needs no breakpoint of its own. */
.et-avatar-cropper-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    border-radius: 14px;
    overflow: hidden;
    background-color: var(--et-color-surface-2);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}
.et-avatar-cropper-frame--ready { cursor: grab; }
.et-avatar-cropper-frame--dragging { cursor: grabbing; }
.et-avatar-cropper-img {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    max-width: none;
    object-fit: cover;
    transform-origin: 0 0;
    pointer-events: none;
    -webkit-user-drag: none;
}
.et-avatar-cropper-frame::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.85), 0 0 0 999px rgba(26, 25, 23, 0.45);
    pointer-events: none;
}
/* The zoom slider under the frame: the native range control in the theme's
   accent (no hand-drawn track — nothing else in the app has a slider to match),
   between a − and a + that say which way is closer. */
.et-avatar-cropper-zoom {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 14px;
}
.et-avatar-cropper-zoom-range {
    flex: 1;
    min-width: 0;
    height: 32px;
    margin: 0;
    accent-color: var(--et-color-accent);
    cursor: pointer;
}
.et-avatar-cropper-zoom-mark {
    width: 16px;
    flex-shrink: 0;
    text-align: center;
    font-size: 18px;
    font-weight: 700;
    line-height: 1;
    color: var(--et-color-ink-3);
}
.et-avatar-cropper-hint {
    margin-top: 6px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--et-color-ink-3);
    text-align: center;
}


/* ---------- EtDropdown ---------- */
.et-dropdown {
    position: relative;
    font-family: var(--et-font-family-base);
}
.et-dropdown-anchor { position: relative; z-index: 1; }
/* Transparent hit area that closes the menu on an outside click without a
   visible dim — the field under the menu must stay readable. */
.et-dropdown-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1490;
}
.et-dropdown-menu {
    position: absolute;
    top: calc(100% + 6px);
    z-index: 1495;
    background: var(--et-color-surface);
    border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.04);
    padding: 6px;
    animation: et-dropdown-in 0.14s ease-out;
}
.et-dropdown--stretch .et-dropdown-menu { left: 0; right: 0; }
.et-dropdown--start .et-dropdown-menu { left: 0; width: var(--etdd-width, 260px); }
.et-dropdown--end .et-dropdown-menu { right: 0; width: var(--etdd-width, 260px); }
.et-dropdown-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: var(--etdd-max-height, 320px);
    overflow-y: auto;
}
/* Dropdown items: roomier rows than the compact TopBar menu (~52px with
   a 28px avatar) and a plum-tinted selection with a trailing check. */
.et-dropdown-list .et-group-menu-item { padding: 12px; }
.et-dropdown-list .et-group-menu-item--selected {
    background-color: color-mix(in srgb, var(--et-color-accent) 9%, transparent);
}
.et-dropdown-list .et-group-menu-item--selected::after {
    content: "✓";
    margin-left: auto;
    color: var(--et-color-accent);
    font-weight: 800;
}
/* Sheet header — only rendered visible at the phone breakpoint. */
.et-dropdown-title { display: none; }
@keyframes et-dropdown-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}
/* Phone breakpoint: the anchored menu becomes the mobile design's bottom
   sheet — same markup, same call site. */
@media (max-width: 640px) {
    .et-dropdown-backdrop {
        background: rgba(26, 25, 23, 0.32);
        backdrop-filter: blur(2px);
    }
    .et-dropdown-menu,
    .et-dropdown--start .et-dropdown-menu,
    .et-dropdown--end .et-dropdown-menu {
        position: fixed;
        top: auto;
        bottom: 0;
        left: 0;
        right: 0;
        width: auto;
        border-radius: 24px 24px 0 0;
        padding: 0 10px calc(14px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
        box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.22);
        animation: et-sheet-rise 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .et-dropdown-menu::before {
        content: "";
        display: block;
        width: 40px;
        height: 4px;
        background: #D8D4CA; /* dc literal — sheet grabber */
        border-radius: 2px;
        margin: 12px auto 0;
    }
    .et-dropdown-title {
        display: block;
        padding: 12px 12px 8px;
        font-size: 15px;
        font-weight: 800;
        letter-spacing: -0.02em;
        color: var(--et-color-ink);
    }
    .et-dropdown-list { max-height: 60vh; }
}
/* ── pilot toggle (temporary): et:motion — the dropdown's way out ──
   EtDropdown keeps the menu rendered for one exit after Open turns false
   (--leaving, see EtDropdown.razor), and nothing in it takes a tap meanwhile.
   Anchored, the menu fades back up the 4px it came down. As the phone sheet it
   leaves the way the EtPanel sheet does: slides down while its backdrop fades
   — and that backdrop now fades IN as well, instead of the dim snapping on
   under the rising sheet. Specificity (0,3,1) outranks the rise above. */
@keyframes et-dropdown-out { to { opacity: 0; transform: translateY(-4px); } }
.et-dropdown--leaving .et-dropdown-backdrop,
.et-dropdown--leaving .et-dropdown-menu { pointer-events: none; }
html:not([data-motion="off"]) .et-dropdown--leaving .et-dropdown-menu {
    animation: et-dropdown-out var(--et-motion-fast) var(--et-ease-exit) forwards;
}
@media (max-width: 640px) {
    html:not([data-motion="off"]) .et-dropdown-backdrop {
        animation: et-fade var(--et-motion-exit) var(--et-ease-standard);
    }
    html:not([data-motion="off"]) .et-dropdown--leaving .et-dropdown-menu {
        animation: et-sheet-drop var(--et-motion-exit) var(--et-ease-exit) forwards;
    }
    html:not([data-motion="off"]) .et-dropdown--leaving .et-dropdown-backdrop {
        animation: et-fade-out var(--et-motion-exit) var(--et-ease-exit) forwards;
    }
}


/* ---------- EtTopBar ---------- */
.et-topbar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 24px;
    background: var(--et-color-surface);
    border-bottom: 1px solid var(--et-color-border);
    font-family: var(--et-font-family-base);
}
.et-topbar-brand {
    display: flex;
    align-items: center;
    gap: 9px;
}
.et-topbar-logo {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    /* Palette accent, not ink: the tile is the one place the active palette
       shows in the chrome. --et-color-surface flips with the theme, so the
       "KK" stays readable on light accents (dark palettes) too. */
    background: var(--et-color-accent);
    color: var(--et-color-surface);
    display: grid;
    place-items: center;
    font-weight: 800;
    font-size: 13px;
}
.et-topbar-name {
    font-weight: 700;
    font-size: 15px;
    color: var(--et-color-ink);
}
.et-topbar-group {
    border: 0;
    background: none;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
}
.et-topbar-right {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 10px;
}
.et-topbar-avatar {
    border: 0;
    background: none;
    padding: 0;
    border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 0 0 2px var(--et-color-surface), 0 0 0 3.5px var(--et-color-border);
    transition: filter .12s, transform .12s;
}
.et-topbar-avatar:hover { filter: brightness(1.06); }
.et-topbar-avatar:active { transform: scale(0.96); }
.et-topbar-avatar:focus-visible,
.et-topbar-group:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
/* Phone: at 390px the word mark wrapped to a second line and shoved the avatar
   out of the bar. Drop the word mark (the KK tile still identifies the app),
   tighten 12/24 → 12/14 and 14 → 10 gap, and drop the header CTA — on a phone
   the design puts "Új költség" on the bottom bar's + button, not up here.

   @media and NOT @container, deliberately. This is shell, not content: the top
   bar always spans the viewport, so its box width and the screen width are the
   same number and a container query would only add indirection. The decisive
   half is what it would cost: the bar hosts the bell and profile dropdowns,
   and container-type brings layout containment, which makes the container the
   containing block for position:fixed descendants — EtDropdown's backdrop
   (fixed, inset:0) and its phone bottom sheet (fixed, bottom:0) would then be
   trapped inside a 60px-tall bar. Compare EtActivityRow / EtNotifRow, which
   are content, sit in boxes narrower than the screen, and carry nothing fixed.

   Consequence: /_dev/ds cannot fire this branch directly — the specimen's
   phone column is a narrow BOX in a wide viewport, and a media query does not
   care. The gallery instead mirrors these three declarations under
   .ds-specimen-phone .et-topbar, near the DsSpecimen rules below. Keep the
   two blocks in lock-step by hand — nothing enforces it.

   640px is the DS's single phone breakpoint — the same one EtPanel,
   EtDropdown and EtNotificationMenu morph at. */
@media (max-width: 640px) {
    .et-topbar {
        gap: 10px;
        padding: 12px 14px;
    }
    .et-topbar-name { display: none; }
    /* The CTA only; the bell is .et-icon-btn and the avatar is its own class. */
    .et-topbar-right > .et-btn { display: none; }
}


/* ---------- EtGroupHeader ----------
   Band gradient + icon fill/shadow are per-group colors passed inline.

   The compact form used to be a `Compact` C# bool that also switched the
   Settings DOM. It is now one @container branch at the end of this block, so
   the header tightens itself in whatever box it is given. */
.et-group-header {
    font-family: var(--et-font-family-base);
    /* Query container for the branch below. The padding lives on
       .et-group-header-inner and not here because a container cannot query
       ITS OWN width — a rule inside @container never matches the container
       element, so a root-level padding could not participate. Safe to
       contain: the header holds buttons and avatars, nothing position:fixed
       (contrast .et-topbar and .et-filter-panel, which host EtDropdown). */
    container-type: inline-size;
}
/* The strip Safari reads for its top-edge colour, the root's first child
   (see EtGroupHeader.razor). Hidden here; the one state that shows it —
   an iPhone browser on a group screen in the phone shell — lives in app.css
   next to the band's top fade, which starts from the same colour. */
.et-group-header-tint { display: none; }
.et-group-header-inner { padding: 22px 32px 24px; }
/* F-37: EtIconButton already supplies the shape/hover (.et-icon-btn); this
   class only exists as the compact-width hook below, since the button sits
   inline in .et-group-header-main now instead of its own row. The pale disc
   fill itself is .et-group-header-disc, shared with the gear below (owner
   request 2026-09-16: the header's two round buttons should read as a pair). */
.et-group-header-back { flex-shrink: 0; }
/* Shared pale disc for the header's two round buttons (back + gear).
   Translucent white reads against the plain band and the photo variant
   alike, same as the gear always did — .et-icon-btn / .et-group-header-gear
   below still own their own size, border-radius and centering, this class
   only carries the fill + shadow so both get it identically. Source order
   matters: this sits after .et-icon-btn (line ~211) so it wins the
   equal-specificity background override for the back button. */
.et-group-header-disc {
    background: rgba(255, 255, 255, 0.75);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
/* flex-start, not center (owner, 2026-09-25): the back button and the gear sit
   in the band's top corners, level with the title, instead of at mid-height —
   centred, both landed on the picture that fills the band's right side. The
   title row starts with them; the avatar line runs on below. */
.et-group-header-main {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}
.et-group-header-body { flex: 1; min-width: 0; }
.et-group-header-title {
    font-size: 27px;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: #1A1917; /* on the light band — fixed */
}
/* R4-03 — what is left of the icon tile. The 60/48px rounded square used to sit
   between the back button and this column and spent ~76px of the row before the
   title could start; the owner wanted the name and the avatars further left, and
   the group switcher at the top of the same page already shows this very glyph.

   An inline span, not a flex child: the imaged title is a -webkit-box with
   line-clamp: 2 (R3-02) and a flex container would drop the clamp. Sized a
   little under the title so an emoji does not out-shout the name, and tinted
   with the group colour (inline style) where the tile used to carry it as a
   fill. `em` throughout, so the ≤480 title's 22px takes it along. */
.et-group-header-title-icon {
    font-size: 0.82em;
    margin-right: 0.28em;
    /* Emoji ignore it; a text glyph from a group whose icon really is a letter
       reads as identity rather than as the first word. */
    font-weight: 700;
}
/* PRO chip inline next to the name (owner 2026-09-26, et:pro-chip=name default): the header
   used to carry the chip on its own line below the avatars (.et-group-header-status-line, still
   used by the et:pro-chip=line pilot form) with a day count; the owner wants it lower and
   quieter — next to the name, "PRO" only. Only rendered when EtGroupHeader.NameChip is set. */
.et-group-header-title-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
/* The name truncates to one line so a long one can never push the (fixed-content, inline-flex)
   chip out of the header or wrap under it. Specificity (0,3,0) beats the --imaged/--illustrated
   two-line clamp below (0,2,0) on purpose — a chip sharing the line with a two-line wrap has
   nowhere reliable to sit, so once a chip is present every band form gets the single-line rule. */
.et-group-header--name-chip .et-group-header-title-row .et-group-header-title {
    min-width: 0;
    /* grow:0 — the name hugs its own text and the chip sits right beside it;
       any leftover row width stays empty after the chip rather than being
       spent stretching the name away from it. shrink:1 is what lets a name
       too wide for the row give ground before the ellipsis kicks in. */
    flex: 0 1 auto;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.et-group-header-name-chip { flex-shrink: 0; }
.et-group-header-meta-line {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 5px;
}
/* F9 (2026-10-18): the same line as a button to the member list. It must look
   exactly like the plain line — no button chrome, no underline — so only the
   cursor and the press (.et-topbar-avatar's scale) say it is tappable. */
.et-group-header-members {
    justify-content: flex-start;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    border-radius: 8px;
    transition: transform .12s;
}
.et-group-header-members:active { transform: scale(0.96); }
.et-group-header-members:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-group-header-avatars { display: flex; }
.et-group-header-avatar {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    color: #fff;
    display: grid;
    place-items: center;
    font-weight: 600;
    font-size: 11px;
    border: 2px solid #fff;
}
.et-group-header-avatar + .et-group-header-avatar { margin-left: -8px; }
/* Uploaded profile picture inside the tone disc. overflow:hidden lives here
   rather than on the parent so the -8px overlap still works. */
.et-group-header-avatar { overflow: hidden; }
.et-group-header-avatar-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.et-group-header-avatar--plum    { background: linear-gradient(135deg, #9c6dab, #7A4E7A); }
.et-group-header-avatar--rose    { background: linear-gradient(135deg, #e088b8, #c94f95); }
.et-group-header-avatar--green   { background: linear-gradient(135deg, #5cb985, #2e8b5c); }
.et-group-header-avatar--blue    { background: linear-gradient(135deg, #5b9bd5, #2e6db5); }
.et-group-header-avatar--mustard { background: linear-gradient(135deg, #f0b362, #d68f2e); }
.et-group-header-avatar--sand    { background: #ECEAE3; color: #8F8C83; }
.et-group-header-meta {
    font-size: 12px;
    color: #6B6860; /* on the light band — fixed */
    font-weight: 500;
    margin-left: 2px;
}
/* V6 entitlement chip's own line (Task 6 fix round) — below the avatars/meta
   row rather than sharing it, so a long chip never fights the fixed-width
   Settings button for space on a narrow phone. Block-level; EtPill is
   inline-flex, so it hugs its own content. */
.et-group-header-status-line { margin-top: 8px; }
.et-group-header-settings { flex-shrink: 0; margin-left: auto; }
/* The Settings affordance — EtIconButton (Icon="gear"), a drawn SVG. Since
   2026-09-25 the only form at every width; the labelled tonal button desktop
   used to get is gone. .et-icon-btn supplies shape/border/cursor/font-inherit,
   the disc fill is .et-group-header-disc above; grid so the rule's own
   place-items centres the glyph. */
.et-group-header-gear { display: grid; }
/* Narrow: below ~480px the 32px side padding and the 27px title stop fitting
   beside the two round buttons. Same threshold as EtActivityRow and
   EtNotifRow: the DS keeps one narrow-box breakpoint rather than three that
   drift apart.
   @container and not @media because this is content in the page's main
   column, not shell — it must read the same in a phone, in the group view and
   in the gallery's 390px specimen, none of which agree on viewport width. */
@container (max-width: 480px) {
    .et-group-header-inner { padding: 16px 16px 18px; }
    /* R4-03 (owner 2026-09-17) — the back button stands in the row again, plain
       and level with the gear (top corners since 2026-09-25, see
       .et-group-header-main).

       It used to overlay the icon tile's top-left corner (#413) with
       `margin-right: -32px; margin-top: -14px; margin-bottom: 22px` and a white
       ring, a composite invented purely to win back the ~76px the tile spent
       before the title could start. With the tile gone there is nothing to sit
       on and nothing to win back: the row's own `align-items: center` puts the
       button in line with the title, and the ring that separated its pale
       disc from the tile's saturated fill has nothing left to separate. */
    .et-group-header-title { font-size: 22px; }

    /* Imaged form stays diagonal here too (F-17/11, amended 2026-09-16). The
       first draft swapped to a full-band background under a scrim, but
       guaranteeing 4.5:1 for an arbitrary photo needs an ~82% black wash, which
       hides the picture at exactly the width most people use. Keeping the cut
       keeps every glyph on the band gradient, where the contrast the header
       already ships holds unchanged — no scrim, no contrast arithmetic. */
}

/* F-17/9 — the banner photo. Every rule below is scoped under --imaged, which
   is what keeps a photo-less header byte-identical to what it was. */
.et-group-header--imaged {
    position: relative;
    /* Keeps the photo inside the rounded corner. It used to be scoped to the
       imaged form to avoid clipping the icon tile's `0 6px 16px` drop shadow in
       the ≤480px branch; the tile went away with R4-03, so that reason is gone,
       but the scoping stays — a photo-less header has nothing to clip and no
       reason to become a containing block.
       clip over hidden where it exists (hidden stays the fallback): hidden
       makes the band a scroll container, and the sticky Safari strip inside it
       (.et-group-header-tint, app.css) would then stick to the band instead of
       the screen. Both paint the same clip. */
    overflow: hidden;
    overflow: clip;
}
.et-group-header--imaged .et-group-header-inner { position: relative; z-index: 1; }

.et-group-header-image {
    position: absolute;
    inset: 0 0 0 auto;
    width: 40%;
    /* ~12° slant, narrower at the top so the title gets the extra clearance.
       The offset is band-height · tan(12°); the desktop band measures ~106px
       (22 + 60 tile + 24), hence 22px. A token because the angle is a function
       of band height — change the padding or the tile and re-derive it. */
    clip-path: polygon(var(--et-gh-slant, 22px) 0, 100% 0, 100% 100%, 0 100%);
}
.et-group-header-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* The band dissolving into the photo along the cut, so the title never sits
   against a hard edge, plus (R3-02) a wash across the rest of the picture so
   the title and avatars can run onto it. Two layers, listed in paint order —
   the seam strip on top of the wash:

     1. a horizontal strip at the cut;
     2. a ramp from the photo's BOTTOM-LEFT corner out to nothing.

   Both end on a 0%-alpha mix of the band colour rather than the `transparent`
   keyword: that keyword is rgba(0,0,0,0) and greys the midpoint of the ramp.

   R4-03 (owner 2026-09-17: "az átmenet is nagyon csicsás"): the seam strip
   drops 72px → 40px and the wash's reach 70% → 58%. What the owner reads as
   flashy is the WIDTH of the smear, not its presence — a 72px fade over a
   ~150px-wide photo on a phone was half the picture dissolved. Narrowing both
   gives a crisper cut and shows more of the actual photo. The strip's opacity
   is untouched on purpose: with the title's text-shadow now gone (above), the
   wash is the only thing carrying contrast and this is not the moment to thin
   it as well. */
.et-group-header-image::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(90deg,
            var(--et-gh-fade) 0%,
            color-mix(in srgb, var(--et-gh-fade) 0%, transparent) 100%),
        linear-gradient(to top right,
            var(--et-gh-fade) 0%,
            color-mix(in srgb, var(--et-gh-fade) 0%, transparent) 58%);
    background-size: 40px 100%, 100% 100%;
    background-position: left top, left top;
    background-repeat: no-repeat;
    z-index: 1;
}
/* R3-02, superseding F-17/10. The text used to be fenced off the picture by a
   60% max-width on .et-group-header-main; it bought clearance at the price of
   "Csal…" for a six-letter name. The owner asked for the opposite — the group
   name and the member avatars run OVER the photo — so the fence is gone and the
   wash above is what carries the contrast. The title now wraps to at most two
   lines instead of ellipsising on the first.

   No max-width replaces it on this width on purpose: .et-group-header-settings
   is still an in-flow flex item here, so the Settings button reserves its own
   track and the title cannot run under it. The ≤480 branch, where the gear is
   absolute and reserves nothing, pays for that with a padding-right instead. */
.et-group-header--imaged .et-group-header-title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    /* R4-03 (owner 2026-09-17): "nem tudom hogy ezt a fura árnyékot … mikor
       találtuk ki. A korábbi sima betutipus jobb volt." The R3-02 shadow
       (0 1px 2px rgba(0,0,0,.35)) is gone from the title and the meta line
       alike. It was only ever edge definition for the stretch where the wash
       thins out over the picture; the wash below is and was the contrast
       mechanism, and it is what carries the text on its own now. */
}

/* The imaged form's narrow branch (F-17/11, amended 2026-09-16). It keeps the
   diagonal — the first draft swapped to a full-band background under a scrim,
   but guaranteeing 4.5:1 for an arbitrary photo needs an ~82% black wash, which
   hides the picture at exactly the width most people use. Staying on the band
   keeps the contrast the header already ships, with no scrim and no arithmetic.

   THIS BLOCK MUST STAY BELOW THE BASE RULES ABOVE. A container query adds no
   specificity, so `@container ... { .et-group-header-image { width } }` and the
   plain `.et-group-header-image { width }` tie at (0,1,0) and source order
   decides. These overrides originally sat in the earlier @container block near
   the top of this component and silently lost to the base rules written after
   it — the compact widths never applied and a six-letter group name shipped
   ellipsised. Do not move them back up. */
@container (max-width: 480px) {
    .et-group-header--imaged .et-group-header-image { width: 38%; }
    /* Same 12°, shorter band. The tile no longer sets the height (R4-03) — the
       text column does: 16 + (22px title + 5 + 26px avatars ≈ 58) + 18 ≈ 92px,
       and 92·tan(12°) ≈ 19px. */
    .et-group-header--imaged .et-group-header-image { --et-gh-slant: 19px; }
    /* Narrower band, narrower seam — the base 40px over a ~158px photo is
       already a quarter of it. Only the first layer shrinks; the R3-02 wash
       stays full width. */
    .et-group-header--imaged .et-group-header-image::before {
        background-size: 26px 100%, 100% 100%;
    }
    /* R3-02: the 62% fence is gone here too. Unlike the desktop row the gear
       below is absolute and reserves nothing, so the text column keeps clear of
       it by hand — the gear's disc (36px, R5-05) plus room to breathe. */
    .et-group-header--imaged .et-group-header-body { padding-right: 52px; }
    /* The gear floats over the photo rather than holding a place in the text
       column. Measured at 430px (415px of header once the scrollbar is out):
       in the flow the gear plus its gap took 58px that the group name needed,
       and "Család" shipped as "Csal…". The gear's translucent-white disc was
       drawn to sit on the band and reads just as well on a photo, and it is the
       one element in this row with no text to lose. R4-03 freed the tile's
       ~64px on top of that, so the name now has room to spare — but the gear
       stays absolute: it is also what keeps the back button and the gear on one
       centre line, which is what the owner asked for. */
    .et-group-header--imaged .et-group-header-settings {
        position: absolute;
        top: 50%;
        right: 16px;
        transform: translateY(-50%);
    }
}

/* Spec 2026-09-19 §4.2 — the illustrated form. Far less machinery than the
   photo above it: the artwork has its own alpha and is drawn to sit on the
   band, so there is no diagonal cut, no fade gradient and no scrim. The band's
   gradient runs behind it unchanged, which is why the title keeps exactly the
   contrast it has today even where the two overlap.
   Height-driven sizing (`height:100%; width:auto`) rather than a fixed width,
   so the 5:2 motif scales with whatever the band's own padding makes the band
   — the 58% box is only the slot it may not grow past. Desktop is deliberately
   left on this same sizing for now: at 1200px the motif reads small and sparse,
   and the owner chose to ship it and fix it later in the art rather than with a
   min-height here (see todos/GROUP-ILLUSTRATIONS-BRIEF.md, 2026-09-21). */
/* clip over hidden: the same reason as .et-group-header--imaged above. */
.et-group-header--illustrated { position: relative; overflow: hidden; overflow: clip; }
.et-group-header--illustrated .et-group-header-inner { position: relative; z-index: 1; }
.et-group-header-illu {
    position: absolute;
    inset: 0 0 0 auto;
    width: 58%;
    display: flex;
    justify-content: flex-end;
    align-items: flex-end;
    pointer-events: none;
}
.et-group-header-illu img {
    height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    object-position: right bottom;
    display: block;
}

/* ── pilot toggle (temporary, removed once the owner decides) ──
   Owner request 2026-09-22: with §4.4 moving the band into the top bar's
   place, the motif will read small — it is sized off the band's HEIGHT
   (`height:100%` above), and the band's height is only what its two lines of
   text and its padding make it (≈105px at 430px, ≈123px at 1200px). `large`
   gives the illustrated band a taller FLOOR, which the height-bound motif
   then fills. Nothing else changes: same image, same `object-fit: contain`
   and `object-position: right bottom`, same 58% slot.

   Only the illustrated form grows. A plain-colour band has nothing to make
   room for, and a photo band (--imaged) has its own diagonal geometry that a
   floor would stretch into a different composition.

   @media, not @container: the phone/desktop split here is about how much
   VERTICAL room the device has to spend on a decorative band, which is a
   viewport question — unlike the 480px rule above, which is about how much
   text fits in the box the band is given.

   min-height, not height, and the inner block is untouched: .et-group-header
   is a plain block box, so .et-group-header-inner stays in normal flow at the
   TOP and the title/meta do not drift down as the floor rises. That is the
   behaviour we want and not merely the one we inherit — the band is read
   top-down (back button, title, meta) and it sits directly under the status
   bar in the §4.4 shell, so anchoring the text to the top keeps it in the
   same place whichever size the owner picks, and the extra height lands where
   the illustration is, at the bottom right. No align-self/justify-content is
   needed for that; adding one would be the change, not the guard. */
@media (max-width: 959.98px) {
    html[data-group-art-size="large"] .et-group-header--illustrated,
    html[data-group-art-size="large"] .et-page-band--illustrated { min-height: 190px; }
    /* On a phone the floor alone buys nothing, which only measurement shows:
       the motif is 2.5:1 and the slot is 58% of 430px = 249px, so
       `object-fit: contain` caps the painted image at 249/2.5 = 100px however
       tall the band gets. The motif is WIDTH-bound here, not height-bound —
       the height-bound premise is only true from ~960px up, where 58% is
       ~700px of slot. Without this the `large` band would just be 85px of
       empty air and the pilot could not answer the question it exists for.

       78% and not more: the slot's left edge then lands at 94.6px, still to
       the RIGHT of where the title and meta start (68px). That is the whole
       rule — the artwork may grow until it would start before the text does.
       Measured at 430px it takes the painted motif from 100px to 134px.
       Overlapping the text from there is expected and allowed (spec §4.2:
       "a cím és a meta belelóghat a képbe"), and the motifs are transparent
       and right-anchored, so their left third is empty anyway.

       Desktop keeps 58%: it is already height-bound there, and widening the
       slot would move the motif left across a column three times as wide. */
    html[data-group-art-size="large"] .et-group-header--illustrated .et-group-header-illu,
    html[data-group-art-size="large"] .et-page-band--illustrated .et-page-band-illu {
        width: 78%;
    }
}
@media (min-width: 960px) {
    html[data-group-art-size="large"] .et-group-header--illustrated,
    html[data-group-art-size="large"] .et-page-band--illustrated { min-height: 160px; }
}


/* ---------- EtPageBand (spec 2026-09-19 §10) ----------
   The tab-root header band: same illustrated look the group band got in §4.2,
   on Kezdőlap / Költségek / Profil. The geometry below is deliberately the
   group band's, line for line (58% slot, height-driven `contain`, right-bottom
   anchor), because the two are the same picture on the same screen and a
   second set of numbers would drift.

   What is NOT shared is the colour. A group band is tinted with the GROUP's
   colour (inline style, EtGroupBand.Gradient) — a page has no group, so the
   band follows the user's PALETTE instead, mixed from --et-color-accent into
   --et-color-surface at the same 12%/22% stops EtGroupBand's non-trio
   fallback uses. Tokens rather than the trio's baked hexes, so the band is
   correct in dark mode by construction: the dark ramp redefines both terms
   and the mix lands on a tinted dark surface under light ink, instead of a
   pastel plate with invisible text. */
.et-page-band {
    position: relative;
    overflow: hidden;
    font-family: var(--et-font-family-base);
    border-radius: 18px;
    background: linear-gradient(135deg,
                color-mix(in srgb, var(--et-color-accent) 12%, var(--et-color-surface)),
                color-mix(in srgb, var(--et-color-accent) 22%, var(--et-color-surface)));
}
.et-page-band-illu {
    position: absolute;
    inset: 0 0 0 auto;
    width: 58%;
    display: flex;
    justify-content: flex-end;
    align-items: flex-end;
    pointer-events: none;
}
.et-page-band-illu img {
    height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    object-position: right bottom;
    display: block;
}
/* z-index 1 for the same reason .et-group-header-inner takes one: the motif is
   an absolutely positioned earlier sibling, so without a stacking order of its
   own the text would paint under it. */
.et-page-band-inner {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 22px 24px 24px;
}
.et-page-band-text { flex: 1 1 auto; min-width: 0; }
/* The .et-page-hero type scale, copied rather than inherited: the hero is page
   chrome in app.css and this is a DS atom, and one of the two forms is always
   the one NOT on screen. Same 28/800/-0.03em title and 14/1.35 subtitle, same
   22/13 phone branch below. */
.et-page-band-title {
    font-size: 1.75rem;         /* 28px */
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.15;
    color: var(--et-color-ink);
}
/* ink-2, where the hero's subtitle is ink-3: the hero sits on the page
   background and this sits on a tint two steps darker, where ink-3 stops
   holding its line. */
.et-page-band-subtitle {
    margin-top: 2px;
    font-size: 0.875rem;        /* 14px */
    line-height: 1.35;
    color: var(--et-color-ink-2);
}
/* Hidden by default and turned on ONLY where the band replaces the app top bar
   (app.css, html.et-shell-bar + data-route="page-band" below 960px). Anywhere
   else the real top bar is still above the band with its own bell in it, and
   two bells on one screen is the bug this rule exists to prevent. */
.et-page-band-trailing { display: none; flex-shrink: 0; }
@media (max-width: 767.98px) {
    .et-page-band-inner { padding: 18px 16px 20px; }
    .et-page-band-title { font-size: 1.375rem; }     /* 22px */
    .et-page-band-subtitle { font-size: 0.8125rem; } /* 13px */
}


/* ---------- EtGroupMenu ---------- */
.et-group-menu {
    background: var(--et-color-surface);
    border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.04);
    padding: 6px;
    font-family: var(--et-font-family-base);
}
.et-group-menu--flat { box-shadow: 0 0 0 1px var(--et-color-border); }
.et-group-menu-item {
    display: flex;
    align-items: center;
    /* Explicit even though it's the flex default. It was load-bearing while
       MudBlazor's global `button` rule injected justify-content:center into
       any button class that stayed silent; kept so a future global button
       rule can't re-centre these rows. */
    justify-content: flex-start;
    gap: 9px;
    width: 100%;
    padding: 10px 11px;
    border: 0;
    background: none;
    border-radius: 9px;
    font-family: inherit;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--et-color-ink);
    text-align: left;
    cursor: pointer;
    transition: background-color .15s;
}
.et-group-menu-item:hover { background-color: #F5F2EB; } /* dc literal hover tint */
.et-group-menu-item--selected {
    font-weight: 700;
    background-color: #F5F2EB;
}
.et-group-menu-glyph {
    width: 16px;
    text-align: center;
    color: var(--et-color-ink-2);
    font-size: 13px;
}
.et-group-menu-dot-wrap {
    width: 16px;
    display: flex;
    justify-content: center;
}
.et-group-menu-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
}
.et-group-menu-divider {
    height: 1px;
    background: var(--et-color-border);
    margin: 5px 8px;
}
.et-group-menu-new { color: var(--et-color-accent); }
.et-group-menu-item:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: -2px;
}

/* GroupSwitcher = EtDropdown shell + EtGroupMenu rows. Both atoms ship the
   same surface (14px radius, the same shadow pair, 6px padding), so the
   inner menu is flattened and the shell keeps the chrome — that also leaves
   the scroll container on .et-dropdown-list, where a long group list needs
   it, instead of clipping the inner menu's own shadow.
   The shell's roomier item vocabulary (12px rows, plum tint, trailing ✓) is
   reverted to GroupMenu's: the DS shows this menu with the #F5F2EB
   selected row and no checkmark. */
.et-group-switcher-dropdown .et-group-menu {
    background: none;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
}
.et-group-switcher-dropdown .et-dropdown-list .et-group-menu-item { padding: 10px 11px; }
.et-group-switcher-dropdown .et-dropdown-list .et-group-menu-item--selected {
    background-color: #F5F2EB; /* dc literal — GroupMenu selected row */
}
.et-group-switcher-dropdown .et-dropdown-list .et-group-menu-item--selected::after { content: none; }


/* ---------- EtNotificationMenu ---------- */
.et-notif-menu {
    width: 340px;
    background: var(--et-color-surface);
    border-radius: 16px;
    border: 1px solid var(--et-color-border);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
    overflow: hidden;
    font-family: var(--et-font-family-base);
    /* Query container for the EtNotifRows inside — the menu's second home
       besides the notifications page's .et-surface. Without this the rows in
       the bell dropdown would have no container ancestor at all and their
       @container rule would silently never fire: 340px of menu showing the
       wide, pill-carrying row. Safe to contain here — the menu holds only
       rows and labels, no position:fixed descendant. */
    container-type: inline-size;
}
.et-notif-menu-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 14px 16px 10px;
}
.et-notif-menu-title {
    font-size: 14px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
}
.et-notif-menu-badge {
    background: var(--et-color-accent);
    color: #fff;
    border-radius: 999px;
    padding: 3px 9px;
    font-size: 11px;
    font-weight: 700;
}
/* The "ÚJ" bucket holds every unread row, so the body scrolls rather than
   growing unbounded. Its ÚJ / KORÁBBI labels are EtListLabel Dense="true"
   (the old .et-notif-menu-section lived here). */
.et-notif-menu-list {
    max-height: 380px;
    overflow-y: auto;
}
.et-notif-menu-loading {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px 16px 12px;
}
.et-notif-menu-empty {
    text-align: center;
    padding: 24px 20px 26px;
}
.et-notif-menu-empty-emoji {
    font-size: 32px;
    margin-bottom: 8px;
}
.et-notif-menu-empty-title {
    font-size: 13.5px;
    font-weight: 700;
    color: var(--et-color-ink);
}
.et-notif-menu-empty-text {
    font-size: 12px;
    color: var(--et-color-ink-3);
    margin-top: 3px;
    line-height: 1.4;
}
.et-notif-menu-footer {
    display: block;
    width: 100%;
    border: 0;
    background: none;
    border-top: 1px solid var(--et-color-border);
    padding: 12px 16px;
    text-align: center;
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--et-color-accent);
    cursor: pointer;
    transition: background-color .15s;
}
.et-notif-menu-footer:hover { background-color: rgba(122, 78, 122, 0.05); }
.et-notif-menu-footer:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: -2px;
}
/* ---------- EtNotificationMenu hosted in an EtDropdown (the TopBar bell) ----------
   The menu already is a surface (radius, border, shadow), so inside a dropdown
   it drops its own chrome and becomes the dropdown's body. Opt-in via
   Class="et-notif-dropdown" on the EtDropdown so other dropdowns are untouched. */
/* The bell sits in the app bar's flex row — keep the anchor at the button's
   own 40px instead of letting it shrink. */
.et-notif-dropdown { flex-shrink: 0; }
.et-notif-dropdown .et-dropdown-menu { padding: 0; overflow: hidden; }
.et-notif-dropdown .et-dropdown-list {
    max-height: none;
    overflow: visible;
    gap: 0;
}
.et-notif-dropdown .et-notif-menu {
    width: 100%;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}
@media (max-width: 640px) {
    /* Bottom-sheet form: keep the sheet's safe-area inset (the dropdown's own
       mobile rule sets it) and let the taller viewport give the list room. */
    .et-notif-dropdown .et-dropdown-menu {
        padding: 0 0 var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
    }
    .et-notif-dropdown .et-notif-menu-list { max-height: 60vh; }
}


/* ---------- EtFilterPanel ---------- */
.et-filter-panel {
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-md);
    box-shadow: var(--et-shadow-card);
    padding: 12px;
    font-family: var(--et-font-family-base);
}
.et-filter-panel-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    padding: 2px 6px;
    cursor: pointer;
}
.et-filter-panel-title {
    font-size: 13.5px;
    font-weight: 700;
    color: var(--et-color-ink);
}
.et-filter-panel-helper {
    font-size: 12px;
    color: var(--et-color-ink-3);
}
.et-filter-panel-spacer { flex: 1; }
/* F-39: the ▾/▴ text glyph became a 32px EtIconButton (chevron-down SVG) so
   the header row gets a hit target and a hover disc, matching the clear
   action next to it. The button itself rotates via this modifier class
   rather than the C# swapping the icon, so ds.css owns the animation.
   tokens.css already forces transition-duration to ~0 under
   prefers-reduced-motion (its blanket *, *::before, *::after rule), so the
   rotation itself needs no separate reduced-motion override here. */
.et-filter-panel-chevron-btn {
    color: var(--et-color-ink-2);
}
.et-filter-panel-chevron-btn svg {
    transition: transform 0.18s ease;
}
.et-filter-panel-chevron-btn--open svg {
    transform: rotate(180deg);
}
/* One template for BOTH routes — the read-only picker grid and the
   AdvancedActivityFilters grid inside .et-filter-panel-body.

   --etfp-cols is the WIDE column count. EtFilterPanel writes it as a custom
   property (`style="--etfp-cols:3"`); it used to write the whole
   `grid-template-columns` inline, which no stylesheet rule can override
   without !important — so the read-only route kept three columns at 390px and
   burst out of its box by ~170px. A custom property lets the stylesheet own
   the layout and the component own only the number. AdvancedActivityFilters
   sets nothing, so it takes the 3 fallback, which is what it had before.

   The narrow collapse is intrinsic — auto-fill with a track floor — rather
   than a width query, because both other tools are wrong here:
     - @media keys off the viewport, so it cannot see a narrow panel on a wide
       screen, and it is invisible in the gallery specimen (that is exactly
       what the deleted max-width:640px rule did, and it only ever matched the
       .et-filter-panel-body route anyway).
     - @container would need a query container above this grid, and the only
       candidates are the panel itself or one of its ancestors. Either gets
       layout containment, which makes it the containing block for
       position:fixed descendants — and the ChildContent route puts EtDropdowns
       in this grid, whose backdrop (fixed, inset:0) and phone bottom sheet
       (fixed, bottom:0) are laid out against the viewport. The sheet would
       open at the bottom edge of the filter panel instead of the screen.
   auto-fill answers the same question ("do N pickers still fit?") from the
   real available width, with no container at all. auto-fill and not auto-fit:
   the ChildContent route renders a single picker until a group is chosen, and
   auto-fit would collapse the empty tracks and stretch that one field across
   the whole panel.

   190px is the floor: a picker cell's min-content width measures ~160px, so
   this is the first size at which one is not being squeezed. */
.et-filter-panel-grid {
    display: grid;
    gap: var(--etfp-gap);
    padding: 10px 6px 0;
    --etfp-gap: 10px;
    --etfp-col-min: 190px;
    grid-template-columns: repeat(auto-fill, minmax(
        max(var(--etfp-col-min), 100% / var(--etfp-cols, 3) - var(--etfp-gap)), 1fr));
}
/* Live-picker mode (EtFilterPanel ChildContent): same inset as the grid, but
   the child brings its own layout. */
.et-filter-panel-body { padding: 10px 6px 0; }
/* AdvancedActivityFilters reuses the read-only grid inside the body wrapper,
   which already supplies the inset. */
.et-filter-panel-body > .et-filter-panel-grid { padding: 0; }
.et-filter-panel-dates {
    display: flex;
    gap: 10px;
}
/* Native <input type="date"> inside a picker cell: keep it from forcing the
   grid column wider than its share, and tone down the UA calendar button. */
.et-filter-panel-date .et-field-input { min-width: 0; }
.et-filter-panel-date .et-field-input::-webkit-calendar-picker-indicator {
    margin-left: auto;
    opacity: 0.55;
    cursor: pointer;
}
.et-filter-panel-date {
    flex: 1;
    min-width: 0;
}
/* F-39: lives in .et-filter-panel-head now (right of the pill/helper, before
   the chevron button), not in a row under the grid — reachable while
   collapsed, which is the point. */
.et-filter-panel-clear {
    border: 0;
    background: none;
    padding: 0;
    font-family: inherit;
    color: var(--et-color-owe);
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    flex-shrink: 0;
}
.et-filter-panel-clear:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}


/* ---------- EtFab ---------- */
/* Centered labelled pill floating over the bottom of the phone screen.
   Canvas literal: bottom 22px, left 50% + translateX(-50%), 13/26 padding,
   radius 999, 14/700 label, 8px gap.

   The 22px is measured in a canvas frame that has no home indicator, so the
   safe-area inset is ours to add — without it the pill lands under the
   indicator on a notched iPhone.

   The gradient IS --et-cta-gradient: the canvas's three stops are the token's
   three stops, character for character. The shadow is NOT --et-cta-shadow.
   That token (0 4px 14px / 0.35) is tuned for a CTA resting ON a surface;
   this pill floats above the page and the canvas asks for 0 10px 28px / 0.5.
   Only the blur and alpha are ours, though — the tint comes from
   --et-cta-shadow-rgb so the glow follows the palette along with the gradient
   above it. A literal here would leave a plum halo under a coral pill.

   z-index 1101 carries over from the .app-fab this replaces: below the sticky
   app header (app.css .app-topbar, 1102 — deliberately, so the header's own
   dropdown sheets cover the pill), the panel outlet (1350), .et-panel (1400)
   and .et-modal-overlay (1500), so a sheet or dialog still covers it.

   No viewport query lives in this atom on purpose: a display:none media
   query here would also blank the pill in the DS gallery, which runs at
   desktop width. The app hides its own FAB from 960px up in app.css. */
.et-fab {
    position: fixed;
    left: 50%;
    bottom: var(--et-fab-bottom);
    transform: translateX(-50%);
    z-index: 1101;
    display: flex;
    align-items: center;
    gap: 8px;
    border: 0;
    padding: 13px 26px;
    border-radius: 999px;
    background: var(--et-cta-gradient);
    color: #fff;
    font-family: var(--et-font-family-base);
    font-size: 14px;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
    box-shadow: 0 10px 28px rgba(var(--et-cta-shadow-rgb), 0.5);
    transition: transform .14s, filter .14s, box-shadow .14s;
}
/* Every state below has to restate the centering translate: `transform` is a
   single property, so a bare translateY would drop the -50% and snap the pill
   half its own width to the right. */
.et-fab:hover {
    filter: brightness(1.03);
    transform: translateX(-50%) translateY(-1px);
    box-shadow: 0 12px 32px rgba(var(--et-cta-shadow-rgb), 0.55);
}
.et-fab:active {
    filter: brightness(0.94);
    transform: translateX(-50%);
    box-shadow: 0 6px 18px rgba(var(--et-cta-shadow-rgb), 0.45);
}
.et-fab:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 3px;
}
/* Design sets the glyph two steps up from the label — hence its own element. */
.et-fab-icon {
    font-size: 17px;
    font-weight: 800;
    line-height: 1;
}

/* Gallery/demo containment — same trick as .et-panel--inline: a fixed pill
   escapes the specimen card, so the section drops it into a position:relative
   box and switches it to absolute. */
.et-fab--inline { position: absolute; }


/* ---------- EtListLabel ---------- */
/* Uppercase micro-label introducing a run of rows inside a list surface.
   Collapses three hand-rolled copies that had drifted apart:
   .et-notif-menu-section (10px, 8/16/4), .et-notifications-day (10.5px,
   12/16/6) and .et-expenses-month (10.5px, 10/10/4). The canvas says 10.5px,
   so the 10px was the drift; the paddings are reconciled below.

   The horizontal gutter comes from the LIST rather than the atom, because the
   label has to line up with the TEXT of the rows it introduces and the row
   atoms carry different insets: EtNotifRow is 11px/16px, EtActivityRow is
   12px/10px. That is one declaration on the list
   (`--et-list-label-gutter: 10px` on .et-expenses-list) instead of a padding
   variant per list — and it stays right when a list swaps its row atom. */
.et-list-label {
    padding: 8px var(--et-list-label-gutter, 16px) 4px;
    font-family: var(--et-font-family-base);
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--et-color-ink-3);
}
/* A second or later group follows a row; the first follows the edge of the
   surface. The canvas gives the former 14px of air above and the latter 8px,
   so the position decides it, not the caller. The --dense exclusion is what
   the Dense parameter buys: in the bell dropdown every group keeps the 8px
   (canvas does the same there) because that menu is a 340x380 scroller and
   6px of air costs a visible row. Written as one :not() chain rather than a
   modifier override so the two rules cannot be reordered into a tie. */
.et-list-label:not(.et-list-label--dense):not(:first-child) {
    padding-top: 14px;
}


/* ============================================================
   /_dev/ds gallery-specific styles (Pages/_Dev/DesignSystem.razor)
   ============================================================ */

.ds-page {
    padding: 32px;
    background: var(--et-color-bg);
    min-height: 100vh;
    font-family: var(--et-font-family-base);
    /* 1240 = the app shell's own max-width, which is what makes a specimen's
       desktop column able to be the real 682px main column rather than
       whatever is left after the phone column. */
    max-width: 1240px;
    margin: 0 auto;
}
.ds-header { margin-bottom: 24px; }
.ds-eyebrow {
    font-size: 12px;
    font-weight: 800;
    color: var(--et-color-ink-3);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin-bottom: 6px;
}
.ds-eyebrow-sm {
    font-size: 11px;
    font-weight: 700;
    color: var(--et-color-ink-3);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 10px;
}
.ds-title {
    font-size: 30px;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--et-color-ink);
    margin: 0 0 4px 0;
}
.ds-lede {
    font-size: 13.5px;
    color: var(--et-color-ink-2);
    margin: 0;
}
.ds-section {
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-lg);
    box-shadow: var(--et-shadow-card);
    padding: 22px 24px;
    margin-bottom: 20px;
}
.ds-section-title {
    font-size: 15px;
    font-weight: 800;
    color: var(--et-color-ink);
    margin-bottom: 16px;
}
.ds-two-col { display: grid; grid-template-columns: 1fr 260px; gap: 20px; }
.ds-color-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
}
.ds-swatch { display: flex; flex-direction: column; gap: 4px; }
.ds-swatch > div:first-child {
    height: 52px;
    border-radius: 11px;
}
.ds-swatch span {
    font-size: 11.5px;
    font-weight: 700;
    color: var(--et-color-ink);
}
.ds-swatch code {
    font-size: 10.5px;
    color: var(--et-color-ink-3);
    font-family: 'JetBrains Mono', monospace;
}
.ds-shape-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}
.ds-shape-row b {
    font-size: 12px;
    font-weight: 700;
    color: var(--et-color-ink);
    display: block;
}
.ds-caption {
    font-size: 11px;
    color: var(--et-color-ink-3);
    font-weight: 500;
    display: block;
}
.ds-row {
    display: flex;
    align-items: center;
    gap: 12px;
}
.ds-row-align-end {
    display: flex;
    align-items: flex-end;
    gap: 20px;
}
.ds-wrap { flex-wrap: wrap; }
.ds-stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
}
.ds-mt { margin-top: 18px; }


/* ---------- Typography specimen (gallery only) ----------
   A type specimen that doesn't state its values is decoration, so every sample
   in TypographySection carries its size/weight/tracking underneath. Mono and
   ink-3 so the annotation cannot be mistaken for the sample: the samples are
   the app's own classes at the app's own sizes, and anything styled like this
   line is measurement, not UI. */
.ds-type-item + .ds-type-item { margin-top: 16px; }
.ds-type-val {
    display: block;
    margin-top: 4px;
    font-family: var(--et-font-mono);
    font-size: 10.5px;
    line-height: 1.5;
    color: var(--et-color-ink-3);
}
/* NOTE: .ds-type-canvasref lived here — a dashed "measured but not shipped"
   block whose only user was the page-hero phone type. That branch is live now
   (.et-page-hero in app.css), the sample was deleted with it, and no other
   section had ever adopted the class. */
.ds-empty-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
}
/* NOTE: the old .ds-phone preview frame lived here. It faked the phone branch
   by re-stating each md- media query scoped to a wrapper class, which had to
   be kept in sync by hand with every component block. DsSpecimen's real
   container column replaced it, no razor file referenced it any more, and its
   two .et-notif-row-* lines would have fought the @container rule those
   media queries became — so it is gone rather than left to rot. */


/* ---------- DsSpecimen (gallery only) ----------
   Two columns of the same children: natural width, then a 390px phone
   column. The phone column is a container so @container-branched atoms
   actually flip here — see the note in Pages/_Dev/DsSpecimen.razor. */
/* Both columns are FIXED at a real width rather than fluid. 682px is the
   Home/group main column inside the 1240 shell; 390px is the phone. A fluid
   desktop column would silently become whatever the catalog had left over —
   at one point 478px, below the 480 threshold, so both columns rendered the
   compact row and the specimen showed no difference at all. */
.ds-specimen {
    display: grid;
    grid-template-columns: 682px 390px;
    justify-content: start;
    gap: 20px;
    align-items: start;
    /* Safety net: the tracks are fixed-width on purpose, so when the section
       card is narrower than 682+20+390 (odd zoom levels, stale-cache
       breakpoints), the specimen scrolls inside the card instead of the
       phone frame bleeding past its rounded edge. */
    max-width: 100%;
    overflow-x: auto;
}
.ds-specimen-col { min-width: 0; }
.ds-specimen-label {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--et-color-ink-3);
    margin-bottom: 8px;
}
.ds-specimen-desktop {
    container-type: inline-size;
    min-width: 0;
}
/* The phone column carries the DS background so a transparent atom reads the
   same as it does on a real screen, plus the device-ish inset. */
.ds-specimen-phone {
    container-type: inline-size;
    width: 390px;
    padding: 12px;
    border-radius: 18px;
    background: var(--et-color-bg);
    box-shadow: inset 0 0 0 1px var(--et-color-border);
}
.ds-specimen-note {
    margin-top: 12px;
    padding: 10px 12px;
    border-radius: 10px;
    background: var(--et-color-surface-2);
    font-size: 12px;
    line-height: 1.5;
    color: var(--et-color-ink-2);
}
.ds-specimen-note--same { color: var(--et-color-ink-3); }
/* Below the specimen's own footprint the two columns stack rather than
   shrink. Shrinking would be worse than useless here: a squeezed desktop
   column stops standing for 682px, and the container queries inside it would
   start answering a width no real screen produces.

   1280, not the specimen's raw 1092 (682+20+390): the section card's 24px
   side paddings, the page and shell paddings all sit on top of that, so
   between ~1180 and ~1280 the columns still "fit the viewport" while the
   phone frame overflows the .ds-section card on the right. */
@media (max-width: 1279.98px) {
    .ds-specimen { grid-template-columns: 682px; }
    .ds-specimen-col { overflow-x: auto; }
}


/* ---------- EtTopBar gallery mirror (gallery only) ----------
   .et-topbar's phone form is a @media query (see the note above that block),
   which cannot fire inside the specimen's fixed-width phone COLUMN — @media
   keys off the viewport, and the viewport here stays wide. This restates
   that branch's three declarations scoped to .ds-specimen-phone so the
   gallery can preview it. Keep this in lock-step with .et-topbar's
   @media (max-width: 640px) block by hand — nothing enforces the mirror. */
.ds-specimen-phone .et-topbar {
    gap: 10px;
    padding: 12px 14px;
}
.ds-specimen-phone .et-topbar-name { display: none; }
.ds-specimen-phone .et-topbar-right > .et-btn { display: none; }

/* ---------- EtFab gallery absence (gallery only) ----------
   EtFab is a phone-only atom — its three callers all sit in the MudHidden
   phone branch, and on desktop the primary action is the header CTA. The
   specimen's desktop column therefore shows the ABSENCE: the pill is hidden
   and a muted label says why. Showing the pill there read as "this floats
   on desktop too", which is false. */
.ds-specimen-desktop .et-fab { display: none; }
.ds-fab-absent {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 16px;
    text-align: center;
    font-size: 12px;
    color: var(--et-color-ink-3);
}
.ds-specimen-phone .ds-fab-absent { display: none; }

/* ---------- QrCode (2026-09-30, D31) ----------
   Fixed black-on-white SVG (EtQrCode.razor) — a scanner expects a light
   background, so this stays the same in the dark theme on purpose: no
   [data-theme="dark"] override anywhere in this block. */
.et-qr-code {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 12px;
}

/* ---------- Touch-device input zoom guard ----------
   iOS Safari auto-zooms the page when a focused text control's font-size is
   under 16px, and the user has to pinch back out — so on touch devices every
   sub-16px text control bumps to exactly 16px. Gated on pointer:coarse, not
   width: iPads zoom too, desktop pointers never do, and desktop keeps the
   designed 14px. (.et-amount-input-num is 48px — already safe.) LAST in the
   file on purpose: it must out-cascade the components' own equal-specificity
   font-size declarations above. */
@media (pointer: coarse) {
    .et-field-input,
    .et-mini-input-field,
    .et-split-row-box-value {
        font-size: 16px;
    }
}


/* ══ pilot toggle (temporary, removed once the owner decides) ══════════════
   et:bg-plain = plain — the card hairline that the plain page background
   makes necessary. The token half of this pilot is at the end of tokens.css;
   this half is here because it is component CSS, and it comes out with the
   same deletion.

   Why it is needed, measured on the rendered dumps (headless Edge, 430px and
   1200px): --et-color-surface is ALREADY #FFFFFF in light, so once the page
   is #FFFFFF too the two are byte-identical (rgb(255,255,255) both) and every
   card's only remaining separation is --et-shadow-card, a 0.05-alpha 10px
   blur. On the default tinted page the delta is small but real (#FAF8F3 page
   vs #FFFFFF card); at parity the cards visibly melt into the page.

   Light only. Dark needs nothing: measured at #000000 page vs #1E1722 surface
   the cards stand out MORE than they do on the default #15101A page, so a
   border there would be decoration, not repair. `:not([data-theme="dark"])`
   says exactly that.

   A ring rather than a `border`, deliberately: this project has no global
   border-box (app.css says so where it sets box-sizing locally), so a real
   1px border would add 2px to every card and reflow the 430px carousels that
   size their slides off the strip. `0 0 0 1px` is the same hairline with no
   layout at all, follows each card's own border-radius, and is the idiom
   .et-group-menu--flat already uses. The card shadow is restated after it
   because box-shadow does not merge.

   Hover is left alone on purpose: all four card hovers replace the shadow
   with a much heavier 0 10px 24px lift, which separates the card far better
   than a hairline — and hover barely exists on the phone this pilot is for.

   .et-surface is the exception that takes the plain border: it already
   reserves `border: 1px solid transparent`, so colouring it costs nothing. */
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-surface {
    border-color: var(--et-color-border);
}
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-todo-card,
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-todo-card-lg,
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-event-card,
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-event-card-lg,
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-group-card,
html[data-bg-plain="plain"]:not([data-theme="dark"]) .et-filter-panel {
    box-shadow: 0 0 0 1px var(--et-color-border), var(--et-shadow-card);
}
/* ══ end pilot toggle ══════════════════════════════════════════════════════ */
