﻿/* ============================================================================
   AllRegions — «День и Ночь» design system.
   One skeleton, two skins driven ENTIRELY by tokens:
     Light («Джой»):    milk paper, white cards, Nintendo red, yellow discount
                        stickers, tactile thick-bottom cards.
     Dark («Полночь»):  OLED near-black, indigo accent, green discount chips,
                        ambient cover glow, flat hairline cards.
   Theme switching: <html data-theme="light|dark"> (set before first paint by
   the inline script in index.html; toggled from the header). No attribute =
   follow the system via prefers-color-scheme.
   Components — global AND scoped (.razor.css) — style through these tokens
   only; never hardcode a surface/ink color in a component again.
   ============================================================================ */

:root {
    /* surfaces + ink */
    --bg: #FDF9F4;            /* page — warm milk */
    --sf: #FFFFFF;            /* cards, panels */
    --sf2: #F7F2EA;           /* inset surfaces: inputs, hover, thumbs */
    --ln: #F0E9DF;            /* hairlines / borders */
    --ln2: #EBE2D4;           /* thick-bottom ("lift") tint */
    --tx: #22252B;            /* primary ink */
    --tx2: #4A4538;           /* secondary ink */
    --mut: #8A8578;           /* muted ink */
    /* action accent (light = Nintendo red) */
    --acc: #E4032E;
    --acc-ink: #FFFFFF;
    --acc-brd: #C00227;
    --acc-soft: rgba(228, 3, 46, 0.10);
    /* constants across themes */
    --red: #E4032E;           /* hearts, destructive, brand dot */
    --deal: #0C9A5B;          /* savings / "good" semantics */
    --deal-bg: #E6F6EC;
    --deal-brd: #CDEBD8;
    --gold: #D98E00;          /* stars, chart-NEW */
    --warn: #B7791F;          /* stale-price notes */
    --info: #2E7CE4;          /* preorder */
    --coming: #7A5AF5;        /* coming-soon, SW2 flavor */
    /* discount presentation: yellow rotated sticker by day, green chip by night */
    --disc-bg: #FFD400;
    --disc-ink: #3A3000;
    --disc-rot: -5deg;
    --disc-sh: 0 3px 8px rgba(58, 48, 0, 0.18);
    /* cover overlays */
    --sw2-bg: rgba(255, 255, 255, 0.94);
    --sw2-tx: #6A48E8;
    --heart-bg: rgba(255, 255, 255, 0.95);
    /* form */
    --lift: 3px;                                        /* thick bottom — CARDS only */
    --card-sh: 0 4px 14px -8px rgba(122, 90, 40, 0.25); /* hover shadow */
    --frame-sh: 0 16px 40px -24px rgba(122, 90, 40, 0.4);
    --shelf-sh: 0 8px 16px -14px rgba(122, 90, 40, 0.55); /* mobile nav shelf */
    --glow-op: 0;                                       /* ambient cover glow (dark only) */
    /* type */
    --fd: 'Nunito', system-ui, -apple-system, sans-serif;    /* interface + headings */
    --fn: 'Manrope', system-ui, sans-serif;                  /* digits, prices, data */
    --fl: 'Unbounded', system-ui, sans-serif;                /* logo wordmark only */
}

/* The same three stacks with the flag font in FRONT, for the platforms that ship no flag glyphs
   (Windows). index.html stamps the class before first paint; the @font-face and the whole reason
   this exists are in fonts.css.

   **Restating the stacks is the point of putting this HERE.** Prepending a family cannot be done
   from a variable — CSS has no way to add to an inherited list — so the choice was between an
   obscure empty-token trick and three copied lines. Copied lines three rows below their originals
   cannot drift unseen; a trick in another file can. Change a stack above, change it here.

   Front, not back, and `unicode-range: U+1F1E6-1F1FF` on the face is what makes that safe: the
   family can only ever be consulted for regional-indicator characters, so it cannot touch a letter,
   a digit or any other emoji, and no element that prints a flag has to be found and listed. */
html.needs-flag-font {
    --fd: 'Twemoji Country Flags', 'Nunito', system-ui, -apple-system, sans-serif;
    --fn: 'Twemoji Country Flags', 'Manrope', system-ui, sans-serif;
    --fl: 'Twemoji Country Flags', 'Unbounded', system-ui, sans-serif;
}

/* A native <select> does not inherit the page font, so the tokens above never reach the pickers
   that actually carry the flags — the one place this whole file exists for. */
html.needs-flag-font select,
html.needs-flag-font option {
    font-family: var(--fd);
}

[data-theme="dark"] {
    --bg: #0B0B10;
    --sf: #15151E;
    --sf2: #1B1B27;
    --ln: #272736;
    --ln2: #20202C;
    --tx: #F1EFF7;
    --tx2: #C9C5DA;
    --mut: #9C98B0;
    --acc: #8B7CFF;
    --acc-ink: #FFFFFF;
    --acc-brd: #8B7CFF;
    --acc-soft: rgba(139, 124, 255, 0.14);
    --deal: #34D399;
    --deal-bg: rgba(52, 211, 153, 0.10);
    --deal-brd: rgba(52, 211, 153, 0.40);
    --gold: #F5C066;
    --warn: #D9A93E;
    --info: #5CA8FF;
    --coming: #A18CFF;
    --disc-bg: #34D399;
    --disc-ink: #07281B;
    --disc-rot: 0deg;
    --disc-sh: none;
    --sw2-bg: rgba(11, 11, 16, 0.72);
    --sw2-tx: #B9AFFF;
    --heart-bg: rgba(21, 21, 30, 0.86);
    --lift: 1px;
    --card-sh: 0 8px 24px -10px rgba(0, 0, 0, 0.55);
    --frame-sh: 0 16px 44px -24px rgba(0, 0, 0, 0.8);
    --shelf-sh: 0 10px 22px -16px rgba(0, 0, 0, 0.9);
    --glow-op: 0.4;
}

[data-theme="light"] {
    --bg: #FDF9F4;
    --sf: #FFFFFF;
    --sf2: #F7F2EA;
    --ln: #F0E9DF;
    --ln2: #EBE2D4;
    --tx: #22252B;
    --tx2: #4A4538;
    --mut: #8A8578;
    --acc: #E4032E;
    --acc-ink: #FFFFFF;
    --acc-brd: #C00227;
    --acc-soft: rgba(228, 3, 46, 0.10);
    --deal: #0C9A5B;
    --deal-bg: #E6F6EC;
    --deal-brd: #CDEBD8;
    --gold: #D98E00;
    --warn: #B7791F;
    --info: #2E7CE4;
    --coming: #7A5AF5;
    --disc-bg: #FFD400;
    --disc-ink: #3A3000;
    --disc-rot: -5deg;
    --disc-sh: 0 3px 8px rgba(58, 48, 0, 0.18);
    --sw2-bg: rgba(255, 255, 255, 0.94);
    --sw2-tx: #6A48E8;
    --heart-bg: rgba(255, 255, 255, 0.95);
    --lift: 3px;
    --card-sh: 0 4px 14px -8px rgba(122, 90, 40, 0.25);
    --frame-sh: 0 16px 40px -24px rgba(122, 90, 40, 0.4);
    --shelf-sh: 0 8px 16px -14px rgba(122, 90, 40, 0.55); /* mobile nav shelf */
    --glow-op: 0;
}

/* Added tokens (O.7 "why is it cheap" reason chips + O.5 empty-star outline). Light defaults on
   :root, dark overrides for both explicit dark and system-dark, so every theme state resolves.
     --sale   = eShop discount (warm / urgent, "buy the moment")
     --region = structurally-cheap region (cool / always-available)
     --star-empty = visible empty-star outline (theme-aware: readable on milk paper AND near-black) */
:root                { --sale: #E24E23; --sale-ink: #fff; --region: #3663B8; --star-empty: #C2B4A0; --nin-red: #E60012; }
[data-theme="dark"]  { --sale: #FF6A4D; --region: #7FB0FF; --star-empty: #565673; --nin-red: #FF5566; }
[data-theme="light"] { --sale: #E24E23; --region: #3663B8; --star-empty: #C2B4A0; --nin-red: #E60012; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) { --sale: #FF6A4D; --region: #7FB0FF; --star-empty: #565673; --nin-red: #FF5566; }
}

/* --chart-alt = the SECOND series on an admin chart, and it is a different hue per theme on purpose.
   The first series wears --acc, which is red on milk paper and VIOLET on near-black. Against violet a
   blue second line is unreadable — measured ΔE 11.6 in OKLab against a floor of 15, i.e. hard to tell
   apart with normal colour vision, never mind a colour-blind reader. Blue against the light theme's
   red measures 36.6, and amber against the dark theme's violet 35.0, so each theme takes the hue that
   actually separates from the accent it has. */
:root                { --chart-alt: #2E7CE4; }
[data-theme="dark"]  { --chart-alt: #F5C066; }
[data-theme="light"] { --chart-alt: #2E7CE4; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) { --chart-alt: #F5C066; }
}

/* No explicit choice → follow the system. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) {
        --bg: #0B0B10;
        --sf: #15151E;
        --sf2: #1B1B27;
        --ln: #272736;
        --ln2: #20202C;
        --tx: #F1EFF7;
        --tx2: #C9C5DA;
        --mut: #9C98B0;
        --acc: #8B7CFF;
        --acc-ink: #FFFFFF;
        --acc-brd: #8B7CFF;
        --acc-soft: rgba(139, 124, 255, 0.14);
        --deal: #34D399;
        --deal-bg: rgba(52, 211, 153, 0.10);
        --deal-brd: rgba(52, 211, 153, 0.40);
        --gold: #F5C066;
        --warn: #D9A93E;
        --info: #5CA8FF;
        --coming: #A18CFF;
        --disc-bg: #34D399;
        --disc-ink: #07281B;
        --disc-rot: 0deg;
        --disc-sh: none;
        --sw2-bg: rgba(11, 11, 16, 0.72);
        --sw2-tx: #B9AFFF;
        --heart-bg: rgba(21, 21, 30, 0.86);
        --lift: 1px;
        --card-sh: 0 8px 24px -10px rgba(0, 0, 0, 0.55);
        --frame-sh: 0 16px 44px -24px rgba(0, 0, 0, 0.8);
        --shelf-sh: 0 10px 22px -16px rgba(0, 0, 0, 0.9);
        --glow-op: 0.4;
    }
}

/* Tell the UA which skin it is painting into. Our tokens colour everything WE draw; this covers what
   the browser draws for us — form controls, scrollbars, the canvas behind the app before any CSS of
   ours applies — and it must mirror the four states resolved above exactly. Declarative on purpose:
   a JS copy would be a second place for the theme to be decided. (The Android PWA's status and
   navigation bars are a separate mechanism — <meta name="theme-color">, set in index.html.) */
:root,
[data-theme="light"] {
    color-scheme: light;
}

[data-theme="dark"] {
    color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) {
        color-scheme: dark;
    }
}

/* ============================== Base ============================== */

html, body {
    font-family: var(--fd);
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--tx);
    transition: background-color 0.18s ease, color 0.18s ease;
}

/* Page freeze while a mobile overlay (filter sheet / hamburger drawer) is open — toggled by
   allregions.lockScroll. overflow:hidden holds the current scroll position (no jump), so a swipe
   over the overlay can't scroll the feed behind it; the overlay itself keeps its own overflow. */
html.scroll-locked, html.scroll-locked body {
    overflow: hidden;
}

*, *::before, *::after {
    box-sizing: border-box;
}

/* A BUTTON'S LABEL IS NOT CONTENT — a long press on one used to select its text, and on the header's
   two segments it selected "REGIONS DE +22" over the very control the finger was resting on (owner,
   2026-09-09). Nobody copies "Regions", and on a touch device the gesture that selects text is the
   same one that waits for a press to register, so the selection is pure accident. `touch-callout`
   goes with it: on iOS the same long press raises a callout over the control.
   `<a>` IS DELIBERATELY NOT IN THIS LIST. A long press on a link raises the browser's own menu — open
   in a new tab, copy the address — which is a feature, and the feed's tiles and rows are links. So
   this covers buttons and things that claim to be buttons, and stops there.
   Inherited, so a button's inner spans and <b> are covered without naming them. */
button,
[role="button"] {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

a {
    color: var(--acc);
}

a:focus-visible, button:focus-visible, [role="button"]:focus-visible {
    outline: 2px solid var(--acc);
    outline-offset: 2px;
}

/* Digits that line up: prices, counters, table numbers. */
.money {
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
}

/* The page column. Its width is a TOKEN because a second consumer now depends on it: the shops and
   regions panels dock to this column's right edge, and a 1200 written twice is a 1200 that drifts. */
:root {
    --page-w: 1200px;
    --panel-w: 340px;
}

.container {
    max-width: var(--page-w);
    margin: 0 auto;
    padding: 0 1rem;
}

.page-title {
    font-size: 1.45rem;
    font-weight: 900;
    margin-bottom: 1rem;
    color: var(--tx);
}

/* Generic status text fallback for pages/components without their own scoped rule. */
.loading, .empty {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--mut);
}

/* "Try again" next to a failed-load message — the only way out of that state without a reload. */
.btn-retry {
    display: inline-block;
    margin-left: 0.6rem;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 800;
    padding: 0.3rem 0.9rem;
    cursor: pointer;
}

.btn-retry:hover {
    border-color: var(--mut);
    color: var(--tx);
}

/* The chart footer's badge (§2.5 of TASK_GAME_PAGE_SIMPLIFICATION.md): a count or a short reading
   whose full sentence is its `title` — the region habit, and the "N regions outside {shop}" line. A
   quiet outlined pill, deliberately NOT one of the coloured .legend-tag marks: those name a property of
   ONE line, and the pass this comes from exists to stop half a surface being marked. `cursor: help` is
   the affordance for the tooltip.
   GLOBAL on purpose, for the same reason as the site chrome below: the two badges are authored in two
   different components (PriceChart draws the habit, GameDetail the dropped count) and Blazor's scoped
   CSS does not cross that boundary, so a scoped rule would have to be written twice and kept in step. */
.chart-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border: 1px solid var(--ln);
    border-radius: 8px;
    background: var(--sf2);
    color: var(--tx2);
    font-size: 0.7rem;
    line-height: 1.5;
    cursor: help;
}

/* Brief highlight of the card the catalog scrolled back to (Back button). */
@keyframes ar-card-flash {
    0%   { box-shadow: 0 0 0 0 rgba(228, 3, 46, 0); }
    15%  { box-shadow: 0 0 0 3px var(--acc); }
    100% { box-shadow: 0 0 0 3px rgba(228, 3, 46, 0); }
}

.scroll-highlight {
    animation: ar-card-flash 1.6s ease-out;
    position: relative;
    z-index: 3;
}

/* ============================================================================
   Site chrome (header / nav / footer). Global on purpose: the nav is built
   from <NavLink> child components and Blazor's scoped CSS does not cross
   component boundaries.
   ============================================================================ */
/* ---- Safe areas -------------------------------------------------------------------------------
   The page opts into edge-to-edge (viewport-fit=cover in index.html), so the viewport now reaches
   under the system bars and every edge the layout touches has to be paid for. The horizontal pair
   sits HERE rather than on .container because two rules inside the header cancel that gutter with a
   negative margin to go full-bleed (.ns-header-top, .ns-tabs) — widening .container would leave them
   short by exactly the inset. On a phone in portrait all four are 0, so this is inert until there is
   a notch or a gesture bar to avoid. */
.ns-app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
}

.ns-header {
    background: var(--bg);
    border-bottom: 1px solid var(--ln);
    position: sticky;
    top: 0;
    z-index: 100;
}

/* Tier 1 — brand + utilities.
   The status bar's height is added to the TOP padding rather than pushing the header down, so the
   header's own surface keeps painting all the way to the screen edge (a gap there would read as a
   bar of page background above a floating header) while the logo row starts below the clock. */
.ns-header-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    padding: calc(0.6rem + env(safe-area-inset-top)) 0 0.6rem;
}

/* Logo: Unbounded wordmark + the constant red dot (red stays brand even at night). */
.ns-logo {
    font-family: var(--fl);
    font-size: 0.82rem;
    font-weight: 800;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--tx);
    text-decoration: none;
    white-space: nowrap;
}

.ns-logo::after {
    content: '●';
    color: var(--red);
    font-size: 0.6em;
    margin-left: 4px;
    vertical-align: 2px;
}

.ns-logo:hover {
    color: var(--tx);
    opacity: 0.85;
}

/* Wordmark + beta badge cluster (the badge can't live inside the <a> — see MainLayout). */
.ns-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    white-space: nowrap;
}

/* Beta badge: tiny pill in the wordmark's own font, tinted the brand red so it pairs with the
   logo dot in both themes (--acc goes violet at night, --red stays). */
.ns-beta {
    font-family: var(--fl);
    font-size: 0.5rem;
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1;
    color: var(--red);
    background: color-mix(in srgb, var(--red) 9%, transparent);
    border: 1px solid color-mix(in srgb, var(--red) 45%, transparent);
    border-radius: 999px;
    padding: 0.28em 0.75em 0.24em;
    cursor: help;
}

/* Right-hand cluster. Desktop: one inline row — context chip, theme, bell, account — exactly as
   before. Phones: the utilities detach into a fixed slide-in panel, leaving the context chip and
   the ☰ behind in the top row. */
.ns-hcluster {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* The markup inside .ns-utils is grouped for the PHONE drawer (identity / links / foot). On desktop
   those groups — and .ns-utils itself — are display:contents, so every control becomes a direct flex
   item of .ns-hcluster and `order` restores the one row this header always had:
   context · theme · bell · account. Reset in the @860 block, where DOM order rules instead. */
.ns-ctx {
    order: 1;
    /* The group and the ⋯ are two separate controls and were touching, which reads as one broken pill.
       `.ns-hcluster`'s own gap, not a new number: this wrapper sits inside that cluster and its parts
       should be spaced the way everything else in the header is. */
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.ns-theme {
    order: 2;
}

.ns-bell-slot {
    order: 3;
    display: inline-flex;
    align-items: center;
}

.ns-user {
    order: 4;
}

.ns-logout,
.ns-cta {
    order: 5;
}

/* Positioning anchor for the context popover (this was .ns-utils until the chip moved out). */
.ns-ctx {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* Desktop: not a box at all — its children flow straight into .ns-hcluster (ordered above), so the
   phone-drawer grouping costs the desktop row nothing. Phones turn it into the slide-in panel (@860).
   .ns-drawer-clip is the box that panel slides inside on phones; here it collapses with everything
   else so the desktop row is untouched. */
.ns-utils,
.ns-drawer-clip {
    display: contents;
}

/* Drawer furniture: only the phone panel has an identity block and destination links. On desktop the
   wrappers collapse and the decorations are simply absent. */
.ns-drawer-id,
.ns-drawer-id-main,
.ns-drawer-links,
.ns-drawer-foot {
    display: contents;
}

.ns-drawer-avatar,
.ns-drawer-code,
.ns-drawer-link {
    display: none;
}

/* Phones only: the ☰ ends up underneath the open panel, so the panel carries its own close. */
.ns-drawer-close {
    display: none;
    width: 34px;
    height: 34px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ln);
    border-radius: 50%;
    background: var(--sf);
    color: var(--tx2);
    font-size: 1rem;
    line-height: 1;
    font-family: inherit;
    cursor: pointer;
    padding: 0;
    flex: 0 0 auto;
}

.ns-drawer-dim {
    display: none;
}

/* ---- Buying-context chip + popover: country · currency · top-up shop (+ languages). ---- */
.ns-ctx-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 5px 13px;
    background: var(--sf);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

.ns-ctx-chip b {
    font-weight: 800;
    color: var(--tx);
}

.ns-ctx-chip .chev {
    font-size: 0.7em;
    color: var(--mut);
}

.ns-ctx-chip:hover {
    border-color: var(--mut);
}

/* THE TWO PANEL BUTTONS ARE ONE SEGMENTED CONTROL, not two chips (owner, 2026-09-07: "чтобы объединить
   их в группу"). Three separate pills read as three unrelated controls; a group says these two are one
   choice — and here that is literally true, since one panel at a time means at most one segment is ever
   on. The `⋯` stays OUTSIDE it: it opens a menu rather than a panel, and inside the group it would read
   as a third exclusive option.

   SAME VOCABULARY AS `FilterBar`'s tiles/list segment, value for value — restated here only because
   that stylesheet is SCOPED and its class names cannot reach this markup. Change one and change the
   other; they are meant to be the same object in two places.

   Written as its own vocabulary first, and that is what the grouping fixes at the source: two loose
   pills diverged from the chip beside them in four ways at once — hover, resting fill, border tone,
   padding and size — so a cluster of three controls had two behaviours and no border lift on two of
   them ("бордюрчик не подсвечивается"). Now the group owns the border and lifts it like the chip does,
   and each segment owns its own fill. */
.ns-hgroup {
    display: inline-flex;
    gap: 2px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
    padding: 3px;
    flex: 0 0 auto;
}

.ns-hgroup:hover {
    border-color: var(--mut);
}

.ns-hbtn {
    display: inline-flex;
    /* ONE BASELINE, not centred boxes. A segment holds four type sizes — the label at 0.62rem, the code
       chip, the count at 0.78rem and the shop's name at 0.8rem — and `center` centres each item's BOX,
       whose height follows its font. Measured, the baselines then landed 16.16 / 17.23 / 18.00 from the
       segment's top: the shop's name sat 1.84px below the label next to it (owner, 2026-09-08: "название
       магазина Seagm сместилось ниже"). Mixed sizes on one line share a baseline or they share nothing. */
    align-items: baseline;
    gap: 5px;
    border: none;
    background: transparent;
    color: var(--tx2);
    border-radius: 999px;
    /* 5px, SYMMETRIC, and it stopped needing to be anything else. It was `5px 11px 4px` for an hour:
       the code chip's own box sat 1.01px high inside its border, and under baseline alignment that chip
       was the deepest thing below the line, so the segment inherited the error and a bottom pixel was
       shaved off to hide it. Giving the chip the family's cap-box rule fixed the cause and the
       compensation dissolved — symmetric padding now lands the cap box on centre with the shared
       baseline on a whole pixel (18), which is what stops it jittering between rows. If this ever needs
       an odd pixel again, something below the baseline has drifted; fix that instead. */
    padding: 5px 11px;
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

/* The per-segment half of the feedback the group's border gives for the pair. */
.ns-hbtn:hover {
    background: var(--sf2);
    color: var(--tx);
}

/* "This panel is open." The accent FIELD, the same one the view segment marks its choice with — and the
   whole reason these are two controls rather than one chip, which could say nothing about either.

   `:active` IS IN THE SAME LIST ON PURPOSE, so the press and its result are the same colour: press it,
   it lights, the panel arrives and it is still lit. On a phone that is the only thing that answers
   "which one did I hit" — there is no hover, and the sheet slides over the very button that was tapped
   (owner, 2026-09-09: «не понятно, на что ты нажал… открытая шторка перекрывает эту кнопку»). Folding
   it into the lit group rather than giving it a colour of its own also keeps ONE definition of "this
   segment is lit": five rules dress a segment (field, `b`, label, code chip, count) and a second
   palette would have to be kept level with all five.
   It sits after `:hover` in source, which is what lets it win a press on a desktop — same specificity,
   so order decides. On iOS `:active` can need a touch listener in the ancestor chain, and Blazor's
   clicks are delegated on the document; the browser's own tap highlight is deliberately left in place
   as the fallback rather than zeroed out. */
.ns-hbtn.on,
.ns-hbtn.on:hover,
.ns-hbtn:active {
    background: var(--acc);
    color: var(--acc-ink);
}

/* …and pressing the one that is ALREADY open — the tap that closes it — has to answer too, which the
   rule above cannot do: it is already this colour. A deeper field, so the press reads on both states. */
.ns-hbtn.on:active {
    background: color-mix(in srgb, var(--acc) 76%, #000);
}

.ns-hbtn.on b,
.ns-hbtn:active b {
    color: var(--acc-ink);
}

/* A segment says WHAT it is and then answers, and the answer is in the site's own words rather than in
   the header's private notation. "23/32" and "2+2" were the shorthand of a chip that had to fit at
   390px — two internal numbers each, readable only by whoever wrote them. Now: a fixed label, the
   region's own CODE in the chip every price row already wears, and the shop's NAME.

   The label is a NOUN THAT NEVER INFLECTS — the number stays a separate item beside it — because a
   counted noun would need plural forms in six dictionaries, which this project has already refused
   once. Both labels already exist as keys, so this costs no new strings. */
.ns-hb-lbl {
    font-family: var(--fn);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--mut);
}

.ns-hbtn:hover .ns-hb-lbl { color: var(--tx2); }

/* The landmark `game.pickToCompare` points at, in six languages. It survives the phone because it is
   not inside the collapsible label — see the markup. Sized to the label rather than to the value: it
   marks the control, it is not the answer. */
.ns-hb-pin {
    font-size: 0.72rem;
    line-height: 1;
}
.ns-hbtn.on .ns-hb-lbl,
.ns-hbtn:active .ns-hb-lbl { color: rgba(255, 255, 255, 0.78); }

/* The region code, in `GameCard`'s `.rc` vocabulary — value for value, restated here only because that
   stylesheet is SCOPED and its name cannot reach this markup. Change one and change the other: the
   point of using it is that the code in the header and the code on a price row are the same object. */
.ns-hb-rc {
    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;
}

/* It is an uppercase plaque like every other one, so it centres its CAP BOX and lands its baseline on a
   whole pixel — the family rule stated above `.via-tag` in `GameDetail.razor.css`. Measured 1.01px high
   inside its own border before this; `N = 12` keeps the height it had to within 0.02px. It sits in a
   baseline-aligned row, so moving its own baseline moves the CHIP against the label rather than the
   label against the chip, which is what we want. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(12px - 1cap)) {
    .ns-hb-rc {
        text-box: trim-both cap alphabetic;
        padding-block: calc(12px - 1cap);
    }
}

/* On the lit segment the chip's own field would sit on the accent as a grey patch. */
.ns-hbtn.on .ns-hb-rc,
.ns-hbtn:active .ns-hb-rc {
    background: rgba(255, 255, 255, 0.22);
    border-color: transparent;
    color: var(--acc-ink);
}

/* The count. Tabular, so a header does not twitch as regions are ticked. */
.ns-hb-n {
    font-family: var(--fn);
    font-weight: 800;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    color: var(--mut);
}

.ns-hbtn:hover .ns-hb-n { color: var(--tx2); }
.ns-hbtn.on .ns-hb-n,
.ns-hbtn:active .ns-hb-n { color: rgba(255, 255, 255, 0.86); }

/* THE SAME SIZE AS THE COUNT, and that is what keeps the two segments on ONE baseline. Under baseline
   alignment a flex line sits by its TALLEST item, so a shop's name one step larger than everything in
   the other segment put the two labels 1px apart — each segment was internally right and they disagreed
   with each other. Equal here, the tallest text is the same in both and so is the baseline. */
.ns-hbtn b {
    font-size: 0.78rem;
    font-weight: 800;
    color: var(--tx);
}

/* It shows the interface language it sets, like the two controls beside it show what they set. The
   letter-spacing that used to widen a bare `⋯` goes with the glyph: this is a word now, not a mark. */
.ns-ctx-more {
    font-family: var(--fn);
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.03em;
}

/* The display currency, stated beside the country it is derived from. Quiet: it is a consequence of
   the select next to it, never a control, and a reader who wants to change it changes the country. */
.ns-rgn-cur {
    font-family: var(--fn);
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.03em;
    color: var(--mut);
    margin-left: 0.4rem;
}

.ns-ctx-dim {
    position: fixed;
    inset: 0;
    z-index: 190;
    background: transparent;
    border: none;
    padding: 0;
    cursor: default;
}

.ns-ctx-panel {
    display: none;
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    z-index: 200;
    min-width: 300px;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 16px;
    /* Asymmetric on purpose. The top is absorbed by .ns-ctx-head's own box, while the bottom stacked
       the last row's 7px on top of the panel's own — leaving ~20px of unexplained space under the
       final select against ~13px above the heading, which read as a broken panel. */
    padding: 0.8rem 1rem 0.45rem;
    box-shadow: var(--frame-sh);
}

.ns-ctx-panel.open {
    display: grid;
    gap: 0;
}

.ns-ctx-head {
    font-family: var(--fn);
    font-size: 0.64rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--mut);
    padding-bottom: 6px;
}

.ns-ctx-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 0;
    border-top: 1px solid var(--ln);
    font-size: 0.85rem;
}

.ns-ctx-row > span {
    color: var(--mut);
    font-weight: 700;
    white-space: nowrap;
}

.ns-ctx-row select {
    max-width: 170px;
    background-color: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 9px;
    padding: 4px 26px 4px 9px;
    font-family: inherit;
    font-size: 0.84rem;
    font-weight: 700;
    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 8px center;
}

.ns-ctx-row select:focus {
    outline: none;
    border-color: var(--acc);
}

/* ---- The panel row that opens the regions sheet. Looks like the <label> rows around it (same box,
   same border-top) but is a button, so the whole strip stays one visual list. ---- */
.ns-ctx-rowbtn {
    width: 100%;
    background: none;
    border-left: none;
    border-right: none;
    border-bottom: none;
    font-family: inherit;
    cursor: pointer;
    text-align: left;
}

.ns-ctx-rowval {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--tx);
    font-size: 0.84rem;
    font-weight: 700;
    white-space: nowrap;
}

.ns-ctx-rowdim {
    color: var(--mut);
    font-weight: 400;
}

/* "You changed this from the site default" — the same signal the filter sheet uses for a deviation. */
.ns-ctx-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--acc);
}

/* ---- Regions-to-compare sheet. A cheap region is useless if you can't hold an account there, so this
   is where a viewer takes one out of the comparison (or puts a restricted one back in). Centred dialog
   on desktop, bottom sheet on phones — the list is up to 31 rows, which is why it isn't a drop-down. ---- */
/* Above the cookie notice (z-index 1000), which is a fixed BOTTOM bar and would otherwise sit on top
   of the bottom sheet's last rows. The filter sheet already resolves the same collision the same way. */
/* The home region leading the regions row's value. Same weight as the count beside it — they are one
   value now ("DE · 23/32"), not a label and a number. */
.ns-ctx-rowhome {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

/* "23/32" is one value, so it is one flex item — see the markup note. */
.ns-ctx-rowcount {
    font-variant-numeric: tabular-nums;
}

/* NOT DRAWN ON A DESKTOP. The two panels below are modal only on a phone — see .ns-rgn-sheet. A dim
   over a panel the reader is meant to watch the feed through would say the opposite of what the panel
   is for, and `display: none` takes it out of hit-testing too, so a click on the feed reaches the feed. */
.ns-rgn-dim {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1090;
    background: rgba(0, 0, 0, 0.45);
    border: none;
    padding: 0;
    cursor: default;
}

/* ---- The shops and regions panels share ONE frame: a full-height SLIDE-OVER on the side the control
   that opens them is on, which since 2026-09-07 is the RIGHT (owner; §10.2 bought the slide-over
   itself, TASK_SHOP_ACCOUNTS.md).

   THE NON-MODAL DESKTOP IS THE POINT, NOT DECORATION. Centred with a dim, a reader could not see what
   ticking a shop did until they closed the dialog — so the list read as a settings screen, and the one
   thing it needed to be was a FILTER. Beside the feed, with the feed still rendering next to it, the
   choice and its effect are on one screen.

   THE SIDE IS THE TRIGGER'S, AND IT WAS LEFT BY DEFAULT RATHER THAN BY ARGUMENT. Measured on dev at
   1366x860: the buying-context chip spans x 906..1092 and its popover 792..1092 — anchored to the
   chip's own right edge, correctly — while this panel opened at 0..340. Pressing "Stores" at x 999 put
   the answer 829px away, at the opposite edge of the screen. Nothing the slide-over was adopted FOR
   names a side: non-modal, the feed visible beside it and the full height all hold either way, which is
   why this moved and the rest of the frame did not.

   AND IT DOCKS TO THE PAGE COLUMN, NOT TO THE WINDOW, WHICH IS THE OTHER HALF OF THE SAME MISTAKE.
   `.container` is capped at `--page-w` and centred, so the trigger lives inside a 1200px column while
   the window can be far wider — pin the panel to the window and HALF THE DEAD MARGIN sits between the
   two, growing without bound: measured 197px of travel at 1366, 474 at 1920, 794 at 2560 and 1234 at
   3440, where the panel stands in empty page and covers nothing at all (owner, on a wide monitor:
   "далековато"). Docked to the column it is a CONSTANT 114px at every width, and it goes back to
   covering the last column of tiles, which is what a slide-over is for. Below `--page-w` the `max()`
   collapses and nothing changes.

   On a phone it is a full-height drawer WITH the dim, because nothing else fits: a 320px panel over a
   360px screen leaves no feed to watch, so there it really is modal and is treated as one (the page
   behind it is frozen too — allregions.lockScrollPanel, which restates this breakpoint). The right edge
   suits the phone too: the chip sits in the top-right corner and the drawer arrives under the thumb.

   Full height rather than the old min(88vh, 760px): the list is 32 rows and never fits, so every pixel
   spent on the frame is a row the reader cannot see, and a side panel has the whole viewport to use. ---- */
.ns-rgn-sheet {
    position: fixed;
    z-index: 1100;
    /* BESIDE the page column, never over it, wherever the dead margin can hold the panel — see the
       note above. `100%` and not `100vw`: on a fixed element the percentage resolves against the
       viewport WITHOUT the scrollbar, which is the same frame `.container` centres in, while `100vw`
       includes it and would leave the panel half a scrollbar off the content it belongs to.
       The `max()` is the whole adaptation and it is CONTINUOUS, not a breakpoint: from ~1880px up the
       offset is positive and the panel's left edge sits flush on the column's right edge, covering
       nothing; below that it clamps to 0, the panel stops at the window and the widening column slides
       under it — so the overlap grows from zero rather than appearing all at once. */
    right: max(0px, calc((100% - var(--page-w)) / 2 - var(--panel-w)));
    top: 0;
    bottom: 0;
    width: min(var(--panel-w), calc(100vw - 44px));
    display: flex;
    flex-direction: column;
    background: var(--sf);
    border-left: 1px solid var(--ln);
    /* cast INTO the page, away from the edge the panel hugs — the sign is half the rule */
    box-shadow: -10px 0 34px -22px rgba(0, 0, 0, 0.5);
    padding: 0.9rem 1rem 0.6rem;
    padding-bottom: max(0.6rem, env(safe-area-inset-bottom));
    /* the inset that matters is the one on the edge this panel is against */
    padding-right: max(1rem, env(safe-area-inset-right));
}

.ns-rgn-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 1rem;
}

.ns-rgn-close {
    background: none;
    border: none;
    color: var(--mut);
    font-size: 1rem;
    line-height: 1;
    padding: 4px;
    cursor: pointer;
    font-family: inherit;
}

/* `.ns-rgn-headbtns` STOOD HERE AND IS DELETED, 2026-09-09. It held the ? and the ✕ side by side with a
   2px gap, which measured out as two 22px targets whose centres were **23.54px** apart — inside one
   fingertip, on a pair where hitting the wrong one throws away whatever the reader opened the sheet to
   do. The ? moved to the heading it explains and the ✕ kept the far edge, so there is no pair left to
   wrap. Do not reinstate a group here without a reason the two belong together. */

/* BOTH HEAD CONTROLS ARE HIT-EXPANDED WITHOUT GROWING, and the pseudo-element is why: the ? IS a 22px
   circle and the head IS 24px tall — in short landscape that head is sticky and every pixel of it is
   taken from the list. An overlay pinned 8px outside the border box gives each a ~38px target while the
   layout still sees 22px, so nothing above or below moves. `position: relative` is what the inset
   resolves against; it is on the elements, not inherited. */
.ns-rgn-help,
.ns-rgn-close {
    position: relative;
}

.ns-rgn-help::after,
.ns-rgn-close::after {
    content: "";
    position: absolute;
    inset: -8px;
}

/* Unfolds the sheet's prose. A round outline so it reads as one of the sheet's own controls rather
   than a second close button beside the ✕, and lit when open, like every other state in here. */
.ns-rgn-help {
    /* Pushes the ✕ to the far edge while this stays beside the heading — which is what removes the pair
       without needing a wrapper around the title. The head's own `justify-content: space-between` then
       has nothing left to distribute. */
    margin-right: auto;
    width: 22px;
    height: 22px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: none;
    color: var(--mut);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1;
    padding: 0;
    cursor: pointer;
}

.ns-rgn-help.on {
    border-color: var(--acc);
    color: var(--acc);
}

.ns-rgn-note {
    color: var(--mut);
    font-size: 0.8rem;
    margin: 4px 0 10px;
}

/* Wraps, because there are three of these now and the third carries whatever the reader's own
   language calls a hand-made selection — at 320px the row is not always wide enough for all three. */
/* The reader's own country, first thing in the sheet. Laid out like the panel row it replaced —
   label, then the control taking the rest of the line — so the two read as the same control that
   moved rather than a new one. */
.ns-rgn-home {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0 2px;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--tx2);
    cursor: pointer;
}

.ns-rgn-home select {
    flex: 1 1 auto;
    min-width: 0;
    border: 1px solid var(--ln);
    border-radius: 8px;
    background: var(--sf2);
    color: var(--tx);
    font-family: inherit;
    font-size: 0.86rem;
    font-weight: 700;
    padding: 5px 8px;
    cursor: pointer;
}

.ns-rgn-home select:focus-visible {
    outline: 2px solid var(--acc);
    outline-offset: 1px;
}

.ns-rgn-presets {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 0;
}

.ns-rgn-preset {
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 5px 12px;
    background: var(--bg);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    cursor: pointer;
}

.ns-rgn-preset.on {
    border-color: var(--acc);
    color: var(--tx);
}

/* The only scrolling part, so the heading and presets stay put while 31 rows move under them.
   `flex: 1 1 auto` + `min-height: 0` is what makes a FULL-HEIGHT panel spend its height on the list
   rather than on its own furniture: without it the list sizes to content and then shrinks under the
   blocks below it — measured at 1366×768 in the shops panel, the list got 130px, i.e. one and a half
   rows, with the aggregate group holding 280px underneath. `min-height: 0` is the load-bearing half; a
   flex item's default `min-height: auto` refuses to shrink below its content and the scroll never
   engages. */
.ns-rgn-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    margin: 0 -0.35rem;
    padding: 0 0.35rem;
}

.ns-rgn-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 2px;
    border-top: 1px solid var(--ln);
    font-size: 0.86rem;
    cursor: pointer;
}

.ns-rgn-item input {
    flex: 0 0 auto;
    accent-color: var(--acc);
    cursor: pointer;
}

.ns-rgn-item input:disabled {
    cursor: default;
}

/* An excluded row stays legible — it is a state, not a disabled control: the viewer has to be able to
   read which region they switched off and put it back. */
.ns-rgn-item.off .ns-rgn-name,
.ns-rgn-item.off .ns-rgn-code {
    color: var(--mut);
}

.ns-rgn-item.blocked {
    cursor: default;
}

.ns-rgn-code {
    flex: 0 0 auto;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--tx);
    min-width: 2.1em;
}

.ns-rgn-name {
    flex: 1 1 auto;
    color: var(--tx2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ns-rgn-tag {
    flex: 0 0 auto;
    border-radius: 999px;
    padding: 2px 8px;
    font-size: 0.7rem;
    font-weight: 700;
    white-space: nowrap;
}

.ns-rgn-tag.home {
    background: color-mix(in srgb, var(--region) 18%, transparent);
    color: var(--region);
}

.ns-rgn-tag.blocked {
    background: color-mix(in srgb, var(--mut) 15%, transparent);
    color: var(--mut);
}

/* WHY a region is off by default — a caption on its OWN line, not a pill in the row. Found by
   rendering the first region that ever carried one: the sentence is a whole clause ("an eShop account
   here needs local verification"), it will not shrink, and beside it the country name collapsed to
   "S…" — the row named a barrier and stopped naming the region. Indented to the name's column so the
   two read as one row, and it is the only caption here that may wrap. */
.ns-rgn-tag.why {
    flex-basis: 100%;
    order: 9;
    padding: 0 0 0 2.9em;
    background: none;
    color: var(--mut);
    font-weight: 500;
    font-size: 0.74rem;
    white-space: normal;
    line-height: 1.3;
}

.ns-rgn-tag.mine {
    background: color-mix(in srgb, var(--acc) 16%, transparent);
    color: var(--acc);
}

/* ---- How much this reader wants to hear about regions they do NOT hold. Above the list, because it
   decides what the list is FOR. It replaced two EUR sliders (WorthBars): asking someone to price their
   own tolerance for hassle in euros, from a page showing one game, is the wrong question in the wrong
   unit at the wrong moment — the cumulative version of it lives on /accounts. Nothing here moves a
   price; it decides whether a row NAMES the cheaper region it already knows about. ---- */
.frv {
    border: 0;
    border-top: 1px solid var(--ln);
    border-bottom: 1px solid var(--ln);
    padding: 10px 2px 8px;
    margin: 0 0 2px;
}

.frv-legend {
    padding: 0;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--tx2);
}

.frv-opts {
    display: flex;
    gap: 6px;
    margin-top: 7px;
}

.frv-opt {
    flex: 1 1 0;
    min-width: 0;
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 5px 6px;
    background: var(--bg);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.74rem;
    font-weight: 700;
    line-height: 1.2;
    cursor: pointer;
}

.frv-opt.on {
    border-color: color-mix(in srgb, var(--acc) 45%, transparent);
    background: color-mix(in srgb, var(--acc) 12%, transparent);
    color: var(--acc);
}

/* The gate's own number, as a control. Only rendered under "notable", which is the only value that
   reads it. Laid out as label / field / reset on one line, wrapping on a phone. */
.frv-thr {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 8px;
    margin-top: 8px;
    font-size: 0.78rem;
    color: var(--tx2);
}

.frv-num {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
}

.frv-thr input {
    width: 68px;
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 4px 6px;
    background: var(--sf);
    color: var(--tx);
    font-family: var(--fn);
    font-size: 0.84rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.frv-thr input:focus-visible {
    outline: 2px solid var(--acc);
    outline-offset: 1px;
}

.frv-unit {
    color: var(--mut);
    font-size: 0.74rem;
}

.frv-reset {
    margin-left: auto;
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 3px 10px;
    background: var(--bg);
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.72rem;
    font-weight: 700;
    cursor: pointer;
}

/* The gate's own number, stated where it applies. A control whose effect is a hidden constant is
   precisely what this replaced, so the hint names the figure rather than alluding to it. */
.frv-hint {
    color: var(--mut);
    font-size: 0.74rem;
    line-height: 1.35;
    margin: 7px 0 0;
}

/* "Is a new account worth it?" — the cumulative answer, which is the only place that question can
   honestly be answered. A link and not a button: it is a page, and it should be linkable. */
.ns-rgn-worthlink {
    display: inline-block;
    margin: 8px 0 2px;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--acc);
    text-decoration: none;
}

.ns-rgn-worthlink:hover {
    text-decoration: underline;
}

/* "I have an account here". A tag that can be pressed, not a second checkbox: it is the same kind of
   fact as the ones beside it, and two checkboxes on one 390px row leaves no room for the name. */
.ns-rgn-own {
    flex: 0 0 auto;
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 2px 8px;
    background: transparent;
    color: var(--mut);
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
}

.ns-rgn-own.on {
    border-color: transparent;
    background: color-mix(in srgb, var(--region) 18%, transparent);
    color: var(--region);
}

/* Two things at once, split between the two properties.

   The RING is the same in both states and means only "the pointer is on the control": the tag sits
   inside the row's <label>, so everything under the cursor is already clickable and nothing marked out
   the one item on that line whose click is caught (@onclick:stopPropagation) rather than passed to the
   label that toggles the comparison.

   The FILL keeps meaning what it means at rest — how marked this region is. An unmarked tag takes a
   trace of the region colour, previewing what pressing it would do; a marked one only deepens. It must
   never lighten on hover: the press that follows takes the mark AWAY, but a tag that looks LESS marked
   under the cursor reads as a rendering glitch rather than an offer. */
.ns-rgn-own:hover {
    border-color: color-mix(in srgb, var(--region) 55%, transparent);
    background: color-mix(in srgb, var(--region) 10%, transparent);
    color: var(--region);
}

.ns-rgn-own.on:hover {
    background: color-mix(in srgb, var(--region) 32%, transparent);
}

/* ---- WHICH SHOPS MAY PRICE A ROW. The one control in this panel that moves a figure rather than
   ink, so it leads the panel and it REFETCHES (ShopPriceModes; it is in PriceLensSignature, unlike the
   two reading controls it replaced — ShopsHear and PriceMode, both deleted with it). Framed as a block
   rather than dropped in as two more pills, because the pills below it choose a LIST of shops and this
   chooses what the site is allowed to do with that list. ---- */
.ns-mode {
    margin-top: 10px;
    border: 1px solid var(--ln);
    border-radius: 12px;
    background: var(--sf2);
    padding: 8px;
}

.ns-mode-lbl {
    display: block;
    margin-bottom: 6px;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--mut);
}

.ns-mode-row {
    display: flex;
    gap: 4px;
    padding: 3px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf);
}

.ns-mode-row button {
    flex: 1 1 0;
    min-width: 0;
    border: none;
    border-radius: 999px;
    padding: 5px 6px;
    background: none;
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    cursor: pointer;
}

.ns-mode-row button.on {
    background: var(--acc);
    color: var(--acc-ink);
}

/* The note says what the mode DOES to the prices, which is the half a two-word button cannot carry —
   "from all shops" is only honest beside "these need a free sign-up". Always shown, on both surfaces:
   unlike the sheet's other hints this one is not an explanation of a control, it is a condition on
   every price the reader is about to read. */
.ns-mode-note {
    margin: 7px 2px 0;
    font-size: 0.72rem;
    line-height: 1.4;
    color: var(--mut);
}

/* ---- The panel's own two tabs. Deliberately NOT .ns-tabs, which is the header's nav strip — one class
   for two unrelated objects is how a rule ends up matching something nobody was looking at. ---- */
.ns-shop-tabs {
    display: flex;
    gap: 14px;
    margin-top: 10px;
    padding: 0 2px;
    border-bottom: 1px solid var(--ln);
}

.ns-shop-tabs button {
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--mut);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    padding: 0 0 8px;
    cursor: pointer;
}

.ns-shop-tabs button.on {
    color: var(--tx);
    border-bottom-color: var(--acc);
}

/* The count is where a row MOVING between the tabs is legible: the list itself deliberately does not
   re-cut under a press, so this is the feedback that the claim was recorded. */
.ns-shop-tabcnt {
    font-weight: 600;
    color: var(--mut);
}

.ns-shop-tabempty {
    margin: 12px 2px;
    font-size: 0.8rem;
    color: var(--mut);
}

/* ---- "My stores" sheet. Reuses the regions sheet's frame (.ns-rgn-*) on purpose: it is the same kind
   of object — a list of things with reasons attached, opened from the same panel — and a second frame
   would be two dialogs to keep in sync. What differs is the ROW: a shop is chosen on facts (coverage,
   which currencies it wins, how fresh its rates are), so the row is two lines rather than one. ---- */
/* A GRID AT EVERY WIDTH, because the panel is ~340px at every width now. The rule used to apply below
   560px only, back when this was a 460px centred dialog: as a wrapping FLEX row the account chip takes
   ~66px out of the width the facts line wraps in, and at the panel's width that pushed the chip onto a
   line of its own — measured on the shops panel the day it landed, one row 3 lines tall and the next
   one clipped. As a grid the chip sits beside the NAME, which is one short word, and the facts get the
   whole row back.
   `display: contents` on the wrapper, so its two children become grid items with no markup change; the
   leading column collapses to zero where there is no control to draw (one account, hence no star to
   break a tie with). */
.ns-shop-item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
        "box name own"
        "box facts facts";
    align-items: center;
    column-gap: 8px;
    row-gap: 3px;
    padding: 10px 2px;
}

.ns-shop-item > input[type="checkbox"],
.ns-shop-item > .ns-shop-star {
    grid-area: box;
}

.ns-shop-name {
    grid-area: name;
}

.ns-shop-facts {
    grid-area: facts;
}

.ns-shop-own {
    grid-area: own;
}

.ns-shop-main {
    display: contents;
}

.ns-shop-name {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-weight: 700;
    color: var(--tx);
}

.ns-shop-item.off .ns-shop-name {
    color: var(--mut);
}

/* The facts line. Wraps rather than truncates: a shop's coverage and what it is cheapest for are the
   whole basis of the choice, so losing the tail to an ellipsis would defeat the row. */
.ns-shop-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    font-size: 0.75rem;
    color: var(--mut);
    line-height: 1.35;
}

.ns-shop-fact.best {
    color: var(--acc);
}

/* "I have an account here" — the SAME chip the regions sheet uses (.ns-rgn-own), because it is the same
   claim about a different thing, and a second vocabulary for it would be a second thing to keep in step.
   It is placed by the row's grid (beside the NAME, not on the row's right edge) — see .ns-shop-item. */

/* A held shop's name carries the mark, so its facts line does not need to shout as well. */
.ns-shop-item.held .ns-shop-name {
    color: var(--tx);
}

/* "Prefer this shop" — a star, not a drag handle: the order only decides a near-tie, so one tap
   expresses all of it. Sized to the 32px touch target the rest of the header uses.
   It LEADS its row, in the slot the watch tick occupies in the other tab: there is exactly one control
   per row per tab, and it is always the first thing on the line. */
.ns-shop-star {
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.95rem;
    line-height: 1;
    padding: 4px 2px;
    min-width: 22px;
    text-align: left;
    color: var(--mut);
}

.ns-shop-star.on {
    color: var(--acc);
}

/* "Which card price to assume" — the foot of the shops sheet. A radio group rather than the preset
   pills above it: the presets are shortcuts to one list of shops, while these three are different
   ASSUMPTIONS, and each needs a line of its own to say what it assumes. */
.ns-agg {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--ln);
}

.ns-agg-head {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--tx2);
    margin-bottom: 6px;
}

.ns-agg-item {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 6px 4px;
    border-radius: 6px;
    cursor: pointer;
}

.ns-agg-item:hover {
    background: var(--sf2);
}

.ns-agg-item input {
    margin-top: 2px;
    flex: none;
}

.ns-agg-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.ns-agg-lbl {
    font-size: 0.82rem;
    color: var(--tx);
}

.ns-agg-item.on .ns-agg-lbl {
    font-weight: 600;
}

/* The hint carries the whole decision, so it is small but not faint — the label alone is only a name. */
.ns-agg-hint {
    font-size: 0.71rem;
    line-height: 1.35;
    color: var(--mut);
}

.ns-agg-reset {
    margin-top: 4px;
    padding: 2px 0;
    background: none;
    border: none;
    font-size: 0.71rem;
    color: var(--mut);
    text-decoration: underline;
    cursor: pointer;
}

.ns-shop-summary {
    margin: 8px 0 2px;
    padding-top: 8px;
    border-top: 1px solid var(--ln);
    font-size: 0.78rem;
    color: var(--mut);
}

/* What one free sign-up would ADD. Deliberately not the warning colour beside it: this is an offer, not
   a gap, and the two lines sit together — one saying what is reachable for nothing, the other what is
   reachable for nobody. */
.ns-shop-more {
    display: block;
    margin-top: 3px;
    color: var(--acc);
}

/* A gap is not decoration: comparing a region none of your shops can fund means looking at a price you
   cannot pay, so it gets the warning colour the rest of the site uses for "this needs attention". */
.ns-shop-gap {
    display: block;
    margin-top: 3px;
    color: var(--warn);
}

/* THE DRAWER BREAKPOINT, and it restates MainLayout.PanelDrawerMaxWidth — the CSS decides whether the
   dim is drawn and the C# decides whether the page behind it is frozen, so the two must move together.
   Below it the panel is genuinely modal: 340px over a 600px viewport leaves no feed worth watching, and
   a filter you cannot watch is the dialog this change was undoing. */
@media (max-width: 760px) {
    .ns-rgn-dim {
        display: block;
    }

    .ns-rgn-sheet {
        --panel-w: 320px;
    }
}

@media (max-width: 560px) {
    /* The reason is the POINT of the row — "off by default" without "you can't verify an account
       there" is just an unexplained missing region — so on narrow screens it drops to its own line
       instead of being hidden. Only the two or three restricted rows grow; the other 29 stay one line. */
    .ns-rgn-item {
        flex-wrap: wrap;
    }

    /* PROSE FOLDS BEHIND THE SHEET'S `?` ON A PHONE, and the list keeps the height. The head paragraph
       already did; the three reading hints and the shop-suggestion one did not, and measured at 360×760
       they cost the LIST 126px — leaving it 74px, ONE row of four, on the one control the reader opened
       this sheet to use. Desktop is untouched: there the hints simply show. */
    .ns-shop-sheet .ns-agg-hint {
        display: none;
    }

    .ns-shop-sheet.help .ns-agg-hint {
        display: block;
    }

    .ns-rgn-tag.why {
        flex-basis: 100%;
        margin-left: 2.1em;
        background: none;
        padding: 0;
        white-space: normal;
        font-weight: 400;
        font-size: 0.72rem;
        line-height: 1.35;
    }

    /* The rule above indents a dropped reason past the regions sheet's 2.1em CODE column. The stores
       sheet has no such column, so there the indent is 2.1em of nothing ADDED to a 100% basis — measured
       at 390px it pushed the row to 319px inside a 295px box. Reset rather than re-scoped upward: the
       borrowed .ns-rgn-tag styling is what makes the two sheets look like one family, and only this one
       mobile geometry rule is specific to the other sheet's layout. */
    .ns-shop-sheet .ns-rgn-tag.why,
    .ns-shop-sheet .ns-rgn-tag.blocked {
        flex-basis: auto;
        margin-left: 0;
    }
}

/* Language / seller / region selectors — slim flat capsules (no thick bottom, no shadow). */
.ns-pill {
    color: var(--tx2);
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 5px 28px 5px 12px;
    font-size: 0.85rem;
    font-weight: 700;
    font-family: var(--fd);
    cursor: pointer;
    field-sizing: content;
    appearance: none;
    -webkit-appearance: none;
    background-color: var(--sf);
    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 11px center;
}

.ns-pill:hover {
    border-color: var(--mut);
}

.ns-utils-sep {
    width: 1px;
    height: 22px;
    background: var(--ln);
    margin: 0 0.15rem;
    flex: 0 0 auto;
}

.ns-region-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.ns-region-icon {
    position: absolute;
    left: 11px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.8rem;
    line-height: 1;
    pointer-events: none;
}

.ns-region {
    padding-left: 30px;
}

/* Theme toggle — a round icon button; the icon itself comes from MainLayout. */
.ns-theme {
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ln);
    border-radius: 50%;
    background: var(--sf);
    color: var(--tx2);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    flex: 0 0 auto;
}

/* The theme toggle keeps its emoji (more legible than a pale stroke icon); wrap it so we can nudge
   it up a hair to sit optically centred in the round button. */
.ns-theme-ico {
    display: block;
    width: 19px;
    height: 19px;
}

/* Desktop keeps the bare round icon button; the label is a phone-panel affordance (see @860). */
.ns-theme-label {
    display: none;
}

.ns-theme:hover {
    border-color: var(--mut);
}

/* Centre the label inside the text buttons. They were inline / inline-block, so the glyph could sit
   a touch high/left; inline-flex + centring makes the label sit dead-centre and consistent. */
.ns-tab,
.ns-logout,
.ns-cta,
.ns-user,
.auth-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.ns-user {
    color: var(--acc);
    font-weight: 800;
    text-decoration: none;
    font-size: 0.9rem;
    padding: 4px 6px;
}

.ns-user:hover {
    opacity: 0.85;
}

.ns-logout {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 5px 12px;
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
    font-family: inherit;
}

.ns-logout:hover {
    border-color: var(--mut);
}

.ns-cta {
    color: var(--acc-ink);
    background: var(--acc);
    padding: 5px 14px;
    border-radius: 999px;
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 800;
}

.ns-cta:hover {
    color: var(--acc-ink);
    opacity: 0.9;
}

.ns-burger {
    display: none;
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx);
    border-radius: 10px;
    padding: 4px 11px;
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
}

/* Tier 2 — primary nav: slim flat capsule chips (the approved «Стало» tab). */
.ns-tabs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.45rem;
    padding: 0.15rem 0 0.7rem;
}

.ns-tab {
    color: var(--tx2);
    background: var(--sf);
    border: 1px solid var(--ln);
    text-decoration: none;
    font-weight: 800;
    font-size: 0.85rem;
    padding: 0.28rem 0.85rem;
    border-radius: 999px;
    white-space: nowrap;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.ns-tab:hover {
    border-color: var(--mut);
    color: var(--tx);
}

/* `.tap-on` is the optimistic highlight the strip paints on the chip you just tapped, in the same
   frame as the scroll — Blazor's real `.active` only arrives with the route change's re-render, and
   waiting for it let the row move under a still-lit old chip. While a tap is pending the strip
   carries `.tapping`, which switches the active look off entirely, so the stale chip falls back to
   the resting look of whatever breakpoint is in play (no duplicated colours here). */
.ns-tabs:not(.tapping) .ns-tab.active,
.ns-tab.tap-on {
    color: var(--acc-ink);
    background: var(--acc);
    border-color: var(--acc-brd);
}

/* A tap swaps the highlight with no cross-fade. The 0.15s background transition is right for hover,
   but on a tap it left the old chip still visibly tinted for ~150ms while the row was already at its
   new position — the same "which one is selected?" beat we're removing. Transitions come back when
   `.tapping` is dropped, and by then the painted state already matches. */
.ns-tabs.tapping .ns-tab {
    transition: none;
}

.ns-tab-admin {
    color: var(--gold);
}

.ns-tab-admin:hover {
    color: var(--gold);
}

.ns-tabs:not(.tapping) .ns-tab-admin.active,
.ns-tab-admin.tap-on {
    background: var(--gold);
    border-color: var(--gold);
    color: #2E2003;
}

.ns-main {
    flex: 1;
    padding: 1.25rem 0;
}

/* Last thing in the flow, so it is what the navigation bar would otherwise sit on top of. */
.ns-footer {
    padding: 1rem 0 calc(1rem + env(safe-area-inset-bottom));
    font-size: 0.85rem;
    color: var(--mut);
    border-top: 1px solid var(--ln);
}

/* Phone/tablet: categories and the buying context stay permanently in the header; only the
   remaining utilities live behind the ☰, as a panel that slides in OVER the page. */
@media (max-width: 860px) {
    .ns-burger {
        display: inline-flex;
        align-items: center;
    }

    /* The buying context re-prices every number on screen, so it belongs in the top row rather than
       two taps deep. Only the region code shows here — currency + shop would push the row past a
       390px screen, and both are visible the moment the panel opens. */
    .ns-hbtn-rest,
    .ns-ctx-rest {
        display: none;
    }

    /* THE SEAM, not the edges. With the labels gone the two segments meet paddings — 11 + the group's
       2 + 11 = a 24px hole between "+22" and the shop's name, which reads as a gap in the control
       rather than as a join (owner, 2026-09-08: "нужно убрать пустое место перед seagm"). 8px keeps the
       pill's shape and closes the seam to 18, and takes 12px off the group's width where every pixel is
       already spoken for. The VERTICAL padding is untouched, so the tap target keeps its height. */
    .ns-hbtn {
        padding-inline: 8px;
    }

    /* The panel is right-aligned to its anchor. On a phone that anchor — the chip — sits mid-row with
       the bell and ☰ to its right, so a 320px panel hung off the chip's right edge runs off the LEFT
       of the screen (measured: left = -59px at a 360px viewport, clipping every label). Take the
       anchoring away from .ns-ctx and let it fall to the sticky header, which is full-width: `right`
       then resolves against the viewport and the panel is on-screen at any width. `top` is inherited,
       so the same 10px gap now hangs below the whole header — the panel stops covering the tab shelf
       it used to open on top of. */
    .ns-ctx {
        position: static;
    }

    /* The clamp is 100% (of the header, the containing block above) and NOT 100vw on purpose: 100vw
       counts a classic desktop scrollbar that the header's box does not, so the two disagreed by the
       scrollbar width and a narrow desktop window put the panel at left:-1 instead of left:16.
       Against the same box `right` resolves against, the 16px gutter comes out symmetric by
       construction. Phones (overlay scrollbars) are unaffected either way. */
    .ns-ctx-panel {
        min-width: 0;
        right: 16px;
        width: min(320px, calc(100% - 32px));
    }

    /* Desktop ordering off: inside the drawer the groups are real boxes again and DOM order is the
       reading order (identity → links → foot; theme above logout inside the foot). Left in place,
       .ns-user's order:4 would sort it BELOW the friend code it labels. */
    .ns-ctx,
    .ns-theme,
    .ns-bell-slot,
    .ns-user,
    .ns-logout,
    .ns-cta {
        order: 0;
    }

    /* ---- The box the drawer is measured and clipped against ------------------------------------
       The drawer has to LIVE inside the header: on desktop .ns-utils is display:contents and its
       children ARE the header's inline row, so the panel cannot be hoisted out of the DOM without
       rebuilding that row. But the header carries a transform (the auto-hide at the bottom of this
       file, plus its will-change), and a transformed ancestor becomes the containing block for its
       position:fixed descendants — so the panel stopped being anchored to the VIEWPORT, and its
       off-screen resting place (translateX(100%), 300px past the right edge) became 300px of PAGE.
       Measured: a 390px phone reported a 690px-wide document, i.e. the whole site could be dragged
       sideways with the menu shut, showing a void where two thirds of the screen used to be.
       This box gives the panel back a viewport-flush frame to be positioned in and clipped by, so
       the parked panel adds nothing to the page while everything the open one legitimately paints —
       its shadow, the two thirds that hang BELOW the header — is inside the box and stays visible.
       Three details are load-bearing:
       - `width: 100%`, never `100vw`: 100vw counts a classic desktop scrollbar that the header's own
         box does not, and this block reaches narrow desktop windows too (same trap as .ns-ctx-panel).
       - Clipping on the HEADER instead was tried and is wrong. Its box ends at the header's bottom
         edge, and Chrome stops HIT-TESTING a clipped area even where it still paints it — verified:
         the drawer looked perfect and a tap on "Sign in" landed on the filter bar behind it.
       - pointer-events:none because this box covers the whole screen while the drawer is SHUT; the
         panel turns them back on for itself. */
    .ns-drawer-clip {
        display: block;
        position: fixed;
        top: 0;
        right: 0;
        width: 100%;
        height: 100dvh;
        overflow: clip;
        pointer-events: none;
        /* Only has to beat the header's own children — the header is a stacking context, so the whole
           panel rises above the page via .ns-header.menu-open's z-index, not this number. */
        z-index: 200;
    }

    /* Drawer = an overlay panel. It no longer expands the sticky header, so the nav shelf never
       shifts and the header keeps a constant height. Kept in the DOM (not display:none) so it can
       animate; visibility keeps it untabbable while closed. Positioned ABSOLUTELY inside the clip
       box above — which is exactly the viewport rect, so top/right/max-height mean what they always
       did. */
    .ns-utils {
        display: flex;
        position: absolute;
        top: 0;
        right: 0;
        pointer-events: auto;
        /* Content-height sheet, NOT floor-to-ceiling. With the categories on their own shelf the menu
           is four rows, and a full-height panel was ~60% empty — the void was the complaint. Capped to
           the screen (and still scrollable) so it can't outgrow it as rows are added. */
        max-height: 100vh;
        max-height: 100dvh;
        width: min(80vw, 300px);
        flex-direction: column;
        align-items: stretch;
        flex-wrap: nowrap;
        gap: 0.55rem;
        padding: calc(0.9rem + env(safe-area-inset-top)) 1rem calc(1rem + env(safe-area-inset-bottom));
        background: var(--sf);
        border-left: 1px solid var(--ln);
        border-bottom: 1px solid var(--ln);
        border-bottom-left-radius: 18px;
        box-shadow: var(--frame-sh);
        overflow-y: auto;
        overscroll-behavior: contain;
        transform: translateX(100%);
        visibility: hidden;
        transition: transform 0.22s ease, visibility 0s linear 0.22s;
    }

    .ns-header.menu-open .ns-utils {
        transform: translateX(0);
        visibility: visible;
        transition: transform 0.22s ease;
    }

    /* The sticky header is a stacking context (z-index:100), so NO z-index on the drawer inside it can
       beat the cookie notice (1000) — the whole context has to rise, and only while the drawer is
       open. Matters because the panel's controls now sit at its foot, exactly where that notice band
       is: on a first visit it swallowed the theme toggle and Logout. 1100 = the filter sheet's tier. */
    .ns-header.menu-open {
        z-index: 1100;
    }

    /* Rendered outside <header> (see MainLayout) so `inset: 0` is the VIEWPORT and not the header's
       own 99px strip. The tier it needs is the one it inherited while it still lived in there: above
       the cookie notice (1000), below the header the drawer rides in (1100 while open). */
    .ns-drawer-dim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1090;
        background: rgba(0, 0, 0, 0.4);
        border: none;
        padding: 0;
        cursor: default;
    }

    .ns-drawer-close {
        display: inline-flex;
        align-self: flex-end;
    }

    /* ---- Primary categories: their own shelf ----
       The strip sits on a surface of its own so navigation stops sharing the page ground with the
       search field and the filter chips, and the chips inside drop their outlines — only the ACTIVE
       one stays a filled capsule. Net effect: one obvious "you are here", and 8 fewer hairlines on
       screen.

       The shelf is the HEADER's own surface rather than a wrapper around the strip: the strip has
       to stay unpainted, because its right-edge mask fades a background as readily as it fades the
       chips, notching the shelf. So the header carries --sf and the brand row paints the page
       ground back over its own band. */
    .ns-header {
        background: var(--sf);
        border-bottom: none;
        box-shadow: var(--shelf-sh);
    }

    .ns-header-top {
        margin: 0 -1rem;
        /* Tighter minimum gap than the desktop 1rem: the row is space-between, so this only bites at
           the width where logo + context + bell + ☰ would otherwise wrap onto a second line. */
        gap: 0.5rem 0.6rem;
        /* Keeps the status-bar inset the base rule adds — a plain `0.6rem 1rem` here would drop it on
           exactly the screens that have a status bar to clear. */
        padding: calc(0.6rem + env(safe-area-inset-top)) 1rem 0.6rem;
        background: var(--bg);
        border-bottom: 1px solid var(--ln);
    }

    /* Full-bleed to the screen edges (the negative margin cancels the .container gutter, the padding
       puts it back on the content) so chips scroll in and out at the screen edge and the last one
       peeks under the fade — the cue that there's more sideways. With margin:0 the scroll window
       stops at the gutter and the next chip is clipped away entirely, cue and all. */
    .ns-tabs {
        flex-wrap: nowrap;
        gap: 0.4rem;
        margin: 0 -1rem;
        padding: 0.45rem 1rem 0.5rem;
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        scroll-padding-inline: 1rem;
        /* Edge fade on BOTH sides: it's the standing "there's more sideways" cue, and the side it
           sits on says which way. JS toggles .at-start / .at-end (both, when the row fits and nothing
           scrolls) and each kills its own fade, so the first/last chip is never left permanently
           dimmed — and unlike the old right-only fade, reaching the end still shows you can go back. */
        --tab-fade-l: 24px;
        --tab-fade-r: 24px;
        -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--tab-fade-l), #000 calc(100% - var(--tab-fade-r)), transparent 100%);
        mask-image: linear-gradient(to right, transparent 0, #000 var(--tab-fade-l), #000 calc(100% - var(--tab-fade-r)), transparent 100%);
    }

    .ns-tabs.at-start {
        --tab-fade-l: 0px;
    }

    .ns-tabs.at-end {
        --tab-fade-r: 0px;
    }

    .ns-tabs::-webkit-scrollbar {
        display: none;
    }

    .ns-tab {
        flex: 0 0 auto;
        background: transparent;
        border-color: transparent;
        color: var(--mut);
    }

    /* Re-assert the gold admin ink: the transparent-chip rule above sits later in the cascade at
       the same specificity as .ns-tab-admin, so without this the admin tab would go muted. */
    .ns-tab-admin {
        color: var(--gold);
    }

    .ns-pill,
    .ns-logout,
    .ns-cta,
    .ns-user {
        width: 100%;
        text-align: center;
    }

    /* In the panel the toggle is a labelled row, not the desktop icon disc. */
    .ns-theme {
        width: 100%;
        height: auto;
        gap: 9px;
        padding: 0.5rem 0.9rem;
        border-radius: 999px;
        font-size: 0.87rem;
        font-weight: 800;
        color: var(--tx);
    }

    .ns-theme-label {
        display: inline;
    }

    /* ---- Drawer = the account menu ----
       Since the categories moved to their own shelf, the panel's only job is the account: who you're
       signed in as, the destinations that don't deserve a category tab, and the session actions. */
    .ns-drawer-id {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        padding: 0.15rem 0.15rem 0.6rem;
    }

    /* The disc itself is drawn by <Avatar>; this wrapper only reserves the slot in the drawer row. */
    .ns-drawer-avatar {
        display: flex;
        flex: 0 0 auto;
    }

    .ns-drawer-id-main {
        display: flex;
        flex-direction: column;
        gap: 1px;
        min-width: 0;
    }

    /* Left-aligned in the identity block: the full-width centring above is for the button rows. */
    .ns-drawer-id .ns-user {
        width: auto;
        text-align: left;
        padding: 0;
        font-size: 0.95rem;
    }

    .ns-drawer-code {
        display: block;
        font-size: 0.74rem;
        color: var(--mut);
        letter-spacing: 0.02em;
    }

    .ns-drawer-links {
        display: flex;
        flex-direction: column;
    }

    .ns-drawer-link {
        display: flex;
        align-items: center;
        padding: 0.62rem 0.15rem;
        border-top: 1px solid var(--ln);
        color: var(--tx2);
        font-size: 0.9rem;
        font-weight: 700;
        text-decoration: none;
    }

    .ns-drawer-links .ns-drawer-link:last-child {
        border-bottom: 1px solid var(--ln);
    }

    .ns-drawer-link.active {
        color: var(--acc);
    }

    /* Session actions, set apart from the destination links above by the padding rather than a rule.
       margin-top:auto only bites if the sheet ever grows tall enough to hit the max-height cap — then
       these stay pinned to its foot instead of drifting up with the list. */
    .ns-drawer-foot {
        display: flex;
        flex-direction: column;
        gap: 0.55rem;
        margin-top: auto;
        padding-top: 1rem;
    }

    /* Secondary next to the "Sign in" capsule — no hairline, no button chrome. */
    .ns-drawer-foot .ns-drawer-link {
        border: none;
        justify-content: center;
        color: var(--mut);
    }

    .ns-utils-sep {
        display: none;
    }
}

/* Below 600 the header is TWO deliberate rows — see the note inside. Its own breakpoint and not the
   860 the labels collapse at: between the two a single row still fits (556 of 860), and forcing a
   second one there would spend a row nobody needed. */
@media (max-width: 600px) {
    /* ── TWO ROWS, DELIBERATELY. The header does not fit on one line on a phone and no amount of
       narrowing a chip will change that: measured at 393px, 369 are usable and the fixed furniture
       takes 273 of them — the brand with its BETA badge is 190.2 ALONE, bell 42.1, burger 41.3 —
       leaving 96 for regions (96.1), shops (82.5) and the language chip (60) together. Swapping the
       shop's NAME for an icon buys 24px against a shortfall of 143 (owner asked; measured, and the
       answer was no). So the row was already wrapping — badly, three ragged levels with the burger
       alone on the last one (owner, 2026-09-08). It costs the same height arranged, and unlike every
       narrowing trick it cannot break on a long shop name: `Digiseller +2` lands where `Seagm +1` does.

       Row 1 is the site's furniture — brand, bell, burger. Row 2 is the buying context. `display:
       contents` on the cluster is what allows it: the four controls become direct flex children of the
       header, so ORDER can put two of them beside the brand and give the third a row of its own, with
       no markup change and no second cluster. `.ns-drawer-clip` inside it is `position: fixed`, so it
       never joined the flex flow and is unaffected.
       600px, not the 860 the labels collapse at: between them the one-row header still fits (556 of
       860), and forcing two rows there would spend a row nobody needed. ── */
    .ns-hcluster {
        display: contents;
    }

    .ns-brand {
        margin-right: auto;
    }

    .ns-bell-slot { order: 1; }
    .ns-burger { order: 2; }

    .ns-ctx {
        order: 3;
        flex-basis: 100%;
    }
}

/* ── …AND THEN IT FIT ON ONE, once three things stopped paying rent (owner, 2026-09-09: "две строки
   это решительно много"). The note above is kept because its arithmetic is still the reason this
   block has to exist: at 390px there are 358 usable and the furniture wanted 406.

   What was cut, measured at 390px: the BETA badge (41.2 + 5.6 of gap), the language chip (68.7 + 9.6
   — the two pickers moved into the ☰ drawer, which already carries the theme switch), and the 📍
   (15.8 + 4). That is 145 against a shortfall of 48, and the surplus buys back the shop's NAME, which
   the earlier pass had given up on.

   THE WORDMARK IS THE ITEM THAT GIVES, and it gives by FADING rather than by being covered. The owner
   asked for the context chip to ride over the logo with a fade; a mask on the wordmark itself is the
   same picture and strictly safer — nothing overlaps, so there is no z-index, no letters showing
   through a translucent edge, and no tap near the seam landing on whichever of the two happens to be
   on top. It is also theme-proof: `mask-image` reveals whatever is actually behind the glyphs, where a
   gradient overlay would have to be painted in the header's own background colour and would break the
   moment the theme changed.

   Measured after: all four items on one optical line (centres within 0.01px), header top row
   94.23 → 57.03, no horizontal overflow, and the wordmark at 82% of itself. It degrades with the
   viewport rather than wrapping — 69% at 375, 57% at 360 — which is why the shop's name becomes an
   icon below 360 (see the next block) instead of the logo thinning to "AL…". ── */
@media (max-width: 600px) {
    .ns-header-top {
        /* Load-bearing: a wrapping flex line never SHRINKS its items, it wraps them — so without this
           the brand keeps its full width and the row breaks in two again, whatever else is trimmed. */
        flex-wrap: nowrap;
    }

    .ns-ctx {
        order: 0;
        flex-basis: auto;
    }

    /* The context, the bell and the ☰ are the fixed furniture: they hold real values and tap targets,
       so the deficit is not theirs to absorb. */
    .ns-ctx,
    .ns-bell-slot,
    .ns-burger {
        flex-shrink: 0;
    }

    .ns-brand {
        flex: 0 1 auto;
        min-width: 0;
        overflow: hidden;
    }

    .ns-logo {
        display: block;
        white-space: nowrap;
        overflow: hidden;
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent);
        mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent);
    }

    /* The badge is a fact about the SITE and it was costing 47px of a row that carries the reader's own
       money (owner: "убрать плашку БЕТА — она нужна где-то, но не в угоду UX"). It keeps its place on
       every wider screen, and `/legal` says the same thing in words that do not expire. */
    .ns-beta {
        display: none;
    }

    /* The pin named nothing the chip beside it did not — a region code IS a place — and the owner had
       already asked what it was doing there (2026-09-08). Dropped on phones only: on a desktop the
       segment has the width and the glyph anchors the label. */
    .ns-hb-pin {
        display: none;
    }

    /* Its two pickers are in the drawer now — see MainLayout's `LanguageRows`, rendered from one
       fragment into both surfaces so they cannot drift. */
    .ns-ctx-more {
        display: none;
    }
}

/* ONE RULE BETWEEN TWO GROUPS, NOT THREE. This block shipped with a `border-top` of its own on
   2026-09-09 and drew a third line into a seam that already had two: `.ns-drawer-link:last-child`
   closes the destinations with a bottom rule, and `.ns-ctx-row` — styled for the header popover, where
   it separates one row from the next — brings its own top rule to whichever row comes first. Stacked
   they read as a broken frame rather than as a boundary (owner, 2026-09-09: «между правовой
   информацией и интерфейс — три сепаратора»).
   The links' closing rule is the boundary; this block adds only the BREATH that says a new group
   started, and the first row inside it drops the rule it would have drawn against nothing. */
.ns-drawer-langs {
    display: grid;
    gap: 8px;
    padding: 8px 0 2px;
}

.ns-drawer-langs .ns-ctx-row:first-child {
    border-top: none;
}

/* Above the phone breakpoint the drawer is not the language's home — the header's own button is.
   AFTER the base rule, not before: same specificity, so source order decides, and written the other
   way round the drawer kept a second copy of both pickers on every desktop. */
@media (min-width: 601px) {
    .ns-drawer-langs {
        display: none;
    }
}

/* ── AT 360 THE SHOP GIVES UP ITS NAME, and it is the right item to ask. The wordmark fades gracefully
   (82% at 390, 69% at 375, 57% at 360) but below that it thins to "AL…", and the fixed furniture is
   259px of a 328px row. The shop's name is the only piece here whose width is a property of the
   READER's choice rather than of the layout — "Digiseller" is 1.7× "Seagm" — so it is both the largest
   remaining saving (~25px) and the one thing that could otherwise break the row on a shop nobody
   anticipated. With it as a glyph, 320px still keeps ~44% of the wordmark.
   The COUNT stays: "+1" is the fact that there are others, and it is two characters. ── */
@media (max-width: 360px) {
    /* `.ns-hbtn` is in the selector because `.ns-hbtn b` (0,1,1) sets this element's size 250 lines up
       and a bare `.ns-hb-shop` (0,1,0) loses to it — silently, with the glyph rendering underneath a
       name that never left. The computed size is the thing to check, never the source. */
    .ns-hbtn .ns-hb-shop {
        /* Not `display: none` — the name has to leave the LINE, not the accessible name. The button's
           own aria-label carries the shop, so a screen reader is unaffected either way; this keeps the
           element in flow for the pseudo-element to hang off. */
        font-size: 0;
    }

    .ns-hbtn .ns-hb-shop::before {
        content: "🏬";
        font-size: 0.72rem;
    }
}

/* Small phones (360px and under is still common): the top row now carries logo + context + bell + ☰,
   which overflowed its 328px of content width by ~10px and wrapped the controls onto a second line.
   Reclaimed from the wordmark's tracking and the ☰'s side padding — never from a tap target's height
   or from the context chip, which has to stay readable at a glance. */
@media (max-width: 400px) {
    .ns-logo {
        font-size: 0.75rem;
        letter-spacing: 0.1em;
    }

    .ns-brand {
        gap: 0.35rem;
    }

    .ns-beta {
        font-size: 0.45rem;
    }

    .ns-burger {
        padding: 4px 9px;
    }
}

/* ---- Auto-hiding header ---------------------------------------------------------------------
   Slides out of the way as you scroll down, returns the moment you scroll up (see
   initHeaderAutoHide in index.html — the media condition here MUST match the one in that script).
   Only a transform moves: the header stays position:sticky and keeps its space in the flow, so
   hiding it reflows nothing below. Landscape is included by HEIGHT, not width, because that is
   where it matters most and where the phone can be wider than the 860px shelf breakpoint —
   header + tabs are ~100px of a 390px-tall screen, i.e. a quarter of everything you can see. */
@media (max-width: 860px), (orientation: landscape) and (max-height: 520px) {
    .ns-header {
        transition: transform 0.24s ease;
        will-change: transform;
    }

    .ns-header.hdr-off {
        transform: translateY(-100%);
    }

    /* …BUT IT DOES NOT SLIDE BACK IN UNDER AN OPENING DRAWER. The drawer lives INSIDE the sticky
       header, so a retracted header would carry it off the top of the screen — which is why the
       auto-hide script forces the bar visible while a panel is open. That un-hiding used to ANIMATE:
       locking the page scroll fires a scroll event, `evaluate()` sees the drawer open, drops `hdr-off`,
       and 103px of header slid down over the feed just as the drawer slid in from the right. Two
       motions in opposite axes at once read as the page itself jumping (owner, 2026-09-09: «на заднем
       фоне сайт уезжает чутка наверх и получается скачок»).
       Suppressing the transition while `menu-open` is set makes the bar simply BE there in the first
       frame: the only thing that moves is the drawer, which is the thing the tap asked for. Closing
       still animates — the class is gone by then, and the bar stays put until the next scroll. */
    .ns-header.menu-open {
        transition: none;
    }
}

/* ---- Short landscape ------------------------------------------------------------------------
   A phone on its side is ~390px tall, and the fixed chrome above the feed (header, search, filter
   row, section heading) used to eat 259px of it — two thirds of the screen before a single result.
   Everything vertical is tightened here; nothing changes horizontally, and portrait is untouched. */
@media (orientation: landscape) and (max-height: 520px) {
    .ns-header-top {
        padding-top: calc(0.3rem + env(safe-area-inset-top));
        padding-bottom: 0.3rem;
    }

    .ns-tabs {
        padding-top: 0.2rem;
        padding-bottom: 0.25rem;
    }

    .ns-main {
        padding-top: 0.6rem;
        padding-bottom: 0.6rem;
    }
    /* The rest of the vertical trim lives in the components that own those rules — SearchBar,
       FilterBar, Rail and GameDetail — because scoped CSS carries an extra attribute selector and
       would outrank a global override here. */

    /* ── THE SHEETS SCROLL AS ONE COLUMN HERE, and the list stops being a 128px window on 1484px of
       content. Measured at 844 × 390 before this: the regions panel spent 252 of 390px on its head,
       its country select, two preset groups and a link, leaving the list 128px — THREE rows of
       thirty-two, at 39.8px each. The shops panel was worse: its controls ran to 295px, its list
       computed to a height of ZERO, and a 281px aggregate block hung below the fold, so the sheet had
       to be scrolled before the list could be (owner, 2026-09-09: «списками в менюшках (регионы,
       магазины) пользоваться невозможно»).

       WHY ONE SCROLL AND NOT TWO COLUMNS. Two columns — controls beside the list — is the nicer answer
       and it is what the width invites: the panel is 340px of an 844px screen with 504px of dimmed
       page behind it that nobody can use. It also needs a wrapper element in both sheets, and the
       shops sheet's summary and aggregate block sit AFTER its list in the DOM for a reason (the
       summary is a caption about the list), so they cannot join a controls column without reordering
       what portrait reads. One scroll needs no markup at all, and it gives the list not 8 rows but
       ALL of them. What it costs is that the controls scroll away — and they are set-once things,
       which is why the head below stays put and keeps the ✕ and the ? reachable from anywhere in the
       scroll.

       The width still goes up: 340 → 520 through `--panel-w`, so the ROW gets it too, and the
       anchoring maths keeps using one number. At a phone's width the `max(0px, …)` in `right` clamps
       to 0 either way, so the panel stays flush against the edge. ── */
    .ns-rgn-sheet {
        --panel-w: 520px;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        /* THE SHEET GIVES UP ITS TOP PADDING SO THE STICKY HEAD CAN OWN IT. `position: sticky; top: 0`
           pins to the scrollport's PADDING edge, so with 0.9rem of padding here the head parked 14.4px
           down and rows scrolled visibly through the strip above it — "BG Bulgaria" riding over the
           heading (owner, 2026-09-09). The head restores the inset with its own padding below. */
        padding-top: 0;
    }

    .ns-rgn-head {
        position: sticky;
        top: 0;
        z-index: 1;
        /* Opaque, or the rows scroll through the heading. The negative margins pull the bar out to the
           sheet's own edges so it covers the full width of what passes under it, and the padding puts
           the text back where it was. Verified: at the head's own line the topmost element is the head
           at the sheet's left edge, its middle and its right edge — the only px it does not cover is
           the sheet's 1px border-left, which is not its to cover. */
        background: var(--sf);
        /* No negative TOP any more — the sheet has no top padding to escape here, and a negative top
           margin on a sticky element shifts what it sticks to. Horizontal only, to reach the sheet's
           own edges so the bar covers the full width of whatever passes under it. */
        margin: 0 -1rem;
        /* Tighter than the sheet's own 0.9rem top: this bar is permanent here and 45.58px of a 390px
           screen was 12% of everything, spent on a title and a ✕. 38.4px says the same. */
        padding: 0.5rem 1rem 0.4rem;
        /* AND A LINE UNDER IT, which is what it was missing. Opaque and full-width it worked, but it
           carries the sheet's own background and had no boundary, so rows slid under the heading with
           nothing marking where the bar ended — text vanishing beneath identical-coloured text, which
           reads as an overlap rather than as a header (owner, 2026-09-09: «полоска с надписью магазины
           или регионы остаётся поверх на одном месте»). `.ns-rgn-home` below it draws no top rule, so
           this adds one line rather than doubling one — checked, the way the drawer's seam taught. */
        border-bottom: 1px solid var(--ln);
    }

    /* …AND WHATEVER FOLLOWS IT DRAWS NO TOP RULE, because the head now draws that line. Caught by
       measuring rather than by looking: the regions sheet's first block has no border and the SHOPS
       sheet's `.ns-mode` has one, so the same new rule was a boundary in one panel and a double line in
       the other. Written as `+ *` rather than naming `.ns-mode`, so a sheet that gains a different
       first block cannot reintroduce it. Third time this shape has appeared in two days — a rule that
       separates SIBLINGS duplicates a BOUNDARY the moment something above it starts drawing one. */
    .ns-rgn-head + * {
        border-top: none;
    }

    /* No longer the flex child that absorbs the leftover height — there is no leftover height here,
       and clipping to it was the defect. It takes its content and the sheet scrolls. */
    .ns-rgn-list {
        flex: 0 0 auto;
        min-height: 0;
        overflow: visible;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ns-utils {
        transition: none;
    }

    /* The header still hides and returns — that is behaviour, not decoration — it just doesn't slide. */
    .ns-header {
        transition: none;
    }
}

/* ============================== Misc surfaces ============================== */

/* A horizontal strip while it is being dragged (index.html railInit). GLOBAL rather than scoped,
   because the same class lands on Rail's own `.rail-track` and on Home's `.cluster-addons`, and
   neither scope can see the other's. Only `grabbing` — no resting `grab`, since every tile in the
   strip is a link and the pointer must keep saying so; the ‹ › arrows are the visible affordance. */
.rail-dragging {
    cursor: grabbing;
    user-select: none;
}

.access-denied {
    max-width: 480px;
    margin: 3rem auto;
    text-align: center;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-radius: 16px;
    padding: 2rem 1.5rem;
}

.access-denied h1 {
    color: var(--tx);
    font-size: 1.4rem;
    margin: 0 0 0.75rem;
}

.access-denied p {
    color: var(--mut);
    margin: 0 0 1rem;
}

/* "You are looking at this page as report #N's reader saw it."

   Global rather than scoped for the reason at the top of MainLayout.razor.css. Inverted ink on the
   page's own foreground colour, which is the one treatment nothing else on the site uses — this must
   not read as a notice the reader can act on or dismiss, because it is a statement about the whole
   page underneath it. It wraps rather than truncating: on a phone the substituted values are exactly
   what the admin came to read. */
.ns-viewas {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.3rem 0.6rem;
    padding: 0.5rem 1rem;
    background: var(--tx);
    color: var(--bg);
    font-family: var(--fn);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.35;
}

.ns-viewas-t {
    font-weight: 700;
}

.ns-viewas-vals {
    opacity: 0.72;
    font-weight: 500;
}

/* Pushed to the end of the row on a wide screen; on a phone it simply follows the values onto the
   next line, where it is still the last thing and still a full-height target. */
.ns-viewas-x {
    margin-left: auto;
    padding: 0.32rem 0.7rem;
    border: 1px solid currentColor;
    border-radius: 8px;
    color: inherit;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}

.ns-viewas-x:hover,
.ns-viewas-x:focus-visible {
    background: rgba(127, 127, 127, 0.28);
    color: inherit;
}

@media (max-width: 560px) {
    .ns-viewas-x {
        margin-left: 0;
    }
}

.ns-footer-beta {
    margin-top: 0.35rem;
}

.ns-footer-beta a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.ns-footer-beta a:hover {
    color: var(--tx2);
}

.ns-footer-links {
    margin-top: 0.35rem;
}

.ns-footer-links a {
    color: var(--mut);
    font-size: 0.85rem;
    text-decoration: none;
}

.ns-footer-links a:hover {
    color: var(--tx2);
    text-decoration: underline;
}

/* Legal / Privacy page */
.legal-doc {
    max-width: 820px;
    margin: 1.5rem auto 3rem;
    padding: 0 1rem;
    line-height: 1.6;
    color: var(--tx2);
}

.legal-doc h1 {
    font-size: 1.7rem;
    color: var(--tx);
    margin-bottom: 0.25rem;
}

.legal-doc h2 {
    font-size: 1.15rem;
    color: var(--tx);
    margin: 1.6rem 0 0.5rem;
}

.legal-doc p {
    margin: 0.5rem 0;
}

.legal-doc a {
    color: var(--acc);
}

.legal-doc .legal-updated {
    color: var(--mut);
    font-size: 0.85rem;
    margin-top: 0;
}

/* Beta / debugging notice — in the page flow under the header, so it overlaps nothing and scrolls
   away with the page; the fixed bottom slot belongs to .cookie-notice. */
.beta-notice {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.4rem 0.75rem;
    padding: 0.55rem 1rem;
    background: var(--sf);
    border-bottom: 1px solid var(--ln);
    font-size: 0.85rem;
    color: var(--tx2);
}

.beta-text {
    text-align: center;
}

.beta-mail {
    color: var(--acc);
    text-decoration: none;
    white-space: nowrap;
}

.beta-mail:hover {
    text-decoration: underline;
}

.beta-close {
    flex: none;
    background: none;
    border: none;
    color: var(--mut);
    font-size: 0.9rem;
    line-height: 1;
    padding: 0.3rem 0.45rem;
    font-family: inherit;
    cursor: pointer;
}

.beta-close:hover {
    color: var(--tx);
}

/* Essential-storage / cookie notice */
.cookie-notice {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.6rem 1.25rem;
    padding: 0.7rem 1rem calc(0.7rem + env(safe-area-inset-bottom));
    background: var(--sf);
    border-top: 1px solid var(--ln);
    font-size: 0.85rem;
    color: var(--tx2);
    box-shadow: var(--frame-sh);
}

.cookie-text {
    max-width: 70ch;
}

.cookie-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.9rem;
}

.cookie-link {
    color: var(--acc);
    text-decoration: none;
    white-space: nowrap;
}

.cookie-link:hover {
    text-decoration: underline;
}

.cookie-ok {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    border-radius: 999px;
    padding: 0.35rem 1.1rem;
    font-size: 0.85rem;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
}

.cookie-ok:hover {
    opacity: 0.9;
}

/* Blazor error UI */
#blazor-error-ui {
    background: #B32121;
    color: white;
    bottom: 0;
    box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.3);
    display: none;
    left: 0;
    /* Pinned to the bottom edge, so it clears the navigation bar too — this is the one banner that
       must never be half-hidden. */
    padding: 0.6rem 1.25rem calc(0.6rem + env(safe-area-inset-bottom));
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.blazor-error-boundary {
    background: #B32121;
    padding: 1rem;
    color: white;
    border-radius: 8px;
}

.blazor-error-boundary::after {
    content: "An error has occurred.";
}

/* First-load progress ring (before Blazor boots — tokens already apply via this stylesheet). */
.loading-progress {
    position: absolute;
    display: block;
    width: 8rem;
    height: 8rem;
    inset: 20vh 0 auto 0;
    margin: 0 auto;
}

.loading-progress circle {
    fill: none;
    stroke: var(--ln);
    stroke-width: 0.6rem;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}

.loading-progress circle:last-child {
    stroke: var(--red);
    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
    transition: stroke-dasharray 0.05s ease-in-out;
}

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: bold;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
    color: var(--mut);
}

.loading-progress-text:after {
    content: var(--blazor-load-percentage-text, "Loading");
}

/* ============================== Auth / profile forms ============================== */

.auth-card {
    max-width: 380px;
    margin: 2rem auto;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-radius: 18px;
    padding: 1.5rem;
}

.auth-card h1 {
    font-size: 1.4rem;
    font-weight: 900;
    margin: 0 0 1.25rem;
    color: var(--tx);
}

.auth-field {
    margin-bottom: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.auth-field label {
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--mut);
}

.auth-field input,
.auth-field textarea {
    width: 100%;
    background: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.6rem 0.75rem;
    font-size: 1rem;
    font-family: inherit;
}

.auth-field textarea {
    resize: vertical;
}

.auth-field input:focus,
.auth-field textarea:focus,
.auth-field select:focus {
    outline: none;
    border-color: var(--acc);
}

.auth-field select {
    width: 100%;
    background-color: var(--bg);
    color: var(--tx);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.6rem 2rem 0.6rem 0.75rem;
    font-size: 1rem;
    font-family: inherit;
    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.75rem center;
}

.auth-hint {
    color: var(--mut);
    font-size: 0.75rem;
}

.auth-error {
    background: var(--acc-soft);
    color: var(--red);
    border: 1px solid var(--acc-brd);
    border-radius: 10px;
    padding: 0.5rem 0.75rem;
    font-size: 0.85rem;
    margin-bottom: 1rem;
}

.auth-btn {
    width: 100%;
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    border-radius: 999px;
    padding: 0.65rem;
    font-size: 1rem;
    font-weight: 800;
    font-family: inherit;
    cursor: pointer;
    transition: opacity 0.2s;
}

.auth-btn:hover:not(:disabled) { opacity: 0.9; }
.auth-btn:disabled { opacity: 0.55; cursor: not-allowed; }

.auth-alt {
    margin: 1rem 0 0;
    text-align: center;
    font-size: 0.85rem;
    color: var(--mut);
}

/* ── Profile picture ─────────────────────────────────────────────────────────────────────────
   Lives in app.css (not a scoped Profile.razor.css) because <Avatar> is a child component: a
   scoped rule from the page would be rewritten with the PAGE's scope attribute and never match
   the child's markup. Everything here targets the row wrapper the page itself renders. */
.av-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0 0 1.25rem;
}

/* The disc IS the control — a <label for> wrapping the avatar, so the browser opens the file picker
   natively (see the note in Profile.razor). Transparent chrome so it reads as the avatar itself. */
.av-pick {
    position: relative;
    flex: 0 0 auto;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    border-radius: 50%;
    line-height: 0;
    display: inline-block;
}

/* A <label> can't take focus — the (visually collapsed) file input does. Reflect ITS focus ring onto
   the visible controls, or keyboard users would tab into an invisible element with no indication. */
.av-row:has(.av-file:focus-visible) .av-pick {
    outline: 2px solid var(--acc);
    outline-offset: 3px;
}

.av-row:has(.av-file:focus-visible) .av-link[for] {
    text-decoration: underline;
    outline: 2px solid var(--acc);
    outline-offset: 2px;
    border-radius: 4px;
}

.av-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--acc);
    color: var(--acc-ink);
    /* Ring in the card's own surface colour so the badge reads as lifted off the disc. */
    box-shadow: 0 0 0 3px var(--sf);
    transition: transform 0.16s ease;
}

.av-pick:hover .av-badge { transform: scale(1.08); }

.av-row-main {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.av-row-title {
    font-weight: 800;
    color: var(--tx);
}

.av-row-actions {
    display: flex;
    gap: 0.85rem;
    margin-top: 0.35rem;
}

.av-link {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--acc);
    cursor: pointer;
}

.av-link:hover:not(:disabled) { text-decoration: underline; }
.av-link:disabled { opacity: 0.5; cursor: not-allowed; }
.av-link.danger { color: var(--mut); }
.av-link.danger:hover { color: var(--red); }

/* Kept in the layout (not display:none) so it stays focusable/clickable for assistive tech, but
   visually collapsed — the styled disc and text buttons are the real affordances. */
.av-file {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

/* ── Crop dialog ─────────────────────────────────────────────────────────────────────────── */
.av-modal {
    position: fixed;
    inset: 0;
    /* Above .cookie-notice (1000) and the open mobile drawer (1100). At 1000 it tied with the
       cookie bar, and the tie broke on DOM order — so the bar painted over the dim on a phone. */
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    /* A full-viewport overlay under edge-to-edge: keep the dialog itself clear of the system bars. */
    padding: max(1rem, env(safe-area-inset-top)) 1rem max(1rem, env(safe-area-inset-bottom));
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(2px);
}

.av-dialog {
    width: min(360px, 100%);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 18px;
    padding: 1.25rem;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}

.av-dialog-title {
    margin: 0 0 0.9rem;
    font-size: 1.05rem;
    font-weight: 900;
    color: var(--tx);
    text-align: center;
}

.av-stage {
    display: flex;
    justify-content: center;
}

/* Square box; the circle is punched out by .av-mask below. */
.av-viewport {
    position: relative;
    width: min(260px, 68vw);
    aspect-ratio: 1;
    overflow: hidden;
    border-radius: 14px;
    background: var(--sf2);
    cursor: grab;
    touch-action: none;   /* we own pan + pinch; without this the browser scrolls the page instead */
    user-select: none;
}

.av-viewport:active { cursor: grabbing; }

.av-src {
    position: absolute;
    left: 0;
    top: 0;
    transform-origin: 0 0;
    max-width: none;      /* the cropper sets explicit px dimensions; a max-width would fight it */
    will-change: transform;
    -webkit-user-drag: none;
}

/* The dim + the circle in one element: a spread box-shadow larger than the box paints everything
   OUTSIDE the circle, at any size, with no second overlay to intercept the drag. */
.av-mask {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
    /* A hairline so the crop edge is legible against a dark photo. */
    outline: 1px solid rgba(255, 255, 255, 0.55);
    outline-offset: -1px;
    pointer-events: none;
}

.av-zoom {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.9rem 0 0;
}

.av-zoom input[type="range"] {
    flex: 1;
    accent-color: var(--acc);
}

.av-zoom-ico {
    color: var(--mut);
    font-weight: 900;
    width: 1ch;
    text-align: center;
}

.av-dialog-hint {
    margin: 0.55rem 0 0;
    text-align: center;
    font-size: 0.78rem;
    color: var(--mut);
}

.av-dialog-actions {
    display: flex;
    gap: 0.6rem;
    margin-top: 1rem;
}

.av-dialog-actions .auth-btn { width: auto; flex: 1; }

.auth-btn.ghost {
    background: var(--sf2);
    color: var(--tx);
    border: 1px solid var(--ln);
}

/* Profile page */
.profile-card {
    max-width: 560px;
    margin: 1.5rem auto;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: calc(var(--lift) + 1px);
    border-radius: 18px;
    padding: 1.5rem;
}

.profile-card h1 {
    font-size: 1.5rem;
    font-weight: 900;
    margin: 0 0 0.25rem;
    color: var(--tx);
}

.profile-email {
    color: var(--mut);
    font-size: 0.85rem;
    margin: 0 0 1.25rem;
}

/* Sub-section divider inside the profile card (e.g. the friends-only "Contacts" group). */
.profile-subhead {
    font-size: 1rem;
    font-weight: 800;
    color: var(--tx);
    margin: 1.5rem 0 0.15rem;
    padding-top: 1rem;
    border-top: 1px solid var(--ln);
}
.profile-subnote {
    color: var(--mut);
    font-size: 0.82rem;
    margin: 0 0 1rem;
    line-height: 1.5;
}

.check-inline {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--tx2);
    font-size: 0.9rem;
    cursor: pointer;
}

.check-inline input {
    width: 16px;
    height: 16px;
    accent-color: var(--acc);
}

.profile-actions {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 0.5rem;
    flex-wrap: wrap;
}

.profile-save {
    width: auto;
    padding: 0.6rem 1.5rem;
}

.profile-status {
    font-size: 0.9rem;
}

.profile-status.ok { color: var(--deal); }
.profile-status.err { color: var(--red); }

/* ---- Account section: recovery email + the irreversible bit ----
   The delete control is deliberately not styled as a primary button competing with "Save changes";
   it reads as an outlined danger action and only turns solid on hover, and the real guard is the
   confirm dialog that asks for the password. */
.acct-row {
    display: flex;
    align-items: flex-end;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.acct-row .auth-field {
    flex: 1 1 12rem;
    min-width: 0;   /* lets the input shrink on a phone instead of overflowing the card */
    margin-bottom: 0;
}
.acct-row .auth-btn {
    width: auto;
    padding: 0.6rem 1.15rem;
    flex: 0 0 auto;
}

.dz {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--ln);
}
.dz-title {
    font-size: 1rem;
    font-weight: 800;
    color: var(--red);
    margin: 0 0 0.15rem;
}

.auth-btn.danger {
    width: auto;
    padding: 0.6rem 1.15rem;
    background: transparent;
    color: var(--red);
    border: 1px solid var(--red);
}
.auth-btn.danger:hover:not(:disabled) {
    background: var(--red);
    color: #fff;
}

@media (max-width: 480px) {
    /* Stack the field over its button — side by side leaves the input too narrow to read an address. */
    .acct-row { align-items: stretch; }
    .acct-row .auth-btn,
    .dz .auth-btn.danger { width: 100%; }
}

/* ---- Coupon top-up cost badges ---- */
.coupon-markup {
    font-size: 0.72em;
    font-weight: 700;
    padding: 1px 6px;
    border-radius: 6px;
    margin-left: 4px;
    white-space: nowrap;
    vertical-align: middle;
}

.coupon-markup.good { color: var(--deal); background: var(--deal-bg); }
.coupon-markup.bad  { color: var(--warn); background: rgba(217, 119, 6, 0.14); }

/* Subtitle under a price table explaining the active lens (seller + display currency). */
.price-lens-note {
    margin: -0.4rem 0 0.7rem;
    font-size: 0.85rem;
    color: var(--mut);
}

.price-lens-note strong { color: var(--tx2); }

/* "save X%" — how much cheaper the best deal is than the viewer's own region. */
.savings-badge {
    font-size: 0.72em;
    font-weight: 800;
    padding: 1px 6px;
    border-radius: 6px;
    margin-left: 4px;
    white-space: nowrap;
    vertical-align: middle;
    color: var(--deal);
    background: var(--deal-bg);
}

/* ============================================================================================
   Family workbench. Density is the point: the admin scans dozens of SKUs looking for holes, so
   a row is one line and a region is one cell. Covers stay at 32px in the QUEUE — big enough to
   tell two pieces of box art apart at a glance, small enough that 60 rows still fit on a screen.

   The family screen keeps that density for the matrix and spends it in exactly one place: the
   compare card, which is the only place on the page where a decision is actually made. There the
   layout goes wide and calm — two 128px covers side by side and a word over every row of evidence
   — because a person answering "is this the same product" reads pictures, names, prices and dates,
   and the machine's arithmetic is only a hint about where to look.
   ============================================================================================ */

/* A solo grind through ~40 families needs to know which one this is. */
.wb-topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 1rem;
    margin-bottom: 0.9rem;
}

.wb-qpos {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--fn);
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--mut);
}

.wb-qpos b { color: var(--tx); font-variant-numeric: tabular-nums; font-weight: 800; }

.wb-qnav {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 7px;
    width: 24px;
    height: 22px;
    color: var(--tx2);
    font-family: inherit;
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.wb-qnav:hover:not(:disabled) { border-color: var(--acc); color: var(--acc); }
.wb-qnav:disabled { opacity: 0.4; cursor: not-allowed; }

.wb-head {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    margin-bottom: 0.75rem;
}

.wb-head-cover {
    width: 96px;
    height: 96px;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid var(--ln);
    background: var(--sf2);
    flex: none;
}

.wb-head-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.wb-head-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    align-items: center;
}

/* The game page's identity label, in its own words rather than the raw enum (audit #4). */
.wb-plat {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 11px 3px 9px;
    border-radius: 9px;
    background: var(--sf2);
    color: var(--tx2);
    font-size: 0.82rem;
    font-weight: 800;
    white-space: nowrap;
}

.wb-plat svg { width: 15px; height: 15px; flex: none; opacity: 0.9; }
.wb-plat.sw2 { color: var(--sw2-tx); background: var(--acc-soft); }

/* The generation sibling, worded as the relation the game page states rather than as "⇄". */
.wb-twin {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 12px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    font-size: 0.79rem;
    font-weight: 800;
    color: var(--acc);
    text-decoration: none;
    background: var(--sf);
}

.wb-twin:hover { border-color: var(--acc); }

.wb-facts {
    font-size: 0.88rem;
    color: var(--tx2);
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: baseline;
}

.wb-facts .sep { color: var(--ln2); }
.wb-facts b { font-family: var(--fn); font-variant-numeric: tabular-nums; color: var(--tx); font-weight: 800; }

.wb-regions {
    font-family: var(--fn);
    letter-spacing: 0.06em;
    font-weight: 800;
    color: var(--tx2);
}

.wb-sibling {
    color: var(--acc);
    text-decoration: none;
    font-weight: 800;
    font-size: 0.8rem;
}

.wb-sibling:hover { text-decoration: underline; }

.wb-flash {
    border-radius: 10px;
    padding: 0.5rem 0.85rem;
    margin-bottom: 0.75rem;
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
}

.wb-flash.bad { background: var(--acc-soft); color: var(--red); }
.wb-flash.ok { background: var(--deal-bg); color: var(--deal); }
.wb-flash.warn { background: rgba(217, 158, 20, 0.16); color: var(--gold); cursor: default; }

.wb-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
    margin-bottom: 0.6rem;
}

.wb-chips { display: flex; gap: 0.35rem; flex-wrap: wrap; }

.wb-chip {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
    border-radius: 999px;
    padding: 3px 11px;
    font-size: 0.78rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
}

.wb-chip.on { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); }
.wb-chip-n { opacity: 0.65; margin-left: 4px; font-variant-numeric: tabular-nums; }

.wb-check {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 0.82rem;
    color: var(--tx2);
}

.wb-search {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx);
    border-radius: 8px;
    padding: 5px 10px;
    font-family: inherit;
    font-size: 0.85rem;
    min-width: 180px;
}

.wb-hint {
    color: var(--mut);
    font-size: 0.74rem;
    margin-left: auto;
}

/* Two chip groups now sit in one toolbar, so each carries a label — without them the accent colour
   would mean two different things in one strip (audit #42's fault, in miniature). */
.wb-cl {
    font-family: var(--fn);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--mut);
}

/* The keyboard model, written out as sentences instead of the one-line hint only its author could
   read (audit #14). A disclosure, so it costs nothing once it is known. */
.wb-keys { margin-left: auto; }

.wb-keys > summary {
    list-style: none;
    cursor: pointer;
    font-size: 0.79rem;
    font-weight: 800;
    color: var(--tx2);
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.wb-keys > summary::-webkit-details-marker { display: none; }
.wb-keys > summary::after { content: "▾"; font-size: 0.7em; color: var(--mut); }
.wb-keys[open] > summary::after { content: "▴"; }

.wb-keysheet {
    margin-top: 0.5rem;
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.7rem 0.9rem;
    font-size: 0.82rem;
    color: var(--tx2);
    display: grid;
    gap: 0.3rem;
    max-width: 30rem;
}

.wb-keysheet div { display: flex; gap: 0.55rem; align-items: baseline; }

.wb-kbd {
    font-family: var(--fn);
    font-size: 0.71rem;
    font-weight: 800;
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-bottom-width: 2px;
    border-radius: 6px;
    padding: 1px 7px;
    color: var(--tx);
    flex: none;
    min-width: 2rem;
    text-align: center;
}

.wb:focus { outline: none; }
.wb:focus-visible { outline: 2px solid var(--acc); outline-offset: 3px; border-radius: 10px; }

/* ---- The ledger --------------------------------------------------------------------------------
   How many listings exist under this game and how many are still unplaced — the denominator a pair
   shown on its own does not have. It sits ABOVE the matrix because it is what makes a hard pair
   decidable without reading a score: "Korea holds one unplaced bundle and this family has one bundle
   with no Korean listing" settles a question no amount of arithmetic on a title can.
   It counts and nothing else — no ranking, no threshold, no second opinion. */
.wb-inv {
    margin-bottom: 1.1rem;
    border: 1px solid var(--ln);
    border-radius: 14px;
    background: var(--sf);
    overflow: hidden;
}

.wb-inv-head { padding: 0.7rem 0.9rem 0.55rem; }
.wb-inv-head h3 { margin: 0; font-size: 0.98rem; font-weight: 900; letter-spacing: -0.01em; }
.wb-inv-head p { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--tx2); }
.wb-inv-head p b { font-family: var(--fn); font-variant-numeric: tabular-nums; color: var(--tx); font-weight: 800; }
.wb-inv-head p b.left { color: var(--gold); }

/* The heading says "everything Nintendo sells under this game" over a body that can only count what
   a key can reach from here. That gap is stated rather than glossed. */
.wb-inv-bound { margin: 0.3rem 0 0; font-size: 0.76rem; color: var(--mut); }

.wb-inv-wrap { overflow-x: auto; }

.wb-inv-grid {
    width: 100%;
    border-collapse: collapse;
    min-width: 34rem;
}

.wb-inv-grid th {
    font-family: var(--fn);
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--mut);
    padding: 0.35rem 0.7rem;
    text-align: center;
    border-top: 1px solid var(--ln);
    border-bottom: 1px solid var(--ln);
    background: var(--sf2);
    white-space: nowrap;
}

.wb-inv-grid th:first-child { text-align: left; }
.wb-inv-grid th.tail,
.wb-inv-grid td.tail { text-align: right; }

.wb-inv-grid td {
    padding: 0.32rem 0.7rem;
    text-align: center;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--tx2);
    border-top: 1px solid var(--ln);
}

.wb-inv-grid tbody tr:first-child td { border-top: 0; }

.wb-inv-grid td:first-child {
    text-align: left;
    font-family: inherit;
    font-weight: 700;
    color: var(--tx);
    white-space: nowrap;
}

.wb-inv-grid td.done { color: var(--deal); font-weight: 800; }
.wb-inv-grid td.left { color: var(--gold); font-weight: 800; }

/* Updates have no row of numbers because they have no listings — said in words, because an empty
   row of dashes reads as missing data rather than as "there is nothing here to map". */
.wb-inv-grid tr.na td {
    text-align: left;
    font-family: inherit;
    font-weight: 600;
    font-size: 0.81rem;
    color: var(--mut);
}

.wb-float { border-top: 1px solid var(--ln); background: var(--sf2); }

.wb-float > summary {
    list-style: none;
    cursor: pointer;
    padding: 0.5rem 0.9rem;
    font-size: 0.84rem;
    font-weight: 800;
    color: var(--tx2);
    display: flex;
    align-items: center;
    gap: 6px;
}

.wb-float > summary::-webkit-details-marker { display: none; }
.wb-float > summary::after { content: "▾"; font-size: 0.7em; color: var(--mut); }
.wb-float[open] > summary::after { content: "▴"; }

.wb-frow {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    padding: 0.38rem 0.9rem;
    border-top: 1px solid var(--ln);
    font-size: 0.85rem;
}

.wb-frow.on { background: var(--acc-soft); }

.wb-frgn {
    font-family: var(--fn);
    font-size: 0.66rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    color: var(--tx2);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 6px;
    padding: 1px 7px;
    flex: none;
}

.wb-fname { font-weight: 700; color: var(--tx); flex: 1 1 12rem; min-width: 0; }
.wb-fread { color: var(--mut); font-size: 0.79rem; font-style: italic; font-weight: 400; }

/* The machine translation, beside the foreign name where the list is scanned. Brighter than the
   transliteration it replaces because it carries MEANING rather than sound — but still italic and
   still secondary, since it is a machine's reading and not the store's own words. */
.wb-ftr {
    color: var(--tx2);
    font-size: 0.81rem;
    font-style: italic;
    font-weight: 400;
}

/* When a row carries both, the TRANSLATION gives way first. The store's own name is the identity of
   the thing being decided; the translation is a reading of it, and a reading truncated to
   «"FINAL FANTASY XIV -» still helps while a name truncated to «ファイナルファンタジー -…» does not. */
.wb-title-in > .wb-name,
.wb-title-in > .wb-namebtn,
.wb-title-in > .wb-open { flex: 0 1 auto; min-width: 7rem; }

.wb-title-in > .wb-ftr {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The row's name is a button now (it opens the compare card) and must not look like one. */
.wb-namebtn {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
    color: var(--tx);
}

.wb-namebtn:hover { color: var(--acc); text-decoration: underline; }
.wb-namebtn:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; border-radius: 4px; }

/* When the cover is the only thing in the link, it should not stretch across the cell — and it must
   drop the 7rem floor above with it. `flex: none` alone left a 32px thumbnail sitting in a 112px box,
   which is where the empty gap between every cover and its title came from: min-width is not part of
   the flex shorthand, so overriding one said nothing about the other. */
.wb-open.thumb-only { flex: none; min-width: 0; }

/* "Open the details page on the site". An icon, because it repeats on every row of a 175-row matrix
   where a word would compete with the name beside it — and because the NAME now means "decide about
   this row", so the two intents need visibly different controls rather than two links that look the
   same and go to different places. */
.wb-page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    color: var(--mut);
    text-decoration: none;
}

.wb-page svg { width: 15px; height: 15px; }
.wb-page:hover { color: var(--acc); background: var(--acc-soft); }
.wb-page:focus-visible { outline: 2px solid var(--acc); outline-offset: 1px; }

.wb-famt {
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-weight: 800;
    color: var(--tx);
    white-space: nowrap;
}

/* How many rows a loose listing could go to. ONE is a free win; several is exactly the ambiguity
   Pass 6.5 excludes rather than scores ("442 skips are not a backlog"), handed to the only judge
   that can settle it. */
.wb-fits {
    font-family: var(--fn);
    font-size: 0.61rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 1px 7px;
    border-radius: 6px;
    color: var(--deal);
    background: var(--deal-bg);
    border: 1px solid var(--deal-brd);
    flex: none;
    cursor: help;
}

.wb-fits.many { color: var(--gold); background: rgba(217, 158, 20, 0.16); border-color: rgba(217, 158, 20, 0.38); }
.wb-fits.no { color: var(--mut); background: var(--sf); border-color: var(--ln); }

/* The matrix can outgrow a phone; it scrolls in its own box so the page body never does. */
.wb-scroll {
    overflow-x: auto;
    border: 1px solid var(--ln);
    border-radius: 12px;
    background: var(--sf);
}

.wb-matrix {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.84rem;
}

.wb-matrix th {
    text-align: left;
    padding: 7px 8px;
    background: var(--sf2);
    border-bottom: 1px solid var(--ln);
    color: var(--tx2);
    font-size: 0.72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    position: sticky;
    top: 0;
    z-index: 1;
    /* A wrapped heading reads as two columns. The widths below are hints on an auto-layout table,
       so a label wider than its hint widens the column instead of breaking in half — which is what
       "€ GAME" did at the 62px price width. */
    white-space: nowrap;
}

/* 76px on the QUEUE, whose region headers are labels ("JP/HK/KR") rather than codes. The family
   matrix opts into `tight`: its cell is a 22px badge under a two-letter code, so 76px would leave
   the row half empty and push the name column into an ellipsis for no reason. */
.wb-th-region { width: 76px; text-align: center; }
.wb-th-region.tight { width: 48px; }
.wb-th-price { width: 62px; text-align: right; }
/* Wide enough for "129.99–199.99" on one line — a wrapped range reads as two numbers. */
.wb-th-band { width: 108px; text-align: right; }
.wb-th-act { width: 150px; }
/* The QUEUE's action column holds one icon: its way into a family is the row's name now. */
.wb-th-act.tight { width: 46px; }

.wb-row > td {
    padding: 4px 8px;
    border-bottom: 1px solid var(--ln);
    vertical-align: middle;
}

.wb-row:hover > td { background: var(--sf2); }
.wb-row.cursor > td { background: var(--acc-soft); }
.wb-row.root > td { background: var(--sf2); font-weight: 700; }

/* The merge target gets a single accent bar down its left edge. An inset ring on every td drew a
   box around each CELL instead of the row, which read as a broken table. */
.wb-row.target > td { background: var(--acc-soft); }
.wb-row.target > td:first-child { box-shadow: inset 3px 0 0 var(--acc); }

/* A td must stay a table-cell: `display: flex` on it drops the cell out of the column algorithm,
   so the region columns stop lining up with their headers and a colspan row stops spanning. The
   flex row lives one level in. */
/* Wider than it was, because the cell now carries a foreign name AND its translation. */
.wb-title {
    min-width: 240px;
    max-width: 640px;
}

.wb-title-in {
    display: flex;
    align-items: center;
    gap: 6px;
}

.wb-pick { flex: none; }
/* Keeps the box so the root row's name starts on the same pixel as every other row's. */
.wb-pick.ghost { visibility: hidden; }

/* Cover + title are a link to that SKU's own page on the site (new tab). It inherits the row's colour
   rather than taking link blue — every row would be blue and the table would stop reading as data —
   and `min-width: 0` is what lets .wb-name still ellipsize, since a flex item defaults to
   min-width: auto and would otherwise refuse to shrink below its text. */
.wb-open {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.wb-open:hover .wb-name,
.wb-open:hover .wb-sug-title { text-decoration: underline; }

.wb-thumb {
    width: 32px;
    height: 32px;
    border-radius: 6px;
    object-fit: cover;
    background: var(--sf2);
    flex: none;
}

.wb-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--tx);
}

.wb-sku {
    flex: none;
    font-size: 0.62rem;
    font-weight: 800;
    text-transform: uppercase;
    border-radius: 5px;
    padding: 1px 5px;
    letter-spacing: 0.04em;
}

.wb-sku-base { background: var(--acc-soft); color: var(--acc); }
.wb-sku-dlc { background: var(--sf2); color: var(--tx2); border: 1px solid var(--ln); }
.wb-sku-bundle { background: rgba(217, 158, 20, 0.16); color: var(--gold); }

.wb-mirror {
    flex: none;
    color: var(--mut);
    font-size: 0.7rem;
    font-variant-numeric: tabular-nums;
}

.wb-cell {
    text-align: center;
    position: relative;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
}

/* Three cell states, and the third is the point. Before, a hole was "·" whether Nintendo does not
   sell it there or a listing is sitting unplaced two rows down — opposite situations printing the
   same character. The nsuid tail that used to fill this cell was the loudest thing in it and decided
   nothing on this screen (audit #22); it moves to the tooltip and the space pays for the mark. */
.wb-tick {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 7px;
    background: var(--deal-bg);
    border: 1px solid var(--deal-brd);
    color: var(--deal);
    font-size: 0.78rem;
    font-weight: 900;
    cursor: help;
}

.wb-tick.dead {
    background: transparent;
    border-color: var(--ln);
    color: var(--mut);
    text-decoration: line-through;
}

/* Quiet, but it still has to be READ: this is one of the three marks the legend explains, and at
   the hairline tint it was indistinguishable from an empty cell. */
.wb-dot { color: var(--mut); font-weight: 800; opacity: 0.6; }

/* Blue, never amber: amber already means "delisted" on the same row, and one visual with two
   meanings is the fault audit #42 raises about the parent picker. */
.wb-loose {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 7px;
    border: 1px dashed var(--info);
    background: transparent;
    color: var(--info);
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
    font-weight: 800;
    cursor: pointer;
    padding: 0;
    transition: border-style 0.12s, transform 0.12s;
}

.wb-loose:hover { border-style: solid; transform: translateY(-1px); }

/* The strikethrough said "crossed out"; it never said what had happened. Nintendo took the listing
   down, and a dead SKU is the one thing on this screen a merge must not chase (audit #23). */
.wb-dead {
    display: block;
    margin-top: 2px;
    font-family: var(--fn);
    font-size: 0.55rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--gold);
    cursor: help;
}

.wb-x {
    position: absolute;
    top: 1px;
    right: 1px;
    background: none;
    border: none;
    color: var(--mut);
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0 2px;
    opacity: 0;
}

.wb-cell:hover .wb-x { opacity: 1; }
.wb-x:hover { color: var(--red); }

/* The row is the summary and the compare card is the drill-down — the game page's own accordion
   (its price rows work the same way, and for the same reason: only one thing is being read). */
.wb-chev {
    border: 0;
    background: transparent;
    color: var(--mut);
    font-size: 0.8rem;
    cursor: pointer;
    padding: 2px 3px;
    line-height: 1;
    flex: none;
    font-family: inherit;
}

.wb-chev:hover { color: var(--acc); }
.wb-chev.ghost { visibility: hidden; }

/* One vocabulary for the type. `base`/`dlc`/`bundle` off the nsuid prefix and Game/DLC/Bundle off
   ContentType were two names for one fact (audit #3 and #20); this is the only one now. */
.wb-type {
    display: inline-block;
    font-family: var(--fn);
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--tx2);
    background: var(--sf2);
    border: 1px solid var(--ln);
    padding: 1px 8px;
    border-radius: 6px;
}

.wb-type.bundle { color: var(--sw2-tx); border-color: var(--ln2); }

.wb-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1.1rem;
    padding: 0.5rem 0.2rem 0;
    font-size: 0.78rem;
    color: var(--mut);
}

.wb-legend span { display: inline-flex; align-items: center; gap: 0.4rem; }

.wb-price {
    text-align: right;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    color: var(--tx2);
}

/* The add-on price band on the worklist. Quieter than the base game's own price: it is context for
   reading a suggestion, not the row's headline. */
.wb-band {
    text-align: right;
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    color: var(--mut);
    white-space: nowrap;
}

.wb-act { white-space: nowrap; }
.wb-act .wb-btn { margin-right: 3px; }

/* Buttons and text helpers live here rather than in a scoped .razor.css because all three
   workbench pages share them, and Blazor scopes a component stylesheet to its own component. */
.wb-btn {
    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;
    text-decoration: none;
    display: inline-block;
}

.wb-btn:hover:not(:disabled) { border-color: var(--mut); }
.wb-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.wb-btn.primary { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); }
.wb-btn.danger { color: var(--red); }

.wb-action {
    background: var(--acc);
    color: var(--acc-ink);
    border: none;
    padding: 0.45rem 1.05rem;
    border-radius: 999px;
    font-weight: 800;
    font-family: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
}

.wb-action:hover:not(:disabled) { opacity: 0.9; }
.wb-action:disabled { opacity: 0.5; cursor: not-allowed; }

.wb-action.sec {
    background: var(--sf);
    border: 1px solid var(--ln);
    color: var(--tx2);
}

.wb-action.sec:hover:not(:disabled) { border-color: var(--mut); opacity: 1; }

.wb-h3 {
    font-size: 1.05rem;
    font-weight: 900;
    margin: 1.4rem 0 0.6rem;
    color: var(--tx);
}

.wb-muted { color: var(--tx2); font-size: 0.85rem; }

.wb-sug-reg {
    font-family: var(--fn);
    font-weight: 800;
    letter-spacing: 0.05em;
    color: var(--tx2);
    font-size: 0.72rem;
}

.wb-reading { color: var(--acc); font-style: italic; }
.wb-signals { color: var(--mut); font-family: var(--fn); font-size: 0.72rem; }

.wb-veto {
    background: var(--acc-soft);
    color: var(--red);
    border-radius: 5px;
    padding: 1px 6px;
    font-size: 0.68rem;
    font-weight: 800;
}

/* ---- The arrangement: one proposed placement for the whole family -----------------------------
   Replaced a column of independent yes/no questions. Every entry is one line — mark, row, chip,
   reason, controls — because the screen's whole claim is that the family can be read at once. */
.wb-arr {
    margin-bottom: 1.1rem;
    border: 1px solid var(--ln);
    border-radius: 14px;
    background: var(--sf);
    overflow: hidden;
}

.wb-arr-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--ln);
}

.wb-arr-headline { flex: 1 1 18rem; min-width: 0; }
.wb-arr-headline h3 { margin: 0; font-size: 0.98rem; font-weight: 900; letter-spacing: -0.01em; }

.wb-arr-sum { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--tx2); }
.wb-arr-sum b { font-family: var(--fn); font-variant-numeric: tabular-nums; color: var(--tx); font-weight: 800; }
.wb-arr-sum b.ok { color: var(--deal); }
.wb-arr-sum b.left { color: var(--gold); }

/* The body owns the keyboard, so it takes focus — without the outline suppressed the whole panel
   draws a ring the moment J/K start working, which reads as an error state. */
.wb-arr-body { outline: none; }
.wb-arr-pad { padding: 0.7rem 0.9rem; }

.wb-sec-head {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
    margin: 0;
    padding: 0.5rem 0.9rem 0.35rem;
    background: var(--sf2);
    border-bottom: 1px solid var(--ln);
    font-size: 0.82rem;
    font-weight: 900;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.wb-sec-n {
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    color: var(--tx2);
    font-weight: 800;
}

/* How much work is in THIS section — a page total cannot say which one to open. */
.wb-sec-need { color: var(--gold); font-weight: 800; text-transform: none; }
.wb-sec-done { color: var(--deal); font-weight: 800; text-transform: none; }

.wb-ent {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.9rem;
    border-bottom: 1px solid var(--ln);
    font-size: 0.83rem;
    min-width: 0;
}

.wb-ent.cursor { background: var(--acc-soft); }

.wb-ent-mark {
    flex: 0 0 auto;
    width: 1.15rem;
    text-align: center;
    font-weight: 900;
    font-size: 0.95rem;
}

.wb-ent-mark.assigned { color: var(--deal); }
.wb-ent-mark.ambiguous { color: var(--gold); }
.wb-ent-mark.alone { color: var(--mut); }

.wb-ent-name {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    flex: 1 1 16rem;
    min-width: 0;
}

/* The row's name reads as a NAME, not as a link, even though it opens the comparison — link blue on
   a line that already carries a chip and a reason would make the name the loudest thing on it. */
.wb-ent-name .nm {
    font-size: inherit;
    font-weight: 700;
    color: var(--tx);
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.wb-ent-arrow { color: var(--mut); flex: 0 0 auto; }

/* The chip: the listing this row was placed on. */
.wb-chip {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    flex: 1 1 16rem;
    min-width: 0;
    padding: 2px 9px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    background: var(--sf2);
}

.wb-chip .nm {
    font-weight: 700;
    color: var(--tx);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.wb-ent-why {
    flex: 0 0 auto;
    font-size: 0.75rem;
    font-weight: 800;
    color: var(--deal);
    background: var(--deal-bg);
    border: 1px solid var(--deal-brd);
    border-radius: 999px;
    padding: 1px 9px;
}

/* "Nothing else was left" is not the same evidence as "these two read the same", and a reader
   confirming a page of chips at once is owed the difference. */
.wb-ent-why.forced { color: var(--gold); background: rgba(217, 158, 20, 0.16); border-color: rgba(217, 158, 20, 0.35); }
.wb-ent-why.none { color: var(--mut); background: var(--sf2); border-color: var(--ln); }

.wb-ent-act { display: flex; gap: 0.3rem; margin-left: auto; flex: 0 0 auto; }
.wb-ent-more { font-weight: 900; letter-spacing: 0.08em; }
.wb-ent-type { color: var(--mut); font-size: 0.72rem; }

/* The picker and the compare card are the entry's own content, so they take the whole line under it
   rather than sitting beside the controls that opened them. */
.wb-picker, .wb-ent-card { flex: 1 1 100%; min-width: 0; }

.wb-picker {
    margin: 0.35rem 0 0.15rem;
    border: 1px solid var(--acc);
    border-radius: 10px;
    background: var(--sf);
    overflow: hidden;
}

.wb-picker-head {
    padding: 0.4rem 0.6rem;
    background: var(--sf2);
    border-bottom: 1px solid var(--ln);
    font-size: 0.78rem;
    font-weight: 800;
    color: var(--tx2);
}

.wb-picker .wb-muted { padding: 0.5rem 0.6rem; margin: 0; }

.wb-picker-row {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    width: 100%;
    padding: 0.32rem 0.6rem;
    border: 0;
    border-bottom: 1px solid var(--ln);
    background: none;
    font-family: inherit;
    font-size: 0.81rem;
    color: var(--tx);
    text-align: left;
    cursor: pointer;
    min-width: 0;
}

.wb-picker-row:last-child { border-bottom: 0; }
.wb-picker-row:hover:not(:disabled) { background: var(--acc-soft); }
.wb-picker-row:disabled { opacity: 0.5; cursor: default; }
.wb-picker-row .nm { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.wb-picker-row .wb-ent-type { margin-left: auto; }
.wb-picker-row.off { color: var(--mut); font-style: italic; }

/* The – entries are the majority by construction. Behind a disclosure so the work is visible rather
   than deleted: each still carries the one click that lets the queue shrink. */
.wb-arr-alone > summary {
    list-style: none;
    cursor: pointer;
    padding: 0.5rem 0.9rem;
    background: var(--sf2);
    font-size: 0.8rem;
    font-weight: 800;
    color: var(--tx2);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.wb-arr-alone > summary::-webkit-details-marker { display: none; }
.wb-arr-alone > summary::after { content: "▾"; font-size: 0.7em; color: var(--mut); }
.wb-arr-alone[open] > summary::after { content: "▴"; }

/* A verdict the reader can act on, in one phrase. The hard refusals are amber and grey rather than
   a small grey tag either way (audit #37) — a veto is a refusal, not a low score. */
.wb-verdict {
    font-size: 0.77rem;
    font-weight: 800;
    color: var(--deal);
    background: var(--deal-bg);
    border: 1px solid var(--deal-brd);
    border-radius: 999px;
    padding: 1px 10px;
}

.wb-verdict.caution { color: var(--gold); background: rgba(217, 158, 20, 0.16); border-color: rgba(217, 158, 20, 0.35); }
.wb-verdict.none { color: var(--mut); background: var(--sf2); border-color: var(--ln); }

.wb-linkish {
    border: 0;
    background: none;
    font-family: inherit;
    font-size: 0.79rem;
    font-weight: 800;
    color: var(--acc);
    cursor: pointer;
    padding: 0;
}

.wb-linkish:hover { text-decoration: underline; }

/* ---- The compare card -------------------------------------------------------------------------
   The one place the layout goes wide, because it is the only place a decision is made. */
.wb-card-row > td {
    padding: 0 8px 10px;
    border-bottom: 1px solid var(--ln);
    background: var(--acc-soft);
}

/* The card sits in a colspan cell of a table that scrolls sideways, so left alone it inherits the
   TABLE's width — over 1000px with five regions and an action column — and on a phone the verdict
   buttons end up past the right edge, reachable only by scrolling the matrix. Pinning it to the
   scroll box's left edge and sizing it to the viewport keeps the ONE surface where a decision is
   made readable without moving. `min()` makes this a no-op on a desktop, where 100% is already the
   narrower of the two. */
.wb-card {
    position: sticky;
    left: 0;
    width: min(100%, calc(100vw - 2.5rem));
    background: var(--sf);
    border: 1px solid var(--ln);
    border-bottom-width: var(--lift);
    border-radius: 14px;
    overflow: hidden;
}

.wb-sides {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: var(--ln);
}

.wb-side { background: var(--sf); padding: 0.8rem 1rem 0.9rem; min-width: 0; }

.wb-side-lab {
    font-family: var(--fn);
    font-size: 0.62rem;
    font-weight: 800;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--mut);
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}

.wb-side-lab .count { color: var(--tx2); letter-spacing: 0.04em; }

.wb-side-body { display: flex; gap: 0.8rem; align-items: flex-start; min-width: 0; }

/* 128px side by side is the whole point of "closer to the game page": two 64px thumbs 300px apart
   cannot be compared, and comparing the pictures is how this question is actually answered. */
.wb-side-cover {
    width: 128px;
    height: 128px;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid var(--ln);
    background: var(--sf2);
    flex: none;
}

.wb-side-cover.empty {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mut);
    font-size: 0.7rem;
    text-align: center;
    padding: 0.4rem;
}

.wb-side-title {
    font-size: 1rem;
    font-weight: 800;
    line-height: 1.25;
    margin: 0 0 0.2rem;
    color: var(--tx);
}

.wb-side-sub {
    font-family: var(--fn);
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--mut);
    letter-spacing: 0.04em;
}

/* What the key read the foreign title as — the one line a reader can genuinely judge, so it is a
   labelled fact rather than a footnote in a tooltip (audit #33). */
.wb-reads { font-size: 0.83rem; color: var(--tx2); margin: 0.3rem 0 0; }

.wb-reads .lab {
    font-family: var(--fn);
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--mut);
    display: block;
}

.wb-reads q { font-style: italic; }

/* ---- The evidence table: a word over every row ------------------------------------------------
   This is what turns `translit 0.62 · cover Δ4 · price ×1.03 · 12d` into something readable without
   training. Same numbers — the phrases are bands over them, and every raw value is reproduced
   verbatim under the developer disclosure below. */
.wb-ev-list { border-top: 1px solid var(--ln); }

.wb-ev {
    display: grid;
    grid-template-columns: 7.5rem 1fr 1fr 1.9rem;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 1rem;
    border-top: 1px solid var(--ln);
    font-size: 0.86rem;
}

.wb-ev-list .wb-ev:first-child { border-top: 0; }

.wb-ev-lab {
    font-family: var(--fn);
    font-size: 0.63rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mut);
}

.wb-ev-a, .wb-ev-b { color: var(--tx); min-width: 0; }
.wb-ev-b { color: var(--tx2); }
.wb-ev .num { font-family: var(--fn); font-variant-numeric: tabular-nums; font-weight: 800; }
.wb-ev .said { color: var(--tx2); font-size: 0.85em; }
.wb-ev.wide .wb-ev-a { grid-column: 2 / 4; color: var(--tx2); }

.wb-mark {
    justify-self: end;
    font-size: 0.82rem;
    font-weight: 900;
    color: var(--deal);
}

.wb-mark.warn { color: var(--gold); }
.wb-mark.bad { color: var(--red); }

/* ---- Counting, stated as a sentence ------------------------------------------------------------
   And it says so when the two numbers do NOT settle it. A tool that admits when it is useless is
   worth more than one that always sounds confident. */
.wb-count {
    display: flex;
    gap: 0.7rem;
    align-items: flex-start;
    padding: 0.55rem 1rem;
    border-top: 1px solid var(--ln);
    background: var(--sf2);
    font-size: 0.85rem;
    color: var(--tx2);
}

.wb-count .lab {
    font-family: var(--fn);
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--mut);
    flex: none;
    width: 6rem;
    padding-top: 0.18rem;
}

.wb-count b { color: var(--tx); font-weight: 800; }
.wb-count .num { font-family: var(--fn); font-variant-numeric: tabular-nums; }
.wb-count .concl { display: block; margin-top: 0.2rem; font-weight: 800; color: var(--deal); }
.wb-count .concl.no { color: var(--mut); }

.wb-note {
    display: flex;
    gap: 0.5rem;
    align-items: flex-start;
    padding: 0.55rem 1rem;
    border-top: 1px solid var(--ln);
    background: rgba(217, 158, 20, 0.16);
    color: var(--gold);
    font-size: 0.84rem;
    font-weight: 700;
}

.wb-note.stop { background: var(--acc-soft); color: var(--red); }

/* Two verdicts, not three. With one operator there is nobody to escalate to, and J/K already means
   "later" — moving on records nothing, which the shortcut sheet says in those words. */
.wb-verdicts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--ln);
}

.wb-vb {
    font-family: inherit;
    font-size: 0.87rem;
    font-weight: 800;
    border-radius: 10px;
    padding: 8px 15px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    border: 1px solid var(--ln);
    background: var(--sf);
    color: var(--tx);
    transition: transform 0.12s, border-color 0.16s;
}

.wb-vb:hover:not(:disabled) { transform: translateY(-1px); }
.wb-vb:disabled { opacity: 0.5; cursor: not-allowed; }

.wb-vb .k {
    font-family: var(--fn);
    font-size: 0.66rem;
    font-weight: 800;
    border: 1px solid currentColor;
    border-radius: 5px;
    padding: 0 5px;
    opacity: 0.55;
}

.wb-vb.yes { background: var(--deal); border-color: var(--deal); color: #fff; }

/* Night's --deal is a mint (#34D399), and white on mint is ~1.6:1. The ink has to flip with the
   fill, in all three dark states the token cascade resolves. */
[data-theme="dark"] .wb-vb.yes { color: #06251A; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) .wb-vb.yes { color: #06251A; }
}

.wb-vb.no:hover:not(:disabled) { border-color: var(--acc); color: var(--acc); }

.wb-disc {
    display: flex;
    flex-wrap: wrap;
    gap: 1.2rem;
    padding: 0 1rem 0.8rem;
}

/* The curated-name editor. Deliberately quiet — closed it is one line of text, because the common
   answer on this card is still one of the two verdicts above it. */
.wb-alias { padding: 0.7rem 1rem 0; }

.wb-alias-body {
    margin-top: 0.55rem;
    display: grid;
    gap: 0.5rem;
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 0.75rem 0.9rem;
}

.wb-alias-hint { margin: 0; font-size: 0.78rem; color: var(--tx2); line-height: 1.5; }

.wb-alias-f { display: grid; gap: 0.25rem; }

.wb-alias-f > span {
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--mut);
}

.wb-alias-f > input {
    font-family: inherit;
    font-size: 0.87rem;
    color: var(--tx);
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 8px;
    padding: 7px 10px;
    width: 100%;
    min-width: 0;
}

.wb-alias-f > input:focus { outline: 2px solid var(--acc); outline-offset: 1px; }
.wb-alias-f > input:disabled { opacity: 0.5; }

.wb-alias-msg { margin: 0; font-size: 0.8rem; font-weight: 700; color: var(--deal); }
.wb-alias-msg.bad { color: var(--red); }

.wb-alias .wb-btn { justify-self: start; }
.wb-alias-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* The gate's verdict. It sits UNDER the buttons rather than replacing them, because the operator's
   next action depends on what it says and both have to be readable at once. */
.wb-gate {
    display: grid;
    gap: 0.4rem;
    border-top: 1px solid var(--ln);
    padding-top: 0.6rem;
}

.wb-gate-err { margin: 0; font-size: 0.82rem; font-weight: 700; color: var(--red); line-height: 1.45; }

.wb-gate-head { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }

/* The verdict word carries the TEXT colour, never the accent one: measured on the light theme, every
   accent in this palette lands between 3.26:1 and 4.33:1 on the card's own surface, i.e. under AA for
   type this size. So the hue moves to the border and a wash of the fill, where being decorative is
   fine, and the word itself sits at --tx (13.8:1 light, higher dark). Colour still says which verdict
   at a glance, and the sentence under it says so in words for anyone the hue does not reach. */
.wb-gate-badge {
    --vc: var(--mut);
    color: var(--tx);
    font-size: 0.7rem;
    font-weight: 900;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--vc) 60%, transparent);
    background: color-mix(in srgb, var(--vc) 14%, transparent);
}

.wb-gate-badge.ok { --vc: var(--deal); }
.wb-gate-badge.warn { --vc: var(--sale); }
.wb-gate-badge.bad { --vc: var(--red); }
.wb-gate-badge.flat { --vc: var(--mut); }

.wb-gate-sub { font-size: 0.74rem; color: var(--mut); font-variant-numeric: tabular-nums; }
.wb-gate-why { margin: 0; font-size: 0.78rem; color: var(--tx2); line-height: 1.5; }

.wb-gate-pair {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.35rem;
    font-size: 0.8rem;
    line-height: 1.5;
}

.wb-gate-plus { font-weight: 900; color: var(--deal); }
.wb-gate-arrow { color: var(--mut); }
.wb-gate-t { color: var(--tx); }
/* What the KEY read — the thing actually being judged, so it is set apart from the store's own text. */
.wb-gate-romaji { color: var(--tx2); font-style: italic; }
.wb-gate-num { color: var(--mut); font-size: 0.74rem; font-variant-numeric: tabular-nums; }

.wb-gate-find { margin: 0; font-size: 0.76rem; color: var(--tx2); line-height: 1.5; }
.wb-gate-find > b { color: var(--tx); }

.wb-gate-frags > summary {
    cursor: pointer;
    font-size: 0.76rem;
    font-weight: 700;
    color: var(--tx2);
}

.wb-gate-frag {
    display: flex;
    gap: 0.5rem;
    font-size: 0.78rem;
    line-height: 1.5;
    padding-top: 0.2rem;
}

.wb-gate-frag.moved .wb-gate-why-code { color: var(--deal); font-weight: 800; }

.wb-gate-why-code {
    flex: 0 0 9.5rem;
    color: var(--mut);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
}

/* An opened disclosure claims the full row, so its body never sits in a narrow column beside the
   other summary. */
.wb-lvl[open] { flex: 1 1 100%; }

.wb-lvl > summary {
    list-style: none;
    cursor: pointer;
    font-size: 0.8rem;
    font-weight: 800;
    color: var(--tx2);
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.wb-lvl > summary::-webkit-details-marker { display: none; }
.wb-lvl > summary::after { content: "▾"; font-size: 0.7em; color: var(--mut); }
.wb-lvl[open] > summary::after { content: "▴"; }

/* Level 2 — the matcher's own words, unchanged. Nothing that was on the old screen is deleted; it
   is relocated, which is how "keep the information, change the language" is satisfied literally. */
.wb-raw {
    margin-top: 0.55rem;
    background: var(--sf2);
    border: 1px solid var(--ln);
    border-radius: 10px;
    padding: 0.7rem 0.9rem;
    font-family: var(--fn);
    font-size: 0.77rem;
    font-variant-numeric: tabular-nums;
    color: var(--tx2);
    overflow-x: auto;
    line-height: 1.7;
}

.wb-raw div { white-space: pre-wrap; overflow-wrap: anywhere; }
.wb-raw b { color: var(--tx); font-weight: 800; }
.wb-raw .hd { color: var(--mut); }

.wb-alts { margin-top: 0.55rem; display: grid; gap: 0.45rem; }

.wb-alt {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--ln);
    border-radius: 10px;
    background: var(--sf2);
    font-size: 0.84rem;
}

.wb-alt .nm { font-weight: 700; flex: 1 1 10rem; min-width: 0; }

.wb-alt .amt {
    font-family: var(--fn);
    font-variant-numeric: tabular-nums;
    font-weight: 800;
    color: var(--tx2);
}

/* ---- The journal, in the past tense ------------------------------------------------------------ */
.wb-jrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--ln);
    font-size: 0.85rem;
}

.wb-jrow:last-child { border-bottom: none; }

.wb-when {
    font-family: var(--fn);
    font-size: 0.75rem;
    color: var(--mut);
    font-weight: 700;
    flex: none;
    min-width: 5rem;
}

.wb-kind {
    font-family: var(--fn);
    font-size: 0.63rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 1px 8px;
    border-radius: 6px;
    background: var(--sf2);
    border: 1px solid var(--ln);
    color: var(--tx2);
    flex: none;
}

.wb-kind.join { color: var(--deal); background: var(--deal-bg); border-color: var(--deal-brd); }
.wb-kind.sep { color: var(--acc); background: var(--acc-soft); border-color: var(--acc-soft); }

.wb-jwhat { color: var(--tx2); flex: 1 1 14rem; min-width: 0; }
.wb-jwhat b { color: var(--tx); font-weight: 700; }

/* An nsuid key is one unbreakable 14-character token and a flex item will not shrink below its
   content, so left alone these push the PAGE wider than a phone. The matrix has its own scroll box;
   nothing else on this page may scroll the body sideways. */
.wb-jwhat code {
    overflow-wrap: anywhere;
    word-break: break-all;
    font-size: 0.75rem;
}

.wb-bulkbar {
    position: sticky;
    bottom: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
    background: var(--sf);
    border: 1px solid var(--acc);
    border-radius: 12px;
    padding: 0.55rem 0.85rem;
    margin-top: 0.75rem;
    font-size: 0.85rem;
    font-weight: 700;
    z-index: 2;
}

.wb-panel {
    background: var(--sf);
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 0.5rem 0.75rem;
    min-width: 0;
}

.wb-hit {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 0;
    border-bottom: 1px solid var(--ln);
    font-size: 0.82rem;
    flex-wrap: wrap;
}

.wb-hit .wb-name { flex: 1 1 12rem; min-width: 0; }

.wb-hit:last-child { border-bottom: none; }
.wb-hit .wb-btn { margin-left: auto; }
.wb-hit .wb-btn + .wb-btn { margin-left: 0; }

/* An nsuid key is one unbreakable 14-character token, and a flex item will not shrink below its
   content — so on a phone these rows pushed the PAGE wider than the viewport rather than wrapping.
   The matrix has its own scroll box; nothing else on this page may scroll the body sideways. */
.wb-hit code {
    overflow-wrap: anywhere;
    word-break: break-all;
    min-width: 0;
    font-size: 0.75rem;
}

.wb-result { color: var(--tx2); font-size: 0.75rem; font-family: var(--fn); }
.wb-result.bad { color: var(--red); font-weight: 800; }

.wb-details {
    margin-top: 1.5rem;
    border-top: 1px solid var(--ln);
    padding-top: 0.75rem;
}

.wb-details > summary {
    cursor: pointer;
    font-weight: 800;
    color: var(--tx2);
    font-size: 0.9rem;
}

@media (max-width: 720px) {
    .wb-title { min-width: 180px; }
    .wb-hint { display: none; }
    .wb-th-act { width: auto; }

    /* The card is the one wide thing on the page, so it is the one thing that has to fold. Two
       128px covers side by side do not fit a phone; stacked they still compare, which is the job. */
    .wb-sides { grid-template-columns: 1fr; }

    /* Label on its own line, then the two sides stacked — a 7.5rem label column beside two values
       leaves each value about eight characters wide on a 390px phone. */
    .wb-ev { grid-template-columns: 1fr 1.9rem; gap: 0.15rem 0.6rem; }
    .wb-ev .wb-ev-lab { grid-column: 1 / 3; }
    .wb-ev .wb-ev-a,
    .wb-ev .wb-ev-b { grid-column: 1; }
    .wb-ev.wide .wb-ev-a { grid-column: 1; }
    .wb-mark { grid-column: 2; grid-row: 2; }

    .wb-count { flex-direction: column; gap: 0.2rem; }
    .wb-count .lab { width: auto; padding-top: 0; }
    .wb-qpos { margin-left: 0; }
    .wb-side-cover { width: 96px; height: 96px; }
}

/* ---- Chip icon slot (.ri) --------------------------------------------------------------------
   A leading emoji in a chip (🔥 sale / 📍 region / 📉 history) is drawn from the OS emoji font,
   whose vertical metrics we do NOT control — the font-metric overrides in fonts.css cannot reach
   it. Left inline with the label it does two visible things: it is taller than the text, so it
   grows the shared line box and drags the flex centring UP (the label then sits high in a filled
   pill); and its side bearings are wider than a Latin glyph's, so the content looks pushed right
   (measured 3.4px of left-vs-right imbalance on the sale pill).

   Giving the icon its own fixed box takes it out of the label's line box altogether: the label
   centres on its own font, and the icon centres inside a slot of known width. `height: 1em` with
   visible overflow means a tall glyph paints freely but cannot stretch the row it lives in.
   Global (not scoped) so every component's chips share one definition. */
.ri {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.15em;
    height: 1em;
    line-height: 1;
    overflow: visible;
    /* the emoji is decoration; the chip's words already say it */
    -webkit-user-select: none;
    user-select: none;
}

/* ================= "Is a new account worth it?" (/accounts) =================
   The cumulative answer, and the reason the two per-game EUR sliders could be deleted: a single game can
   never justify an account, so the judgement had to move somewhere the sum exists. Everything here is
   built to be CHECKED — every total names its base and links one worked example. */
.acc-lede {
    max-width: 62ch;
    margin: -2px 0 14px;
    line-height: 1.5;
}

.acc-base {
    font-size: 0.82rem;
    color: var(--mut);
    margin: 0 0 18px;
}

.acc-warn {
    color: var(--warn);
    font-weight: 700;
    margin-left: 6px;
}

.acc-h {
    font-size: 1.02rem;
    font-weight: 800;
    margin: 22px 0 2px;
}

.acc-sub,
.acc-none {
    font-size: 0.82rem;
    color: var(--mut);
    margin: 0 0 10px;
    max-width: 62ch;
    line-height: 1.45;
}

.acc-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.acc-row {
    display: flex;
    align-items: center;
    gap: 12px;
    border: 1px solid var(--ln);
    border-radius: 12px;
    padding: 10px 12px;
    background: var(--sf);
}

/* An account they already hold is a different kind of row — it answers "is this earning its keep" — so it
   reads as settled rather than as an offer. */
.acc-row.owned {
    background: var(--sf2);
}

.acc-rc {
    flex: 0 0 auto;
    font-family: var(--fn);
    font-size: 0.82rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    border: 1px solid var(--ln);
    border-radius: 7px;
    padding: 5px 8px;
    background: var(--bg);
    color: var(--tx);
}

/* A SHOP candidate — the free step. Same slot as the region code, because the rows are read down a
   column and what identifies them has to sit in one place; not the same object, because a sign-up is not
   a place: no code chip, the shop's own name, and the accent that means "a shop" everywhere else on the
   site (the panel's chips, the game page's via tags). */
.acc-shop {
    flex: 0 0 auto;
    font-family: var(--fn);
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    border: 1px solid color-mix(in srgb, var(--acc) 55%, transparent);
    border-radius: 7px;
    padding: 5px 8px;
    color: color-mix(in srgb, var(--acc) 75%, var(--tx));
}

/* 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.86px high**, the worst on the
   site, and it is a SHOP plaque wearing the same accent as the game page's via tags — which the comment
   above already says. `.ns-beta` further up this file is the one member left out on purpose: it was
   tuned by hand to `2.24px 6px 1.92px` before this rule existed and is already within 0.16px. */
@supports (text-box: trim-both cap alphabetic) and (padding: calc(19px - 1cap)) {
    .acc-shop {
        text-box: trim-both cap alphabetic;
        padding-block: calc(19px - 1cap);
    }
}

.acc-body {
    flex: 1 1 auto;
    min-width: 0;
}

/* How many regions the row's representative speaks for. A chip and not a second code: the codes are what
   this column is read DOWN, and a second one in the same slot would break that. */
.acc-rc-more {
    margin-left: 5px;
    font-size: 0.68rem;
    font-weight: 700;
    color: var(--mut);
}

/* WHERE a shop sign-up would help — the shop row's own "which regions", since a sign-up is not a place. */
.acc-where {
    margin-top: 2px;
    font-family: var(--fn);
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    color: var(--mut);
}

/* The SECOND ask on a region row, where its saving needs a shop as well as the eShop account. Under the
   total rather than beside it: the heading above this list says "accounts you could open", so the extra
   step is a qualification on the row and reads as one. */
.acc-second {
    margin-top: 2px;
    font-size: 0.78rem;
    color: var(--mut);
}

.acc-total {
    display: flex;
    align-items: baseline;
    gap: 7px;
    font-size: 1.02rem;
    font-weight: 800;
    color: var(--deal);
    font-variant-numeric: tabular-nums;
}

.acc-games {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--mut);
}

.acc-best {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-top: 3px;
    font-size: 0.78rem;
    color: var(--mut);
    text-decoration: none;
    min-width: 0;
}

.acc-best:hover .acc-best-title {
    text-decoration: underline;
}

.acc-best-title {
    color: var(--tx2);
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.acc-best-amt {
    color: var(--deal);
    font-weight: 700;
    flex: 0 0 auto;
}

.acc-mark {
    flex: 0 0 auto;
    border: 1px solid color-mix(in srgb, var(--acc) 45%, transparent);
    border-radius: 999px;
    padding: 5px 12px;
    background: color-mix(in srgb, var(--acc) 10%, transparent);
    color: var(--acc);
    font-family: inherit;
    font-size: 0.76rem;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
}

.acc-have {
    flex: 0 0 auto;
    font-size: 0.74rem;
    font-weight: 700;
    color: var(--mut);
    white-space: nowrap;
}

.acc-load {
    color: var(--mut);
    padding: 18px 0;
}

.acc-retry,
.acc-cta {
    display: inline-block;
    margin-left: 8px;
    border: 1px solid var(--ln);
    border-radius: 999px;
    padding: 4px 12px;
    background: var(--bg);
    color: var(--acc);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    text-decoration: none;
    cursor: pointer;
}

@media (max-width: 620px) {
    .acc-row {
        flex-wrap: wrap;
    }

    .acc-body {
        flex: 1 1 100%;
        order: 3;
    }
}

/* ---- The one-shot nudge that makes all of the above mean anything. Every "your price" on the site is the
   best of the accounts this reader has marked, and marking them used to be a checkbox in a sheet with
   nothing to say what it was for — so a reader with three accounts got the same answers as one with
   none. Dismissible for good: a prompt that returns is an advertisement. ---- */
/* WHOSE PRICES THESE ARE (HomeRegionNotice) — deliberately NOT built like .acc-prompt below it.
   That one is a QUESTION: bordered, tinted with the accent, dismissible. This one is a LABEL on the data
   under it, true for as long as it is shown and self-cancelling the moment a region is named, so it gets
   a line's weight and no chrome at all. A reader must be able to tell at a glance which of the two is
   asking them for something — and only one of them can ever be on screen, so the difference is carried by
   the object itself rather than by their being side by side. */
.home-notice {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 0 12px;
    font-size: 0.82rem;
    line-height: 1.45;
    color: var(--mut);
}

.home-notice .hn-text {
    flex: 0 1 auto;
    min-width: 0;
}

/* A link, not a button: the action is "go and look", the same weight as the sentence it ends. */
.home-notice .hn-cta {
    flex: 0 0 auto;
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: 700;
    color: var(--acc);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.home-notice .hn-cta:hover {
    text-decoration-thickness: 2px;
}

/* The "I know" exit, on the mode variant only. Pushed to the far end by `margin-left: auto` so it can
   never be mistaken for part of the sentence, and sized as a TAP TARGET rather than as a glyph: at
   0.82rem the ✕ itself is ~10px, which is a third of what a thumb needs. Muted until touched — it is
   the least important thing on the line, and a notice whose loudest element is its own dismissal is
   asking to be dismissed. */
.home-notice .hn-close {
    flex: 0 0 auto;
    margin-left: auto;
    align-self: center;
    border: 0;
    background: none;
    /* 6/8 and not 4/6: measured at the smaller inset the box came out 22.7 × 21.1, under the 24px a
       thumb needs, on a control whose whole job is to be tapped once and never thought about again.
       The negative margin gives the padding back to the layout so the GLYPH still lines up with the
       column edge rather than the box does. */
    padding: 6px 8px;
    margin-right: -8px;
    font: inherit;
    line-height: 1;
    color: var(--mut);
    opacity: 0.7;
    cursor: pointer;
}

.home-notice .hn-close:hover,
.home-notice .hn-close:focus-visible {
    color: var(--tx);
    opacity: 1;
}

.acc-prompt {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    border: 1px solid color-mix(in srgb, var(--acc) 30%, var(--ln));
    border-radius: 12px;
    padding: 10px 12px;
    margin: 0 0 14px;
    background: color-mix(in srgb, var(--acc) 6%, transparent);
}

.acc-prompt-text {
    flex: 1 1 22ch;
    min-width: 0;
    font-size: 0.84rem;
    line-height: 1.45;
    color: var(--tx2);
}

.acc-prompt-cta {
    border: 1px solid color-mix(in srgb, var(--acc) 45%, transparent);
    border-radius: 999px;
    padding: 5px 12px;
    background: var(--bg);
    color: var(--acc);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
}

.acc-prompt-no {
    border: 0;
    background: none;
    color: var(--mut);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
}

/* ============================================================================
   Feed list column geometry (variant D, wide desktop). Declared HERE, on the
   container, because two different components read it: every `GameRow`'s price
   pair and the `FeedListHeader` above them. One source of truth, or the header
   drifts off the columns it names by a pixel per release.
   ========================================================================== */
/* ON :root, not on .games-list — the header is a SIBLING of the list, not a descendant, so a variable
   declared on the list did not resolve for it: the whole `grid-template-columns` was dropped as invalid
   and the two labels stacked on top of each other in one auto column. Found by rendering. The point of
   these being variables at all is that both readers see the same numbers, which only holds from a common
   ancestor.

   FOUR widths, not two, and every one of them MEASURED rather than guessed. The cells are
   `justify-self: end`, so a cell wider than its track does not stretch the track — it overflows to the
   LEFT, into the column beside it, which is exactly what "the layout is all mixed up" looked like. Read
   off the live feed (natural widths over 25 rows, EUR display): label 47, amount 125 — a sale row carries
   price + struck-through was + saving — second label 59 with its key glyph, second amount 86. Each track
   is that plus headroom.

   The two sides are deliberately NOT the same width: your own price can be on sale and carry three
   things, the invitation never carries a "was".

   Known limit, stated rather than hidden: a display currency with long amounts (HUF, "39 990 Ft") can
   still exceed these. It degrades by pushing left into the title's `minmax(0, 1fr)` rather than by
   clipping a price, and the real fix if it ever matters is `subgrid` on the list, which would size these
   columns from the widest row instead of from a constant. */
:root {
    --pp-lbl: 54px;
    /* 136 held price + struck-through was + a bare percentage; the baseline label ("vs DE") measures a
       further 33px, and a percentage without its baseline was the second unlabelled number on the row. */
    --pp-amt: 184px;
    --pp-lbl2: 66px;
    --pp-amt2: 100px;
    /* pair 356 + the widest reason chip 106 + gaps + the two row actions. Measured, like the tracks. */
    --row-right-w: 638px;

    /* —— and the SAME geometry for a reader who has never named a home region (`.game-row.one-col`).
       There is no second column for them at all: with no home there is no reach split, so no invitation
       and no "at home" figure can ever exist — and the amount track can be much narrower too, because
       the "−50% vs DE" caption that sized it is a comparison against a home region they do not have.
       Measured on prod's deals list in that state: widest amount cell 101px (price + struck-through was),
       median 84 — against the 184px reserved. 132 clears the widest currency the display map can print
       ("39 990 Ft" twice) with room to spare.
       What this is worth: the right cluster goes 638 → 380px on a 1166px row, so the price ends near the
       row's edge instead of 300px short of it, with a column of em-dashes behind it. */
    --pp-amt1: 132px;
    --row-right-w-1: 380px;
}

/* The header exists only where the columns do. Below this width the pair is two stacked rows, each
   carrying its own region chip as a label, so a header would name columns that are not there. */
.feed-cols {
    display: none;
}

@media (min-width: 1180px) {
    .feed-cols {
        display: grid;
        grid-template-columns: minmax(0, 1fr) var(--row-right-w);
        gap: 14px;
        /* The row's own horizontal padding, so the labels sit over the columns and not beside them. */
        padding: 5px 14px 4px;
        border-bottom: 1px solid var(--ln);
        /* No tint: the list's rows are white on the page's own warm ground, and a grey band above them
           reads as a toolbar rather than as a heading. The rule alone separates it. */
    }

    .feed-cols .fc-labels {
        display: grid;
        /* The same four columns as the row, so each label sits over the amounts it describes. The
           GROUP is right-aligned, like the row's own pair inside its wider cell, and each label is
           then start-aligned within its two tracks — see .fc below. */
        grid-template-columns: var(--pp-lbl) var(--pp-amt) var(--pp-lbl2) var(--pp-amt2);
        justify-content: end;
    }

    /* Over the LEFT edge of the pair it names, because that is where its content starts: the amount
       cells are `justify-self: start` (see GameRow.razor.css), so a right-aligned heading would sit
       over the empty tail of the track instead of over the price. */
    .feed-cols .fc {
        grid-column: span 2;
        justify-self: start;
        padding-left: 6px;
        font-family: var(--fn);
        font-size: 0.56rem;
        font-weight: 800;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--mut);
        white-space: nowrap;
    }

    .feed-cols .fc.open {
        color: var(--mut);
        opacity: 0.85;
    }

    /* The baseline the column's percentages are measured against — the region name, so NOT uppercased
       and not letter-spaced like the heading it hangs off: a code is already caps, and the tracking a
       heading needs makes a two-letter code read as two words. It is a qualifier, so it is quieter than
       the heading, the same relation `.fy-vs` has to `.fy-pct` in the row it replaces at this width. */
    .feed-cols .fc .fc-vs {
        margin-left: 0.42em;
        text-transform: none;
        letter-spacing: 0.01em;
        font-weight: 600;
        opacity: 0.8;
    }
}

/* The home row's gift-card tag. Same pill as the account tag beside it, tinted with the ACCENT rather
   than the region colour: the two say different things — "I have an eShop account here" is a fact about
   the region, "I buy cards for it" is a fact about how this reader pays — and one colour for both would
   read as two halves of one setting. */
.ns-rgn-gc.on {
    background: var(--acc-soft);
    color: var(--acc);
}

.ns-rgn-gc:hover {
    border-color: color-mix(in srgb, var(--acc) 55%, transparent);
    background: color-mix(in srgb, var(--acc) 10%, transparent);
    color: var(--acc);
}

.ns-rgn-gc.on:hover {
    background: color-mix(in srgb, var(--acc) 22%, transparent);
}

/* …and the same mark in the header, where it is the only sign of a setting that moves every price of
   the reader's own region. Sized down to sit inside a 0.78rem label without pushing the row taller. */
.ns-ctx-gc {
    display: inline-flex;
    align-items: center;
    font-size: 0.72em;
    line-height: 1;
}

/* The middle reading's half on the TILE used to be styled from here, as `.card-strip.mine` — a
   region-tinted GROUND under the strip plus a region-coloured chip. Both rules are GONE with the strip
   itself (2026-09-02: the tile is two rows and the half moved into the status line as `.sa`), and they
   are not restated here: a ground needs a strip to tint, and the chip's rule has to out-specify the
   tile's own scoped `.rc`, which only a scoped rule can do. It lives in `GameCard.razor.css` beside the
   rest of `.sa` — the same "scoped CSS cannot share a rule" split `.rc` itself is restated under.
   What the two surfaces still say in ONE voice is the DISTINCTION: the invitation's chip is dashed
   because something is being asked for, the reader's own route is solid and region-coloured because
   nothing is. See docs/PRICE_SURFACES.md § "Two rows under the title".

   The middle reading's own second column in the list (`.pp-lbl.mine` / `.pp-amt.mine`) is DELETED with
   that reading, 2026-09-07 — see PriceFacts.Row. It drew the solid region-coloured chip that told "a
   route you already hold" from "one you would have to open"; with only the invitation left in that slot
   there is no second, solid thing for it to be told apart from, so the pair goes rather than standing as
   a rule that matches nothing. */
