/* ==========================================================================================
   devices.css — multi-device layer (loaded last). Desktop Chrome / Edge / Firefox / Safari, iPad and Android tablets
   in landscape, phones in landscape (≥ 640×360 CSS px). js/ui/device.js puts the feature classes on <html>
   (sp-touch, sp-coarse, sp-hover / sp-no-hover, sp-fs, sp-standalone, sp-reduced-motion) — never UA sniffing.

   1. no page zoom / scroll / pull-to-refresh while playing (touch-action, overscroll-behavior, callouts)
   2. safe-area insets (notches, rounded corners, home indicator): the HUD and every screen stay inside them while the
      board and the backdrops stay full-bleed
   3. touch: every small control gets an invisible ≥ 44 px hit area (::before, no layout change); on short touch
      screens (phones) the most used controls also grow visually
   4. reduced motion extras; fullscreen button
   ========================================================================================== */

:root {
  --sa-t: env(safe-area-inset-top, 0px);
  --sa-r: env(safe-area-inset-right, 0px);
  --sa-b: env(safe-area-inset-bottom, 0px);
  --sa-l: env(safe-area-inset-left, 0px);
  --tap-min: 44px;
}

/* ---- 1. gestures ------------------------------------------------------------------------------------------------- */
html, body { overscroll-behavior: none; }
/* pan only: no pinch-zoom / double-tap zoom anywhere (scrollable lists still scroll); the field canvas uses none */
html { touch-action: pan-x pan-y; }
body { -webkit-touch-callout: none; }
button, a, label, summary, [role="button"], [role="tab"], [role="option"], .scard, .lvcard, .dband, .bond, .pavatar { touch-action: manipulation; }
img { -webkit-user-drag: none; }
.gm__field, .gm__field canvas, .ff, .fwheel, .fwheel__dia { touch-action: none; }
/* the DOM fallback board drags with pointer events too */
.ff-piece, .ff-slot, .ff-tile { touch-action: none; -webkit-user-select: none; user-select: none; }
input, textarea, select { -webkit-user-select: text; user-select: text; }

/* ---- 2. safe areas ------------------------------------------------------------------------------------------------ */
/* the in-match HUD layer sits inside the safe area; the field (canvas) below it stays full-bleed */
.gm__hud { top: var(--sa-t); right: var(--sa-r); bottom: var(--sa-b); left: var(--sa-l); }
/* the selected unit's underframe (ui/underframe.js) is placed at the tile's CLIENT px (view.tileScreen) inside the HUD
   layer: step back over the HUD's safe-area offset so it stays on its unit on a notched phone */
.gm__hud > .uframe { margin-left: calc(-1 * var(--sa-l)); margin-top: calc(-1 * var(--sa-t)); }
/* the shop bar keeps its place at the bottom edge (it may reach into the home-indicator strip): raised by the bottom
   inset it covered ~3/4 of the bench pads 2–9 on a notched phone (user playtest #4 item 1), and a bench piece is
   pressed / dropped on by its tile. The prep camera keeps the bench clear of the bar (ui/fieldHost.js hudBands →
   render/projection.js clearHud, user playtest #5 item 9). Its right edge stays inside the inset (the notch / rounded
   corner). */
.gm__hud > .shopbar { bottom: calc(.2rem - var(--sa-b)); }
.gm > .spov { left: calc(2.8rem + var(--sa-l)); right: var(--sa-r); }
/* every other screen is laid out inside the safe area (the app backdrop fills the notch strip) */
.screen:not(.gm) { top: var(--sa-t); right: var(--sa-r); bottom: var(--sa-b); left: var(--sa-l); }
.guide, .awayov { padding: var(--sa-t) var(--sa-r) var(--sa-b) var(--sa-l); }
/* (in a match the toasts sit under the top bar: components.css .sp-in-match .toast-host already adds --sa-t) */
html:not(.sp-in-match) .toast-host { margin-top: var(--sa-t); }
.conn-banner { margin-bottom: var(--sa-b); }
.rotate-hint { padding: calc(24px + var(--sa-t)) calc(24px + var(--sa-r)) calc(24px + var(--sa-b)) calc(24px + var(--sa-l)); }

/* ---- 3. touch targets ---------------------------------------------------------------------------------------------- */
/* An invisible hit area of at least --tap-min around small controls on touch screens: centred, never smaller than the
   control, no layout change. Only controls that do not use ::before themselves are listed. */
:where(.sp-coarse) :where(.tapx, .gtop__exit, .gtop__iconbtn, .enemybtn, .readybtn, .gm__gear, .fsbtn, .ewheel__btn, .ewheel__nav,
  .toolbtn, .funds__collapse, .team__btn, .effect, .bslot .bond, .vswitch__arrow, .edrawer__close, .dpanel__close, .bpop__close,
  .modal__close, .uf__btn, .chud__back, .tabs__tab, .btn, .pill-btn, .stephead__guide, .set-toggle, .set-seg button) {
  position: relative;
}
.sp-coarse :is(.tapx, .gtop__exit, .gtop__iconbtn, .enemybtn, .readybtn, .gm__gear, .fsbtn, .ewheel__btn, .ewheel__nav,
  .toolbtn, .funds__collapse, .team__btn, .effect, .bslot .bond, .vswitch__arrow, .edrawer__close, .dpanel__close, .bpop__close,
  .modal__close, .uf__btn, .chud__back, .tabs__tab, .btn, .pill-btn, .stephead__guide, .set-toggle, .set-seg button)::before {
  content: '';
  position: absolute; left: 50%; top: 50%; z-index: 0;
  width: max(100%, var(--tap-min)); height: max(100%, var(--tap-min));
  transform: translate(-50%, -50%);
  background: transparent; border: 0; box-shadow: none; clip-path: none; pointer-events: auto;
}
/* The 交流 panel's pager (‹ dots ›) sits in the strip under the emote cells (panel y 1.97–2.31rem, css/emotes.css). Its
   hit areas stay inside that strip and never overlap each other: a centred 44 px area of ‹ / › covered the centres of
   the bottom-left / bottom-right emotes (a tap there turned the page or hit the disabled ‹), and 28 px dot areas 0.21rem
   apart covered the neighbouring dot (a tap on dot N opened page N+1). ‹ / › grow outwards (away from the dots), the
   dots only up to half the gap. Selectors ≥ the specificity of the generic hit-area rule above. */
.sp-coarse .ewheel__panel .ewheel__nav::before { top: .03rem; bottom: -.15rem; height: auto; transform: none; }
.sp-coarse .ewheel__panel .ewheel__nav.is-prev::before { left: auto; right: 0; }
.sp-coarse .ewheel__panel .ewheel__nav.is-next::before { left: 0; right: auto; }
.sp-coarse .ewheel__dot::before { inset: -.02rem -.05rem -.2rem; width: auto; height: auto; transform: none; }

/* Phones in landscape (short touch screens: 1rem ≈ 40 px here): the most used controls grow a little so they are
   comfortable to hit and to read, without changing the official layout. */
@media (max-height: 600px) and (pointer: coarse), (max-height: 600px) and (any-pointer: coarse) {
  .readybtn { height: max(.56rem, calc(34px * var(--ui))); }
  .toolbtn { min-height: max(.5rem, calc(30px * var(--ui))); }
  .gm__gear { width: max(.56rem, calc(34px * var(--ui))); height: max(.56rem, calc(34px * var(--ui))); }
  /* ⚙ / 📖 / ⛶ keep their desktop proportion (half the button) instead of an 11 px glyph in the grown button */
  .gm__corner .gm__gear .icon { width: max(.28rem, calc(17px * var(--ui))); height: max(.28rem, calc(17px * var(--ui))); }
  .gtop__exit { min-width: max(.56rem, calc(34px * var(--ui))); min-height: max(.56rem, calc(34px * var(--ui))); }
  .gtop__iconbtn.has-sprite, .enemybtn.has-sprite { height: max(.46rem, calc(26px * var(--ui))); width: calc(max(.46rem, calc(26px * var(--ui))) * 35 / 29); }
  .gtop__iconbtn.has-sprite.is-wide, .enemybtn.has-sprite.is-wide { width: calc(max(.46rem, calc(26px * var(--ui))) * 61 / 29); }
  .gtop__iconbtn:not(.has-sprite), .enemybtn:not(.has-sprite) { height: max(.46rem, calc(26px * var(--ui))); min-width: max(.46rem, calc(26px * var(--ui))); }
  .ewheel__panel { scale: 1.3; transform-origin: 0 100%; }
  .fwheel__cancel { min-height: 30px; }
  .set-toggle, .set-seg button { height: max(.38rem, calc(30px * var(--ui))); }
  /* a taller range input whose visible track stays thin: transparent borders are part of the hit area */
  .set-range { height: 30px; border: 11px solid transparent; border-left-width: 0; border-right-width: 0; background-clip: padding-box; box-shadow: inset 0 0 0 1px var(--line-2); }
  .field--lg .field__box { height: max(.68rem, calc(40px * var(--ui))); }
}

/* ---- 4. misc ------------------------------------------------------------------------------------------------------ */
.fsbtn { display: grid; place-items: center; padding: 0; background: rgba(8, 11, 10, .85); border: 1px solid var(--line-2); color: var(--text-lo); transition: color var(--t-fast), border-color var(--t-fast); }
.fsbtn .icon { width: .26rem; height: .26rem; }
.fsbtn:hover, .fsbtn[aria-pressed="true"] { color: var(--mint-400); border-color: var(--mint-700); }
/* standalone / fullscreen: the browser bars are gone, nothing to reserve */
.sp-standalone .rotate-hint__micro::after { content: ' · PWA'; }

/* reduced motion: camera-free UI pieces that animate continuously stop (the global rule in theme.css shortens the rest) */
@media (prefers-reduced-motion: reduce) {
  .team__status.is-gold, .ff-tile--gate, .boot__hex { animation: none !important; }
}
.sp-reduced-motion .team__status.is-gold, .sp-reduced-motion .ff-tile--gate { animation: none; }

/* (the shop cards have no ⓘ corner: its invisible 44 px touch area — the 30 px override here lost on specificity to the
   generic rule above — covered the bottom-right quarter of a phone's 67 × 97 px card and half of its 确认购买 strip, so
   the confirming tap re-opened the detail instead; user playtest #6 item 10) */

/* Narrow viewports (< 768 CSS px: the root scale is clamped at 40 px, so the layout is narrower than the 19.2 rem
   design): the shop bar would cover the corner's ⚙ / 📖 / ⛶ buttons — they wrap into a row above the 交流 button. */
/* The left-docked detail card then ends above that two-row corner (.24rem + 交流 + .1rem gap + ⚙ row + .1rem): at its
   desktop bottom (1.02rem) it hid ⚙ / 📖 / ⛶. (gameLogic panelSlots keeps the taller desktop slot: conservative.) */
@media (max-width: 767px) {
  .gm__corner { flex-wrap: wrap-reverse; align-content: flex-start; width: max(calc(72px * var(--ui)), calc(3 * .56rem + .2rem)); }
  .dpanel:not(.dpanel--right) { bottom: calc(.44rem + max(1.51rem, calc(72px * var(--ui))) * 55 / 151 + .56rem); }
}
@media (max-width: 767px) and (pointer: coarse), (max-width: 767px) and (any-pointer: coarse) {
  .gm__corner { width: max(calc(96px * var(--ui)), calc(3 * max(.56rem, calc(34px * var(--ui))) + .2rem)); }
  .dpanel:not(.dpanel--right) { bottom: calc(.44rem + max(1.51rem, calc(96px * var(--ui))) * 55 / 151 + max(.56rem, calc(34px * var(--ui)))); }
}
/* With the shop folded (收起) no bar covers the corner: one row again, so the folded shop's prep camera, which keeps the
   bench above the corner's hit areas (ui/fieldHost.js hudBands), grows the board on narrow phones too (public issue #5). */
@media (max-width: 767px) {
  .gm.is-collapsed .gm__corner { flex-wrap: nowrap; width: auto; }
}

/* Short landscape phones (≤ 460 CSS px tall, 1rem = 40 px; the lobby's own layout: css/screens/lobby.css). The detail
   card's header is shorter (no
   romanised name, a shorter portrait) so the operator's own effect (特质) under it stays in view even in the short card
   docked above the shop bar (user playtest #3 item 8). */
@media (max-height: 460px) {
  .dhead__art { height: 1.66rem; }
  .dhead__en { display: none; }
}

.rotate-hint__pwa { pointer-events: auto; margin-top: 6px; }
.rotate-hint__pwa[hidden] { display: none; }

/* Phones (short touch screens in landscape, narrow ones): the match HUD's readable floors. */
@media (max-height: 600px) and (pointer: coarse), (max-width: 767px) {
  /* Keep phone cards readable at the 40 px root floor. Larger shops scroll their offers instead of shrinking;
     the level, item and funds stay reachable, and the left corner keeps its own touch area. */
  .gm:not(.is-collapsed) .gm__corner { flex-wrap: wrap-reverse; align-content: flex-start; width: calc(148px * var(--ui)); }
  .gm .dpanel:not(.dpanel--right) { bottom: calc(.44rem + max(1.51rem, calc(96px * var(--ui))) * 55 / 151 + max(.56rem, calc(34px * var(--ui)))); }
  .gm .shopbar { max-width: calc(100% - calc(164px * var(--ui)) - .26rem); }
  /* the kill/level ticker under the top bar: readable at the 40 px root floor (it was 6.4 px), still clear of the bond discs */
  .gm .ticker { top: calc(1rem - calc(4px * var(--ui))); height: calc(20px * var(--ui)); }
  .gm .ticker__line { font-size: calc(11px * var(--ui)); padding: 0 calc(12px * var(--ui)); }
  .gm .shopbar__row { max-width: 100%; min-width: 0; gap: calc(4px * var(--ui)); padding: calc(4px * var(--ui)); align-items: flex-end; }
  .gm .shopbar__cards, .gm .shopbar__rwcards { min-width: 0; gap: calc(4px * var(--ui)); overflow-x: auto; padding: calc(10px * var(--ui)) calc(4px * var(--ui)) calc(4px * var(--ui));
    touch-action: pan-x; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
  .gm .shopbar__cards { flex: 0 1 auto; }
  .gm .shopbar__reward { min-width: 0; flex: 0 1 auto; gap: calc(4px * var(--ui)); padding: 0; }
  .gm .shopbar__reward::before { inset: 0; }
  .gm .rwtag { flex: none; width: calc(44px * var(--ui)); }
  .gm .rwtag__later { min-height: calc(44px * var(--ui)); font-size: calc(12px * var(--ui)); }
  .gm .shopbar .scard { flex: none; width: max(1.56rem, calc(80px * var(--ui))); height: max(2.24rem, calc(112px * var(--ui))); }
  .gm .shopbar .scard-wrap, .gm .shopbar__item { flex: none; }
  .gm .shopbar__item { gap: calc(4px * var(--ui)); padding-left: calc(4px * var(--ui)); }
  .gm .lvcard { flex: none; width: max(1.24rem, calc(52px * var(--ui))); height: max(2.24rem, calc(112px * var(--ui))); }
  .gm .lvcard__num { font-size: max(calc(.66 * var(--t)), calc(32px * var(--ui))); }
  .gm .funds { flex: none; width: max(1.18rem, calc(56px * var(--ui))); align-self: stretch; }
  .gm .funds__num { font-size: max(calc(.36 * var(--t)), calc(24px * var(--ui))); }
  .gm .funds__hex { width: max(1rem, calc(44px * var(--ui))); height: max(.9rem, calc(40px * var(--ui))); }
  .gm .shopbar .hex { --hex-h: max(.32rem, calc(20px * var(--ui))); }
  .gm .shopbar .tier { --tier-h: max(.28rem, calc(16px * var(--ui))); }
  .gm .scard__skill { width: max(.3rem, calc(18px * var(--ui))); height: max(.3rem, calc(18px * var(--ui))); }
  .gm .scard__confirm b { font-size: max(.18rem, calc(12px * var(--ui))); letter-spacing: .04em; }
  .gm .scard__confirm small { font-size: max(.11rem, calc(10px * var(--ui))); }
  .gm .scard__name { font-size: max(.19rem, calc(13px * var(--ui))); }
  .gm .lvcard__label, .gm .toolbtn, .gm .readybtn__label { font-size: max(.19rem, calc(12px * var(--ui))); }
  .gm .scard__class, .gm .scard__bond, .gm .scard__idesc, .gm .lvcard__micro, .gm .funds__label, .gm .shopbar__remain { font-size: max(.13rem, calc(10px * var(--ui))); }
  .gm .funds__collapse, .gm .shopbar-tab__btn { min-height: calc(44px * var(--ui)); font-size: calc(12px * var(--ui)); }
  .gm .chud__prog, .gm .chud__pname { font-size: calc(12px * var(--ui)); }
  .gm .chud__progress { max-height: calc(96px * var(--ui)); overflow-y: auto; pointer-events: auto; touch-action: pan-y; }
  .gm .chud__prog .num { font-size: calc(12px * var(--ui)); }
  .set-row__label { font-size: max(calc(.17 * var(--t)), calc(13px * var(--ui))); }
  .set-row .set-toggle, .set-row .set-seg button { min-height: calc(44px * var(--ui)); font-size: calc(12px * var(--ui)); }
  .set-row { gap: calc(10px * var(--ui)); }
  .set-hint { font-size: max(calc(.14 * var(--t)), calc(12px * var(--ui))); }
}

/* ---- 6. 异形屏适配 (设置, ui/screenFit.js) ------------------------------------------------------------------------
   The player's own insets for a screen whose cutout / curved edge the browser does not report (or reports wrongly in
   fullscreen): .app-root becomes the inset box — every .screen is absolute inset 0, so it lands inside, and the board is
   sized from its host — and the layout root 1rem is computed from that box (theme.css html[data-fit], the root rule
   with the insets taken off). Only while <html data-fit> is set: with all four at 0 nothing here applies. The bands outside show the page
   background; fixed overlays (dialogs, toasts) still use the whole screen. */
/* the screens' own box (theme.css .app-root: fixed, inset 0) and the full-page panels fixed over the screens (统计,
   S.W.E.E.P., the protocol pages, 干员调配, 玩法说明, 成就统计) */
html[data-fit] :is(.app-root, .st, .sw, .proto, .lo, .guide, .ach) {
  left: var(--fit-l, 0px); right: var(--fit-r, 0px); top: var(--fit-t, 0px); bottom: var(--fit-b, 0px);
}
.set-fit { display: flex; flex-direction: column; gap: .06rem; }
.set-fit__body { display: flex; flex-direction: column; gap: .06rem; }
.set-fit__toggle { display: flex; align-items: center; gap: .1rem; min-width: 0; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.set-fit__badge { flex: none; padding: .01rem .08rem; border: 1px solid var(--gold-2); color: var(--gold-2); font-size: calc(.13 * var(--t)); white-space: nowrap; }
.set-fit__chev { flex: none; width: .12rem; height: .12rem; min-width: 8px; min-height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); opacity: .6; transition: transform var(--t-fast); }
.set-fit.is-open .set-fit__chev { transform: rotate(-135deg); }
.set-fit__row { grid-template-columns: minmax(0, 1.5rem) auto minmax(.8rem, 1fr) auto minmax(.36rem, auto); gap: .1rem; }
.set-fit__step { flex: none; width: .4rem; min-width: 28px; height: .34rem; min-height: 26px; padding: 0; border: 1px solid var(--line-2); background: rgba(0, 0, 0, .3);
  color: var(--text-hi); font-size: calc(.2 * var(--t)); line-height: 1; cursor: pointer; }
.set-fit__step:disabled { opacity: .35; cursor: default; }
