/* =====================================================================
   eGun — desktop layer (2026-07-26)
   =====================================================================

   Everything in this file is scoped under `.ui-desktop` on <html>, which
   base.html.twig only emits when the twig global `desktop_ui_enabled`
   (env DESKTOP_UI_ENABLED) is true — and the same flag decides whether
   this stylesheet is linked at all. Switching it off therefore returns
   the app to exactly the phone-width PWA it was before: no rule here can
   match, and the file isn't even downloaded.

   ---------------------------------------------------------------------
   THE IDEA

   eGun's own artifact is a booking line: Kontoauszug, accountpos,
   Rechnung, Gebotsliste. So the desktop is not a wider phone — it is a
   LEDGER. A card on the phone becomes a row here: thumbnail, title,
   price, count, remaining time, all in fixed columns with tabular
   figures. app.css already leaned this way; its own comment calls the
   price "scannable like a printed price column".

   THE SIGNATURE is not built here — it is inherited. `list-item__pulse`
   already exists: a gold hairline along each card's bottom edge, filled
   to the auction's elapsed share, red inside the final hour. On a 380px
   phone that is a three-pixel hint. At 1180px, with every row sharing a
   left edge and a width, the same element becomes a BAR CHART down the
   page — one glance answers "what is about to end?". All this layer adds
   is a faint track behind it, so the bar reads as a position along a
   span rather than just a length.

   WHAT WAS DELIBERATELY NOT ADDED: a separate desktop navigation. The
   bottom tab bar already carries the five primary destinations with
   icons, labels and active-state logic, so base.html.twig now renders
   that same <nav> inside <header> (it is position:fixed on phones, so
   its place in the DOM never mattered there) and this file lays it out
   horizontally. One nav, one set of labels, no new translation keys —
   the design matches by construction instead of by imitation.

   No new colours and no new typefaces: the palette, the display face and
   the card surfaces are app.css's. What changes is measure, density and
   the type SCALE.
   ===================================================================== */

@media (min-width: 900px) {
    .ui-desktop {
        /* One measure for every page, so headers, rows and the item page
           share a left and right edge as you move between them. 1180 is
           four 260px product cards plus gaps — the browse grid decides
           the page width, because that is the page with a real grid. */
        --desk-max: 1180px;
        /* The readable measure. Forms and full-width action buttons share
           it so a page never mixes a 46rem input with a 1180px button
           under it — one number, stated once. */
        --desk-measure: 46rem;
        /* The bottom bar is gone up here; zeroing the variable reclaims
           the space app.css reserved for it on <body> AND repositions
           the install prompt that hangs off the same value. (The toast
           stack has its own top-center desktop placement since
           2026-09-02, see the notice block further down.) */
        --tab-bar-h: 0rem;
        /* Nav active tint as a TOKEN, not a literal: app.css redefines
           --egun-blue for dark mode, so a hardcoded light-mode rgba would
           be invisible on the dark surface. Overridden below. */
        --desk-nav-active: rgba(37, 25, 188, 0.08);
        /* How far a sticky element must sit below the top of the
           viewport to clear the sticky site header. ONE number: three
           panes and the ledger table head all cleared "the header" with
           two different hand-measured values (4.75rem / 3.9rem) that
           would drift apart the next time the header changes. The table
           head sits flush against it, everything else keeps a gap. */
        --desk-sticky-top: 3.9rem;
        --desk-sticky-gap: 0.85rem;
    }

    /* ---------------------------------------------------------------
       Shell
       --------------------------------------------------------------- */

    /* Full-bleed bar, centred content, no wrapper element required. */
    .ui-desktop .site-header {
        padding-inline: max(1.5rem, calc((100vw - var(--desk-max)) / 2));
        gap: 1.25rem;
    }

    .ui-desktop .site-main {
        max-width: var(--desk-max);
        /* Centred, not left-pinned: max-width alone hugged the left edge
           on anything wider than 1180 + padding, and every page read as
           off-balance on a real monitor (user report 2026-08-08). The
           header centres itself via its padding formula; this is the
           same geometry for the content. */
        margin-inline: auto;
        padding: 1.25rem 1.5rem 3rem;
    }

    /* Document pages (help, legal topics, the messages list, the tax
       form): their content is ONE reading column, so the page narrows to
       the measure and sits centred - a Formular on the desk, not a
       ledger. Opt-in per page via the main_class block, exactly like
       .page-search. The 3rem accounts for .site-main's own padding, so
       the CONTENT is --desk-measure wide. */
    .ui-desktop .site-main.desk-doc {
        max-width: calc(var(--desk-measure) + 3rem);
    }

    /* Centred COLUMN pages (2026-08-20, "all pages should be the same"):
       same geometry as .desk-doc, but WITHOUT desk-doc's semantics - the
       Formular caption-grid selectors exclude .desk-doc (document pages
       keep stacked labels), while these pages keep the caption grid and
       only gain the centred column. Applied via main_class to the
       account family (account, preferences, ratings, contacts,
       blacklist, devices) and the auth forms - the pages whose measure-
       capped content used to hug the container's left edge. --wide is
       the ratings queue (its ledger row needs 60rem, see .rating-list). */
    .ui-desktop .site-main.desk-col {
        max-width: calc(var(--desk-measure) + 3rem);
    }

    .ui-desktop .site-main.desk-col--wide {
        max-width: calc(60rem + 3rem);
    }

    /* ... and the prose variant justifies its long legal paragraphs.
       German legal text in Blocksatz needs hyphenation or it grows
       rivers - lang="de" is on <html>, so `hyphens: auto` has real
       dictionaries behind it. */
    .ui-desktop .desk-doc--prose p,
    .ui-desktop .desk-doc--prose small {
        text-align: justify;
        hyphens: auto;
    }

    /* The ad line: originally hidden here in favor of the header's
       masthead slot; the third pass below reverses that (the masthead
       caps banners at logo height - no desktop ad format fits it) and
       makes this strip THE desktop banner line instead. */

    /* --- Primary nav, promoted out of the bottom bar --- */
    .ui-desktop .tab-bar {
        position: static;
        z-index: auto;
        flex: 1 1 auto;
        gap: 0.15rem;
        background: none;
        border-top: 0;
        padding-bottom: 0;
    }

    .ui-desktop .tab-bar__item {
        flex: 0 0 auto;
        flex-direction: row;
        align-items: center;
        justify-content: center;
        gap: 0.45rem;
        min-height: 0;
        padding: 0.45rem 0.8rem;
        font-size: 0.92rem;
        line-height: 1.2;
        border-radius: 999px;
    }

    /* The notification count follows the label instead of overlaying
       the icon: in the horizontal nav the phone's absolute badge landed
       LEFT of "Mein eGun" (it is the item's first DOM child), which read
       as belonging to the Merkliste next door (user report 2026-08-08).
       order beats markup surgery: the same element, right of the text. */
    .ui-desktop .tab-bar__badge {
        position: static;
        order: 3;
        margin-left: 0.1rem;
    }

    /* The home search bar at a typing width, centred over the Specials
       gallery - a 1180px input is not a better input, it is just a
       longer border (user report 2026-08-08). 40rem comfortably holds
       an Artikel-ID or a three-word query. */
    .ui-desktop .search-form--home {
        max-width: 40rem;
        margin-inline: auto;
    }

    .ui-desktop .tab-bar__item svg {
        width: 1.05rem;
        height: 1.05rem;
    }

    /* Quiet pills, the same device the rest of the app uses for
       secondary actions — the nav should not shout on every page. */
    .ui-desktop .tab-bar__item:hover {
        background: var(--egun-bg);
        color: var(--egun-text);
        text-decoration: none;
    }

    .ui-desktop .tab-bar__item--active {
        background: var(--desk-nav-active);
        color: var(--egun-blue);
        font-weight: 600;
    }

    /* Verkaufen stays the one loud thing, but flattened: the phone's
       raised circular badge is a thumb-target idiom and reads as clutter
       next to a row of text pills. */
    .ui-desktop .tab-bar__item--sell {
        background: var(--egun-gold);
        color: var(--egun-text);
        font-weight: 600;
    }

    .ui-desktop .tab-bar__item--sell:hover {
        background: #ffd75c;
        color: var(--egun-text);
    }

    .ui-desktop .tab-bar__sell-badge {
        width: auto;
        height: auto;
        margin: 0;
        padding: 0;
        background: none;
        box-shadow: none;
        border-radius: 0;
        transform: none;
    }

    /* --- Option A (2026-08-08): the drawer's trigger is the ACCOUNT.
       The burger disappears up here; the username - restyled as a
       button with a caret - opens the same list. Anonymous visitors
       get the drawer's two links (Anmelden/Registrieren) laid inline
       instead: two links are not a menu. --- */
    .ui-desktop .site-header__toggle {
        display: none;
    }

    .ui-desktop .site-header__user {
        display: none;
    }

    .ui-desktop .site-header__account {
        display: inline-flex;
        align-items: center;
        gap: 0.3rem;
        border: 0;
        background: none;
        padding: 0.35rem 0.6rem;
        border-radius: 999px;
        font: inherit;
        font-weight: 600;
        font-size: 0.9rem;
        color: var(--egun-blue);
        white-space: nowrap;
        cursor: pointer;
    }

    .ui-desktop .site-header__account:hover,
    .ui-desktop .site-header__account[aria-expanded="true"] {
        background: var(--desk-nav-active);
    }

    .ui-desktop .site-header__account-caret {
        font-size: 0.7em;
        line-height: 1;
    }

    /* Anonymous: always-visible inline links, never a panel. The
       !important is not bravado - app.css deliberately resets
       `[hidden] { display: none !important }` (its own show-once
       buttons need it), and only an important declaration of higher
       specificity can override that for THIS one element. The phone
       keeps the hidden attr + burger flow untouched. */
    .ui-desktop .site-header__links--anon,
    .ui-desktop .site-header__links--anon[hidden] {
        display: flex !important;
        position: static;
        width: auto;
        flex-direction: row;
        align-items: center;
        gap: 0.25rem;
        padding: 0;
        background: none;
        border: 0;
        box-shadow: none;
    }

    .ui-desktop .site-header__links--anon > a {
        padding: 0.35rem 0.6rem;
        border-radius: 999px;
        font-weight: 600;
        font-size: 0.9rem;
        white-space: nowrap;
    }

    /* "Klassische Ansicht" ist eine Burger-/Dropdown-Fusszeile; im
       staendig sichtbaren Anon-Kopf waere sie ein Fremdkoerper -
       dort bleiben Fusszeilen-Link und Banner die Wege (2026-08-18). */
    .ui-desktop .site-header__links--anon .nav-classic {
        display: none;
    }

    /* --- The burger list becomes the account dropdown --- */
    .ui-desktop .site-header__links:not([hidden]) {
        position: absolute;
        top: calc(100% + 0.4rem);
        /* left:auto is load-bearing: the phone panel sets left:0, and
           with BOTH offsets plus a width, LTR honours left and ignores
           right - the dropdown rendered at the LEFT viewport edge ever
           since the first desktop pass (found 2026-08-08 while wiring
           the account trigger). */
        left: auto;
        right: max(1.5rem, calc((100vw - var(--desk-max)) / 2));
        z-index: 20;
        width: 16rem;
        display: flex;
        flex-direction: column;
        padding: 0.35rem;
        background: var(--egun-surface);
        border: 1px solid var(--egun-border);
        border-radius: var(--radius);
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
    }

    .ui-desktop .site-header__links:not([hidden]) > a {
        padding: 0.5rem 0.6rem;
        border-radius: var(--radius-control);
    }

    .ui-desktop .site-header__links:not([hidden]) > a:hover {
        background: var(--egun-bg);
        text-decoration: none;
    }

    /* Die Klassisch-Fusszeile uebernimmt im Dropdown dessen
       Zeilen-Einzug (0.6rem statt der Panel-0.35rem). */
    .ui-desktop .site-header__links:not([hidden]) .nav-classic button {
        padding: 0.5rem 0.6rem 0.15rem;
    }

    /* ---------------------------------------------------------------
       Type scale. Same faces — the phone scale is simply too compressed
       once a line can run 1100px.
       --------------------------------------------------------------- */
    .ui-desktop .site-main h1 {
        font-size: 1.9rem;
        line-height: 1.15;
    }

    .ui-desktop .site-main h2 {
        font-size: 1.25rem;
    }

    /* ---------------------------------------------------------------
       The ledger row
       --------------------------------------------------------------- */
    .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item {
        padding: 0.5rem 0.75rem 0.6rem;
    }

    .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__link {
        gap: 1rem;
    }

    .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__thumb {
        /* 112px (was 58, F1 2026-08-26): see app.css - the desktop
           ledger had shrunk the merchandise to an icon. */
        width: 112px;
        height: 112px;
    }

    /* The card's stacked title/price/meta becomes three real columns.
       Fixed bases on the right two so figures line up down the page;
       the title takes the rest.

       CONTAINER-gated, not viewport-gated (QA fix 2026-08-01): the
       columns need ~490px before the title gets a pixel, and the LIST's
       width is what varies - inside the search rail layout it is ~520px
       at a 905px viewport, where the viewport-keyed version compressed
       the title to one letter per line. The list is its own size
       container; below 44rem of list width the row keeps the phone's
       stacked anatomy, wherever it happens to live. Still nested inside
       @media 900px so phones and the kill switch see no change. */
    .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) {
        container-type: inline-size;
    }

    @container (min-width: 44rem) {
        /* Anchored anatomy (user 2026-08-26: text "floated" mid-card,
           chips landed somewhere new on every row): the row stretches,
           the TITLE starts at the top like on the phone, and the chip
           line pins to the CARD BOTTOM (margin-top:auto below) - same
           spot on every card, level with the image's bottom edge. The
           thumb and the two number columns stay vertically centered. */
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__link {
            align-items: stretch;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__link > .list-item__thumb {
            align-self: center;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__body {
            flex-direction: row;
            align-items: stretch;
            gap: 1.25rem;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price-line,
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__meta {
            align-self: center;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__title {
            display: flex;
            flex-direction: column;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__title .list-item__tags {
            margin-top: auto;
            padding-top: 0.3rem;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__title {
            flex: 1 1 auto;
            min-width: 0;
            /* full 1rem in the wide ledger (F1 2026-08-26): the title
               column owns the row's middle - let it read like one. */
            font-size: 1rem;
            /* Still UNCLAMPED, on purpose: full titles were an explicit
               product decision (see _list_item.html.twig), and a trader
               scanning optics needs "8x56" at the end of the name. Rows
               therefore vary in height — which costs the ledger nothing,
               because the pulse bars below still share a left edge and a
               width, and that is what makes them comparable. */
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price-line {
            /* 8.5rem (was 11.5, rebalance 2026-08-26): the label sits on
               its OWN line above the amount now (rule below), so the
               column only needs the amount's width - the reclaimed rem
               go to the starved title column (user report: five-line
               titles beside an empty middle). */
            flex: 0 0 8.5rem;
            justify-content: flex-end;
            text-align: right;
            flex-wrap: wrap;
            row-gap: 0;
        }

        /* Label always stacked above the amount in the ledger - one
           consistent two-line cell instead of inline-until-it-wraps. */
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price-label {
            flex: 1 1 100%;
            text-align: right;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price {
            font-size: 1.35rem;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__meta {
            /* 9.5rem (was 14; first cut 7.5 overflowed on "endet bald"
               rows - the badge + "59 Min" need ~9rem): fits the
               F4-compact times INCLUDING the urgency badge. */
            flex: 0 0 9.5rem;
            justify-content: flex-end;
            flex-wrap: nowrap;
            font-size: 0.85rem;
        }

        /* The bid/stock count moves UNDER the price in ledger rows (user
           request 2026-08-09): reveal the price-cell copy as a full-width
           second line, hide the meta-line original - the figures column
           keeps only the urgency badge and the remaining time. */
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price-sub {
            display: block;
            flex: 1 1 100%;
            text-align: right;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__meta > .list-item__bids,
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__meta > .list-item__qty {
            display: none;
        }

        /* (The condition/Händler pills live on their own line INSIDE the
           title cell - see _list_item.html.twig's .list-item__tags - so
           the fixed price/figures columns never have to host them.) */

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__ends {
            margin-left: 0;
            font-variant-numeric: tabular-nums;
        }
    }

    /* --- The signature: the Auktionspuls as a bar chart --- */
    .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__pulse {
        height: 3px;
        z-index: 1;
    }

    /* The track. Without it a short bar and a nearly-finished auction
       look like the same kind of nothing; with it, every row shows the
       same span and you read POSITION along it. */
    .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 0;
        height: 3px;
        background: var(--egun-border);
        border-bottom-left-radius: var(--radius);
        border-bottom-right-radius: var(--radius);
        pointer-events: none;
    }

    /* ---------------------------------------------------------------
       Item page: the two things a visitor weighs against each other —
       the photographs and the trade decision — sit side by side at the
       top. Everything that is READ rather than weighed (seller card,
       description, shipping, legal text) runs full width beneath them.

       Only the top region is a grid, and it is exactly ONE row: the
       gallery and the trade card share it, so a taller trade card simply
       makes that row taller instead of distributing its height into rows
       below it. Two full-height grid columns were tried first and tore
       visible gaps into the left column; a float was tried second and
       cannot work at all, because the aside follows the gallery in source
       order and a float can never rise above earlier block content.
       --------------------------------------------------------------- */
    .ui-desktop .page-item {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 27rem);
        column-gap: 2rem;
        /* Row rhythm lives on the GRID, not on child margins: the elastic
           stage must end exactly at the trade card's bottom edge, so both
           row-2 children have their phone bottom-margins zeroed below and
           this gap separates them from row 3 instead. */
        row-gap: 1rem;
        align-items: start;
    }

    /* Read full width unless placed otherwise (the head row, the
       recommendation rails at the end). */
    .ui-desktop .page-item > * {
        grid-column: 1 / -1;
        min-width: 0;
    }

    /* "The eBay way" (user decision 2026-08-09; elastic refinement the
       same day): row 2 = photo stage beside the trade card. The stage's
       WIDTH is constant; its HEIGHT is elastic - it stretches to exactly
       the trade card's height (min 380px), the letterbox absorbing the
       difference. That inversion is what kills the dead space the fixed
       380px stage left under the picture whenever the card ran taller:
       the card is the ruler, the frame always matches it, and nothing
       can ever clip. Row 3 = legal-notice card, item-facts strip, seller
       card and description across the FULL page width. */
    .ui-desktop .page-item > .item-main-a {
        grid-column: 1;
        grid-row: 2;
        align-self: stretch;
    }

    .ui-desktop .page-item > .item-main-b {
        grid-column: 1 / -1;
        grid-row: 3;
    }

    .ui-desktop .page-item > .item-aside {
        grid-column: 2;
        grid-row: 2;
        align-self: start;
    }

    /* The phone bottom-margins of the two row-2 children would stretch
       their tracks past the visible card edges - and the elastic stage
       tracks the ASIDE, so the photo frame would end below the trade
       card's edge. Zeroed; the grid's row-gap carries the rhythm. */
    .ui-desktop .page-item .item-aside .trade-card {
        margin-bottom: 0;
    }

    .ui-desktop .page-item .item-main-a .auction-gallery-wrap {
        margin-bottom: 0;
    }

    .ui-desktop .page-item > .item-head {
        margin-bottom: 0; /* the row-gap already separates head and stage */
    }

    /* Share/recommend inside the card render as regular stack buttons on
       every form factor (user 2026-08-09: same look as the Merkliste
       button - an earlier muted-text-link treatment here was rejected). */

    /* The stage IS the full column - constant width, so the chips
       anchoring to the wrap sit at the stage's corner, eBay-style
       (replaces the earlier shrink-wrap, which made the frame width
       follow the photo and gave every item a different silhouette).
       Full height too: the elastic-stage chain (stretched main-a ->
       wrap -> stage) is what lets the frame track the trade card. */
    /* Since the thumbnail rail (2026-08-26) the column is a flex chain:
       main-a stacks wrap + rail, the wrap takes the elastic remainder -
       the stage keeps tracking the trade card, the corner-anchored
       chips stay on the photo frame (the rail lives OUTSIDE the wrap). */
    .ui-desktop .page-item > .item-main-a {
        display: flex;
        flex-direction: column;
    }

    .ui-desktop .page-item .auction-gallery-wrap {
        width: 100%;
        flex: 1 1 auto;
        min-height: 0;
    }

    .ui-desktop .page-item .gallery-thumbs {
        flex: 0 0 auto;
        margin: 0.6rem 0 0;
    }

    .ui-desktop .page-item .gallery-thumbs__item {
        width: 56px;
        height: 56px;
    }

    /* The description deliberately has NO measure cap since the aside
       stopped floating (2026-08-09): legacy descriptions are full-width
       HTML layouts - tables, banners - and clamping them was what made
       them tiny. Seller AGB stay plain prose and keep the measure. */
    .ui-desktop .page-item .auction-agb__text {
        max-width: 68ch;
    }

    /* ---------------------------------------------------------------
       Forms. A 1180px-wide text input is unusable — the eye loses the
       line. Inputs keep a readable measure and simply sit left.
       --------------------------------------------------------------- */
    .ui-desktop .site-main form.form-stack,
    .ui-desktop .site-main fieldset,
    .ui-desktop .site-main > .button-link--block,
    .ui-desktop .site-main > p > .button-link--block {
        max-width: var(--desk-measure);
    }

    /* /sell (user decisions 2026-08-09): ONE uniform card silhouette -
       every fieldset spans the full content width like the Beschreibung
       card (mismatched card lengths read as clutter), while the FIELDS
       inside keep a readable width via the caption grid's capped control
       column below. The generic form:has(>fieldset) measure cap would
       win over any child, so the form itself is uncapped. */
    .ui-desktop .site-main form.sell-form:has(> fieldset) {
        max-width: none;
    }

    /* beats the generic .site-main fieldset measure cap */
    .ui-desktop .site-main .sell-form fieldset {
        max-width: none;
    }

    /* The cards keep the generic caption+1fr grid, so FULL-ROW content
       (hints, pill groups, the description editor, the shipping matrix)
       genuinely spans the card - only the paired CONTROLS are capped to
       a readable width and simply sit left in their wide column (user
       report 2026-08-09: a 36rem grid track made every full-row hint
       wrap early with the card's right half empty). `form.sell-form`
       with the element type: the generic pairing rules tie specificity
       via :is(form.form-stack) and sit later in the file. */
    .ui-desktop .site-main:not(.desk-doc) form.sell-form :is(fieldset, fieldset > div:not([class])) > label:not(:has(input[type="checkbox"], input[type="radio"])) + :is(input, select, textarea, details.cat-picker) {
        max-width: 36rem;
    }

    .ui-desktop .sell-form .fieldset--description textarea {
        min-height: 420px;
        max-width: none;
    }

    /* ---------------------------------------------------------------
       The pages this session added: already row-shaped, they only need
       the wider measure and figures that line up.
       --------------------------------------------------------------- */
    .ui-desktop .blacklist__row {
        grid-template-columns: minmax(9rem, 13rem) minmax(0, 1fr) auto;
        align-items: center;
        column-gap: 0.75rem;
    }

    /* The add form ends the Formular grid with an intrinsic-width button
       in the field column (user 2026-08-20: the full-measure bar read as
       a page-wide CTA for what is a small utility form). Selector matches
       the Formular default's specificity - see the grid rules below. */
    .ui-desktop .site-main:not(.desk-doc) form.blacklist-add > button {
        grid-column: 2;
        justify-self: start;
        width: auto;
        padding-inline: 1.5rem;
    }

    /* device-list__row not included since the 2026-08-22 card redesign:
       the card's own icon+body layout applies on both layers. */
    .ui-desktop .bid-history__row {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 1.5rem;
    }


    /* ---------------------------------------------------------------
       "Mein eGun" — the account pages.

       These are where the ledger idea actually earns its keep: a seller
       with 200 sales or a buyer reading a Kontoauszug is doing accounting,
       not browsing. They all share one markup family — a `*-row` surface
       card holding a `__head` (thumbnail + text stack), a `__details`
       definition grid and a `.row-actions` pill cluster — so they get one
       shared treatment rather than fifteen bespoke ones.

       Deliberately NOT converted to real <table>s: the rows carry forms,
       toggles and step tracks (the .fulfill chips on /my-sales), which a
       table would fight. Columns without tabling is the honest middle.
       --------------------------------------------------------------- */

    /* A little more air once a row is 1100px wide - the phone padding
       reads cramped at that length. */
    .ui-desktop .my-purchase-row,
    .ui-desktop .my-sales-row,
    .ui-desktop .my-listings-row,
    .ui-desktop .my-haggle-row,
    .ui-desktop .feedback-list__row,
    .ui-desktop .saved-search-row,
    .ui-desktop .invoice-card {
        padding: 0.85rem 1rem;
    }

    /* Preisvorschlag cards: the stacked action forms are phone grammar -
       at 1100px card width a wall-to-wall "Annehmen" reads shouty
       (user feedback 2026-08-30). Cap the stack instead of restyling it:
       accept, fused counter row and reject keep one left-aligned column. */
    .ui-desktop .my-haggle-row__actions {
        max-width: 26rem;
    }

    /* Gunni-Launcher: unten rechts ohne Tab-Bar-Versatz; Panel als
       schwebende Karte statt Bottom-Sheet (2026-08-31, Option B). */
    .ui-desktop .gunni-launcher {
        right: 1.5rem;
        bottom: 1.5rem;
    }

    .ui-desktop .gunni-nudge {
        right: 1.5rem;
        bottom: 5.4rem;
    }

    /* Scales with the screen (2026-09-02): 24rem was one fixed box on
       1080p and 4K alike. Width follows the viewport within a clamp,
       height up to 80vh - the log inside is the element that grows. */
    .ui-desktop .gunni-panel {
        width: clamp(24rem, 28vw, 34rem);
        max-height: min(80vh, 48rem);
        margin: auto 1.5rem 1.5rem auto;
        border-radius: var(--radius);
        border: 1px solid var(--egun-border);
    }

    .ui-desktop .gunni-panel .gunni-log {
        min-height: 12rem;
    }

    .ui-desktop .gunni-panel__expand {
        display: inline-flex;
    }

    /* Expand mode (remembered per device): a centred reading window for
       long answers. Fixed height so the log fills it. */
    .ui-desktop .gunni-panel--large {
        width: min(60vw, 64rem);
        height: 85vh;
        max-height: 85vh;
        margin: auto;
    }

    /* Ausblenden-Bestätigung folgt am Rechner der Panel-Ecke statt
       über der (hier nicht vorhandenen) Tab-Bar zu schweben. */
    .ui-desktop .gunni-hide-toast {
        left: auto;
        right: 1.5rem;
        bottom: 1.5rem;
        max-width: 24rem;
        text-align: left;
    }

    /* The key/value grids: two columns on a phone, four here. Same
       information, a quarter of the vertical space. */
    .ui-desktop .my-purchase-row__details {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    /* Actions sit as one line where they fit - but a row with an unusual
       number of actions wraps rather than pushing buttons out of reach.
       (`nowrap` here clipped nothing in the current data; it had no
       escape hatch for the next action someone adds.) */
    .ui-desktop .row-actions {
        flex-wrap: wrap;
        justify-content: flex-start;
    }

    /* --- Purchase rows: thumb | title | bought | total ---
       FIXED widths on the last two columns, not `auto`: an auto track
       sizes to its own row's content, so the totals landed at a different
       x on every row and the one column a buyer actually reads down the
       page didn't line up at all. Fixed slots make it a real column. */
    .ui-desktop .my-purchase-row__headline {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 14rem 9rem;
        align-items: baseline;
        column-gap: 1.5rem;
    }

    .ui-desktop .my-purchase-row__bought,
    .ui-desktop .my-purchase-row__total {
        white-space: nowrap;
        text-align: right;
        font-variant-numeric: tabular-nums;
    }

    /* The money is the column the eye runs down, so it gets the display
       face here exactly as the price does on a listing row. */
    .ui-desktop .my-purchase-row__total {
        font-family: var(--font-display);
        font-size: 1.15rem;
        text-align: right;
    }

    /* --- Sales rows: title left, price right, on one line --- */
    .ui-desktop .my-sales-row__meta-line {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 1.5rem;
    }

    .ui-desktop .my-sales-row__price {
        font-variant-numeric: tabular-nums;
        white-space: nowrap;
    }

    /* Per-buyer sub-rows. This head is a <p> of INLINE content ("Käufer:",
       the nick, the score chip, then any status pills), not a set of
       grouped boxes - so space-between spreads every word across 1100px
       and strands the nick mid-row. The label/nick/score cluster stays
       together on the left instead, and the status pills are pushed right
       as a block by giving the FIRST of them the auto margin. */
    .ui-desktop .my-sales-buyer-row__head {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        gap: 0.5rem;
        /* wrap, not nowrap: the common case fits on one line anyway, and
           a long nick or a fifth status pill should push down rather
           than out of the card. The auto-margin below still pushes the
           pills right whenever they DO share the line. */
        flex-wrap: wrap;
    }

    .ui-desktop .my-sales-buyer-row__head .status-pill:first-of-type {
        margin-left: auto;
    }

    /* --- Listing rows: stats read as one line --- */
    .ui-desktop .my-listings-row__stats {
        flex-wrap: nowrap;
        font-variant-numeric: tabular-nums;
    }

    /* ---------------------------------------------------------------
       Kontoauszug — the one page that IS a ledger, already a real
       <table>. On a phone it is shrunk to 0.8rem just to fit four
       columns; here it gets its natural size back, fixed column widths
       so Datum and Betrag do not drift as amounts change length, and
       figures that line up digit over digit.
       --------------------------------------------------------------- */
    .ui-desktop .statement-table {
        font-size: 0.92rem;
        table-layout: fixed;
        font-variant-numeric: tabular-nums;
    }

    .ui-desktop .statement-table th,
    .ui-desktop .statement-table td {
        padding-block: 0.5rem;
    }

    .ui-desktop .statement-table th:nth-child(1),
    .ui-desktop .statement-table td:nth-child(1) {
        width: 10rem;
    }

    .ui-desktop .statement-table th:nth-child(2),
    .ui-desktop .statement-table td:nth-child(2) {
        width: 9rem;
    }

    .ui-desktop .statement-table th:nth-child(4),
    .ui-desktop .statement-table td:nth-child(4) {
        width: 10rem;
    }

    /* Amounts right-aligned in the display face: this is the column the
       eye adds up. */
    .ui-desktop .statement-table__value {
        text-align: right;
        font-family: var(--font-display);
        font-size: 1rem;
    }

    /* ---------------------------------------------------------------
       Card grids. Customer cards and the short account lists are narrow
       by nature - one per 1100px line would be mostly empty page.

       Only lists whose rows are SHORT and single-purpose go two-up.
       Purchases, sales, listings, haggles and feedback deliberately stay
       one per line: they carry actions and forms, and a two-column
       zigzag makes a working list harder to scan, not easier.
       --------------------------------------------------------------- */
    .ui-desktop .customer-list {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.75rem;
    }

    .ui-desktop .followed-sellers,
    .ui-desktop .saved-search-list,
    .ui-desktop .device-list,
    .ui-desktop .wanted-list {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.6rem;
        align-items: start;
    }

    /* NOT two-up (2026-08-20, was in the list above): each blacklist row
       carries a whole inline form - who / comment+save / remove - and at
       half width the comment input shrank to a few characters while the
       pills wrapped to random heights. Full measure, one entry per line:
       the ledger treatment. */
    .ui-desktop .blacklist {
        max-width: var(--desk-measure);
    }

    /* ---------------------------------------------------------------
       Dashboard: the directory reads as four labelled columns instead of
       four stacked blocks - the whole map visible without scrolling.
       --------------------------------------------------------------- */
    .ui-desktop .dashboard-group {
        break-inside: avoid;
    }

    .ui-desktop .dashboard-tiles {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    /* ---------------------------------------------------------------
       Rails: visible scroll affordance. The shelves (Specials, Neue
       Top-Auktionen) scroll horizontally, and a bottom scrollbar is
       invisible until touched (user report 2026-08-08). app.js wraps
       each overflowing rail in .rail-shell and adds two chevron
       buttons; edge fades signal hidden cards on either side. The
       buttons are pointer sugar (aria-hidden, tabindex -1) - keyboard
       and touch scroll the strip natively.
       --------------------------------------------------------------- */
    .ui-desktop .rail-shell {
        position: relative;
    }

    /* The phone rail full-bleeds itself (margin-inline: -1rem + 1rem
       padding, tuned to the phone's page padding). Inside the shell that
       bleed pushed the strip 1rem PAST the shell on both sides - while
       the fades and chevrons anchor to the shell's edges, so the
       gradients washed over card content mid-strip and the right
       cut-edge ran 1rem beyond the fade (user report 2026-08-08:
       "white-out", "no longer aligns with the right side"). Inside the
       shell, strip edge = shell edge = fade = chevron. */
    .ui-desktop .rail-shell .auction-rail {
        margin-inline: 0;
        padding-inline: 0;
        scroll-padding-left: 0;
    }

    .ui-desktop .rail-shell::before,
    .ui-desktop .rail-shell::after {
        content: '';
        position: absolute;
        top: 0;
        bottom: 0;
        width: 2.5rem;
        z-index: 3;
        pointer-events: none;
        opacity: 0;
        transition: opacity 150ms ease;
    }

    .ui-desktop .rail-shell::before {
        left: 0;
        background: linear-gradient(to right, var(--egun-bg), transparent);
    }

    .ui-desktop .rail-shell::after {
        right: 0;
        background: linear-gradient(to left, var(--egun-bg), transparent);
    }

    .ui-desktop .rail-shell--more-left::before,
    .ui-desktop .rail-shell--more-right::after {
        opacity: 1;
    }

    .ui-desktop .rail-nav {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        z-index: 4;
        width: 2.6rem;
        height: 2.6rem;
        display: grid;
        place-items: center;
        border: 1px solid var(--egun-border);
        border-radius: 50%;
        background: var(--egun-surface);
        color: var(--egun-text);
        font-size: 1.5rem;
        line-height: 1;
        cursor: pointer;
        box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
    }

    .ui-desktop .rail-nav:hover {
        background: var(--egun-bg);
    }

    .ui-desktop .rail-nav--prev {
        left: -0.8rem;
    }

    .ui-desktop .rail-nav--next {
        right: -0.8rem;
    }

    /* ---------------------------------------------------------------
       Pointer affordances. Only where a pointer exists — these must not
       fire on a touch device that happens to be wide.
       --------------------------------------------------------------- */
    @media (hover: hover) and (pointer: fine) {
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item {
            transition: border-color 120ms ease, box-shadow 120ms ease;
        }

        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item:hover {
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
        }
    }

    @media (prefers-reduced-motion: reduce) {
        .ui-desktop .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item {
            transition: none;
        }
    }
}

/* =====================================================================
   Wide tier. The only things that earn more room are the browse grid
   and the masthead ad; the text measure deliberately does NOT grow.
   ===================================================================== */
@media (min-width: 1200px) {
    .ui-desktop .category-grid,
    .ui-desktop .subcategory-list,
    .ui-desktop .rubriken-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* =====================================================================
   Dark mode, desktop only. app.css flips --egun-blue to a light accent
   and gives solid buttons dark ink; the two desktop-only fills have to
   follow the same rules or they break contrast.
   ===================================================================== */
@media (min-width: 900px) and (prefers-color-scheme: dark) {
    .ui-desktop {
        /* The light-mode 8% blue is invisible on a #1e2026 surface. */
        --desk-nav-active: rgba(142, 151, 255, 0.16);
    }

    /* The gold Verkaufen pill is a light fill in BOTH themes, so its ink
       must stay dark — `var(--egun-text)` flips to near-white in dark
       mode and left white-on-gold. Same rule app.css already applies to
       every other solid button in dark mode. */
    .ui-desktop .tab-bar__item--sell,
    .ui-desktop .tab-bar__item--sell:hover {
        color: #0e0f12;
    }
}

/* =====================================================================
   Second pass (2026-07-27): forms, search, messages, recommendation
   shelves, dense tables, and the pages the first pass never visited.
   Same tokens, same thesis - nothing here introduces a new color or
   face. Kept as its own media block so the first pass reads unchanged.
   ===================================================================== */
@media (min-width: 900px) {
    .ui-desktop {
        /* The Formular caption column (see Forms below): one width for
           every caption so the field column starts at the same x on
           every form in the app. */
        --desk-caption: 13rem;
    }

    /* ---------------------------------------------------------------
       Forms: the Formular.

       The phone stacks caption above field - correct for thumbs, but on
       a desktop it made every form a meter-long single column. The
       desktop treatment is the German paper form the app's users fill
       in anyway: caption column left, field column right, one line per
       entry. This halves a form's height WITHOUT reordering anything -
       a two-column card flow was rejected because the sell form's
       show/hide sections (Später starten, Versandkosten) would rebalance
       the columns mid-entry and yank fields out from under the cursor.

       A GRID, not flex rows: the caption column must be structural. A
       flex-basis version was tried first and short caption+field pairs
       packed two to a line (login), while a caption could ride along to
       the right of the previous field (invoice form) - grid placement
       makes both states unrepresentable. Everything spans the full row
       by default; only a caption (col 1) and the control directly
       following it (col 2) opt out. Option labels (checkbox/radio) are
       answers, not captions - they keep their full line.

       The measure stays 46rem - caption 13rem + field ~31rem - so forms
       share their edges with everything else; the win is height.
       --------------------------------------------------------------- */
    /* :not(.desk-doc): document pages (tax-details) keep the phone's
       stacked label-above-field flow - their labels are SENTENCES
       ("Steuer-Identifikationsnummer, Wirtschafts-Identifikations-
       nummer oder Steuernummer"), and a caption column of multi-line
       monsters reads worse than stacking ever did (user 2026-08-08). */
    /* The unclassed-toggle-wrapper treatment below also covers wrappers
       sitting directly in a flat form.form-stack (and one level deeper,
       for register's #tax-block nesting its own #bundesland-row /
       #brn-field) - the register form has no fieldsets around its
       commercial rows, and without these variants the JS show/hide divs
       fell out of the caption grid entirely: intrinsic-width inputs with
       hint text wrapping around them (user report 2026-08-10). */
    .ui-desktop .site-main:not(.desk-doc) :is(fieldset, form.form-stack, .fieldset-flow),
    .ui-desktop .site-main:not(.desk-doc) fieldset > div:not([class]),
    .ui-desktop .site-main:not(.desk-doc) :is(form.form-stack, form.form-stack > div:not([class])) > div:not([class]) {
        display: grid;
        grid-template-columns: var(--desk-caption) minmax(0, 1fr);
        gap: 0.6rem 0.75rem;
        align-items: start;
    }

    /* Default: a full row (legends, option labels, hints, pill groups,
       buttons, nested wrappers). */
    .ui-desktop .site-main:not(.desk-doc) :is(fieldset, form.form-stack, .fieldset-flow) > *,
    .ui-desktop .site-main:not(.desk-doc) fieldset > div:not([class]) > *,
    .ui-desktop .site-main:not(.desk-doc) :is(form.form-stack, form.form-stack > div:not([class])) > div:not([class]) > * {
        grid-column: 1 / -1;
        min-width: 0;
    }

    /* The caption column. Excludes option labels via :has. The weight
       is set here (not inherited) so captions inside the unclassed
       toggle wrappers read the same as their fieldset siblings. */
    .ui-desktop .site-main:not(.desk-doc) :is(fieldset, form.form-stack, .fieldset-flow) > label:not(:has(input[type="checkbox"], input[type="radio"])),
    .ui-desktop .site-main:not(.desk-doc) fieldset > div:not([class]) > label:not(:has(input[type="checkbox"], input[type="radio"])),
    .ui-desktop .site-main:not(.desk-doc) :is(form.form-stack, form.form-stack > div:not([class])) > div:not([class]) > label:not(:has(input[type="checkbox"], input[type="radio"])) {
        grid-column: 1;
        margin: 0;
        /* aligns the caption's text with the field text beside it
           (input padding 0.5rem + 1px border) */
        padding-top: 0.55rem;
        font-weight: 600;
        /* long-locale insurance (hu/fi compounds): a caption word wider
           than the column hyphenates instead of colliding with fields */
        hyphens: auto;
        overflow-wrap: break-word;
    }

    /* The control directly after its caption shares the row; the error
       (or a .field-hint explainer) directly after that control lands
       beneath the FIELD, visually attached to what it belongs to. */
    /* details.cat-picker in the control list: the sell/edit category
       tree (2026-08-22) sits after its caption exactly like a select
       would - without this it fell to the full-row default and the
       caption stood alone. The required-hint chain (.error + .error:
       JS hint first, server error after) stays under the field too. */
    .ui-desktop .site-main :is(fieldset, form.form-stack, .fieldset-flow, fieldset > div:not([class]), form.form-stack > div:not([class]), form.form-stack > div:not([class]) > div:not([class])) > label:not(:has(input[type="checkbox"], input[type="radio"])) + :is(input, select, textarea, details.cat-picker),
    .ui-desktop .site-main :is(fieldset, form.form-stack, .fieldset-flow, fieldset > div:not([class]), form.form-stack > div:not([class]), form.form-stack > div:not([class]) > div:not([class])) > label:not(:has(input[type="checkbox"], input[type="radio"])) + :is(input, select, textarea, details.cat-picker) + :is(.error, .field-hint),
    .ui-desktop .site-main :is(fieldset, form.form-stack, .fieldset-flow, fieldset > div:not([class]), form.form-stack > div:not([class]), form.form-stack > div:not([class]) > div:not([class])) > label:not(:has(input[type="checkbox"], input[type="radio"])) + details.cat-picker + .error + .error {
        grid-column: 2;
        width: auto;
    }

    /* Inline captions before pill groups ("Zahlungsweisen", "Versand",
       "Internationaler Versand" inside its toggle wrapper): same caption
       column, pills flow beside it. */
    .ui-desktop .site-main :is(fieldset, fieldset > div:not([class])) > p:has(+ .choice-pills) {
        grid-column: 1;
        margin: 0;
        padding-top: 0.35rem;
        font-weight: 600;
        font-size: 0.9rem;
    }

    .ui-desktop .site-main :is(fieldset, fieldset > div:not([class])) > p + .choice-pills {
        grid-column: 2;
    }

    /* Every form built from fieldsets shares the measure - without this
       the submit row (sell-actions, btn-block) spanned the full 1180px
       under 46rem cards. The standalone pieces around the sell form
       follow the same edge. */
    .ui-desktop .site-main form:has(> fieldset),
    .ui-desktop .profile-section,
    .ui-desktop .sell-draft-notice,
    .ui-desktop .sell-preview {
        max-width: var(--desk-measure);
    }

    /* ---------------------------------------------------------------
       Search: the filters stop hiding.

       On a phone "Erweiterte Suche" is a <details> fold - correct where
       every open panel costs a screen. Up here the fold is the whole
       page's problem: the results and the controls that shape them
       never visible together. The form (search bar + filters) becomes a
       sticky left rail, the result ledger runs beside it. The template
       opens the <details> via JS at this width; the summary is hidden
       ONLY once open, so without JS the fold still works.
       --------------------------------------------------------------- */
    .ui-desktop .page-search {
        display: grid;
        grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
        column-gap: 2rem;
        align-items: start;
    }

    .ui-desktop .page-search > * {
        grid-column: 2;
        min-width: 0;
    }

    /* ONE results wrapper beside the rail (template: .page-search__
       results). The first pass placed the rail with `grid-row: 1 / -1`
       and let the loose result children auto-flow - but in an IMPLICIT
       grid `-1` is the end of the EXPLICIT grid, i.e. line 2, so the
       rail spanned only row 1, row 1 stretched to the rail's full
       height, and everything after the first column-2 child (the whole
       hit list!) landed BELOW the rail's bottom edge - the "results at
       the bottom of the page" the user reported on 2026-08-08 the
       moment a search actually had results. With both sides as exactly
       one grid item in row 1, there is no row arithmetic left to get
       wrong. */
    .ui-desktop .page-search > .page-search__results {
        grid-column: 2;
        grid-row: 1;
        min-width: 0;
    }

    .ui-desktop .page-search > .search-form {
        grid-column: 1;
        grid-row: 1;
        position: sticky;
        top: calc(var(--desk-sticky-top) + var(--desk-sticky-gap));
        max-height: calc(100vh - var(--desk-sticky-top) - var(--desk-sticky-gap) - 1.25rem);
        overflow-y: auto;
        margin-bottom: 0;
    }

    /* Child-scoped (2026-08-20): the panel now CONTAINS another details
       widget (the category tree picker), whose summaries must survive. */
    .ui-desktop .search-form__advanced[open] > summary {
        display: none;
    }

    .ui-desktop .search-form__advanced {
        padding: 0.75rem 0.9rem;
    }

    /* ---------------------------------------------------------------
       Messages: two panes. The thread list (rendered by the template
       only when the desktop layer is on) sits left, the conversation
       keeps a reading measure beside it. The phone keeps its two-page
       flow - below 900px the pane is hidden (rule at file end).
       --------------------------------------------------------------- */
    .ui-desktop .messages-pane {
        display: grid;
        grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
        column-gap: 2rem;
        align-items: start;
    }

    .ui-desktop .messages-pane__list {
        position: sticky;
        top: calc(var(--desk-sticky-top) + var(--desk-sticky-gap));
        max-height: calc(100vh - var(--desk-sticky-top) - var(--desk-sticky-gap) - 1.25rem);
        overflow-y: auto;
    }

    .ui-desktop .messages-pane__list .message-threads {
        gap: 0.4rem;
    }

    .ui-desktop .messages-pane__thread {
        min-width: 0;
        max-width: var(--desk-measure);
    }

    /* The list already IS the back navigation - but only once it is
       actually there. The pane is filled by a fetch (app.js); while it is
       still empty, or if that fetch failed, or with JS off, the back link
       stays: :empty is what tells the two states apart. */
    .ui-desktop .messages-pane__list:not(:empty) ~ .messages-pane__thread .message-back {
        display: none;
    }

    .ui-desktop .message-thread--active {
        border-color: var(--egun-blue);
        background: var(--desk-nav-active);
    }

    /* The index page (no thread open): head, tools and the list (now
       inside the poll holder, 2026-08-10) keep the measure. */
    .ui-desktop .site-main > .message-threads,
    .ui-desktop .site-main > #message-list-holder,
    .ui-desktop .site-main > .message-head,
    .ui-desktop .site-main > .message-tools {
        max-width: var(--desk-measure);
    }

    /* The narrow two-pane list: the enriched rows shed their widest
       extras - the open thread's header carries the full context
       anyway. */
    .ui-desktop .messages-pane__list .message-thread__auction-price,
    .ui-desktop .messages-pane__list .message-thread__business {
        display: none;
    }

    /* ---------------------------------------------------------------
       Recommendation shelves ("Ähnliche Artikel", "Oft zusammen
       beobachtet", "Zuletzt angesehen"): NOT ledger rows. A ledger is
       for comparing what you asked for; a shelf is a glance sideways.
       These keep the phone's compact card anatomy, three to a line.
       --------------------------------------------------------------- */
    .ui-desktop .auction-list--shelf {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 0.75rem;
    }

    .ui-desktop .auction-list--shelf > li {
        display: flex;
        min-width: 0;
    }

    .ui-desktop .auction-list--shelf .list-item {
        flex: 1 1 auto;
    }

    /* ---------------------------------------------------------------
       Dense tables get the Kontoauszug treatment: natural size back,
       numbers right-aligned in tabular figures, money in the display
       face. Same devices, three more places.
       --------------------------------------------------------------- */

    /* Sammel-Upload review */
    .ui-desktop .sell-bulk-table {
        font-size: 0.92rem;
        font-variant-numeric: tabular-nums;
    }

    .ui-desktop .sell-bulk-table thead th {
        font-size: 0.72rem;
        font-weight: 700;
        letter-spacing: 0.09em;
        text-transform: uppercase;
        color: var(--egun-text-muted);
    }

    .ui-desktop .sell-bulk-table :is(th, td):is(:nth-child(4), :nth-child(5), :nth-child(6)) {
        text-align: right;
        white-space: nowrap;
    }

    /* Gebühren: the column the seller is being asked to confirm. */
    .ui-desktop .sell-bulk-table td:nth-child(6) {
        font-family: var(--font-display);
        font-size: 1rem;
    }

    /* Rechnungen (Meine Kunden detail): the amount is the second meta
       span - position-addressed because the template renders the three
       spans bare; if that markup grows a class, prefer it. */
    .ui-desktop .invoice-card__meta {
        font-variant-numeric: tabular-nums;
        gap: 0.2rem 1.5rem;
    }

    .ui-desktop .invoice-card__meta > span:nth-child(2) {
        font-family: var(--font-display);
        font-size: 1.05rem;
        color: var(--egun-text);
    }

    /* Five action pills don't fit a 46rem card on one line - unlike the
       account rows, wrapping beats clipping here. */
    .ui-desktop .invoice-card .row-actions {
        flex-wrap: wrap;
    }

    .ui-desktop .invoice-list {
        max-width: var(--desk-measure);
    }

    /* Kontoauszug's own invoice list: short single-purpose rows, so
       two-up like the other short account lists. */
    .ui-desktop .statement-invoices {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.6rem;
    }

    /* Meine Kontakte: two columns of text don't earn 1180px. */
    .ui-desktop .contact-history-list {
        max-width: var(--desk-measure);
    }

    .ui-desktop .contact-history-list :is(th, td):last-child {
        text-align: right;
        font-variant-numeric: tabular-nums;
        white-space: nowrap;
    }

    /* ---------------------------------------------------------------
       Item page pictures: the 320px tablet strip is small next to a
       27rem trade column - the gallery is the reason the left column
       exists. (More again at 1200px below.)
       --------------------------------------------------------------- */
    /* Seller card: identity and reputation as ONE line (user request
       2026-08-08) - nick, score, ID badge, positive percentage and the
       myGun pill read as a single introduction row at desktop width.
       Two inline-flex paragraphs in normal inline flow: on a narrow
       column the reputation half simply wraps beneath, nothing clips.
       The cert popover sitting between them in DOM is display:none
       until opened (then top-layer), so it never disturbs the line. */
    .ui-desktop .page-item .auction-seller__identity,
    .ui-desktop .page-item .auction-seller__pct-line {
        display: inline-flex;
        align-items: center;
        vertical-align: middle;
        /* inline-flex collapses the markup's inter-element spaces -
           without an explicit gap the score chip glued to the nick. */
        gap: 0.35rem;
        margin-block: 0.1rem 0.35rem;
    }

    .ui-desktop .page-item .auction-seller__pct-line {
        margin-left: 0.9rem;
    }

    /* The photo sits on a real card: at a fixed 380px the letterboxing
       of non-4:3 photos is transparent, which left the watch/zoom chips
       visually hovering in nothing (user report 2026-08-08). A surface
       behind the strip turns the box into a deliberate frame - the
       chips sit on ITS corner, wherever the photo's pixels end. */
    /* The elastic photo stage: full column width, height tracking the
       trade card (100% of the stretched row, never under 380px), photo
       scaled to FIT and centered - whatever the stage doesn't need for
       the picture stays white, on every side that needs it. A single
       photo centers; a multi-photo strip starts left and scrolls inside
       the stage (justify center would clip a scroll container's start,
       hence the :not(--scrollable) guard on the centering). */
    .ui-desktop .page-item .auction-gallery {
        background: var(--egun-surface);
        border: 1px solid var(--egun-border);
        border-radius: var(--radius);
        height: 100%;
        min-height: 380px;
        align-items: center;
    }

    .ui-desktop .page-item .auction-gallery-wrap:not(.auction-gallery-wrap--scrollable) .auction-gallery {
        justify-content: center;
    }

    /* The photo-less placeholder stage exists only here (phones hide it,
       see app.css) - same frame, quiet label centered in it. */
    .ui-desktop .page-item .auction-gallery--empty {
        display: flex;
        justify-content: center;
    }

    /* Anchor landing for the "(Details)" jump: clear the desktop's
       sticky header via the shared token instead of app.css's phone
       guess. */
    .ui-desktop .auction-shipping {
        scroll-margin-top: calc(var(--desk-sticky-top) + var(--desk-sticky-gap));
    }

    .ui-desktop .page-item .auction-gallery img {
        height: 100%;
        /* full-width slides (2026-08-26), same as the phone strip -
           one image per view, no neighbour peeking. */
        flex: 0 0 100%;
        width: 100%;
        object-fit: contain;
        border-radius: var(--radius);
    }

    /* ---------------------------------------------------------------
       Fixed elements: --tab-bar-h is 0 up here, which left the install
       banner 0.6-0.75rem off the viewport edge - it becomes a corner
       card instead of a full-width phone sheet. Notices dock at the top.
       --------------------------------------------------------------- */
    /* Notice layer, desktop placement (2026-09-02): top-CENTER directly
       under the sticky header, centered on the VIEWPORT - which is where
       the content column sits at every width. The former top-right
       corner was ~950px away from the 1180px column on a 4K monitor.
       Lifetime is one rule on every device now (app.js: 2.5s + 50ms per
       character, 4-8s), so the old 6s duration override is gone; the
       entrance drops in from above instead of rising from below. */
    .ui-desktop .toast-stack {
        left: 50%;
        right: auto;
        bottom: auto;
        top: calc(var(--desk-sticky-top) + var(--desk-sticky-gap));
        transform: translateX(-50%);
        width: 28rem;
        max-width: calc(100vw - 3rem);
        align-items: center;
    }

    /* A page opened narrow (install sheet shown) and then widened: the
       phone rule that lifts the stack above the sheet must not stretch
       it between top and bottom up here. */
    .ui-desktop body:has(#pwa-install-banner:not([hidden])) .toast-stack {
        bottom: auto;
    }

    .ui-desktop .toast {
        --toast-enter: -0.6rem;
        max-width: none;
        font-size: 1.02rem;
        padding: 0.85rem 0.7rem 0.95rem 1rem;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
    }

    .ui-desktop .pwa-install-banner {
        left: auto;
        right: 1.5rem;
        bottom: 1.5rem;
        max-width: 24rem;
    }

    /* ---------------------------------------------------------------
       The pages the first pass never visited.
       --------------------------------------------------------------- */

    /* Forum: category tiles two-up; threads and the reply box keep a
       conversation measure. */
    .ui-desktop .cafe-cats {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.5rem;
    }

    .ui-desktop .cafe-posts,
    .ui-desktop .cafe-new-post {
        max-width: var(--desk-measure);
    }

    /* Help/news prose: 1180px lines are unreadable; legal text keeps
       the same measure as everything else. */
    .ui-desktop .help-content,
    .ui-desktop .app-help__head,
    .ui-desktop .news-show__content {
        max-width: var(--desk-measure);
    }

    /* ---------------------------------------------------------------
       Third pass (2026-07-27, post-sweep polish).
       --------------------------------------------------------------- */

    /* Bewertungen: the pending queue gets 60rem - NOT the full 1180px
       (a page-wide comment box invited essays where 80 chars are
       allowed), but wide enough that the ledger row below leaves the
       comment field ~26rem (~45 visible chars) after pills and button.
       At the 46rem measure the box shrank to ~12ch (user 2026-08-20).

       The summary card shares that SAME 60rem and the SAME left edge
       (user 2026-08-25: the old 46rem-centred card between left-aligned
       heading and left-aligned queue put three axes on one page), and
       the phone's tall stacked card reflows into one ledger-like band -
       score | ratio strip | counts | link - at dashboard density. */
    .ui-desktop .rating-summary {
        max-width: 60rem;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem 1.1rem;
        padding: 0.7rem 1rem;
    }

    .ui-desktop .rating-summary > .rating-eyebrow {
        flex-basis: 100%;
        margin: 0;
    }

    .ui-desktop .rating-summary__score {
        font-size: 1.5rem;
    }

    /* The strip takes the slack between score and counts; capped so it
       stays a gauge, not a runway across the whole band. */
    .ui-desktop .rating-summary .rating-ratio {
        flex: 1 1 10rem;
        max-width: 22rem;
    }

    .ui-desktop .rating-summary__link {
        margin-left: auto;
    }

    .ui-desktop .rating-list {
        max-width: 60rem;
    }

    /* The pending queue as a LEDGER (2026-08-20): on the phone each owed
       rating stacks choices / comment / submit; up here the app.css flex
       row simply stops wrapping - one line per form, seven owed ratings
       fit a screen instead of two. The comment box gets input height
       (resize stays available for the rare longer text). */
    .ui-desktop .rating-list__form {
        flex-wrap: nowrap;
    }

    .ui-desktop .rating-list__choices {
        flex: 0 0 auto;
    }

    /* Choice pills at btn-quiet scale (user 2026-08-25: phone-sized
       pills read as blown up next to the dashboard) - each owed rating
       becomes one calm line like the other desktop ledgers. */
    .ui-desktop .rating-list__choice {
        padding: 0.25rem 0.7rem;
        font-size: 0.85rem;
    }

    .ui-desktop .rating-list__form textarea {
        flex: 1 1 auto;
        min-width: 16rem; /* floor: never shrinks into a stub */
        height: 2.4rem;
        min-height: 2.4rem;
    }

    /* At input height the two-line placeholder clipped mid-glyph -
       one line with an ellipsis instead. */
    .ui-desktop .rating-list__form textarea::placeholder {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .ui-desktop .rating-list__head {
        flex-direction: row;
        align-items: baseline;
        justify-content: space-between;
        gap: 0.75rem;
        margin-bottom: 0.5rem;
    }

    /* Widerrufsanfragen / Zweite Chance: short single-purpose cards,
       two-up like the other short account lists. */
    .ui-desktop .revoke-request-list,
    .ui-desktop .runner-up-offer-list {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.6rem;
        align-items: start;
    }

    /* Kontoauszug/Rechnung: the column headers stay visible while a
       long ledger scrolls. Two parts, both load-bearing: the phone's
       horizontal scroll box must open up (a sticky header can't escape
       an overflow container, and at 1180px the table fits anyway), and
       the header cells need their own surface so rows don't shine
       through. top = the sticky site header's height. */
    .ui-desktop .statement-scroll {
        overflow-x: visible;
    }

    .ui-desktop .statement-table thead th {
        position: sticky;
        top: var(--desk-sticky-top);
        background: var(--egun-surface);
        z-index: 1;
    }

    /* Numbered pages (markup always rendered by _pagination.html.twig,
       hidden by app.css): on a desktop with hundreds of results,
       jumping to page 7 beats seven clicks on Weiter. */
    .ui-desktop .pagination__pages {
        display: flex;
        align-items: center;
        gap: 0.3rem;
    }

    .ui-desktop .pagination__page--current {
        background: var(--egun-blue);
        border-color: var(--egun-blue);
        color: #fff;
        font-weight: 600;
    }

    .ui-desktop .pagination__gap {
        color: var(--egun-text-muted);
    }

    /* ---------------------------------------------------------------
       Ads: the desktop banner line. The masthead slot is capped at the
       logo's 30px - fine in a phone header, but desktop clients book
       desktop formats (leaderboard 728x90 and friends). At desktop
       width the banner moves OUT of the header onto its own full-width
       line under it (the same .ad-strip phones use below 480px, shown
       again and given room), displayed at its native size up to 90px
       tall. Same single banner pick; the impression beacon sits on the
       masthead instance, which stays in the DOM - one pick, one
       impression, one visible banner either way.
       --------------------------------------------------------------- */
    .ui-desktop .site-header__ad {
        display: none;
    }

    .ui-desktop .ad-strip {
        display: flex;
        padding: 0.4rem 1.5rem;
    }

    .ui-desktop .ad-strip img {
        max-height: 90px;
    }
}

/* Wide tier: the stage is the one reader that earns yet more room. */
@media (min-width: 1200px) {
    .ui-desktop .page-item .auction-gallery {
        min-height: 440px;
    }
}

/* Desktop layer ON but a narrow viewport (phone visiting with the flag
   enabled): the messages thread list must stay a separate page, not a
   stacked block above the conversation. Lives here rather than app.css
   so the kill switch keeps its guarantee - flag off, file absent, no
   rule anywhere. */
@media (max-width: 899.98px) {
    .ui-desktop .messages-pane__list {
        display: none;
    }
}

/* ------------------------------------------------------------------ */
/* Competitor-audit round 2026-08-08 - desktop variants.              */
/* ------------------------------------------------------------------ */
@media (min-width: 900px) {
    /* Cards carry more context where there's room: the price label
       (display:none in app.css - phone rows keep their density). */
    .ui-desktop .list-item__price-label {
        display: inline;
    }

    /* App promo flips to the QR variant: code + scan hint visible, the
       phone's install button irrelevant. */
    .ui-desktop .app-promo__qr {
        display: block;
    }

    .ui-desktop .app-promo__scan {
        display: block;
    }

    .ui-desktop .app-promo__install,
    .ui-desktop .app-promo__ios-hint {
        display: none;
    }

    /* Händlerverzeichnis: a directory reads better at document measure
       than full-bleed rows. */
    .ui-desktop .page-dealers {
        max-width: 48rem;
        margin-inline: auto;
    }
}

/* ------------------------------------------------------------------ */
/* Stöbern category dropdown (2026-08-09): hover/focus opens the      */
/* top-level categories from any page - click still navigates home.   */
/* Same popover device family as the account menu.                    */
/* ------------------------------------------------------------------ */
@media (min-width: 900px) {
    .ui-desktop .tab-bar__browse {
        position: relative;
        display: flex;
    }

    .ui-desktop .browse-menu {
        position: absolute;
        top: calc(100% + 0.45rem);
        left: 0;
        z-index: 30;
        min-width: 23rem;
        background: var(--egun-surface);
        border: 1px solid var(--egun-border);
        border-radius: var(--radius);
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
        padding: 0.9rem 1.1rem 1rem;
    }

    .ui-desktop .browse-menu__eyebrow {
        margin: 0 0 0.5rem;
        font-family: var(--font-display);
        font-size: 0.72rem;
        font-weight: 600;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--egun-text-muted);
    }

    .ui-desktop .browse-menu__list {
        list-style: none;
        margin: 0;
        padding: 0;
        column-count: 2;
        column-gap: 1.75rem;
    }

    .ui-desktop .browse-menu__list li {
        break-inside: avoid;
    }

    .ui-desktop .browse-menu__list a {
        display: block;
        padding: 0.32rem 0.4rem;
        border-radius: 5px;
        color: inherit;
        text-decoration: none;
        white-space: nowrap;
    }

    .ui-desktop .browse-menu__list a:hover,
    .ui-desktop .browse-menu__list a:focus-visible {
        background: var(--egun-bg);
    }

    .ui-desktop .browse-menu__all {
        display: block;
        margin-top: 0.7rem;
        padding: 0.55rem 0.4rem 0;
        border-top: 1px solid var(--egun-border);
        font-weight: 600;
        text-decoration: none;
    }
}

/* Account dropdown, beefed up (2026-08-09): wider card, the four world
   sections side by side in two columns. */
@media (min-width: 900px) {
    .ui-desktop .site-header__links:not([hidden]):not(.site-header__links--anon) {
        width: 27rem;
        padding: 0.9rem 1rem 0.8rem;
    }

    .ui-desktop .acct-sections {
        grid-template-columns: 1fr 1fr;
        column-gap: 1.4rem;
        row-gap: 1rem;
    }

    /* ---------------------------------------------------------------
       Seller storefront head (2026-08-12): on a wide screen the score
       chip belongs on the SAME line as the name (it sat stranded on a
       row of its own - user report), and the follow group uses the
       freed right half instead of a third row.
       --------------------------------------------------------------- */
    .ui-desktop .storefront-head {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        justify-content: space-between;
        gap: 0.5rem 1.5rem;
        margin: 0 0 0.75rem;
    }

    .ui-desktop .storefront-head h1 {
        margin: 0;
    }

    .ui-desktop .storefront-head .storefront-follow {
        margin: 0;
    }

    /* Wide screens have room for count + both chip groups on one line. */
    .ui-desktop .search-toolbar--grouped .search-toolbar__sort {
        flex-basis: auto;
    }

    /* Merklisten-Notiz-Stift: am Desktop NEBEN das ✕ (statt darunter) -
       im Ledger sitzt rechts die Restzeit-Spalte, unterm ✕ schnitt der
       Stift hinein bzw. ragte im Kompakt-Modus aus der Zeile. Die
       Kopfleiste rechts oben ist frei (Spalteninhalt ist vertikal
       zentriert). */
    .ui-desktop .watchlist-note:not(.watchlist-note--filled):not([open]) > .watchlist-note__toggle {
        top: 0.4rem;
        right: 2.5rem;
    }

    /* Kompakt-Zeilen (57px): ✕ und ✎ übereinander an der rechten Kante,
       verkleinert - nebeneinander schnitt der Stift in die Restzeit.
       Die generelle Kompakt-Regel gibt dem Body 2.2rem padding-right,
       die Icon-Spalte (1.6rem + 0.3rem) bleibt darunter. */
    .ui-desktop.list-compact .watchlist-list .watchlist-list__remove {
        top: 0.2rem;
        right: 0.3rem;
        width: 1.5rem;
        height: 1.5rem;
    }

    .ui-desktop.list-compact .watchlist-list .watchlist-note:not(.watchlist-note--filled):not([open]) > .watchlist-note__toggle {
        top: auto;
        bottom: 0.2rem;
        right: 0.3rem;
        width: 1.5rem;
        height: 1.5rem;
    }

    /* Ansicht-Umschalter: am Desktop mit Text-Labels. */
    .ui-desktop .view-switch__label {
        display: inline;
    }

    /* In der einzeiligen grouped-Toolbar wandert Kompakt ans Ende
       (rechtsbündig via margin-left:auto aus app.css) - sonst stünde
       der Ansichts-Schalter mitten zwischen Zähler und Chips. */
    .ui-desktop .search-toolbar--grouped .view-switch {
        order: 99;
    }
}


/* =====================================================================
   Item page: secondary actions at desktop weight (user 2026-08-25).

   The app.css block buttons ("Alle N Artikel von X", "Verkäufer
   folgen", "Frage an den Verkäufer", "Verstoß melden", ...) are
   phone-width CTAs; inside the two-column item layout they stretched a
   whole ~550px column and gave every secondary action commerce-button
   weight. Up here they step down: the seller card's actions (incl. the
   red Verstoß-melden entry) become one compact pill row, and a general
   cap catches every other nested block button. The trade card's
   Bieten/Sofortkauf CTAs are explicitly EXEMPT - the bid box is the
   one place where full-width is correct.
   ===================================================================== */
@media (min-width: 900px) {
    /* Safety net first, exemption second (source order = cascade). */
    .ui-desktop .page-item .button-link--block {
        max-width: 24rem;
    }

    .ui-desktop .page-item .trade-card .button-link--block {
        max-width: none;
    }

    /* Seller card: the stacked action column (app.css) becomes a
       wrapping pill row under the identity - "identity + toolbar"
       instead of three stacked bars. */
    .ui-desktop .page-item .auction-seller__actions {
        flex-direction: row;
        flex-wrap: wrap;
        gap: 0.5rem;
    }

    .ui-desktop .page-item .auction-seller__actions .button-link {
        display: inline-flex;
        align-items: center;
        width: auto;
        padding: 0.35rem 0.95rem;
        font-size: 0.9rem;
        /* The row mixes <a> and <button> (follow form): the shared
           button rule sets neither font-family nor line-height, so the
           <button> fell back to UA metrics (~1.15) and rendered a
           shorter pill than its 1.4-line-height anchor neighbours
           (user report 2026-08-25). Pin both so the boxes match. */
        font-family: inherit;
        line-height: 1.4;
    }

    /* Trade-card extras (Artikelempfehlung / Artikel teilen) keep their
       full-width block form on purpose: a smaller left-hugging pill
       under the equally-wide Bieten/Merkliste bars read as broken, not
       demoted (user 2026-08-25 - tried and reverted same day). Inside
       the trade card, every action spans the card. */

    /* "Verstoß melden" is the red pill at the end of the seller action
       row (button-link--report, app.css) - the page-bottom duplicate
       was removed 2026-08-25. A text-link demotion was tried and
       reverted the same day: the fraud-report entry stays a REAL
       button. */
}

/* /wanted-ads (2026-08-26): the page-primary action at intrinsic width -
   the measure-wide block bar read as oversized on desktop. Stays the
   page's one primary (blue), left on the page axis. */
@media (min-width: 900px) {
    .ui-desktop .wanted-new .button-link {
        display: inline-flex;
        width: auto;
        padding: 0.5rem 1.4rem;
    }
}

/* Kompakte Liste im Desktop-Ledger (2026-08-26): einzeilige Zeilen wie
   die Legacy-Tabelle - Titel mit Ellipsis (bewusste Modus-Ausnahme vom
   Nie-Clampen), Chips inline dahinter, Preis-Label aus, ~48px Zeilen. */
@media (min-width: 900px) {
    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item {
        padding: 0.3rem 0.75rem 0.4rem;
    }

    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__thumb {
        width: 44px;
        height: 44px;
    }

    /* The phone-compact wrap-row (app.css) must NOT leak into the
       ledger: with wrap allowed, a long title makes the number columns
       wrap UNDER it instead of the title shrinking (m-dev "pistole"
       report 2026-08-26). */
    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__body {
        flex-wrap: nowrap;
    }

    /* Chips UNTER dem Titel statt daneben (user 2026-08-26: die Chips
       fraßen 37% der Titelzelle - Text bekam nur ~229px und riss
       mitten im Wort). Jetzt: Titeltext volle Zellbreite, 2 Zeilen,
       Chip-Zeile darunter; Chip-Zeilen kosten ~13px Zeilenhöhe. */
    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__title {
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 0;
        font-size: 0.95rem;
    }

    /* Sternfreiheit (user 2026-08-26): in 57px-Zeilen kollidierte die
       rechtsbündige Restzeit vertikal mit dem Merken-Stern oben rechts -
       die Zahlenspalten enden jetzt VOR der Stern-Zone. */
    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__body {
        padding-right: 2.2rem;
    }

    /* 2-Zeilen-Clamp statt einzeiliger Ellipsis (user 2026-08-26: eine
       Zeile schnitt lange Titel unlesbar ab - mindestens zwei Zeilen
       Titeltext auch im Kompakt-Modus). Chips stehen daneben,
       vertikal zentriert. */
    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__title-text {
        flex: 0 0 auto;
        min-width: 0;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__title .list-item__tags {
        margin-top: 0.2rem;
        padding-top: 0;
        flex: 0 0 auto;
    }

    /* Chip-Diät im Kompakt-Modus: Metadaten-Größe statt Label-Größe. */
    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__tags .badge {
        font-size: 0.65rem;
        padding: 0.05rem 0.35rem;
    }

    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price-label {
        display: none;
    }

    .ui-desktop.list-compact .auction-list:not(.auction-rail):not(.auction-list--shelf) .list-item__price {
        font-size: 1.05rem;
    }
}

/* Kategorie-Leiste der Startseite: am Desktop passt die Leiste ohne
   Scrollen - umbrechen statt schneiden, Vollbreiten-Ränder zurücknehmen
   (die Startseite läuft dort in der zentrierten Spalte). Viewport-Query,
   kein Container-Query: die Leiste liegt in der Hauptspalte, außerhalb
   der Listen-Container - ein @container fände hier keinen Vorfahren und
   die Regel griffe NIE (so gefunden); .ui-desktop allein reicht auch
   nicht, die Klasse steht auf Telefonen ebenso im DOM. */
@media (min-width: 900px) {
    .ui-desktop .cat-strip {
        flex-wrap: wrap;
        margin-left: 0;
        margin-right: 0;
        padding-left: 0;
        padding-right: 0;
        overflow-x: visible;
    }
}

@media (min-width: 900px) {
    /* Textarea-Felder STAPELN (2026-08-30, /account/preferences: Satz-
       Captions wie "AGB-Text (HTML erlaubt: ...)" wurden als 4-zeilige
       Fettblöcke in die 13rem-Caption-Spalte neben hohe Textboxen
       gequetscht - dieselbe "labels are SENTENCES"-Erkenntnis wie der
       desk-doc-Ausschluss vom 2026-08-08). Eine Textbox will ohnehin
       die volle Formularbreite: Caption ganze Zeile, Box ganze Zeile.
       Gilt Formular-weit. Das :is() spiegelt BEWUSST die Container-
       Liste der Caption/Feld-Regeln: :is() erbt die Spezifität seines
       schwersten Arguments - mit einer schlankeren Liste verlöre diese
       spätere Regel den Kaskaden-Kampf und die Box hinge weiter
       eingerückt in Spalte 2 (zwei Anläufe gebraucht). */
    .ui-desktop .site-main:not(.desk-doc) :is(fieldset, form.form-stack, .fieldset-flow, fieldset > div:not([class]), form.form-stack > div:not([class]), form.form-stack > div:not([class]) > div:not([class])) > label:not(:has(input[type="checkbox"], input[type="radio"])):has(+ textarea) {
        grid-column: 1 / -1;
        padding-top: 0;
    }

    .ui-desktop .site-main:not(.desk-doc) :is(fieldset, form.form-stack, .fieldset-flow, fieldset > div:not([class]), form.form-stack > div:not([class]), form.form-stack > div:not([class]) > div:not([class])) > label:not(:has(input[type="checkbox"], input[type="radio"])):has(+ textarea) + textarea {
        grid-column: 1 / -1;
    }

    /* Hilfe am Computer (2026-08-31): die Lesespalte ist breit genug
       für zwei Themenspalten - 45 Themen in einer Spalte wären eine
       endlose Kolonne. Das Inhaltsverzeichnis bleibt beim Scrollen
       stehen, damit man in langen Artikeln die Übersicht behält. */
    .ui-desktop .help-group__list {
        grid-template-columns: 1fr 1fr;
        column-gap: 1.5rem;
    }

    .ui-desktop .help-toc {
        position: sticky;
        top: 5rem;
        z-index: 1;
    }

    .ui-desktop .help-nav {
        grid-template-columns: 1fr 1fr;
    }

    .ui-desktop .help-nav__link--next {
        grid-column: 2;
    }
}
