From 7e24eab1716e5b3610c20148f491010a43f67720 Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 18 Jul 2026 14:11:39 +1000 Subject: [PATCH] [ui] fix: the tab scrollbar was swallowing clicks on half of every tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproduced the "clicking POWER does nothing" report with a real mouse: elementFromPoint at the button's own centre returned the strip container, not the button. The horizontal scrollbar was painted over the lower half of every tab and ate clicks there — which is exactly why keyboard paging still worked and clicking didn't. Fixed by giving the scrollbar its own lane (padding-bottom >= its height). The full button height now hit-tests to the button and a real click activates the tab. The strip itself is the approved round-5 design: one row that scrolls rather than wrapping. The catalog keeps growing — RESEARCH joined in v4 and threw REMOVE onto a line of its own — and a wrapping row moves every other tab under the player's cursor. Paging with [ ] scrolls the active tab into view. Co-Authored-By: Claude Opus 4.8 --- fktry/src/ui/buildbar.ts | 3 + fktry/src/ui/style.ts | 133 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/fktry/src/ui/buildbar.ts b/fktry/src/ui/buildbar.ts index dd151ec..361d6f7 100644 --- a/fktry/src/ui/buildbar.ts +++ b/fktry/src/ui/buildbar.ts @@ -115,6 +115,9 @@ export function createBuildBar(data: GameData, sel: BuildSelection): BuildBar { function render() { buildButtons(pages[active]); for (let i = 0; i < tabs.length; i++) cls(tabs[i], 'is-active', i === active); + // The strip scrolls, so paging with [ ] must bring its tab into view or the player + // pages blind. `nearest` avoids yanking the strip when the tab is already visible. + tabs[active]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' }); sync(); } diff --git a/fktry/src/ui/style.ts b/fktry/src/ui/style.ts index 796902f..9bbd690 100644 --- a/fktry/src/ui/style.ts +++ b/fktry/src/ui/style.ts @@ -125,8 +125,30 @@ const CSS = ` justify-content: center; } -/* Kind tabs. A page holds at most nine so 1-9 always reaches every machine. */ -.fk-tabs { display: flex; gap: 3px; margin-bottom: 5px; flex-wrap: wrap; justify-content: center; } +/* Kind tabs. A page holds at most nine so 1-9 always reaches every machine. + v5: a single scrolling strip rather than a wrapping row — the catalog keeps growing + (RESEARCH joined in v4 and threw REMOVE onto a line of its own), and tabs jumping to a + second row moves every other tab under the player's cursor. */ +.fk-tabs { + display: flex; gap: 3px; margin-bottom: 5px; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; + scrollbar-width: thin; + scrollbar-color: var(--fk-line) transparent; + /* The scrollbar gets its OWN lane below the buttons. With too little padding it is + painted over the lower half of every tab and swallows clicks there — the exact + "clicking POWER does nothing" bug from review, which is why keyboard paging still + worked. Must stay >= the scrollbar height below. */ + padding-bottom: 7px; +} +.fk-tabs::-webkit-scrollbar { height: 4px; } +.fk-tabs::-webkit-scrollbar-track { background: transparent; } +.fk-tabs::-webkit-scrollbar-thumb { background: var(--fk-line); } +.fk-tabs::-webkit-scrollbar-thumb:hover { background: var(--fk-dim); } +/* Tabs must not shrink into unreadable slivers when the strip overflows. */ +.fk-tab { flex: 0 0 auto; } .fk-tab { background: var(--fk-bg-solid); border: 1px solid var(--fk-line); @@ -138,9 +160,8 @@ const CSS = ` .fk-tab:hover { color: var(--fk-fg); border-color: var(--fk-cool); } .fk-tab.is-active { color: var(--fk-cool); border-color: var(--fk-cool); background: #0c1a1a; } -/* REMOVE is a mode, not a machine — it sits with the tabs and reads as a hazard. - No auto margin: with the RESEARCH tab added the row wraps, and an auto margin threw - REMOVE onto a line of its own. */ +/* REMOVE is a mode, not a machine — it sits at the end of the strip and reads as a + hazard. It scrolls with the tabs; the hotkey (X) is always available regardless. */ .fk-tab-remove { margin-left: 10px; color: var(--fk-dim); } .fk-tab-remove:hover { color: var(--fk-red); border-color: var(--fk-red); } .fk-tab-remove.is-armed { @@ -227,6 +248,11 @@ const CSS = ` .fk-ins-heat-state { font-size: 10px; color: var(--fk-amber); margin-top: 3px; } .fk-ins-heat-state.is-scram { color: var(--fk-red); font-weight: bold; } +/* MOSQUITO EXPOSURE — wildlife is a hazard, not a fault: amber, quiet, matter-of-fact. */ +.fk-ins-exposure { display: none; margin-top: 6px; } +.fk-ins-exposure.is-on { display: block; } +.fk-ins-exposure-pct { color: var(--fk-amber); font-size: 10px; } + /* Process select: a native