/* ============================================================
   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> (BetaTestSheet's
   step links, for one), 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: var(--et-cta-ink);
    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: var(--et-cta-ink);
    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 / white. */
.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: var(--et-cta-ink);
    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: var(--et-cta-ink); }
.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: var(--et-cta-ink);
    font-size: 9px;
    font-weight: 700;
    display: grid;
    place-items: center;
    /* Design says a 2px solid white ring — 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: var(--et-cta-ink);
    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: var(--et-cta-ink);                   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: var(--et-cta-ink);
    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: var(--et-cta-ink);
    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;
}
/* 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: var(--et-cta-ink);
    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: var(--et-cta-ink);
    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: var(--et-cta-ink);
}
/* 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;
}
/* The {length}/{max} counter under a bound input with a MaxLength (starter,
   spec §8.2): right-aligned under the text, quiet, digits that do not jump. */
.et-field-counter {
    display: block;
    margin-top: 2px;
    text-align: right;
    font-size: 11px;
    color: var(--et-color-ink-3);
    font-variant-numeric: tabular-nums;
}
/* 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: var(--et-cta-ink);
    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);
}


/* ---------- 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; }
}


/* ---------- 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;
}


/* ---------- 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: var(--et-cta-ink);
    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;
    }
}


/* ---------- 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;
}


/* ---------- 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;
}


/* ---------- EtPersonRow (starter, spec §8.2) ----------
   The single-select picker row: EtMemberRow's geometry and type, a whole-row
   <button>, and a round radio mark where the member row keeps its trailing
   slot. The mark is drawn with two box-shadows (ring + inner dot), so the
   selected state changes no layout. */
.et-person-row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px 14px;
    border: 0;
    border-radius: 12px;
    background: none;
    font-family: var(--et-font-family-base);
    text-align: left;
    color: inherit;
    cursor: pointer;
    transition: background-color .15s;
}
.et-person-row:hover { background-color: rgba(0, 0, 0, 0.03); }
.et-person-row:disabled { cursor: default; opacity: 0.5; }
.et-person-row--divider { box-shadow: inset 0 -1px 0 var(--et-color-border); }
.et-person-row-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.et-person-row-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-person-row-sub {
    font-size: 12px;
    color: var(--et-color-ink-2);
    margin-top: 1px;
}
.et-person-row-radio {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1.5px var(--et-color-border-strong);
    transition: box-shadow var(--et-motion-fast) var(--et-ease-standard);
}
.et-person-row--selected .et-person-row-radio {
    box-shadow: inset 0 0 0 1.5px var(--et-color-accent), inset 0 0 0 6px var(--et-color-surface), inset 0 0 0 11px var(--et-color-accent);
}


/* ---------- 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: var(--et-cta-ink);
    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;
}


/* ---------- 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: var(--et-cta-ink);
    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;
}


/* ---------- 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;
}


/* ---------- 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;
}


/* ---------- 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;
}


/* ---------- 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 (▾/▴), in a filled accent disc: a bare 13px grey
   glyph read as decoration, not as "this opens a menu" (owner, 2026-10-05). The 24px
   disc fits the title's own line, so the header keeps its height. Surface-coloured
   glyph, not white: a dark theme's accent is light (coral #FFB0A4). */
.et-panel-header-title-chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    font-size: 13px;
    line-height: 1;
    color: var(--et-color-surface);
    background: 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, review N-10); 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: var(--et-panel-footer-bg); /* dc literal — bg tint at 92%, a token since D13 */
    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;
    }
    .et-panel:not(.et-panel--docked) .et-panel-body { padding: 16px 16px 20px; }
    /* 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 (shell.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; }
}


/* ---------- 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, review N-10): 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; }
}


/* ---------- 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: var(--et-cta-ink);
    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; }
}


/* ---------- 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: var(--et-cta-ink);
    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;
}


/* ============================================================
   Request flow (starter, spec §8.3) — the Taxigether Figma export's
   MatchRequestCard, IncomingRequestModal and SuccessScreen, plus #18's
   minimised bubble, on Kassza tokens: no Figma yellow, no Tailwind. Gallery
   group "Kérés-folyamat", same order.
   ============================================================ */

/* ---------- EtCountdown ----------
   mm:ss in tabular figures, so the digits do not shimmy as they tick. */
.et-countdown {
    font-family: var(--et-font-family-base);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    letter-spacing: 0.01em;
    white-space: nowrap;
}

/* ---------- EtProgressBar ----------
   A 4px track; the fill is full width and SCALED from the left edge, so the
   per-second updates of a countdown stay on the compositor. The 1s linear
   transition makes those steps one smooth drain; motion off = steps. */
.et-progress {
    position: relative;
    width: 100%;
    height: 4px;
    border-radius: 999px;
    background: var(--et-color-surface-2);
    overflow: hidden;
}
.et-progress-fill {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--et-color-accent);
    transform-origin: 0 50%;
}
html:not([data-motion="off"]) .et-progress-fill {
    transition: transform 1s linear;
}
.et-progress--amber .et-progress-fill { background: var(--et-color-pend); }
.et-progress--green .et-progress-fill { background: var(--et-color-owed); }
.et-progress--red   .et-progress-fill { background: var(--et-color-owe); }

/* ---------- EtRequestCard ----------
   The full card: kind line + timer, the party row (avatar, name, meta, pill,
   chevron), the quoted message, the answers lined up under the name
   (52px = the 40px avatar + the 12px gap). Compact: no kind line, the time
   left beside the pill, the bar along the foot, 48px indent (36 + 12). */
.et-request-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
    padding: 14px;
    background: var(--et-color-surface);
    border-radius: var(--et-card-radius-lg);
    box-shadow: var(--et-shadow-card);
    font-family: var(--et-font-family-base);
    cursor: pointer;
    transition: transform .16s;
}
@media (hover: hover) {
    .et-request-card:hover { transform: translateY(-2px); box-shadow: var(--et-shadow-card-hover); }
}
.et-request-card:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 2px;
}
.et-request-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.et-request-card-kind {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--et-color-ink-3);
}
.et-request-card-kind-glyph { font-size: 15px; text-transform: none; }
.et-request-card-timer {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
    font-size: 14px;
    color: var(--et-color-ink);
}
.et-request-card-bar { width: 96px; }
.et-request-card-party {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}
.et-request-card-who { flex: 1 1 auto; min-width: 0; }
.et-request-card-name {
    font-size: 15px;
    font-weight: 700;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-request-card-meta {
    margin-top: 2px;
    font-size: 12px;
    font-weight: 500;
    color: var(--et-color-ink-3);
    font-variant-numeric: tabular-nums;
}
.et-request-card-state {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.et-request-card-state .et-countdown { font-size: 13px; color: var(--et-color-ink-2); }
.et-request-card-chevron {
    flex-shrink: 0;
    font-size: 20px;
    line-height: 1;
    color: var(--et-color-ink-4);
}
.et-request-card-message {
    margin-left: 52px;
    padding: 6px 10px;
    border-radius: 10px;
    background: var(--et-color-surface-2);
    color: var(--et-color-ink-2);
    font-size: 13px;
    font-style: italic;
    line-height: 1.45;
    overflow-wrap: anywhere;
}
.et-request-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-left: 52px;
}
.et-request-card--compact { gap: 8px; padding: 12px; }
.et-request-card--compact .et-request-card-name { font-size: 14px; font-weight: 600; }
.et-request-card--compact .et-request-card-message {
    margin-left: 48px;
    padding: 0;
    background: none;
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-request-card--compact .et-request-card-actions { margin-left: 48px; }
.et-request-card-foot-bar { width: auto; margin-left: 48px; }
/* A closed request is history: no hover lift beyond the cursor. */
.et-request-card--closed .et-request-card-name { color: var(--et-color-ink-2); }

/* ---------- EtSuccessHero ----------
   Centred: the pair joined by the accent line with the check on it, the
   emoji, the title, the subtitle, the caller's rows, the CTA. On mount the
   check scales in and the two line halves draw out from it (scaleX from
   the check's side) — transform/opacity, gated like every motion here. */
.et-success-hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px 0 4px;
    text-align: center;
    font-family: var(--et-font-family-base);
}
.et-success-hero-pair {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 8px;
}
.et-success-hero-link { display: inline-flex; align-items: center; gap: 6px; }
.et-success-hero-line {
    width: 28px;
    height: 3px;
    border-radius: 999px;
    background: var(--et-color-accent);
}
.et-success-hero-line:first-child { transform-origin: 100% 50%; }
.et-success-hero-line:last-child { transform-origin: 0 50%; }
.et-success-hero-check {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--et-cta-gradient);
    color: var(--et-cta-ink);
    font-size: 14px;
    font-weight: 800;
    box-shadow: var(--et-cta-shadow);
}
.et-success-hero-emoji { font-size: 40px; line-height: 1.1; }
.et-success-hero-title {
    font-size: 22px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--et-color-ink);
}
.et-success-hero-subtitle {
    max-width: 340px;
    font-size: 14px;
    line-height: 1.45;
    color: var(--et-color-ink-2);
}
.et-success-hero-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    align-self: stretch;
    margin-top: 12px;
    text-align: left;
}
.et-success-hero-actions { align-self: stretch; margin-top: 12px; }
@keyframes et-success-check { from { transform: scale(0.4); opacity: 0; } }
@keyframes et-success-line { from { transform: scaleX(0); } }
html:not([data-motion="off"]) .et-success-hero-check {
    animation: et-success-check var(--et-motion-enter) cubic-bezier(0.34, 1.4, 0.64, 1) both;
}
html:not([data-motion="off"]) .et-success-hero-line {
    animation: et-success-line var(--et-motion-enter) var(--et-ease-enter) 120ms both;
}

/* ---------- EtIncomingRequest ----------
   Its own .et-modal-overlay (the dialog dim and fade). Below 960px the grid
   pins the card to the bottom edge and it rises in like a sheet (safe-area
   below it); from 960px it sits centred and settles from 96% like EtDialog. */
.et-incoming-overlay { align-items: end; }
.et-incoming {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: min(400px, 100%);
    max-height: calc(100dvh - 32px);
    overflow-y: auto;
    padding: 22px 20px calc(20px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
    box-sizing: border-box;
    background: var(--et-color-surface);
    border-radius: 24px;
    border: 2px solid var(--et-color-accent);
    box-shadow: var(--et-shadow-xl), 0 12px 32px rgba(var(--et-cta-shadow-rgb), 0.22);
    font-family: var(--et-font-family-base);
}
.et-incoming:focus { outline: none; }
.et-incoming-head { display: flex; flex-direction: column; gap: 4px; }
.et-incoming-title {
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--et-color-ink);
}
.et-incoming-subtitle { font-size: 13px; color: var(--et-color-ink-2); }
.et-incoming-timer { display: flex; flex-direction: column; gap: 8px; }
.et-incoming-timer-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}
.et-incoming-timer-label { font-size: 13px; color: var(--et-color-ink-2); }
.et-incoming-timer .et-countdown { font-size: 17px; color: var(--et-color-accent); }
.et-incoming-sender {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    border-radius: var(--et-card-radius-sm);
    background: var(--et-color-surface-2);
}
.et-incoming-sender-body { min-width: 0; }
.et-incoming-sender-name {
    font-size: 15px;
    font-weight: 700;
    color: var(--et-color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-incoming-sender-sub { margin-top: 2px; font-size: 12px; color: var(--et-color-ink-3); }
.et-incoming-message {
    padding: 10px 12px;
    border-radius: var(--et-card-radius-sm);
    border: 1px solid var(--et-color-border);
    color: var(--et-color-ink-2);
    font-size: 14px;
    font-style: italic;
    line-height: 1.45;
    overflow-wrap: anywhere;
}
.et-incoming-actions { display: flex; flex-direction: column; gap: 10px; }
.et-incoming-actions-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.et-incoming-actions-row > .et-btn { justify-content: center; }
@keyframes et-incoming-rise { from { transform: translateY(100%); } }
@keyframes et-incoming-settle { from { transform: scale(0.96); opacity: 0; } }
@media (max-width: 959.98px) {
    .et-incoming-overlay { padding: 0 0 0; }
    .et-incoming { width: 100%; border-radius: 24px 24px 0 0; border-bottom: 0; }
    html:not([data-motion="off"]) .et-incoming-overlay .et-incoming {
        animation: et-incoming-rise var(--et-motion-enter) var(--et-ease-enter) both;
    }
}
@media (min-width: 960px) {
    .et-incoming-overlay { align-items: center; }
    .et-incoming { padding-bottom: 20px; }
    html:not([data-motion="off"]) .et-incoming-overlay .et-incoming {
        animation: et-incoming-settle var(--et-motion-enter) var(--et-ease-enter) both;
    }
}
/* Gallery/demo containment — same trick as .et-panel--inline. */
.et-incoming-overlay.et-incoming-overlay--inline { position: absolute; }

/* ---------- EtIncomingBubble ----------
   56px disc: the 44px avatar centred in a 3px ring that drains clockwise
   from twelve o'clock (the svg is turned a quarter; the arc's dash is the
   fraction left of a 100-unit path). Bottom-right: 16px over the phone's
   bottom bar where the shell paints one, 24px from the corner at 960+. */
.et-incoming-bubble {
    position: fixed;
    right: 16px;
    bottom: calc(16px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
    z-index: 1105;
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--et-color-surface);
    box-shadow: var(--et-shadow-xl);
    cursor: pointer;
}
.et-incoming-bubble:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: 3px;
}
.et-incoming-bubble > * { grid-area: 1 / 1; }
.et-incoming-bubble-ring {
    width: 56px;
    height: 56px;
    transform: rotate(-90deg);
}
.et-incoming-bubble-track,
.et-incoming-bubble-arc {
    fill: none;
    stroke-width: 3;
}
.et-incoming-bubble-track { stroke: var(--et-color-surface-2); }
.et-incoming-bubble-arc { stroke: var(--et-color-accent); stroke-linecap: round; }
@media (max-width: 959.98px) {
    html.et-shell-bar .et-incoming-bubble {
        bottom: calc(var(--et-bottom-bar-height) + 16px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
    }
}
@media (min-width: 960px) {
    .et-incoming-bubble { right: 24px; bottom: 24px; }
}
@keyframes et-incoming-bubble-in { from { transform: scale(0.6); opacity: 0; } }
html:not([data-motion="off"]) .et-incoming-bubble {
    animation: et-incoming-bubble-in var(--et-motion-enter) cubic-bezier(0.34, 1.4, 0.64, 1) both;
}
/* Gallery/demo containment. */
.et-incoming-bubble.et-incoming-bubble--inline { position: static; }


/* ============================================================
   S7 — reusable components from the Taxigether design (spec §8.4),
   rebuilt on the tokens: no Figma yellow, no Tailwind. A re-skin
   (the Taxigether dump) reaches them through the tokens alone.
   ============================================================ */

/* ---------- EtRating ---------- */
/* inline-flex with no gap: the row's :hover box is the stars themselves, so a
   pointer between two stars never lights all five. */
.et-rating {
    display: inline-flex;
    align-items: center;
}
.et-rating-star {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--et-color-ink-4);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: transform .14s;
}
.et-rating-star svg {
    width: 32px;
    height: 32px;
    fill: transparent;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linejoin: round;
}
.et-rating-star--on { color: var(--et-color-accent); }
.et-rating-star--on svg { fill: currentColor; }
.et-rating-star:focus-visible {
    outline: 2px solid var(--et-color-accent);
    outline-offset: -2px;
}
/* Hover preview: every star lights while the pointer is on the row, then the
   ones after the hovered star go dark again — "up to here". Pointer devices
   only: a touch leaves :hover stuck on the tapped star. */
@media (hover: hover) {
    .et-rating:hover .et-rating-star { color: var(--et-color-accent); }
    .et-rating:hover .et-rating-star svg { fill: currentColor; }
    .et-rating:hover .et-rating-star:hover ~ .et-rating-star { color: var(--et-color-ink-4); }
    .et-rating:hover .et-rating-star:hover ~ .et-rating-star svg { fill: transparent; }
    .et-rating-star:hover { transform: scale(1.1); }
}
@media (prefers-reduced-motion: reduce) {
    .et-rating-star { transition: none; }
}
/* ReadOnly — the compact profile line. */
.et-rating-compact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-family: var(--et-font-family-base);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.et-rating-compact-star {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    fill: var(--et-color-accent);
}
.et-rating-compact-value { font-weight: 700; color: var(--et-color-ink); }
.et-rating-compact-sep,
.et-rating-compact-count { font-weight: 500; color: var(--et-color-ink-3); }


/* ---------- EtChipGroup ---------- */
/* The chips are EtChip's filter chips (their own block); the group only lays
   them out — wrapping, so a long tag list never scrolls sideways. */
.et-chip-group {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}


/* ---------- EtVerificationRow ---------- */
/* EtToggleRow's geometry. min-height, not padding, sets the height: a row
   ending in the 24px pill and one ending in the ~33px button stay level. */
.et-verification-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 56px;
    padding: 8px 18px;
    box-sizing: border-box;
    font-family: var(--et-font-family-base);
}
.et-verification-row--divider { border-bottom: 1px solid var(--et-color-border); }
.et-verification-row-icon {
    width: 24px;
    flex-shrink: 0;
    text-align: center;
    font-size: 20px;
    line-height: 1;
}
.et-verification-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-pill.et-verification-row-pill { background: var(--et-status-bg-confirmed); flex-shrink: 0; }
.et-verification-row-action { flex-shrink: 0; }


/* ---------- EtStatTile / EtStatRow ---------- */
/* min-width: 0 on both roots so a grid track can shrink them; the tone is one
   shared modifier (.et-stat--*) that colours whichever value it sits on. */
.et-stat-tile {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    box-sizing: border-box;
    padding: 14px 16px;
    border-radius: var(--et-card-radius-sm);
    background: var(--et-color-surface-2);
    font-family: var(--et-font-family-base);
}
.et-stat-tile-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--et-color-ink-3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-stat-tile-value {
    font-size: 20px;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--et-color-ink);
    font-variant-numeric: tabular-nums;
}
.et-stat-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    min-width: 0;
    padding: 10px 6px;
    font-family: var(--et-font-family-base);
}
.et-stat-row--divider { border-bottom: 1px solid var(--et-color-border); }
.et-stat-row-label {
    min-width: 0;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--et-color-ink-2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.et-stat-row-value {
    flex-shrink: 0;
    font-size: 14px;
    font-weight: 700;
    color: var(--et-color-ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.et-stat--accent :is(.et-stat-tile-value, .et-stat-row-value) { color: var(--et-color-accent); }
.et-stat--green  :is(.et-stat-tile-value, .et-stat-row-value) { color: var(--et-color-owed); }
.et-stat--red    :is(.et-stat-tile-value, .et-stat-row-value) { color: var(--et-color-owe); }
.et-stat--amber  :is(.et-stat-tile-value, .et-stat-row-value) { color: var(--et-color-pend); }


/* ---------- EtContactActions ---------- */
/* Inline: the pair sits at the end of a row and never shrinks. Block: two
   equal columns; a grid item stretches, so the <a> (call) needs no
   box-sizing fix of its own here. */
.et-contact-actions {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.et-contact-actions--block {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.et-contact-actions-glyph {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}


/* ============================================================
   /_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. */


/* EtCardStrip's specimen cards (gallery only): the starter ports no event card. */
.ds-strip-card { min-height: 88px; }
.ds-strip-card-title { font-size: 15px; font-weight: 700; color: var(--et-color-ink); margin-bottom: 4px; }
.ds-strip-card--past { opacity: 0.55; }

/* ---------- 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; }
}


/* ---------- 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;
    }
}

