// Underframe — the selection diamond of a tapped own piece (research 09 §1.2 "Tap a deployed unit", §5, §6.5; // official `act2autochess_panel_character_menu`: `util_btn` icon_sell + price "+1", funcId autochessSale; the // destroy variant `_underFramePanelBtnParamDestroyPos` with icon_destory). // // board operator → 撤退 (upper-left: back to the bench) + 出售 +N (upper-right) // board summon → 撤退 // bench operator → 出售 +N // item / Art → 销毁 // The diamond frames the piece's tile; only its buttons take pointer events, so the unit under it can still be // dragged (moving it — or dropping it back on its own tile to re-orient it with the wheel). The game screen shows the // unit's range tiles (rotated to its facing) and the detail card beside it while it is selected (on the side away // from the unit, `underframeRect` / gameLogic `panelSide`), and the underframe sits above every HUD panel, so its // buttons are never covered. // // Button look (user playtest #2 item 7): the official sprites icon_sell / icon_destory are WHITE octagon plates with // the glyph cut out — tinted in the client. Drawn as is they read as a plain white block, so each button is a coloured // octagon plate: the sprite is used as a CSS mask over the plate colour (出售 amber like every money action, 销毁 / // 撤退 red) on a dark backing that shows through the glyph cut-out. Without the local sprites the same plate carries // the built-in glyph. // // TempRowNotice (user playtest #3 item 3) — the other board-anchored overlay of the prep: while the temp overflow row // (临时整备区, row 8, the 5 pads in front of the bench) holds pieces, a dashed red frame around that row and a label at // its end say that they block 准备就绪 and are destroyed when the prep ends unless moved to the bench, equipped or used. // Placed through view.tileScreen (useTileScreen, so it follows the camera — the Final Assault prep too); it never takes // the pointer, so the pieces under it stay draggable. import { html, HexBadge, Icon } from './components.js'; import { GIcon } from './gameComponents.js'; import { useTileScreen } from './facingWheel.js'; import { localAsset } from '../data.js'; import { GEO } from '../../../shared/constants.js'; const cx = (...p) => p.flat().filter(Boolean).join(' '); /** Runner glyph of 撤退 (a figure leaving through a door; original shape). */ function RetreatGlyph() { return html``; } /** * A tinted octagon plate: the official sprite (white plate, glyph cut out) as a mask over the tone colour, or the * built-in glyph on the same plate when the local sprite is not installed. * @param {{ sprite: string, glyph: string, tone: 'sell'|'destroy' }} props */ export function PlateIcon({ sprite, glyph, tone }) { const url = localAsset('ui/battle', sprite); return html``; } /** * Client-px rect the underframe of a tile covers: the diamond plus its buttons (plates, labels, the +N price) — the * geometry of the css below (.uframe__btn--retreat / --sell: .56rem plates at 25 % / 75 % across, 25 % down, shifted * −80 % / −20 % horizontally and −90 % vertically). Used for the detail panel placement. * @param {{ x: number, y: number, s: number }|null} g view.tileScreen(row, col) * @param {number} [rem] root font size (px) * @returns {{ left: number, right: number, top: number, bottom: number }|null} */ export function underframeRect(g, rem = 100) { if (!g || !Number.isFinite(g.x) || !Number.isFinite(g.y)) return null; const s = g.s > 0 ? g.s : 64; const half = s * 1.05; const P = rem * 0.56; // plate const H = P + rem * 0.26; // plate + label const q = half / 2; // 25 % / 75 % of the diamond box, from its centre const btnTop = g.y - q - 0.9 * H; const left = Math.min(g.x - half, g.x - q - 0.8 * P); const right = Math.max(g.x + half, g.x + q - 0.2 * P + P + rem * 0.14); return { left, right, top: Math.min(g.y - half, btnTop - rem * 0.08), bottom: g.y + half }; } /** * @param {{ view: any, uid?: number|null, row: number, col: number, actions: { retreat: boolean, sell: number|null, destroy: boolean }, * name?: string, busy?: boolean, onRetreat?: () => void, onSell?: () => void, onDestroy?: () => void }} props */ export function Underframe({ view, uid = null, row, col, actions, name = '', busy = false, onRetreat, onSell, onDestroy }) { const g = useTileScreen(view, row, col); if (!g || !actions) return null; const s = g.s > 0 ? g.s : 64; const half = s * 1.05; const stop = (e) => e.stopPropagation(); return html`