Files
ark-stronghold-protocol/public/css/devices.css
T
sganggs 6464e4b46a Playtest #6 fixes: summons placed by hand, boss pool stall, bounties, 联防 leak counter, distinct offers, 机变 card text, phone buy tap, skill triggers, blocking, push force, 孤立, enemies, element bar
- #1 #2 skill / device summons (赫默's drone, 巫恋's doll, 凯瑟琳's devices) are hand cards placed by the player;
  placed summons deploy once at battle start, then a skill summon re-appears at its tile on each cast (the user's call)
- #3 叙拉古 隐匿 ends on time (32 + 0.4 × layers s); 忍冬 S3 迷彩 lasts until her next cast (official), kept apart from 隐匿
- #4 multi-round bounty cards last two battles (the user's call; MULTI_ROUND_BOUNTY_BATTLES), blue / red card text
- #5 the shared boss pool's float dust (≈3.6e-12 HP) stalled Final Assault leaders at 0 %; < 1 HP now clears the pool,
  and the team LP reaching 0 first is a defeat (PRTS)
- #6 #10 机变 cards show their official effect text; an invisible 详情 button no longer swallows the confirming tap on phones
- #7 live ×N counter of a leaker's enemies still on the 联防 field (LP loss stays capped at 10)
- #8 #15 #16 skill triggers per 技能策略: skill-range triggers, injured-ally heals (古米, 塞雷娅), tank take-damage
  triggers with a 3 s cooldown; 新约能天使 S2 shields the highest-aggro operator in range
- #9 official per-enemy model scales (威龙)
- #11 element bar under the unit's HP bar (white = 元素值 left, element-coloured refill after a burst), floor rounding
- #12 枯朽战车 kit per PRTS; every ranged enemy reaches its target's 0.25 collider
- #13 恐惧 wander (萨科塔之翼 at half HP, operator fears make enemies flee) with re-planning after pushes
- #14 push / pull distance by 力度 − 重量 (PRTS table); #17 every blocker attacks what it blocks; ASPD floor 20
- #18 炎佑 is 孤立: no heals or ally buffs
- #19 every free pick-one chess offer (promotion reward, 信标, 寻呼模块) shows different operators
- DESIGN §20 and the normative lines, SIM / META / DATA / PLAYING updated; 2747 unit tests and all 27 browser suites
  green; dev recordings regenerated
2026-10-01 19:08:23 +08:00

151 lines
11 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ==========================================================================================
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, .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, .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 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(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; }
}