- #1 近地悬浮 enemies count as air units (Unit.isFlying): ground-only attacks, splash, terrain and traps skip them; PRTS 不可对空 skill effects made ground-only; 掠海漂移体 / 吉兆飞鳞 drop modes; the bot plans them as air - #2 an operator knocked out before 联防 is fielded and force-exited (PRTS 强制退场): it lies on its tile with the redeploy ring and redeploys (full timer [ASSUMED]); partners, observers and the server fallback included - #3 element audit of every operator, item and enemy: 海嗣 numbers are official; bursts are 无来源, 元素脆弱 only raises 元素伤害, 元素抗性 applied, 盟约·辅助干员 fills all three gauges, 菲莱 / 哈洛德 / 纯烬 / enemy kits fixed - #4 blocking by the official contact radius (0.7071): another operator in contact takes over at once when the blocker dies, withdraws or is stunned; melee blockers attack what they block - #5 enemy target order per PRTS 索敌: blocker, special priority, taunt, last deployed; initial deploy 从上到下>从左到右, operators before summons; special priorities break ties by taunt then deploy order - #6 act2 m01's normal-field blower machines (row 13) are drawn in the normal / 联防 views - #7 Final Assault prep legality reads the player's half of the boss field (fence tiles deployable); all 11 stages audited against the official level files - #8 regular settings gear; #9 the prep camera keeps the bench and temp rows clear of the shop bar on phones - #10 huge bosses have the official 4.95 × 2.95 hit area (sim/body.js) and are 自缚 / unblockable - #11 至简 costs 1: 购买价格为N's bb.price is a discount off the tier price, applied before other price effects - DESIGN §19 and the normative §3 / §5 / §6 / §7 lines, SIM / META / DATA / PLAYING updated; 2595 unit tests and every browser suite green; dev recordings regenerated
150 lines
11 KiB
CSS
150 lines
11 KiB
CSS
/* ==========================================================================================
|
||
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, .scard__detail, .title-guide, .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, .scard__detail, .title-guide, .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, 34px); }
|
||
.toolbtn { min-height: max(.5rem, 30px); }
|
||
.gm__gear { width: max(.56rem, 34px); height: max(.56rem, 34px); }
|
||
/* ⚙ / 📖 / ⛶ 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, 17px); height: max(.28rem, 17px); }
|
||
.gtop__exit { min-width: max(.56rem, 34px); min-height: max(.56rem, 34px); }
|
||
.gtop__iconbtn.has-sprite, .enemybtn.has-sprite { height: max(.46rem, 26px); width: calc(max(.46rem, 26px) * 35 / 29); }
|
||
.gtop__iconbtn.has-sprite.is-wide, .enemybtn.has-sprite.is-wide { width: calc(max(.46rem, 26px) * 61 / 29); }
|
||
.gtop__iconbtn:not(.has-sprite), .enemybtn:not(.has-sprite) { height: max(.46rem, 26px); min-width: max(.46rem, 26px); }
|
||
.ewheel__panel { scale: 1.3; transform-origin: 0 100%; }
|
||
.fwheel__cancel { min-height: 30px; }
|
||
.set-toggle, .set-seg button { height: max(.38rem, 30px); }
|
||
/* 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, 40px); }
|
||
}
|
||
|
||
/* ---- 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); }
|
||
.title-fs { width: .4rem; height: .4rem; margin-left: .06rem; }
|
||
/* 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, .title-bg__sweep, .boot__hex { animation: none !important; }
|
||
}
|
||
.sp-reduced-motion .team__status.is-gold, .sp-reduced-motion .ff-tile--gate { animation: none; }
|
||
|
||
/* the shop card's ⓘ corner: a smaller hit area (the card itself is the main target; its first tap shows the detail) */
|
||
.sp-coarse .scard__detail::before { width: 30px; height: 30px; }
|
||
|
||
/* 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(72px, calc(3 * .56rem + .2rem)); }
|
||
.dpanel:not(.dpanel--right) { bottom: calc(.44rem + max(1.51rem, 72px) * 55 / 151 + .56rem); }
|
||
}
|
||
@media (max-width: 767px) and (pointer: coarse), (max-width: 767px) and (any-pointer: coarse) {
|
||
.gm__corner { width: max(96px, calc(3 * max(.56rem, 34px) + .2rem)); }
|
||
.dpanel:not(.dpanel--right) { bottom: calc(.44rem + max(1.51rem, 96px) * 55 / 151 + max(.56rem, 34px)); }
|
||
}
|
||
|
||
/* Short landscape phones (≤ 460 CSS px tall, 1rem = 40 px): the lobby's two columns are compressed so the
|
||
创建同盟 button and the join panel stay on screen without scrolling. 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; }
|
||
.lobby-body { padding-top: .2rem; padding-bottom: .2rem; gap: .36rem; }
|
||
.mode-cards { margin-bottom: .2rem; }
|
||
.mode-card { min-height: 0; gap: .12rem; padding: .22rem .26rem .2rem; }
|
||
.mode-card__icon { width: .66rem; height: .66rem; }
|
||
.mode-card__name { font-size: .4rem; }
|
||
.mode-card__points, .diff-card__effects, .tips, .create-box__hint { display: none; }
|
||
.diff-list { gap: .1rem; }
|
||
.diff-card { padding-top: .12rem; padding-bottom: .12rem; }
|
||
.create-box { margin-top: .18rem; }
|
||
.create-box .btn--xl { height: .8rem; }
|
||
}
|