/* /Components/Avatar.razor.rz.scp.css */
/* Shared avatar disc. Size + font-size come from the inline style so one component serves the
   36px friends row, the 40px nav drawer and the 96px profile header without a class per size. */
.av[b-7fv3gb0mfn] {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    overflow: hidden;
    /* Clips the <img> to the circle on iOS Safari, which otherwise lets a child paint over an
       overflow:hidden rounded parent during transforms. */
    isolation: isolate;
    background: var(--sf2);
    border: 1px solid var(--ln);
    vertical-align: middle;
    user-select: none;
}

.av img[b-7fv3gb0mfn] {
    width: 100%;
    height: 100%;
    /* The master is square, so this never actually crops — it's here so a legacy off-square image
       degrades to a centred crop rather than a squashed oval. */
    object-fit: cover;
    display: block;
}

/* Initials read as a deliberate accent disc rather than a grey gap, and both tokens re-tint per
   theme (red by day, violet by night) so it never looks pasted on. */
.av-initials[b-7fv3gb0mfn] {
    background: var(--acc-soft);
    color: var(--acc);
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0.02em;
}
/* /Components/CouponMark.razor.rz.scp.css */
/* 0.95rem, not the 0.72rem the emoji used: see the render-size note in CouponMark.razor — the
   tilted shape needs the pixels. The mark grows ~4px, which both callers absorb; GameRow's phone
   layout shrinks its price column to max(price, "HK <mark>") and that budget just moves with it.

   currentColor inherits the same near-white the text glyph used to pick up, and inline-flex + a
   block svg keeps Razor's indentation whitespace out of the box (whitespace-only text nodes never
   become flex items). */
.cpn[b-xtxd6x08s7] {
    display: inline-flex;
    font-size: 0.95rem;
    line-height: 1;
    cursor: help;
}

.cpn svg[b-xtxd6x08s7] {
    display: block;
}
/* /Components/FeedReportFlag.razor.rz.scp.css */
/* One quiet line under a feed. Centred rather than right-aligned: a feed is a grid with no right-hand
   column to hang off, and against 25 tiles an edge-pinned control reads as a stray. */
.frf[b-7sj5xizpdw] {
    display: flex;
    justify-content: center;
    padding: 0.4rem 0 1.4rem;
}
/* /Components/FeedSkeleton.razor.rz.scp.css */
/* Ghost feed. Every surface/ink value resolves through the design tokens, so there is no per-theme
   override in this file and none should be added: the ghost blocks are the same inset surface
   (--sf2) the real covers sit on while they load, inside the same card shell (--sf / --ln / --lift)
   the real tiles and rows use. That is the whole trick — the skeleton is not "a loading graphic",
   it is the feed's own chrome with the content missing, so nothing moves when the data lands.

   Shapes are duplicated from GameCard.razor.css / GameRow.razor.css / the pages' .games-grid rather
   than shared, because scoped CSS cannot reach across components; the numbers that must agree are
   marked below. If a tile's radius or a row's height changes there, the ghost is allowed to be a
   frame late — it is a silhouette, not a mock. */

.feed-sk[b-au9bh1qmrl] {
    margin: 0.4rem 0 1rem;
    /* A short fade so a load that finishes almost immediately doesn't flash a grey grid. Not a
       delay: the words have to be readable from the first frame on a slow load. */
    animation: sk-in-b-au9bh1qmrl 200ms ease both;
}

.feed-sk.compact[b-au9bh1qmrl] {
    margin: 0.2rem 0 0.6rem;
}

@keyframes sk-in-b-au9bh1qmrl { from { opacity: 0; } to { opacity: 1; } }

/* ---- Progress hairline (timing identical to the old FeedLoader — see FeedSkeleton.razor) ---- */
/* Full width of the feed area, right under the filters, so it reads as "this page is fetching"
   rather than as an object placed in the middle of an empty box. Its 3px are reserved even while
   invisible, so nothing shifts when it fades in. */
.sk-track[b-au9bh1qmrl] {
    height: 3px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--tx2) 12%, transparent);
    overflow: hidden;
    opacity: 0;
    animation: sk-barin-b-au9bh1qmrl 500ms ease 800ms forwards;
}

.sk-fill[b-au9bh1qmrl] {
    display: block;
    height: 100%;
    width: 0;
    border-radius: 999px;
    /* Mixed from the SECONDARY ink: primary ink at these alphas lands as a near-black slab on the
       light theme's paper, against a component whose whole brief is "calm". */
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--tx2) 45%, transparent),
        color-mix(in srgb, var(--tx2) 85%, transparent));
    animation: sk-fill-b-au9bh1qmrl var(--fillms, 1000ms) cubic-bezier(0.25, 0.6, 0.3, 1) 800ms forwards;
}

@keyframes sk-barin-b-au9bh1qmrl { to { opacity: 1; } }
@keyframes sk-fill-b-au9bh1qmrl  { to { width: 94%; } }

/* ---- Status words: one caption line above the grid, four messages cross-fading over ~5.2s ---- */
.sk-msgs[b-au9bh1qmrl] {
    position: relative;
    height: 1.3em;
    margin: 0.55rem 0 0.8rem;
    font-family: var(--fd, inherit);
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--tx2);
}

.sk-msg[b-au9bh1qmrl] {
    position: absolute;
    left: 0;
    top: 0;
    opacity: 0;
    animation: sk-msg-b-au9bh1qmrl 5.2s linear infinite;
}

/* A page waiting on something other than prices says one thing and holds still. */
.sk-msgs.static .sk-msg[b-au9bh1qmrl] {
    animation: none;
    opacity: 1;
}

.sk-msg:nth-child(1)[b-au9bh1qmrl] { animation-delay: 0s; }
.sk-msg:nth-child(2)[b-au9bh1qmrl] { animation-delay: 1.3s; }
.sk-msg:nth-child(3)[b-au9bh1qmrl] { animation-delay: 2.6s; }
.sk-msg:nth-child(4)[b-au9bh1qmrl] { animation-delay: 3.9s; }

@keyframes sk-msg-b-au9bh1qmrl {
    0%   { opacity: 0; transform: translateY(5px); }
    6%   { opacity: 1; transform: translateY(0); }
    19%  { opacity: 1; transform: translateY(0); }
    25%  { opacity: 0; transform: translateY(-5px); }
    100% { opacity: 0; transform: translateY(-5px); }
}

/* ---- The ghost body ---- */
/* Cropped at the fold and dissolved into the page rather than ending on a hard edge, so the ghost
   reads as "a feed continues below" and never makes the document taller than the real one. The
   height comes from JS (allregions.fitSkeleton measures this element's own distance to the bottom
   of the viewport); 76dvh is the no-JS fallback — roughly right for the catalog, low enough not to
   overshoot on the rail-topped home page.

   ONE animation for the whole body, on the container: a per-block sweep would put ~100 separately
   animated layers on screen for a state that typically lasts under a second. */
.sk-body[b-au9bh1qmrl] {
    overflow: hidden;
    max-height: var(--sk-max, 76dvh);
    -webkit-mask-image: linear-gradient(to bottom, #000 58%, rgba(0, 0, 0, 0.15) 92%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 58%, rgba(0, 0, 0, 0.15) 92%, transparent 100%);
    animation: sk-pulse-b-au9bh1qmrl 1.9s ease-in-out infinite;
}

/* The load-more slot shows the top of the next page under the button the user just pressed: no
   cropping (it is already short), no dissolve (it has a real list above it, not the fold below). */
.feed-sk.compact .sk-body[b-au9bh1qmrl] {
    max-height: none;
    -webkit-mask-image: none;
    mask-image: none;
}

@keyframes sk-pulse-b-au9bh1qmrl {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.58; }
}

/* Grid: must match .games-grid / .catalog-grid on Home / Deals / NewReleases. */
.sk-grid[b-au9bh1qmrl] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}

/* Tile: the .game-card shell, empty. */
.sk-tile[b-au9bh1qmrl] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    padding: 7px;
}

.sk-cover[b-au9bh1qmrl] {
    aspect-ratio: 1;
    border-radius: 11px;
}

.sk-line[b-au9bh1qmrl] {
    border-radius: 6px;
}

/* Title block (two lines) + price + the bottom region/for-you line, at roughly the offsets
   .card-info puts them: ~83px of ghost against a real tile's ~82px of text, so the grid does not
   visibly re-flow when the cards arrive. */
.sk-t1[b-au9bh1qmrl]     { height: 0.62rem; width: 88%; margin: 12px 4px 0; }
.sk-t2[b-au9bh1qmrl]     { height: 0.62rem; width: 62%; margin: 8px 4px 0; }
.sk-tprice[b-au9bh1qmrl] { height: 0.85rem; width: 46%; margin: 12px 4px 0; }
.sk-tsub[b-au9bh1qmrl]   { height: 0.5rem;  width: 34%; margin: 8px 4px 4px; }

/* Titles are not all the same length. Three widths on a 3/4/5 cycle is enough to stop the grid
   reading as a printed pattern, and the cycles are coprime with every column count we use (2, 4, 5)
   so neighbouring tiles rarely repeat. */
.sk-tile:nth-child(3n)   .sk-t1[b-au9bh1qmrl] { width: 72%; }
.sk-tile:nth-child(4n+1) .sk-t1[b-au9bh1qmrl] { width: 94%; }
.sk-tile:nth-child(4n)   .sk-t2[b-au9bh1qmrl] { width: 44%; }
.sk-tile:nth-child(5n)   .sk-t2[b-au9bh1qmrl] { width: 78%; }
.sk-tile:nth-child(3n+1) .sk-tprice[b-au9bh1qmrl] { width: 38%; }

/* Rails (Trending): must match .rail / .rail-head / .rail-track / .rail-item in Rail.razor.css. The
   track is clipped rather than scrollable — see the note in FeedSkeleton.razor.
   ONE deliberate divergence: on a mouse the real track reserves a ~10px scrollbar lane and trades
   its bottom padding for it (Rail.razor.css), which makes it ~2px taller than this ghost. Not
   chased — a clipped track can have no lane, and the lane's width is engine-dependent, so the only
   way to "match" would be to hard-code one engine's number. 2px is below noticing. */
.sk-rails[b-au9bh1qmrl] {
    display: flex;
    flex-direction: column;
    gap: 1.6rem;
}

.sk-railtitle[b-au9bh1qmrl] {
    height: 1.05rem;
    width: min(260px, 55%);
    margin-bottom: 0.85rem;
}

.sk-railtrack[b-au9bh1qmrl] {
    display: flex;
    gap: 1rem;
    padding: 4px 2px 0.6rem;
    overflow: hidden;
}

.sk-railtrack .sk-tile[b-au9bh1qmrl] {
    flex: 0 0 200px;
    max-width: 200px;
}

/* Alternate the ghost headings so three stacked rails don't read as one repeated block. */
.sk-rail:nth-child(2n) .sk-railtitle[b-au9bh1qmrl] { width: min(190px, 42%); }
.sk-rail:nth-child(3n) .sk-railtitle[b-au9bh1qmrl] { width: min(310px, 64%); }

/* People (Discover): must match .disc-list / .disc-card / .disc-user / .disc-shared. */
.sk-people[b-au9bh1qmrl] {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.sk-pcard[b-au9bh1qmrl] {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 0.7rem 0.9rem;
}

.sk-puser[b-au9bh1qmrl] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 180px;
    flex-shrink: 0;
}

.sk-pavatar[b-au9bh1qmrl] {
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    border-radius: 50%;
}

.sk-pname[b-au9bh1qmrl] { height: 0.7rem; width: 104px; }

.sk-pshared[b-au9bh1qmrl] {
    flex: 1;
    min-width: 0;
}

.sk-p1[b-au9bh1qmrl] { height: 0.62rem; width: min(210px, 38%); }
.sk-p2[b-au9bh1qmrl] { height: 0.52rem; width: min(340px, 66%); margin-top: 8px; }

.sk-pcard:nth-child(2n) .sk-pname[b-au9bh1qmrl] { width: 78px; }
.sk-pcard:nth-child(3n) .sk-p2[b-au9bh1qmrl] { width: min(240px, 48%); }

/* List: must match .games-list + .game-row. */
.sk-list[b-au9bh1qmrl] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
}

.sk-row[b-au9bh1qmrl] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 322px;
    gap: 14px;
    align-items: center;
    padding: 8px 14px;
}

.sk-row + .sk-row[b-au9bh1qmrl] {
    border-top: 1px solid var(--ln);
}

.sk-rmain[b-au9bh1qmrl] {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

.sk-rcover[b-au9bh1qmrl] {
    width: 46px;
    height: 46px;
    flex: 0 0 auto;
    border-radius: 9px;
    border: 1px solid var(--ln);
}

.sk-rtext[b-au9bh1qmrl] {
    flex: 1;
    min-width: 0;
}

.sk-r1[b-au9bh1qmrl]     { height: 0.66rem; width: min(360px, 54%); }
.sk-r2[b-au9bh1qmrl]     { height: 0.55rem; width: min(220px, 34%); margin-top: 9px; }
.sk-rprice[b-au9bh1qmrl] { height: 0.85rem; width: 108px; justify-self: end; }

.sk-row:nth-child(3n)   .sk-r1[b-au9bh1qmrl] { width: min(280px, 42%); }
.sk-row:nth-child(4n+1) .sk-r1[b-au9bh1qmrl] { width: min(420px, 64%); }
.sk-row:nth-child(3n+1) .sk-r2[b-au9bh1qmrl] { width: min(160px, 26%); }

/* The blocks themselves — one declaration, so a ghost cover and a ghost line can never drift apart.
   --sf2 alone is the inset surface, which against a white card is nearly invisible: the ghosts have
   to read as "content missing here", not as a blank card. A few percent of ink over it is enough,
   and because it is mixed FROM the ink it darkens paper and lightens near-black without a per-theme
   value. */
.sk-cover[b-au9bh1qmrl],
.sk-rcover[b-au9bh1qmrl],
.sk-pavatar[b-au9bh1qmrl],
.sk-line[b-au9bh1qmrl] {
    background: color-mix(in srgb, var(--tx) 7%, var(--sf2));
}

/* Phones: 2 columns, matching the pages' own 480px rule — and the rail's own narrower items. */
@media (max-width: 480px) {
    .sk-grid[b-au9bh1qmrl] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.7rem;
    }

    .sk-railtrack[b-au9bh1qmrl] {
        gap: 0.7rem;
    }

    .sk-railtrack .sk-tile[b-au9bh1qmrl] {
        flex-basis: 150px;
        max-width: 150px;
    }
}

/* Dense rows tighten at the same breakpoint .game-row does. */
@media (max-width: 760px) {
    .sk-row[b-au9bh1qmrl] {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px;
        padding: 8px 12px;
    }

    .sk-rcover[b-au9bh1qmrl] {
        width: 44px;
        height: 44px;
    }

    .sk-rprice[b-au9bh1qmrl] {
        width: 74px;
    }
}

/* Reduced motion: the ghost is a static layout — the words stop cycling, the body stops breathing,
   and the bar keeps only its 0.8s hold (an opacity step after a delay is an appearance, not motion,
   and it is what stops a static bar flashing on every quick load). */
@media (prefers-reduced-motion: reduce) {
    .feed-sk[b-au9bh1qmrl] { animation: none; }
    .sk-msg[b-au9bh1qmrl] { animation: none; opacity: 0; }
    .sk-msg:nth-child(1)[b-au9bh1qmrl] { opacity: 1; }
    .sk-body[b-au9bh1qmrl] { animation: none; opacity: 0.85; }
    .sk-track[b-au9bh1qmrl] { animation: sk-barin-b-au9bh1qmrl 1ms linear 800ms forwards; }
    .sk-fill[b-au9bh1qmrl] { animation: none; width: 60%; }
}
/* /Components/FilterBar.razor.rz.scp.css */
/* Toolbar row. Desktop: view segment + the Games/Add-ons/All scope segment + Platform select + an
   "Options ▾" popover (the boolean toggles fold in, so the row stays short). Phones: a «Filters·N»
   chip opens the same controls as a bottom sheet, stacked, with the options as a wrapped chip group. */

.filter-bar[b-h5bokirca7] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}

/* Tiles ⇄ list segment. */
.view-seg[b-h5bokirca7] {
    display: inline-flex;
    gap: 2px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
    padding: 3px;
    flex: 0 0 auto;
}

.seg-btn[b-h5bokirca7] {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: none;
    background: transparent;
    color: var(--mut);
    border-radius: 999px;
    padding: 4px 11px;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 800;
    cursor: pointer;
}

.seg-btn svg[b-h5bokirca7] {
    width: 13px;
    height: 13px;
    fill: currentColor;
}

.seg-btn.on[b-h5bokirca7] {
    background: var(--acc);
    color: var(--acc-ink);
}

/* Carousels on/off — the same capsule as the view segment, because it answers the same kind of
   question (what does this page draw), just not a mutually exclusive one, so it is its own button
   rather than a third cell of that segment. Same type as .seg-btn too (0.78rem/800 + a 6px gap), so
   the labelled pill sits in the row as a peer of «Tiles»/«List» rather than as a bigger sibling.
   The label is NOT hidden on phones the way .seg-label is: there the tooltip does not exist, and
   tiles-vs-list is a pair of self-evident glyphs while "carousels" is not.
   The pill itself never fills — the state is in the ink: accent icon + full-strength label = on,
   everything muted + the icon struck through = off. The background stays --sf in both states
   because the slash carves itself out of the icon with an --sf-coloured stroke. */
.rail-btn[b-h5bokirca7] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex: 0 0 auto;
    border: 1px solid var(--ln);
    background: var(--sf);
    color: var(--tx);
    border-radius: 999px;
    padding: 4px 11px;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 800;
    cursor: pointer;
}

.rail-btn svg[b-h5bokirca7] {
    width: 15px;
    height: 15px;
    fill: currentColor;
    color: var(--acc);
}

.rail-btn:hover[b-h5bokirca7] {
    border-color: var(--mut);
}

.rail-btn.off[b-h5bokirca7],
.rail-btn.off svg[b-h5bokirca7] {
    color: var(--mut);
}

.rail-btn.off:hover[b-h5bokirca7] {
    color: var(--tx2);
}

/* «Filters · N» chip — phones only. */
.fb-open[b-h5bokirca7] {
    display: none;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 0.32rem 0.85rem;
    background: var(--sf);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 800;
    cursor: pointer;
    white-space: nowrap;
}

.fb-open svg[b-h5bokirca7] {
    width: 13px;
    height: 13px;
}

.fb-open.has[b-h5bokirca7] {
    border-color: var(--deal-brd);
    background: var(--deal-bg);
    color: var(--deal);
}

.fb-dim[b-h5bokirca7] {
    display: none;
}

/* The inline count. A FACT, so it carries no border, no background and no pointer — anything
   capsule-shaped in this row is something to press. It flows with «Reset» (order: 1) and ahead of it
   in the DOM, so it lands immediately after the last control rather than at the row's far edge:
   the reader's eye and pointer are already there, which is the whole point of putting a number here.
   Tabular figures because it is the one thing in the row that changes on every filter — proportional
   digits re-flow the row by a pixel or two per change, and «Reset» beside it visibly shifts. */
.fb-count[b-h5bokirca7] {
    order: 1;
    display: inline-flex;
    align-items: center;
    padding: 0.32rem 0.1rem;
    color: var(--tx2);
    font-size: 0.8rem;
    font-weight: 800;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    transition: opacity 0.15s;
}

/* Re-counting: fade, never blank. Same choice as .fb-apply.counting — the last honest figure is
   still worth reading, and a number that vanishes for the length of a fetch takes the row's width
   with it. */
.fb-count.stale[b-h5bokirca7] {
    opacity: 0.45;
}

/* Desktop «Reset» — a quiet text button, not a capsule: it's an escape hatch next to the controls,
   not a fifth filter. Rendered only when something is active; hidden on phones, where the sheet
   header carries its own reset. */
.fb-reset-inline[b-h5bokirca7] {
    display: inline-flex;
    align-items: center;
    /* Markup order puts it before .fb-controls (it must stay OUT of that block so the mobile sheet
       doesn't swallow it), but it reads as a trailing escape hatch — so it flows last in the row.
       .fb-controls is display:contents, so its children are flex items here too and share order 0. */
    order: 1;
    border: none;
    background: none;
    color: var(--mut);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 800;
    padding: 0.32rem 0.35rem;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: var(--ln2);
}

.fb-reset-inline:hover[b-h5bokirca7] {
    color: var(--tx);
    text-decoration-color: currentColor;
}

/* Sheet chrome — hidden while the block is inline (desktop). */
.fb-head[b-h5bokirca7],
.fb-apply[b-h5bokirca7],
.fb-label[b-h5bokirca7] {
    display: none;
}

/* Desktop: controls flow directly in the toolbar row. */
.fb-controls[b-h5bokirca7],
.fb-selects[b-h5bokirca7] {
    display: contents;
}

.filter-group[b-h5bokirca7] {
    display: flex;
}

/* «Показывать» — the feed's scope. Same segment language as the view switch (one of these, the
   active cell filled), but its own classes: the view segment drops its labels on phones, and these
   ARE labels. */
.scope-seg[b-h5bokirca7] {
    display: inline-flex;
    gap: 2px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
    padding: 3px;
    flex: 0 0 auto;
}

.scope-btn[b-h5bokirca7] {
    border: none;
    background: transparent;
    color: var(--mut);
    border-radius: 999px;
    padding: 0.2rem 0.8rem;
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 800;
    white-space: nowrap;
    cursor: pointer;
}

.scope-btn:hover[b-h5bokirca7] {
    color: var(--tx);
}

.scope-btn.on[b-h5bokirca7] {
    background: var(--acc);
    color: var(--acc-ink);
}

/* Add-on kinds — a refinement of the scope, so they read as subordinate to it: plain outlined chips
   rather than a second filled segment, which would compete with the control they belong to. */
.fb-sub[b-h5bokirca7] {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
}

.sub-chip[b-h5bokirca7] {
    border: 1px solid var(--ln);
    background: var(--sf);
    color: var(--mut);
    border-radius: 999px;
    padding: 0.24rem 0.7rem;
    font-family: inherit;
    font-size: 0.76rem;
    font-weight: 800;
    white-space: nowrap;
    cursor: pointer;
}

.sub-chip:hover[b-h5bokirca7] {
    border-color: var(--mut);
    color: var(--tx);
}

/* Selected kind. Deliberately not the segment's filled treatment — two filled controls stacked would
   read as two questions of equal weight. A doubled ring is enough to be unambiguous while staying
   visibly subordinate to the scope above it. */
.sub-chip.on[b-h5bokirca7] {
    border-color: var(--tx2);
    background: var(--sf2);
    color: var(--tx);
    box-shadow: inset 0 0 0 1px var(--tx2);
}

.filter-group select[b-h5bokirca7],
.fb-opts-btn[b-h5bokirca7] {
    background-color: var(--sf);
    color: var(--tx2);
    border: 1px solid var(--ln);
    padding: 0.32rem 1.7rem 0.32rem 0.85rem;
    border-radius: 999px;
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 800;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a8578' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.7rem center;
}

.filter-group select:hover[b-h5bokirca7] {
    border-color: var(--mut);
}

/* "Options ▾" popover trigger — a capsule matching the selects, but built from a real button so it
   can carry the sliders glyph + count. Its own chevron replaces the select's arrow background. */
.fb-opts-wrap[b-h5bokirca7] {
    position: relative;
    display: inline-flex;
}

.fb-opts-btn[b-h5bokirca7] {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0.32rem 0.7rem 0.32rem 0.75rem;
    background-image: none;   /* the chevron is a real element here, not the select arrow */
    white-space: nowrap;
}

.fb-opts-btn svg[b-h5bokirca7] {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
}

.fb-opts-btn .chev[b-h5bokirca7] {
    font-size: 0.7em;
    color: var(--mut);
}

.fb-opts-btn:hover[b-h5bokirca7] {
    border-color: var(--mut);
}

.fb-opts-btn.has[b-h5bokirca7] {
    border-color: var(--deal-brd);
    background: var(--deal-bg);
    color: var(--deal);
}

.fb-opts-btn.has .chev[b-h5bokirca7] {
    color: var(--deal);
}

/* Transparent full-screen catcher that closes the popover on an outside click (desktop only; it's
   rendered only while the popover is open). */
.fb-opts-dim[b-h5bokirca7] {
    position: fixed;
    inset: 0;
    z-index: 190;
    background: transparent;
    border: none;
    padding: 0;
    cursor: default;
}

.fb-opts[b-h5bokirca7] {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 200;
    min-width: 236px;
    flex-direction: column;
    gap: 1px;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 0.4rem;
    box-shadow: var(--frame-sh);
}

.fb-opts.open[b-h5bokirca7] {
    display: flex;
}

/* Checkbox rows inside the popover: borderless, full-width, hover-highlighted — a tidy menu, not a
   box of pills. (Phones override this back to standalone chips; see the media query.) */
.check-chip[b-h5bokirca7] {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    border: 1px solid transparent;
    background: transparent;
    border-radius: 9px;
    padding: 0.45rem 0.55rem;
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s;
}

.check-chip input[b-h5bokirca7] {
    width: 15px;
    height: 15px;
    accent-color: var(--deal);
    cursor: pointer;
    margin: 0;
    flex: 0 0 auto;
}

.check-chip:hover[b-h5bokirca7] {
    background: var(--sf2);
}

/* Active row: the native tick already shows state, so just firm up the label (no full-width fill). */
.check-chip.on-neutral[b-h5bokirca7],
.check-chip.sale-check.on[b-h5bokirca7] {
    color: var(--tx);
    font-weight: 800;
}

/* Locked toggle — forced from the header "How you buy" lens, or no home region yet. */
.check-chip.locked[b-h5bokirca7] {
    opacity: 0.55;
    cursor: not-allowed;
}

.check-chip.locked input[b-h5bokirca7] {
    cursor: not-allowed;
}

/* ---- Phones: chip + bottom sheet. One fixed layer, safe-area padded, никаких перекрытий. ---- */
@media (max-width: 760px) {
    .seg-label[b-h5bokirca7] {
        display: none;
    }

    .seg-btn[b-h5bokirca7] {
        padding: 4px 9px;
    }

    .fb-open[b-h5bokirca7] {
        display: inline-flex;
    }

    /* The sheet header owns the reset here — the toolbar one would just crowd the chip row. */
    .fb-reset-inline[b-h5bokirca7] {
        display: none;
    }

    /* …and the sheet's «Show N games» owns the count. The chip row already carries «Filters · N»,
       which is a count of FILTERS: two unlabelled numbers side by side in a 390px row is worse than
       one, and the number that answers "did it change" is the one the commit button already gives. */
    .fb-count[b-h5bokirca7] {
        display: none;
    }

    /* Controls hide until the sheet opens. */
    .fb-controls[b-h5bokirca7] {
        display: none;
    }

    .filter-bar.open .fb-dim[b-h5bokirca7] {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1090;
        background: rgba(10, 8, 4, 0.42);
        border: none;
        padding: 0;
        cursor: default;
    }

    .filter-bar.open .fb-controls[b-h5bokirca7] {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 0.7rem;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1100;
        background: var(--sf);
        border-top: 1px solid var(--ln);
        border-radius: 22px 22px 0 0;
        padding: 0.5rem 1rem calc(1rem + env(safe-area-inset-bottom));
        max-height: 78vh;
        overflow-y: auto;
        /* The page is frozen by allregions.lockScroll while the sheet is open; contain stops a swipe
           at the sheet's scroll edge from chaining anywhere. */
        overscroll-behavior: contain;
        box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.28);
    }

    .fb-head[b-h5bokirca7] {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        position: relative;
        padding-top: 14px;
        margin-bottom: 0.1rem;
    }

    .fb-head b[b-h5bokirca7] {
        font-size: 1.05rem;
        font-weight: 900;
    }

    .fb-grab[b-h5bokirca7] {
        position: absolute;
        top: 4px;
        left: 50%;
        transform: translateX(-50%);
        width: 38px;
        height: 4px;
        border-radius: 2px;
        background: var(--ln2);
    }

    /* Two states, because most of the time this button can do nothing. At rest it is a dimmed,
       unpressable label — present so the sheet's shape never jumps, but plainly inert. Armed it takes
       the same outlined-capsule treatment as the option chips beside it and states HOW MANY filters it
       would clear, which is the fact that decides whether to press it. */
    .fb-reset[b-h5bokirca7] {
        background: none;
        border: 1px solid transparent;
        border-radius: 999px;
        color: var(--mut);
        font-family: inherit;
        font-size: 0.82rem;
        font-weight: 800;
        cursor: pointer;
        padding: 0.18rem 0.1rem;
        transition: color 0.12s, background 0.12s, border-color 0.12s;
    }

    .fb-reset:disabled[b-h5bokirca7] {
        opacity: 0.4;
        cursor: default;
    }

    .fb-reset.armed[b-h5bokirca7] {
        border-color: var(--deal-brd);
        background: var(--deal-bg);
        color: var(--deal);
        padding: 0.18rem 0.6rem;
    }

    .fb-label[b-h5bokirca7] {
        display: block;
        font-family: var(--fn);
        font-size: 0.62rem;
        font-weight: 700;
        letter-spacing: 0.09em;
        text-transform: uppercase;
        color: var(--mut);
        margin-bottom: 5px;
    }

    /* Stacked, one control per row. The scope segment answers the sheet's first question and wants
       the full width for its three cells; the add-on kinds hang directly under it, so a 2-column grid
       (what Type + Platform used to be) would either split that pair or leave odd half-width gaps
       whenever the kinds row isn't there. Monotony is broken by the shapes now — segment, chips,
       select — not by the columns. */
    .fb-selects[b-h5bokirca7] {
        display: grid;
        grid-template-columns: 1fr;
        gap: 0.7rem;
    }

    /* The kinds belong to the scope above them, not to the select below. */
    .fg-sub[b-h5bokirca7] {
        margin-top: -0.35rem;
    }

    .scope-seg[b-h5bokirca7] {
        display: flex;
    }

    .scope-btn[b-h5bokirca7] {
        flex: 1 1 0;
        padding: 0.5rem 0.4rem;
        font-size: 0.82rem;
    }

    .fb-sub[b-h5bokirca7] {
        gap: 0.4rem;
    }

    .sub-chip[b-h5bokirca7] {
        padding: 0.42rem 0.8rem;
        font-size: 0.8rem;
        border-radius: 12px;
    }

    /* Inside the sheet, "this control is doing something" is said ONE way: green. The checkboxes
       already say it (accent-color: var(--deal) on the native tick), so the selected add-on kind and
       any select holding a non-default value say it too — otherwise the sheet has two visual
       languages for the same fact and only one of them is learnable. The desktop toolbar keeps the
       quieter ring / plain capsule: there these sit in a row of many controls where the tick is the
       only green, and lighting three more would read as an alert. */
    .sub-chip.on[b-h5bokirca7] {
        border-color: var(--deal-brd);
        background: var(--deal-bg);
        color: var(--deal);
        box-shadow: none;   /* drop the desktop double ring — the fill carries it here */
    }

    /* background-COLOR only: the shorthand would take the chevron (a background-image) with it. */
    .filter-group select.on[b-h5bokirca7] {
        border-color: var(--deal-brd);
        background-color: var(--deal-bg);
        color: var(--deal);
    }

    .filter-group[b-h5bokirca7] {
        flex-direction: column;
        min-width: 0;
    }

    .filter-group select[b-h5bokirca7] {
        width: 100%;
        padding-top: 0.55rem;
        padding-bottom: 0.55rem;
        border-radius: 12px;
    }

    /* Options: the popover trigger + backdrop vanish; the checks show inline under a section label as
       a wrapped chip group (content-sized, not full-width bars). */
    .fb-opts-wrap[b-h5bokirca7] {
        position: static;
        display: block;
    }

    .fb-opts-btn[b-h5bokirca7],
    .fb-opts-dim[b-h5bokirca7] {
        display: none;
    }

    .fb-opts[b-h5bokirca7] {
        display: flex;
        position: static;
        flex-direction: row;
        flex-wrap: wrap;
        gap: 0.5rem;
        min-width: 0;
        background: none;
        border: none;
        border-radius: 0;
        padding: 0;
        box-shadow: none;
    }

    /* Restore the standalone pill-chip look for the wrapped options. */
    .check-chip[b-h5bokirca7] {
        width: auto;
        justify-content: flex-start;
        border: 1px solid var(--ln);
        background: var(--sf);
        border-radius: 12px;
        padding: 0.5rem 0.85rem;
        font-weight: 800;
    }

    .check-chip:hover[b-h5bokirca7] {
        background: var(--sf);
    }

    .check-chip.sale-check.on[b-h5bokirca7] {
        background: var(--deal-bg);
        border-color: var(--deal-brd);
        color: var(--deal);
    }

    .check-chip.on-neutral[b-h5bokirca7] {
        border-color: var(--mut);
        color: var(--tx);
    }

    /* Sticky, not merely last: the sheet is capped at 78vh and scrolls, and on a short phone the commit
       fell BELOW the fold — measured at 360×640 on the catalog, where the button sat at y=679 in a
       640px viewport. A user who cannot see the commit reads the sheet as having no way to apply and
       taps outside, which is a CANCEL — so the filters they just set were silently thrown away. That is
       the same symptom as "the filter does nothing", arrived at from the other side. The negative
       margins + padding let the surface run to the sheet's edges so scrolled content passes under it
       rather than beside it. */
    .fb-apply[b-h5bokirca7] {
        display: block;
        position: sticky;
        bottom: calc(-1rem - env(safe-area-inset-bottom));
        z-index: 1;
        margin: 0.4rem -1rem calc(-1rem - env(safe-area-inset-bottom));
        padding: 0.7rem 1rem calc(0.7rem + env(safe-area-inset-bottom));
        background: var(--acc);
        color: var(--acc-ink);
        border: none;
        border-radius: 14px 14px 0 0;
        font-family: inherit;
        font-size: 0.95rem;
        font-weight: 800;
        cursor: pointer;
        transition: opacity 0.15s;
        box-shadow: 0 -8px 18px var(--sf);
    }

    /* Re-counting the staged filters: fade the number rather than blank it, so the button keeps its
       size and the user still has the last honest figure to read. Stays pressable throughout —
       committing mid-count is fine, the feed load is the authority anyway. */
    .fb-apply.counting[b-h5bokirca7] {
        opacity: 0.62;
    }

    /* Nothing matches: read as unavailable rather than merely faded, so it isn't mistaken for the
       counting state above. Wins over .counting — a blocked button is not "still thinking". */
    .fb-apply:disabled[b-h5bokirca7] {
        background: var(--sf2);
        color: var(--mut);
        box-shadow: inset 0 0 0 1px var(--ln);
        opacity: 1;
        cursor: not-allowed;
    }
}

/* Short landscape: a phone on its side is ~390px tall and the chrome above the feed used to eat
   two thirds of it. Vertical only — portrait and desktop are untouched. */
@media (orientation: landscape) and (max-height: 520px) {
    .filter-bar[b-h5bokirca7] {
        margin-bottom: 0.5rem;
    }
}
/* /Components/GameCard.razor.rz.scp.css */
/* Tile card — «День и Ночь». Every tile sits on its own surface (the fix for "price detaches
   from the tile"): white card with a tactile thick bottom by day, hairline dark card with an
   ambient cover glow by night. All colors come from app.css tokens. */

.game-card[b-vvsottbycu] {
    display: flex;
    flex-direction: column;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    padding: 7px;
    /* THE BOTTOM INSET PAYS FOR THE FRAME, so it is 7px like the other three sides — not 7px PLUS the
       frame. The bottom border is `--lift + 1px`, which is 4px by day and 2px by night, and with a flat
       7px of padding under it the status line sat 2.4px (night) / 3.4px (day) ABOVE the centre of the
       space between the price row and the card's edge — owner, 2026-09-08: «нижнюю линию с ценами можно
       опустить на пару пикселей… не по центру». Measured on a grid tile, invitation line, ink to ink:
       5.73px above it, 10.52 (night) / 12.52 (day) below. Subtracting the border leaves 8.52 below in
       BOTH themes, so the gap stops depending on the theme at all — it used to differ by 2px, and the
       CARD did too (307.16 night vs 309.16 day, now 305.16 in both).
       1.39px above centre is the FLOOR here, not a compromise left half-done: closing it would need the
       inset down to 0.2px in the light theme, i.e. the chip touching a 4px border inside a 16px radius.
       Any further centring has to come from the gap ABOVE (`.card-status`'s 4px), which would make the
       tile taller — the opposite of what that number is for. */
    padding-bottom: max(2px, calc(6px - var(--lift)));
    text-decoration: none;
    color: inherit;
    transition: transform 0.16s ease, box-shadow 0.16s ease;
    position: relative;
}

.game-card:hover[b-vvsottbycu] {
    transform: translateY(-3px);
    box-shadow: var(--card-sh);
    color: inherit;
}

/* Ambient glow: a blurred copy of the cover behind the card. Dark theme only (--glow-op). */
.card-glow[b-vvsottbycu] {
    position: absolute;
    left: 8%;
    right: 8%;
    top: 5%;
    aspect-ratio: 1;
    background-size: cover;
    background-position: center;
    filter: blur(22px) saturate(1.5);
    opacity: var(--glow-op);
    border-radius: 22px;
    z-index: 0;
    pointer-events: none;
}

.card-image[b-vvsottbycu],
.card-info[b-vvsottbycu] {
    position: relative;
    z-index: 1;
}

.card-image[b-vvsottbycu] {
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: 11px;
    background: var(--sf2);
}

.card-image img[b-vvsottbycu] {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.no-image[b-vvsottbycu] {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mut);
    font-size: 0.9rem;
}

/* The single top-right chip (kind / platform / chart movement). */
.kind-chip[b-vvsottbycu],
.mv-chip[b-vvsottbycu] {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 2px 7px;
    border-radius: 7px;
    font-weight: 800;
    font-size: 0.66rem;
    letter-spacing: 0.04em;
    z-index: 2;
    background: var(--sw2-bg);
    color: var(--sw2-tx);
    text-transform: uppercase;
}

.kind-chip.kind-bundle[b-vvsottbycu] { color: var(--gold); }
.kind-chip.kind-demo[b-vvsottbycu]   { color: var(--mut); }

.mv-chip[b-vvsottbycu] { font-family: var(--fn); }
.mv-chip.trend-new[b-vvsottbycu]  { color: var(--gold); }
.mv-chip.trend-up[b-vvsottbycu]   { color: var(--deal); }
.mv-chip.trend-down[b-vvsottbycu] { color: #E4587D; }

/* An uppercase chip centres its CAP BOX and lands its baseline on a whole pixel — the family rule,
   stated in full in `GameDetail.razor.css` above `.via-tag`. Measured 0.65px high here (`SW2`) and
   0.72px (`▲ 1`): this is the TILE's copy of the badge `.row-badge` carries in the list, so leaving it
   would put two centrings of one badge on one site — which is the reason the list's copy was fixed.
   Checked before applying, because this chip is the one member of the family whose ink is not a
   capital: `▲` measures exactly the cap height (7.6px against 7.6px), so the cap box centres it. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(14px - 1cap)) {
    .kind-chip[b-vvsottbycu],
    .mv-chip[b-vvsottbycu] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(14px - 1cap);
    }
}

/* Pre-order / coming-soon strip along the cover's bottom edge; leaves room for the heart. */
.release-badge[b-vvsottbycu] {
    position: absolute;
    bottom: 8px;
    left: 8px;
    right: 50px;
    text-align: center;
    padding: 3px 8px;
    border-radius: 8px;
    font-weight: 800;
    font-size: 0.68rem;
    letter-spacing: 0.03em;
    z-index: 2;
    color: #fff;
}

.release-badge.preorder[b-vvsottbycu] { background: var(--info); }
.release-badge.coming[b-vvsottbycu] { background: var(--coming); }

.card-info[b-vvsottbycu] {
    /* Top 8px = breathing room between the cover and the title (a 4px gap let titles bleed into light
       covers); bottom 1px keeps the card tight. */
    padding: 8px 4px 1px;
    flex: 1;
    display: flex;
    flex-direction: column;
    /* Title (fixed 2 lines) + the region/price pair sit at the top; the status line (reason chip +
       the second answer) is the foot, pinned by margin-top:auto on .card-foot. Every tile's content is
       the same height, so all of them share the same title, price and status positions. */
    justify-content: flex-start;
}

/* Title is a fixed TWO-line block so the price + bottom cluster line up across every card. A 1-line
   name is centred vertically inside that block (flex align-items:center); the inner span does the
   2-line clamp (line-clamp can't itself vertically-centre within a taller min-height). */
.card-title[b-vvsottbycu] {
    font-size: 0.88rem;
    font-weight: 800;
    margin: 0;
    line-height: 1.3;
    min-height: 2.6em;
    display: flex;
    align-items: center;
}
.card-title-text[b-vvsottbycu] {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    /* Muted block colour → the clamp ellipsis matches the greyed suffix when a title clips inside
       " — Switch 2 Edition"; .title-main repaints the actual name to the primary ink. */
    color: var(--mut);
}

.title-main[b-vvsottbycu] {
    color: var(--tx);
}

/* M.4: the "— Switch 2 Edition" suffix is redundant with the SW2 badge — mute it so the actual name
   reads first. */
.title-suffix[b-vvsottbycu] {
    color: var(--mut);
    font-weight: 700;
}


.card-price[b-vvsottbycu] {
    padding-top: 2px;   /* small gap under the fixed 2-line title */
    display: flex;
    /* CENTRE, not baseline: the region chip is a BOX and the price is text, so aligning their baselines
       hung the box a pixel low against the digits. The chip's own `text-box: trim-both` already removes
       its leading, which is what makes centring exact here. */
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    /* A 145px phone tile cannot hold price + a struck-through was + chip + ticket. It CLIPS rather than
       spilling into the neighbour, and .price-was is the thing that gives (see below) — the amount and
       its region are the pair, and the pair is what must survive. */
    overflow: hidden;
    line-height: 1.1;
    /* THE PRICE DOES NOT FLOAT. It carried `margin-top: auto` for a week, so that whatever height a tile
       did not use was split above and below it — and the only thing that produced such height was the
       invitation strip, present on some tiles in a row and not on others. The cost was stated then and
       the owner named it on 2026-09-02: on a strip-less tile the price sat 10px lower than its
       neighbours' ("цифры гуляют"). The strip is gone (see .card-status), the tile is two fixed rows
       under a fixed two-line title, so there is no slack to distribute and nothing here to float.
       The one residue is reserved away instead: a priceless row ("Price N/A", "only in KR") is set in a
       smaller face, so its line box was 2–3px shorter than a priced one's — the 0–2px drift
       docs/PRICE_SURFACES.md measured after the status fix. The row is held at the price's own line box
       (1rem × 1.1) whatever it prints. */
    min-height: calc(1rem * 1.1);
}

.price-value[b-vvsottbycu] {
    font-weight: 800;
    font-size: 1rem;
    color: var(--tx);
}

/* 3C: on sale → the price itself reads warm (a real deal), one consistent colour across all sales
   (not a per-card gradient — keeps prices comparable). */
.price-value.on-sale[b-vvsottbycu] { color: var(--sale); }

.price-was[b-vvsottbycu] {
    font-size: 0.76rem;
    color: var(--mut);
    text-decoration: line-through;
    /* The only shrinkable thing on the price line: on a narrow tile the struck-through former price is
       the one item whose loss costs the reader nothing they cannot get from the sale chip below. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Neither the amount nor its label ever gives way — they are THE PAIR. */
.price-value[b-vvsottbycu],
.price-where[b-vvsottbycu] {
    flex: 0 0 auto;
}

/* WHERE THIS PRICE IS FROM — the region code and, when the price went through a gift card, the ticket.
   Pinned to the tile's right edge, so it lands in the same column on every card in a row exactly as the
   amount does on the left. `.card-where` earns its keep the same way on the status line, and for the
   same reason: a mark whose x moves with the width of the number beside it cannot be scanned down a grid.
   Its OWN gap is 3px rather than the line's 6, because these two are one statement about one place and
   at the line's spacing they read as two unrelated marks. */
.price-where[b-vvsottbycu] {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.price-stale[b-vvsottbycu] {
    font-size: 0.75rem;
    color: var(--warn);
    cursor: help;
}

/* PHONE. Below 480px the struck-through former price goes ENTIRELY — that is where a tile is 45vw (see
   the `sizes` attribute in the markup) and shrinking it stops being enough. Measured with a worst-case "199,99 €": a 170px tile
   ellipsises the was down to a 17px stub that says nothing, and a 150px one pushes the region chip 3px
   past the edge — the pair, which must never be what gives. The list row drops its own `.row-was` at
   760px on the same argument, and the sale chip under the price still carries the "−N% sale". */
@media (max-width: 480px) {
    .price-was[b-vvsottbycu] {
        display: none;
    }

    /* …and so does the staleness ⚠, for a different reason that lands at the same breakpoint: everything
       it MEANS is in a `title` tooltip, and a touch device cannot show one. It was costing 18px of the
       tightest line on the site to say nothing at all to the reader looking at it. The phone grid turns
       two-column here (Home.razor.css, same 480px), so a 320px phone gives a ~142px tile and this is
       what keeps the region chip inside it. */
    .price-stale[b-vvsottbycu] {
        display: none;
    }

    /* THE INVITATION'S AMOUNT IS BACK (2026-09-09), and its removal is worth reading as a warning.
       It went on 2026-09-02 with a sound argument — a 142px tile has a 120px status line, and
       "🔥 −45%" beside "🔑 [HK] €13.44" needs ~150 — resting on a second clause: "the code still says
       WHERE and the KEY what it costs to get there". Both halves expired at once when the key went
       (2026-09-09): the 18.06px it freed is more than the amount needs, and the clause that made the
       loss tolerable had named the very element that was removed.
       By then it was not a missing number but a WRONG one. The line read `[BR] ~€3.5` under a lead
       price of €9.69, and a region beside a lone small figure reads as "BR costs €3.5" — owner, on a
       phone: "теперь на мобиле в нижней строчке вообще нет цены, только ~€3.5".
       Measured with it restored, 390px, 149px line: 11 of 12 invitations have 50.9–60.8px to spare.
       The twelfth is below. */

    /* …AND THE SAVING DOES NOT COME TO THE PHONE AT ALL, because the line cannot be made safe in every
       CURRENCY. This first shipped as "hide it only where a reason chip shares the line", which held for
       euros and nothing else: the reader's display currency decides the width, and `MX$1,499.00 −MX$120`
       is 158.36 on a 149px line — measured live, 9px over, and with `.card-status` clipping at its right
       edge that cuts the SAVING'S OWN DIGITS. `$279,900` and `69.90 CHF` clear it; MX$ does not, and a
       dearer game does not help.
       THE AMOUNT ALONE IS SAFE EVERYWHERE — the worst currency leaves 47px — so the phone keeps the
       labelled pair whole and gives up the addition. Nothing is lost that the reader cannot reach: the
       desktop tile has 107–116px of room for it, and the list row's second column GROWS with its content
       (measured 79 → 117px on the same phone, no overflow anywhere).
       A rule that survives on the reader's currency is not a rule. Two states shipped tonight that fit
       by 0.06px and −0.41px and both came straight back; this one is not close to an edge. */
    .sa-save[b-vvsottbycu] {
        display: none;
    }
}

.price-unavailable[b-vvsottbycu] {
    color: var(--mut);
    font-size: 0.85rem;
}

/* "only in KR" — this row has no price because the READER's lens took out the only regions that sell
   it, which is a different fact from "nobody sells this" and gets a different sentence. Same muted ink as
   the "Price N/A" it replaces: neither is bad news, both are just an absence, and colouring one of them
   would rank two absences against each other. The explanation is a tooltip, and it points at the
   "Regions to compare" sheet rather than restating why the region is out. */
.price-off[b-vvsottbycu] {
    color: var(--mut);
    font-size: 0.8rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: help;
}

/* THE SECOND ANSWER — "🔑 [HK] €13.44", "needs an account" or "−43% vs DE": what the price line cannot
   state about ITSELF, ONE object at a time (the priority is in the markup). The region code and the 🎟
   ticket used to live here, a whole line below the amount they qualify; they moved onto the price itself
   (see .card-price), which is the arrangement the list row has always had. Right-packed on the status
   line, so the chip stays left and this stays in a column of its own across a row of tiles — the same
   left-edge / right-edge rule .card-price applies to the amount and its label. */
.card-where[b-vvsottbycu] {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 6px;
    /* THE SECOND ANSWER DOES NOT GIVE; THE SALE CHIP'S TAIL DOES. Both this and `.card-reason` shipped
       shrinkable, so a deficit split between them and the deficit is real: the invitation with its
       saving is 61.3px, and a chip reading "📉 −100% · 12mo" is 103 against a 149px line — 21px short.
       Split, that clips the region chip and the amount, which is the one thing this line exists to
       protect and exactly how `.sa-via` failed on prod. Pinned here so the whole deficit lands on
       `.reason > .rt`, which is BUILT to ellipsise: the worst case becomes "📉 −100% · 12m…", losing a
       character of "how long ago" — an approximation already — instead of a figure. */
    flex-shrink: 0;
    white-space: nowrap;
    min-width: 0;
    overflow: hidden;
    /* line-height 1 tightens each item's line box to its ink, so this text centres against the reason
       chip's box beside it instead of floating above it (inherited 1.5 pushed the text up). */
    line-height: 1;
}

.rc[b-vvsottbycu] {
    font-family: var(--fn);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    border: 1px solid var(--ln);
    border-radius: 5px;
    padding: 1px 5px;
    background: var(--sf2);
    color: var(--tx2);
    flex: 0 0 auto;
}

/* At ~10px the font-metric fix in fonts.css can't finish the job: Chrome snaps such a small text's
   baseline to a whole pixel, so the leftover leading cannot be split evenly and the code sat ~1px
   high. Trimming the box to the cap box removes the leading altogether, which makes centring exact
   and independent of font-size — no magic numbers to re-tune. Behind @supports because Firefox
   doesn't implement it yet; there the padding above still applies and nothing regresses. */
@supports (text-box: trim-both cap alphabetic) {
    .rc[b-vvsottbycu] {
        text-box: trim-both cap alphabetic;
        padding: 3px 5px;
    }
}

/* A price the reader cannot pay yet: the region is real, the number is real, the ACCOUNT is missing.
   Dashed rather than recoloured, and deliberately NOT --warn (spent on stale prices) or --region (spent
   on "not your region, reachable by card"): three different facts must not share one signal, which is the
   confusion the UnsoldRegions/HiddenRegions split was made to end. Dashed reads as provisional, and the
   words beside it carry the meaning — the colour is only there to stop the code looking settled. */
.rc-need[b-vvsottbycu] {
    border-style: dashed;
    border-color: var(--mut);
    background: transparent;
    color: var(--mut);
}

.need-tag[b-vvsottbycu] {
    font-size: 0.68rem;
    font-weight: 700;
    color: var(--mut);
    overflow: hidden;
    text-overflow: ellipsis;
}

/* THE INVITATION, "🔑 [HK] €13.44" — what ONE more account would get them, as the second answer on the
   status line (see .card-where). It was a line of its own for a week: the T2 strip under a hairline, bled
   to both edges, the saving on the right rail. The LINE is what the owner asked to remove on 2026-09-02.
   Present on 39% of tiles for a home-only reader and 12% for one holding accounts (180-tile prod feeds,
   €3 threshold), it made the one tile in a row that carried it taller than its neighbours, and the grid
   stretched the other four over a hole. Reserving it on every tile was drawn and measured first: EMPTY
   on 61% / 88% of tiles. docs/PRICE_SURFACES.md.
   Quiet by construction, as before: it must never out-shout the number above it, which is the one they
   can act on. The region takes the same dashed muted chip the price line uses for "needs an account", so
   that fact is stated one way everywhere; the amount is muted; the key says which STEP is asked for (🔑 a
   new account, 🎟 a gift-card habit). What it no longer prints: the saving and the 🔥 — the price directly
   under the lead price says the same thing, and a percentage with no named baseline reads as broken
   arithmetic (docs/PRICE_REACH.md); the baseline stays in the tooltip. The CouponMark beside a "mine"
   amount keeps its 0.95rem: that is the size its own note measured as legible, and it matches the code
   chip's height. */
/* BASELINE, NOT CENTRE — this line carries three type sizes and `center` aligns their BOXES, whose
   heights follow their own fonts (amount 11.52, saving 10.56, region chip 15.14 with its border). Centred
   that way the three baselines landed 674.16 / 674.63 / 674.48: a 0.47px spread, which Chrome then snaps
   to a whole device pixel and renders as a visible step — owner, 2026-09-09: «~€X.XX на пиксель-два
   отцентрована ниже чем цена». The same defect the header's segments had on 2026-09-08, and the same
   fix: align what the eye actually reads, which is the baseline, not the box.
   The line's reserved height is unaffected — `.card-status`'s `min-height` is derived from the reason
   chip's icon slot (16.16px) and the tallest item here is the 15.14px region chip, so a baseline-aligned
   line still fits inside it. */
.sa[b-vvsottbycu] {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    min-width: 0;
}

.sa-key[b-vvsottbycu] {
    font-size: 0.64rem;
    opacity: 0.7;
    flex: 0 0 auto;
}

.sa-price[b-vvsottbycu] {
    font-family: var(--fn);
    font-weight: 700;
    font-size: 0.72rem;
    color: var(--mut);
    font-variant-numeric: tabular-nums;
    /* The one thing on the line that may give: the code survives, the amount ellipsises. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* WHAT THAT ACCOUNT WOULD SAVE, on the width the 🔑 gave back (2026-09-09). Green, because it is the
   one figure on this line that is an ARGUMENT rather than a fact — the same rule the list row's
   `.bt-save` follows, and the reason the lead line's own `−N% vs DE` is set in muted ink there.
   It is BOUNDED, which is the whole difference from `.sa-via` below: "~€3.5" is 29.28px and "~€48" is
   27.84, where a shop's name had no upper bound at all. So it needs no shrink priority — it either has
   its place on the line or it is not drawn, and it is never the thing that ellipsises. */
.sa-save[b-vvsottbycu] {
    font-family: var(--fn);
    font-size: 0.66rem;
    font-weight: 800;
    color: var(--deal);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Below the floor it states a BOUND, not an offer, and goes grey with it — "<€1" is the site saying it
   cannot tell a saving from its own conversion residue at that size. See `CurrencyFormat.Approx`. */
.sa-save.under[b-vvsottbycu] {
    color: var(--mut);
    font-weight: 700;
}

/* `.sa-via` STOOD HERE AND IS DELETED, 2026-09-08. It printed the shop the invitation's figure goes
   through, and the argument for it was sound — a phone has no hover, so the tooltip hides half of §2's
   cost of entry. What it could not survive was the WIDTH: a third item on a line already holding the
   key, the region and the amount, and the only one of them whose length is unbounded. It was shipped
   with `flex: 0 100 auto` so the name would absorb the whole deficit, and the owner's prod screenshot
   still read "BR €13… Mtcga…" — the amount clipped as well, which is exactly what the labelled pair exists to
   prevent. It is not a shrink-priority problem; there is no width to find. The shop stays in the
   invitation's tooltip and on the game page, which is where the older rule always put it
   (`.claude/rules/pricing-lens.md` § "Where the shop name appears"). Do not re-print it here without a
   NEW row to put it on, and the tile has no third row. */

/* `.sa-mine` (the "my region first" reading's own half on the tile) is DELETED with that reading,
   2026-09-07 — see PriceFacts.Row. It carried the solid region-coloured chip that told an invitation
   from a route the reader already holds, and the DISTINCTION survives without it: the tile now draws
   only the invitation in this slot, which is dashed, and there is no second, solid thing to tell it
   apart from. Deleted rather than left standing, because a rule matching nothing is a lie the next
   reader will believe (.card-strip, 2026-09-02, learned this the expensive way). */

.for-you[b-vvsottbycu] {
    /* The status line is set in the data face throughout — chip, code, amount — so its caption is too. */
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    font-weight: 800;
    color: var(--deal);
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The foot is the status line and nothing else. Still bottom-pinned, as a safety net rather than a
   layout: with a fixed two-line title, a price row held at its own line box and a status row held at the
   chip's, every tile's content is the same height and the grid has nothing to stretch — but if a future
   element ever gives one tile an extra pixel, this is what keeps the foot on the bottom edge across the
   row rather than letting that pixel land under it. */
.card-foot[b-vvsottbycu] {
    margin-top: auto;
    display: flex;
    flex-direction: column;
}

/* THE STATUS LINE, and the whole of what the deleted placeholder was for.
   It used to be filled with the words "No sales yet" whenever a tile had no sale and no history — 46.2%
   of the default feed measured on prod (480 cards: sale 25.8%, history 27.1%, placeholder 46.2%), 5 of 5
   on the Trending rail. The stated reason was geometry: "the tile status line is now ALWAYS FILLED", so
   the price and the foot would sit at one height across a row. It did not deliver that. A preorder or a
   coming-soon tile renders no status at all, and one of those in a row drags its neighbours apart —
   measured on prod at `q=xenoblade`, one row of five held prices at y=257, 257, 257, 270 and 305, i.e.
   48px of drift, on the very surface the words were defending.
   Reserving the HEIGHT does what the words could not: every tile keeps a status line's worth of space
   whether or not it has anything to say, so the baseline is constant for ALL of them, preorders
   included, and nothing has to claim a game has never been discounted to hold a row together.
   The height is the CHIP's own box, not a magic number, and the chip moved on 2026-09-02 (see .reason):
   its tallest item is now the .ri icon slot at 0.75em of 0.68rem, plus 3px padding and a 1px border on
   each side — 16.16px, beside the 15.1px region chip on the price line. The label is trimmed to its cap
   box (`text-box`) and so is never the tallest item, which is what lets the tile trim at all: the old
   note said the tile must NOT, because the reservation was derived from the label's line box and would
   have drifted from a trimmed chip. Derived from the icon slot, it cannot. Firefox (no text-box) gets
   the same 16.16px from the label's own line-height (see .reason > .rt).
   4px off the price: the row used to start on the price's line box, so the chip's tinted ground touched
   the digits (owner: "сильно прижимается к цене"). Even with the gap the tile is 2px shorter than it
   was with the 22.5px chip. */
.card-status[b-vvsottbycu] {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
    min-height: calc(0.68rem * 0.75 + 8px);
    overflow: hidden;
}

/* The chip's wrapper hugs it. As a plain div it laid the inline-flex chip on a TEXT baseline and the
   inherited strut added its descent below, so a tile carrying a chip stood ~5px taller than one carrying
   none — the reserved height above would have been reserving the wrong number. */
.card-status > .card-reason[b-vvsottbycu] {
    display: flex;
    align-items: center;
    min-width: 0;
}

/* THE CHIP IS SET IN THE REGION CHIP'S OWN VOCABULARY (owner, 2026-09-02: "очень большие поля… не
   консистентно с другими элементами"). It stood 22.4px tall in Nunito with 0.34em/0.6em padding and a
   6px radius, on a line whose other members are a 15px Manrope code chip and 11.5px captions, and its
   tinted ground began on the price's line box. Now: the data face, a 1px border on every tier, 5px
   radius, 3px/6px padding — .rc's rules restated at the chip's size — and the emoji slot shrunk to 0.75em
   so the DIGITS set the height, not the OS emoji glyph (which paints freely past its slot, as .ri
   allows). 16.16px. Padding stays symmetric on both axes for the reason the old note gave: the lopsided
   inset (0.4/0.6/0.26/0.4em) was compensating for metrics that are now fixed at their source (fonts.css,
   .ri in app.css), and measured +2.6px LOW once they were. */
.reason[b-vvsottbycu] {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    font-family: var(--fn);
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    padding: 3px 6px;
    border-radius: 5px;
}

/* The icon slot, scoped: app.css gives .ri `height: 1em` for chips whose TEXT sets the height. Here the
   slot is deliberately shorter than the glyph so it cannot be the reason the row grows — the emoji
   overflows it visibly (overflow: visible, app.css) and lands inside the chip's own padding. */
.reason .ri[b-vvsottbycu] {
    height: 0.75em;
}

.reason.sale[b-vvsottbycu] {
    background: color-mix(in srgb, var(--sale) 12%, transparent);
    color: var(--sale);
    /* Declared on the base so the three tiers differ ONLY in the two values that rank them; without it
       the deeper tiers would also gain a pixel of height and the chips would not line up down a list.
       VISIBLE at the lowest tier now: at 16px a tint alone has no edge, and the region chip beside it is
       bordered — one vocabulary. */
    border: 1px solid color-mix(in srgb, var(--sale) 22%, transparent);
}

/* 3C: sale chip intensity scales with discount depth — and the FILL is no longer what scales.
   It used to end in a solid --sale with white ink at >=50%, which on a list of deep discounts was a row
   of shouting orange labels (owner, 2026-08-27: "very bright in dark mode, especially in the list, and
   bright in light mode too"). Deepening the tint instead is not the fix either: the ink is --sale itself,
   so a heavier ground REDUCES its contrast — 15% tint gives about 4.3:1 on white and 32% about 3.3:1.
   So the tint stays shallow and a BORDER does the ranking. Same three steps, a third of the volume, and
   the deepest tier is now the most legible rather than the least. */
.reason.sale.tier-mid[b-vvsottbycu] {
    background: color-mix(in srgb, var(--sale) 16%, transparent);
    border-color: color-mix(in srgb, var(--sale) 34%, transparent);
}

.reason.sale.tier-hi[b-vvsottbycu] {
    background: color-mix(in srgb, var(--sale) 22%, transparent);
    border-color: color-mix(in srgb, var(--sale) 60%, transparent);
}

/* THE CHIP'S WORDS LIVE IN A SPAN, and that span is load-bearing — do not "simplify" it away.
   `.reason` is an inline-flex row, so a bare text node beside the 🔥/📉 span is an ANONYMOUS flex item,
   and an anonymous item does not centre the way a real one does: measured live on prod, the words sat
   **4.0px high** in a 22.4px pill while the emoji beside them was centred to within 0.01px — two halves
   of one chip on two different optical lines (owner, 2026-08-30: "the −20% chips and the chart chip are
   badly centred"). Wrapping the label makes it a real flex item: 4.01px → 0.02px, same width, same
   height, and NO css property does the work — which is exactly why the span reads as removable.
   `text-box: trim-both cap alphabetic` also centres it, and since 2026-09-02 it IS used here: the
   reservation in `.card-status` is derived from the icon slot, not from this label's line box, so
   trimming the label cannot make the chip and the reserve disagree — which is the reason the old note
   forbade it. Without text-box (Firefox) the line-height below gives the label the same 0.75em box the
   slot has, so the chip is 16.16px in both engines; the caps then centre to within the pixel snap the
   row's own note describes.
   The rule below is the second thing the span buys — real ellipsis on a long label, which a flex
   container cannot do to its own children. */
.reason > .rt[b-vvsottbycu] {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 0.75;
    /* CLIP ROOM. `overflow: hidden` clips at the PADDING box, and with the label trimmed to its cap box
       that box is exactly cap-height tall — but the ink is not. Read off the Manrope file itself at
       weight 800 (fontTools, 2026-09-02): the round bowls and the % reach 0.16px below the baseline,
       j / l / % / 0 rise 0.16px above the cap line, and the DESCENDERS go much deeper — g 2.77px, j / p /
       y / q / у / р 2.61px, д / ц / щ 1.31px at this 10.88px size. The first cut of this rule gave 2px
       and the owner saw the g's tail shaved on every language that has one ("7 g" it, "Angebot" de,
       "7 j" fr, "у" ru); English labels have no descenders, which is why the tile looked clean. Three
       pixels below covers the deepest glyph; two above is still ten times what the tallest needs. The
       matching negative margins keep the LAYOUT box at cap height, so the chip's height and the row's
       reservation are untouched. The chip's own 3px padding is exactly the room this needs — do not
       shrink it. */
    padding: 2px 0 3px;
    margin: -2px 0 -3px;
}

@supports (text-box: trim-both cap alphabetic) {
    .reason > .rt[b-vvsottbycu] {
        text-box: trim-both cap alphabetic;
        line-height: 1;
    }
}

/* Sale history = the quietest status: neutral grey, clearly a *past* deal, deliberately not competing
   with the warm (sale) reason above it. `.reason.nosale` sat here too and went with the placeholder. */
.reason.history[b-vvsottbycu] {
    background: var(--sf2);
    color: var(--mut);
    /* A border the dark theme actually draws. On --sf2 over --sf (#1B1B27 on #15151E) with a --ln hairline
       the box was all but invisible, so its 6px inset read as the LABEL standing 7px right of the price's
       edge (owner, 2026-09-02: "слишком далеко вправо от края уезжает — так нужно?"). The box begins at the
       price's own x — measured 708 = 708 — and a border the eye can find is what lets that be seen; the
       sale chip beside it already has one, tinted. Still the quietest status: muted ink, neutral ground. */
    border: 1px solid color-mix(in srgb, var(--mut) 32%, transparent);
    font-weight: 700;
    /* It is the one chip that can be long ("−75% · 3mo") and it shares a line with the notes on the
       right, so it is the thing that gives — its LABEL ellipsises, see `.reason > .rt`. `min-width` here
       is what lets the chip itself shrink at all. NOT `display: inline-block`, which was tried and
       measured: it drops the chip out of flex layout and the leading 📉 then sets the line box, so the
       chip stood 28px against the sale chip's 20 and .card-status varied by tile again, i.e. exactly the
       drift this whole change is about. */
    min-width: 0;
    white-space: nowrap;
}

/* The gift-card-top-up marker is <CouponMark />; it carries its own .cpn styling. Scoped CSS could
   not reach it from here anyway — a child component's elements get the child's scope id, not ours. */
/* /Components/GameRow.razor.rz.scp.css */
/* Dense list row. The parent page wraps rows in a .games-list surface card; rows separate with
   hairlines. Columns: [cover+title flexes] [right cluster 322px]. The "why cheap" reason
   chip lives inside the price cluster (right) so it sits with the price it explains, rather than
   floating in a centred column.

   The hover actions used to be a THIRD column of their own, and that was the bug: they are
   opacity:0 until the row is hovered, so every row at rest ended with a 104px + 14px band of
   nothing and the price cluster floated 118px short of the card's right edge (measured live).
   Price and actions now share one right-hand flex cell whose fixed width keeps the title column
   exactly as wide as before (98px action strip + 14px gap + 210px price = 322px) — so nothing
   reflows when the actions fade in, and the price ends flush with the row's 14px padding,
   mirroring the cover on the left. */

.game-row[b-igeg2nyqxc] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 322px;
    gap: 14px;
    align-items: center;
    padding: 8px 14px;
    text-decoration: none;
    color: inherit;
}

.game-row + .game-row[b-igeg2nyqxc] {
    border-top: 1px solid var(--ln);
}

.game-row:hover[b-igeg2nyqxc] {
    background: var(--sf2);
    color: inherit;
}

/* Column 1: cover + text stacked side by side inside one flex cell. */
.row-main[b-igeg2nyqxc] {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

.row-cover[b-igeg2nyqxc] {
    width: 46px;
    height: 46px;
    flex: 0 0 auto;
    border-radius: 9px;
    overflow: hidden;
    background: var(--sf2);
    border: 1px solid var(--ln);
}

.row-cover img[b-igeg2nyqxc] {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.row-text[b-igeg2nyqxc] {
    min-width: 0;
}

/* Title + its badge. The badge is a SIBLING of the clamped/ellipsised title box rather than the last
   thing inside it, because either truncation mechanism (nowrap+ellipsis here, -webkit-line-clamp on
   phones) cuts the tail of the flow — and the badge was the tail, so it vanished on precisely the
   long titles it was labelling. The title box shrinks to its content (flex: 0 1 auto), so a short
   name still has the badge right after the words; a clipping one keeps it on the first line. */
.row-titleline[b-igeg2nyqxc] {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.row-title[b-igeg2nyqxc] {
    display: block;
    flex: 0 1 auto;
    min-width: 0;
    font-size: 0.9rem;
    font-weight: 800;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Muted block colour so the truncation ellipsis matches the greyed suffix when a title clips
       inside " — Switch 2 Edition"; the actual name is repainted to --tx by .title-main below. */
    color: var(--mut);
}

.row-title .title-main[b-igeg2nyqxc] {
    color: var(--tx);
}

/* M.4: mute the redundant "— Switch 2 Edition" suffix (the SW2 badge already says the generation). */
.title-suffix[b-igeg2nyqxc] {
    color: var(--mut);
    font-weight: 700;
}

/* Two copies exist (title line + sub line); exactly one is displayed per breakpoint. The selector
   is deliberately two-deep: `.row-badge` below sets display:inline-block at the same specificity
   and comes later in the file, so a bare `.row-badge-sub` lost and BOTH copies rendered. */
.row-sub > .row-badge-sub[b-igeg2nyqxc] {
    display: none;
}

/* The sub-line's chunk separators are for text chunks — a badge is not one, so it takes no dot.
   (The phone-only leading-separator variant is neutralised inside the media query below, where
   source order lets it win.) */
.row-sub > .row-badge[b-igeg2nyqxc]::after,
.row-sub > .row-badge[b-igeg2nyqxc]::before {
    content: none;
}

.row-badge[b-igeg2nyqxc] {
    flex: 0 0 auto;   /* never the thing that gives way when the line is tight */
    display: inline-block;
    font-family: var(--fn);
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    border-radius: 6px;
    padding: 1px 6px;
    background: var(--sw2-bg);
    color: var(--sw2-tx);
    border: 1px solid var(--ln);
    text-transform: uppercase;
}

/* An uppercase chip centres its CAP BOX, not its line box — the family rule, stated in full in
   `GameDetail.razor.css` above `.via-tag`. Measured 0.67px high here, the same as the game page's own
   chips: the badge is the one member of the family that lives on the FEED, and leaving it would put
   two centrings of the same chip on one site. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(12px - 1cap)) {
    .row-badge[b-igeg2nyqxc] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(12px - 1cap);
    }
}

.row-sub[b-igeg2nyqxc] {
    display: block;
    font-size: 0.75rem;
    color: var(--mut);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-top: 1px;
}

/* Trailing separators live INSIDE each chunk so a display:none chunk takes its dot with it. */
.row-sub > span:not(:last-child)[b-igeg2nyqxc]::after {
    content: '·';
    margin: 0 5px;
    color: var(--mut);
}

.sub-rating[b-igeg2nyqxc] {
    color: var(--gold);
}

.sub-release[b-igeg2nyqxc] {
    color: var(--info);
    font-weight: 800;
}

/* Savings vs your home region — phone-only copy that rides next to the region reason (desktop shows
   it as ".for-you" in the right price cluster instead). Green = a saving, mirrors ".for-you". */
.sub-savings[b-igeg2nyqxc] {
    display: none;
    color: var(--deal);
    font-weight: 800;
}

/* Home-price copy for phones only — same arrangement as .sub-savings: the right cluster is too tight
   there for a second figure, and the sub-line has the width. Exactly one of the two copies renders. */
.sub-athome[b-igeg2nyqxc] {
    display: none;
}

/* Reason copy for phones only (the desktop reason column is hidden ≤760px). */
.sub-reason[b-igeg2nyqxc] {
    display: none;
    font-weight: 800;
}

.sub-reason.sale[b-igeg2nyqxc] {
    color: var(--sale);
}

/* O.7 "why is it cheap" chip — the top line of the right-hand price cluster (right-aligned by
   .row-price's text-align). ONE kind left, warm: an eShop sale. The cool "cheap region" chip was deleted
   as a repeat of the "−N% vs DE" caption beside it (see GameRow.ReasonKind); the --region token itself
   still earns its keep on .rc.away and the invitation's own code. */
.row-price .reason[b-igeg2nyqxc] {
    margin-bottom: 3px;
}

/* …except in a family list (GameRow.InFamilyList), where it shares the price's line instead of
   standing above it. In a FEED a row is the whole product and the chip earns its own line; a game
   page's add-on list is a table of one family, and a third line there cost 26px on a 62px row — a
   sale row measured 88px against 62px for one without, so the list's rows were not even the same
   height as each other, which is what makes a table hard to scan. Nothing is lost: the chip's figure
   and the struck-through price sit side by side saying the same thing.
   The GEOMETRY that delivers this is the flex row on `.row-price.compact` further down; only the
   margin is left here. The two rules
   that used to do it — `margin-right` plus `vertical-align: middle`, and `display: inline` on a
   `.price-line` element that variant A deleted — were inline-layout rules that stopped applying the
   day the price became a grid, and a rule that matches nothing is how the regression hid. */
.row-price.compact .reason[b-igeg2nyqxc] {
    margin-bottom: 0;
}

/* Restated in the region chip's vocabulary together with the tile's chip on 2026-09-02 (the measurement
   is on GameCard.razor.css .reason): the data face, a 1px border on every tier, 5px radius, 3px/6px
   padding, the icon slot at 0.75em so the digits set the height. ONE SIZE ON BOTH SURFACES, 0.68rem.
   It kept 0.72rem for a day and the owner saw its label sitting HIGH in the pill where the tile's did not
   (2026-09-02, the "−50 % · 7 g" row). The two pills differ only in size, and size is the whole story:
   at 0.72rem the cap box centres with its baseline 12.46px below the pill's top, which Chrome snaps to
   the whole pixel above and lifts the text ~0.5px; at 0.68rem the baseline lands on 12.0px exactly and
   nothing moves. The same snap the `.pricepair .pp-lbl .rc` note records, from the other side. One
   geometry also means one set of measurements to keep true, the row's own captions are 0.7rem so the
   chip does not fall out of the line, and the German label loses 6px it needed (see SaleChipText). */
.reason[b-igeg2nyqxc] {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    max-width: 100%;
    font-family: var(--fn);
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    padding: 3px 6px;
    border-radius: 5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.reason .ri[b-igeg2nyqxc] {
    height: 0.75em;
}

.reason.sale[b-igeg2nyqxc] {
    background: color-mix(in srgb, var(--sale) 12%, transparent);
    color: var(--sale);
    /* Declared on the base so the three tiers differ ONLY in the two values that rank them; without it
       the deeper tiers would also gain a pixel of height and the chips would not line up down a list.
       Visible at the lowest tier now, like the tile's: at 16px a tint alone has no edge. */
    border: 1px solid color-mix(in srgb, var(--sale) 22%, transparent);
}

/* 3C: sale chip intensity scales with discount depth — and the FILL is no longer what scales.
   It used to end in a solid --sale with white ink at >=50%, which on a list of deep discounts was a row
   of shouting orange labels (owner, 2026-08-27: "very bright in dark mode, especially in the list, and
   bright in light mode too"). Deepening the tint instead is not the fix either: the ink is --sale itself,
   so a heavier ground REDUCES its contrast — 15% tint gives about 4.3:1 on white and 32% about 3.3:1.
   So the tint stays shallow and a BORDER does the ranking. Same three steps, a third of the volume, and
   the deepest tier is now the most legible rather than the least. */
.reason.sale.tier-mid[b-igeg2nyqxc] {
    background: color-mix(in srgb, var(--sale) 16%, transparent);
    border-color: color-mix(in srgb, var(--sale) 34%, transparent);
}

.reason.sale.tier-hi[b-igeg2nyqxc] {
    background: color-mix(in srgb, var(--sale) 22%, transparent);
    border-color: color-mix(in srgb, var(--sale) 60%, transparent);
}

/* THE CHIP'S WORDS LIVE IN A SPAN, and that span is load-bearing — do not "simplify" it away.
   `.reason` is an inline-flex row, so a bare text node beside the 🔥/📉 span is an ANONYMOUS flex item,
   and an anonymous item does not centre the way a real one does: measured live on prod, the words sat
   **4.0px high** in a 22.4px pill while the emoji beside them was centred to within 0.01px — two halves
   of one chip on two different optical lines (owner, 2026-08-30: "the −20% chips and the chart chip are
   badly centred"). Wrapping the label makes it a real flex item: 4.01px → 0.02px, same width, same
   height, and NO css property does the work — which is exactly why the span reads as removable.
   `text-box: trim-both cap alphabetic` also centres it, and is used here as well (below); the tile does
   the same since 2026-09-02, now that its reservation is derived from the icon slot rather than from this
   label's line box. The line-height is the fallback for engines without text-box: it gives the label the
   same 0.75em box the icon slot has, so the chip is one height in both.
   The rule below is the second thing the span buys — real ellipsis on a long label, which a flex
   container cannot do to its own children. */
.reason > .rt[b-igeg2nyqxc] {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 0.75;
    /* Clip room, as on the tile: the trimmed cap box is what `overflow: hidden` clips to, and the ink is
       deeper — descenders reach 2.8px below the baseline in Manrope 800 at this size (g, j, p, y, у, р;
       the numbers are on GameCard.razor.css .rt). 2px, the first cut, shaved the g in "7 g" / "Angebot"
       / "7 j" / "у" — every language but English. Three below, two above, cancelled by the margins so the
       layout box stays at cap height. */
    padding: 2px 0 3px;
    margin: -2px 0 -3px;
}

/* …and here, unlike on the tile, the wrap alone does not finish the job. Measured: the tile's 0.68rem
   chip centres to 0.02px on the span alone, this one's 0.72rem to 0.91px — Chrome snaps a small text's
   baseline to a whole pixel, the same effect `.pricepair .pp-lbl .rc` documents. Trimming to the cap box
   closes it (−0.05px). It used to cost 2.3px of chip height, which was free HERE and was exactly why the
   tile did NOT do this; since 2026-09-02 the icon slot sets both chips' height, the trim costs nothing,
   and the tile trims too. Behind @supports because Firefox has no text-box yet; there the 0.91px stands. */
@supports (text-box: trim-both cap alphabetic) {
    .reason > .rt[b-igeg2nyqxc] {
        text-box: trim-both cap alphabetic;
        line-height: 1;
    }
}

/* Sale-history chip = quiet neutral grey (a *past* deal, not an active one). */
.reason.history[b-igeg2nyqxc] {
    background: var(--sf2);
    color: var(--mut);
    /* Same border as the tile's history chip: a hairline --ln on --sf2 vanished in the dark theme and the
       chip's inset read as the label sitting off its edge. */
    border: 1px solid color-mix(in srgb, var(--mut) 32%, transparent);
    font-weight: 700;
}

/* Right-hand cluster: [hover actions][price]. Packed to the end so the price sits on the row's edge
   and the icons appear immediately beside it (rather than in a fixed slot far from a narrow price). */
.row-right[b-igeg2nyqxc] {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 14px;
    min-width: 0;
}

/* Price column — right-aligned, never wraps. min-width:0 lets a long reason chip ellipsise (it used
   to be capped by the 210px grid column) instead of widening the cluster into the title. */
.row-price[b-igeg2nyqxc] {
    min-width: 0;
    text-align: right;
    white-space: nowrap;
}

/* —— A FAMILY LIST IS ONE HEIGHT, and this is what actually delivers it.
   The rules below (.row-price.compact .reason) were written when the price was inline content and they
   put the chip on the price's own line by nudging margins. Variant A then made the price a GRID
   (.pricepair), i.e. a block box, so the inline chip in front of it was pushed onto a line of its own
   again — silently, because the old rules still matched and still looked right in the file. Measured on
   PROD, the game page's own add-on list: rows 67 / 68 / 62px tall, the chip above the price on 7 of the
   7 rows that carry one, against a stylesheet that says in three places they must be one height.
   A flex row is the fix the feed's own variant D already uses for exactly this, and it cannot rot the
   same way: the pair is a flex ITEM here, so its display type is its own business. */
.row-price.compact[b-igeg2nyqxc] {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
}

.row-price.compact .pricepair[b-igeg2nyqxc] {
    flex: 0 0 auto;
}

.row-price.compact .reason[b-igeg2nyqxc] {
    flex: 0 1 auto;
    min-width: 0;
}

.row-now[b-igeg2nyqxc] {
    font-size: 0.95rem;
    font-weight: 800;
}

/* 3C: warm sale price — one consistent colour across all sales (not a gradient). */
.row-now.on-sale[b-igeg2nyqxc] { color: var(--sale); }

.row-was[b-igeg2nyqxc] {
    font-size: 0.74rem;
    color: var(--mut);
    margin-left: 7px;
}

/* THE REGION CHIP — the same object as the tile's, restated because Blazor's scoped CSS cannot
   share a rule across two components. ONE vocabulary, TWO surfaces: a boxed two-letter code at
   ~10px, never body text. (It was three until 2026-09-02: the tile's invitation strip restated it
   a third time, smaller, and both went when the tile became two rows — the invitation now reuses
   the tile's own chip at its own size. docs/PRICE_SURFACES.md.)

   It was LOST for a fortnight and the way it was lost is worth keeping: variant A (2026-08-26)
   replaced the old `.row-where` line with `.pricepair`, and every rule scoped to the old container
   stayed behind — so the chip silently fell back to inherited 16px/400 with no box at all, next to
   a tile that still drew it properly (owner, 2026-08-27: "in the list they are big and have no
   background; on the tiles they are the nice smaller ones in an outline"). The dead `.row-where` /
   `.row-better` blocks go with this change: a rule that matches nothing is what let the regression
   hide in plain sight for a fortnight. */
.pricepair .pp-lbl .rc[b-igeg2nyqxc] {
    font-family: var(--fn);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    border: 1px solid var(--ln);
    border-radius: 5px;
    padding: 1px 5px;
    background: var(--sf2);
    color: var(--tx2);
    /* line-height 1 + symmetric vertical padding, so the chip's box hugs its text. Inheriting 1.5
       wrapped 9.9px text in a 14.9px line box, and Chrome snaps such small text's baseline to a
       whole pixel, so the spare leading could not be split evenly and the code sat ~1.5px HIGH. */
    line-height: 1;
    /* Every eShop code is exactly two letters and the face is proportional, so a column of boxed chips
       came out with a ragged left edge (measured: 852, 853, 859). A floor on the width makes all of
       them one size, which is what turns them into a column — and it is why this chip carries a
       min-width the tile's does not: the tile's sit in a LEFT-aligned row, where varying widths cost
       nothing, while this cell is right-aligned, so a wider chip pushes its own left edge outward.
       The floor has to clear the WIDEST code, not an average: measured on prod over all 36 codes at
       this size, MX is 28.0px, CO 27.7, NO 27.6, CH 27.5 — the old 26px floor left those six drifting
       1-2px left of the rest. In em, so it follows the chip's own font-size instead of needing a
       re-measure the next time that moves. */
    min-width: 2.85em;
    text-align: center;
    flex: 0 0 auto;
}

/* A price whose region needs an account the reader does not hold. Same three-way distinction as the
   tile's (see GameCard.razor.css): dashed = provisional, --warn stays with stale prices, --region
   stays with "not where you are". */
.pricepair .pp-lbl .rc-need[b-igeg2nyqxc] {
    border-style: dashed;
    border-color: var(--mut);
    background: transparent;
    color: var(--mut);
}

/* At ~10px the font-metric fix in fonts.css cannot finish the job — trimming the box to the cap box
   removes the leading altogether, which makes centring exact and independent of font-size. Behind
   @supports because Firefox has no text-box yet; there the padding above stands. */
@supports (text-box: trim-both cap alphabetic) {
    .pricepair .pp-lbl .rc[b-igeg2nyqxc] {
        text-box: trim-both cap alphabetic;
        padding: 3px 5px;
    }
}

/* ============================================================================
   THE LABELLED PAIR — variant A. Two grid rows, each "region -> amount", so the
   label is in a COLUMN rather than merely next to the number: no width, no
   wrap and no added element can separate them again. Right-aligned as a block,
   which is what keeps the amounts in a readable column down the list.
   ========================================================================== */
.pricepair[b-igeg2nyqxc] {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: end;
    align-items: baseline;
    gap: 2px 7px;
}

.pricepair .pp-lbl[b-igeg2nyqxc] {
    justify-self: end;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}

.pricepair .pp-amt[b-igeg2nyqxc] {
    justify-self: end;
    white-space: nowrap;
}

/* The saving sits inside the amount cell, immediately after the number it is a percentage OF — and it
   NAMES ITS BASELINE, because the sale chip on the same row carries a percentage too and the two are
   measured against different things (that listing's own former price, against the reader's own store).
   The discount keeps --sale, "a moment to act".
   NOT GREEN SINCE 2026-09-08, and that is the rule rather than a taste: this figure is a FACT — you are
   already ahead of your own store, nothing to do — while the invitation's `.bt-save` beside it is an
   ARGUMENT, an account you could open. Two greens per row, fifteen down a page of eight, and the one
   that asks for something had no way to stand out (owner: "зеленые проценты слишком перетягивают на себя
   внимание"). Green now marks the argument alone; the fact is set in the row's own muted ink. Same
   treatment the sale chips got on 2026-08-27 for the same complaint, one colour over. */
.pricepair .pp-amt .for-you[b-igeg2nyqxc] {
    margin-left: 5px;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--mut);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The baseline is quieter than the number it qualifies: it is the same fact on every row of the feed,
   so it should be readable without competing with the figure. */
.pricepair .pp-amt .fy-vs[b-igeg2nyqxc] {
    font-weight: 600;
    opacity: 0.75;
    /* The gap is a MARGIN, not a literal space in the markup: Blazor trims whitespace around an
       expression, so " vs DE" rendered as "−99%vs DE". Seen on screen, not in the source. */
    margin-left: 0.32em;
}

/* …and on a phone it does not fit beside the price at all. The tooltip carries it there, and the sale
   chip's own percentage is not on that line either, so nothing is left to confuse it with — the
   invitation's own figure stopped being a percentage on 2026-09-08, so on a phone this is now the ONLY
   percentage on the row. */
@media (max-width: 620px) {
    .pricepair .pp-amt .fy-vs[b-igeg2nyqxc] {
        display: none;
    }
}

/* THE LABEL RIDES WHERE THERE IS NO HEADER TO CARRY IT, and this is the mirror of `.feed-cols` in
   app.css — same 1180px, deliberately. The baseline is the same region on every row of the feed, so
   repeating it 25 times is ink for nothing (owner, 2026-09-08: "когда много раз подряд vs DE — начинает
   двоиться в глазах без видимой информативности"), and a column heading is the one place it can be said
   ONCE. But the heading only exists from 1180px up: between 620 and 1180 there are columns in the row
   and no header above them, which is exactly where the inline label has to stay. Change the breakpoint
   in one file and the percentage goes unlabelled in the gap — so they move together. */
@media (min-width: 1180px) {
    .pricepair .pp-amt .fy-vs[b-igeg2nyqxc] {
        display: none;
    }
}

/* "needs an account" spans both columns on its own line: it is a statement about the pair above it,
   not a third value, and at 0.66rem it does not fit beside a price without pushing the title. */
.pricepair .pp-note[b-igeg2nyqxc] {
    grid-column: 1 / -1;
    justify-self: end;
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--mut);
    white-space: nowrap;
}

/* Nothing in the second column. Only ever seen at the wide-desktop width, where the column exists and
   is headed; everywhere else the pair is one line and there is nothing to fill. NOT rendered at all on a
   `.one-col` row — see GameRow.HasSecondColumn. */
.pricepair .pp-none[b-igeg2nyqxc] {
    display: none;
    justify-self: end;
    color: var(--ln2);
    font-size: 0.8rem;
}

/* ---- the second pair: one account away. PALE carries the hierarchy, the DASHED chip carries the
   meaning. Muted grey alone reads as "stale" before it reads as "needs an account", and this palette
   already spends muted grey on stale prices — so the two marks work together or not at all. */
.pricepair .second .bt-key[b-igeg2nyqxc] {
    font-size: 0.62rem;
    opacity: 0.7;
}

.pricepair .pp-amt.second .bt-price[b-igeg2nyqxc] {
    font-family: var(--fn);
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--mut);
    font-variant-numeric: tabular-nums;
}

/* …and the saving stays GREEN even in the paled row. It is the only figure on that line which is an
   argument rather than a fact, and it is the reason anyone reads the second line at all — which is why
   it is now the ONLY green figure in the row (see `.for-you`). It states MONEY, approximately: what it
   is and why it is not a percentage is in `CurrencyFormat.Approx` and in the markup. */
.pricepair .pp-amt.second .bt-save[b-igeg2nyqxc] {
    margin-left: 5px;
    font-size: 0.7rem;
    font-weight: 800;
    color: var(--deal);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* BELOW THE FLOOR IT IS A BOUND, NOT AN OFFER — "<€1" is the site saying it cannot tell a saving from
   its own conversion residue at that size (1.17% of €70 is €0.82; see `CurrencyFormat.Approx`). Only a
   reader on the "mention everything" view sees these at all — the default gate is €3 — and for them the
   honest rendering is grey: green would be inviting them to open an eShop account, a new address and a
   second library, to save less than a euro. Measured on one dev page under that view: 10 of 17
   invitations were under €1, median €0.65. */
.pricepair .pp-amt.second .bt-save.under[b-igeg2nyqxc] {
    color: var(--mut);
    font-weight: 700;
}

/* `.bt-via` STOOD HERE AND IS DELETED, 2026-09-08 — the tile's `.sa-via` went with it, and the
   measurement is in `GameCard.razor.css`. The shop is in the tooltip. */

/* ---- the family list's second pair is the reader's OWN store, not an invitation. Same geometry, no
   key, no pale: it is a price they can pay today. */
.pricepair .pp-amt.home .at-home[b-igeg2nyqxc] {
    font-family: var(--fn);
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--tx2);
    font-variant-numeric: tabular-nums;
}

.pricepair .pp-amt.home .at-home.none[b-igeg2nyqxc] {
    font-family: inherit;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--mut);
}


/* The gift-card-top-up marker is <CouponMark />; it carries its own .cpn styling. Scoped CSS could
   not reach it from here anyway — a child component's elements get the child's scope id, not ours. */

/* Inside .pp-amt now, so it lands in the price's own track instead of at the row's right edge. */
.row-na[b-igeg2nyqxc] {
    color: var(--mut);
    font-size: 0.85rem;
}

/* "only in KR" — this row has no price because the READER's lens took out the only regions that sell
   it, which is a different fact from "nobody sells this" and gets a different sentence. Same muted ink as
   the "Price N/A" it replaces: neither is bad news, both are just an absence, and colouring one of them
   would rank two absences against each other. The explanation is a tooltip, and it points at the
   "Regions to compare" sheet rather than restating why the region is out. */
.row-off[b-igeg2nyqxc] {
    color: var(--mut);
    font-size: 0.8rem;
    cursor: help;
    /* The label is bounded in the TEXT (GameRow.OffComparisonLabel: two codes then +N), because a CSS
       cap cannot do it here — .pp-amt is justify-self:start, so it shrink-wraps and `max-width: 100%`
       resolves against the label itself. This is only the backstop for a long display of two codes. */
    display: inline-block;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
}

/* Actions — quiet until the row is hovered (desktop); phones use the game page instead.
   order:-1 puts the icon strip LEFT of the price while keeping it last in the DOM (the price is the
   row's information, the actions act on it). flex-shrink:0 so the strip never squeezes: when the
   cluster is tight it is the price box that gives, exactly as the old fixed columns behaved. */
.row-act[b-igeg2nyqxc] {
    order: -1;
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    opacity: 0;
    transition: opacity 0.15s;
}

.game-row:hover .row-act[b-igeg2nyqxc],
.game-row:focus-within .row-act[b-igeg2nyqxc] {
    opacity: 1;
}

/* ---- Phone compaction: cover | title+sub | price. Reason joins the sub-line. ---- */
@media (max-width: 760px) {
    .game-row[b-igeg2nyqxc] {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px;
        padding: 8px 12px;
    }

    .row-cover[b-igeg2nyqxc] {
        width: 44px;
        height: 44px;
    }

    .row-price .reason[b-igeg2nyqxc],
    .row-act[b-igeg2nyqxc] {
        display: none;
    }

    .sub-reason[b-igeg2nyqxc] {
        display: inline;
    }

    .sub-savings[b-igeg2nyqxc] {
        display: inline;
    }

    /* Phone hides .sub-pub, which sits mid/tail in the DOM, so the desktop trailing-dot rule strands
       a "·" after the last visible chunk (e.g. "🔥 −75% sale ·"). Switch to LEADING separators here:
       a display:none chunk drops its own leading dot, so nothing dangles. Desktop keeps trailing dots
       — there the hidden chunks are the FRONT reason/savings, which the trailing rule already eats. */
    .row-sub > span:not(:last-child)[b-igeg2nyqxc]::after {
        content: none;
    }

    .row-sub > span:not(:first-child)[b-igeg2nyqxc]::before {
        content: '·';
        margin: 0 5px;
        color: var(--mut);
    }

    /* ...but not right after the badge. It is a bordered chip, not a text chunk, so a dot between it
       and the next chunk reads as punctuation attached to nothing. Must sit AFTER the rule above:
       the two selectors have identical specificity, so source order is what decides. */
    .row-sub > .row-badge-sub + span[b-igeg2nyqxc]::before {
        content: none;
    }

    /* The savings STAYS at this width now. It used to be dropped here and re-rendered in the phone
       sub-line, which is how it ended up an orphan under the title once the chip it leaned on was
       deleted; in the pair it sits in its own amount cell beside the number it describes, and that is
       true at every width — variant A is one layout, not a desktop one with a phone copy.
       The home figure keeps its old move: in a FAMILY list it still has a sub-line copy of its own
       (.sub-athome), because those rows are height-locked and cannot grow a second pair. */
    .row-price.compact .pricepair .pp-lbl.home[b-igeg2nyqxc],
    .row-price.compact .pricepair .pp-amt.home[b-igeg2nyqxc] {
        display: none;
    }

    .sub-athome[b-igeg2nyqxc] {
        display: inline;
    }

    .sub-pub[b-igeg2nyqxc] {
        display: none;
    }

    .row-now[b-igeg2nyqxc] {
        font-size: 0.88rem;
    }

    .row-was[b-igeg2nyqxc] {
        display: none;
    }

    /* The badge swaps from the title line to the sub-line, handing the name the full row width. */
    .row-badge-inline[b-igeg2nyqxc] {
        display: none;
    }

    .row-sub > .row-badge-sub[b-igeg2nyqxc] {
        display: inline-block;
        /* it leads the sub-line, so only a right gap */
        margin-right: 6px;
        vertical-align: 1px;
    }

    /* M.4: let long names wrap to two lines on phones instead of clipping. */
    .row-title[b-igeg2nyqxc] {
        white-space: normal;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
    }

    /* Nothing renders under the title (no reason chip, release, or stars) → give the name a third
       line so it fills the cover's height and stays centred next to it (.row-main align-items:center),
       instead of a 2-line clamp that leaves a gap below. */
    .row-title.no-sub[b-igeg2nyqxc] {
        -webkit-line-clamp: 3;
    }
}

/* ============================================================================
   VARIANT D — the wide desktop only (owner, 2026-08-26: "the desktop list looks
   very empty, this would save it"). The SAME markup as variant A: the pair's two
   grid rows unfold into four columns, so nothing is re-templated and the phone
   and the tile keep the layout they already have.

   Column widths come from `--pp-lbl` / `--pp-amt` on `.games-list`, which is
   also what `FeedListHeader` measures itself against — ONE source, or the header
   and the rows would drift apart by a pixel per release. The `.game-row` grid's
   right column widens to match, which is where the emptiness went.
   ========================================================================== */
@media (min-width: 1180px) {
    .game-row[b-igeg2nyqxc] {
        grid-template-columns: minmax(0, 1fr) var(--row-right-w, 430px);
    }

    /* Four columns on one line: label, amount, label, amount. Fixed widths, so the amounts line up
       down the whole list instead of only inside their own row. */
    .row-price:not(.compact) .pricepair[b-igeg2nyqxc] {
        grid-template-columns:
            var(--pp-lbl) var(--pp-amt)
            var(--pp-lbl2) var(--pp-amt2);
        column-gap: 0;
        align-items: center;
    }

    .row-price:not(.compact) .pricepair .pp-lbl[b-igeg2nyqxc] {
        justify-self: end;
        padding-right: 6px;
    }

    /* THE AMOUNT STARTS WHERE ITS LABEL ENDS — left-aligned inside its track, not pushed to the far
       edge of it. The track is sized for the WIDEST row (price + struck-through was + "−50% vs DE",
       176px measured), so with the content right-aligned an ordinary row put ~130px of dead air
       between its own region chip and its own price: measured 112-160px across one page, with the
       chip stranded in a rigid column at x=798 and the price wobbling between 939 and 986. The owner
       saw both halves of that at once — "the chips are very far to the left of the prices", and a
       stripe of identical boxes beside them that "ripples".
       Left-aligned, the gap is the label cell's own 6px, and — the part the old comment above claimed
       and never delivered — every price now starts at the SAME x down the whole list, because what
       lines up is the number itself rather than the trailing caption behind it. The widest real
       content still fits: 176px inside a 184px track. */
    .row-price:not(.compact) .pricepair .pp-amt[b-igeg2nyqxc] {
        justify-self: start;
        padding-right: 6px;
    }

    /* The rule between the two columns states what the pale ink implies: two populations, not four
       values. A hairline rather than a gap, because a gap at this width reads as a wrap.
       `align-self: stretch` is what makes it a RULE: the cells are `justify-self: end`, so without it the
       border is only as tall as the two characters beside it and disappears down a list of 25 rows. */
    .row-price:not(.compact) .pricepair .pp-lbl.second[b-igeg2nyqxc],
    .row-price:not(.compact) .pricepair .pp-lbl.home[b-igeg2nyqxc] {
        border-left: 1px solid var(--ln2);
        padding-left: 10px;
        align-self: stretch;
        align-items: center;
        /* STRETCH, overriding the base rule's `end`: shrink-wrapped, the cell's left edge — and therefore
           the rule drawn on it — moved with the width of the region code inside it, so the separator sat
           at 1045 on one row and 1041 on the next. 4px of jitter down 25 rows reads as a wobble, and the
           whole point of a rule is that the eye can trust it. Content stays right-aligned inside. */
        justify-self: stretch;
        justify-content: flex-end;
    }

    /* The placeholder takes the whole second group, rule included, so an empty column still reads as a
       column rather than as the row stopping early.
       `justify-self: stretch` is load-bearing and overrides the base rule's `end`: shrink-wrapped, the
       cell was only as wide as the dash, which put its border 126px to the right of the border on rows
       that DO have an invitation — measured, 1171 against 1045, and a rule that moves down the list is
       worse than no rule. The margin and padding are the second pair's own, so the two land on one pixel. */
    .row-price:not(.compact) .pricepair .pp-none[b-igeg2nyqxc] {
        display: flex;
        align-items: center;
        justify-self: stretch;
        grid-column: 3 / -1;
        border-left: 1px solid var(--ln2);
        align-self: stretch;
        margin-left: 6px;
        padding-left: 10px;
    }

    /* ONE LINE PER ROW, and the reason chip moves BESIDE the columns instead of above them.
       Measured before the change: a row carrying a sale chip stood 71px against 63px for one without, and
       the region chip sat 39px down its row against 20px — so the DE/JP/US column visibly swung row to row
       and half of them read as pushed to the bottom (owner, 2026-08-26). Stacking a chip above a price
       cannot avoid that; taking it out of the vertical flow can.
       `margin-right: auto` pins the chip to the LEFT of the cluster while the pair stays right, so the chip
       lands at a constant x too and fills the horizontal void that made this list look empty in the first
       place. Same move `.row-price.compact` already makes in a family list, and for the same reason. */
    .row-price:not(.compact)[b-igeg2nyqxc] {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: 10px;
        /* Fills the rest of the cluster, so its own left edge is constant and the chip pinned there by
           `margin-right: auto` lands at a constant x too. Sized to content it moved with the chip's own
           width — 730 on one row, 748 on the next. */
        flex: 1 1 auto;
    }

    .row-price:not(.compact) .reason[b-igeg2nyqxc] {
        margin: 0 auto 0 0;
        flex: 0 1 auto;
        min-width: 0;
    }

    /* The columns never give way: their widths are what the header above is aligned to. */
    .row-price:not(.compact) .pricepair[b-igeg2nyqxc] {
        flex: 0 0 auto;
    }

    /* A touch more air between the two column groups than the hairline alone gives — at 0 gap the first
       amount and the second pair's key glyph read as one run of characters. */
    .row-price:not(.compact) .pricepair .pp-lbl.second[b-igeg2nyqxc],
    .row-price:not(.compact) .pricepair .pp-lbl.home[b-igeg2nyqxc] {
        margin-left: 6px;
    }

    /* "needs an account" describes YOUR price, so it sits under your two columns rather than across all
       four — spanning the row would put it under the invitation and read as a note about that instead. */
    .row-price:not(.compact) .pricepair .pp-note[b-igeg2nyqxc] {
        grid-column: 1 / 3;
        padding-right: 6px;
    }

    /* ------------------------------------------------------------------------------------------
       ONE COLUMN — the reader who has never named a home region. Variant D above assumes two price
       groups and reserves both; for this reader the second can NEVER be filled (no home ⇒ no reach
       split ⇒ no invitation, no "at home"), and the headings that would name it are not drawn either,
       because FeedListHeader is silent for exactly the same reason. So the row was reserving 160px per
       row for an em-dash under nothing, plus an amount track sized for a "−N% vs DE" caption that
       cannot exist without a home region. Both go, and the price lands on the row's right edge, which
       is what the original comment at the top of this file says the right cluster is for.
       Measured, 1400px viewport / 1166px row, prod data: right cluster 638 → 380px, the empty band
       after the price 285 → ~40px. Nothing else changes — same markup, same tracks, same chip column.
       ---------------------------------------------------------------------------------------- */
    .game-row.one-col[b-igeg2nyqxc] {
        grid-template-columns: minmax(0, 1fr) var(--row-right-w-1, 380px);
    }

    .game-row.one-col .row-price:not(.compact) .pricepair[b-igeg2nyqxc] {
        grid-template-columns: var(--pp-lbl) var(--pp-amt1);
    }
}
/* /Components/LibraryActions.razor.rz.scp.css */
.lib-actions[b-jjbhygfhh2] {
    display: flex;
}

.lib-act[b-jjbhygfhh2] {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--mut);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.lib-act .ico[b-jjbhygfhh2] {
    display: block;
}

/* The heart glyph is centred inside its own SVG (see the <g transform> in LibraryActions.razor),
   which is resolution-independent — no pixel-nudge fighting sub-pixel rounding. */

.lib-act:hover[b-jjbhygfhh2] {
    border-color: var(--mut);
    color: var(--tx2);
}

/* On-states: favourite = red (constant across themes), owned = deal green, bell = accent. */
.lib-act.on[b-jjbhygfhh2] {
    background: var(--acc-soft);
    border-color: var(--red);
    color: var(--red);
}

.lib-act.owned.on[b-jjbhygfhh2] {
    background: var(--deal-bg);
    border-color: var(--deal-brd);
    color: var(--deal);
}

.lib-act.bell.on[b-jjbhygfhh2] {
    background: var(--acc-soft);
    border-color: var(--acc);
    color: var(--acc);
}

/* "cover" variant — a single heart button in the cover's bottom-right corner (the approved
   tile anatomy: sticker TL, kind chip TR, heart BR; owned/bell live on the game page). */
.lib-actions.lib-cover[b-jjbhygfhh2] {
    position: absolute;
    right: 8px;
    bottom: 8px;
    z-index: 2;
}

.lib-actions.lib-cover .lib-act[b-jjbhygfhh2] {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--heart-bg);
    border: none;
    color: var(--red);
    box-shadow: 0 2px 7px rgba(0, 0, 0, 0.22);
}

.lib-actions.lib-cover .lib-act.on[b-jjbhygfhh2] {
    background: var(--red);
    color: #fff;
}

.lib-actions.lib-cover .ico[b-jjbhygfhh2] {
    /* 18px (not 17) so the flex margin (34−18)/2 = 8px is a WHOLE pixel. A 17px icon left an 8.5px
       half-pixel margin, so the heart landed on a different sub-pixel phase than its circle on every
       tile (tiles sit at varying fractional x) and looked "shifted differently" per tile. */
    width: 18px;
    height: 18px;
}

/* Full GameCard variant (kept for non-cover contexts). */
.lib-actions.lib-card[b-jjbhygfhh2] {
    gap: 6px;
    margin-top: 8px;
}
.lib-actions.lib-card .lib-act[b-jjbhygfhh2] {
    border-radius: 8px;
    padding: 8px 0;
}
.lib-actions.lib-card .ico[b-jjbhygfhh2] {
    width: 18px;
    height: 18px;
}

/* Compact MiniCard (rail tile) variant — margin-top:auto pins the row to the tile's bottom. */
.lib-actions.lib-mini[b-jjbhygfhh2] {
    gap: 3px;
    padding: 2px 5px 6px;
    margin-top: auto;
}
.lib-actions.lib-mini .lib-act[b-jjbhygfhh2] {
    border-radius: 6px;
    padding: 5px 0;
}
.lib-actions.lib-mini .ico[b-jjbhygfhh2] {
    width: 15px;
    height: 15px;
}

/* List-row variant — three quiet icons, revealed on row hover (desktop); see GameRow.razor.css. */
.lib-actions.lib-row[b-jjbhygfhh2] {
    gap: 4px;
}
.lib-actions.lib-row .lib-act[b-jjbhygfhh2] {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    border-radius: 8px;
}
.lib-actions.lib-row .ico[b-jjbhygfhh2] {
    width: 15px;
    height: 15px;
}
/* /Components/Lightbox.razor.rz.scp.css */
/* Above the region/shop sheets (1100) and the cookie bar (1000): this one covers everything. */
.lb[b-26fk4x9js5] {
    position: fixed;
    inset: 0;
    height: 100dvh;
    z-index: 1200;
    background: rgba(0, 0, 0, 0.93);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* The native scrollbar is hidden — the counter and the cut edge of the next frame say "there is
   more", the same way the rails do. `overscroll-behavior: contain` stops a swipe past the last
   frame from turning into a page scroll (or, on iOS, a back-navigation gesture). */
.lb-track[b-26fk4x9js5] {
    display: flex;
    width: 100%;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.lb-track[b-26fk4x9js5]::-webkit-scrollbar {
    display: none;
}

.lb-slide[b-26fk4x9js5] {
    flex: 0 0 100%;
    height: 100%;
    scroll-snap-align: center;
    scroll-snap-stop: always;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: calc(8px + env(safe-area-inset-top)) 8px calc(8px + env(safe-area-inset-bottom));
}

/* --lb-max is ImageUrls.LightboxMaxWidth, set on .lb by the component, and it EQUALS the ladder's
   top rung: a DPR-1 screen then paints the picture 1:1. A wider stage does not show more picture --
   Nintendo publishes 1280x720 and the ladder stops below that -- it only enlarges the upscale, which
   is what "the full-screen shot looks over-compressed" turned out to be. */
.lb-slide img[b-26fk4x9js5] {
    max-width: min(100%, var(--lb-max, 1152px));
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 8px;
    background: #000;
    user-select: none;
    -webkit-user-drag: none;
}

.lb-close[b-26fk4x9js5],
.lb-nav[b-26fk4x9js5] {
    position: absolute;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 999px;
    cursor: pointer;
    font-family: inherit;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(4px);
}

.lb-close[b-26fk4x9js5] {
    top: calc(10px + env(safe-area-inset-top));
    right: calc(10px + env(safe-area-inset-right));
    width: 40px;
    height: 40px;
    font-size: 1rem;
}

.lb-nav[b-26fk4x9js5] {
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    font-size: 1.6rem;
    padding-bottom: 4px;
}

.lb-nav.prev[b-26fk4x9js5] {
    left: calc(12px + env(safe-area-inset-left));
}

.lb-nav.next[b-26fk4x9js5] {
    right: calc(12px + env(safe-area-inset-right));
}

.lb-nav:disabled[b-26fk4x9js5] {
    opacity: 0.25;
    cursor: default;
}

.lb-close:hover[b-26fk4x9js5],
.lb-nav:hover:not(:disabled)[b-26fk4x9js5] {
    background: rgba(0, 0, 0, 0.75);
}

/* A touch screen swipes; two chevrons floating over the picture on a 390px phone cost more room
   than they give. The counter stays — it is the only thing that says how many are left. */
@media (hover: none) {
    .lb-nav[b-26fk4x9js5] {
        display: none;
    }
}

.lb-count[b-26fk4x9js5] {
    position: absolute;
    bottom: calc(12px + env(safe-area-inset-bottom));
    left: 50%;
    transform: translateX(-50%);
    padding: 4px 10px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.5);
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}
/* /Components/MiniCard.razor.rz.scp.css */
.mini-card[b-728vgfy2os] {
    display: flex;
    flex-direction: column;
    width: 118px;
    flex: 0 0 auto;
    text-decoration: none;
    color: var(--tx);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 12px;
    overflow: hidden;
    transition: transform 0.15s, box-shadow 0.15s;
}

.mini-card:hover[b-728vgfy2os] {
    transform: translateY(-2px);
    box-shadow: var(--card-sh);
    color: var(--tx);
}

.mini-thumb[b-728vgfy2os] {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--sf2);
}

.mini-thumb img[b-728vgfy2os] {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.mini-noimg[b-728vgfy2os] {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mut);
}

.mini-type[b-728vgfy2os] {
    position: absolute;
    top: 4px;
    right: 4px;
    font-size: 0.56rem;
    font-weight: 800;
    padding: 1px 5px;
    border-radius: 5px;
    text-transform: uppercase;
    background: var(--sw2-bg);
    color: var(--sw2-tx);
}

.mini-type.type-bundle[b-728vgfy2os] { color: var(--gold); }
.mini-type.type-demo[b-728vgfy2os] { color: var(--mut); }

/* Discount — same sticker→chip morph as the big tile. */
.mini-sale[b-728vgfy2os] {
    position: absolute;
    top: 5px;
    left: 2px;
    transform: rotate(var(--disc-rot));
    font-size: 0.62rem;
    font-weight: 800;
    font-family: var(--fn);
    padding: 1px 6px;
    border-radius: 7px;
    background: var(--disc-bg);
    color: var(--disc-ink);
    box-shadow: var(--disc-sh);
}

/* NO bottom padding, and that is the whole rule: `overflow` clips at the PADDING box, so on a
   line-clamped block every pixel of bottom padding is a window onto the line that was clamped away.
   It used to be 2px, and the top of the third line showed through them — invisible in Latin, where a
   line box opens with the gap above the cap height, and plain as day in Chinese and Japanese, whose
   glyphs fill their em box from the very top ("瑪利歐賽車8 豪華版 新增賽道…" wore the tops of 行證).
   The 2px of air the title still owes the price is paid by .mini-price's padding-top instead, where
   it separates the two without extending anything's clip. The site's other two clamped titles
   (.card-title-text, .row-title) have no padding at all, which is why neither ever showed this. */
.mini-title[b-728vgfy2os] {
    padding: 5px 7px 0;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.2;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.4em;
}

.mini-price[b-728vgfy2os] {
    padding: 2px 7px 2px;   /* the top 2px is the title's old bottom padding — see .mini-title */
    font-size: 0.74rem;
    font-weight: 800;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    color: var(--tx);
}

.mini-price .na[b-728vgfy2os] {
    color: var(--mut);
    font-weight: 400;
}

/* Region + saving, under the price. Its own line because all three do not fit across the tile: at
   118px the content box is 104px (90px on phones) and the widest live add-on price is 69,59 € — the
   price alone takes ~half of it. Same reading order as the full tile's .card-foot: what it costs,
   then where from. */
.mini-where[b-728vgfy2os] {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 0 7px 6px;
    min-width: 0;
}

.mini-where .rc[b-728vgfy2os] {
    font-family: var(--fn);
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    line-height: 1;
    border: 1px solid var(--ln);
    border-radius: 5px;
    padding: 2px 4px;
    background: var(--sf2);
    color: var(--tx2);
    flex: 0 0 auto;
}

/* "Needs an account" on the smallest surface there is: a colour and a tooltip, no glyph and no words —
   nothing legible fits beside a 118px tile, which is the same constraint that keeps <CouponMark /> off
   this component. Dashed + muted, matching the tile and the row exactly. */
.mini-where .rc-need[b-728vgfy2os] {
    border-style: dashed;
    border-color: var(--mut);
    background: transparent;
    color: var(--mut);
}

/* The global .savings-badge sizes itself as 0.72em of its parent; inside a 0.6rem line that lands
   near 7px, which is below this palette's smallest legible type. Restated at the chip's own size,
   and the badge's left margin goes to the flex gap so the two chips sit evenly. */
.mini-where .savings-badge[b-728vgfy2os] {
    font-size: 0.6rem;
    margin-left: 0;
    padding: 2px 5px;
    line-height: 1;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Same trim as the tile and row chips: at this size the leading cannot be split evenly across whole
   pixels, so an untrimmed box sits the code ~1.5px high inside it. */
@supports (text-box: trim-both cap alphabetic) {
    .mini-where .rc[b-728vgfy2os],
    .mini-where .savings-badge[b-728vgfy2os] {
        text-box: trim-both cap alphabetic;
        padding: 3px 4px;
    }
}

@media (max-width: 768px) {
    .mini-card[b-728vgfy2os] { width: 104px; }
}
/* /Components/MiniSeriesChart.razor.rz.scp.css */
.msc-empty[b-izyrb52kon] {
    padding: 0.9rem;
    border: 1px dashed var(--ln);
    border-radius: 8px;
    text-align: center;
    font-size: 0.78rem;
    color: var(--mut);
}

.msc-wrap[b-izyrb52kon] {
    background: var(--sf);
    border-radius: 8px;
    padding: 0.6rem 0.7rem 0.5rem;
    /* The reset button sits over the plot's own top-right corner. */
    position: relative;
}

/* A chart that takes the pointer at all. user-select so dragging across the axis labels does not select
   them instead of panning.

   touch-action is `pan-y`, NOT `none`: on a phone the chart is the full width of the page, so `none`
   makes a finger dragged down it do nothing at all and the page appears frozen under it. `pan-y` gives
   the vertical direction back to the page and keeps the horizontal one, which is the only one a time
   window is dragged along anyway. */
.msc-live[b-izyrb52kon] {
    touch-action: pan-y;
    user-select: none;
}

.msc-zoom[b-izyrb52kon] {
    cursor: grab;
}

/* Added by allregions.chartZoomSync for the duration of a drag, the way scrollToGameCard adds its
   highlight: Blazor only rewrites an attribute whose rendered value changed, so a class the DOM gained
   on its own survives the re-render each dragged frame causes. */
.msc-zoom.msc-drag[b-izyrb52kon] {
    cursor: grabbing;
}

/* Above the plot rather than floating over it: the right-hand gutter carries a price label per line
   now, and a button parked there would sit on one. It is also the only affordance a keyboard, a
   trackpad-less touch device or a first-time reader has — a modifier and a pinch are both invisible. */
.msc-tools[b-izyrb52kon] {
    display: flex;
    justify-content: flex-end;
    gap: 0.25rem;
    margin-bottom: 0.15rem;
    min-height: 1.35rem;
}

.msc-tools button[b-izyrb52kon] {
    padding: 0 0.45rem;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: transparent;
    color: var(--tx2);
    font-size: 0.72rem;
    line-height: 1.3rem;
    cursor: pointer;
}

.msc-tools button:hover[b-izyrb52kon] {
    color: var(--tx);
    border-color: var(--tx2);
}

/* "reset" is rendered only while a zoom is in force, so it doubles as the one thing on screen that says
   so — the axis just quietly carries fewer days. */
.msc-reset[b-izyrb52kon] {
    color: var(--tx);
}

/* Why nothing zoomed. A wheel that does nothing is indistinguishable from a chart that is broken, so a
   plain wheel over the plot scrolls the page AND says what would have zoomed it. */
.msc-hint[b-izyrb52kon] {
    position: absolute;
    left: 50%;
    top: 45%;
    transform: translate(-50%, -50%);
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    border: 1px solid var(--ln);
    color: var(--tx);
    font-size: 0.78rem;
    pointer-events: none;
    opacity: 0;
    transition: opacity .18s ease;
}

.msc-wrap.hint-on .msc-hint[b-izyrb52kon] {
    opacity: 1;
}

.msc-hint-t[b-izyrb52kon],
.msc-wrap.hint-touch .msc-hint-m[b-izyrb52kon] {
    display: none;
}

.msc-wrap.hint-touch .msc-hint-t[b-izyrb52kon] {
    display: inline;
}

.msc[b-izyrb52kon] {
    width: 100%;
    height: auto;
    display: block;
    /* Fallback only: the element carries an inline aspect-ratio built from its own Height, because one
       pinned here applied to every caller and silently resized their coordinate space. */
    aspect-ratio: 720 / 170;
}

.msc-legend[b-izyrb52kon] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.85rem;
    margin-top: 0.45rem;
    font-size: 0.73rem;
    color: var(--tx2);
}

.msc-li[b-izyrb52kon] {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    font-variant-numeric: tabular-nums;
}

.msc-sw[b-izyrb52kon] {
    width: 12px;
    height: 3px;
    border-radius: 2px;
    align-self: center;
}

.msc-last[b-izyrb52kon] {
    color: var(--tx);
    font-weight: 600;
}

/* How many observations this capture actually contributed — a line drawn from two points and one
   drawn from forty look identical otherwise, and on a young series that is the whole question. */
.msc-n[b-izyrb52kon] {
    color: var(--mut);
    font-size: 0.68rem;
}

.msc-n[b-izyrb52kon]::before {
    content: "×";
}
/* /Components/Money.razor.rz.scp.css */
/* FREE is a word, not a number.
   Quiet ink rather than the price colour: the eye scans a price column comparing figures, and a
   full-contrast FREE would shout louder than a real bargain while carrying nothing to compare.

   --mut, and the choice is MEASURED rather than judged by eye. Against the price ink (--tx) that the
   neighbouring cells use, the token one step up (--tx2) is 1.61:1 on the light theme and 1.48:1 on
   the dark one — i.e. the same darkness, so the word reads as an ordinary price and the intent was
   invisible (observed on both themes before this line was written). --mut makes the step real:
   4.17:1 light, 2.45:1 dark. It stays legible against the surface it sits on — 3.68:1 on the white
   card, 6.50:1 on the dark one — and it is the same tier as every other secondary string in these
   rows, including the country name directly under the region code, which is exactly the tier a
   non-figure belongs to. There is no quieter INK token: below --mut the palette is hairlines.

   Both tokens are declared on :root and re-declared for both dark states, so this resolves in every
   theme state and has no colour of its own to keep in step. */
.free-price[b-4nfzfpsi44] {
    color: var(--mut);
}
/* /Components/NotificationBell.razor.rz.scp.css */
.bell-wrap[b-ihy934f5of] { position: relative; display: inline-flex; }

.bell-btn[b-ihy934f5of] {
    position: relative;
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 0.3rem 0.6rem;
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
}

.bell-btn:hover[b-ihy934f5of] { border-color: var(--mut); }

.bell-badge[b-ihy934f5of] {
    position: absolute;
    top: -5px;
    right: -5px;
    background: var(--acc);
    color: #fff;
    font-size: 0.6rem;
    font-weight: 700;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Click-away catcher behind the menu.
   Sized explicitly instead of `inset: 0`, because on phones this sits inside the header, and the
   header carries the auto-hide transform — which makes IT the containing block for fixed children.
   `inset: 0` therefore resolved to the header's own 99px strip (measured), so a tap anywhere on the
   feed hit a game card and navigated away instead of closing the panel. Against that same box,
   100% + 100dvh IS the viewport; `%` rather than `vw` because vw counts a desktop scrollbar the
   header's box does not. It stays a catcher, not a lock: dragging it still scrolls the page, which
   is the behaviour the drawer was changed to match. */
.bell-backdrop[b-ihy934f5of] {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100dvh;
    z-index: 40;
}

.bell-menu[b-ihy934f5of] {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 50;
    width: 320px;
    max-width: 88vw;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 10px;
    box-shadow: var(--frame-sh);
    overflow: hidden;
}

/* Phones: the bell lives in the header's top row (never in the ☰ drawer, which clips its own overflow
   on both axes and would slice this panel in half). Anchoring the panel to the BUTTON is still not
   safe at phone widths — the top row can wrap on a 360px screen and take the bell with it, and a
   right:0 panel then runs off the left edge. Dropping .bell-wrap's `relative` hands the containing
   block to the sticky .ns-header, so the panel becomes a sheet spanning the full header width,
   wherever the button itself ended up. */
@media (max-width: 860px) {
    .bell-wrap[b-ihy934f5of] {
        position: static;
    }

    .bell-menu[b-ihy934f5of] {
        top: calc(100% + 6px);
        left: 10px;
        right: 10px;
        width: auto;
        max-width: none;
    }
}

.bell-head[b-ihy934f5of] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.6rem 0.8rem;
    border-bottom: 1px solid var(--ln);
    font-weight: 600;
    color: var(--tx);
}

.bell-head-acts[b-ihy934f5of] { display: inline-flex; align-items: center; gap: 0.7rem; }

.bell-link[b-ihy934f5of] {
    color: var(--acc);
    text-decoration: none;
    font-size: 0.85rem;
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    font-weight: inherit;
    cursor: pointer;
}

.bell-link:hover[b-ihy934f5of] { text-decoration: underline; }

/* Clear-all is the one destructive control here: quiet until armed, then unmistakably red so the
   second tap is a decision rather than a repeat of the first. */
.bell-link.danger[b-ihy934f5of] { color: var(--mut); font-weight: 600; }
.bell-link.danger:hover[b-ihy934f5of] { color: var(--red); }
.bell-link.danger.armed[b-ihy934f5of] { color: var(--red); font-weight: 800; }

.bell-master[b-ihy934f5of] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0.8rem;
    border-bottom: 1px solid var(--ln);
    color: var(--tx2);
    cursor: pointer;
}

.bell-master input[b-ihy934f5of] { width: 16px; height: 16px; accent-color: var(--acc); }

.bell-device[b-ihy934f5of] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.6rem 0.8rem;
    border-bottom: 1px solid var(--ln);
}

.bell-hint[b-ihy934f5of] { font-size: 0.8rem; color: var(--mut); }
.bell-ok[b-ihy934f5of] { font-size: 0.85rem; color: var(--deal); flex: 1; }

.bell-mini[b-ihy934f5of] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 6px;
    padding: 0.3rem 0.6rem;
    font-size: 0.8rem;
    cursor: pointer;
}

.bell-mini.primary[b-ihy934f5of] { background: var(--acc); border-color: var(--acc); color: #fff; font-weight: 600; }
.bell-mini:disabled[b-ihy934f5of] { opacity: 0.6; cursor: default; }

/* No-account tier: says what the alerts are attached to, and how to keep them. */
.bell-note[b-ihy934f5of] {
    padding: 0.5rem 0.8rem;
    border-bottom: 1px solid var(--ln);
    color: var(--mut);
    font-size: 0.78rem;
    line-height: 1.35;
}

.bell-note a[b-ihy934f5of] { color: var(--acc); }

/* A failure the visitor is meant to read and repeat back, so it is coloured as one and allowed to
   wrap — the browser's own error text is long and must not be clipped to its first half. */
.bell-note.error[b-ihy934f5of] {
    color: var(--sale);
    overflow-wrap: anywhere;
}

/* The explanation under a raw browser error: same block, quieter ink, because the red line is the
   fact and this is the advice. */
.bell-note-sub[b-ihy934f5of] {
    margin-top: 0.35rem;
    color: var(--mut);
    overflow-wrap: normal;
}

.bell-list[b-ihy934f5of] { max-height: 320px; overflow-y: auto; }

.bell-empty[b-ihy934f5of] {
    padding: 1rem 0.8rem;
    color: var(--mut);
    font-size: 0.85rem;
    text-align: center;
    line-height: 1.4;
}

/* ---- One row of the list feed, at the bell's width. -------------------------------------------
   GameRow's phone compaction is the model: [cover | title over a "·"-joined sub-line] [price],
   two lines, and the "why is this cheap" reason is a COLOURED WORD in the sub-line rather than a
   filled badge. This panel had three stacked lines of filled chips with the timestamp floating
   between them, which at 320px reads as noise rather than as a list.
   The rules are copied, not shared: .game-row lives in GameRow's SCOPED stylesheet, which another
   component cannot inherit from (Blazor scopes by a per-component attribute). The tokens are
   shared, so the colours stay in step by construction. ---------------------------------------- */

/* Row = link + its own delete button. The border lives on the wrapper so the two can't separate. */
.bell-item[b-ihy934f5of] {
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid var(--ln);
}

.bell-item:hover[b-ihy934f5of] { background: var(--sf2); }
.bell-item.unread[b-ihy934f5of] { background: var(--acc-soft); }

/* [cover + text] [price]. `auto` on the price so it takes exactly what it needs and the title gets
   the rest — the feed's own column shape. */
.bell-item-link[b-ihy934f5of] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.5rem 0.35rem 0.5rem 0.75rem;
    text-decoration: none;
    color: var(--tx2);
}

.bi-main[b-ihy934f5of] { display: flex; align-items: center; gap: 9px; min-width: 0; }

.bi-cover[b-ihy934f5of] {
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    border-radius: 8px;
    overflow: hidden;
    background: var(--sf2);
    border: 1px solid var(--ln);
}

.bi-cover img[b-ihy934f5of] { width: 100%; height: 100%; object-fit: cover; display: block; }

.bi-text[b-ihy934f5of] { min-width: 0; }

.bi-title[b-ihy934f5of] {
    display: block;
    font-size: 0.85rem;
    font-weight: 800;
    color: var(--tx);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* One muted line, chunks joined by "·" — and the separator lives INSIDE each chunk so a chunk that
   never renders takes its dot with it (the feed's scheme, same reason). */
.bi-sub[b-ihy934f5of] {
    display: block;
    margin-top: 1px;
    font-size: 0.72rem;
    color: var(--mut);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.bi-sub > span:not(:last-child)[b-ihy934f5of]::after {
    content: '·';
    margin: 0 5px;
    color: var(--mut);
}

.bi-reason[b-ihy934f5of] { font-weight: 800; }
.bi-reason.sale[b-ihy934f5of] { color: var(--sale); }
.bi-reason.region[b-ihy934f5of] { color: var(--region); }
/* A new low is not a running sale: cool "structural" semantics, a step quieter than a discount. */
.bi-reason.low[b-ihy934f5of] { color: var(--deal); }

/* Price column — right-aligned against the row's edge, never wraps. */
.bi-price[b-ihy934f5of] {
    min-width: 0;
    text-align: right;
    white-space: nowrap;
    font-family: var(--fn);
}

.bi-now[b-ihy934f5of] { font-size: 0.88rem; font-weight: 800; color: var(--tx); }
.bi-now.on-sale[b-ihy934f5of] { color: var(--sale); }

.bi-where[b-ihy934f5of] { display: block; margin-top: 1px; }

/* The feed's region code, including its box-trim: at ~10px Chrome snaps the baseline to a whole
   pixel, so symmetric padding alone leaves the text sitting ~1.5px high. See GameRow.razor.css. */
.bi-where .rc[b-ihy934f5of] {
    display: inline-block;
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    line-height: 1;
    padding: 2px 4px;
    border: 1px solid var(--ln);
    border-radius: 5px;
    background: var(--sf2);
    color: var(--tx2);
}

/* Not your region — reachable only through a gift-card top-up. Cool --region is the token the whole
   app already spends on that fact, so the chip states it without a word (there is no width for one)
   and the title attribute carries the wording. */
.bi-where .rc.away[b-ihy934f5of] {
    color: var(--region);
    background: color-mix(in srgb, var(--region) 12%, transparent);
    border-color: color-mix(in srgb, var(--region) 35%, transparent);
}

/* The shop the price is reachable through, sitting beside the region chip as one segment. Deliberately
   NOT a second chip: two pills of equal weight read as two independent facts, while the pair is a single
   instruction ("open your Eneba account for MX"). So the region keeps the chip and the shop is plain
   muted text — present when it matters, invisible in the scan when it does not. */
.bi-where .rc-via[b-ihy934f5of] {
    font-size: 0.6rem;
    font-weight: 600;
    line-height: 1;
    margin-left: 4px;
    color: var(--tx2);
    white-space: nowrap;
}

@supports (text-box: trim-both cap alphabetic) {
    .bi-where .rc[b-ihy934f5of] { text-box: trim-both cap alphabetic; padding: 3px 4px; }
}

/* Quiet at rest, full on hover — the feed's treatment for its row actions. Not fully transparent
   like .row-act, though: those actions are reachable from the game page, and this ✕ is the only
   place a notification can be removed, so it has to stay discoverable without a hover. */
.bell-del[b-ihy934f5of] {
    flex: 0 0 auto;
    align-self: stretch;
    width: 28px;
    background: none;
    border: none;
    color: var(--mut);
    font-size: 0.72rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.3;
    transition: opacity 0.15s;
}

.bell-item:hover .bell-del[b-ihy934f5of],
.bell-item:focus-within .bell-del[b-ihy934f5of] { opacity: 1; }
.bell-del:hover[b-ihy934f5of] { color: var(--red); }

/* Touch screens have no hover to reveal it with, and 28px is already at the floor of what a thumb
   hits — so there it is always visible and a little wider. */
@media (hover: none) {
    .bell-del[b-ihy934f5of] { opacity: 1; width: 36px; }
}
/* /Components/NotificationSettingsCard.razor.rz.scp.css */
.settings-h[b-gho7umfs7n] {
    margin: 0 0 0.8rem;
    font-size: 1.15rem;
    color: var(--tx);
}

.settings-sub[b-gho7umfs7n] {
    margin: 0.6rem 0 0.3rem;
    font-size: 0.8rem;
    color: var(--mut);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.check-inline.disabled[b-gho7umfs7n] {
    opacity: 0.5;
}

.auth-btn.ghost[b-gho7umfs7n] {
    background: transparent;
    border: 1px solid var(--ln);
    color: #cdd6ea;
}
/* /Components/PriceChart.razor.rz.scp.css */
.chart-msg[b-ryjq8g2jmm] {
    background: var(--sf);
    border: 1px dashed var(--ln);
    border-radius: 10px;
    padding: 1.5rem 1rem;
    text-align: center;
    color: #8a93ab;
    font-size: 0.9rem;
}

/* The series palette. Eight slots, validated as a set against BOTH surfaces (lightness band, chroma
   floor, adjacent-pair CVD separation, normal-vision separation, contrast) — do not add a ninth or
   re-step one in isolation: the ORDER is what keeps neighbouring pairs apart under colour blindness.
   Slot 1 is always the cheapest line and slot 2 always the viewer's own region; the rest follow the
   order the server sent. Three of the light steps sit under 3:1 against white, which is why every line
   also carries its region code at its end and in the legend — identity never rests on the swatch.
   Declared as custom properties because the SVG body is injected as a MarkupString and carries no
   style-isolation attribute: no scoped CLASS can reach those elements, but an inherited VARIABLE can. */
/* A refetch dims the drawing instead of replacing it.

   The chart used to be swapped for a one-line "loading" message on every shop switch, which collapsed
   this container to a text row and flashed the page behind it. Keeping the frame is the point; the
   dimming says the lines under it are the previous reading and are being replaced. Short and eased so
   a fast reply does not read as a blink of its own, and `pointer-events: none` so a second click
   cannot queue a third request against a chart that is already stale. */
.chart-wrap.refreshing[b-ryjq8g2jmm] {
    opacity: 0.5;
    pointer-events: none;
    transition: opacity 0.12s ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .chart-wrap.refreshing[b-ryjq8g2jmm] {
        transition: none;
    }
}

.chart-wrap[b-ryjq8g2jmm] {
    background: var(--sf);
    border-radius: 10px;
    padding: 0.9rem 1rem 0.75rem;
    --sc1: #008300;   /* green — the cheapest region */
    --sc2: #2a78d6;   /* blue — the viewer's own region */
    --sc3: #eb6834;   /* orange */
    --sc4: #1baf7a;   /* aqua */
    --sc5: #eda100;   /* yellow */
    --sc6: #e87ba4;   /* magenta */
    --sc7: #4a3aa7;   /* violet */
    --sc8: #e34948;   /* red */
}

[data-theme="dark"] .chart-wrap[b-ryjq8g2jmm] {
    --sc1: #008300;
    --sc2: #3987e5;
    --sc3: #d95926;
    --sc4: #199e70;
    --sc5: #c98500;
    --sc6: #d55181;
    --sc7: #9085e9;
    --sc8: #e66767;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) .chart-wrap[b-ryjq8g2jmm] {
        --sc1: #008300;
        --sc2: #3987e5;
        --sc3: #d95926;
        --sc4: #199e70;
        --sc5: #c98500;
        --sc6: #d55181;
        --sc7: #9085e9;
        --sc8: #e66767;
    }
}

.price-chart[b-ryjq8g2jmm] {
    width: 100%;
    height: auto;
    display: block;
    /* preserveAspectRatio="none" stretches the viewBox to the container; matching the CSS aspect to the
       viewBox keeps the scaling uniform. It also makes a viewBox coordinate a plain per-cent of this
       box, which is what lets the HTML labels below land exactly on the geometry they name. */
    aspect-ratio: 600 / 240;
}

/* The label layer. Everything textual is HTML positioned over the SVG rather than <text> inside it:
   the SVG is scaled to the container, so type inside it shrank to ~5.6px on a 390px phone. Out here a
   label is 11px everywhere, in the page's own font, and inherits the theme's ink. */
.chart-plot[b-ryjq8g2jmm] {
    position: relative;
}

.lbl[b-ryjq8g2jmm] {
    position: absolute;
    font-size: 11px;
    line-height: 1;
    white-space: nowrap;
    /* Labels must never eat a click meant for the line underneath them. */
    pointer-events: none;
    color: var(--tx2);
}

/* A price the selected line held. Right-aligned into the gutter; if it is wider than the gutter it
   runs into the card's own padding, which is why the wrap is not clipped. */
.lbl-y[b-ryjq8g2jmm] {
    transform: translate(-100%, -50%);
}

/* A step of the fallback ruler asserts nothing about this game, so it recedes. */
.lbl.ruler[b-ryjq8g2jmm] {
    color: var(--mut);
}

.lbl-off[b-ryjq8g2jmm] {
    transform: translateY(-100%);
    color: var(--deal);
    font-weight: 600;
    font-size: 10.5px;
    /* Sits inside the plot, where a line can run underneath it. */
    text-shadow: 0 0 3px var(--sf), 0 0 3px var(--sf), 0 0 2px var(--sf);
}

.lbl-end[b-ryjq8g2jmm] {
    transform: translateY(-50%);
    font-weight: 700;
    font-size: 11.5px;
    white-space: nowrap;
}

/* A shared label carries two (or more) codes, each in its own line's colour — see EndLabels(). The
   markup already puts a real space between them; this only opens it up a little. */
.lbl-end > span + span[b-ryjq8g2jmm] {
    margin-left: 2px;
}

.lbl-end.dim[b-ryjq8g2jmm] {
    opacity: 0.5;
    font-weight: 500;
}

/* The date strip, outside the plot box: these labels are fixed-size while the room under the baseline
   is a share of a viewBox that gets short on a phone — two rows of text do not fit in it there. */
.chart-xaxis[b-ryjq8g2jmm] {
    position: relative;
    height: 30px;
    margin-top: 2px;
}

.lbl-x[b-ryjq8g2jmm] {
    transform: translateX(-50%);
    top: 0;
    color: var(--mut);
}

.lbl-edge[b-ryjq8g2jmm] {
    top: 15px;
    color: var(--tx2);
}

/* Both are positioned inline, at the PLOT's edges — see the markup. The right one is pulled back by its
   own width so it ENDS where the lines end, instead of starting there and overflowing the box. */
.lbl-edge-r[b-ryjq8g2jmm] {
    transform: translateX(-100%);
}

/* Hover readout. The one question the static rules cannot answer — with a region selected the price
   axis is about THAT region, so another line's price on a given day is written nowhere else. */
.chart-tip[b-ryjq8g2jmm] {
    position: absolute;
    top: 4px;
    z-index: 2;
    pointer-events: none;
    transform: translateX(10px);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 6px 8px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
    font-size: 11px;
    line-height: 1.45;
}

/* Past the middle it would run off the right edge, so it changes sides instead. */
.chart-tip.flip[b-ryjq8g2jmm] {
    transform: translateX(calc(-100% - 10px));
}

.tip-date[b-ryjq8g2jmm] {
    color: var(--mut);
    font-size: 10px;
    margin-bottom: 2px;
}

.tip-row[b-ryjq8g2jmm] {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--tx2);
}

.tip-row.sel[b-ryjq8g2jmm] {
    color: var(--tx);
    font-weight: 700;
}

.tip-rgn[b-ryjq8g2jmm] {
    min-width: 1.8em;
}

.tip-val[b-ryjq8g2jmm] {
    margin-left: auto;
    padding-left: 8px;
    font-variant-numeric: tabular-nums;
}

/* The shop a readout line's figure is a price THROUGH. Quiet and uppercase-small like the legend's own
   via tag — it qualifies the amount beside it, it does not compete with it. Truncates rather than
   wrapping: the tip is one line per series and eight of them must stay eight lines. */
.tip-via[b-ryjq8g2jmm] {
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--mut);
    max-width: 6.5em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The chart in words, for anyone who cannot use a pointer or see the picture. Not display:none — that
   would take it out of the accessibility tree as well, which is the opposite of the point. */
/* The chart in words, for a screen reader. It MUST be a plain block box, never the <table> itself:
   a table sizes to its content and ignores `width: 1px`, and `clip` clips the paint rather than the
   layout — so applied to the table this rule hid ~520px of nowrap cells in plain sight and made the
   whole game page scrollable sideways. `overflow: hidden` on this div is what actually contains it. */
.chart-sr[b-ryjq8g2jmm] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Time range. A segmented control, matching the eShop/lens switch above it — same visual language for
   the same kind of choice. A disabled rung is kept on screen rather than hidden: "we have not been
   watching this game for a year" is itself worth reading. */
.chart-ranges[b-ryjq8g2jmm] {
    display: inline-flex;
    border: 1px solid var(--ln);
    border-radius: 8px;
    overflow: hidden;
    margin-bottom: 0.6rem;
}

.chart-ranges button[b-ryjq8g2jmm] {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--mut);
    font: inherit;
    font-size: 0.76rem;
    padding: 4px 10px;
    cursor: pointer;
    white-space: nowrap;
}

.chart-ranges button + button[b-ryjq8g2jmm] {
    border-left: 1px solid var(--ln);
}

.chart-ranges button.on[b-ryjq8g2jmm] {
    background: var(--sf2);
    color: var(--tx);
    font-weight: 600;
}

.chart-ranges button:hover:not(.on):not(.off)[b-ryjq8g2jmm] {
    color: var(--tx);
}

/* Out of reach, but still a button: `disabled` would suppress its pointer events, and with them the
   title that says WHY it is out — the only reason the rung stays on screen at all. The click is
   refused in code (SetRange), not by the UA. */
.chart-ranges button.off[b-ryjq8g2jmm] {
    opacity: 0.4;
    cursor: default;
}

/* NOTE: the SVG's own text rules used to live here (.grid / .axis-label / .dot) and were dead the whole
   time — anything injected as a MarkupString carries no style-isolation attribute, so no scoped
   selector can reach it. What is left inside the SVG is geometry, styled inline through CSS variables
   so the theme still reaches it; every label is now real HTML above and these rules do apply to it. */

.chart-legend[b-ryjq8g2jmm] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    margin-top: 0.6rem;
    font-size: 0.8rem;
    color: var(--tx2);
}

/* Each entry selects its own line, so it resets the UA button chrome and keeps the type of the row it
   lives in. The selected one is framed rather than coloured — the swatch already carries the colour,
   and a second colour cue on the same control would compete with it. */
.legend-item[b-ryjq8g2jmm] {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    appearance: none;
    border: 1px solid transparent;
    border-radius: 7px;
    background: transparent;
    padding: 2px 7px;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.legend-item:hover[b-ryjq8g2jmm] {
    border-color: var(--ln);
}

.legend-item.sel[b-ryjq8g2jmm] {
    border-color: var(--ln2, var(--ln));
    background: var(--sf2);
}

/* The click target for a line. A 1.5-unit stroke is not something anyone can hit, least of all with a
   thumb, so a transparent fat copy of the path takes the click; pointer-events:stroke keeps it to the
   line itself rather than the box it spans. */
.series-hit[b-ryjq8g2jmm] {
    fill: none;
    stroke: transparent;
    stroke-width: 12;
    pointer-events: stroke;
    cursor: pointer;
}

.legend-swatch[b-ryjq8g2jmm] {
    width: 14px;
    height: 3px;
    border-radius: 2px;
    display: inline-block;
}

.legend-price[b-ryjq8g2jmm] {
    /* var(--tx), not a fixed near-white: this component is rendered in both themes and the literal
       was invisible on the light one. */
    color: var(--tx);
    font-weight: 600;
}

.legend-tag[b-ryjq8g2jmm] {
    font-size: 0.68rem;
    color: var(--deal);
    background: rgba(74, 222, 128, 0.12);
    border-radius: 4px;
    padding: 1px 6px;
}

/* An uppercase chip centres its CAP BOX, not its line box — the family rule, stated in full in
   `GameDetail.razor.css` above `.via-tag`. This was the worst of them at 0.94px high, and it is the
   shop chip's own twin one section down, so it moves with it.
   ONLY the uppercase variant: `.legend-tag` and `.legend-home` are set in sentence case, and trimming
   to `alphabetic` would put their descenders outside the box. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(12px - 1cap)) {
    .legend-tag.legend-via[b-ryjq8g2jmm] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(12px - 1cap);
    }
}

/* "yours" is a statement of fact about the row, not a good-news badge, so it drops the savings colour
   the cheapest-now tag carries. Must stay AFTER .legend-tag — same specificity, later wins. */
.legend-home[b-ryjq8g2jmm] {
    color: var(--tx2);
    background: var(--sf2);
}

/* Which shop funded this line. Deliberately the price table's own .via-tag palette (accent on a soft
   accent field), because it states the identical fact about the identical region one section up — two
   visual languages for one fact would read as two facts. Same specificity rule as .legend-home: it must
   stay AFTER .legend-tag to win. */
.legend-via[b-ryjq8g2jmm] {
    color: var(--acc);
    background: var(--acc-soft);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-weight: 700;
    font-size: 0.62rem;
}

/* And its opposite, matching .noroute-tag on the row: no shop we know of funds this wallet, so the line
   is a conversion. Muted and dashed rather than alarming — it is the limit of OUR coverage, not a
   warning about the region. */
.legend-noroute[b-ryjq8g2jmm] {
    color: var(--mut);
    background: transparent;
    border: 1px dashed var(--ln);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.62rem;
}

/* Who prices the dashed line: Nintendo, and therefore nobody the reader has to hold an account with.
   Muted rather than the accent .legend-via carries — that palette means "a shop funded this", and this
   entry is the one line on the chart that no shop did. Must stay AFTER .legend-tag to win, the same
   specificity rule as .legend-home and .legend-via. */
.legend-anchor[b-ryjq8g2jmm] {
    color: var(--tx2);
    background: transparent;
    border: 1px dashed var(--ln);
}

/* Wraps rather than scrolls: at 390px these become two or three short lines, which stays readable,
   whereas a horizontal scroller hides exactly the figure someone came for. */
.chart-stats[b-ryjq8g2jmm] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1rem;
    margin-top: 0.55rem;
    padding-top: 0.55rem;
    border-top: 1px solid var(--ln);
    font-size: 0.8rem;
    color: var(--tx2);
}

.stat[b-ryjq8g2jmm] {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
}

.stat-lbl[b-ryjq8g2jmm] {
    color: var(--mut);
}

/* Which region the figures beside it are about — always stated, since the reader chooses that now. */
.stat-scope[b-ryjq8g2jmm] {
    gap: 6px;
    align-items: center;
}

.stat-scope b[b-ryjq8g2jmm] {
    color: var(--tx);
    font-weight: 700;
}

.stat b[b-ryjq8g2jmm] {
    color: var(--tx);
    font-weight: 600;
}

.stat b.stat-min[b-ryjq8g2jmm] {
    color: var(--deal);
}

.stat small[b-ryjq8g2jmm] {
    color: var(--mut);
    font-size: 0.72rem;
}

.stat-delta[b-ryjq8g2jmm] {
    color: var(--mut);
}

.stat-good[b-ryjq8g2jmm] {
    color: var(--deal);
    font-weight: 600;
}

.stat-since[b-ryjq8g2jmm] {
    color: var(--mut);
    font-size: 0.72rem;
    margin-left: auto;
}

/* margin-left:auto pushes the provenance note to the far right on a wide row, but on a wrapped
   narrow one that would leave it stranded against the edge under a half-empty line. */
@media (max-width: 560px) {
    .stat-since[b-ryjq8g2jmm] {
        margin-left: 0;
    }
}

/* §7.5 — the habit, now a badge in the legend strip (§2.5). The rule that kept it out of the FIGURES
   row is unchanged and is why it moved HERE and not there: those figures are about the SELECTED series,
   this is about the whole chart. Pushed to the far end of the strip so it does not read as one more
   line entry — the same device, and the same narrow-screen caveat, as .stat-since below. */
.chart-habit[b-ryjq8g2jmm] {
    margin-left: auto;
}

/* margin-left:auto strands it against the edge under a half-empty wrapped line, exactly as it would
   .stat-since. Same breakpoint, so the two rows give up the trick together. */
@media (max-width: 560px) {
    .chart-habit[b-ryjq8g2jmm] {
        margin-left: 0;
    }
}

/* The anchor's legend swatch. It is the one entry that is not an offer — the viewer's own region at the
   price Nintendo charges — so it reads as a RULE rather than as a filled block, matching the dashed
   stroke it stands for. The background comes from the inline style, as it does for every other swatch;
   this only reshapes it. */
.legend-swatch.anchor[b-ryjq8g2jmm] {
    height: 0;
    border-radius: 0;
    background: none !important;
    border-top: 2px dashed var(--mut);
    align-self: center;
}
/* /Components/Rail.razor.rz.scp.css */
/* Horizontal rail. Same scrolling machinery as before (no desktop scroll-snap — see index.html
   railInit); skinned with tokens + the chart-rail rank numerals. */

.rail[b-giuahp7sbm] {
    margin: 0 0 1.6rem;
}

.rail-head[b-giuahp7sbm] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.6rem;
}

.rail-title[b-giuahp7sbm] {
    font-size: 1.1rem;
    font-weight: 900;
    margin: 0;
    color: var(--tx);
}

.rail-more[b-giuahp7sbm] {
    font-size: 0.8rem;
    font-weight: 800;
    color: var(--acc);
    text-decoration: none;
    white-space: nowrap;
    flex: 0 0 auto;
}

.rail-more:hover[b-giuahp7sbm] {
    text-decoration: underline;
}

.rail-viewport[b-giuahp7sbm] {
    position: relative;
}

.rail-track[b-giuahp7sbm] {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 4px 2px 0.6rem;
    scrollbar-width: none;      /* Firefox */
    -ms-overflow-style: none;   /* legacy Edge */
}

.rail-track[b-giuahp7sbm]::-webkit-scrollbar {
    display: none;              /* Chrome / Safari */
}

/* A MOUSE GETS THE NATIVE BAR BACK, ON HOVER (owner, 2026-09-03: "как на youtube"). The arrows and
   drag-to-scroll are what a rail says "there is more" with, and they stay — this is the third
   affordance the two of them cannot be: a POSITION readout. Restricted to hover+fine pointers
   because a finger scrolls the track directly, has no hover to reveal anything with, and gets
   native momentum and snap instead.
   Only the RAILS, deliberately: the feed's clustered add-on strips keep the bar hidden, since a
   classic bar costs its height under EVERY cluster in the grid, several per screen.
   THE LANE IS RESERVED AT REST, NOT CREATED ON HOVER. A scrollbar entering the flow grows the track
   by its own height and pushes each rail below it — and the catalog grid under those — down on
   mouse-in and back on mouse-out, which is a page that twitches when the pointer crosses it. So the
   bar is always present and always occupies its strip; only its INK is switched, and it is paid for
   out of the bottom padding (0.6rem → 2px) so the rail is no taller than it was. */
@media (hover: hover) and (pointer: fine) {
    .rail-track[b-giuahp7sbm] {
        padding-bottom: 2px;
        /* Standard properties first — modern Chrome and Firefox honour these and ignore the
           ::-webkit-scrollbar rules below entirely once either one is set. Transparent on both
           tracks means "present, invisible". */
        scrollbar-width: thin;
        scrollbar-color: transparent transparent;
        transition: scrollbar-color 0.15s;
    }

    /* Legacy WebKit (and any engine without the standard pair) reaches the same look this way. */
    .rail-track[b-giuahp7sbm]::-webkit-scrollbar {
        display: block;
        height: 8px;
    }

    .rail-track[b-giuahp7sbm]::-webkit-scrollbar-track {
        background: transparent;
    }

    .rail-track[b-giuahp7sbm]::-webkit-scrollbar-thumb {
        background: transparent;
        border-radius: 999px;
    }

    /* Revealed from the whole rail, title included, so it does not blink as the pointer travels
       between the heading and the cards. */
    .rail:hover .rail-track[b-giuahp7sbm] {
        scrollbar-color: var(--ln2) transparent;
    }

    .rail:hover .rail-track[b-giuahp7sbm]::-webkit-scrollbar-thumb {
        background: var(--ln2);
    }

    .rail:hover .rail-track[b-giuahp7sbm]::-webkit-scrollbar-thumb:hover {
        background: var(--mut);
    }
}

.rail-item[b-giuahp7sbm] {
    flex: 0 0 200px;
    max-width: 200px;
    position: relative;
}

/* Chart rail: the numeral is a FLEX SIBLING of the card, not a decoration floating in a fixed
   gutter — so each item is exactly as wide as its own rank needs and no item carries space for a
   digit it does not have.

   A fixed gutter cannot do this, and both failures were visible. Sized for the widest rank it hid
   digits (22px against numerals 41–78px wide: 45% of every single digit, and the whole second digit
   of 10..15, which all rendered as the same clipped "1"). Sized for the widest numeral instead, the
   numeral is right-aligned in it, so ALL the slack piles up on its left — i.e. in the gap after the
   PREVIOUS card, and the slack is whatever that particular number does not use. Measured on the
   phone: 41px of blank after rank 1, 24px after 10 (a narrow "11" follows), 13px after 11, 11px
   after 14. Nintendo's own numerals differ by 3× in width — Unbounded's "1" is 21px against 60px
   for "10" — so no single gutter is right for all fifteen.

   In flow, the item's width is its own numeral plus the card, so the blank strip the eye reads
   between two cards is just the track's gap — the same 16px (11.2px on a phone) for every item,
   whatever number is in it, measured. Nothing to keep in step with the font, either: change the
   size below and the layout follows. */
.rail-track.ranked .rail-item[b-giuahp7sbm] {
    --rank-tuck: 2px;     /* the numeral slides back under its own card by a hairline, see below */
    display: flex;
    flex: 0 0 auto;
    max-width: none;
}

.rail-track.ranked .rail-item[b-giuahp7sbm]  .game-card {
    flex: 0 0 200px;
}

/* Chart position — a big "pencil" numeral against the card's left edge. Light theme (base): a
   penciled-in look — crisp stroke + a faint graphite fill so it reads on the milk background.
   Dark theme (below): a pure airy outline, since the covers already glow there.

   --rank-tuck is a HAIRLINE, not the "peeking from behind" overlap this started as. An overlap
   only reads as depth when it is a large fraction of the shape; at this size it reads as a cut,
   and worst on the numeral that matters most — a 6px tuck took 28% of that 21px "1" and turned
   rank 1 into something like a "T". The card paints over it (it is the positioned one, z-index 1),
   so no z-index is needed here. */
.rail-rank[b-giuahp7sbm] {
    align-self: flex-end;
    flex: 0 0 auto;
    margin: 0 calc(-1 * var(--rank-tuck)) 52px 0;
    font-family: var(--fl);
    font-weight: 800;
    font-size: 44px;
    line-height: 0.8;
    letter-spacing: -0.05em;
    white-space: nowrap;
    color: color-mix(in srgb, var(--tx2) 12%, transparent);
    -webkit-text-stroke: 3px var(--tx2);
    opacity: 0.9;
    pointer-events: none;
    user-select: none;
}

[data-theme="dark"] .rail-rank[b-giuahp7sbm] {
    color: transparent;
    -webkit-text-stroke-width: 2px;
    opacity: 0.5;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) .rail-rank[b-giuahp7sbm] {
        color: transparent;
        -webkit-text-stroke-width: 2px;
        opacity: 0.5;
    }
}

.rail-item[b-giuahp7sbm]  .game-card {
    height: 100%;
    z-index: 1;
}

/* Touch: native momentum + snap (safe there — see note above). */
@media (hover: none) and (pointer: coarse) {
    .rail-track[b-giuahp7sbm] {
        scroll-snap-type: x proximity;
    }

    .rail-item[b-giuahp7sbm] {
        scroll-snap-align: start;
    }
}

/* Arrow buttons over the row edges, centered on the cover art. */
.rail-nav[b-giuahp7sbm] {
    position: absolute;
    top: 6.2rem;
    transform: translateY(-50%);
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ln);
    border-radius: 50%;
    background: var(--sf);
    color: var(--tx);
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    z-index: 5;
    box-shadow: var(--card-sh);
    opacity: 0.95;
    transition: opacity 0.15s, border-color 0.15s;
}

.rail-nav > span[b-giuahp7sbm] {
    margin-top: -3px; /* optical centering of the chevron glyph */
}

.rail-nav:hover[b-giuahp7sbm] {
    border-color: var(--mut);
    opacity: 1;
}

.rail-prev[b-giuahp7sbm] {
    left: -8px;
}

.rail-next[b-giuahp7sbm] {
    right: -8px;
}

@media (max-width: 768px) {
    .rail-nav[b-giuahp7sbm] {
        display: none; /* touch: swipe to scroll */
    }
}

@media (max-width: 480px) {
    .rail-track[b-giuahp7sbm] {
        gap: 0.7rem;
    }

    .rail-item[b-giuahp7sbm] {
        flex-basis: 150px;
        max-width: 150px;
    }

    .rail-track.ranked .rail-item[b-giuahp7sbm]  .game-card {
        flex-basis: 150px;
    }

    .rail-rank[b-giuahp7sbm] {
        font-size: 34px;
        margin-bottom: 54px;
    }

    .rail-title[b-giuahp7sbm] {
        font-size: 1rem;
    }
}

/* Short landscape: a phone on its side is ~390px tall and the chrome above the feed used to eat
   two thirds of it. Vertical only — portrait and desktop are untouched. */
@media (orientation: landscape) and (max-height: 520px) {
    .rail[b-giuahp7sbm] {
        margin-bottom: 1rem;
    }

    .rail-head[b-giuahp7sbm] {
        margin-bottom: 0.35rem;
    }
}
/* /Components/RegionOnboarding.razor.rz.scp.css */
/* First-run region offer. Deliberately the same furniture as the header's own sheets — the dim, the
   18px card, --frame-sh — because this IS that setup, shown once without being hunted for. What differs
   is that it is centred and never becomes a bottom sheet on a phone: it is a decision to read, not a
   list to scroll, and a sheet rising from the bottom edge reads as dismissible-by-swipe, which this
   should not be. */

.geo-dim[b-9c553zsnk1] {
    position: fixed;
    inset: 0;
    z-index: 1190;
    border: 0;
    padding: 0;
    background: rgba(16, 12, 8, 0.44);
    backdrop-filter: blur(2px);
    cursor: pointer;
}

.geo-card[b-9c553zsnk1] {
    position: fixed;
    z-index: 1200;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(430px, calc(100vw - 28px));
    max-height: min(88vh, 660px);
    overflow-y: auto;
    background: var(--sf);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 18px;
    box-shadow: var(--frame-sh);
    padding: 1.15rem 1.15rem 0.9rem;
    font-family: var(--fd);
}

/* Heading and the language corner share a row. The picker is deliberately quiet: since the page already
   comes up in the browser's language, this is where you CORRECT that, not where you set it. */
.geo-top[b-9c553zsnk1] {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin-bottom: 0.45rem;
}

.geo-head[b-9c553zsnk1] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-size: 1.12rem;
    font-weight: 800;
    line-height: 1.25;
}

.geo-lang[b-9c553zsnk1] {
    flex: 0 0 auto;
    max-width: 8.5em;
    margin-top: 0.05rem;
    padding: 0.2rem 0.3rem;
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--mut);
    background: var(--bg);
    border: 1px solid var(--ln);
    border-radius: 8px;
    cursor: pointer;
}

.geo-lang:hover[b-9c553zsnk1] {
    color: var(--tx);
    border-color: var(--acc-brd);
}

.geo-lang:focus-visible[b-9c553zsnk1] {
    outline: 2px solid var(--acc);
    outline-offset: 1px;
}

.geo-globe[b-9c553zsnk1] {
    font-size: 1.35rem;
    line-height: 1;
}

.geo-lead[b-9c553zsnk1] {
    margin: 0 0 0.9rem;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--mut);
}

/* The picker is the escape hatch the whole dialog rests on, so it is a full-width row with a real
   label rather than a link or an "advanced" disclosure — a detected country you cannot correct in one
   gesture is the pattern this design is trying not to be. */
.geo-pick[b-9c553zsnk1] {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin-bottom: 0.9rem;
}

.geo-pick-lbl[b-9c553zsnk1] {
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--mut);
}

.geo-pick select[b-9c553zsnk1] {
    width: 100%;
    padding: 0.55rem 0.6rem;
    font: inherit;
    font-size: 0.95rem;
    color: var(--tx);
    background: var(--bg);
    border: 1px solid var(--ln);
    border-radius: 10px;
}

.geo-pick select:focus-visible[b-9c553zsnk1] {
    outline: 2px solid var(--acc);
    outline-offset: 1px;
}

/* WHERE THEY ALREADY TOP UP — the one shop question this dialog asks, and the reason the whole-setup
   button is allowed to touch the shops at all: it used to claim an account at every shop that funds their
   currency, which is the assumption this feature removes.

   Chips rather than a column of checkboxes: the answer is usually none or one, the names are one word,
   and a form in a first-run dialog is the thing this design is trying not to be. They wrap, because seven
   shops appear for a reader whose currency nothing funds. */
.geo-shops[b-9c553zsnk1] {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 0.9rem;
}

.geo-shops-lbl[b-9c553zsnk1] {
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--mut);
}

.geo-shops-row[b-9c553zsnk1] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.geo-shop[b-9c553zsnk1] {
    font: inherit;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--tx2);
    background: var(--bg);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 5px 11px;
    cursor: pointer;
}

.geo-shop.on[b-9c553zsnk1] {
    border-color: var(--acc);
    color: var(--acc);
    background: var(--acc-soft);
}

.geo-shop:focus-visible[b-9c553zsnk1] {
    outline: 2px solid var(--acc);
    outline-offset: 2px;
}

/* "Nowhere yet" is the ANSWER none of the shops is, not a fourth shop — so it is set apart, and it is
   LIT when nothing is ticked. A resting state that lights nothing would read as an unanswered question,
   and the whole point is that "nowhere yet" is a real and common answer. */
/* Set apart by its TYPE, not by being pushed to the far edge: `margin-left: auto` in a wrapping row
   sent it to its own second line as soon as four shops filled the first, which reads as a layout
   accident rather than as a deliberate separation. */
.geo-shop.none[b-9c553zsnk1] {
    font-weight: 600;
    font-style: italic;
    border-style: dashed;
}

/* What "set it all up" will do, itemised. Sunk into the page tint so it reads as a preview of a state
   rather than as more controls. */
.geo-plan[b-9c553zsnk1] {
    margin: 0 0 1rem;
    padding: 0.6rem 0.7rem;
    list-style: none;
    background: var(--bg);
    border: 1px solid var(--ln);
    border-radius: 12px;
    font-size: 0.85rem;
}

.geo-plan li[b-9c553zsnk1] {
    display: flex;
    gap: 0.6rem;
    align-items: baseline;
    padding: 0.16rem 0;
}

.geo-plan-k[b-9c553zsnk1] {
    flex: 0 0 7.4em;
    color: var(--mut);
}

.geo-plan-v[b-9c553zsnk1] {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 600;
    overflow-wrap: anywhere;
}

/* Three buttons, three sizes of commitment, drawn in that order so the weight of the ink matches the
   weight of the action. Skip is quiet but never hidden: a dismiss that has to be hunted for is how a
   one-time dialog becomes the thing people remember about a site. */
.geo-actions[b-9c553zsnk1] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    align-items: stretch;
}

.geo-btn[b-9c553zsnk1] {
    flex: 1 1 auto;
    min-height: 40px;
    padding: 0.5rem 0.7rem;
    font: inherit;
    font-size: 0.86rem;
    font-weight: 700;
    border-radius: 10px;
    border: 1px solid var(--ln);
    background: var(--sf);
    color: var(--tx);
    cursor: pointer;
}

.geo-btn:hover:not(:disabled)[b-9c553zsnk1] {
    border-color: var(--acc-brd);
}

.geo-btn:disabled[b-9c553zsnk1] {
    opacity: 0.45;
    cursor: default;
}

.geo-skip[b-9c553zsnk1] {
    flex: 0 0 auto;
    color: var(--mut);
    background: transparent;
    border-color: transparent;
}

.geo-all[b-9c553zsnk1] {
    background: var(--acc);
    border-color: var(--acc-brd);
    color: var(--acc-ink);
}

.geo-foot[b-9c553zsnk1] {
    margin: 0.75rem 0 0;
    font-size: 0.76rem;
    line-height: 1.45;
    color: var(--mut);
}

/* Phones: the two apply buttons need the full width to keep their labels on one line, and Skip drops
   under them rather than competing for the row. */
@media (max-width: 420px) {
    .geo-actions[b-9c553zsnk1] {
        flex-direction: column-reverse;
    }

    .geo-skip[b-9c553zsnk1] {
        flex: 1 1 auto;
    }

    .geo-plan-k[b-9c553zsnk1] {
        flex-basis: 6.4em;
    }
}
/* /Components/ReportFlag.razor.rz.scp.css */
/* The report flag, three sizes.

   QUIET BY DEFAULT, EVERYWHERE. This control competes with prices for attention and must lose: it is
   the muted ink until it is hovered or focused. The one place that is not enough is a list of add-on
   rows, where twenty of them stack — those are dimmed further still (.rf-icon), which is affordable
   only because that row's whole job is to be reported on. */

.rf[b-wijb7tehp2] {
    display: inline-flex;
    align-items: center;
    gap: 0.38rem;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: var(--mut);
    font-family: var(--fd);
    font-weight: 700;
    line-height: 1.2;
    cursor: pointer;
    /* A tap must not select the label text on the way to the click. */
    -webkit-tap-highlight-color: transparent;
    user-select: none;
}

.rf svg[b-wijb7tehp2] {
    width: 0.78em;
    height: 0.86em;
    flex: none;
}

.rf-t[b-wijb7tehp2] {
    /* The label wraps rather than pushing its row sideways: on a 320px screen "Сообщить о проблеме"
       plus a neighbouring line does not fit, and a flag that widens its row is how a footer starts
       scrolling horizontally. */
    min-width: 0;
    overflow-wrap: anywhere;
}

/* ── link: the standing invitation under a block ─────────────────────────────────────────────── */
.rf-link[b-wijb7tehp2] {
    padding: 0.3rem 0.5rem;
    font-size: 0.82rem;
}

/* ── inline: inside a row that is already open ───────────────────────────────────────────────── */
.rf-inline[b-wijb7tehp2] {
    padding: 0.22rem 0.42rem;
    font-size: 0.76rem;
}

/* ── icon: one per row in a list ─────────────────────────────────────────────────────────────── */
.rf-icon[b-wijb7tehp2] {
    padding: 0.4rem;
    opacity: 0.42;
}

.rf-icon svg[b-wijb7tehp2] {
    width: 0.82rem;
    height: 0.9rem;
}

/* ── states ──────────────────────────────────────────────────────────────────────────────────── */
.rf:hover[b-wijb7tehp2],
.rf:focus-visible[b-wijb7tehp2] {
    color: var(--acc);
    background: var(--acc-soft);
    opacity: 1;
}

.rf:focus-visible[b-wijb7tehp2] {
    outline: 2px solid var(--acc);
    outline-offset: 1px;
}

/* Filed. Not a button any more — there is nothing left to press — so it carries no hover and no
   pointer, and it keeps the row's height so nothing jumps when it swaps in. */
.rf-done[b-wijb7tehp2] {
    color: var(--deal);
    opacity: 1;
    cursor: default;
}

.rf-done.rf-icon[b-wijb7tehp2] {
    opacity: 0.85;
}

/* ── touch ───────────────────────────────────────────────────────────────────────────────────── */
/* On a phone there is no hover to reveal anything, so the icon variant sits a little more visible and
   every variant grows to a real target. 44px is the floor a thumb needs; the glyph stays its size and
   the box grows around it, so the row's rhythm does not change. */
@media (pointer: coarse) {
    .rf-icon[b-wijb7tehp2] {
        opacity: 0.55;
        padding: 0.62rem;
        margin: -0.22rem;
    }

    .rf-link[b-wijb7tehp2],
    .rf-inline[b-wijb7tehp2] {
        padding: 0.5rem 0.55rem;
        margin: -0.2rem -0.1rem;
    }

    .rf-link[b-wijb7tehp2] {
        font-size: 0.86rem;
    }
}

/* A coarse pointer with no hover must not leave the pressed state stuck on: :active is what a touch
   actually produces, and without it the tap gives no feedback at all before the sheet paints. */
.rf:active[b-wijb7tehp2] {
    color: var(--acc);
    background: var(--acc-soft);
}
/* /Components/ReportSheet.razor.rz.scp.css */
/* The report sheet.

   TWO SHAPES, ONE COMPONENT. On a phone it rises from the bottom edge and may fill most of the screen;
   on a desktop it is a centred card. That is the opposite of RegionOnboarding, which is centred
   everywhere on purpose — and the difference is real: that one is a decision to read once, this is a
   form to fill, so it belongs under the thumb and should look dismissible, because nobody is obliged
   to finish it.

   Every tap target here clears 44px on a coarse pointer. The reasons are the whole interaction: on the
   commonest path a reader taps one and sends, so they are full-width rows rather than radio dots. */

.rs-dim[b-8fg5nthsw6] {
    position: fixed;
    inset: 0;
    z-index: 1190;
    border: 0;
    padding: 0;
    background: rgba(16, 12, 8, 0.44);
    backdrop-filter: blur(2px);
    cursor: pointer;
}

.rs-card[b-8fg5nthsw6] {
    position: fixed;
    z-index: 1200;
    background: var(--sf);
    color: var(--tx);
    border: 1px solid var(--ln);
    box-shadow: var(--frame-sh);
    font-family: var(--fd);
    display: flex;
    flex-direction: column;
}

/* ── phone: a sheet on the bottom edge ───────────────────────────────────────────────────────── */
/* The default, not the override. This is the majority case for a site people open on the sofa, and
   writing it first means the desktop rule is the exception that has to justify itself. */
.rs-card[b-8fg5nthsw6] {
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 18px 18px 0 0;
    border-bottom: 0;
    /* Never taller than the visible viewport, keyboard included. dvh follows the phone's collapsing
       address bar; vh does not, and a sheet sized in vh hides its own send button behind it. */
    max-height: 92dvh;
    /* The home-indicator strip on a modern phone would otherwise eat the send button's bottom half. */
    padding-bottom: env(safe-area-inset-bottom, 0);
}

.rs-grip[b-8fg5nthsw6] {
    flex: none;
    width: 36px;
    height: 4px;
    margin: 0.5rem auto 0.1rem;
    border-radius: 999px;
    background: var(--ln2, var(--ln));
}

/* ── desktop: a centred card ─────────────────────────────────────────────────────────────────── */
@media (min-width: 640px) {
    .rs-card[b-8fg5nthsw6] {
        left: 50%;
        right: auto;
        bottom: auto;
        top: 50%;
        transform: translate(-50%, -50%);
        width: min(430px, calc(100vw - 28px));
        max-height: min(88vh, 680px);
        border-radius: 18px;
        border-bottom: 1px solid var(--ln);
        padding-bottom: 0;
    }

    .rs-grip[b-8fg5nthsw6] {
        display: none;
    }
}

/* ── head ────────────────────────────────────────────────────────────────────────────────────── */
.rs-head[b-8fg5nthsw6] {
    flex: none;
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.85rem 1.05rem 0.35rem;
}

.rs-head h2[b-8fg5nthsw6] {
    flex: 1 1 auto;
    margin: 0;
    font-size: 1.12rem;
    font-weight: 800;
    line-height: 1.25;
    text-wrap: balance;
}

.rs-x[b-8fg5nthsw6] {
    flex: none;
    border: 0;
    background: transparent;
    color: var(--mut);
    font-size: 1rem;
    line-height: 1;
    /* 44px even though the glyph is 16 — this sits in the corner a thumb reaches for by feel. */
    padding: 0.72rem;
    margin: -0.6rem -0.5rem -0.5rem 0;
    border-radius: 10px;
    cursor: pointer;
}

.rs-x:hover[b-8fg5nthsw6],
.rs-x:focus-visible[b-8fg5nthsw6] {
    color: var(--tx);
    background: var(--acc-soft);
}

.rs-chips[b-8fg5nthsw6] {
    flex: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0.28rem;
    padding: 0 1.05rem 0.6rem;
}

.rs-chip[b-8fg5nthsw6] {
    font-family: var(--fn);
    font-size: 0.72rem;
    font-weight: 700;
    padding: 0.16rem 0.5rem;
    border-radius: 999px;
    border: 1px solid var(--ln);
    background: var(--bg);
    color: var(--mut);
    white-space: nowrap;
}

/* ── body ────────────────────────────────────────────────────────────────────────────────────── */
/* The only scrolling region, so the heading stays put while a long reason list moves under it. */
.rs-body[b-8fg5nthsw6] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: 0 1.05rem 1rem;
}

.rs-reasons[b-8fg5nthsw6] {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.rs-opt[b-8fg5nthsw6] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    /* 0.62rem top and bottom on a 0.88rem line clears 44px. The whole row is the target, not a dot. */
    padding: 0.62rem 0.7rem;
    border: 1px solid var(--ln);
    border-radius: 11px;
    background: var(--sf);
    color: var(--tx);
    font-family: var(--fd);
    font-size: 0.88rem;
    font-weight: 500;
    line-height: 1.3;
    text-align: left;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.rs-opt.on[b-8fg5nthsw6] {
    border-color: var(--acc);
    background: var(--acc-soft);
    font-weight: 700;
}

.rs-opt:focus-visible[b-8fg5nthsw6] {
    outline: 2px solid var(--acc);
    outline-offset: 1px;
}

.rs-mark[b-8fg5nthsw6] {
    flex: none;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    border: 1.5px solid var(--ln2, var(--ln));
    background: var(--sf);
}

.rs-opt.on .rs-mark[b-8fg5nthsw6] {
    border-color: var(--acc);
    border-width: 4.5px;
}

/* ── "what we already know" ──────────────────────────────────────────────────────────────────── */
/* Deliberately warm rather than red: this is not an error, it is the site answering the question
   before it is asked, and most readers who read it will close the sheet instead of sending. */
.rs-known[b-8fg5nthsw6] {
    border-radius: 11px;
    padding: 0.65rem 0.75rem;
    background: color-mix(in srgb, var(--warn) 12%, var(--sf));
    font-family: var(--fn);
    font-size: 0.79rem;
    line-height: 1.45;
}

.rs-known h3[b-8fg5nthsw6] {
    margin: 0 0 0.4rem;
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--warn);
}

.rs-known ul[b-8fg5nthsw6] {
    margin: 0;
    padding-left: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.22rem;
}

/* ── fields ──────────────────────────────────────────────────────────────────────────────────── */
.rs-field[b-8fg5nthsw6] {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.rs-lbl[b-8fg5nthsw6] {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--mut);
}

.rs-in[b-8fg5nthsw6] {
    width: 100%;
    border: 1px solid var(--ln2, var(--ln));
    border-radius: 10px;
    padding: 0.62rem 0.7rem;
    background: var(--bg);
    color: var(--tx);
    font-family: var(--fn);
    /* 16px exactly. Anything smaller and iOS Safari zooms the page on focus, which on a bottom sheet
       scrolls the send button off-screen and does not zoom back out. */
    font-size: 16px;
    line-height: 1.4;
}

textarea.rs-in[b-8fg5nthsw6] {
    resize: vertical;
    min-height: 2.6rem;
}

.rs-in:focus-visible[b-8fg5nthsw6] {
    outline: 2px solid var(--acc);
    outline-offset: 0;
    border-color: var(--acc);
}

.rs-privacy[b-8fg5nthsw6] {
    margin: 0;
    font-size: 0.72rem;
    line-height: 1.5;
    color: var(--mut);
}

.rs-error[b-8fg5nthsw6] {
    margin: 0;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--acc);
}

.rs-send[b-8fg5nthsw6] {
    width: 100%;
    border: 1px solid var(--acc-brd, var(--acc));
    border-radius: 11px;
    padding: 0.72rem;
    background: var(--acc);
    color: var(--acc-ink);
    font-family: var(--fd);
    font-size: 0.95rem;
    font-weight: 800;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.rs-send:disabled[b-8fg5nthsw6] {
    opacity: 0.6;
    cursor: default;
}

.rs-send:focus-visible[b-8fg5nthsw6] {
    outline: 2px solid var(--tx);
    outline-offset: 2px;
}

/* ── sent ────────────────────────────────────────────────────────────────────────────────────── */
.rs-done[b-8fg5nthsw6] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
    padding: 1.2rem 1.05rem 1.15rem;
    text-align: center;
}

.rs-tick[b-8fg5nthsw6] {
    width: 46px;
    height: 46px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: var(--deal);
    background: var(--deal-bg);
    border: 1px solid var(--deal-brd);
}

.rs-tick svg[b-8fg5nthsw6] {
    width: 24px;
    height: 24px;
}

.rs-done h2[b-8fg5nthsw6] {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 800;
}

.rs-num[b-8fg5nthsw6] {
    margin: 0;
    font-family: var(--fn);
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--mut);
    font-variant-numeric: tabular-nums;
}

.rs-note[b-8fg5nthsw6] {
    margin: 0 0 0.35rem;
    font-size: 0.83rem;
    line-height: 1.5;
    color: var(--mut);
    max-width: 30ch;
}
/* /Components/SearchBar.razor.rz.scp.css */
/* Big friendly search capsule («Джой»), flat by design. */
.search-bar[b-us1ng0zag1] {
    margin-bottom: 0.9rem;
}

.search-input[b-us1ng0zag1] {
    width: 100%;
    max-width: 480px;
    background: var(--sf);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 0.6rem 1.1rem;
    font-size: 0.95rem;
    font-family: inherit;
    font-weight: 600;
    transition: border-color 0.15s;
}

.search-input[b-us1ng0zag1]::placeholder {
    color: var(--mut);
}

.search-input:focus[b-us1ng0zag1] {
    outline: none;
    border-color: var(--acc);
}

/* Short landscape: a phone on its side is ~390px tall and the chrome above the feed used to eat
   two thirds of it. Vertical only — portrait and desktop are untouched. */
@media (orientation: landscape) and (max-height: 520px) {
    .search-bar[b-us1ng0zag1] {
        margin-bottom: 0.5rem;
    }

    .search-input[b-us1ng0zag1] {
        padding-top: 0.35rem;
        padding-bottom: 0.35rem;
    }
}
/* /Components/StarRating.razor.rz.scp.css */
/* Read-only fractional display: a gold layer clipped over a dim track. */
.stars-ro[b-hsoxf4b11a] {
    position: relative;
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    font-size: 0.95rem;
}

/* Empty stars: hollow outline (not a solid dark glyph) in a theme-aware colour, so "no rating"
   reads as empty on both the milk-paper light theme and the near-black dark theme. */
.stars-track[b-hsoxf4b11a] {
    color: transparent;
    -webkit-text-stroke: 1.1px var(--star-empty);
}

.stars-fill[b-hsoxf4b11a] {
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    color: var(--gold);
    white-space: nowrap;
}

/* Interactive input */
.stars-input[b-hsoxf4b11a] {
    display: inline-flex;
    gap: 2px;
}

.star-btn[b-hsoxf4b11a] {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-size: 1.5rem;
    line-height: 1;
    color: transparent;
    -webkit-text-stroke: 1.1px var(--star-empty);
    transition: color 0.1s, transform 0.1s;
}

.star-btn:hover[b-hsoxf4b11a] {
    transform: scale(1.15);
}

.star-btn.on[b-hsoxf4b11a] {
    color: var(--gold);
    -webkit-text-stroke-width: 0;
}
/* /Layout/MainLayout.razor.rz.scp.css */
/* Site header / nav / footer styles moved to GLOBAL wwwroot/css/app.css.
   Reason: the nav is built from <NavLink> child components, and Blazor's scoped
   CSS does not cross component boundaries — a scoped `.ns-tab`/`.ns-nav-link`
   rule never matches the <a> a NavLink renders, so the links rendered unstyled
   (default blue + underline). Global css is what makes them pick up the styling. */
/* /Pages/AdminAnalytics.razor.rz.scp.css */
/* Scoped to AdminAnalytics.razor. Everything draws from app.css's tokens so the page follows the
   site's light/dark themes without a second palette — the admin surfaces are the site, not a
   separate app. Nothing here renders through a child component: scoped CSS does not cross that
   boundary, and eleven breakdowns sharing one shape is worth more than the markup it saves. */

.an-head[b-bq3ux1cv19] {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 20px;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 18px;
}

.an-head .muted[b-bq3ux1cv19] {
    max-width: 68ch;
}

.an-tools[b-bq3ux1cv19] {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.an-chip[b-bq3ux1cv19] {
    font: 600 12px/1 var(--fn);
    padding: 7px 12px;
    border-radius: 999px;
    border: 1px solid var(--ln);
    background: var(--sf);
    color: var(--mut);
    cursor: pointer;
}

.an-chip:hover:not(:disabled)[b-bq3ux1cv19] {
    border-color: var(--acc-brd, var(--acc));
    color: var(--tx);
}

.an-chip.on[b-bq3ux1cv19] {
    background: var(--acc);
    border-color: var(--acc);
    color: var(--acc-ink);
}

.an-chip:disabled[b-bq3ux1cv19] {
    opacity: .5;
    cursor: default;
}

.an-note[b-bq3ux1cv19] {
    font: 500 13px/1.5 var(--fn);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-left: 3px solid var(--acc);
    border-radius: 0 8px 8px 0;
    padding: 10px 14px;
    margin: 0 0 16px;
}

.an-note.warn[b-bq3ux1cv19] {
    border-left-color: var(--warn);
    color: var(--warn);
}

/* ── the four headline numbers ───────────────────────────────────────────────────────── */
.an-kpis[b-bq3ux1cv19] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}

.an-kpi[b-bq3ux1cv19] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.k-title[b-bq3ux1cv19] {
    font: 700 14px/1.3 var(--fd);
}

.k-sub[b-bq3ux1cv19] {
    font: 500 11px/1.4 var(--fn);
    color: var(--mut);
    margin-bottom: 8px;
}

.k-stats[b-bq3ux1cv19] {
    display: flex;
    gap: 16px;
    align-items: baseline;
    flex-wrap: wrap;
}

.k-stats b[b-bq3ux1cv19] {
    font: 800 22px/1.1 var(--fn);
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
}

.k-stats b.warn[b-bq3ux1cv19] { color: var(--warn); }
.k-stats b.bad[b-bq3ux1cv19]  { color: var(--red, var(--acc)); }

.k-stats i[b-bq3ux1cv19] {
    font: 600 10px/1 var(--fn);
    font-style: normal;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--mut);
    margin-left: -12px;
}

/* ── panels ──────────────────────────────────────────────────────────────────────────── */
.an-grid[b-bq3ux1cv19] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 12px;
}

.an-panel[b-bq3ux1cv19] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 14px 16px 15px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

.an-panel.wide[b-bq3ux1cv19] {
    margin-bottom: 12px;
}

.an-panel > header[b-bq3ux1cv19] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
}

.an-panel h3[b-bq3ux1cv19] {
    margin: 0;
    font: 700 14px/1.3 var(--fd);
}

.an-hint[b-bq3ux1cv19] {
    margin: 0;
    font: 500 11px/1.5 var(--fn);
    color: var(--mut);
}

.an-hint code[b-bq3ux1cv19],
.an-note code[b-bq3ux1cv19] {
    font-size: .92em;
}

.an-link[b-bq3ux1cv19] {
    font: 600 11px/1 var(--fn);
    color: var(--mut);
    text-decoration: none;
}

.an-link:hover[b-bq3ux1cv19] { color: var(--acc); }

.small[b-bq3ux1cv19] { font-size: 12px; }

/* ── chart ───────────────────────────────────────────────────────────────────────────── */
/* The gutter the scale is drawn in. Wide enough for five digits plus a gap, so a busy day does not
   push a label under the plot. */
.an-chart[b-bq3ux1cv19] {
    position: relative;
    padding-left: 46px;
}

.an-scale span[b-bq3ux1cv19] {
    position: absolute;
    left: 0;
    width: 38px;
    text-align: right;
    font: 500 11px/1 var(--fn);
    font-variant-numeric: tabular-nums;
    color: var(--mut);
}

/* The readout's positioning context, so `left: N%` on the tooltip means N% of the PLOT rather than of
   the chart including its gutter — which is what lets the day slices place it with no pixel maths. */
.an-plot[b-bq3ux1cv19] {
    position: relative;
}

/* overflow stays HIDDEN (the SVG default, restated because it is load-bearing here): the scale is
   drawn in a gutter to the left of this box, so anything allowed to spill past x=0 lands on top of the
   axis numbers. */
.an-chart svg[b-bq3ux1cv19] {
    display: block;
    width: 100%;
    height: 190px;
    overflow: hidden;
}

/* preserveAspectRatio="none" stretches the drawing, so a stroke width set here would stretch with
   it. vector-effect keeps every line one pixel whatever the container does. */
.an-chart .line[b-bq3ux1cv19] {
    fill: none;
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

.an-chart .line.s1[b-bq3ux1cv19] { stroke: var(--acc); }
.an-chart .line.s2[b-bq3ux1cv19] { stroke: var(--chart-alt); }

.an-chart .grid[b-bq3ux1cv19] {
    stroke: var(--ln);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

.an-chart .backfill[b-bq3ux1cv19] {
    fill: var(--mut);
    opacity: .10;
}

/* ── readout ─────────────────────────────────────────────────────────────────────────── */
.an-chart .slice[b-bq3ux1cv19] {
    fill: transparent;
    cursor: crosshair;
}

.an-chart .xhair[b-bq3ux1cv19] {
    stroke: var(--mut);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

/* A circle's radius is in viewBox units, and the plot is stretched horizontally to fill its box — so
   an SVG <circle> renders as an ellipse whose flattening changes with the window. These markers are
   zero-length round-capped strokes instead: non-scaling-stroke keeps a stroke's WIDTH in screen
   pixels, so the cap stays round at any width. */
.an-chart .dot[b-bq3ux1cv19] {
    stroke-width: 8;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.an-chart .dot.s1[b-bq3ux1cv19] { stroke: var(--acc); }
.an-chart .dot.s2[b-bq3ux1cv19] { stroke: var(--chart-alt); }

.an-tip[b-bq3ux1cv19] {
    position: absolute;
    top: 6px;
    transform: translateX(12px);
    z-index: 2;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 132px;
    padding: 8px 10px;
    border-radius: 10px;
    background: var(--sf);
    border: 1px solid var(--ln2);
    box-shadow: 0 10px 26px -14px rgba(0, 0, 0, .55);
    font: 500 11.5px/1.3 var(--fn);
}

/* Past the midpoint it flips to the left of the crosshair, which keeps it inside the panel and off
   the lines either way. */
.an-tip.at-end[b-bq3ux1cv19] { transform: translateX(calc(-100% - 12px)); }

.an-tip .t-day[b-bq3ux1cv19] {
    font-weight: 700;
    color: var(--mut);
    text-transform: capitalize;
}

.an-tip .t-row[b-bq3ux1cv19] {
    display: flex;
    align-items: center;
    gap: 6px;
}

.an-tip .t-row b[b-bq3ux1cv19] {
    margin-left: auto;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.an-tip .t-row.t-mut[b-bq3ux1cv19] { color: var(--mut); }
.an-tip .t-row.t-bad[b-bq3ux1cv19] { color: var(--red, var(--acc)); }

.an-tip .sw[b-bq3ux1cv19] {
    width: 10px;
    height: 3px;
    border-radius: 2px;
    display: inline-block;
    flex: none;
}

.an-tip .sw.s1[b-bq3ux1cv19] { background: var(--acc); }
.an-tip .sw.s2[b-bq3ux1cv19] { background: var(--chart-alt); }

.an-tip .t-note[b-bq3ux1cv19] {
    margin-top: 2px;
    max-width: 170px;
    color: var(--mut);
    font-size: 10.5px;
    line-height: 1.35;
}

.an-axis[b-bq3ux1cv19] {
    display: flex;
    justify-content: space-between;
    font: 500 11px/1 var(--fn);
    color: var(--mut);
    margin-top: 6px;
}

.an-axis .peak[b-bq3ux1cv19] {
    font-variant-numeric: tabular-nums;
}

.an-legend[b-bq3ux1cv19] {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font: 500 11px/1.4 var(--fn);
    color: var(--mut);
}

.an-legend .sw[b-bq3ux1cv19] {
    width: 14px;
    height: 3px;
    border-radius: 2px;
    display: inline-block;
}

.an-legend .sw.s1[b-bq3ux1cv19] { background: var(--acc); }
.an-legend .sw.s2[b-bq3ux1cv19] { background: var(--chart-alt); }
.an-legend .sw.s3[b-bq3ux1cv19] { background: var(--mut); opacity: .3; height: 10px; width: 10px; border-radius: 3px; }

/* ── ranked bars ─────────────────────────────────────────────────────────────────────── */
.an-bars[b-bq3ux1cv19] {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.bar-row[b-bq3ux1cv19] {
    display: grid;
    grid-template-columns: minmax(70px, 38%) 1fr auto;
    gap: 9px;
    align-items: center;
}

.bar-key[b-bq3ux1cv19] {
    font: 600 12px/1.3 var(--fn);
    /* A game slug can outgrow its track; the full value is on the title attribute. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bar-track[b-bq3ux1cv19] {
    height: 8px;
    border-radius: 4px;
    background: var(--bg);
    border: 1px solid var(--ln);
    overflow: hidden;
}

.bar-fill[b-bq3ux1cv19] {
    display: block;
    height: 100%;
    background: var(--acc);
    opacity: .75;
}

.bar-row b[b-bq3ux1cv19] {
    font: 700 12px/1 var(--fn);
    font-variant-numeric: tabular-nums;
}

/* ── funnel ──────────────────────────────────────────────────────────────────────────── */
.an-funnel[b-bq3ux1cv19] {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.fn-row[b-bq3ux1cv19] {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 10px;
    align-items: center;
}

/* The label sits at full width over the fill rather than inside it: a 1% step would otherwise clip
   its own text, and a clipped label is a bug however small the bar. */
.fn-track[b-bq3ux1cv19] {
    position: relative;
    height: 28px;
    border-radius: 7px;
    background: var(--bg);
    border: 1px solid var(--ln);
    overflow: hidden;
}

.fn-fill[b-bq3ux1cv19] {
    position: absolute;
    inset: 0 auto 0 0;
    background: var(--acc);
    opacity: .16;
}

.fn-label[b-bq3ux1cv19] {
    position: relative;
    display: flex;
    align-items: center;
    height: 100%;
    padding: 0 10px;
    font: 600 12px/1 var(--fn);
    white-space: nowrap;
}

.fn-row b[b-bq3ux1cv19] {
    font: 800 13px/1 var(--fn);
    font-variant-numeric: tabular-nums;
}

/* ── key/value blocks ────────────────────────────────────────────────────────────────── */
.an-kv[b-bq3ux1cv19] {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.an-kv > div[b-bq3ux1cv19] {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    align-items: baseline;
}

.an-kv dt[b-bq3ux1cv19] {
    font: 500 12px/1.4 var(--fn);
    color: var(--mut);
}

.an-kv dd[b-bq3ux1cv19] {
    margin: 0;
    font: 700 13px/1.3 var(--fn);
    font-variant-numeric: tabular-nums;
}

.an-kv dd.bad[b-bq3ux1cv19] { color: var(--red, var(--acc)); }

.an-foot[b-bq3ux1cv19] {
    margin: 20px 0 0;
    font: 500 12px/1.6 var(--fn);
    color: var(--mut);
    max-width: 88ch;
}
/* /Pages/AdminDashboard.razor.rz.scp.css */
/* «Пульт» — the admin hub as a live status board. Bento grid; every card carries its own
   accent hue (--pc) for the icon chip, hover ring and arrow. Both themes via tokens. */

.dash-head[b-93z27q1jxu] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.6rem 1.1rem;
    margin-bottom: 1.1rem;
}

.admin-dash h1[b-93z27q1jxu] {
    color: var(--tx);
    font-size: 1.45rem;
    font-weight: 900;
    margin: 0;
}

/* One-line system pulse: idle summary or the running-crawl indicator. */
.sys-line[b-93z27q1jxu] {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--mut);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.sys-line .dot[b-93z27q1jxu] {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--deal);
    flex: 0 0 auto;
}

.sys-line.busy .dot[b-93z27q1jxu] {
    background: var(--gold);
    animation: pulse-b-93z27q1jxu 1.5s ease-in-out infinite;
}

.sys-line.busy[b-93z27q1jxu] {
    color: var(--gold);
}

@keyframes pulse-b-93z27q1jxu {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

/* Bento: 6 columns → 3+3 / 2+2+1+1. */
.pult[b-93z27q1jxu] {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 0.8rem;
}

.span3[b-93z27q1jxu] { grid-column: span 3; }
.span2[b-93z27q1jxu] { grid-column: span 2; }
.wide[b-93z27q1jxu]  { grid-column: 1 / -1; }

.pult-card[b-93z27q1jxu] {
    --pc: var(--acc);
    position: relative;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto 1fr;
    align-items: start;
    gap: 0.35rem 0.8rem;
    padding: 1rem 1.05rem 0.95rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    text-decoration: none;
    color: var(--tx);
    overflow: hidden;
    transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
}

.pult-card[b-93z27q1jxu]::after {
    content: '';
    position: absolute;
    inset: 0 0 auto;
    height: 3px;
    background: var(--pc);
    opacity: 0;
    transition: opacity 0.15s;
}

.pult-card:hover[b-93z27q1jxu] {
    transform: translateY(-2px);
    box-shadow: var(--card-sh);
    border-color: var(--mut);
    color: var(--tx);
}

.pult-card:hover[b-93z27q1jxu]::after {
    opacity: 1;
}

/* Per-function hues (accent stays for actions elsewhere; these are identities). */
.pult-card.crawl[b-93z27q1jxu] { --pc: var(--deal); }
.pult-card.match[b-93z27q1jxu] { --pc: var(--sw2-tx); }
.pult-card.proxy[b-93z27q1jxu] { --pc: var(--info); }
.pult-card.shops[b-93z27q1jxu] { --pc: var(--warn); }
.pult-card.users[b-93z27q1jxu] { --pc: var(--gold); }
.pult-card.cfg[b-93z27q1jxu]   { --pc: var(--mut); }
.pult-card.fire[b-93z27q1jxu]  { --pc: var(--red); }

.pc-ico[b-93z27q1jxu] {
    grid-row: 1 / 3;
    width: 42px;
    height: 42px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--pc);
    background: color-mix(in srgb, var(--pc) 13%, transparent);
}

.pc-ico svg[b-93z27q1jxu] {
    width: 21px;
    height: 21px;
}

.pc-text[b-93z27q1jxu] {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.pc-title[b-93z27q1jxu] {
    font-weight: 800;
    font-size: 0.95rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pc-sub[b-93z27q1jxu] {
    font-size: 0.76rem;
    color: var(--mut);
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pc-arrow[b-93z27q1jxu] {
    color: var(--mut);
    font-weight: 800;
    transition: color 0.15s, transform 0.15s;
}

.pult-card:hover .pc-arrow[b-93z27q1jxu] {
    color: var(--pc);
    transform: translateX(2px);
}

/* Live numbers along the card's bottom. */
.pc-stats[b-93z27q1jxu] {
    grid-column: 2 / 4;
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.4rem;
    margin-top: 0.55rem;
    align-self: end;
}

.pc-stat[b-93z27q1jxu] {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.pc-stat b[b-93z27q1jxu] {
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-size: 1.02rem;
    font-weight: 800;
    color: var(--tx);
    line-height: 1.2;
}

.pc-stat span[b-93z27q1jxu] {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--mut);
    white-space: nowrap;
}

.pc-stat.deal b[b-93z27q1jxu] { color: var(--deal); }
.pc-stat.warn b[b-93z27q1jxu] { color: var(--red); }

/* Hangfire: reachable only over the SSH tunnel. */
.pult-card.disabled[b-93z27q1jxu] {
    cursor: default;
    opacity: 0.75;
}

.pult-card.disabled:hover[b-93z27q1jxu] {
    transform: none;
    box-shadow: none;
    border-color: var(--ln);
}

.pult-card.disabled:hover[b-93z27q1jxu]::after {
    opacity: 0;
}

.pc-lock[b-93z27q1jxu] {
    font-family: var(--fn);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    color: var(--mut);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: 2px 6px;
    align-self: start;
}

/* Hangfire: a quiet full-width strip (it's a note, not a destination). */
.pult-card.wide[b-93z27q1jxu] {
    grid-template-rows: auto;
    align-items: center;
    padding: 0.6rem 1.05rem;
}

.pult-card.wide .pc-ico[b-93z27q1jxu] {
    grid-row: 1;
    width: 34px;
    height: 34px;
    border-radius: 10px;
}

.pult-card.wide .pc-ico svg[b-93z27q1jxu] {
    width: 17px;
    height: 17px;
}

@media (max-width: 1000px) {
    .span3[b-93z27q1jxu] { grid-column: span 6; }
    .span2[b-93z27q1jxu] { grid-column: span 3; }
}

@media (max-width: 620px) {
    .span2[b-93z27q1jxu] { grid-column: span 6; }

    .pc-stats[b-93z27q1jxu] {
        gap: 0.25rem 1rem;
    }
}
/* /Pages/AdminLinkBulk.razor.rz.scp.css */
/* The script and the batch history side by side: the box is written against the history, since the
   label being typed has to be new and the batch being retired has to be found. They collapse to one
   column on a narrow screen, where the history is the half that can wait. */
.bulk-grid[b-gcu905l0hm] {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 1.25rem;
    align-items: start;
}

@media (max-width: 900px) {
    .bulk-grid[b-gcu905l0hm] { grid-template-columns: minmax(0, 1fr); }
}

/* Monospace and no wrapping: the format is columns of fixed fields, and a soft-wrapped line reads as
   two decisions. The box scrolls sideways instead. */
.bulk-script[b-gcu905l0hm] {
    width: 100%;
    box-sizing: border-box;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.82rem;
    line-height: 1.5;
    white-space: pre;
    overflow-x: auto;
    padding: 0.6rem 0.7rem;
    color: var(--tx);
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 8px;
    resize: vertical;
}

.bulk-script:focus[b-gcu905l0hm] {
    outline: none;
    border-color: var(--acc);
}

.bulk-bar[b-gcu905l0hm] { margin-top: 0.6rem; }

.bulk-label[b-gcu905l0hm] { min-width: 14rem; }

/* Narrower than the label: the label is required for a write and this one is usually empty. */
.bulk-agent[b-gcu905l0hm] { min-width: 10rem; }

.bulk-dry[b-gcu905l0hm] {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.85rem;
    color: var(--mut);
    cursor: pointer;
    user-select: none;
}

.bulk-warn[b-gcu905l0hm] {
    margin: 0.5rem 0 0;
    font-size: 0.82rem;
    color: var(--red);
}

/* The response is already the compact thing a reader wants — counts, then only the failures — so it
   is shown verbatim rather than parsed into a table that could disagree with it. */
.bulk-out[b-gcu905l0hm] {
    margin-top: 0.75rem;
    padding: 0.7rem 0.8rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    line-height: 1.55;
    white-space: pre;
    overflow-x: auto;
    color: var(--tx);
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 8px;
}

.bulk-h3[b-gcu905l0hm] {
    margin: 0 0 0.25rem;
    font-size: 1rem;
    font-weight: 600;
}

.bulk-batches code[b-gcu905l0hm] { font-size: 0.82rem; }

/* A timestamp broken across two lines reads as two figures. It is one field and the column has to
   fit it, so the width is stated rather than left to the table's own guess. */
.bulk-when[b-gcu905l0hm] { white-space: nowrap; }

.wb-th-when[b-gcu905l0hm] { width: 8.5rem; }

.bulk-danger[b-gcu905l0hm] {
    background: var(--red);
    border-color: var(--red);
    color: #fff;
}
/* /Pages/AdminMatchDetail.razor.rz.scp.css */
/* Match detail — token skin, capsule actions, both themes. */

.admin-detail-header[b-3frswkfsnz] {
    margin-bottom: 1rem;
}

.admin-detail-header a[b-3frswkfsnz] {
    color: var(--acc);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 700;
}

.match-meta[b-3frswkfsnz] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 1rem;
    color: var(--tx2);
    font-size: 0.85rem;
}

.confidence-pill[b-3frswkfsnz] {
    padding: 2px 9px;
    border-radius: 999px;
    font-weight: 800;
    font-size: 0.78rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.confidence-pill.high[b-3frswkfsnz] { background: var(--deal-bg); color: var(--deal); }
.confidence-pill.med[b-3frswkfsnz]  { background: rgba(217, 158, 20, 0.16); color: var(--gold); }
.confidence-pill.low[b-3frswkfsnz]  { background: var(--acc-soft); color: var(--red); }

.method-pill[b-3frswkfsnz] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
}

.status-pill[b-3frswkfsnz] {
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.66rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.status-pill.ok[b-3frswkfsnz] { background: var(--deal-bg); color: var(--deal); }
.status-pill.bad[b-3frswkfsnz] { background: var(--acc-soft); color: var(--red); }

.admin-actions[b-3frswkfsnz] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
}

.btn-action[b-3frswkfsnz] {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    padding: 0.45rem 1.05rem;
    border-radius: 999px;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
}

.btn-action:hover:not(:disabled)[b-3frswkfsnz] { opacity: 0.9; }

.btn-action.btn-secondary[b-3frswkfsnz] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
}

.btn-action.btn-secondary:hover:not(:disabled)[b-3frswkfsnz] { border-color: var(--mut); opacity: 1; }
.btn-action:disabled[b-3frswkfsnz] { opacity: 0.5; cursor: not-allowed; }

.btn-tiny[b-3frswkfsnz] {
    background: var(--sf);
    color: var(--tx2);
    border: 1px solid var(--ln);
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    margin-top: 4px;
}

.btn-tiny:hover[b-3frswkfsnz] { border-color: var(--mut); }
.btn-tiny:disabled[b-3frswkfsnz] { opacity: 0.5; cursor: not-allowed; }

.section-title[b-3frswkfsnz] {
    font-size: 1.1rem;
    font-weight: 900;
    margin: 1.5rem 0 0.75rem;
    color: var(--tx);
}

.region-grid[b-3frswkfsnz] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0.85rem;
}

/* Narrow screens: one full-width card per row so the meta (code/TitleId/nsuid) never overflows. */
@media (max-width: 640px) {
    .region-grid[b-3frswkfsnz] {
        grid-template-columns: 1fr;
    }
}

.region-card[b-3frswkfsnz] {
    background: var(--sf);
    border-radius: 12px;
    overflow: hidden;
    border: 1px solid var(--ln);
    display: flex;
    flex-direction: column;
}

.region-card.anchor[b-3frswkfsnz] {
    border-color: var(--acc);
    box-shadow: 0 0 0 1px var(--acc);
}

.region-card.alt[b-3frswkfsnz] {
    background: var(--sf2);
}

.region-card.chosen[b-3frswkfsnz] {
    border-color: var(--deal);
    box-shadow: 0 0 0 1px var(--deal);
}

.region-card-header[b-3frswkfsnz] {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: var(--sf2);
    border-bottom: 1px solid var(--ln);
    font-size: 0.85rem;
    color: var(--tx2);
}

.anchor-marker[b-3frswkfsnz] {
    background: var(--acc);
    color: var(--acc-ink);
    padding: 2px 7px;
    border-radius: 6px;
    font-size: 0.65rem;
    font-weight: 800;
    text-transform: uppercase;
}

.anchor-marker.chosen[b-3frswkfsnz] { background: var(--deal); }

.region-card-img[b-3frswkfsnz] {
    aspect-ratio: 1;
    background: var(--sf2);
    display: flex;
    align-items: center;
    justify-content: center;
}

.region-card-img img[b-3frswkfsnz] {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.region-card-img .no-image[b-3frswkfsnz] {
    color: var(--mut);
    font-size: 0.85rem;
}

.region-card-title[b-3frswkfsnz] {
    padding: 8px 10px;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--tx);
    line-height: 1.3;
}

.region-card-meta[b-3frswkfsnz] {
    padding: 0 10px 10px;
    color: var(--mut);
    font-size: 0.78rem;
}

.region-card-meta small[b-3frswkfsnz] { color: var(--mut); }

.meta-grid[b-3frswkfsnz] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 10px;
    margin-top: 6px;
}

.meta-grid label[b-3frswkfsnz] {
    color: var(--mut);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.meta-grid span[b-3frswkfsnz] {
    color: var(--tx);
    font-family: ui-monospace, monospace;
    font-size: 0.8rem;
}

.empty[b-3frswkfsnz] {
    text-align: center;
    padding: 1.5rem;
    color: var(--mut);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
}

.admin-search-row[b-3frswkfsnz] {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 1rem;
}

.admin-search-row select[b-3frswkfsnz] {
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    padding: 0.4rem 0.6rem;
    border-radius: 8px;
    font-family: inherit;
}

.muted[b-3frswkfsnz] { color: var(--mut); }

.method-hint[b-3frswkfsnz] {
    margin: -0.5rem 0 1rem;
    font-size: 0.85rem;
    color: var(--mut);
}

.region-card-meta code[b-3frswkfsnz] {
    font-family: ui-monospace, monospace;
    font-size: 0.72rem;
    background: var(--sf2);
    border: 1px solid var(--ln);
    padding: 1px 4px;
    border-radius: 4px;
    color: var(--tx2);
    word-break: break-all;
}

.sku-tag[b-3frswkfsnz] {
    display: inline-block;
    font-size: 0.65rem;
    padding: 0 6px;
    border-radius: 5px;
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.source-tag[b-3frswkfsnz] {
    display: inline-block;
    font-size: 0.68rem;
    padding: 1px 7px;
    border-radius: 5px;
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--sw2-tx);
    font-family: ui-monospace, monospace;
}
/* /Pages/AdminMatches.razor.rz.scp.css */
/* Match review — dense admin surface on the shared tokens (both themes). */

.muted[b-yjysnrmirv] {
    color: var(--mut);
    margin: 0 0 1rem;
    font-size: 0.9rem;
}

/* Stat tiles: big Manrope number over a quiet label. */
.admin-stats[b-yjysnrmirv] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.6rem;
    margin-bottom: 1.5rem;
}

.admin-stat-card[b-yjysnrmirv] {
    background: var(--sf);
    border: 1px solid var(--ln);
    padding: 0.7rem 0.9rem;
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.admin-stat-card .stat-value[b-yjysnrmirv] {
    font-size: 1.35rem;
    font-weight: 800;
    color: var(--tx);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.admin-stat-card .stat-label[b-yjysnrmirv] {
    font-size: 0.72rem;
    color: var(--mut);
    font-weight: 700;
}

.admin-stat-card.confidence-high[b-yjysnrmirv] { box-shadow: inset 3px 0 0 var(--deal); }
.admin-stat-card.confidence-med[b-yjysnrmirv]  { box-shadow: inset 3px 0 0 var(--gold); }
.admin-stat-card.confidence-low[b-yjysnrmirv]  { box-shadow: inset 3px 0 0 var(--red); }

/* Filter panel */
.admin-filters[b-yjysnrmirv] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 1rem;
    padding: 0.7rem 0.8rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
}

.admin-filters .search-input[b-yjysnrmirv] {
    flex: 1;
    min-width: 200px;
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    padding: 0.38rem 0.8rem;
    border-radius: 999px;
    font-family: inherit;
}

.admin-filters .search-input:focus[b-yjysnrmirv] {
    outline: none;
    border-color: var(--acc);
}

.admin-filters label[b-yjysnrmirv] {
    color: var(--tx2);
    font-size: 0.82rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 4px;
}

.admin-filters input[type=number][b-yjysnrmirv],
.admin-filters select[b-yjysnrmirv] {
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    padding: 0.3rem 0.5rem;
    border-radius: 8px;
    font-family: inherit;
}

.admin-filters input[type=number][b-yjysnrmirv] {
    width: 70px;
    font-family: var(--fn);
}

.btn-action[b-yjysnrmirv] {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    padding: 0.42rem 1.05rem;
    border-radius: 999px;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
}

.btn-action:hover:not(:disabled)[b-yjysnrmirv] { opacity: 0.9; }
.btn-action:disabled[b-yjysnrmirv] { opacity: 0.5; cursor: not-allowed; }

/* Match rows */
.match-list[b-yjysnrmirv] {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.match-row[b-yjysnrmirv] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 0.85rem 1rem;
    box-shadow: inset 4px 0 0 var(--ln);
}

.match-row.high[b-yjysnrmirv] { box-shadow: inset 4px 0 0 var(--deal); }
.match-row.med[b-yjysnrmirv]  { box-shadow: inset 4px 0 0 var(--gold); }
.match-row.low[b-yjysnrmirv]  { box-shadow: inset 4px 0 0 var(--red); }

.match-anchor[b-yjysnrmirv] {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.match-title[b-yjysnrmirv] {
    font-weight: 800;
    color: var(--tx);
    text-decoration: none;
}

.match-title:hover[b-yjysnrmirv] { color: var(--acc); }

.match-meta[b-yjysnrmirv] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    color: var(--mut);
}

.confidence-pill[b-yjysnrmirv] {
    padding: 2px 9px;
    border-radius: 999px;
    font-weight: 800;
    font-size: 0.75rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.confidence-pill.high[b-yjysnrmirv] { background: var(--deal-bg); color: var(--deal); }
.confidence-pill.med[b-yjysnrmirv]  { background: rgba(217, 158, 20, 0.16); color: var(--gold); }
.confidence-pill.low[b-yjysnrmirv]  { background: var(--acc-soft); color: var(--red); }

.method-pill[b-yjysnrmirv] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
}

.status-pill[b-yjysnrmirv] {
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.66rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.status-pill.ok[b-yjysnrmirv] { background: var(--deal-bg); color: var(--deal); }
.status-pill.bad[b-yjysnrmirv] { background: var(--acc-soft); color: var(--red); }

/* Per-region cover cards */
.match-regions[b-yjysnrmirv] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 0.6rem;
}

.region-card[b-yjysnrmirv] {
    background: var(--sf2);
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--ln);
    display: flex;
    flex-direction: column;
}

.region-card.anchor[b-yjysnrmirv] {
    border-color: var(--acc);
    box-shadow: 0 0 0 1px var(--acc);
}

.region-card-header[b-yjysnrmirv] {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    background: var(--sf);
    border-bottom: 1px solid var(--ln);
    font-size: 0.75rem;
    color: var(--tx2);
    flex-wrap: wrap;
}

.region-card-header strong[b-yjysnrmirv] { color: var(--tx); }

.anchor-marker[b-yjysnrmirv] {
    background: var(--acc);
    color: var(--acc-ink);
    padding: 1px 6px;
    border-radius: 5px;
    font-size: 0.6rem;
    font-weight: 800;
    text-transform: uppercase;
}

.dist-pill[b-yjysnrmirv], .sim-pill[b-yjysnrmirv] {
    background: var(--bg);
    border: 1px solid var(--ln);
    color: var(--mut);
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.65rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.region-card-img[b-yjysnrmirv] {
    aspect-ratio: 1;
    background: var(--sf2);
    display: flex;
    align-items: center;
    justify-content: center;
}

.region-card-img img[b-yjysnrmirv] {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.region-card-img .no-image[b-yjysnrmirv] {
    color: var(--mut);
    font-size: 0.75rem;
}

.region-card-title[b-yjysnrmirv] {
    padding: 6px 8px;
    font-size: 0.75rem;
    color: var(--tx);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pagination[b-yjysnrmirv] {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 1rem;
    margin-top: 1.5rem;
}

.btn-page[b-yjysnrmirv] {
    background: var(--sf);
    color: var(--tx2);
    border: 1px solid var(--ln);
    padding: 0.4rem 1.1rem;
    border-radius: 999px;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
}

.btn-page:hover:not(:disabled)[b-yjysnrmirv] { border-color: var(--mut); }
.btn-page:disabled[b-yjysnrmirv] { opacity: 0.4; cursor: not-allowed; }

.page-info[b-yjysnrmirv] {
    color: var(--mut);
    font-size: 0.85rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.empty[b-yjysnrmirv], .loading[b-yjysnrmirv] {
    text-align: center;
    padding: 2rem;
    color: var(--mut);
}
/* /Pages/AdminModeration.razor.rz.scp.css */
.mod-page[b-vvsgn9377h] { max-width: 980px; margin: 0 auto; padding: 1rem 0 3rem; }
.muted[b-vvsgn9377h] { color: var(--mut); }

.admin-stats[b-vvsgn9377h] { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.6rem; margin: 1rem 0; }
.admin-stat-card[b-vvsgn9377h] { background: var(--sf); border: 1px solid var(--ln); border-radius: 12px; padding: 0.7rem 0.9rem; }
.stat-value[b-vvsgn9377h] { font-size: 1.5rem; font-weight: 800; font-family: var(--fn); }
.stat-label[b-vvsgn9377h] { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--mut); }

.mod-toolbar[b-vvsgn9377h] { display: flex; gap: 1rem; align-items: center; margin: 0.5rem 0 1rem; font-size: 0.85rem; }
.mod-h[b-vvsgn9377h] { margin: 1.6rem 0 0.6rem; font-size: 1.05rem; }

.report-list[b-vvsgn9377h] { display: flex; flex-direction: column; gap: 0.7rem; }
.report-card[b-vvsgn9377h] { background: var(--sf); border: 1px solid var(--ln); border-radius: 12px; padding: 0.8rem 1rem; }
.report-card.resolved[b-vvsgn9377h] { opacity: 0.55; }
.report-top[b-vvsgn9377h] { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; font-size: 0.8rem; }
.report-reason[b-vvsgn9377h] {
    font-weight: 800; text-transform: uppercase; font-size: 0.66rem; letter-spacing: 0.04em;
    padding: 2px 8px; border-radius: 6px; background: var(--sf2); border: 1px solid var(--ln);
}
.reason-illegal[b-vvsgn9377h], .reason-offensive[b-vvsgn9377h] { color: var(--red); border-color: var(--red); }
.report-game[b-vvsgn9377h] { font-weight: 700; color: var(--acc); text-decoration: none; }
.report-date[b-vvsgn9377h] { color: var(--mut); margin-left: auto; }
.report-body[b-vvsgn9377h] {
    margin: 0.5rem 0; padding: 0.5rem 0.7rem; background: var(--sf2); border-radius: 8px;
    white-space: pre-wrap; font-size: 0.9rem; overflow-wrap: anywhere;
}
.report-meta[b-vvsgn9377h] { font-size: 0.8rem; color: var(--tx2); }
.report-meta a[b-vvsgn9377h] { color: var(--acc); text-decoration: none; }
.report-banned[b-vvsgn9377h] { color: var(--red); margin-left: 6px; font-size: 0.72rem; }
.report-actions[b-vvsgn9377h] { display: flex; gap: 6px; margin-top: 0.6rem; flex-wrap: wrap; }

.mini-btn[b-vvsgn9377h] { background: var(--sf2); border: 1px solid var(--ln); color: var(--tx); cursor: pointer; font-size: 0.78rem; padding: 4px 10px; border-radius: 7px; }
.mini-btn:hover[b-vvsgn9377h] { border-color: var(--acc); }
.mini-btn.warn:hover[b-vvsgn9377h] { border-color: var(--gold); color: var(--gold); }
.mini-btn.del:hover[b-vvsgn9377h] { border-color: var(--red); color: var(--red); }
.btn-ghost[b-vvsgn9377h] { background: none; border: 1px solid var(--ln); color: var(--tx2); cursor: pointer; padding: 4px 12px; border-radius: 7px; font-size: 0.82rem; }

.mod-users[b-vvsgn9377h] { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.mod-users th[b-vvsgn9377h] { text-align: left; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--mut); padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--ln); }
.mod-users td[b-vvsgn9377h] { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--ln); vertical-align: middle; }
.mod-users a[b-vvsgn9377h] { color: var(--acc); text-decoration: none; }
/* /Pages/AdminProxies.razor.rz.scp.css */
.muted[b-u1l7o8zwh0] { color: var(--mut); font-size: 0.9rem; margin-bottom: 1rem; }

.admin-stats[b-u1l7o8zwh0] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.6rem;
    margin-bottom: 1.25rem;
}

.admin-stat-card[b-u1l7o8zwh0] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.7rem 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.stat-value[b-u1l7o8zwh0] {
    font-size: 1.35rem;
    font-weight: 800;
    color: var(--tx);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.stat-label[b-u1l7o8zwh0] {
    font-size: 0.68rem;
    color: var(--mut);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.confidence-high[b-u1l7o8zwh0] { box-shadow: inset 3px 0 0 var(--deal); }
.confidence-high .stat-value[b-u1l7o8zwh0] { color: var(--deal); }
.confidence-med[b-u1l7o8zwh0] { box-shadow: inset 3px 0 0 var(--gold); }
.confidence-med .stat-value[b-u1l7o8zwh0] { color: var(--gold); }
.confidence-low[b-u1l7o8zwh0] { box-shadow: inset 3px 0 0 var(--red); }
.confidence-low .stat-value[b-u1l7o8zwh0] { color: var(--red); }

.proxy-actions[b-u1l7o8zwh0] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 0.9rem 1rem;
    margin-bottom: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.proxy-add[b-u1l7o8zwh0] { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.proxy-add input[b-u1l7o8zwh0] {
    flex: 1;
    min-width: 240px;
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 0.45rem 0.9rem;
    font-size: 0.9rem;
    font-family: inherit;
}
.proxy-add input:focus[b-u1l7o8zwh0] { outline: none; border-color: var(--acc); }

.auth-btn.narrow[b-u1l7o8zwh0] { width: auto; padding: 0.45rem 1.1rem; }

.btn-ghost[b-u1l7o8zwh0] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 0.42rem 1rem;
    font-size: 0.85rem;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
}
.btn-ghost:hover:not(:disabled)[b-u1l7o8zwh0] { border-color: var(--mut); }
.btn-ghost:disabled[b-u1l7o8zwh0] { opacity: 0.5; cursor: not-allowed; }

.proxy-btns[b-u1l7o8zwh0] { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.check-inline[b-u1l7o8zwh0] { display: flex; align-items: center; gap: 0.4rem; color: var(--tx2); font-size: 0.85rem; cursor: pointer; }
.check-inline input[b-u1l7o8zwh0] { width: 15px; height: 15px; accent-color: var(--acc); }

.proxy-msg[b-u1l7o8zwh0] { font-size: 0.85rem; color: var(--acc); font-weight: 700; }
.probe-result[b-u1l7o8zwh0] { font-size: 0.85rem; font-weight: 700; }
.probe-result.ok[b-u1l7o8zwh0] { color: var(--deal); }
.probe-result.bad[b-u1l7o8zwh0] { color: var(--red); }

/* A containment context so descendants can size themselves against the VISIBLE width (100cqw) rather
   than the table's — the two differ by ~70px here, and that gap is exactly what hid the log panel's
   scrollbar off the right edge. `100vw` was the first attempt and is the wrong ruler: it measures the
   window, not this scrolling box. */
.proxy-table-wrap[b-u1l7o8zwh0] { overflow-x: auto; border-radius: 12px; border: 1px solid var(--ln); container-type: inline-size; }
.proxy-table[b-u1l7o8zwh0] { width: 100%; border-collapse: collapse; font-size: 0.82rem; background: var(--sf); min-width: 820px; }
.proxy-table th[b-u1l7o8zwh0] {
    text-align: left;
    padding: 0.55rem 0.7rem;
    background: var(--sf2);
    color: var(--mut);
    font-family: var(--fn);
    font-weight: 700;
    font-size: 0.66rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    white-space: nowrap;
}
.proxy-table td[b-u1l7o8zwh0] {
    padding: 0.5rem 0.7rem;
    border-top: 1px solid var(--ln);
    white-space: nowrap;
    color: var(--tx2);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}
.proxy-table tr.direct td[b-u1l7o8zwh0] { color: var(--mut); font-style: italic; }
.proxy-table td.url[b-u1l7o8zwh0] { max-width: 260px; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, monospace; font-size: 0.78rem; }
.proxy-table td.ok[b-u1l7o8zwh0] { color: var(--deal); font-weight: 700; }
.proxy-table td.bad[b-u1l7o8zwh0] { color: var(--red); }
.empty-row[b-u1l7o8zwh0] { text-align: center; color: var(--mut); padding: 1.5rem !important; font-style: italic; }

.status[b-u1l7o8zwh0] { padding: 2px 9px; border-radius: 999px; font-size: 0.72rem; font-weight: 800; }
.status-ok[b-u1l7o8zwh0] { background: var(--deal-bg); color: var(--deal); }
.status-bad[b-u1l7o8zwh0] { background: var(--acc-soft); color: var(--red); }
.status-cool[b-u1l7o8zwh0] { background: rgba(217, 158, 20, 0.16); color: var(--gold); }
.status-unk[b-u1l7o8zwh0] { background: var(--sf2); border: 1px solid var(--ln); color: var(--mut); }

.src[b-u1l7o8zwh0] { padding: 1px 8px; border-radius: 6px; font-size: 0.66rem; font-weight: 700; text-transform: uppercase; }
.src-manual[b-u1l7o8zwh0] { background: var(--acc-soft); color: var(--acc); }
.src-free[b-u1l7o8zwh0] { background: var(--sf2); border: 1px solid var(--ln); color: var(--sw2-tx); }
.src-config[b-u1l7o8zwh0] { background: rgba(217, 158, 20, 0.16); color: var(--gold); }
.src-direct[b-u1l7o8zwh0] { background: var(--sf2); border: 1px solid var(--ln); color: var(--mut); }

/* Routing column: egress tier + what the node is used for. The tier is a button because switching a
   node between the shared free pool and the operator's own home egress is the one routing decision worth
   making from the table itself; purposes are narrowed from config or the API. */
/* Laid out inline, NOT as a flex container. `display: flex` on a <td> stops it generating a table-cell
   box, so the cell no longer stretches to the row's height — measured 37px against its siblings' 42px,
   inset 3px top and bottom. That was invisible while rows had no background of their own, and the moment
   the row highlight below started painting each cell it showed up as a notch around this one column. */
.routing[b-u1l7o8zwh0] { white-space: nowrap; }
.routing > *[b-u1l7o8zwh0] { vertical-align: middle; }
.routing > * + *[b-u1l7o8zwh0] { margin-left: 6px; }
.tier[b-u1l7o8zwh0] {
    padding: 1px 8px;
    border-radius: 6px;
    font-size: 0.66rem;
    font-weight: 700;
    text-transform: uppercase;
    border: 1px solid var(--ln);
    background: var(--sf2);
    color: var(--sw2-tx);
    cursor: pointer;
}
.tier-home[b-u1l7o8zwh0] { background: var(--acc-soft); color: var(--acc); border-color: transparent; }
.tier-direct[b-u1l7o8zwh0] { background: var(--sf2); color: var(--mut); cursor: default; }
.purposes[b-u1l7o8zwh0] { font-size: 0.66rem; font-weight: 700; color: var(--mut); letter-spacing: 0.03em; }
.purposes.ok[b-u1l7o8zwh0] { color: var(--ok, #3fb950); }
.purposes.bad[b-u1l7o8zwh0] { color: var(--bad, #f85149); }

/* Same reason as .routing above — a flex <td> is not a table cell.
   Pinned to the right edge because this table is wider than its box at ordinary window sizes, and the
   column that fell off the end was the one holding the only per-row controls there are: measured 96px
   of buttons clipped by 70px, leaving half of ⏸ and no ✕ at all. Sticky keeps them reachable without
   scrolling, and needs its own background or the scrolling cells show through. */
.proxy-table td.row-actions[b-u1l7o8zwh0],
.proxy-table th:last-child[b-u1l7o8zwh0] {
    position: sticky;
    right: 0;
    z-index: 1;
    white-space: nowrap;
}
.proxy-table td.row-actions[b-u1l7o8zwh0] { background: var(--sf); }
.proxy-table th:last-child[b-u1l7o8zwh0] { background: var(--sf2); }
.proxy-row:hover td.row-actions[b-u1l7o8zwh0], .proxy-row.open td.row-actions[b-u1l7o8zwh0] { background: var(--sf2); }
.row-actions > * + *[b-u1l7o8zwh0] { margin-left: 4px; }
.mini-btn[b-u1l7o8zwh0] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 2px 10px;
    cursor: pointer;
    font-size: 0.78rem;
    font-weight: 700;
    font-family: inherit;
}
.mini-btn:hover[b-u1l7o8zwh0] { border-color: var(--mut); }
.mini-btn.del:hover[b-u1l7o8zwh0] { color: var(--red); border-color: var(--red); }

/* ---- per-node live log ----------------------------------------------------------------------
   A row unfolds into a tail of what the pool is actually doing with that node. The table above can
   only carry cumulative counters and one status word, which cannot separate a node that is timing
   out from one being 403'd, nor say whether it is being tried at all right now. */

.proxy-row[b-u1l7o8zwh0] { cursor: pointer; }
.proxy-row:hover td[b-u1l7o8zwh0] { background: var(--sf2); }
.proxy-row.open td[b-u1l7o8zwh0] { background: var(--sf2); }
.row-caret[b-u1l7o8zwh0] { display: inline-block; width: 0.9rem; color: var(--mut); }

.proxy-log-row td[b-u1l7o8zwh0] { padding: 0 !important; border-top: none; background: var(--bg); }

/* The panel spans a 12-column cell, so it would inherit the TABLE's width — which is wider than what you
   can see whenever the table scrolls. Two things break at once then: you drag sideways to read a line,
   and the panel's own scrollbar sits past the right edge, invisible. Bounding it by the scroll box's
   VISIBLE width (100cqw, see the container above) and pinning it left keeps the whole panel — scrollbar
   included — inside the viewport at any width. `100%` still wins when the table fits. */
.plog-head[b-u1l7o8zwh0], .plog[b-u1l7o8zwh0] {
    position: sticky;
    left: 0;
    width: min(100%, 100cqw);
    box-sizing: border-box;
}

.plog-head[b-u1l7o8zwh0] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.45rem 0.8rem;
    border-top: 1px solid var(--ln);
}

/* Per-purpose standing — health is tracked per purpose, so a node good for Nintendo and parked for the
   shops must not read as one state. */
.plog-purpose[b-u1l7o8zwh0] {
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--mut);
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 1px 9px;
    white-space: nowrap;
}
.plog-purpose b[b-u1l7o8zwh0] { color: var(--tx2); text-transform: uppercase; letter-spacing: 0.04em; margin-right: 3px; }
.plog-purpose.ok[b-u1l7o8zwh0] { color: var(--deal); border-color: transparent; background: var(--deal-bg); }
.plog-purpose.ok b[b-u1l7o8zwh0] { color: var(--deal); }
.plog-purpose.warn[b-u1l7o8zwh0] { color: var(--gold); border-color: transparent; background: rgba(217, 158, 20, 0.16); }
.plog-purpose.warn b[b-u1l7o8zwh0] { color: var(--gold); }
.plog-purpose.bad[b-u1l7o8zwh0] { color: var(--red); border-color: transparent; background: var(--acc-soft); }
.plog-purpose.bad b[b-u1l7o8zwh0] { color: var(--red); }

.plog-hint[b-u1l7o8zwh0] { font-size: 0.7rem; color: var(--mut); margin-left: auto; font-style: italic; }

.plog[b-u1l7o8zwh0] {
    max-height: 260px;
    overflow-y: auto;
    padding: 0.4rem 0.8rem 0.6rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.74rem;
    line-height: 1.55;
}
.plog-line[b-u1l7o8zwh0] { display: flex; gap: 0.55rem; white-space: nowrap; }
.plog-time[b-u1l7o8zwh0] { color: var(--mut); flex: 0 0 auto; }
.plog-glyph[b-u1l7o8zwh0] { flex: 0 0 0.9rem; text-align: center; font-weight: 700; }
.plog-purp[b-u1l7o8zwh0] { flex: 0 0 1.6rem; color: var(--mut); font-size: 0.68rem; }
.plog-msg[b-u1l7o8zwh0] { color: var(--tx2); white-space: pre-wrap; word-break: break-word; }
.plog-empty[b-u1l7o8zwh0] { color: var(--mut); font-style: italic; font-family: var(--fn); padding: 0.3rem 0; }

/* The glyph carries the colour, not the whole line: at a glance the reader is scanning the left edge
   for the shape of the traffic, and a wall of coloured text does not have a shape. */
.plog-acquire[b-u1l7o8zwh0] { opacity: 0.62; }
.plog-ok .plog-glyph[b-u1l7o8zwh0] { color: var(--deal); }
.plog-blocked .plog-glyph[b-u1l7o8zwh0], .plog-fail .plog-glyph[b-u1l7o8zwh0] { color: var(--red); }
.plog-blocked .plog-msg[b-u1l7o8zwh0], .plog-fail .plog-msg[b-u1l7o8zwh0] { color: #f3a3a3; }
.plog-park .plog-glyph[b-u1l7o8zwh0], .plog-park .plog-msg[b-u1l7o8zwh0] { color: var(--gold); font-weight: 700; }
.plog-revive .plog-glyph[b-u1l7o8zwh0], .plog-revive .plog-msg[b-u1l7o8zwh0] { color: var(--deal); font-weight: 700; }
.plog-probe .plog-glyph[b-u1l7o8zwh0] { color: var(--acc); }
.plog-admin .plog-glyph[b-u1l7o8zwh0], .plog-admin .plog-msg[b-u1l7o8zwh0] { color: var(--acc); }

/* Purpose selector beside "Test": the two purposes are different questions of the same proxy. */
.probe-purpose[b-u1l7o8zwh0] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 8px;
    padding: 6px 8px;
    font-size: 0.8rem;
}
.probe-for[b-u1l7o8zwh0] { font-weight: 700; opacity: 0.75; margin-right: 4px; }

/* ---- purpose lens ----------------------------------------------------------------------------------
   Which purpose's counters the table's four traffic columns show. A lens rather than eight columns: the
   table already measures exactly as wide as its scroll box, and splitting every counter in place would
   push the sticky action column back off the right edge — the very regression this page was last fixed
   for. The full side-by-side breakdown lives in each node's detail panel, where width is not scarce. */
.purpose-lens[b-u1l7o8zwh0] {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
    margin: 0 0 0.5rem;
}

.lens-label[b-u1l7o8zwh0] { font-size: 0.78rem; color: var(--mut); margin-right: 0.2rem; }

.lens-btn[b-u1l7o8zwh0] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 3px 11px;
    font-size: 0.75rem;
    font-weight: 700;
    cursor: pointer;
}

.lens-btn:hover[b-u1l7o8zwh0] { border-color: var(--acc); }
.lens-btn.on[b-u1l7o8zwh0] { background: var(--acc-soft); border-color: transparent; color: var(--acc); }

/* The purposes chip is a BUTTON now (it cycles nintendo+sellers → nintendo → sellers), so it needs the
   button chrome reset back to what the span looked like — and a hover that says it is actionable. */
.purposes.act[b-u1l7o8zwh0], .purposes.pin[b-u1l7o8zwh0] {
    background: none;
    border: 1px solid transparent;
    border-radius: 6px;
    padding: 1px 5px;
    font: inherit;
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--mut);
    cursor: pointer;
}

.purposes.act:hover[b-u1l7o8zwh0], .purposes.pin:hover[b-u1l7o8zwh0] { border-color: var(--acc); color: var(--acc); }
.purposes.pin[b-u1l7o8zwh0] { color: var(--gold); font-size: 0.72rem; }

/* Traffic beside a purpose's standing in the node panel. Deliberately quieter than the standing itself:
   the state is the answer, the numbers are the evidence for it. */
.plog-traffic[b-u1l7o8zwh0] {
    font-weight: 400;
    opacity: 0.8;
    margin-left: 4px;
    font-variant-numeric: tabular-nums;
}

/* Chips carry a sentence now, so they must be allowed to wrap rather than force the panel to scroll. */
.plog-purpose[b-u1l7o8zwh0] { white-space: normal; }
/* /Pages/AdminRates.razor.rz.scp.css */
/* The KPI strip. Copied rather than shared because Blazor style isolation scopes a page's CSS to that
   page — four other admin pages already carry their own copy of these five rules, so a fifth follows the
   house pattern; hoisting all five into app.css is a separate job that touches those four pages. */
.admin-stats[b-5154vciv93] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.6rem;
    margin-bottom: 1.25rem;
}

.admin-stat-card[b-5154vciv93] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.7rem 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.stat-value[b-5154vciv93] {
    font-size: 1.35rem;
    font-weight: 800;
    color: var(--tx);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.stat-label[b-5154vciv93] {
    font-size: 0.68rem;
    color: var(--mut);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.confidence-high[b-5154vciv93] { box-shadow: inset 3px 0 0 var(--deal); }
.confidence-high .stat-value[b-5154vciv93] { color: var(--deal); }
.confidence-med[b-5154vciv93] { box-shadow: inset 3px 0 0 var(--gold); }
.confidence-med .stat-value[b-5154vciv93] { color: var(--gold); }
.confidence-low[b-5154vciv93] { box-shadow: inset 3px 0 0 var(--red); }
.confidence-low .stat-value[b-5154vciv93] { color: var(--red); }

.rates-notes[b-5154vciv93] {
    margin: 0.9rem 0 1rem;
}

.rates-note[b-5154vciv93],
.rates-warn[b-5154vciv93] {
    margin: 0 0 0.5rem;
    padding: 0.6rem 0.75rem;
    border-radius: 8px;
    font-size: 0.82rem;
    line-height: 1.45;
    border: 1px solid var(--ln);
    background: var(--sf);
    color: var(--tx2);
}

.rates-warn[b-5154vciv93] {
    border-color: var(--red);
    color: var(--tx);
}

.rates-toolbar[b-5154vciv93] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.8rem;
    font-size: 0.8rem;
}

.rates-toolbar select[b-5154vciv93] {
    margin-left: 6px;
    background: var(--sf);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: 3px 6px;
    font: inherit;
}

.rate-series[b-5154vciv93] {
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 0.7rem 0.8rem;
    margin-bottom: 0.7rem;
}

/* A series that has only ever reported its seed is the one state worth spotting from across the page. */
.rate-series.seed-only[b-5154vciv93] {
    border-style: dashed;
    opacity: 0.85;
}

.rs-head[b-5154vciv93] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.35rem 0.9rem;
    margin-bottom: 0.5rem;
    font-size: 0.8rem;
    color: var(--tx2);
}

.rs-name[b-5154vciv93] {
    font-size: 0.88rem;
    color: var(--tx);
}

.rs-pooled b[b-5154vciv93] {
    color: var(--tx);
}

.rs-pooled.bad[b-5154vciv93] {
    color: var(--red);
}

.rs-depth[b-5154vciv93] {
    color: var(--mut);
    font-variant-numeric: tabular-nums;
}

.rs-spread[b-5154vciv93] {
    color: var(--mut);
    font-variant-numeric: tabular-nums;
}

.rs-spread.wide[b-5154vciv93] {
    color: var(--red);
    font-weight: 600;
}

/* The header is a wrapping row of facts; on a phone it becomes several lines and must stay legible
   rather than collapse into a paragraph. */
@media (max-width: 560px) {
    .rs-head[b-5154vciv93] {
        gap: 0.2rem 0.7rem;
        font-size: 0.76rem;
    }
}

.rs-head[b-5154vciv93] {
    cursor: pointer;
}

.rs-caret[b-5154vciv93] {
    color: var(--mut);
    width: 10px;
    display: inline-block;
}

.rate-series.open[b-5154vciv93] {
    border-color: var(--tx2);
}

.rate-series:hover[b-5154vciv93] {
    border-color: var(--tx2);
}
/* /Pages/AdminReports.razor.rz.scp.css */
/* The reader-report inbox. Operator surface: dense, scannable, no ornament.

   The one piece of visual encoding is the count — it is the priority, so it is the biggest thing in
   the row and everything else is subordinate to it. */

.rpt-tools[b-c76wvpm9vm] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1.1rem;
    margin: 0.8rem 0 1rem;
}

.rpt-tool[b-c76wvpm9vm] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}

.rpt-lbl[b-c76wvpm9vm] {
    font-size: 0.66rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mut);
}

.rpt-chip[b-c76wvpm9vm] {
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 0.26rem 0.66rem;
    background: var(--sf);
    color: var(--mut);
    font-family: var(--fn);
    font-size: 0.74rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

.rpt-chip.on[b-c76wvpm9vm] {
    background: var(--tx);
    border-color: var(--tx);
    color: var(--bg);
}

.rpt-chip:disabled[b-c76wvpm9vm] {
    opacity: 0.5;
    cursor: default;
}

.rpt-filter[b-c76wvpm9vm] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0 0 0.7rem;
    font-size: 0.85rem;
    color: var(--mut);
}

.rpt-filter b[b-c76wvpm9vm] {
    color: var(--tx);
    font-family: var(--fm, monospace);
    font-size: 0.8rem;
}

.rpt-error[b-c76wvpm9vm] {
    color: var(--acc);
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

/* The table is wide by nature — a route, a breakdown and four buttons — so it scrolls in its own
   container rather than making the page scroll sideways. */
.rpt-scroll[b-c76wvpm9vm] {
    overflow-x: auto;
}

.rpt-table[b-c76wvpm9vm] {
    width: 100%;
    min-width: 780px;
    border-collapse: collapse;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    overflow: hidden;
}

.rpt-table th[b-c76wvpm9vm] {
    text-align: left;
    padding: 0.5rem 0.8rem;
    background: var(--sf2);
    border-bottom: 1px solid var(--ln);
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mut);
}

.rpt-table td[b-c76wvpm9vm] {
    padding: 0.7rem 0.8rem;
    border-bottom: 1px solid var(--ln);
    vertical-align: top;
}

.rpt-table tbody tr:last-child td[b-c76wvpm9vm] {
    border-bottom: 0;
}

/* Still open — the rows that want an evening. A tint rather than a badge: it marks the whole row,
   which is what "this is unfinished" is about. */
.rpt-table tr.hot td[b-c76wvpm9vm] {
    background: var(--acc-soft);
}

.rpt-n[b-c76wvpm9vm] {
    width: 5.5rem;
    white-space: nowrap;
}

.rpt-count[b-c76wvpm9vm] {
    display: block;
    font-family: var(--fn);
    font-size: 1.1rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.rpt-open[b-c76wvpm9vm] {
    display: block;
    font-family: var(--fn);
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--mut);
}

.rpt-sub[b-c76wvpm9vm] {
    min-width: 0;
}

/* The route is the row's identity and its way in, so it is a button that looks like the text it is —
   a route rendered as a link-blue anchor in a dense table is noise. */
.rpt-route[b-c76wvpm9vm] {
    border: 0;
    background: none;
    padding: 0;
    color: var(--tx);
    font-family: var(--fm, monospace);
    font-size: 0.78rem;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    word-break: break-all;
}

.rpt-route:hover[b-c76wvpm9vm] {
    color: var(--acc);
    text-decoration: underline;
}

.rpt-meta[b-c76wvpm9vm] {
    margin-top: 0.2rem;
    font-family: var(--fn);
    font-size: 0.74rem;
    color: var(--mut);
}

.rpt-break[b-c76wvpm9vm] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin-top: 0.4rem;
}

.rpt-kind[b-c76wvpm9vm] {
    font-family: var(--fn);
    font-size: 0.66rem;
    font-weight: 700;
    padding: 0.1rem 0.42rem;
    border-radius: 999px;
    background: var(--bg);
    border: 1px solid var(--ln);
    color: var(--mut);
    white-space: nowrap;
}

/* The note comes out of a <textarea>, so the reader could put lines in it, and this is EVIDENCE — it
   is printed the way it was typed. `pre-wrap` rather than the description's `pre-line`: their own
   spacing is part of what they wrote, where a vendor's stray double space is not. Same rule as the
   profile bio and the moderation queue. */
.rpt-note[b-c76wvpm9vm] {
    margin: 0.4rem 0 0;
    font-size: 0.8rem;
    line-height: 1.45;
    border-left: 2px solid var(--ln);
    padding-left: 0.6rem;
    white-space: pre-wrap;
}

.rpt-acts-h[b-c76wvpm9vm] {
    width: 17rem;
}

/* The flex lives on an inner div, never on the <td> itself: a table cell with `display: flex` stops
   participating in the row's height and leaves a band of untinted cell under the buttons — which is
   exactly what it did, and it reads as a rendering fault rather than as a layout choice. */
.rpt-acts-in[b-c76wvpm9vm] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    align-content: flex-start;
}

.rpt-btn[b-c76wvpm9vm] {
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.28rem 0.6rem;
    background: var(--sf);
    color: var(--tx);
    font-family: var(--fd);
    font-size: 0.74rem;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}

.rpt-btn:hover[b-c76wvpm9vm] {
    border-color: var(--acc);
    color: var(--acc);
}

.rpt-btn.primary[b-c76wvpm9vm] {
    background: var(--acc);
    border-color: var(--acc);
    color: var(--acc-ink);
}

.rpt-btn.primary:hover[b-c76wvpm9vm] {
    color: var(--acc-ink);
    opacity: 0.9;
}

.rpt-btn:disabled[b-c76wvpm9vm] {
    opacity: 0.5;
    cursor: default;
}

.rpt-when[b-c76wvpm9vm] {
    width: 8rem;
    font-family: var(--fn);
    font-size: 0.74rem;
    color: var(--mut);
    white-space: nowrap;
}

/* ── detail ──────────────────────────────────────────────────────────────────────────────────── */

.rpt-dim[b-c76wvpm9vm] {
    position: fixed;
    inset: 0;
    z-index: 1190;
    border: 0;
    padding: 0;
    background: rgba(16, 12, 8, 0.5);
    cursor: pointer;
}

.rpt-card[b-c76wvpm9vm] {
    position: fixed;
    z-index: 1200;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(920px, calc(100vw - 24px));
    max-height: min(90vh, 780px);
    display: flex;
    flex-direction: column;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    box-shadow: var(--frame-sh);
}

.rpt-card-head[b-c76wvpm9vm] {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--ln);
}

.rpt-card-head h3[b-c76wvpm9vm] {
    flex: 1 1 auto;
    margin: 0;
    font-size: 1rem;
    font-weight: 800;
}

.rpt-x[b-c76wvpm9vm] {
    border: 0;
    background: none;
    color: var(--mut);
    font-size: 1rem;
    padding: 0.45rem;
    border-radius: 8px;
    cursor: pointer;
}

.rpt-x:hover[b-c76wvpm9vm] {
    color: var(--tx);
    background: var(--acc-soft);
}

/* Two columns: what they said, and how they saw it. They stack below 780px — this page is used at a
   desk, but a report often gets opened on a phone from a notification. */
.rpt-card-body[b-c76wvpm9vm] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    padding: 1rem;
}

@media (max-width: 780px) {
    .rpt-card-body[b-c76wvpm9vm] {
        grid-template-columns: 1fr;
    }
}

.rpt-panel h4[b-c76wvpm9vm] {
    margin: 0 0 0.5rem;
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--mut);
}

.rpt-panel h4 + h4[b-c76wvpm9vm],
.rpt-json + h4[b-c76wvpm9vm] {
    margin-top: 1rem;
}

/* The same note in the detail card — verbatim for the same reason; see .rpt-note. */
.rpt-quote[b-c76wvpm9vm] {
    margin: 0 0 0.8rem;
    padding-left: 0.7rem;
    border-left: 3px solid var(--acc);
    font-size: 0.9rem;
    line-height: 1.5;
    white-space: pre-wrap;
}

.rpt-kv[b-c76wvpm9vm] {
    display: grid;
    grid-template-columns: 7.5rem 1fr;
    gap: 0.3rem 0.7rem;
    margin: 0 0 0.8rem;
    font-family: var(--fn);
    font-size: 0.78rem;
}

.rpt-kv dt[b-c76wvpm9vm] {
    color: var(--mut);
}

.rpt-kv dd[b-c76wvpm9vm] {
    margin: 0;
    font-weight: 600;
    min-width: 0;
    overflow-wrap: anywhere;
}

.rpt-json[b-c76wvpm9vm] {
    margin: 0;
    padding: 0.6rem 0.7rem;
    background: var(--bg);
    border: 1px solid var(--ln);
    border-radius: 9px;
    font-family: var(--fm, monospace);
    font-size: 0.7rem;
    line-height: 1.5;
    max-height: 13rem;
    overflow: auto;
}

.rpt-vp[b-c76wvpm9vm] {
    margin: 0.2rem 0 0.8rem;
    font-size: 0.74rem;
    line-height: 1.45;
    color: var(--mut);
}

.rpt-status[b-c76wvpm9vm] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-top: 0.6rem;
}

.mono[b-c76wvpm9vm] {
    font-family: var(--fm, monospace);
    font-size: 0.74rem;
}
/* /Pages/AdminSellers.razor.rz.scp.css */
/* Every class is `sl-`-prefixed on purpose: Blazor's scoped CSS does not shield a custom class name
   from the global bootstrap.min.css, so a generic name like .card or .table gets bootstrap's rules
   stacked on top of ours. */

.muted[b-eshdeeifb4] { color: var(--mut); font-size: 0.9rem; margin-bottom: 1rem; }

.admin-stats[b-eshdeeifb4] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.6rem;
    margin-bottom: 1.25rem;
}

.admin-stat-card[b-eshdeeifb4] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.7rem 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.stat-value[b-eshdeeifb4] {
    font-size: 1.35rem;
    font-weight: 800;
    color: var(--tx);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.stat-label[b-eshdeeifb4] {
    font-size: 0.68rem;
    color: var(--mut);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.confidence-high[b-eshdeeifb4] { box-shadow: inset 3px 0 0 var(--deal); }
.confidence-high .stat-value[b-eshdeeifb4] { color: var(--deal); }
.confidence-med[b-eshdeeifb4] { box-shadow: inset 3px 0 0 var(--gold); }
.confidence-med .stat-value[b-eshdeeifb4] { color: var(--gold); }
.confidence-low[b-eshdeeifb4] { box-shadow: inset 3px 0 0 var(--red); }
.confidence-low .stat-value[b-eshdeeifb4] { color: var(--red); }

/* ---- action bar ---- */

.sl-actions[b-eshdeeifb4] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 0.9rem 1rem;
    margin-bottom: 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

.sl-btns[b-eshdeeifb4] { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.auth-btn.narrow[b-eshdeeifb4] { width: auto; padding: 0.45rem 1.1rem; }

.btn-ghost[b-eshdeeifb4] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 0.42rem 1rem;
    font-size: 0.85rem;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
}
.btn-ghost:hover:not(:disabled)[b-eshdeeifb4] { border-color: var(--mut); }
.btn-ghost:disabled[b-eshdeeifb4] { opacity: 0.5; cursor: not-allowed; }

.check-inline[b-eshdeeifb4] { display: flex; align-items: center; gap: 0.4rem; color: var(--tx2); font-size: 0.85rem; cursor: pointer; }
.check-inline input[b-eshdeeifb4] { width: 15px; height: 15px; accent-color: var(--acc); }

.sl-msg[b-eshdeeifb4] { font-size: 0.85rem; color: var(--acc); font-weight: 700; }

.sl-settings[b-eshdeeifb4] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.8rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--ln);
}
.sl-setting[b-eshdeeifb4] { display: flex; flex-direction: column; gap: 0.25rem; }
.sl-setting-label[b-eshdeeifb4] { font-size: 0.66rem; color: var(--mut); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.sl-setting select[b-eshdeeifb4],
.sl-setting input[b-eshdeeifb4] {
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.35rem 0.6rem;
    font-size: 0.85rem;
    font-family: inherit;
    min-width: 110px;
}
.sl-setting select:focus[b-eshdeeifb4], .sl-setting input:focus[b-eshdeeifb4] { outline: none; border-color: var(--acc); }

/* ---- seller cards ---- */

.sl-cards[b-eshdeeifb4] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 0.8rem;
    margin-bottom: 1.75rem;
}

.sl-shop[b-eshdeeifb4] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    padding: 0.9rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}
.sl-shop.live[b-eshdeeifb4] { box-shadow: inset 3px 0 0 var(--deal); }
.sl-shop.empty[b-eshdeeifb4] { box-shadow: inset 3px 0 0 var(--red); }
.sl-shop.nocode[b-eshdeeifb4] { box-shadow: inset 3px 0 0 var(--mut); }

.sl-shop-head[b-eshdeeifb4] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
.sl-shop-name[b-eshdeeifb4] { font-size: 1.05rem; font-weight: 800; color: var(--tx); text-transform: uppercase; letter-spacing: 0.02em; }
.sl-tags[b-eshdeeifb4] { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }

.sl-state[b-eshdeeifb4] { padding: 2px 9px; border-radius: 999px; font-size: 0.66rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; }


.sl-state-live[b-eshdeeifb4] { background: var(--deal-bg); color: var(--deal); }
.sl-state-empty[b-eshdeeifb4] { background: var(--acc-soft); color: var(--red); }
.sl-state-nocode[b-eshdeeifb4] { background: var(--sf2); border: 1px solid var(--ln); color: var(--mut); }

.sl-tag[b-eshdeeifb4] {
    padding: 1px 7px;
    border-radius: 6px;
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
}

/* The two real chips on this page join the uppercase-plaque family — the rule is stated in full above
   `.via-tag` in `GameDetail.razor.css`. Measured here 2026-09-08: `.sl-tag` sat **1.42px** above centre,
   the worst anywhere on the site, and `.sl-state` 0.65. Admin is a lower-polish tier — the same tier
   `admin.*` is left untranslated for — but 1.4px is a size people catch by eye.

   IT SITS BELOW BOTH RULES, and that is not tidiness: placed above `.sl-tag` this block applied to
   `.sl-state` and silently lost on `.sl-tag`, whose own `padding` shorthand comes later and wins the
   tie. Second time that trap has been walked into, in a second file — check the COMPUTED padding after
   adding one of these, never the source.
   And `.sl-tags` centres its items now: with the two chips at different natural heights the flex
   default (stretch) hands `.sl-tag` a box its own padding did not set, and a cap box cannot be centred
   in a box somebody else sized. It was invisible before only because both chips happened to be 19.84.

   NOT the three header cells on `/admin/reports` (`.rpt-when`, `.rpt-n`, `.rpt-acts-h`): those are 32px
   table headings whose height comes from the row, not chips whose box wraps their caps, and they are
   already within 0.37px. The rule is about plaques; a `<th>` with a bottom border only looks like one
   to a detector. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(12px - 1cap)) {
    .sl-tag[b-eshdeeifb4] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(12px - 1cap);
    }

    .sl-state[b-eshdeeifb4] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(14px - 1cap);
    }
}

.sl-shop-nums[b-eshdeeifb4] { display: flex; gap: 0.9rem; flex-wrap: wrap; font-size: 0.82rem; color: var(--mut); }
.sl-shop-nums b[b-eshdeeifb4] { color: var(--tx); font-family: var(--fn); font-variant-numeric: tabular-nums; font-size: 0.95rem; }

.sl-shop-line[b-eshdeeifb4] { font-size: 0.82rem; color: var(--tx2); }
.sl-shop-line b[b-eshdeeifb4] { font-family: var(--fn); font-variant-numeric: tabular-nums; }
.sl-dim[b-eshdeeifb4] { color: var(--mut); font-size: 0.78rem; }
.sl-warn[b-eshdeeifb4] { color: var(--gold); font-weight: 700; }

.sl-shop-warn[b-eshdeeifb4] {
    font-size: 0.78rem;
    color: var(--gold);
    background: rgba(217, 158, 20, 0.1);
    border-radius: 8px;
    padding: 0.35rem 0.55rem;
    line-height: 1.35;
}

.sl-shop-foot[b-eshdeeifb4] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.2rem; flex-wrap: wrap; }

.mini-btn[b-eshdeeifb4] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 2px 10px;
    cursor: pointer;
    font-size: 0.78rem;
    font-weight: 700;
    font-family: inherit;
}
.mini-btn:hover:not(:disabled)[b-eshdeeifb4] { border-color: var(--mut); }
.mini-btn:disabled[b-eshdeeifb4] { opacity: 0.45; cursor: not-allowed; }

.sl-empty[b-eshdeeifb4] { color: var(--mut); font-style: italic; padding: 1rem; }

/* ---- section headers ---- */

.sl-section-head[b-eshdeeifb4] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.8rem;
    flex-wrap: wrap;
    margin: 1.75rem 0 0.3rem;
}
.sl-section-head h3[b-eshdeeifb4] { font-size: 1.05rem; font-weight: 800; color: var(--tx); margin: 0; }
.sl-note[b-eshdeeifb4] { font-size: 0.8rem; color: var(--mut); margin: 0 0 0.8rem; line-height: 1.45; max-width: 78ch; }

.sl-filter[b-eshdeeifb4] { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.sl-chip[b-eshdeeifb4] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 0.25rem 0.75rem;
    font-size: 0.78rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
}
.sl-chip.on[b-eshdeeifb4] { background: var(--acc-soft); border-color: var(--acc); color: var(--acc); }

/* ---- tables ---- */

.sl-table-wrap[b-eshdeeifb4] { overflow-x: auto; border-radius: 12px; border: 1px solid var(--ln); }
.sl-table[b-eshdeeifb4] { width: 100%; border-collapse: collapse; font-size: 0.82rem; background: var(--sf); min-width: 760px; }
.sl-table th[b-eshdeeifb4] {
    text-align: left;
    padding: 0.55rem 0.7rem;
    background: var(--sf2);
    color: var(--mut);
    font-family: var(--fn);
    font-weight: 700;
    font-size: 0.66rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    white-space: nowrap;
}
.sl-table td[b-eshdeeifb4] {
    padding: 0.5rem 0.7rem;
    border-top: 1px solid var(--ln);
    white-space: nowrap;
    color: var(--tx2);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}
.sl-table th.num[b-eshdeeifb4], .sl-table td.num[b-eshdeeifb4] { text-align: right; }

.sl-rate-row[b-eshdeeifb4] { cursor: pointer; }
.sl-rate-row:hover td[b-eshdeeifb4] { background: var(--sf2); }
.sl-rate-row.open td[b-eshdeeifb4] { background: var(--sf2); }
.sl-caret[b-eshdeeifb4] { width: 1.6rem; color: var(--mut); }
.sl-cur[b-eshdeeifb4] { font-weight: 800; color: var(--tx); letter-spacing: 0.03em; }
/* Razor trims the leading space before the seller name, so the gap has to be a margin. */
.sl-cur + .sl-dim[b-eshdeeifb4] { margin-left: 0.4rem; }
.sl-regions[b-eshdeeifb4] { white-space: normal; max-width: 22rem; line-height: 1.4; }

.sl-good[b-eshdeeifb4] { color: var(--deal); font-weight: 700; }
.sl-ok[b-eshdeeifb4] { color: var(--tx2); }
.sl-warnc[b-eshdeeifb4] { color: var(--gold); font-weight: 700; }
.sl-bad[b-eshdeeifb4] { color: var(--red); font-weight: 700; }

.empty-row[b-eshdeeifb4] { text-align: center; color: var(--mut); padding: 1.5rem !important; font-style: italic; }

/* ---- expanded rate detail ---- */

.sl-detail-row td[b-eshdeeifb4] { background: var(--bg); padding: 0 !important; white-space: normal; }
.sl-detail[b-eshdeeifb4] {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 1.2rem;
    padding: 0.9rem 1rem 1.1rem 2.3rem;
}
.sl-detail-title[b-eshdeeifb4] {
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    font-weight: 700;
    color: var(--mut);
    margin-bottom: 0.45rem;
}

.sl-quotes[b-eshdeeifb4] { display: flex; flex-direction: column; gap: 0.1rem; }
.sl-quote[b-eshdeeifb4] {
    display: grid;
    grid-template-columns: 2.6rem 5.2rem 4rem 1fr auto auto auto auto;
    align-items: center;
    gap: 0.5rem;
    padding: 0.22rem 0.35rem;
    border-radius: 7px;
    font-size: 0.78rem;
}
.sl-quote:hover[b-eshdeeifb4] { background: var(--sf2); }
.sl-quote.off[b-eshdeeifb4] { opacity: 0.42; text-decoration: line-through; }
.sl-quote.off .mini-btn[b-eshdeeifb4] { text-decoration: none; }
.sl-q-cur[b-eshdeeifb4] { font-weight: 800; color: var(--tx); }
.sl-q-rate[b-eshdeeifb4], .sl-q-mk[b-eshdeeifb4] { font-family: var(--fn); font-variant-numeric: tabular-nums; text-align: right; }
.sl-q-n[b-eshdeeifb4], .sl-q-when[b-eshdeeifb4] { color: var(--mut); font-size: 0.72rem; }
.sl-q-flag[b-eshdeeifb4] { color: var(--gold); }
.sl-q-src[b-eshdeeifb4] { color: var(--mut); text-decoration: none; }
.sl-q-src:hover[b-eshdeeifb4] { color: var(--acc); }

.sl-ladder-table[b-eshdeeifb4] { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
.sl-ladder-table th[b-eshdeeifb4] {
    text-align: left;
    padding: 0.25rem 0.45rem;
    color: var(--mut);
    font-size: 0.64rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-bottom: 1px solid var(--ln);
}
.sl-ladder-table td[b-eshdeeifb4] {
    padding: 0.25rem 0.45rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    color: var(--tx2);
}
.sl-ladder-table th.num[b-eshdeeifb4], .sl-ladder-table td.num[b-eshdeeifb4] { text-align: right; }
.sl-ladder-foot[b-eshdeeifb4] { margin-top: 0.4rem; line-height: 1.4; }

/* ---- coverage matrix ---- */

/* Three policy controls were added to every row, so the table needs the room; it still lives in
   .sl-table-wrap and scrolls sideways rather than squeezing the seller columns to nothing. */
.sl-coverage[b-eshdeeifb4] { min-width: 900px; }
.sl-coverage tr.sl-inactive td[b-eshdeeifb4] { opacity: 0.5; }
/* A region that is not compared is dimmed the same way an inactive one is — the seller cells to its
   right are true but no longer actionable, and that should be visible without reading the segment. */
.sl-coverage tr.sl-off .sl-cell[b-eshdeeifb4] { opacity: 0.45; }
.sl-cell[b-eshdeeifb4] { font-size: 1rem; }
.sl-cell-rate[b-eshdeeifb4] { color: var(--deal); }
.sl-cell-profile[b-eshdeeifb4] { color: var(--gold); }
.sl-cell-none[b-eshdeeifb4] { color: var(--ln); }

/* ---- comparison policy controls ---- */

.sl-seg[b-eshdeeifb4] { display: inline-flex; border: 1px solid var(--ln); border-radius: 999px; overflow: hidden; }
.sl-seg-btn[b-eshdeeifb4] {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    background: var(--sf);
    border: 0;
    border-left: 1px solid var(--ln);
    color: var(--mut);
    font-family: inherit;
    font-size: 0.74rem;
    font-weight: 700;
    padding: 0.28rem 0.6rem;
    cursor: pointer;
    white-space: nowrap;
}
.sl-seg-btn:first-child[b-eshdeeifb4] { border-left: 0; }
.sl-seg-btn:hover[b-eshdeeifb4] { color: var(--tx); background: var(--sf2); }
.sl-seg-glyph[b-eshdeeifb4] { font-size: 0.85rem; line-height: 1; }
.sl-seg-btn.on[b-eshdeeifb4] { color: var(--tx); background: var(--sf2); }
.sl-seg-on.on[b-eshdeeifb4] { color: var(--deal); }
.sl-seg-offByDefault.on[b-eshdeeifb4] { color: var(--gold); }
.sl-seg-blocked.on[b-eshdeeifb4] { color: var(--red); }

.sl-reason[b-eshdeeifb4], .sl-note[b-eshdeeifb4] {
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.25rem 0.45rem;
    font-family: inherit;
    font-size: 0.78rem;
}
.sl-reason[b-eshdeeifb4] { max-width: 11rem; }
.sl-note[b-eshdeeifb4] { width: 100%; min-width: 8rem; }
.sl-reason:disabled[b-eshdeeifb4] { opacity: 0.4; cursor: not-allowed; }

.sl-legend[b-eshdeeifb4] { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 0.5rem; font-size: 0.76rem; color: var(--mut); }
.sl-legend span[b-eshdeeifb4] { display: flex; align-items: center; gap: 0.35rem; }
.sl-legend i[b-eshdeeifb4] { width: 9px; height: 9px; border-radius: 50%; background: currentColor; display: inline-block; }
.sl-legend i.sl-cell-rate[b-eshdeeifb4] { color: var(--deal); }
.sl-legend i.sl-cell-profile[b-eshdeeifb4] { color: var(--gold); }
.sl-legend i.sl-cell-none[b-eshdeeifb4] { color: var(--mut); opacity: 0.45; }
.sl-legend-sep[b-eshdeeifb4] { gap: 0.9rem; padding-left: 0.9rem; border-left: 1px solid var(--ln); flex-wrap: wrap; }
.sl-legend-mode[b-eshdeeifb4] { gap: 0.3rem; }

/* ---- run journal ---- */

.sl-quotelist[b-eshdeeifb4] { white-space: normal; max-width: 20rem; font-size: 0.74rem; color: var(--mut); line-height: 1.4; }
.sl-verdict[b-eshdeeifb4] { padding: 2px 9px; border-radius: 999px; font-size: 0.7rem; font-weight: 800; white-space: normal; }
.sl-verdict-ok[b-eshdeeifb4] { background: var(--deal-bg); color: var(--deal); }
.sl-verdict-warn[b-eshdeeifb4] { background: rgba(217, 158, 20, 0.16); color: var(--gold); }
.sl-verdict-bad[b-eshdeeifb4] { background: var(--acc-soft); color: var(--red); }

/* ---- phone ---- */

@media (max-width: 720px) {
    /* The detail sits in a cell of a min-width:760px table, so on a phone it would be laid out
       760px wide and half of it reachable only by scrolling the table sideways — the exclude
       buttons and the ladder's price columns land off-screen. Pinning it to the scroll container's
       left edge and bounding it to the viewport keeps the whole panel readable in place; the rate
       table above it still scrolls horizontally as normal. */
    .sl-detail[b-eshdeeifb4] {
        position: sticky;
        left: 0;
        box-sizing: border-box;
        width: calc(100vw - 2.5rem);
        grid-template-columns: 1fr;
        padding: 0.8rem 0.8rem 1rem;
        gap: 0.9rem;
    }
    /* Flex, not grid: the row has a variable number of children (the outlier flag only appears on
       some), and a fixed column count leaves the last item orphaned on its own line. */
    .sl-quote[b-eshdeeifb4] { display: flex; flex-wrap: wrap; align-items: center; gap: 0.15rem 0.5rem; }
    .sl-q-rate[b-eshdeeifb4], .sl-q-mk[b-eshdeeifb4] { text-align: left; }
    .sl-q-src[b-eshdeeifb4] { margin-left: auto; }
    .sl-q-when[b-eshdeeifb4], .sl-q-n[b-eshdeeifb4] { display: none; }
    .sl-cards[b-eshdeeifb4] { grid-template-columns: 1fr; }
    .sl-section-head[b-eshdeeifb4] { margin-top: 1.4rem; }
    /* The coverage table already scrolls sideways on a phone; spelling out "off by default" three
       times per row would triple how far. The glyph carries it, the legend under the table names the
       three states, and every button keeps its title for a long-press. */
    .sl-seg-label[b-eshdeeifb4] { display: none; }
    .sl-reason[b-eshdeeifb4] { max-width: 7rem; }
    .sl-note[b-eshdeeifb4] { min-width: 6rem; }
}

/* A capture that has aged out of the pooling window (Sellers:CouponMaxAgeDays): still stored and still
   shown, but no longer part of the effective rate. Dimmed rather than hidden — the operator needs to see
   that it exists, or "the rate changed and nothing was scraped" has no explanation on the page. */
.sl-quote.aged[b-eshdeeifb4] { opacity: 0.55; }
.sl-q-aged[b-eshdeeifb4] {
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--mut);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: 0 6px;
}

/* "the site applies: off by default" — the EFFECTIVE mode, under the segment that edits the stored one.
   Only rendered on a difference, so it is a flag rather than a label: quiet ink, no box, and the reason
   beside it in the same breath. Not a control by design — the computed source must lift itself the day a
   shop adds a currency. */
.sl-eff[b-eshdeeifb4] {
    margin-top: 3px;
    font-size: 0.66rem;
    line-height: 1.3;
    color: var(--warn);
    cursor: help;
}

.sl-eff-why[b-eshdeeifb4] {
    color: var(--mut);
}
/* /Pages/AdminSettings.razor.rz.scp.css */
.admin-settings h1[b-1a90bazvir] { color: var(--tx); font-size: 1.45rem; font-weight: 900; }

.settings-h[b-1a90bazvir] {
    margin: 0 0 0.6rem;
    font-size: 1.1rem;
    color: var(--tx);
}

.set-row[b-1a90bazvir] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    padding: 0.65rem 0;
    border-bottom: 1px solid var(--ln);
}

.set-row:last-child[b-1a90bazvir] { border-bottom: none; }

.set-info[b-1a90bazvir] { flex: 1 1 240px; min-width: 200px; }
.set-info label[b-1a90bazvir] { display: block; color: var(--tx); font-weight: 600; font-size: 0.9rem; }

.set-restart[b-1a90bazvir] {
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    background: rgba(250, 204, 21, 0.15);
    color: var(--gold);
    border-radius: 4px;
    padding: 1px 5px;
    margin-left: 6px;
}

.set-edit[b-1a90bazvir] { display: flex; gap: 0.4rem; align-items: center; }

.set-edit input[b-1a90bazvir], .set-edit select[b-1a90bazvir] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx);
    border-radius: 6px;
    padding: 0.35rem 0.5rem;
    min-width: 130px;
}

.auth-btn.ghost[b-1a90bazvir] {
    background: transparent;
    border: 1px solid var(--ln);
    color: var(--tx2);
}

.set-meta[b-1a90bazvir] {
    flex-basis: 100%;
    font-size: 0.75rem;
    color: var(--mut);
    display: flex;
    gap: 0.8rem;
}

.set-ovr[b-1a90bazvir] { color: var(--acc); }
.set-status[b-1a90bazvir] { color: var(--deal); }
/* /Pages/AdminStores.razor.rz.scp.css */
/* The stores page. Colours come from the app's own tokens so both themes work without a second
   palette — the same lesson the price chart learned when it shipped hard-coded to dark. */

.stores-bar[b-qrxhcupudd] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1rem;
    margin: .75rem 0 .5rem;
}

.sb-group[b-qrxhcupudd] {
    display: inline-flex;
    gap: .25rem;
}

.sb-sel[b-qrxhcupudd],
.sb-check[b-qrxhcupudd] {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .82rem;
    color: var(--tx2);
}

.sb-sel select[b-qrxhcupudd] {
    background: var(--sf2);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: .2rem .4rem;
    font-size: .82rem;
}

.chip[b-qrxhcupudd] {
    background: var(--sf2);
    color: var(--tx2);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: .28rem .7rem;
    font-size: .82rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
}

.chip:hover[b-qrxhcupudd] { border-color: var(--tx2); }

.chip.on[b-qrxhcupudd] {
    background: var(--acc);
    border-color: var(--acc-brd);
    color: var(--acc-ink);
}

.chip.sm[b-qrxhcupudd] { padding: .2rem .55rem; font-size: .78rem; }

/* The multi-selects (shops, wallets) are on by default and there are twenty of them, so the solid
   accent would paint the whole strip. Soft fill, real ink: still unmistakably "selected", still
   readable — which the solid version was not once the ink went white on it. */
.chip.sm.on[b-qrxhcupudd] {
    background: var(--acc-soft);
    border-color: var(--acc-brd);
    color: var(--tx);
}

.chip.ghost[b-qrxhcupudd] { opacity: .7; }

.sc-dot[b-qrxhcupudd] {
    width: .6rem;
    height: .6rem;
    border-radius: 50%;
    display: inline-block;
    flex: none;
}

.sc-tag[b-qrxhcupudd] {
    font-size: .68rem;
    opacity: .75;
    border: 1px solid currentColor;
    border-radius: 4px;
    padding: 0 .25rem;
}

.sc-tag.off[b-qrxhcupudd] { opacity: .9; color: var(--warn); }

.stores-chips[b-qrxhcupudd] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .3rem;
    margin: .35rem 0;
}

.stores-method[b-qrxhcupudd],
.stores-note[b-qrxhcupudd],
.stores-warn[b-qrxhcupudd] {
    font-size: .8rem;
    line-height: 1.5;
    color: var(--tx2);
    margin: .4rem 0;
}

.stores-note.sm[b-qrxhcupudd] { font-size: .75rem; color: var(--mut); }

.stores-warn[b-qrxhcupudd] {
    border-left: 3px solid var(--warn);
    padding-left: .6rem;
}

.stores-error[b-qrxhcupudd] {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    align-items: flex-start;
    border: 1px solid var(--ln);
    border-left: 3px solid var(--red);
    border-radius: 8px;
    padding: .8rem 1rem;
    margin: 1rem 0;
}

/* Small multiples. auto-fill keeps a panel readable on a narrow desktop window instead of
   compressing eight of them into a column of slivers. */
.stores-grid[b-qrxhcupudd] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: .75rem;
    margin: .75rem 0;
}

.stores-panel[b-qrxhcupudd] {
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: .5rem .6rem;
    background: var(--sf);
    min-width: 0;
}

.stores-panel.wide[b-qrxhcupudd] { margin: .75rem 0; }

.sp-head[b-qrxhcupudd] {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .5rem;
    font-size: .82rem;
    margin-bottom: .2rem;
}

.sp-head .muted[b-qrxhcupudd] { font-size: .74rem; }

/* --- The two tables. Deliberately the game page's own price-table register: flat rows, a hairline under
   each, small uppercase headers, tabular figures. What stood here before was a 7×15 heatmap with a focus
   frame and a bump chart under it, which answered more than anyone asked. --- */
.st[b-qrxhcupudd] {
    width: 100%;
    border-collapse: collapse;
    font-size: .84rem;
    margin: .35rem 0 .6rem;
}

.st th[b-qrxhcupudd],
.st td[b-qrxhcupudd] {
    border-bottom: 1px solid var(--ln);
    padding: .4rem .5rem;
    text-align: left;
    white-space: nowrap;
}

/* Sentence case, not uppercase: one of these headers contains a currency code ("1000 EUR costs") and
   shouting the whole line makes the code stop reading as a code. */
.st th[b-qrxhcupudd] {
    color: var(--mut);
    font-weight: 500;
    font-size: .76rem;
}

.st td.num[b-qrxhcupudd],
.st th.num[b-qrxhcupudd] { text-align: right; font-variant-numeric: tabular-nums; }

/* Cheaper than funding the wallet yourself at the bank rate — mtcgame's EUR and CAD really are. */
.st td.good[b-qrxhcupudd] { color: var(--deal); }

/* A shop we stopped being able to read: the level is the last one we saw, not a claim about today. It is
   dimmed rather than dropped, because a missing row and an old one are different statements. */
.st tr.stale td[b-qrxhcupudd] { opacity: .6; }

.st tr.hidden-row td:first-child[b-qrxhcupudd] { opacity: .6; }

.st-win[b-qrxhcupudd] {
    color: var(--deal);
    font-weight: 700;
    display: inline-block;
    min-width: .8rem;
}

/* The region row: 32 two-letter chips, which is the whole list in one reach. */
.rgn-row[b-qrxhcupudd] { margin: .4rem 0 .2rem; }

/* Shares the selected region's card, so picking it changes nothing. Shown rather than explained. */
.chip.sm.kin[b-qrxhcupudd] {
    border-style: dashed;
    border-color: var(--tx2);
}

/* The chart's own legend, made of controls. */
.ch-row[b-qrxhcupudd] { margin: .8rem 0 .2rem; }

.ch-item[b-qrxhcupudd] {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: .78rem;
    color: var(--tx);
    cursor: pointer;
    padding: .15rem .45rem .15rem .3rem;
    border: 1px solid var(--ln);
    border-radius: 999px;
}

.ch-item input[b-qrxhcupudd] { margin: 0; cursor: pointer; }
.ch-item.off[b-qrxhcupudd] { color: var(--mut); opacity: .65; }

/* What this table is about, in a sentence, because a region and the wallet it pays in are two facts and
   the table only shows one of them. */
.st-cap[b-qrxhcupudd] {
    font-size: .84rem;
    color: var(--tx2);
    margin: .9rem 0 .1rem;
    /* The caption gains a clause while the table is reading a moment out, and loses it again when the
       pointer leaves. Reserving the line stops the table from stepping up and down under the cursor. */
    min-height: 2.6em;
}

/* Which moment the table is currently about. Marked out from the sentence around it because it is a
   STATE, not a fact about the region: it is there only while something is pointing at the chart. */
.st-at[b-qrxhcupudd] {
    color: var(--tx);
}

/* The lens and the publication switch: reachable, but not in the way of the question. */
.st-admin[b-qrxhcupudd] { margin: 1.25rem 0 2rem; font-size: .84rem; }
.st-admin summary[b-qrxhcupudd] { cursor: pointer; color: var(--mut); }
.st-admin .st[b-qrxhcupudd] { max-width: 34rem; }
.st-admin .sb-check[b-qrxhcupudd] { margin-top: .5rem; }

/* The table is only four columns; a narrow one still needs it to scroll rather than the page. */
@media (max-width: 720px) {
    .st[b-qrxhcupudd] { display: block; overflow-x: auto; }
}

/* A second control row under the mode switch, so the page's own axis is not buried in the toolbar. */
.stores-bar.sub[b-qrxhcupudd] { margin: .25rem 0 .1rem; }

.star[b-qrxhcupudd],
.eye[b-qrxhcupudd] {
    background: none;
    border: 1px solid var(--ln);
    border-radius: 6px;
    color: var(--mut);
    cursor: pointer;
    font-size: .74rem;
    padding: .1rem .35rem;
}

.star.on[b-qrxhcupudd] { color: var(--acc); border-color: var(--acc-brd); }
.star:disabled[b-qrxhcupudd] { opacity: .35; cursor: default; }
.eye.off[b-qrxhcupudd] { color: var(--warn); border-color: var(--warn); }

.stores-how[b-qrxhcupudd] {
    margin: 1rem 0 2rem;
    font-size: .8rem;
    color: var(--tx2);
}

.stores-how summary[b-qrxhcupudd] { cursor: pointer; color: var(--mut); }
.stores-how ul[b-qrxhcupudd] { margin: .5rem 0 0 1rem; line-height: 1.55; }
.stores-how li[b-qrxhcupudd] { margin-bottom: .4rem; }

/* --- one game, decomposed (§7.3) --- */

.factors[b-qrxhcupudd] { margin: .75rem 0 1.25rem; }

.stores-chips.wrap[b-qrxhcupudd] { align-items: flex-start; }

.fx-head[b-qrxhcupudd] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    margin: .5rem 0;
    font-size: .9rem;
}

.fx-head .muted[b-qrxhcupudd] { font-size: .8rem; margin-right: .4rem; }

.fx-table[b-qrxhcupudd] {
    width: 100%;
    border-collapse: collapse;
    font-size: .84rem;
    margin: .5rem 0 .25rem;
}

.fx-table td[b-qrxhcupudd] {
    border-bottom: 1px solid var(--ln);
    padding: .3rem .5rem;
    white-space: nowrap;
}

.fx-table td.num[b-qrxhcupudd] { text-align: right; font-variant-numeric: tabular-nums; }
.fx-table .fx-what[b-qrxhcupudd] { display: flex; align-items: center; gap: .4rem; }
.fx-table .fx-why[b-qrxhcupudd] { white-space: normal; font-size: .78rem; }

/* The total is the product of the rows above it, so it reads as a sum line rather than a fourth peer. */
.fx-table tr.total td[b-qrxhcupudd] {
    border-top: 2px solid var(--ln2);
    border-bottom: none;
    font-weight: 600;
}

/* Dearer for the buyer / cheaper for the buyer — the same semantics as the shop table's markup column. */
.fx-table td.up[b-qrxhcupudd] { color: var(--sale); }
.fx-table td.good[b-qrxhcupudd] { color: var(--deal); }

/* Four factors read best two-up: wider than the shop grid's panels, because a 0.4% wobble needs the
   horizontal room to be a shape at all. */
.fx-grid[b-qrxhcupudd] { grid-template-columns: repeat(auto-fill, minmax(520px, 1fr)); }
/* /Pages/AdminSync.razor.rz.scp.css */
/* Catalog sync.

   It uses the crawler-status page's CLASS NAMES (progress-header, running-indicator, elapsed,
   step-detail, actions, btn-action, trigger-msg) so the two ops pages read as one tool — but Blazor
   scopes a page's CSS to that page, so those names arrive here carrying NOTHING and the rules have to
   be restated below. Measured on screen, not read off the file: the header rendered as
   "Finished1m 06s Step: done" with no spacing and the button as a bare browser default, and the
   markup looked perfectly correct the whole time. Change one copy and the other does not follow. */

.sync-head[b-io688a5fvz] {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: baseline;
    justify-content: space-between;
}

.sync-label[b-io688a5fvz] {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--mut);
}

.sync-upstream[b-io688a5fvz] {
    font-size: 1.05rem;
    font-weight: 600;
    word-break: break-all;
}

.sync-facts[b-io688a5fvz] {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    font-size: .85rem;
    color: var(--mut);
}

.sync-facts b[b-io688a5fvz] {
    font-family: var(--fn);
    color: var(--tx);
    margin-right: .3rem;
}

.sync-explain[b-io688a5fvz] {
    margin: .75rem 0 0;
    font-size: .85rem;
    line-height: 1.5;
}

/* The crawl switch. Its left edge carries the state, because this is the one control on the page
   whose value has to be read at a glance BEFORE the other one is pressed. */
.sync-crawl[b-io688a5fvz] {
    margin: 1rem 0 .25rem;
    padding: .7rem .85rem;
    border: 1px solid var(--ln);
    border-left-width: 3px;
    border-radius: 8px;
}

.sync-crawl.on[b-io688a5fvz] { border-left-color: var(--warn); }
.sync-crawl.off[b-io688a5fvz] { border-left-color: var(--ln); }

.sync-switch[b-io688a5fvz] {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-weight: 600;
    cursor: pointer;
}

.sync-crawl .auth-hint[b-io688a5fvz] {
    display: block;
    margin-top: .35rem;
}

.sync-busy[b-io688a5fvz] {
    margin-top: .75rem;
    padding: .55rem .75rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--warn) 14%, transparent);
    font-size: .85rem;
}

.sync-actions[b-io688a5fvz] { margin-top: 1rem; }

.btn-action.danger[b-io688a5fvz] {
    background: var(--red);
    border-color: var(--red);
    color: #fff;
}

.sync-conf[b-io688a5fvz] {
    margin-top: .75rem;
    padding: .75rem;
    border-radius: 8px;
    background: var(--sf2);
    font-size: .8rem;
    line-height: 1.6;
    overflow-x: auto;
}

/* Progress ------------------------------------------------------------------------------------ */

.sync-progress[b-io688a5fvz] { margin-top: 1rem; }

.sync-bar[b-io688a5fvz] {
    height: 10px;
    margin: .75rem 0 .35rem;
    border-radius: 999px;
    background: var(--sf2);
    overflow: hidden;
}

.sync-bar-fill[b-io688a5fvz] {
    height: 100%;
    background: var(--acc);
    transition: width .4s ease;
}

.sync-bar-legend[b-io688a5fvz] {
    font-family: var(--fn);
    font-size: .8rem;
    color: var(--mut);
}

.sync-ok[b-io688a5fvz] { color: var(--deal); font-weight: 600; }
.sync-failed[b-io688a5fvz] { color: var(--red); font-weight: 600; }

.sync-error[b-io688a5fvz] {
    margin-top: .75rem;
    padding: .6rem .75rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--red) 12%, transparent);
    font-size: .85rem;
    line-height: 1.5;
}

.sync-tables[b-io688a5fvz] {
    width: 100%;
    margin-top: .9rem;
    border-collapse: collapse;
    font-size: .82rem;
}

.sync-tables td[b-io688a5fvz] {
    padding: .28rem .4rem;
    border-bottom: 1px solid var(--ln);
}

.st-rows[b-io688a5fvz], .st-bytes[b-io688a5fvz] {
    font-family: var(--fn);
    text-align: right;
    color: var(--mut);
    white-space: nowrap;
}

.st-state[b-io688a5fvz] { text-align: right; width: 6.5rem; color: var(--mut); }

tr.st-done .st-state[b-io688a5fvz] { color: var(--deal); }
tr.st-copying[b-io688a5fvz] { font-weight: 600; }
tr.st-copying .st-state[b-io688a5fvz] { color: var(--acc); }
tr.st-pending[b-io688a5fvz] { opacity: .55; }

/* Restated from CrawlerStatus.razor.css — see the note at the top of this file. */

.actions[b-io688a5fvz] {
    display: flex;
    gap: .75rem;
    align-items: center;
    flex-wrap: wrap;
}

.btn-action[b-io688a5fvz] {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    padding: .45rem 1.2rem;
    border-radius: 999px;
    cursor: pointer;
    font-weight: 800;
    font-family: inherit;
    font-size: .9rem;
    transition: opacity .2s;
}

.btn-action:hover:not(:disabled)[b-io688a5fvz] { opacity: .85; }
.btn-action:disabled[b-io688a5fvz] { opacity: .5; cursor: not-allowed; }

.btn-action.btn-secondary[b-io688a5fvz] {
    background: transparent;
    color: var(--tx);
    border: 1px solid var(--ln);
}

.trigger-msg[b-io688a5fvz] { color: var(--deal); font-size: .85rem; }

.muted[b-io688a5fvz] { color: var(--mut); }

.loading[b-io688a5fvz] { color: var(--mut); padding: 1rem 0; }

.progress-header[b-io688a5fvz] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .75rem;
    margin-bottom: .25rem;
}

.running-indicator[b-io688a5fvz] { color: var(--gold); font-weight: 700; font-size: .95rem; }

.elapsed[b-io688a5fvz] { color: var(--mut); font-size: .85rem; font-family: var(--fn); }

.step-detail[b-io688a5fvz] { color: var(--mut); font-weight: 400; font-size: .85rem; }
/* /Pages/AdminUsers.razor.rz.scp.css */
.admin-users[b-nelxrkp0vu] {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.88rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 14px;
    overflow: hidden;
}

.admin-users th[b-nelxrkp0vu],
.admin-users td[b-nelxrkp0vu] {
    padding: 8px 12px;
    border-bottom: 1px solid var(--ln);
    text-align: left;
}

.admin-users tr:last-child td[b-nelxrkp0vu] {
    border-bottom: none;
}

.admin-users th[b-nelxrkp0vu] {
    color: var(--mut);
    font-family: var(--fn);
    font-weight: 700;
    font-size: 0.68rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    background: var(--sf2);
}

.reset-cell[b-nelxrkp0vu] {
    display: flex;
    gap: 6px;
    align-items: center;
}

.reset-cell input[b-nelxrkp0vu] {
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 4px 9px;
    font-size: 0.85rem;
    font-family: inherit;
}

.reset-cell input:focus[b-nelxrkp0vu] {
    outline: none;
    border-color: var(--acc);
}

.reset-msg[b-nelxrkp0vu] {
    margin-top: 0.75rem;
    color: var(--deal);
    font-size: 0.9rem;
    font-weight: 700;
}
/* /Pages/CrawlerStatus.razor.rz.scp.css */
.actions[b-7uz9yyttxo] {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    margin-bottom: 1.5rem;
    flex-wrap: wrap;
}

/* Live log panel */
.log-panel-wrap[b-7uz9yyttxo] {
    margin-bottom: 1.5rem;
}

.log-toggle[b-7uz9yyttxo] {
    font-size: 0.82rem;
    padding: 0.35rem 0.9rem;
}

.log-hint[b-7uz9yyttxo] {
    margin-left: 0.75rem;
    color: var(--mut);
    font-size: 0.75rem;
}

.log-panel[b-7uz9yyttxo] {
    margin-top: 0.6rem;
    max-height: 360px;
    overflow-y: auto;
    background: #0b0f1e;
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.5rem 0.75rem;
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    font-size: 0.76rem;
    line-height: 1.5;
}

.log-empty[b-7uz9yyttxo] {
    color: var(--mut);
    padding: 0.5rem;
}

.log-line[b-7uz9yyttxo] {
    display: flex;
    gap: 0.6rem;
    white-space: pre-wrap;
    word-break: break-word;
    padding: 1px 0;
}

.log-time[b-7uz9yyttxo] {
    color: #6b7a99;
    flex-shrink: 0;
}

.log-level[b-7uz9yyttxo] {
    flex-shrink: 0;
    width: 2.6em;
    font-weight: 700;
    color: var(--acc);
}

.log-msg[b-7uz9yyttxo] {
    color: #c8d3e6;
    flex: 1;
    min-width: 0;
}

.log-line.log-warning .log-level[b-7uz9yyttxo] { color: var(--gold); }
.log-line.log-warning .log-msg[b-7uz9yyttxo] { color: #e6d98a; }
.log-line.log-error .log-level[b-7uz9yyttxo],
.log-line.log-fatal .log-level[b-7uz9yyttxo] { color: #ef4444; }
.log-line.log-error .log-msg[b-7uz9yyttxo],
.log-line.log-fatal .log-msg[b-7uz9yyttxo] { color: #f3a3a3; }

.btn-action[b-7uz9yyttxo] {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    padding: 0.45rem 1.2rem;
    border-radius: 999px;
    cursor: pointer;
    font-weight: 800;
    font-family: inherit;
    font-size: 0.9rem;
    transition: opacity 0.2s;
}

.btn-action:hover:not(:disabled)[b-7uz9yyttxo] {
    opacity: 0.85;
}

.btn-action:disabled[b-7uz9yyttxo] {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-secondary[b-7uz9yyttxo] {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
}

.trigger-msg[b-7uz9yyttxo] {
    color: var(--deal);
    font-size: 0.85rem;
}

/* Progress panel */
.progress-panel[b-7uz9yyttxo] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 1.25rem;
    margin-bottom: 1.5rem;
}

.progress-header[b-7uz9yyttxo] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
}

.running-indicator[b-7uz9yyttxo] {
    color: var(--gold);
    font-weight: 700;
    font-size: 0.95rem;
    animation: pulse-b-7uz9yyttxo 1.5s ease-in-out infinite;
}

@keyframes pulse-b-7uz9yyttxo {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

.elapsed[b-7uz9yyttxo] {
    color: var(--mut);
    font-size: 0.85rem;
    font-family: monospace;
}

.progress-step[b-7uz9yyttxo] {
    padding: 0.5rem 0;
    font-size: 0.9rem;
    color: var(--mut);
}

.progress-step.active[b-7uz9yyttxo] {
    color: var(--tx);
    font-weight: 700;
}

.progress-step.done[b-7uz9yyttxo] {
    color: var(--deal);
}

.progress-step.done[b-7uz9yyttxo]::before {
    content: "\2713  ";
}

.progress-step.active[b-7uz9yyttxo]::before {
    content: "\25B6  ";
    color: var(--gold);
}

.step-detail[b-7uz9yyttxo] {
    color: var(--mut);
    font-weight: 400;
    margin-left: 0.5rem;
    font-size: 0.85rem;
}

.progress-bar-track[b-7uz9yyttxo] {
    background: var(--sf2);
    border-radius: 4px;
    height: 6px;
    margin-top: 0.5rem;
    overflow: hidden;
}

.progress-bar-fill[b-7uz9yyttxo] {
    background: linear-gradient(90deg, var(--deal), #22c55e);
    height: 100%;
    border-radius: 4px;
    transition: width 0.5s ease;
}

.region-progress[b-7uz9yyttxo] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.75rem;
}

.region-chip[b-7uz9yyttxo] {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.6rem;
    border-radius: 6px;
    font-size: 0.75rem;
    font-weight: 600;
    background: var(--sf2);
    color: var(--mut);
}

.region-chip.processing[b-7uz9yyttxo] {
    background: var(--ln);
    color: var(--gold);
    animation: pulse-b-7uz9yyttxo 1.5s ease-in-out infinite;
}

.region-chip.done[b-7uz9yyttxo] {
    background: rgba(74, 222, 128, 0.12);
    color: var(--deal);
}

.region-code[b-7uz9yyttxo] {
    font-family: monospace;
}

.region-count[b-7uz9yyttxo] {
    font-weight: 400;
    font-size: 0.7rem;
    opacity: 0.7;
}

/* Stats and tables */
.loading[b-7uz9yyttxo] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
}

.stats-grid[b-7uz9yyttxo] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 1rem;
    margin-bottom: 2rem;
}

.stat-card[b-7uz9yyttxo] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 1.1rem 1rem;
    text-align: center;
}

.stat-value[b-7uz9yyttxo] {
    font-size: 1.7rem;
    font-weight: 800;
    color: var(--deal);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

.stat-card:nth-child(4) .stat-value[b-7uz9yyttxo] {
    color: var(--acc);
}

.stat-label[b-7uz9yyttxo] {
    font-size: 0.8rem;
    color: var(--mut);
    margin-top: 4px;
}

/* FX freshness under the currency count: rates are served at any age, so the age is the signal. */
.stat-sub[b-7uz9yyttxo] {
    font-size: 0.72rem;
    color: var(--mut);
    margin-top: 6px;
    line-height: 1.35;
}

.stat-sub-warn[b-7uz9yyttxo] {
    display: block;
    color: var(--warn);
    font-weight: 700;
}

.stat-card.stat-warn[b-7uz9yyttxo] {
    border-color: var(--warn);
}

.section-title[b-7uz9yyttxo] {
    font-size: 1.1rem;
    font-weight: 900;
    margin-bottom: 0.75rem;
    color: var(--tx);
}

.region-table[b-7uz9yyttxo] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    overflow: hidden;
}

.region-header[b-7uz9yyttxo] {
    display: grid;
    grid-template-columns: 2.5fr 1fr 1fr 1fr 1.5fr;
    padding: 0.6rem 1rem;
    background: var(--sf2);
    font-family: var(--fn);
    font-weight: 700;
    font-size: 0.68rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--mut);
}

.region-row[b-7uz9yyttxo] {
    display: grid;
    grid-template-columns: 2.5fr 1fr 1fr 1fr 1.5fr;
    padding: 0.5rem 1rem;
    border-top: 1px solid var(--ln);
    font-size: 0.85rem;
    align-items: center;
}

.region-row.inactive[b-7uz9yyttxo] {
    opacity: 0.4;
}

.region-row.has-data[b-7uz9yyttxo] {
    background: rgba(74, 222, 128, 0.03);
}

.region-row small[b-7uz9yyttxo] {
    color: var(--mut);
    font-size: 0.7rem;
}

.timestamp[b-7uz9yyttxo] {
    color: var(--mut);
    font-size: 0.8rem;
}

/* Busy bar — the one place that answers "is the server working right now?". Deliberately loud when
   busy and quiet when idle, so the answer is readable at a glance instead of inferred from a
   greyed-out button. */
.busy-bar[b-7uz9yyttxo] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem .75rem;
    padding: .55rem .8rem;
    border-radius: 10px;
    margin-bottom: .75rem;
    font-size: .9rem;
    border: 1px solid transparent;
}

.busy-bar.idle[b-7uz9yyttxo] {
    background: color-mix(in srgb, var(--surface-2, #8882) 60%, transparent);
    color: var(--text-muted, #667);
}

.busy-bar.busy[b-7uz9yyttxo] {
    background: color-mix(in srgb, #f59e0b 14%, transparent);
    border-color: color-mix(in srgb, #f59e0b 45%, transparent);
    color: var(--text, inherit);
}

.busy-dot[b-7uz9yyttxo] {
    width: .55rem;
    height: .55rem;
    border-radius: 50%;
    background: var(--text-muted, #9aa);
    flex: none;
}

.busy-bar.busy .busy-dot[b-7uz9yyttxo] {
    background: #f59e0b;
    animation: busy-pulse-b-7uz9yyttxo 1.4s ease-in-out infinite;
}

@keyframes busy-pulse-b-7uz9yyttxo {
    0%, 100% { opacity: 1; }
    50%      { opacity: .25; }
}

/* Respect a reduced-motion preference: the colour already carries the meaning. */
@media (prefers-reduced-motion: reduce) {
    .busy-bar.busy .busy-dot[b-7uz9yyttxo] { animation: none; }
}

.busy-job[b-7uz9yyttxo] {
    padding: .1rem .5rem;
    border-radius: 999px;
    background: color-mix(in srgb, #f59e0b 22%, transparent);
    white-space: nowrap;
}

.busy-age[b-7uz9yyttxo] {
    opacity: .7;
    font-variant-numeric: tabular-nums;
}

/* A ghost job (its worker died; Hangfire clears it after InvisibilityTimeout) blocks nothing, so it
   must not look like work in progress. */
.busy-job.stale[b-7uz9yyttxo] {
    background: color-mix(in srgb, var(--surface-2, #8882) 70%, transparent);
    color: var(--text-muted, #778);
    opacity: .75;
}

.busy-stale-tag[b-7uz9yyttxo] {
    margin-left: .35rem;
    font-size: .78em;
    text-transform: uppercase;
    letter-spacing: .03em;
    opacity: .8;
}
/* /Pages/Deals.razor.rz.scp.css */
/* Deals / New Releases: same grid + list shells as the catalog (scoped per page). */
.games-grid[b-v0j98tfqdc] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}

.games-list[b-v0j98tfqdc] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    overflow: hidden;
}

.empty[b-v0j98tfqdc] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
    font-size: 1.1rem;
}

.pagination[b-v0j98tfqdc] {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 1rem;
    margin-top: 1.6rem;
    padding-bottom: 1rem;
}

.btn-page[b-v0j98tfqdc] {
    background: var(--sf);
    color: var(--tx2);
    border: 1px solid var(--ln);
    padding: 0.45rem 1.25rem;
    border-radius: 999px;
    font-family: inherit;
    font-weight: 800;
    cursor: pointer;
    transition: border-color 0.2s;
}

.btn-page:hover:not(:disabled)[b-v0j98tfqdc] {
    border-color: var(--mut);
}

.btn-page:disabled[b-v0j98tfqdc] {
    opacity: 0.4;
    cursor: not-allowed;
}

.page-info[b-v0j98tfqdc] {
    color: var(--mut);
    font-size: 0.9rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 480px) {
    .games-grid[b-v0j98tfqdc] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.7rem;
    }
}

/* ---- Whose deals these are: the reach split. Row-level tabs, not a pricing control — every price is
   identical under all three values, and the resting one is "everywhere", i.e. the feed this page always
   showed. Only rendered once a home region exists, since without one there is no "mine" to split off. ---- */
.reach-tabs[b-v0j98tfqdc] {
    display: flex;
    gap: 6px;
    margin: 2px 0 14px;
    flex-wrap: wrap;
}

.reach-tab[b-v0j98tfqdc] {
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 4px 12px;
    background: var(--bg);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    cursor: pointer;
}

.reach-tab.on[b-v0j98tfqdc] {
    border-color: color-mix(in srgb, var(--acc) 45%, transparent);
    background: color-mix(in srgb, var(--acc) 12%, transparent);
    color: var(--acc);
}
/* /Pages/Discover.razor.rz.scp.css */
.muted[b-ui4k9trjir] { color: var(--mut); }

.disc-sub[b-ui4k9trjir] {
    margin-top: -0.5rem;
    margin-bottom: 0.75rem;
    font-size: 0.9rem;
}

.disc-note[b-ui4k9trjir] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.5rem;
    margin: 0 0 1.25rem;
    padding: 0.6rem 0.85rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 10px;
    font-size: 0.85rem;
    color: var(--tx2);
    line-height: 1.5;
}
.disc-note a[b-ui4k9trjir] { color: var(--acc); font-weight: 600; text-decoration: none; white-space: nowrap; }
.disc-note a:hover[b-ui4k9trjir] { text-decoration: underline; }

.empty[b-ui4k9trjir] {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--mut);
    line-height: 1.6;
}

.disc-list[b-ui4k9trjir] {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.disc-card[b-ui4k9trjir] {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 0.7rem 0.9rem;
}

.disc-user[b-ui4k9trjir] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: var(--tx);
    flex-shrink: 0;
    width: 180px;
    min-width: 0;
}


.disc-name[b-ui4k9trjir] {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.disc-user:hover .disc-name[b-ui4k9trjir] { text-decoration: underline; }

.disc-shared[b-ui4k9trjir] {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.disc-count[b-ui4k9trjir] {
    color: var(--deal);
    font-weight: 600;
    font-size: 0.9rem;
}

.disc-titles[b-ui4k9trjir] {
    font-size: 0.8rem;
    color: var(--mut);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.disc-action[b-ui4k9trjir] {
    flex-shrink: 0;
}

.disc-btn[b-ui4k9trjir] {
    background: var(--sf2);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.45rem 1rem;
    font-size: 0.85rem;
    cursor: pointer;
    transition: background 0.15s;
}

.disc-btn:hover:not(:disabled)[b-ui4k9trjir] { background: var(--sf2); }
.disc-btn.primary[b-ui4k9trjir] { background: var(--acc); border-color: var(--acc); color: #fff; }
.disc-btn.primary:hover[b-ui4k9trjir] { background: var(--acc); }
/* Established relationship = filled green badge (adds the --deal-bg fill the old rule lacked). The
   :hover override re-asserts green so the generic .disc-btn:hover grey fill doesn't win on hover. */
.disc-btn.friends[b-ui4k9trjir],
.disc-btn.following[b-ui4k9trjir] {
    background: var(--deal-bg);
    color: var(--deal);
    border-color: var(--deal-brd);
    font-weight: 600;
}
.disc-btn.friends:hover[b-ui4k9trjir],
.disc-btn.following:hover[b-ui4k9trjir] { background: var(--deal-bg); border-color: var(--deal); }
.disc-btn.friends[b-ui4k9trjir] { cursor: default; }   /* a static status badge (rendered as a <span>) */
.disc-btn.ghost[b-ui4k9trjir] { color: var(--mut); }
.disc-btn:disabled[b-ui4k9trjir] { cursor: default; }

@media (max-width: 560px) {
    .disc-card[b-ui4k9trjir] {
        flex-wrap: wrap;
    }
    .disc-user[b-ui4k9trjir] { width: auto; flex: 1; }
    .disc-shared[b-ui4k9trjir] {
        order: 3;
        width: 100%;
        flex: none;
    }
}
/* /Pages/Favorites.razor.rz.scp.css */
.muted[b-ufce6gpenn] { color: var(--mut); }

.fav-filters[b-ufce6gpenn] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 1.25rem;
}

.chip[b-ufce6gpenn] {
    background: var(--sf);
    color: #b0b0b0;
    border: 1px solid var(--ln);
    border-radius: 20px;
    padding: 0.4rem 1rem;
    font-size: 0.9rem;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.chip:hover[b-ufce6gpenn] { color: #fff; }

.chip.active[b-ufce6gpenn] {
    background: var(--acc);
    border-color: var(--acc);
    color: #fff;
    font-weight: 600;
}

.games-grid[b-ufce6gpenn] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1.25rem;
}

.empty[b-ufce6gpenn] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
    font-size: 1.05rem;
}

/* Anonymous (no-account) local favorites: lightweight snapshot tiles (cover + title + remove). */
.fav-anon-note[b-ufce6gpenn] {
    margin: -0.4rem 0 1.25rem;
    font-size: 0.95rem;
}
.fav-anon-note a[b-ufce6gpenn] { color: var(--acc); font-weight: 600; text-decoration: none; }
.fav-anon-note a:hover[b-ufce6gpenn] { text-decoration: underline; }

.fav-tile[b-ufce6gpenn] {
    position: relative;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    overflow: hidden;
    transition: transform 0.15s, border-color 0.15s;
}
.fav-tile:hover[b-ufce6gpenn] { transform: translateY(-3px); border-color: var(--acc); }

.fav-tile-link[b-ufce6gpenn] {
    display: flex;
    flex-direction: column;
    text-decoration: none;
    color: inherit;
}
.fav-tile-img[b-ufce6gpenn] { aspect-ratio: 1 / 1; background: var(--bg); }
.fav-tile-img img[b-ufce6gpenn] { width: 100%; height: 100%; object-fit: cover; display: block; }
.fav-tile-noimg[b-ufce6gpenn] {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--mut);
    font-size: 0.85rem;
}
.fav-tile-title[b-ufce6gpenn] {
    padding: 0.6rem 0.7rem;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.fav-tile-remove[b-ufce6gpenn] {
    position: absolute;
    top: 0.45rem;
    right: 0.45rem;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    border: none;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background 0.15s;
}
.fav-tile-remove:hover[b-ufce6gpenn] { background: var(--acc); }

@media (max-width: 480px) {
    .games-grid[b-ufce6gpenn] {
        grid-template-columns: repeat(2, 1fr);
        gap: 0.75rem;
    }
}
/* /Pages/ForYou.razor.rz.scp.css */
.muted[b-70z968hf3d] { color: var(--mut); }

.sub[b-70z968hf3d] {
    margin-top: -0.5rem;
    margin-bottom: 1.25rem;
    font-size: 0.9rem;
}

.games-grid[b-70z968hf3d] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1.25rem;
}

.empty[b-70z968hf3d] {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--mut);
    font-size: 1.05rem;
    line-height: 1.6;
}

@media (max-width: 480px) {
    .games-grid[b-70z968hf3d] {
        grid-template-columns: repeat(2, 1fr);
        gap: 0.75rem;
    }
}
/* /Pages/Friends.razor.rz.scp.css */
.muted[b-2ayifkca09] { color: var(--mut); }

.fr-section[b-2ayifkca09] {
    margin-bottom: 2rem;
}

.fr-section h3[b-2ayifkca09] {
    font-size: 1.1rem;
    color: var(--tx);
    margin: 0 0 0.75rem;
}

.fr-list[b-2ayifkca09] {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.fr-row[b-2ayifkca09] {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 0.6rem 0.9rem;
}

.fr-user[b-2ayifkca09] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: var(--tx);
    min-width: 0;
    flex: 1;
}


.fr-name[b-2ayifkca09] {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.fr-user:hover .fr-name[b-2ayifkca09] {
    text-decoration: underline;
}

.fr-mid[b-2ayifkca09] {
    flex-shrink: 0;
}

.fr-code[b-2ayifkca09] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: 3px 10px;
    font-family: 'Consolas', monospace;
    color: var(--info);
    font-size: 0.85rem;
}

.fr-nocode[b-2ayifkca09] {
    font-size: 0.8rem;
}

/* A followed user who has since gone private — the follower keeps them in the list but loses access. */
.fr-private[b-2ayifkca09] {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--mut);
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: 2px 8px;
    white-space: nowrap;
}

.fr-actions[b-2ayifkca09] {
    display: flex;
    gap: 0.4rem;
    flex-shrink: 0;
}

.fr-btn[b-2ayifkca09] {
    background: var(--sf2);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.4rem 0.9rem;
    font-size: 0.85rem;
    cursor: pointer;
    transition: background 0.15s;
}

.fr-btn:hover[b-2ayifkca09] { background: var(--sf2); }
.fr-btn.primary[b-2ayifkca09] { background: var(--acc); border-color: var(--acc); color: #fff; }
.fr-btn.primary:hover[b-2ayifkca09] { background: var(--acc); }

/* Phone: stack the code + actions under the name */
@media (max-width: 560px) {
    .fr-row[b-2ayifkca09] {
        flex-wrap: wrap;
    }
    .fr-mid[b-2ayifkca09] {
        order: 3;
        width: 100%;
    }
}
/* /Pages/GameDetail.razor.rz.scp.css */
/* Game page — «Полночь» structure in both skins: blurred-cover hero, capsule actions,
   token-driven price table with the pinned home row and the best-price highlight. */

.loading[b-u1kqyldli3], .not-found[b-u1kqyldli3] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
}

.not-found p[b-u1kqyldli3] {
    margin: 0 0 0.4rem;
}

/* The second line is the explanation, so it sits under the statement rather than beside it. */
.not-found .nf-hint[b-u1kqyldli3] {
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.not-found a[b-u1kqyldli3] {
    color: var(--acc);
    font-weight: 700;
}

/* A link-shaped button: it repeats an action the reader already has (reload), so it must not carry a
   primary button's weight — but it is the only way out of this state that keeps their place. */
.not-found .nf-retry[b-u1kqyldli3] {
    margin-top: 0.6rem;
    border: 1px solid var(--ln);
    border-radius: 10px;
    background: var(--sf);
    color: var(--acc);
    font: 700 0.9rem var(--fd);
    padding: 0.45rem 1rem;
    cursor: pointer;
}

.not-found .nf-retry:hover[b-u1kqyldli3] {
    border-color: var(--mut);
}

.game-detail[b-u1kqyldli3] {
    position: relative;
}

/* The blurred cover behind the header. Subtle by day, atmospheric by night. */
.detail-backdrop[b-u1kqyldli3] {
    position: absolute;
    top: -1.25rem;
    left: -1rem;
    right: -1rem;
    height: 300px;
    background-size: cover;
    background-position: center 22%;
    filter: blur(46px) saturate(1.3);
    opacity: 0.14;
    pointer-events: none;
    z-index: 0;
}

[data-theme="dark"] .detail-backdrop[b-u1kqyldli3] {
    opacity: 0.3;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) .detail-backdrop[b-u1kqyldli3] {
        opacity: 0.3;
    }
}

.game-detail > *:not(.detail-backdrop)[b-u1kqyldli3] {
    position: relative;
    z-index: 1;
}

.game-detail h2[b-u1kqyldli3] {
    font-size: 1.2rem;
    font-weight: 900;
    margin-bottom: 1rem;
    color: var(--tx);
    border-bottom: 1px solid var(--ln);
    padding-bottom: 0.5rem;
}

.game-header[b-u1kqyldli3] {
    display: flex;
    gap: 2rem;
    margin-bottom: 2rem;
}

/* The box reserves its own height from the server's shape hint (see GameDetail.razor), so nothing
   below it moves when the picture lands. Both ratios are exactly what the finished image occupies
   today — 260x260 for square art, 260x146 for the 16:9 hero — so this changes only the BEFORE state. */
.game-cover[b-u1kqyldli3] {
    position: relative;
    flex-shrink: 0;
    width: 260px;
    aspect-ratio: 16 / 9;
    border-radius: 16px;
    overflow: hidden;
    background: var(--sf2);
    border: 1px solid var(--ln);
    box-shadow: var(--frame-sh);
    align-self: flex-start;
}

.game-cover.sq[b-u1kqyldli3] {
    aspect-ratio: 1;
}

.game-cover img[b-u1kqyldli3] {
    position: relative;   /* above the skeleton below */
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Loading skeleton. Same fill as the feed's ghost cards (FeedSkeleton) — a few percent of ink mixed
   INTO the inset surface, so it darkens paper and lightens near-black with no per-theme value. It is
   painted UNDER the image rather than swapped for it, which is why a missed load event is harmless:
   the opaque cover simply covers it. */
.game-cover[b-u1kqyldli3]::before {
    content: "";
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--tx) 7%, var(--sf2));
    animation: cover-pulse-b-u1kqyldli3 1.9s ease-in-out infinite;
}

.game-cover.loaded[b-u1kqyldli3]::before {
    display: none;
}

@keyframes cover-pulse-b-u1kqyldli3 {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
    .game-cover[b-u1kqyldli3]::before {
        animation: none;
    }
}

.game-info h1[b-u1kqyldli3] {
    font-size: 1.7rem;
    font-weight: 900;
    margin: 0 0 0.75rem;
    line-height: 1.2;
}

/* O.4: pre-order / coming-soon badge above the title — the blue (pre-order) / purple (coming-soon)
   pill catches the eye where the quiet "Released: DATE" chip didn't. */
.detail-release[b-u1kqyldli3] {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 0.36em 0.75em;
    border-radius: 8px;
    color: #fff;
    margin-bottom: 0.6rem;
}

.detail-release.preorder[b-u1kqyldli3] { background: var(--info); }
.detail-release.coming[b-u1kqyldli3] { background: var(--coming); }

/* Meta facts as quiet chips. */
.game-meta[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.9rem;
}

.meta-item[b-u1kqyldli3] {
    font-size: 0.8rem;
    color: var(--mut);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 3px 11px;
}

.meta-item strong[b-u1kqyldli3] {
    color: var(--tx2);
    font-weight: 800;
}

.genres[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.genre-tag[b-u1kqyldli3] {
    background: var(--sf2);
    border: 1px solid var(--ln);
    padding: 3px 11px;
    border-radius: 999px;
    font-size: 0.78rem;
    color: var(--mut);
}

/* Price tables */
.price-section[b-u1kqyldli3], .all-regions-section[b-u1kqyldli3] {
    margin-bottom: 2rem;
}

/* "Best deal — on sale" headline above the table: says "it's a live sale" at a glance, so the
   cheapest row isn't misread as "just a cheap region". Only rendered when the best price is on sale. */
.best-deal[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    margin: 0.15rem 0 0.85rem;
    padding: 0.6rem 0.9rem;
    border-radius: 12px;
    background: color-mix(in srgb, var(--sale) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--sale) 35%, transparent);
}

.bd-flame[b-u1kqyldli3] { font-size: 1.25rem; line-height: 1; }

/* What stands where the recommendation would be, for a reader with no baseline to be recommended
   against. Two sentences use it: "Nintendo doesn't sell this in your regions" and, for a first-time
   visitor, the invitation to name one. It may never be a blank — the no-identity state is the
   FIRST-VISIT state, and a gap where the chart was reads as "this site has no charts".
   Calm rather than loud: it is not an error and not a sale, so it borrows the surface the page already
   uses for a quiet panel and stays a shade above .price-lens-note, which is the only thing near it. */
.price-instead[b-u1kqyldli3] {
    margin: 0.15rem 0 1rem;
    padding: 0.7rem 0.9rem;
    border-radius: 12px;
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    font-size: 0.92rem;
    line-height: 1.5;
}

.bd-body[b-u1kqyldli3] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.35rem 0.55rem;
    font-family: var(--fn);
}

.bd-lead[b-u1kqyldli3] {
    font-weight: 800;
    color: var(--sale);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.8rem;
}

.bd-price[b-u1kqyldli3] {
    font-size: 1.15rem;
    font-weight: 900;
    color: var(--tx);
    font-variant-numeric: tabular-nums;
}

.bd-region[b-u1kqyldli3] { font-weight: 800; color: var(--mut); }

.bd-disc[b-u1kqyldli3] {
    background: var(--sale);
    color: var(--sale-ink);
    padding: 2px 8px;
    border-radius: 7px;
    font-weight: 800;
    font-size: 0.82rem;
}

.bd-until[b-u1kqyldli3] { font-size: 0.75rem; font-weight: 700; color: var(--mut); }

/* THE COLUMNS ARE THE TABLE'S, NOT EACH ROW'S (owner, 2026-09-05). Every row used to be its own
   4-column grid, so the tracks were sized per row — and with the local-price column at `auto` (sized to
   its content, see the phone block below) that meant a different geometry on every line: measured at
   412px, "estimated cost" started at 185 / 190 / 194px depending on the row and at 202px in the HEADER,
   i.e. the column heading stood 17px away from the column it names. The prices were right-aligned to
   four different right edges and the reader could see it.
   So the table itself carries the tracks and the header and the rows are SUBGRIDS of it: one sizing
   pass over every cell in the table, and `auto` now means "as wide as the widest local price there is",
   which is what a column is. Everything else in the table — the captions, the reference block's own
   line, an expanded row's drawer — spans all four.
   ONE constraint this brings, and it is why the padding moved: a subgrid's own padding INDENTS its
   tracks, so a row padded 1rem would push its last column past the table's edge. The gutter therefore
   belongs to the first and last CELL (below), which also keeps the row's background, its hairline and
   the `mine` rail full-bleed — the three things the padding was never about. */
.price-table[b-u1kqyldli3] {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr 1fr;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 14px;
    overflow: hidden;
}

/* NOTHING IN THIS TABLE IS DISCOUNTED, so the fourth column stops being a column: its heading is not
   rendered (see GameDetail.razor) and the track shrinks to the row's own "stop comparing this region"
   button. It was ~75px of a 412px screen labelled DISCOUNT with nothing under it but ✕ marks — a
   heading that names something the column does not contain, on the three columns' own width (owner,
   2026-09-05). Judged over ALL rows including the hidden reference ones, so revealing them cannot make
   a heading appear. */
.price-table.no-discount[b-u1kqyldli3] {
    /* THE FREED SHARE GOES TO THE REGION COLUMN, NOT TO THE PRICES (owner, 2026-09-05: "there is no
       discount column but the space for one seems to still be there"). Collapsing the fourth track to
       `auto` takes its `1fr` out of the denominator, so the three that remain each grow — and the
       estimated-cost cell is LEFT-aligned, so all of its extra width appears as blank space where a
       column used to be, which reads as a reserved one. `3fr` restores the arithmetic: 3+1+1 = the old
       2+1+1+1, so the two price columns keep exactly the width they had and only the region column,
       which can always use it (a market's member list truncates), takes the difference. */
    grid-template-columns: 3fr 1fr 1fr auto;
}

.price-table > *[b-u1kqyldli3] {
    grid-column: 1 / -1;
}

/* The rows themselves are the group's children, so the group must not be a box between them and the
   table's tracks. */
.price-row-group[b-u1kqyldli3] {
    display: contents;
}

.price-header[b-u1kqyldli3] {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--ln);
    font-weight: 800;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mut);
    font-family: var(--fn);
}

.price-row[b-u1kqyldli3] {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    padding: 0.65rem 0;
    border-top: 1px solid var(--ln);
    align-items: center;
    font-size: 0.9rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

/* The table's gutter, carried by the cells at its two edges rather than by the row — see .price-table. */
.price-header > :first-child[b-u1kqyldli3],
.price-row > :first-child[b-u1kqyldli3] {
    padding-left: 1rem;
}

.price-header > :last-child[b-u1kqyldli3],
.price-row > :last-child[b-u1kqyldli3] {
    padding-right: 1rem;
}

.price-row-group:first-of-type .price-row[b-u1kqyldli3] {
    border-top: none;
}

.price-row .region[b-u1kqyldli3] {
    font-family: var(--fd);
}

.region small[b-u1kqyldli3] {
    display: block;
    font-size: 0.75rem;
    color: var(--mut);
}

.eur-price[b-u1kqyldli3] {
    font-weight: 800;
    color: var(--tx);
}

/* Discount pill — warm (--sale), matching the card's "buy now" sale language, so the discount
   reads as an urgent price cut, not the neutral yellow/green "cheap" sticker used elsewhere. */
.discount-badge[b-u1kqyldli3] {
    background: var(--sale);
    color: var(--sale-ink);
    padding: 2px 8px;
    border-radius: 7px;
    font-weight: 800;
    font-size: 0.78rem;
    font-family: var(--fn);
}

/* Last cell holds two things now: the sale stack (badge over "until DATE") and the row's
   "do not compare" control pushed to the far edge. The stacking moved down into .dc-sale so the cell
   itself can lay them out side by side — a fifth grid column was not an option, the four are already
   fighting for room at 390px. */
.discount-cell[b-u1kqyldli3] {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}

.dc-sale[b-u1kqyldli3] {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    min-width: 0;
}

/* The row's `✕` / `＋` is DELETED (owner, 2026-09-05) and so is everything that styled it. Its three
   problems were the shape of the control, not its ink: it wrote a saved preference on one tap from inside
   the row's own tap target, it could only ever turn a region OFF, and it refused a collapsed market. All
   three are answered by the edit mode below. `.tick` is what stands here now. */

/* A row the lens excluded, on screen only because the reader pressed "show": its price is real but it
   counts for nothing on this page, and dimming is what says so without a second label per row. */
.price-row.excluded[b-u1kqyldli3] {
    opacity: 0.55;
}

.price-row.excluded .eur-price[b-u1kqyldli3] { font-weight: 700; }

/* "3 regions hidden by your settings: KR, AR — show" */
.rgn-hidden[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.35rem;
    margin: -0.35rem 0 0.6rem;
    font-size: 0.8rem;
    color: var(--mut);
    font-family: var(--fn);
}

/* The way OUT of the reference block: a lens switch, not a per-row control. Sits at the FOOT of the block
   (the collapse control is at its head) because it answers a question the rows raise, so it must come after
   them — and unlike that control it is not a disclosure, so it gets no chevron. */
.price-table > .ref-out[b-u1kqyldli3] {
    border-top: 1px dashed var(--ln);
}

/* "I have an account here" and its PADLOCK are DELETED with the ✕ above (owner, 2026-09-05). The lock's
   argument was never about ink either: a switch that commits on one tap, inside a row that is itself a tap
   target, needed a second physically separate control to arm it. The edit mode removes the premise — a row
   does not open while it is on, and nothing is written until Save — so the fact has a COLUMN in the table
   instead, and the whole concept leaves the page. */

/* A DISCLOSURE, and it must not look like a decision (owner, 2026-08-23). It used to sit within a few
   pixels of `.rgn-drop`, which was a different KIND of thing — this one only uncovers rows and is forgotten
   when you open another game, while that one rewrote a saved preference and moved prices, the feed and the
   pushes — and their weights were INVERTED: this one was accent-coloured, 800 and underlined while the
   consequential one sat at `--mut` and `opacity: .45`, which is why the free action read as the main one.
   The consequential control is gone (see the note above) and this treatment is what stayed right.
   So: secondary ink and normal weight, with a chevron doing the "this opens" job. The underline STAYS
   rather than being dropped for quietness: `.trend-chevron`'s own note records what happens when a quiet
   chevron is the only affordance ("too quiet to read, especially on a phone"), and this control sits at the
   end of a sentence where there is even less to hint at it. */
.rgn-hidden-btn[b-u1kqyldli3] {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-weight: 600;
    color: var(--tx2);
    text-decoration: underline;
    /* Softened rather than removed. `--ln2` was tried first and is the wrong token: it is the pale
       "lift" tint, and an underline drawn in it disappears, which is exactly the affordance this rule is
       trying to keep. */
    text-decoration-color: color-mix(in srgb, var(--tx2) 45%, transparent);
    text-underline-offset: 2px;
    cursor: pointer;
}

.rgn-hidden-btn:hover[b-u1kqyldli3],
.rgn-hidden-btn:focus-visible[b-u1kqyldli3] {
    color: var(--acc);
    text-decoration-color: currentColor;
}

/* The chevron is the state: pointing down means "there is more below", up means "fold it away". Sized and
   spaced like `.row-chevron`, which says the same thing one column to the left. */
/* Sized in em of its OWN line, not shrunk further: the sentence it ends is already 0.8rem, so the 0.85em
   this started at came out ~0.68rem and read as a stray dot rather than a chevron. */
.rgn-hidden-chev[b-u1kqyldli3] {
    display: inline-block;
    width: 0.85em;
    margin-right: 3px;
    font-size: 1em;
    text-decoration: none;
}

/* "Nintendo doesn't sell this in 9 other regions." — the store's half of "why is this table short",
   the sibling of .rgn-hidden above it. Deliberately quieter and BELOW the table: it is a footnote
   about absence, not a figure to read the table against, and it offers no control because there is
   nothing the reader could switch. Interface font, not the tabular one — it is a sentence, and the
   number in it is prose rather than a column to line up. */
.rgn-unsold[b-u1kqyldli3] {
    margin: 0.55rem 0 0;
    font-size: 0.8rem;
    color: var(--mut);
}

.sale-until-cell[b-u1kqyldli3] {
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--mut);
    white-space: nowrap;
}

/* THERE IS NO ROW FRAMING FOR A SALE any more, and the empty space here is the point (owner,
   2026-08-23, TASK_GAME_PAGE_SIMPLIFICATION.md §11). `.price-row.on-sale` used to warm the whole row —
   a tint plus a 3px rail — so that a live discount read as "buy now" rather than "cheap region". What
   that missed is how MANY rows it catches: 11 of 21 on the game it was re-read on, so half the table
   glowed, and which half was decided by Nintendo's sale calendar rather than by anything about the
   reader. The reader's own regions, none of them discounted that day, were the only quiet rows on the
   page — the hierarchy exactly upside down.
   The framing now belongs to the TIER (.price-row.mine below) and the sale keeps its own column: the
   -25% badge, "until DATE" under it, and the struck-through regular beside the price. Three statements,
   none of which repaints a row. The `on-sale` CLASS is gone from the markup too rather than left
   unstyled — that state was itself a defect report once ("the detail page hid the sale"), and leaving
   a hook behind would read as the same thing rather than as a decision. */

/* A caption INSIDE the price table, splitting "yours" from "cheaper, but needs an account". A row of type
   rather than a colour, because the 3px left rail is already the sale's and in the light theme the accent and
   the sale hue are the same strip. Drawn only when both groups exist. */
.price-cap[b-u1kqyldli3] {
    padding: 0.5rem 1rem 0.3rem;
    border-top: 1px solid var(--ln);
    font-family: var(--fn);
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mut);
    background: var(--sf2);
}

.price-table > .price-cap:first-child[b-u1kqyldli3] {
    border-top: none;
}

/* Reference rows: shown to orient by, not to act on. Two producers, one meaning — the guaranteed US / EU
   rows of the cheapest-regions selection, and (since 2026-08-22, §2.7) a row on the game page whose estimate
   is a bank CONVERSION rather than a route. The tint is the same one .price-cap uses, so the caption and the
   rows under it read as one section of the table rather than as a second table. */
.price-row.comparison[b-u1kqyldli3] {
    background: var(--sf2);
}

/* .cmp-tag is gone with the per-row "reference" chip it styled: the tier is now a whole BLOCK with a
   caption, and a chip on rows that already carry a funding tag and a "по курсу ЕЦБ" mark is what this pass
   exists to remove. Deleted rather than left dangling — a hook nothing emits reads as an oversight. */

/* The vertical padding of every uppercase chip below is finished by ONE rule at the foot of this
   file — search "EVERY UPPERCASE CHIP". It has to sit after them all, so do not move it up here.
   */

/* Which of the viewer's shops funds this region. Only rendered with several sources selected, so it is
   never repetition — and it carries the accent because with more than one shop in play it is the only
   thing on the row that says where to go. Shaped like .noroute-tag so the two read as one family. */
.via-tag[b-u1kqyldli3] {
    display: inline-block;
    margin-top: 3px;
    margin-left: 6px;
    font-size: 0.6rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--acc);
    border: 1px solid color-mix(in srgb, var(--acc) 40%, transparent);
    background: var(--acc-soft);
    padding: 1px 6px;
    border-radius: 6px;
}

/* A shop the reader would have to SIGN UP at. It was `.via-tag.needed` — the held chip, DASHED — and the
   dash was doing too much work: it reads as a condition on the price beside it, and a price is the one
   thing on a row that cannot be conditional. Set as plain words instead for an hour, and that was too far
   the other way: "SEAGM €36.04" beside a filled MTCGAME chip read as loose text rather than as the same
   kind of thing (owner, 2026-09-07: «выглядит очень куцим»).
   SO IT IS A CHIP, IN THE SHOP VOCABULARY, WITH NO FILL — the outline says "a shop" at a glance and the
   missing ground says "not the one funding this row". A SOLID stroke, deliberately: the dash was the part
   that made a price look provisional, and the whole point of the change was to stop doing that. Where the
   panel's mode says "from all shops" the server prices these shops into the row and this stops rendering
   altogether. */
.via-step[b-u1kqyldli3] {
    display: inline-block;
    margin-top: 3px;
    margin-left: 6px;
    font-size: 0.6rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: color-mix(in srgb, var(--acc) 70%, var(--tx2));
    border: 1px solid color-mix(in srgb, var(--acc) 65%, transparent);
    background: none;
    padding: 1px 6px;
    border-radius: 6px;
}

/* The figure, where there is one: it is the whole reason to read the chip, and a phone has no hover to
   put it behind. Set in the data face and NOT upper-cased — it is a number, and the chip's letter-spacing
   would push its digits apart. */
.via-step b[b-u1kqyldli3] {
    margin-left: 3px;
    font-family: var(--fn);
    font-weight: 800;
    text-transform: none;
    letter-spacing: 0;
    color: var(--tx);
    font-variant-numeric: tabular-nums;
}

/* The opposite of .via-tag: we know of no shop that funds this wallet, so the price beside it is a
   conversion. Muted rather than alarming — it is not a warning about the region, it is the limit of
   OUR coverage, and the reader may well have a way in that we do not track. */
.noroute-tag[b-u1kqyldli3] {
    display: inline-block;
    margin-top: 3px;
    margin-left: 6px;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--mut);
    border: 1px dashed var(--ln);
    padding: 1px 6px;
    border-radius: 6px;
}

/* The same absence line, but INSIDE the price table: the reference block owns its own collapse control
   now, directly under its caption and above the rows it uncovers (owner, 2026-08-23 — above the table it
   sat a thousand pixels from the block). So it needs the table's gutter instead of the section's, and no
   negative top margin: it follows a caption rather than leading a table. */
.price-table > .rgn-hidden-in[b-u1kqyldli3] {
    margin: 0;
    padding: 0.5rem 1rem 0.55rem;
    background: var(--sf2);
    border-top: 1px solid var(--ln);
}

/* What KIND of number the figure beside it is. It sits where the percentage would have been — the server
   sends none for a reference row, because a percentage compares payable with payable — so this replaces a
   figure rather than joining one. Dashed like .noroute-tag, which is the same family of statement: the
   limit of what we can price, not a warning about the region. */
.bank-tag[b-u1kqyldli3] {
    display: inline-block;
    margin-left: 6px;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--mut);
    border: 1px dashed var(--ln);
    padding: 1px 6px;
    border-radius: 6px;
    cursor: help;
    white-space: nowrap;
}

/* The two headline lines, one per LIST: what the reader can buy now, and — only when it is really
   cheaper — what an account elsewhere would buy. The second is deliberately quieter than the first: it is
   an option, not the answer, and the old single line's whole defect was shouting a price the reader could
   not act on. */
.tier-lead[b-u1kqyldli3] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    margin: 2px 0 0;
    font-size: 0.9rem;
    color: var(--tx);
}

.tier-lead-lbl[b-u1kqyldli3] {
    color: var(--mut);
    font-size: 0.82rem;
}

.tier-lead-rgn[b-u1kqyldli3] {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.tier-lead-note[b-u1kqyldli3] {
    color: var(--mut);
    font-size: 0.78rem;
}

.tier-lead-other[b-u1kqyldli3] {
    font-size: 0.84rem;
    color: var(--tx2);
}

/* The middle ask, and it is drawn between the other two in every sense: nearer than "cheaper elsewhere"
   (it keeps the accent, because it IS one of the reader's regions) and quieter than the first line, which
   is the one they can act on with no step at all. */
.tier-lead-shop[b-u1kqyldli3] {
    font-size: 0.84rem;
    color: var(--tx2);
}

.tier-lead-shop .tier-lead-rgn[b-u1kqyldli3] {
    color: var(--acc);
}

/* "10 regions" / "+9 more" — how many regions this one row stands for. Neutral, shaped like the two
   tags above so the row keeps one family of marks: it is a fact about the row's scope, not a
   recommendation, and it must not compete with the .via-tag that says where to buy. */
.grp-tag[b-u1kqyldli3] {
    display: inline-block;
    margin-top: 3px;
    margin-left: 6px;
    font-size: 0.6rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    color: var(--mut);
    border: 1px solid var(--ln);
    background: var(--bg2);
    padding: 1px 6px;
    border-radius: 6px;
}

/* A collective row names its members here instead of a country. Ten codes do not fit the cell the
   4-column grid already runs short of at 390px, so it clips — the full list is in the row's tooltip
   and the count tag beside it never clips. */
.price-row .region > small[b-u1kqyldli3] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* "Cheapest through each of your shops" — the line that answers "which shop do I open". One flex row
   that wraps, so three shops fit on a phone as three lines rather than being cut off. */
.shop-bests[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 14px;
    margin: 2px 0 10px;
    font-size: 0.8rem;
    color: var(--tx2);
}

.shop-bests-lbl[b-u1kqyldli3] {
    color: var(--mut);
}

.shop-best[b-u1kqyldli3] {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    white-space: nowrap;
}

.shop-best-rgn[b-u1kqyldli3] {
    color: var(--mut);
    font-variant-numeric: tabular-nums;
}

.shop-best-amt[b-u1kqyldli3] {
    font-weight: 700;
    color: var(--tx);
}

/* A shop the reader would have to JOIN. This line answers "which of my shops do I open", so an entry
   that is not one of theirs yet has to say so — otherwise it answers a different question, which is the
   understatement this whole feature removes. Marked with a word rather than a colour or a dash: the
   line is a run of names and figures at one weight, and a second visual language in it would compete
   with the amounts it exists to compare. Paled, because the entries the reader can act on today come
   first and should read first. */
.shop-best.step[b-u1kqyldli3] {
    color: var(--mut);
}

.shop-best.step .shop-best-amt[b-u1kqyldli3] {
    color: var(--tx2);
}

.shop-best-new[b-u1kqyldli3] {
    font-family: var(--fn);
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--mut);
    border: 1px dashed var(--ln2);
    border-radius: 5px;
    padding: 0 4px;
}

/* --- Gift-card ladder, inside an expanded region row --- */
.ladder[b-u1kqyldli3] {
    margin-top: 0.75rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--ln);
}

.ladder-head[b-u1kqyldli3] {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--tx2);
    margin-bottom: 0.45rem;
}

/* "What is already on this wallet" — the one place on the site that asks (§2.1). Sits between the
   ladder's heading and its rungs, because that is the order the answer is read in: this is what you have,
   these are the cards, this is the receipt. Boxed and dashed rather than a bare line, so a figure that
   silently decides the basket below cannot be mistaken for one of its outputs; quiet enough not to
   compete with the price rows above it.

   One row in every state — there is deliberately no collapse. The collapsed and expanded forms were the
   same height, so collapsing bought a click and saved nothing, and a balance that can be hidden is a
   balance that goes stale unseen. */
.wallet-line[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 7px;
    margin: 0 0 0.55rem;
    padding: 6px 8px;
    border: 1px dashed var(--ln);
    border-radius: 8px;
    font-size: 0.74rem;
    color: var(--tx2);
}

.wallet-line.editing[b-u1kqyldli3] {
    border-style: solid;
    border-color: color-mix(in srgb, var(--region) 45%, transparent);
}

.wl-what[b-u1kqyldli3] {
    flex: 0 1 auto;
}

/* The pencil and the clear button share their geometry with the row's own ✕: they are the same size of
   gesture, and two different square buttons in one drawer would read as two different kinds of thing. */
.wl-ed[b-u1kqyldli3],
.wl-x[b-u1kqyldli3] {
    appearance: none;
    flex: none;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid var(--ln);
    border-radius: 6px;
    background: var(--bg);
    color: var(--tx2);
    font-size: 0.68rem;
    line-height: 1;
    cursor: pointer;
}

.wl-ed:hover[b-u1kqyldli3],
.wl-x:hover[b-u1kqyldli3] {
    border-color: color-mix(in srgb, var(--region) 55%, transparent);
    color: var(--tx);
}

/* "＋ record your wallet balance" — a link, not a button: it offers to tell us something rather than
   changing anything, and a filled control here would read as the more important thing on the row. */
.wl-link[b-u1kqyldli3] {
    appearance: none;
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: 600;
    color: var(--region);
    border-bottom: 1px dashed color-mix(in srgb, var(--region) 45%, transparent);
    cursor: pointer;
}

.wl-box[b-u1kqyldli3] {
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    padding: 2px 6px;
    border: 1px solid var(--ln);
    border-radius: 6px;
    background: var(--bg);
}

.wl-in[b-u1kqyldli3] {
    width: 4.5rem;
    border: 0;
    padding: 0;
    background: none;
    color: var(--tx);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    text-align: right;
}

.wl-in:focus[b-u1kqyldli3] {
    outline: none;
}

/* The wallet currency beside the box, never as a placeholder inside it: the amount is in the REGION's
   billing currency, and a reader typing yen into a box that looks like euros is the one mistake this
   cannot detect. */
.wl-box i[b-u1kqyldli3] {
    font-style: normal;
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--mut);
}

.wl-of[b-u1kqyldli3] {
    color: var(--mut);
}

/* Rungs wrap into as many columns as fit: a ladder is 3–10 short rows, so a fixed column count
   either wastes a wide screen or overflows a narrow one. */
.ladder-steps[b-u1kqyldli3] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    gap: 3px 14px;
}

.ladder-step[b-u1kqyldli3] {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: 0.79rem;
    color: var(--tx2);
    font-variant-numeric: tabular-nums;
}

.ls-arrow[b-u1kqyldli3] {
    color: var(--mut);
}

.ls-cost[b-u1kqyldli3] {
    font-weight: 600;
    color: var(--tx);
}

.ls-mk[b-u1kqyldli3] {
    color: var(--mut);
    font-size: 0.72rem;
}

.ls-mk.good[b-u1kqyldli3] {
    color: var(--deal);
}

.ladder-step.best .ls-cost[b-u1kqyldli3] {
    color: var(--deal);
}

.ls-best[b-u1kqyldli3] {
    font-size: 0.66rem;
    color: var(--deal);
    background: rgba(12, 154, 91, 0.12);
    border-radius: 4px;
    padding: 1px 5px;
    white-space: nowrap;
}

.ladder-note[b-u1kqyldli3] {
    margin: 0.5rem 0 0;
    font-size: 0.71rem;
    line-height: 1.35;
    color: var(--mut);
}

/* --- The receipt: what this row's price actually costs in whole cards --- */
.receipt[b-u1kqyldli3] {
    margin-top: 0.6rem;
    padding-top: 0.55rem;
    border-top: 1px dashed var(--ln);
}

.receipt-head[b-u1kqyldli3] {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--tx2);
    margin-bottom: 0.4rem;
}

/* One row per statement, the amount always in the same column: the three methods are meant to be
   read DOWN as a comparison, which a ragged right edge would defeat. */
.rc-line[b-u1kqyldli3] {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: 0.79rem;
    color: var(--tx2);
    font-variant-numeric: tabular-nums;
}

.rc-what[b-u1kqyldli3] {
    flex: 1 1 auto;
    min-width: 0;
}

.rc-pay[b-u1kqyldli3] {
    font-weight: 600;
    white-space: nowrap;
}

.rc-total[b-u1kqyldli3] {
    margin-top: 0.15rem;
    color: var(--tx);
}

.rc-total .rc-pay[b-u1kqyldli3] {
    font-size: 0.92rem;
}

/* Which of the two rate lines the rest of the page is actually priced at. A quiet marker, not a
   badge: it distinguishes two lines that otherwise differ only by a name the reader chose themselves. */
.rc-site[b-u1kqyldli3] {
    font-size: 0.66rem;
    color: var(--mut);
    border: 1px solid var(--ln);
    border-radius: 4px;
    padding: 0 4px;
    margin-left: 5px;
    white-space: nowrap;
}

.rc-mk[b-u1kqyldli3] {
    font-size: 0.7rem;
    color: var(--mut);
    white-space: nowrap;
    min-width: 3.2em;
    text-align: right;
}

.rc-mk.good[b-u1kqyldli3] {
    color: var(--deal);
}

/* The line the other two are measured against. Faint on purpose: it holds the column's alignment and
   says "nothing to compare here", while the `what you're charged` marker beside the label carries the
   actual meaning. Never a "0%", which would read as a measurement rather than as the baseline. */
.rc-base[b-u1kqyldli3] {
    color: var(--ln2, var(--ln));
}

/* What the percentages in each block are measured against. Two of these on one screen, deliberately —
   the ladder rungs compare a shop's denominations (a rate question, so the bank rate is the reference)
   and the receipt compares ways of buying this game (a route question, so the site's own price is). The
   cost of two baselines is that NEITHER may go unlabelled. */
.ladder-ref[b-u1kqyldli3],
.rc-ref[b-u1kqyldli3] {
    margin: 0.35rem 0 0;
    font-size: 0.68rem;
    line-height: 1.35;
    color: var(--mut);
}

/* Set apart, not indented away: these are the two RATE methods, a different kind of answer from the
   receipt above and not a footnote to it. */
.rc-methods[b-u1kqyldli3] {
    margin-top: 0.45rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--ln);
    opacity: 0.9;
}

.rc-left[b-u1kqyldli3],
.rc-alt[b-u1kqyldli3],
.rc-cover[b-u1kqyldli3],
.rc-none[b-u1kqyldli3] {
    margin: 0.3rem 0 0;
    font-size: 0.71rem;
    line-height: 1.35;
    color: var(--mut);
}

/* What is being covered sits ABOVE the cards, so it reads as the question rather than as a footnote
   to the answer. */
.rc-cover[b-u1kqyldli3] {
    margin: -0.15rem 0 0.35rem;
}

/* --- Price over time, under the price table --- */
.price-trend[b-u1kqyldli3] {
    margin-top: 1.1rem;
}

.trend-head[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.trend-head h3[b-u1kqyldli3] {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--tx);
}

/* The heading IS the fold control: it resets the UA's button chrome and inherits the h3's type, so the
   section reads exactly as it did before it became clickable. The whole title is the hit area — a 12px
   chevron is not a phone target, and there is nothing else in this row to press. */
.trend-toggle[b-u1kqyldli3] {
    appearance: none;
    border: 0;
    background: transparent;
    margin: 0;
    padding: 3px 4px 3px 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: baseline;
    text-align: left;
}

/* Its own chevron rather than the price rows' `.row-chevron`: that one is deliberately a quiet
   0.8rem `--mut` tick beside a row whose whole line is obviously pressable, and at the head of a
   section it was the ONLY thing saying "this opens" — too quiet to read, especially on a phone. */
.trend-chevron[b-u1kqyldli3] {
    display: inline-block;
    width: 0.85em;
    margin-right: 5px;
    color: var(--tx);
    font-size: 0.85rem;
}

/* DASHED, not solid. A solid underline is this site's link idiom and would promise navigation; a
   dashed one is the disclosure convention. It has to live in the RESTING state, not on hover —
   a phone has no hover, and the phone is where the section reads as inert text. */
.trend-label[b-u1kqyldli3] {
    border-bottom: 1px dashed var(--mut);
    padding-bottom: 1px;
}

.trend-toggle:hover .trend-label[b-u1kqyldli3],
.trend-toggle:focus-visible .trend-label[b-u1kqyldli3] {
    color: var(--acc);
    border-bottom-color: var(--acc);
}

.trend-toggle:hover .trend-chevron[b-u1kqyldli3],
.trend-toggle:focus-visible .trend-chevron[b-u1kqyldli3] {
    color: var(--acc);
}

.trend-toggle:focus-visible[b-u1kqyldli3] {
    outline: 2px solid var(--acc);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Folded, the head's bottom margin would be a gap under the last thing on the page. */
.price-trend.folded .trend-head[b-u1kqyldli3] {
    margin-bottom: 0;
}

/* A two-state segmented control, not a checkbox: both states are named, so neither reading depends
   on knowing what the unchecked one would have meant. */
/* Was two buttons, then "best of my shops" + one per shop + ECB, and since §2.6 "all shops" + one per
   shop — so it still has to survive a viewer with four accounts on a narrow window, and it no longer
   renders at all below two. Wraps rather than overflowing: a mode the reader cannot see is a mode they
   do not have. */
.trend-modes[b-u1kqyldli3] {
    display: inline-flex;
    flex-wrap: wrap;
    border: 1px solid var(--ln);
    border-radius: 8px;
    overflow: hidden;
}

.trend-modes button[b-u1kqyldli3] {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--mut);
    font: inherit;
    font-size: 0.78rem;
    padding: 4px 10px;
    cursor: pointer;
    white-space: nowrap;
}

.trend-modes button + button[b-u1kqyldli3] {
    border-left: 1px solid var(--ln);
}

/* A shop the reader only WATCHES: the same chip, dashed underneath — the site's own "not yours yet" mark,
   the one the region row's shop chip already carries. Its line is drawable and real; what it is not is a
   price they can pay today, and a picker that made the two look identical would be the assumption this
   whole model removed. */
.trend-modes button.watched[b-u1kqyldli3] {
    text-decoration: underline dashed;
    text-underline-offset: 3px;
    text-decoration-color: color-mix(in srgb, var(--mut) 55%, transparent);
}

.trend-modes button.on[b-u1kqyldli3] {
    background: var(--sf2, rgba(255, 255, 255, 0.06));
    color: var(--tx);
    font-weight: 600;
}

.trend-modes button:hover:not(.on)[b-u1kqyldli3] {
    color: var(--tx);
}

.trend-note[b-u1kqyldli3] {
    margin: 0.4rem 0 0;
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--mut);
}

/* "6 regions outside seagm", the sentence in its `title` (§2.5). A statement of scope, not a warning:
   the reader asked for one shop's view and this is what that view is. The pill itself is the global
   .chart-badge — shared with the habit badge the component draws, which Blazor's scoped CSS cannot
   reach from here. What is left scoped is only its PLACE: its own line under the plot, because the set
   is derived by comparing two payloads and only the page holds both. */
.trend-dropped[b-u1kqyldli3] {
    margin-top: 0.5rem;
}

@media (max-width: 480px) {
    .trend-head[b-u1kqyldli3] {
        /* The control drops to its own full line rather than squeezing the heading into two words. */
        flex-direction: column;
        align-items: flex-start;
    }
}

/* A row of the reader's OWN — their home region or one they hold an account in. The rail is what makes
   the first block read as first, and it can mean this at all only because the sale gave the device up
   (see .price-row.on-sale's note above): --acc and --sale are the SAME strip in the light theme, so one
   of the two had to have it, and the one that ranks the table won. Rail only, no tint — the tint is how
   the reader's own region stays distinct from the other rows of its own block. */
.price-row.mine[b-u1kqyldli3] {
    box-shadow: inset 3px 0 0 var(--acc);
}

/* `.price-row.shopstep` — the paler rail on a row that is theirs but not payable today — is DELETED
   (2026-09-07) with the dashed chip it was the row-level half of. The tier is a captioned BLOCK, and a
   caption is what names a group: "Yours — one shop sign-up away" is a sentence, which is exactly what a
   rail cannot carry, and the two together said one thing twice at two strengths. The `mine` rail below
   keeps its job, which is to mark the reader's own regions inside a table that also holds other
   people's. */

/* The viewer's own region — pinned on top, accent-framed. Home is always in the `mine` tier
   (PricingContext.OwnsRegion is "home OR owned"), so the rail above already applies and is restated here
   at the identical value: the rule stays self-sufficient, and two rules that agree cannot fight. */
.price-row.home[b-u1kqyldli3] {
    background: var(--acc-soft);
    box-shadow: inset 3px 0 0 var(--acc);
}

.home-tag[b-u1kqyldli3] {
    display: inline-block;
    margin-left: 6px;
    vertical-align: middle;
    font-size: 0.58rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    color: var(--acc-ink);
    background: var(--acc);
    padding: 2px 7px;
    border-radius: 6px;
}

.home-tag-mini[b-u1kqyldli3] {
    display: none;
}

.stale-note[b-u1kqyldli3] {
    display: block;
    margin-top: 2px;
    font-size: 0.68rem;
    font-weight: 700;
    color: var(--warn);
    text-decoration: none;
    white-space: nowrap;
}

.row-chevron[b-u1kqyldli3] {
    display: inline-block;
    width: 0.9em;
    margin-right: 3px;
    color: var(--mut);
    font-size: 0.8rem;
}

.price-row.expanded[b-u1kqyldli3] {
    background: var(--sf2);
}

/* A grid item of the TABLE, not of the row it belongs to — its group is `display: contents` — so it has
   to be told to span the four columns; left to auto-placement it would sit in the first one. */
.price-row-chart[b-u1kqyldli3] {
    grid-column: 1 / -1;
    padding: 0.75rem 1rem 0.9rem;
    background: var(--sf2);
    border-top: 1px solid var(--ln);
}

/* The row's own shop picker, in the cross-region chart's control language on purpose: it asks the same
   kind of question about a narrower thing, and a second visual idiom for it would read as a different
   kind of control. */
.row-shops[b-u1kqyldli3] {
    margin-bottom: 0.6rem;
}

/* A shop that cannot fund THIS wallet is shown and disabled rather than dropped — the reason lives in
   its title. Muted and un-clickable, never hidden: an absent entry would read as a shop the reader has
   lost, which is the one thing this control must not imply.
   NO strike-through (owner, 2026-08-18): on this site a struck-through string is a PRICE that no longer
   applies (.price-regular, the card tiles), so wearing it here made an unavailable shop read as a
   crossed-out offer. Grey is the whole signal, and the cursor and the title carry the rest. */
.row-shops button:disabled[b-u1kqyldli3] {
    opacity: 0.45;
    cursor: not-allowed;
}

.row-shops button:disabled:hover[b-u1kqyldli3] {
    color: var(--mut);
}

/* Cross-links: back to parent game, and the other-platform twin */
.parent-link[b-u1kqyldli3],
.platform-switch[b-u1kqyldli3] {
    display: inline-block;
    color: var(--sw2-tx);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 800;
}

.parent-link[b-u1kqyldli3] {
    margin-bottom: 0.75rem;
    color: var(--acc);
}

.platform-switch[b-u1kqyldli3] {
    padding: 3px 12px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
}

.parent-link:hover[b-u1kqyldli3],
.platform-switch:hover[b-u1kqyldli3] {
    text-decoration: underline;
}

/* Platform indicator row: the game's own generation + (optional) twin link, side by side. */
.platform-row[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.85rem;
}

/* An identity LABEL, not a button: a soft flat tag (square-ish radius, no border, quiet inset tint)
   so it reads differently from the pill-shaped, bordered twin link next to it. */
.platform-tag[b-u1kqyldli3] {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 11px 4px 9px;
    border-radius: 9px;
    background: var(--sf2);
    color: var(--tx2);
    font-size: 0.83rem;
    font-weight: 800;
    white-space: nowrap;
    cursor: default;
}

.platform-tag .pt-ico[b-u1kqyldli3] {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    opacity: 0.9;
}

/* Switch 2 — the app's violet identity washed subtly into the tag (matching the tile SW2 chip /
   twin link), plus a Nintendo-red "2". Quiet in both themes: a soft wash, no loud fill or border. */
.platform-tag.sw2[b-u1kqyldli3] {
    color: var(--sw2-tx);
    background: color-mix(in srgb, var(--sw2-tx) 14%, var(--sf2));
}

.platform-tag.sw2 .pt-gen[b-u1kqyldli3] {
    color: var(--nin-red);
    font-weight: 900;
    margin-left: 3px;
}

/* Add-ons — compact capsule rows, grouped by type (M.5) */
.addons-section[b-u1kqyldli3] {
    margin-bottom: 2rem;
}

.addon-group + .addon-group[b-u1kqyldli3] {
    margin-top: 1.1rem;
}

/* The type label lives here, once per group, instead of on every row. */
.addon-group-head[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0.25rem 0.5rem;
}

.addon-group-count[b-u1kqyldli3] {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--mut);
}

/* One surface card per group, rows separated by hairlines — the same frame the feed's list view puts
   around <GameRow> (.games-list), because these are the same rows. The per-row capsule it replaces
   could not host them: a row that is itself a bordered card cannot also be a hairline-separated
   member of a list, and the row brings its own grid. */
.addons-list[b-u1kqyldli3] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    overflow: hidden;
}

.addon-type[b-u1kqyldli3] {
    font-size: 0.62rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 8px;
    border-radius: 6px;
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--mut);
    flex-shrink: 0;
}

/* EVERY UPPERCASE CHIP CENTRES ITS CAPS, NOT ITS LINE BOX — one rule for the whole family, because
   the alternative is nine hand-tuned paddings and `.ns-beta` in app.css is already one of them
   (`2.24px 6px 1.92px`, tuned by eye; left alone, but do not tune a tenth).

   `text-transform: uppercase` means a descender never appears, while the line box goes on reserving
   room for one below the baseline — so the caps sit HIGH in the chip by half that reserve. Measured on
   the game page 2026-09-07, in px above optical centre: .legend-via 0.94 · .addon-type 0.91 ·
   .shop-best-new / .row-badge 0.67 · .via-tag / .via-step / .grp-tag 0.57 · .home-tag 0.24. Reported by
   the owner against the shop plaque — «текст немного смещён… вверх относительно центра».

   `text-box: trim-both cap alphabetic` makes the content box the CAP BOX, which is the thing that
   should be centred, so with EQUAL padding above and below the caps land dead centre by construction.

   THE PADDING IS `calc(Npx - 1cap)` AND THE SHAPE IS THE WHOLE POINT: the cap cancels, so the baseline
   ends up exactly `border + N` below the top of the chip — a WHOLE NUMBER, whatever the font. That is
   not cosmetic. Chrome snaps a glyph baseline to a device pixel while a chip sits at whatever
   fractional Y its row puts it at, so a FRACTIONAL baseline offset is rounded up in some rows and down
   in others: the first version of this rule centred the caps perfectly at an offset of 12.563px and the
   same chip then rendered a pixel high in one row and a pixel low in the next, which is worse than the
   uniform half-pixel it replaced (owner, 2026-09-07: «в некоторых плашках на пиксель ниже центра, а в
   некоторых на пиксель выше»). Measured over 14 live chips: 12.563 gave painted offsets of both 12 and
   13; this form gives one value on every instance.

   N IS THE BASELINE'S WHOLE-PIXEL DEPTH, and it is the only knob: it is picked per chip as the integer
   nearest `(height - borders + cap) / 2`, i.e. the one that keeps the chip closest to the height it
   had. Dead centre and a whole-pixel baseline together DETERMINE the height, so it moves: .via-tag /
   .via-step / .grp-tag / .noroute-tag 18.39 → 19.15px, .shop-best-new 16.39 → 17.06, .home-tag
   17.92 → 17.45, .addon-type 20.88 → 21, and a price row grows 0.42px. That is the price of the fix;
   a version that keeps every height exactly can only get within 0.4px of centre, and was rejected for
   being the same complaint again.

   Two things checked rather than assumed: the formula must be written OUT in each rule, because
   `1lh`/`1cap` inside a custom property is dropped as invalid; and a 1/64px LayoutUnit residual leaves
   the bordered chips at 12.984 rather than 13, which is inside the rounding window on ~1.6% of
   positions instead of 44% — measured as one painted offset across every instance on the page.

   IT MUST STAY BELOW EVERY CHIP IT NAMES. `padding-block` and a chip's own `padding` shorthand have
   the same specificity, so the later one wins — placed above `.via-tag` this rule applied its trim
   and lost its padding, and the chips came out 7.5px short (measured, not guessed). Same ordering
   idiom `.legend-home` already depends on in `PriceChart.razor.css`. A new uppercase chip added
   BELOW this block needs its own `@supports`, or it will do the same thing.

   HORIZONTALLY THESE ARE ALREADY CENTRED and were measured to be: the text advance sits 7px from both
   edges. Do not "fix" that too. Guarded, so a browser without `text-box` keeps today's padding and
   today's look rather than a chip 7.5px short. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(12px - 1cap)) {
    .via-tag[b-u1kqyldli3], .via-step[b-u1kqyldli3], .grp-tag[b-u1kqyldli3], .noroute-tag[b-u1kqyldli3], .home-tag[b-u1kqyldli3] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(12px - 1cap);
    }

    .shop-best-new[b-u1kqyldli3] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(11px - 1cap);
    }

    .addon-type[b-u1kqyldli3] {
        text-box: trim-both cap alphabetic;
        padding-block: calc(13px - 1cap);
    }
}

.addon-type.type-dlc[b-u1kqyldli3] {
    color: var(--sw2-tx);
}

.addon-type.type-bundle[b-u1kqyldli3] {
    color: var(--gold);
}

/* Rows the pipeline is nearly certain are ONE product and refuses to merge, drawn as one block.
   A left bar plus the hedge above them, and deliberately NOT a tinted background: .game-row's hover
   state IS var(--sf2), so a recessed block would look permanently hovered and hover would go mute.

   The bar is --mut, the SAME ink as its caption, so the two read as one mark. It was --ln2 first and
   that was invisible in both themes, not just the light one: measured against the card it carries,
   --ln2 is #EBE2D4 on #FFFFFF and #20202C on #15151E — about 1.2:1 either way, i.e. a hairline
   colour doing a marker's job. --mut is the tier the FREE token was measured onto (3.68:1 on the
   white card, 6.50:1 on the dark one) and is the quietest thing in the palette that is still seen. */
.addon-same[b-u1kqyldli3] {
    position: relative;
}

/* The bar takes NO layout space, deliberately. As a real border-left it pushed the block's rows 2px
   right, so their covers fell out of the column the ungrouped rows keep — and compensating with a
   padding-left could only ever be right at one width: <GameRow> already compacts its own padding
   from 14px to 12px on phones, so a fixed compensation fixed the desktop and broke the phone
   (measured: covers at 31px against 29px). A positioned pseudo-element cannot have that bug, and it
   paints above .game-row:hover's background where an inset box-shadow would not. */
.addon-same[b-u1kqyldli3]::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 2px;
    background: var(--mut);
}

/* The wrapper breaks .game-row + .game-row, so both seams around it are restated — the same edge
   Home.razor.css's .list-addons had to close. */
.addon-same + .addon-same[b-u1kqyldli3],
.addons-list > .addon-same:not(:first-child)[b-u1kqyldli3] {
    border-top: 1px solid var(--ln);
}

.addons-list[b-u1kqyldli3]  .addon-same + .game-row {
    border-top: 1px solid var(--ln);
}

.addon-same[b-u1kqyldli3]  .game-row + .game-row {
    border-top-style: dashed;
}

.addon-same-note[b-u1kqyldli3] {
    margin: 0;
    padding: 7px 14px 1px;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--mut);
}

/* The full sentence, once for the whole list. --mut is the same tier the FREE token was measured
   onto: legible on the surface, and not a figure competing with the prices to its right. */
.addons-note[b-u1kqyldli3] {
    margin: -0.2rem 0.25rem 0.6rem;
    font-size: 0.76rem;
    line-height: 1.4;
    color: var(--mut);
}

/* Phone compaction, the two-line name and the sub-line all belong to <GameRow> now and travel with
   it — this list no longer restates any of them. */

/* Description */
.description-section[b-u1kqyldli3] {
    margin-bottom: 2rem;
}

/* `pre-line` keeps the line breaks INSIDE a block — Nintendo writes a heading on its own line above
   the paragraph it introduces, and HTML would collapse that into the sentence. The breaks BETWEEN
   blocks are paragraphs (DescriptionParagraphs), so they are spaced by this margin and not by an empty
   line. Runs of spaces stay collapsed: only the newlines are the vendor's structure. */
.description-section p[b-u1kqyldli3] {
    color: var(--tx2);
    line-height: 1.7;
    white-space: pre-line;
    margin: 0 0 0.85rem;
}

.description-section p:last-child[b-u1kqyldli3] {
    margin-bottom: 0;
}

/* Screenshots */
.screenshots-section[b-u1kqyldli3] {
    margin-bottom: 2rem;
}

/* Attribution for a borrowed gallery. Sits between the heading and the grid so it is read BEFORE
   the pictures, not discovered under them — the whole point is that it qualifies what they show. */
.screenshots-credit[b-u1kqyldli3] {
    margin: -0.35rem 0 0.6rem;
    font-size: 0.85rem;
    color: var(--mut);
}

.screenshots-credit a[b-u1kqyldli3] {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.screenshots-credit a:hover[b-u1kqyldli3] {
    color: var(--tx);
}

.screenshots-grid[b-u1kqyldli3] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 0.75rem;
}

/* The grid cell is a button (it opens the full-screen viewer), so it has to be stripped back to
   the picture it wraps — a UA button brings its own background, border, padding and font. */
.screenshots-grid .shot[b-u1kqyldli3] {
    display: block;
    width: 100%;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    cursor: zoom-in;
    border-radius: 12px;
}

.screenshots-grid .shot:focus-visible[b-u1kqyldli3] {
    outline: 2px solid var(--acc);
    outline-offset: 3px;
}

.screenshots-grid .shot:hover img[b-u1kqyldli3] {
    border-color: color-mix(in srgb, var(--acc) 45%, var(--ln));
}

/* Same reservation as the hero, and here the ratio needs no hint: Nintendo ships screenshots 16:9.
   Without it a row of them was zero-height until each decoded, so the page grew under the reader as
   they arrived one by one — everything below walked down the screen. */
.screenshots-grid img[b-u1kqyldli3] {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    display: block;
    background: color-mix(in srgb, var(--tx) 7%, var(--sf2));
    border-radius: 12px;
    border: 1px solid var(--ln);
}

.muted[b-u1kqyldli3] {
    color: var(--mut);
}

/* Rating (header) */
.rating-block[b-u1kqyldli3] {
    margin-top: 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.rating-agg[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.95rem;
}

.rating-agg-num[b-u1kqyldli3] {
    font-weight: 800;
    color: var(--gold);
    font-family: var(--fn);
}

.rating-agg-count[b-u1kqyldli3] {
    color: var(--mut);
    font-size: 0.85rem;
}

.rating-mine[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 0.9rem;
    color: var(--mut);
}

.rating-mine-label[b-u1kqyldli3] {
    color: var(--mut);
}

.rating-clear[b-u1kqyldli3] {
    background: none;
    border: 1px solid var(--ln);
    color: var(--mut);
    border-radius: 8px;
    cursor: pointer;
    padding: 2px 8px;
    font-size: 0.85rem;
    line-height: 1.2;
}

.rating-clear:hover[b-u1kqyldli3] {
    color: var(--red);
    border-color: var(--red);
}

/* Action capsules: one primary (follow price), the rest quiet. */
.lib-actions[b-u1kqyldli3] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.55rem;
    margin-top: 1rem;
}

.lib-btn[b-u1kqyldli3] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35em;
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 0.48rem 1.05rem;
    font-size: 0.88rem;
    font-family: inherit;
    font-weight: 800;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.lib-btn:hover[b-u1kqyldli3] { border-color: var(--mut); }

.lib-btn.pri[b-u1kqyldli3] {
    background: var(--acc);
    border-color: var(--acc-brd);
    color: var(--acc-ink);
}

.lib-btn.pri:hover[b-u1kqyldli3] {
    opacity: 0.9;
    border-color: var(--acc-brd);
}

.lib-btn.on[b-u1kqyldli3] {
    background: var(--acc-soft);
    border-color: var(--red);
    color: var(--red);
}

.lib-btn.owned.on[b-u1kqyldli3] {
    background: var(--deal-bg);
    border-color: var(--deal-brd);
    color: var(--deal);
}

/* An active follow stays a filled primary — state shows in the label + a deal tint. */
.lib-btn.pri.follow.on[b-u1kqyldli3] {
    background: var(--deal);
    border-color: var(--deal-brd);
    color: #fff;
}

/* Struck-through regular price beside the discounted one */
.price-regular[b-u1kqyldli3] {
    color: var(--mut);
    text-decoration: line-through;
    font-size: 0.8rem;
    margin-left: 0.4rem;
    font-weight: 400;
}

/* Mobile */
@media (max-width: 768px) {
    .detail-backdrop[b-u1kqyldli3] {
        height: 220px;
    }

    .game-header[b-u1kqyldli3] {
        flex-direction: column;
        gap: 1rem;
    }

    .game-cover[b-u1kqyldli3] {
        width: 100%;
        max-width: 280px;
    }

    .price-table[b-u1kqyldli3] {
        /* The local price is sized to its CONTENT, not to a fraction. As a fixed 0.75fr it was
           ~67px, and a long value like "MX$166.50" needs ~72px — so it silently spilled into the
           estimated-cost column and the two numbers touched with no gap at all. `auto` lets that
           one column take exactly what it needs and the three proportional ones share the rest,
           which adapts to whatever currency a game actually lists.
           Since 2026-09-05 the tracks live on the TABLE, so this measures every row at once and the
           column is as wide as the widest price in it — before, `auto` was resolved per row and the
           four rows disagreed by up to 9px (see .price-table). */
        grid-template-columns: minmax(0, 1.05fr) auto minmax(0, 1.15fr) minmax(0, 0.85fr);
        column-gap: 8px;
    }

    .price-table.no-discount[b-u1kqyldli3] {
        /* Same arithmetic as the wide rule: 1.9 + 1.15 = the old 1.05 + 1.15 + 0.85, so the estimate
           keeps its share and the region column absorbs the collapsed one. */
        grid-template-columns: minmax(0, 1.9fr) auto minmax(0, 1.15fr) auto;
    }

    .price-header[b-u1kqyldli3], .price-row[b-u1kqyldli3] {
        font-size: 0.78rem;
        padding: 0.5rem 0;
    }

    .price-header > :first-child[b-u1kqyldli3],
    .price-row > :first-child[b-u1kqyldli3] {
        padding-left: 0.55rem;
    }

    .price-header > :last-child[b-u1kqyldli3],
    .price-row > :last-child[b-u1kqyldli3] {
        padding-right: 0.55rem;
    }

    /* Never let the local figure be the thing that breaks the row apart. */
    .price-row > span:nth-child(2)[b-u1kqyldli3],
    .price-header > span:nth-child(2)[b-u1kqyldli3] {
        white-space: nowrap;
    }

    /* Stop the estimated-cost cell (real price + vs-best chip + strikethrough "was") from spilling
       into the local-price column — the "83.00 kr€7.68 ✓best €19.24" collision. min-width:0 lets
       cells shrink instead of overflowing; the cell stacks — real price + vs-best on line 1, the
       strikethrough "was" wraps to its own line. */
    .price-row > span[b-u1kqyldli3],
    .price-header > span[b-u1kqyldli3],
    .price-row .region[b-u1kqyldli3] {
        min-width: 0;
    }

    .eur-price[b-u1kqyldli3] {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 1px 4px;
    }

    .eur-price .eur-amount[b-u1kqyldli3] { white-space: nowrap; }
    .eur-price .price-regular[b-u1kqyldli3] { flex-basis: 100%; margin: 0; font-size: 0.72rem; }
    /* Own line, like the strikethrough above it: inline it ran the full width of the cell and read
       as if it belonged to the discount column next door ("⚠ upd. 30.07.2026 until 18.08.2026").
       It is also allowed to WRAP here — nowrap made it wider than its column, so it kept reaching
       into the discount cell no matter how the columns were sized. */
    .eur-price .stale-note[b-u1kqyldli3] {
        flex-basis: 100%;
        margin-top: 0;
        font-size: 0.62rem;
        white-space: normal;
    }
    .coupon-markup[b-u1kqyldli3] { white-space: nowrap; }
    .sale-until-cell[b-u1kqyldli3] { font-size: 0.62rem; white-space: normal; }
    .region small[b-u1kqyldli3] { font-size: 0.68rem; }

    /* Header labels can be two words ("ESTIMATED COST", "LOCAL PRICE") — shrink the header a touch so
       they wrap to two tidy lines and never overflow the narrow columns on phones. */
    .price-header[b-u1kqyldli3] {
        font-size: 0.64rem;
        letter-spacing: 0.02em;
        line-height: 1.15;
    }

    .home-tag-full[b-u1kqyldli3] { display: none; }

    .home-tag-mini[b-u1kqyldli3] {
        display: inline-block;
        margin-left: 5px;
        font-size: 0.55rem;
        padding: 1px 5px;
        letter-spacing: 0;
    }
}

/* Short landscape: a phone on its side is ~390px tall and the chrome above the feed used to eat
   two thirds of it. Vertical only — portrait and desktop are untouched. */
@media (orientation: landscape) and (max-height: 520px) {
    /* The page leads with a big cover + title block, which pushed the price table — the reason
       anyone opens the page — entirely below the fold. */
    .game-header[b-u1kqyldli3] {
        gap: 0.9rem;
        margin-bottom: 0.8rem;
    }

    .game-cover[b-u1kqyldli3] {
        max-width: 190px;
    }

    .lib-actions[b-u1kqyldli3] {
        margin-top: 0.5rem;
    }
}

/* §8e — "why this price", under the open region row's ladder. Deliberately quieter than the receipt
   above it: the receipt is what you pay, this is an explanation of how it got there. */
.why-block[b-u1kqyldli3] {
    margin-top: 0.75rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--ln);
}

.why-head[b-u1kqyldli3] {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--tx2);
    margin-bottom: 0.4rem;
}

.why-legs[b-u1kqyldli3] {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.78rem;
}

/* Three columns, and the last one is the point — so it is the one that keeps its width when the row
   has to wrap on a phone. */
.why-legs li[b-u1kqyldli3] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 0.25rem 0.6rem;
    align-items: baseline;
    padding: 0.18rem 0;
}

.why-what[b-u1kqyldli3] {
    color: var(--mut);
    min-width: 0;
}

.why-was[b-u1kqyldli3] {
    color: var(--tx2);
    font-variant-numeric: tabular-nums;
}

.why-d[b-u1kqyldli3] {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    text-align: right;
    min-width: 4.2rem;
}

/* The shops that fund this wallet, so a viewer holding several can see the difference — the one question
   a markup cannot answer alone. The bank row closes the list because it is the reference the markups are
   markups OVER, and it is muted and italic so it can never be mistaken for a row you could buy. */
.why-shops[b-u1kqyldli3] {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0.4rem 0 0;
    border-top: 1px solid var(--ln);
    font-size: 0.76rem;
}

.why-shops li[b-u1kqyldli3] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 0.25rem 0.6rem;
    align-items: baseline;
    padding: 0.14rem 0;
    color: var(--tx2);
}

/* The shop the row above is priced through — the list must not read as an open choice when one of them
   is already the answer. */
.why-shops li.on .why-shop-name[b-u1kqyldli3],
.why-shops li.on .why-shop-price[b-u1kqyldli3] { font-weight: 600; color: var(--tx); }

.why-shops li.ref[b-u1kqyldli3] {
    color: var(--mut);
    font-style: italic;
}

.why-shop-name[b-u1kqyldli3] { min-width: 0; }

.why-shop-price[b-u1kqyldli3],
.why-shop-mk[b-u1kqyldli3] {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.why-shop-mk[b-u1kqyldli3] { min-width: 4.2rem; }

/* The total is the product of the legs above it, so it reads as a sum line rather than a further item. */
.why-total[b-u1kqyldli3] {
    border-top: 1px solid var(--ln);
    margin-top: 0.2rem;
    padding-top: 0.35rem;
}

.why-d.good[b-u1kqyldli3] { color: var(--deal); }
.why-d.bad[b-u1kqyldli3]  { color: var(--warn, var(--tx2)); }

.why-kind[b-u1kqyldli3] {
    margin: 0.45rem 0 0;
    font-size: 0.76rem;
    color: var(--tx2);
}

/* C3 at the metric: whose measurement, at which reading, against what. Small, but never hidden behind
   a tooltip — a claim about somebody else's pricing has to carry its method in the open. */
.why-src[b-u1kqyldli3] {
    margin: 0.4rem 0 0;
    font-size: 0.72rem;
    line-height: 1.45;
    color: var(--mut);
}

/* ── reader reports ─────────────────────────────────────────────────────────────────────────── */

/* The page-level flag, last in the region block. Right-aligned so it does not read as another line
   of explanation beside the "N regions hidden" notes above it. */
.rgn-report[b-u1kqyldli3] {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.15rem;
}

/* The region row's flag, last in the open drawer. Same alignment for the same reason. */
.rgn-row-report[b-u1kqyldli3] {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.35rem;
}

/* One flag per add-on row.

   The wrapper exists because GameRow's root is an <a> and a button may not live inside one. It is
   layout-neutral except for the width the flag takes, but it DOES break every `.game-row + .game-row`
   seam — GameRow's own included, since scoped CSS does not cross the boundary — so all three are
   restated below. Same edge `.addon-same` had to close, and the reason that comment exists. */
.addon-line[b-u1kqyldli3] {
    display: flex;
    align-items: center;
}

.addon-line[b-u1kqyldli3]  .game-row {
    flex: 1 1 auto;
    min-width: 0;
}

/* GameRow's own `.game-row + .game-row` seam, restated across the wrapper. One `::deep` per selector
   — Blazor rewrites the compound immediately before it and leaves the rest alone, so a second one in
   the same chain matches nothing. */
.addon-line + .addon-line[b-u1kqyldli3]  .game-row,
.addon-same + .addon-line[b-u1kqyldli3]  .game-row {
    border-top: 1px solid var(--ln);
}

/* …and the dashed seam INSIDE a same-edition block, which is the more specific of the two. */
.addon-same > .addon-line + .addon-line[b-u1kqyldli3]  .game-row {
    border-top: 1px dashed var(--ln);
}

/* The flag sits in the row's own right padding rather than adding to it, so a row keeps its width for
   the price. It is the last thing in the row, which on a phone is where a thumb already is. */
.addon-line[b-u1kqyldli3]  .rf {
    margin-left: -0.35rem;
    margin-right: 0.35rem;
}

/* The chart's own flag, under the row drill-down chart. Right-aligned like the row's other report
   control below it, so the drawer has one edge for "something here is wrong" rather than two. */
.rgn-chart-report[b-u1kqyldli3] {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.15rem;
}

/* The flag under the three "why this table is shorter than all regions" lines. Indented to sit under
   the sentences it belongs to rather than under the table below them. */
.rgn-notice-report[b-u1kqyldli3] {
    display: flex;
    margin: -0.15rem 0 0.35rem;
}

/* The ladder's heading becomes a row: its sentence, then the flag at the end.

   NO NEGATIVE MARGIN here, unlike the add-on row's flag. `.ladder` carries `padding-right: 0`, so
   there is no padding for the glyph to sit in and pulling it right hung it 5px past the card's edge
   and gave the card its own horizontal scrollbar — measured, not read off the stylesheet, which is
   the only way this kind of thing is caught. The sentence takes the rest of the row and wraps inside
   it, so the heading is no taller than it was. */
.ladder-head[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    gap: 0.3rem;
}

.ladder-head-t[b-u1kqyldli3] {
    flex: 1 1 auto;
    min-width: 0;
}

.ladder-head[b-u1kqyldli3]  .rf {
    flex: none;
}

/* ── EDIT MODE ────────────────────────────────────────────────────────────────────────────────────
   Two decisions per region — is it compared, do I hold an account there — as two tick columns, with
   one Save at the foot of the screen. It replaces the row's `✕`/`＋` and the drawer's padlocked
   account switch; both are deleted, and the notes where they used to be styled say why.
   Design record and the argument: docs/PRICE_SURFACES.md. */

/* The heading row that carries the way in. `h2` keeps its own bottom border, so the button has to sit
   INSIDE the same line rather than above it — otherwise the rule would cut between them. */
.sec-head[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.sec-head h2[b-u1kqyldli3] {
    flex: 1;
    min-width: 0;
}

/* Quiet at rest: it opens a mode, it does not change anything. Same register as `.rgn-hidden-btn` — an
   affordance, not a decision — but it is a real button because it acts on the whole table, and on a
   phone there is no hover to discover an underline with. */
.rgn-edit-btn[b-u1kqyldli3] {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    flex: 0 0 auto;
    margin-bottom: 1rem;
    padding: 5px 11px;
    background: none;
    border: 1px solid var(--ln);
    border-radius: 9px;
    font: 700 0.78rem var(--fn);
    color: var(--tx2);
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.rgn-edit-btn:hover[b-u1kqyldli3],
.rgn-edit-btn:focus-visible[b-u1kqyldli3] {
    color: var(--acc);
    border-color: color-mix(in srgb, var(--acc) 45%, transparent);
}

/* The mode's own column set. The LOCAL price leaves — the question here is "do I want this region" and
   the figure that answers it is what the region would cost the reader — which is also what pays for two
   touch-sized columns at 412px. The tick tracks are FIXED: they hold one 22px box each and must not
   breathe with the row's content, or the two columns would step in and out as prices change length. */
.price-table.editing[b-u1kqyldli3] {
    grid-template-columns: 2fr 1fr 92px 92px;
}

.tick-cell[b-u1kqyldli3] {
    display: flex;
    align-items: center;
    justify-content: center;
}

.tick-head[b-u1kqyldli3] {
    text-align: center;
}

/* A DRAWN box, not an input[type=checkbox]: a collapsed market answers for nine regions at once, so
   MIXED is a real state, and `indeterminate` is settable only from script while `aria-checked="mixed"`
   says it in the markup. The 22px box is the drawing; the button around it is the touch target. */
.tick[b-u1kqyldli3] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 40px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
}

.tick-box[b-u1kqyldli3] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border: 1.5px solid var(--ln);
    border-radius: 6px;
    background: var(--sf2);
    color: var(--acc-ink);
    font: 800 0.72rem var(--fn);
    line-height: 1;
    transition: background 0.12s, border-color 0.12s;
}

.tick:hover:not(:disabled) .tick-box[b-u1kqyldli3],
.tick:focus-visible .tick-box[b-u1kqyldli3] {
    border-color: color-mix(in srgb, var(--acc) 55%, transparent);
}

.tick.on .tick-box[b-u1kqyldli3],
.tick.mixed .tick-box[b-u1kqyldli3] {
    background: var(--acc);
    border-color: var(--acc);
}

/* Mixed reads as "partly", so it is the same fill at a lower strength rather than a third colour: the
   two states are one answer, and a new hue here would compete with the accent that already means
   "yours" three columns to the left. */
.tick.mixed .tick-box[b-u1kqyldli3] {
    background: color-mix(in srgb, var(--acc) 55%, transparent);
}

/* Disabled, never removed — the reason is in the title, and a missing control reads as one the reader
   lost. Not `opacity` on the button: the box still carries an answer and has to stay legible. */
.tick:disabled[b-u1kqyldli3] {
    cursor: default;
}

.tick:disabled .tick-box[b-u1kqyldli3] {
    border-color: var(--ln);
    background: color-mix(in srgb, var(--mut) 22%, transparent);
    color: var(--mut);
}

/* A region the reader has just unticked: still on screen, still priced, simply not counted after Save.
   Dims the two READING cells only — the ticks are the controls that undo it and must stay at full
   contrast, which is why this is not `.price-row.excluded`'s blanket opacity. */
.price-row.unticked .region[b-u1kqyldli3],
.price-row.unticked .eur-price[b-u1kqyldli3] {
    opacity: 0.5;
}

/* A collapsed market's members, opened. Indented under their group and set a notch quieter: they are
   the same offer, and the only thing that differs between them is the tick. */
.price-row.member[b-u1kqyldli3] {
    background: var(--sf2);
    font-size: 0.82rem;
}

.price-row.member > :first-child[b-u1kqyldli3] {
    padding-left: 2rem;
}

.price-row.member .eur-price.member-cost[b-u1kqyldli3] {
    font-weight: 600;
    font-size: 0.72rem;
    color: var(--mut);
}

/* THE SAVE BAR — fixed to the foot of the screen, because the mode spans BOTH region tables and the
   reader is meant to keep scrolling while they decide. It is also what makes "leaving discards" fair
   rather than a trap: the count follows them down the page.
   A sibling of the lightbox, not a child of `.game-detail`, for the reason that element's own note
   gives: every direct child of it is a stacking context, and a fixed element inside one is trapped. */
.edit-bar[b-u1kqyldli3] {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 900;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1rem calc(0.7rem + env(safe-area-inset-bottom));
    background: var(--sf);
    border-top: 1px solid var(--ln);
    box-shadow: 0 -10px 26px rgba(0, 0, 0, 0.28);
    font-family: var(--fn);
}

.edit-count[b-u1kqyldli3] {
    flex: 1;
    min-width: 0;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--tx);
}

.edit-count.none[b-u1kqyldli3] {
    font-weight: 400;
    color: var(--mut);
}

.edit-bar button[b-u1kqyldli3] {
    flex: 0 0 auto;
    font: 800 0.82rem var(--fn);
    border-radius: 10px;
    padding: 8px 16px;
    cursor: pointer;
    transition: filter 0.15s, color 0.15s, border-color 0.15s;
}

.edit-cancel[b-u1kqyldli3] {
    background: none;
    border: 1px solid var(--ln);
    color: var(--tx2);
}

.edit-cancel:hover[b-u1kqyldli3] {
    color: var(--tx);
    border-color: var(--mut);
}

.edit-save[b-u1kqyldli3] {
    background: var(--acc);
    border: 1px solid var(--acc);
    color: var(--acc-ink);
}

.edit-save:hover:not(:disabled)[b-u1kqyldli3] {
    filter: brightness(1.08);
}

.edit-save:disabled[b-u1kqyldli3] {
    opacity: 0.45;
    cursor: default;
}

/* What the save DID, where the bar was — a mode that simply vanishes leaves the reader hunting for the
   change they just made. It is a status line, so it says its piece and then gets out of the way. */
.edit-saved[b-u1kqyldli3] {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 900;
    margin: 0;
    padding: 0.7rem 1rem calc(0.7rem + env(safe-area-inset-bottom));
    background: var(--sf);
    border-top: 1px solid color-mix(in srgb, var(--acc) 40%, var(--ln));
    color: var(--tx);
    font: 700 0.82rem var(--fn);
    animation: edit-saved-out-b-u1kqyldli3 5s forwards;
}

@keyframes edit-saved-out-b-u1kqyldli3 {
    0%, 78% { opacity: 1; }
    100% { opacity: 0; visibility: hidden; }
}

@media (prefers-reduced-motion: reduce) {
    .edit-saved[b-u1kqyldli3] { animation: none; }
}

@media (max-width: 640px) {
    /* Two touch columns still fit, and the region and cost tracks share what is left as FRACTIONS.
       Not `auto` for the cost — that is what the phone's reading layout uses for the LOCAL price, which
       is one short figure, whereas this cell carries the amount, its percentage and the staleness note:
       measured, `auto` took its max-content (244px of a 378px table) and starved the region column down
       to 17px, so the codes drew over the prices. */
    .price-table.editing[b-u1kqyldli3] {
        grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) 46px 46px;
    }

    .tick[b-u1kqyldli3] { width: 46px; height: 42px; }

    .tick-head[b-u1kqyldli3] { font-size: 0.5rem; letter-spacing: 0; }

    .price-row.member > :first-child[b-u1kqyldli3] { padding-left: 1.4rem; }

    .rgn-edit-btn[b-u1kqyldli3] { font-size: 0.72rem; padding: 4px 9px; }
}
/* /Pages/Home.razor.rz.scp.css */
/* Catalog: tiles grid / dense list, add-on clusters, pagination. Colors via app.css tokens. */

.games-grid[b-zke6zyozp7] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}

/* Grouped catalog: games WITH add-ons span a full-width cluster row. */
.catalog-grid[b-zke6zyozp7] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
    grid-auto-flow: dense;
    /* stretch (not start) so every tile in a row is the same height — the bottom status cluster then
       lines up, and a Price-N/A tile is the same size as its neighbours. */
    align-items: stretch;
}

.cluster[b-zke6zyozp7] {
    grid-column: 1 / -1;
    min-width: 0;
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 18px;
    padding: 0.75rem;
}

.cluster-main[b-zke6zyozp7] {
    flex: 0 0 190px;
    max-width: 190px;
}

.cluster-addons[b-zke6zyozp7] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-content: flex-start;
}

.mini-more[b-zke6zyozp7] {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 118px;
    align-self: stretch;
    min-height: 118px;
    border: 1px dashed var(--ln);
    border-radius: 12px;
    background: var(--sf);
    color: var(--acc);
    font-weight: 800;
    font-size: 1rem;
    text-decoration: none;
    transition: border-color 0.15s;
}

.mini-more:hover[b-zke6zyozp7] {
    border-color: var(--mut);
}

/* ---- List view («Терминал»): one surface card, hairline-separated rows. ---- */
.games-list[b-zke6zyozp7] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    overflow: hidden;
}

/* Add-on rows sit under their game, indented and slightly recessed. */
.list-addons[b-zke6zyozp7] {
    background: var(--sf2);
    border-top: 1px dashed var(--ln);
}

.list-addons[b-zke6zyozp7]  .game-row {
    padding-left: 2.9rem;
}

.list-addons[b-zke6zyozp7]  .game-row + .game-row {
    border-top-style: dashed;
}

.list-more[b-zke6zyozp7] {
    display: block;
    padding: 6px 14px 8px 2.9rem;
    font-size: 0.78rem;
    font-weight: 800;
    color: var(--acc);
    text-decoration: none;
}

.list-more:hover[b-zke6zyozp7] {
    text-decoration: underline;
}

/* Rows need a top border against the card edge when following a recessed add-on block. */
.games-list[b-zke6zyozp7]  .list-addons + .game-row {
    border-top: 1px solid var(--ln);
}

/* Mobile: stack the cluster's game over a horizontally-scrolling add-on strip. */
@media (max-width: 768px) {
    .cluster[b-zke6zyozp7] {
        flex-direction: column;
        gap: 0.6rem;
        padding: 0.6rem;
    }

    .cluster-main[b-zke6zyozp7] {
        flex: none;
        max-width: none;
        width: 150px;
    }

    /* A horizontal strip, and the site already has two of those — the Discover rails and the header's
       tab row. Both HIDE the native scrollbar and give the affordance another way; this one did not,
       and below 768px that reaches a narrow DESKTOP window as well as a phone. A phone draws an
       overlay bar that fades, but a mouse gets the classic control with its two arrow buttons: 15px
       of chrome under every cluster, permanently, measured — and no other strip on the site has it.
       What says "there is more" instead is the next tile, cut by the edge (the strip is never sized
       to end on a card boundary), and `allregions.railInitAll` gives the wheel the rails' behaviour,
       which is what a mouse is left with once the bar is gone: scroll the strip, and hand the page
       back its own scroll at either end. */
    .cluster-addons[b-zke6zyozp7] {
        flex-wrap: nowrap;
        overflow-x: auto;
        width: 100%;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;      /* Firefox */
        -ms-overflow-style: none;   /* legacy Edge */
    }

    .cluster-addons[b-zke6zyozp7]::-webkit-scrollbar {
        display: none;              /* Chrome / Safari */
    }

    .mini-more[b-zke6zyozp7] {
        width: 104px;
        min-height: 104px;
    }

    .list-addons[b-zke6zyozp7]  .game-row,
    .list-more[b-zke6zyozp7] {
        padding-left: 1.6rem;
    }
}

/* Touch only, exactly as the rails do it: snap makes a swipe land on a whole tile. Safe here because
   nothing scrolls this strip programmatically — the rails' desktop track has no snap ON PURPOSE,
   since Chromium re-snaps every scrollLeft assignment and that fights the wheel handler. Written as
   its own query rather than nested inside the one above, so no CSS-isolation rewriter has to. */
@media (max-width: 768px) and (hover: none) and (pointer: coarse) {
    .cluster-addons[b-zke6zyozp7] {
        scroll-snap-type: x proximity;
    }

    .cluster-addons[b-zke6zyozp7]  .mini-card,
    .mini-more[b-zke6zyozp7] {
        scroll-snap-align: start;
    }
}

.empty[b-zke6zyozp7] {
    grid-column: 1 / -1;
    text-align: center;
    padding: 3rem;
    color: var(--mut);
    font-size: 1.1rem;
}

/* The carousels' own one-liner (fetch failed / nothing to show). Left-aligned and quiet, unlike
   .empty above: the catalog underneath is fine, so this reports on a strip of the page rather than
   standing in for the whole feed. */
.rails-note[b-zke6zyozp7] {
    display: flex;
    align-items: center;
    gap: 0.2rem;
    flex-wrap: wrap;
    margin: 0.2rem 0 1.4rem;
    color: var(--mut);
    font-size: 0.85rem;
    font-weight: 600;
}

/* «Показать ещё» — one friendly button instead of pagination. */
.show-more-wrap[b-zke6zyozp7] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1.6rem;
    padding-bottom: 1rem;
}

.show-more[b-zke6zyozp7] {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    border-radius: 999px;
    padding: 0.6rem 1.8rem;
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 800;
    cursor: pointer;
    transition: opacity 0.15s;
}

.show-more:hover:not(:disabled)[b-zke6zyozp7] {
    opacity: 0.9;
}

.show-more:disabled[b-zke6zyozp7] {
    opacity: 0.55;
    cursor: progress;
}

.page-info[b-zke6zyozp7] {
    color: var(--mut);
    font-size: 0.85rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 480px) {
    .games-grid[b-zke6zyozp7],
    .catalog-grid[b-zke6zyozp7] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.7rem;
    }
}
/* /Pages/NewReleases.razor.rz.scp.css */
/* Deals / New Releases: same grid + list shells as the catalog (scoped per page). */
.games-grid[b-i8b4g7ey7q] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}

.games-list[b-i8b4g7ey7q] {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-bottom-color: var(--ln2);
    border-radius: 16px;
    overflow: hidden;
}

.empty[b-i8b4g7ey7q] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
    font-size: 1.1rem;
}

.pagination[b-i8b4g7ey7q] {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 1rem;
    margin-top: 1.6rem;
    padding-bottom: 1rem;
}

.btn-page[b-i8b4g7ey7q] {
    background: var(--sf);
    color: var(--tx2);
    border: 1px solid var(--ln);
    padding: 0.45rem 1.25rem;
    border-radius: 999px;
    font-family: inherit;
    font-weight: 800;
    cursor: pointer;
    transition: border-color 0.2s;
}

.btn-page:hover:not(:disabled)[b-i8b4g7ey7q] {
    border-color: var(--mut);
}

.btn-page:disabled[b-i8b4g7ey7q] {
    opacity: 0.4;
    cursor: not-allowed;
}

.page-info[b-i8b4g7ey7q] {
    color: var(--mut);
    font-size: 0.9rem;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 480px) {
    .games-grid[b-i8b4g7ey7q] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.7rem;
    }
}

/* Order switch — the same capsule segment the filter bar uses for tiles/list, so the two read as one
   family of controls. Scrolls horizontally instead of wrapping: three labels do not fit on a 360px
   phone, and a wrapped segment loses the "one control, three peers" shape that makes it legible. */
.sort-seg[b-i8b4g7ey7q] {
    display: flex;
    gap: 2px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
    padding: 3px;
    margin: 0 0 0.7rem;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
}

.sort-seg[b-i8b4g7ey7q]::-webkit-scrollbar { display: none; }

.sort-btn[b-i8b4g7ey7q] {
    border: none;
    background: transparent;
    color: var(--mut);
    border-radius: 999px;
    padding: 5px 13px;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 800;
    white-space: nowrap;
    cursor: pointer;
}

.sort-btn.on[b-i8b4g7ey7q] {
    background: var(--acc);
    color: var(--acc-ink);
}
/* /Pages/PublicProfile.razor.rz.scp.css */
.pub-profile[b-1toykl0el5] {
    max-width: 720px;
    margin: 1.5rem auto;
}

.loading[b-1toykl0el5] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
}

.muted[b-1toykl0el5] {
    color: var(--mut);
}

.pub-head[b-1toykl0el5] {
    display: flex;
    gap: 1.25rem;
    align-items: flex-start;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 1.25rem;
}



.pub-head-main[b-1toykl0el5] {
    flex: 1;
    min-width: 0;
}

.pub-head-main h1[b-1toykl0el5] {
    margin: 0 0 0.5rem;
    font-size: 1.5rem;
    color: var(--tx);
}

.pub-stats[b-1toykl0el5] {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    color: var(--mut);
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.pub-actions[b-1toykl0el5] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.pub-btn[b-1toykl0el5] {
    background: var(--sf2);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.45rem 1rem;
    font-size: 0.9rem;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.15s;
}

.pub-btn:hover[b-1toykl0el5] { background: var(--sf2); }
.pub-btn.primary[b-1toykl0el5] { background: var(--acc); border-color: var(--acc); color: #fff; }
.pub-btn.primary:hover[b-1toykl0el5] { background: var(--acc); }
/* Established relationship = a filled green badge. The old rule only tinted the text/border and left
   the default grey button background, so it read as unstyled; adding the --deal-bg fill finishes it.
   Both themes covered by tokens (light: pale green on #E6F6EC; dark: soft green on a 10% tint). */
.pub-btn.friends[b-1toykl0el5],
.pub-btn.following[b-1toykl0el5] {
    background: var(--deal-bg);
    color: var(--deal);
    border-color: var(--deal-brd);
    font-weight: 600;
}
.pub-btn.friends:hover[b-1toykl0el5],
.pub-btn.following:hover[b-1toykl0el5] { border-color: var(--deal); background: var(--deal-bg); }

.pub-private[b-1toykl0el5] {
    margin: 1.25rem 0;
    padding: 1.5rem 1.25rem;
    background: var(--sf);
    border: 1px dashed var(--ln);
    border-radius: 12px;
    text-align: center;
}
.pub-private p[b-1toykl0el5] { margin: 0; color: var(--mut); line-height: 1.6; }

/* "Friends only" tag next to a section heading — small green chip via the deal tokens. */
.pub-friends-tag[b-1toykl0el5] {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--deal);
    background: var(--deal-bg);
    border: 1px solid var(--deal-brd);
    border-radius: 6px;
    padding: 0.1rem 0.45rem;
    letter-spacing: 0.02em;
}

.pub-contacts[b-1toykl0el5] {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.pub-contact[b-1toykl0el5] {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 0.55rem 0.85rem;
}
.pub-contact-k[b-1toykl0el5] {
    flex-shrink: 0;
    min-width: 120px;
    color: var(--mut);
    font-size: 0.85rem;
    font-weight: 600;
}
.pub-contact-v[b-1toykl0el5] {
    color: var(--tx);
    word-break: break-all;
}
.pub-contact-v.mono[b-1toykl0el5] {
    font-family: 'Consolas', monospace;
    letter-spacing: 0.04em;
    color: var(--info);
}

.pub-section[b-1toykl0el5] {
    margin-top: 1.5rem;
}

.pub-section h2[b-1toykl0el5] {
    font-size: 1.1rem;
    color: var(--tx);
    margin: 0 0 0.5rem;
    border-bottom: 1px solid var(--ln);
    padding-bottom: 0.4rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.pub-bio[b-1toykl0el5] {
    color: var(--tx2);
    line-height: 1.7;
    white-space: pre-wrap;
    word-break: break-word;
}

.pub-fav-grid[b-1toykl0el5] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 1rem;
}

@media (max-width: 560px) {
    .pub-head[b-1toykl0el5] {
        flex-direction: column;
        align-items: center;
        text-align: center;
    }
    .pub-stats[b-1toykl0el5], .pub-actions[b-1toykl0el5] { justify-content: center; }

    .pub-fav-grid[b-1toykl0el5] {
        grid-template-columns: repeat(2, 1fr);
        gap: 0.75rem;
    }
}
/* /Pages/Trending.razor.rz.scp.css */
.trending-status[b-aqjiieh7l3] {
    text-align: center;
    padding: 3rem;
    color: var(--mut);
    font-size: 1.1rem;
}
