/* 桌面版式 · 公共框架（docs/specs/2026-10-08-web-desktop-ux-design.md）；全部规则在 900px / hover 媒体查询里。
   The desktop layout's frame: tokens, the top navigation that replaces the tab bar, the page header bar (the phone's
   .nav), the content width (--page-w), the fixed bottom bars, the centred dialogs, and hover / focus / cursor for the
   shared parts of base.css. Below 900px nothing here applies (the phone stays as prd/ draws it); the only rules
   outside a media query hide what base.html adds for the desktop (.topnav, .skip-link). Page lines write their pages'
   desktop rules in desktop-content.css and desktop-account.css, loaded after this file. */

/* ---------------------------------------------------------------- every width: what base.html adds stays hidden */
.topnav { display: none; }
.skip-link { position: absolute; left: -9999px; top: 0; z-index: 100; }
.skip-link:focus { left: 16px; top: 8px; padding: 8px 14px; border-radius: 8px; background: var(--panel-2);
  color: var(--text); font-size: 15px; outline: 2px solid #4da3ff; outline-offset: 0; }

/* ---------------------------------------------------------------- tokens (design §2) */
@media (min-width: 900px) {
  :root {
    --top-h: 56px;               /* the top navigation */
    --bar-h: 48px;               /* the page header bar (the phone's .nav) */
    --page-w: 960px;             /* the content's width; a page sets its own on body (560 / 720 / 960 / 1280) */
    --gutter: 24px;              /* the least room between the window's edge and the content */
    --focus: #4da3ff;            /* the focus ring: 8.0:1 on black, 6.5 on #1c1c1e, 5.3 on #2c2c2e */
    --hover: rgba(255, 255, 255, 0.06);  /* a row's or a card's hover ground */
    --hover-2: #3a3a3c;          /* a grey button's (#2c2c2e) hover ground */
    --line-2: #48484a;           /* a 1px divider */
    --text-2d: #aeaeb2;          /* secondary text: 6.3:1 on #2c2c2e (the phone's #8e8e93 is 4.27) */
    --blue-d: #0a6fe0;           /* the primary button's ground: white text 4.8:1 (#0a84ff is 3.65) */
    --red-d: #ff6961;            /* red text on grey: 4.9:1 on #2c2c2e */
    --radius-l: 14px;
    --radius-m: 10px;
    --radius-s: 8px;
    --shadow-pop: 0 24px 64px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.08);
  }
  /* the fixed area at the top: the navigation and, on most pages, the header bar; base.css, admin.css, decks.css,
     library.css and scan.css place things under it with --nav-h */
  body.has-topnav { --nav-h: calc(var(--top-h) + var(--bar-h)); }
  body.has-topnav:not(:has(> header.nav)) { --nav-h: var(--top-h); }
}

/* ---------------------------------------------------------------- the top navigation (design §4) */
@media (min-width: 900px) {
  .topnav { display: block; position: fixed; inset: 0 0 auto 0; z-index: 30; height: var(--top-h); background: #111;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
  .tn-in { height: 100%; max-width: 1280px; margin: 0 auto; padding: 0 var(--gutter); display: flex; align-items: center;
    gap: 16px; }
  .tn-brand { flex: none; margin-right: 8px; font-size: 22px; font-weight: 600; line-height: 1; letter-spacing: 0.01em;
    color: #16a2fa; background: linear-gradient(180deg, #0aa8f6, #2a9cfb); -webkit-background-clip: text;
    background-clip: text; -webkit-text-fill-color: transparent; }
  .tn-links, .tn-side { flex: none; display: flex; align-items: center; gap: 4px; list-style: none; margin: 0; padding: 0; }
  .tn-links a, .tn-side a { position: relative; display: inline-flex; align-items: center; gap: 6px; height: 36px;
    padding: 0 12px; border-radius: 8px; color: #aeaeb2; font-size: 15px; white-space: nowrap; }
  .tn-links a.on, .tn-side a.on { color: #fff; font-weight: 600; }
  /* the current entry: a blue line on the navigation's bottom edge */
  .tn-links a.on::after, .tn-side a.on::after { content: ""; position: absolute; left: 12px; right: 12px; bottom: -10px;
    height: 2px; border-radius: 1px; background: var(--blue); }
  .tn-side svg { width: 20px; height: 20px; flex: none; fill: currentColor; }
  .tn-search { position: relative; flex: 1 1 360px; min-width: 160px; max-width: 420px; margin-left: auto; height: 36px;
    display: flex; align-items: center; gap: 8px; padding: 0 4px 0 14px; border-radius: 18px; background: #1c1c1e;
    border: 1px solid #3a3a3c; cursor: text; }
  .tn-search input { flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; background: transparent; color: #fff;
    font-size: 14px; }
  /* the hint, under the empty box (a placeholder's look: #8e8e93, 5.9:1); gone once a word is typed */
  .tn-hint { position: absolute; left: 15px; right: 40px; top: 50%; transform: translateY(-50%); color: #8e8e93;
    font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
  .tn-search:has(input:valid) .tn-hint { display: none; }
  .tn-search input::-webkit-search-cancel-button { filter: invert(0.6); }
  .tn-search:focus-within { border-color: var(--focus); box-shadow: 0 0 0 3px rgba(77, 163, 255, 0.25); }
  .tn-search button { flex: none; width: 30px; height: 30px; display: inline-flex; align-items: center;
    justify-content: center; border-radius: 50%; color: var(--blue); }
  .tn-search svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.2; }
  .tn-avatar { flex: none; width: 28px; height: 28px; border-radius: 50%; background: var(--panel-2); color: #fff;
    font-size: 13px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; }
  .tn-side a.on .tn-avatar { box-shadow: 0 0 0 2px var(--blue); }
  .tn-name { max-width: 6em; overflow: hidden; text-overflow: ellipsis; }
  .tn-live { width: 6px; height: 6px; border-radius: 50%; background: var(--red); margin-left: 2px; }
  .topnav a:focus-visible, .topnav button:focus-visible { outline-offset: -2px; }

  /* the tab bar gives way to the top navigation (the same entries, the same current one) */
  .tabbar { display: none; }
  body.has-tabbar.has-topnav .page { padding-bottom: 0; }
  /* one search box in view: the home page has its big one; /search and /card have the same one in their header bar */
  body.home .tn-search,
  body.has-topnav:has(> header.nav .nav-search[action="/search"]) .tn-search { visibility: hidden; }
}
/* 900–1199: 商城 / 购物车 / the name show their icon only (aria-label and title still name them) */
@media (min-width: 900px) and (max-width: 1199px) {
  .tn-side a > span:not(.tn-avatar):not(.tn-live) { position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; }
  .tn-side a { padding: 0 8px; }
  .tn-side a.on::after { left: 8px; right: 8px; }
}

/* ---------------------------------------------------------------- the page header bar (design §5) */
@media (min-width: 900px) {
  body.has-topnav .page { padding-top: var(--nav-h); }
  body.has-topnav.viewing .page { padding-top: calc(var(--nav-h) + 42px); }
  body.has-topnav .nav { top: var(--top-h); height: var(--bar-h); padding-top: 0; gap: 12px;
    padding-inline: max(16px, calc(50% - var(--page-w) / 2 + 16px));  /* the content's own 16px inset */ background: #000;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
  /* 返回 with its word; the house goes (the navigation has 首页) */
  body.has-topnav .nav .capsule { position: static; flex: none; width: auto; height: 32px; border-radius: 16px;
    border-color: #3a3a3c; overflow: hidden; }
  body.has-topnav .nav .capsule .sep, body.has-topnav .nav .capsule a:last-child:not(:first-child) { display: none; }
  body.has-topnav .nav .capsule a[data-back] { flex: none; gap: 4px; padding: 0 12px 0 8px; }
  body.has-topnav .nav .capsule a[data-back]::after { content: attr(aria-label); font-size: 14px; color: #d3d3d3; }
  body.has-topnav .nav .capsule a:focus-visible { outline-offset: -2px; border-radius: 16px; }
  body.has-topnav .nav .title { margin: 0; max-width: none; min-width: 0; flex: 0 1 auto; font-size: 17px;
    font-weight: 600; letter-spacing: 0; text-align: left; }
  body.has-topnav .nav .nav-search { position: static; flex: 0 1 560px; min-width: 0; height: 36px; border-radius: 18px;
    border: 1px solid transparent; font-size: 14px; cursor: text; }
  body.has-topnav .nav .nav-search input { height: 100%; font-size: 14px; }
  body.has-topnav .nav .nav-search:focus-within { border-color: var(--focus); box-shadow: 0 0 0 3px rgba(77, 163, 255, 0.25); }
  /* an action at the right end (识别结果's 拍照): a pill with its word */
  body.has-topnav .nav .nav-cam { position: static; flex: none; width: auto; height: 32px; margin-left: auto; gap: 6px;
    padding: 0 12px 0 10px; border-radius: 16px; border: 1px solid #3a3a3c; color: #d3d3d3; font-size: 14px; }
  body.has-topnav .nav .nav-cam::before { display: none; }
  body.has-topnav .nav .nav-cam::after { content: attr(aria-label); }
  /* what base.css puts at the top of a page without the phone's nav goes under the fixed area */
  body.has-topnav .view-banner { top: var(--nav-h); }
  body.has-topnav .site-note { top: calc(var(--nav-h) + 8px); left: 50%; right: auto; transform: translateX(-50%);
    width: min(720px, calc(100% - 48px)); }
}

/* ---------------------------------------------------------------- the content's width (design §3) */
@media (min-width: 900px) {
  /* :where keeps this at main.page's own weight, so a page's margin (decks.css: body.dka-open .page) still wins */
  :where(body.has-topnav) main.page { max-width: var(--page-w); margin-inline: auto; }
  /* the bars fixed at the bottom: as wide as the content, centred (50% of a fixed box is the viewport without its
     scroll bar, so a Windows scroll bar does not shift it) */
  body.has-topnav :is(.actionbar, .dk-bar, .lib-actionbar, .col-selbar, .cd-bar) {
    left: max(0px, calc(50% - var(--page-w) / 2)); right: max(0px, calc(50% - var(--page-w) / 2));
    border-radius: var(--radius-l) var(--radius-l) 0 0; box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.08); }
  /* the deck page with its 加牌 panel open: the page moves right of the panel (decks.css), its bar with it */
  body.has-topnav.dka-open .dk-bar { left: 0; right: 0; border-radius: 0; }
  /* the 加牌 panel starts under the navigation */
  body.has-topnav .dka { top: var(--top-h); }
  /* the notice at the foot of every page */
  body.has-topnav .fcp { max-width: 960px; margin-inline: auto; font-size: 12px; }
  /* a toast: no tab bar under it any more; above a bottom bar where the page has one */
  body.has-topnav:not(.has-actionbar):not(.selecting) .toast { bottom: 32px; }
  body.has-topnav:not(.has-actionbar):not(.selecting) .toast-undo ~ .toast { bottom: 82px; }
  /* Tab never leaves a focused row under the fixed bars (design §13.3: 56 + 48 + 12; --nav-h is on body) */
  html:has(> body.has-topnav) { scroll-padding-top: 116px; scroll-padding-bottom: 88px; }
}

/* ---------------------------------------------------------------- bottom sheets become centred dialogs (design §9)
   Every sheet is a <dialog> opened with showModal(); only its place changes. A click on the backdrop lands on the
   dialog itself (event.target === dialog closes it), so the dialog is exactly as big as what it shows: height
   fit-content, and the sheets drawn by an inner .sheet-panel (sort, set picker) keep a transparent dialog of the
   panel's size. The version picker (.vp, versionpicker.css) and the zoomed picture (.hd-zoom) have their own. */
@media (min-width: 900px) {
  dialog:is(.add-sheet, .adm-sheet, .filter-sheet, .sort-sheet, .dk-sheet, .col-dialog, .set-picker, .dka-filters) {
    inset: 0; margin: auto; width: min(var(--dlg-w, 560px), calc(100% - 48px)); max-width: none; height: fit-content;
    max-height: min(84vh, 780px); padding-bottom: 0; border-radius: var(--radius-l); box-shadow: var(--shadow-pop); }
  dialog:is(.add-sheet, .adm-sheet, .filter-sheet, .sort-sheet, .dk-sheet, .col-dialog, .set-picker, .dka-filters)::backdrop {
    background: rgba(0, 0, 0, 0.6); }
  dialog:is(.sort-sheet, .set-picker) { box-shadow: none; }  /* the panel inside draws the edge */
  dialog:is(.sort-sheet, .set-picker) .sheet-panel { max-height: min(84vh, 780px); padding-bottom: 0;
    border-radius: var(--radius-l); box-shadow: var(--shadow-pop); }
  dialog:is(.sort-sheet, .filter-sheet, .dka-filters) .pop-head { border-radius: var(--radius-l) var(--radius-l) 0 0; }
  /* the foot's buttons: grouped at the right, secondary then primary, each as wide as its word */
  dialog .pop-foot { justify-content: flex-end; padding-bottom: 12px; }
  dialog .pop-foot > * { flex: 0 0 auto; min-width: 120px; padding-inline: 20px; }
  /* widths (--dlg-w): menus 360, confirmations and short forms 480, forms 560, choosing and filters 640, the big
     ones 720; the same weight for all (:where), the later line wins (the scan page's #add-sheet is a .fb-sheet) */
  dialog:where(#dk-menu) { --dlg-w: 360px; }
  dialog:where(.adm-sheet, #dk-edit, #col-dialog, .sort-sheet, #lib-save-sheet, #lib-add-sheet, #cdk-move) { --dlg-w: 480px; }
  dialog:where(#add-sheet, .take-sheet, #dl-new, #dk-export, #dk-pick, #col-fill, .cd-roles) { --dlg-w: 560px; }
  dialog:where(.filter-sheet, .set-picker, .dka-filters) { --dlg-w: 640px; }
  dialog:where(#dk-stats, .fb-sheet, .find-sheet) { --dlg-w: 720px; }
  /* the advanced search's choice sheets (_criteria_form.html: a div[role=dialog] over the page) */
  body.has-topnav .sheet { align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.6); }
  body.has-topnav .sheet > .sheet-panel { width: min(480px, calc(100% - 48px)); max-height: min(84vh, 780px);
    padding-bottom: 0; border-radius: var(--radius-l); box-shadow: var(--shadow-pop); }
  body.has-topnav .sheet .sheet-head { border-radius: var(--radius-l) var(--radius-l) 0 0; }
}

/* ---------------------------------------------------------------- focus, cursor, disabled (design §11.2–11.4, §12) */
@media (min-width: 900px) {
  /* the keyboard's ring everywhere (a mouse click shows none); rows cut by a rounded card draw it inside */
  :focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .setting-row:focus-visible { outline-offset: -2px; }
  /* a box drawn by a sibling of its hidden input (the switch) shows the ring on the drawn box */
  .switch:has(input:focus-visible) span { outline: 2px solid var(--focus); outline-offset: 2px; }
  .switch input:focus-visible { outline: 0; }
  /* contrast on the computer (design §12): the primary button's blue, a segmented control's off state */
  .btn-primary { background: var(--blue-d); }
  .seg :is(a, button):not(.on) { color: var(--text-2d); }
  .state-tag { cursor: default; }
}

/* ---------------------------------------------------------------- contrast, every page (design §12; review 2026-10-08:
   the tokens of §2 reached only a few shared parts, 265 texts on 60 pages stayed under WCAG AA at 1440). The greys as
   tokens: secondary #8e8e93 → #aeaeb2 (6.3:1 on #2c2c2e), tertiary #5b5b5d → #8e8e93 (6.4 on black; disabled words
   still the dimmest); the grey chip under white words #8e8e93 → #636366 (6.0). Coloured grounds under white words
   are named one by one (the same blue and red also draw lines, dots and blue words, which keep the phone's colour):
   blue → --blue-d (4.8), #3989e8 tags → #2f6fd0 (4.9), red → #d70015 (5.4), green → #1a7f37 (5.1); blue words on grey
   → --focus #4da3ff (5.3), red words on grey → --red-d (4.9) or #ff8a84 on #3a3a3c (5.0). Below 900px nothing changes. */
@media (min-width: 900px) {
  :root { --text-2: #aeaeb2; --text-3: #8e8e93; --gray-chip: #636366; }
  .seg .on, .sets-ctl .seg a.on, .chip.on, a.chip.on, .dl-mine .chip.on, .chip.lib-ci.on, .dl-chip, .btn-shoot.im-pick,
  .dl-format input:checked + span, .dk-tabs a.on, .dka-toggle.on, .sort-dir a.on, .sort-dir label:has(input:checked),
  .adv > .sort-dir label:has(input:checked), .result-bar .views .filter b, .dk-filter b, .dka-filter b, .cd-chip,
  .cd-ver-sub i.cheap, .vp-tag.cheap, .cd-yi input:checked + span, .ms-pre-tag, .ms-pre-seg a.on, .adm-new,
  .adm-link button, .adm-sheet .go, .adm-filter .btns button, .fb-card[aria-pressed="true"], .badge-manual,
  .num-manual { background-color: var(--blue-d); }
  .cd-lang-btns button.on { background-color: var(--blue-d); border-color: var(--blue-d); }
  .ms-pre-seg a.on, .ms-pre-seg a.on small { color: #fff; }
  .dk-fmt, .cd-tag, .bt-kind { background-color: #2f6fd0; }
  .adm-badge, .dot-count, .me-dot, .dk-chip.issues, .col-tag.bad, .hd-tag.bad, .badge-unknown, .num-unknown,
  .miss-dot, .adm-sheet .go.danger, .login .btn-primary.danger { background-color: #d70015; }
  .dk-chip.ok.lib-chip-ok, .dk-chip.ok:not(:disabled) { background-color: #1a7f37; }
  .chip.act, .hd-step button, .stepper button, .adm-wide, .acct-row.blue .k, .dka-toggle:not(.on), .dka-sort,
  .adm-mailtest button:not(:disabled) { color: var(--focus); }
  .dk-tabs a:not(.on), .ms-pre-seg a:not(.on), .ms-pre-seg a:not(.on) small { color: var(--text-2d); }
  .sel .val:not(.on) { color: var(--text-3); }  /* 选择赛制…: a placeholder's grey, 5.2:1 on #1c1c1c */
  .acct-btn.danger, .acct-row .k.danger, .log-row.denied .log-what, .pill-st:is(.disabled, .deleting) { color: var(--red-d); }
  .inv-state.s-已作废, .ss-revoke { color: #ff8a84; }
}
@media (hover: hover) and (pointer: fine) {
  summary, select, [role="button"], label:has(> input:is([type="checkbox"], [type="radio"], [type="file"])),
  label.check, .switch { cursor: pointer; }
  :is(button, input, select, textarea, a):is(:disabled, [aria-disabled="true"]) { cursor: not-allowed; }
  [data-soon] { cursor: default; }
  .busy, [aria-busy="true"] { cursor: progress; }
}

/* ---------------------------------------------------------------- hover (design §11.1, the shared parts) — colour
   only, never size or place; nothing for what cannot be clicked */
@media (hover: hover) and (pointer: fine) {
  .tn-links a, .tn-side a, .tn-search button, .btn-2, .seg a, .seg button, .setting-row, .pop-close, .as-close,
  .nav .capsule a, .nav .nav-cam, .view-banner button, .toast-undo button {
    transition: background-color 0.12s, color 0.12s, filter 0.12s; }
  .tn-links a:hover, .tn-side a:hover { color: #fff; background: var(--hover); }
  .tn-search button:hover { background: var(--hover-2); }
  .btn-primary:not(:disabled):not([aria-disabled="true"]):hover { filter: brightness(1.12); }
  .btn-2:not(:disabled):not([aria-disabled="true"]):hover { background: var(--hover-2); }
  .seg :is(a, button):not(.on):not(:disabled):not([aria-pressed="true"]):hover { background: var(--hover-2); color: #fff; }
  .setting-row:is(a, button):not(:disabled):hover { background: var(--hover); }
  .check:has(input:not(:checked):not(:disabled)):hover input,
  label:hover > .check input:not(:checked):not(:disabled) { background-color: #4a4a4c; }
  .switch:hover input:not(:checked):not(:disabled) + span { background: #48484c; }
  .pop-close, .as-close { border-radius: 50%; }
  .pop-close:hover, .as-close:hover { background: var(--hover-2); color: var(--text); }
  .nav .capsule a:hover, .nav .nav-cam:hover { background: var(--hover-2); }
  .view-banner button:hover { background: #000; color: #ffb340; }
  .toast-undo button:hover { text-decoration: underline; text-underline-offset: 3px; }
  .fcp a:hover { color: var(--text); }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .tn-links a, .tn-side a, .tn-search button, .btn-2, .seg a, .seg button, .setting-row, .pop-close, .as-close,
  .nav .capsule a, .nav .nav-cam, .view-banner button, .toast-undo button { transition: none; }
}
