public/js/ui/gameLogic.js becomes a re-export barrel. The bodies move verbatim into public/js/ui/gameLogic/*.js (shared, phases, camera, watch, bonds, shop, placement, draft, enemies, format, shortcuts, settings, result, loadout, panel). The barrel re-exports the same 96 names in the same order. isObj and int gain export on shared.js so sibling modules can import them; they stay off the barrel. No importer changes.
85 lines
4.9 KiB
JavaScript
85 lines
4.9 KiB
JavaScript
// ui/gameLogic/panel.js — detail card and bond popup placement. Re-exported from ../gameLogic.js.
|
|
|
|
import { isObj } from './shared.js';
|
|
|
|
|
|
// ---- detail panel placement (user playtest #2 item 8) ---------------------------------------------------------------
|
|
|
|
/**
|
|
* Which side the detail panel goes to so it never covers the selected unit's underframe: the default 'left' panel
|
|
* unless the underframe (buttons included) overlaps it and the 'right' slot is clear of it (else 'left' — the
|
|
* underframe is drawn above the panel anyway, css z-index).
|
|
* @param {{ left: number, right: number, top: number, bottom: number }|null} uf underframe rect (client px)
|
|
* @param {{ left: {left:number,right:number,top:number,bottom:number}, right: {left:number,right:number,top:number,bottom:number} }} slots
|
|
* @returns {'left'|'right'}
|
|
*/
|
|
export function panelSide(uf, slots) {
|
|
if (!isObj(uf) || !isObj(slots)) return 'left';
|
|
const hit = (a, b) => !!a && !!b && a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
|
|
if (!hit(uf, slots.left)) return 'left';
|
|
return hit(uf, slots.right) ? 'left' : 'right';
|
|
}
|
|
|
|
/**
|
|
* Client-px rects of the two detail panel slots (css .dpanel / .dpanel--right in rem): left at 2.3rem, right against
|
|
* the right edge above the shop bar when it is open.
|
|
* @param {{ width: number, height: number, left?: number, top?: number }} vp the HUD layer's box (.gm__hud): its size,
|
|
* and its client offset — the HUD sits inside the safe-area insets (css/devices.css), so on a notched phone the
|
|
* panels start `left` / `top` px further in than the viewport edge (default 0: the whole viewport)
|
|
* @param {number} rem root font size (px)
|
|
* @param {{ shopOpen?: boolean }} [o]
|
|
*/
|
|
export function panelSlots(vp, rem, { shopOpen = false } = {}) {
|
|
const W = Number(vp?.width) || 1920;
|
|
const H = Number(vp?.height) || 1080;
|
|
const X = Number.isFinite(vp?.left) ? vp.left : 0;
|
|
const Y = Number.isFinite(vp?.top) ? vp.top : 0;
|
|
const R = rem > 0 ? rem : 100;
|
|
const top = R * 2.1;
|
|
const leftH = Math.min(R * 8.4, H - top - R * 1.02);
|
|
const rightBottom = H - R * (shopOpen ? PANEL_RIGHT_BOTTOM_SHOP : PANEL_RIGHT_BOTTOM);
|
|
return {
|
|
left: { left: X + R * 2.3, right: X + R * (2.3 + 4.9), top: Y + top, bottom: Y + top + leftH },
|
|
right: { left: X + W - R * (PANEL_RIGHT_GAP + 4.9), right: X + W - R * PANEL_RIGHT_GAP, top: Y + top, bottom: Y + Math.max(top + R, rightBottom) },
|
|
};
|
|
}
|
|
/** Right detail panel slot insets (rem; keep in sync with css/screens/game-panels.css .dpanel--right). */
|
|
export const PANEL_RIGHT_GAP = 0.9;
|
|
export const PANEL_RIGHT_BOTTOM = 1.02;
|
|
export const PANEL_RIGHT_BOTTOM_SHOP = 3.78;
|
|
|
|
/** Bond popup geometry (rem; keep in sync with css/screens/game-panels.css .bpop / .bpop--*). */
|
|
export const BPOP = Object.freeze({ width: 5.4, top: 2.1, bottomGap: 0.3, left: 2.3, beside: 7.32, besideR: 5.92, right: 0.9 });
|
|
|
|
/**
|
|
* Where the bond popup opens (css .bpop--<place>) so it neither hides the open detail card nor covers the selected
|
|
* unit's underframe (user playtest #2 items 8 / 9): next to the card first ('beside' the left card, 'besideR' left of
|
|
* the right card), else the free side of the screen ('left' at 2.3rem / 'right' against the right edge), else over the
|
|
* card it was opened from; without a card the popup's usual place is 'left'. When every candidate covers the
|
|
* underframe the first one is kept (the underframe is drawn above the popup, css z-index).
|
|
* @param {{ left: number, right: number, top: number, bottom: number }|null} uf underframe rect (client px) or null
|
|
* @param {{ width: number, height: number, left?: number, top?: number }} vp the HUD layer's box (see panelSlots)
|
|
* @param {number} rem root font size (px)
|
|
* @param {'left'|'right'|null} card side of the open detail card (null: none)
|
|
* @returns {'left'|'beside'|'besideR'|'right'}
|
|
*/
|
|
export function bondPopupPlace(uf, vp, rem, card = null) {
|
|
const W = Number(vp?.width) || 1920;
|
|
const H = Number(vp?.height) || 1080;
|
|
const X = Number.isFinite(vp?.left) ? vp.left : 0;
|
|
const Y = Number.isFinite(vp?.top) ? vp.top : 0;
|
|
const R = rem > 0 ? rem : 100;
|
|
const top = Y + R * BPOP.top;
|
|
const bottom = Y + Math.max(R * BPOP.top + R, H - R * BPOP.bottomGap);
|
|
const rectOf = (p) => {
|
|
const left = X + (p === 'left' ? R * BPOP.left : p === 'beside' ? R * BPOP.beside
|
|
: p === 'besideR' ? W - R * (BPOP.besideR + BPOP.width) : W - R * (BPOP.right + BPOP.width));
|
|
return { left, right: left + R * BPOP.width, top, bottom };
|
|
};
|
|
// last resort: over the card it was opened from (closing the popup shows the card again)
|
|
const order = card === 'left' ? ['beside', 'right', 'left'] : card === 'right' ? ['besideR', 'left', 'right'] : ['left', 'right'];
|
|
if (!isObj(uf)) return order[0];
|
|
const hit = (a, b) => a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
|
|
return order.find((p) => !hit(uf, rectOf(p))) || order[0];
|
|
}
|