diff --git a/.claude/rules/components.md b/.claude/rules/components.md index 78415574..5a51124a 100644 --- a/.claude/rules/components.md +++ b/.claude/rules/components.md @@ -33,12 +33,12 @@ ALttPR's modules live under `apps/web/src/games/alttpr/`: a short path below (`l - `ui/Tooltip.tsx` — the page's single tooltip, driven by `stores/tooltipStore.ts` (kept out of the game store so hovering never re-renders the board). Controls opt in with `useTooltip({ kind, … })`: mouse hover, keyboard focus, or a touch long-press (a tap stays the control's action). A map marker's (`kind: "marker"`, or a grid boss cell's `kind: "dungeon"`) lists each location it stands for with its state, and what one not in logic is missing (`MissingLines`, read from the run's analysis, never worked out on hover); a dungeon's says the items its counter has left and how many are surely in logic. The marker's accessible name says its state, so the tooltip is `aria-hidden`. When several hooks give one element the same handler, combine them with `mergeHandlers` (`apps/web/src/utils/mergeHandlers.ts`), never by spreading twice. - `ui/` — `Header`, `Tabs` (the ARIA tabs pattern, automatic activation; the setup guide's systems and the map panel's views), `Modal` (native `` + `showModal()`; `wide` scrolls a long body between a fixed header and footer), `ConfirmDialog` (opens on Cancel via `data-autofocus`), `PresetSelector` (the run types; the summary's tunable settings, `TUNING` in `data/presets.ts` (goal, dungeon items, swords, crystals, item placement, pool, functionality, accessibility), are labelled selects that change the run in place through `updateSettings`; in a Mystery run, or while hidden, a hideable one offers "? Unknown" (`hideSetting`); and `SeedImport`: a seed's alttpr.com link or hash, its settings checked against the board's run type with `checkSeed`; another run type goes to the same confirmation as a preset, then `applyRun`), `TrackerControls`, `BigKeyToggle`, `ShortcutsButton` → `ShortcutsDialog` (the master switch, reset, and `ShortcutTable`: the rows, the key capture and the conflict dialog, over any keymap; in the desktop app the dialog has a second tab, Global, below), `KeyChord` (a chord as `` keys, labelled for the player's layout), `SharedRunPrompt` (the notice among `.notices`, rendered by each game's app with its `game`: a `#run=` fragment, at load or on `hashchange`, is decoded and checked by the game's `importRun`; **Open** loads it and clears the fragment, **Keep mine** only clears it, a link that is no run says so, "Nothing was changed"; another game's run switches game on Open), the Share button (`share-button`, in each toolbar beside Export; in ALttPR's More menu as `toolbar-more-share`; its title is the one-line help on where the run travels; `shareRun` returns the status line), `StreamViewControl` (the `?view=obs` dialog; its checkbox adds `&timer=1`), `StreamTimer` (the stream view's run timer: an SVG one cell high in the chest counters' 3×5 font and frame, `pixelText`, every width even so it centres on a whole pixel; `ObsView` keeps it a row under the grid), `ToolbarMenu` (the More menu button, ARIA menu button pattern), `UpdatePrompt` (the non-modal "new version" notice; never reload on its own). - `ui/GamePicker.tsx` (shared) — the game this device tracks, at the top of each game's run type dialog: one button per game (`aria-pressed` on the current), never radios, so arrowing never switches; another game calls `switchGame` (`games/registry.ts`), which saves the choice and loads the page again. `ui/Header.tsx` takes the `game`: the randomizer's link (alttpr.com, maprando.com for Super Metroid, samus.link for SMZ3) and, for ALttPR, the original tracker's. `map/MapLegend.tsx` (shared) — the map states' swatches and words, every game's map below it; `seen={false}` leaves out the seen state for a logic without a visible channel (SMZ3). -- `games/sm/` — Super Metroid (Map Rando), its module loaded alone with its stylesheet (`sm.css`): `SmApp` (the shared header, `SmControls`, `SmGrid` and `SmObjectives`; Ctrl/Cmd+Z and Shift+Z or Ctrl+Y undo and redo), `SmGrid` (six columns, `smGrid` of `data.ts` laying the cells the seed's settings add; the shared item cell, `griditem` and `griditem--dimmed`, its icon a frame of Map Rando's item sheet set by `--frame`, `SPRITE_FRAME`; a click adds one, a right-click or "-" takes one away; packs and tanks show their count; one Tab stop through the shared `useGridNavigation`), `SmObjectives` (the seed's objectives as `aria-pressed` toggles, how many are done, and once all are, that the barriers before Mother Brain are down), `SmControls` (Mode: the game picker, the objectives by preset or one by one, the Wall Jump Boots and the split Speed Booster; New game; Undo, Redo; Autotracking; Export, Import; the stream view's address; the bar folds through the shared `useToolbarFit`, its labels only, having no More menu), `SmAutotrack` (the shared `GameAutotrackControl` and `useGameAutotrack` over `smAutotrack`) opening the lazy `SmAutotrackDialog` (the shared `GameAutotrackDialog`, `game="Map Rando"`, and "What I read": the seed linked to maprando.com, its presets, its objectives with those done) and `SmObsView` (the grid alone, fitted by `fitObsGrid` over six columns). -- `games/smz3/` — SMZ3 (samus.link), its module loaded alone with its stylesheet (`smz3.css`): `Smz3App` (the shared header, `Smz3Controls`, the board (`Smz3Grid`, `Smz3Tokens`, and under keysanity `Smz3Keycards`) beside `Smz3Maps`; Ctrl/Cmd+Z and Shift+Z or Ctrl+Y undo and redo), `Smz3Grid` (seven columns, `gridOf` of `data.ts`: A Link to the Past's items (ALttPR's assets by `--sprite`, a leveled one's level drawn on its sprite, never as a number), under keysanity its dungeons' key cells laid as ALttPR's (`bigkey-*`, the short name top left, the big key and small keys diagonal so they keep the 24 px target spacing on a phone), Super Metroid's (Map Rando's sheet by `--frame`), the rewards; a click adds one, a right-click or "-" takes one away; one Tab stop through `useGridNavigation`), `Smz3Tokens` (the boss tokens as `aria-pressed` toggles, and how many Tourian asks for), `Smz3Keycards` (a ``, an area a row and a level a column, its toggles one Tab stop through `useGridNavigation`), `Smz3Maps` (`Tabs` Hyrule | Zebes over one panel: Hyrule's two worlds as ALttPR's map draws them, with the smz3 pack's `HYRULE_MARKERS` (ALttPR's, by alias, as data: importing ALttPR's markers would download its world), Zebes VARIA's composed map (`assets/smz3/zebes.webp`, fetched only when its tab opens, the service worker leaving it on demand) with the pack's `ZEBES_MARKERS`; each marker a `mapspan` button named by its locations and state, its state from `logic.ts`'s `useMarkerState` (opened once all its locations are checked, else the alttpr pack's `markerStatus`, a module of its own, over the engine's answers), a click checking them all or none), `Smz3Controls` (Mode: the game picker, a preset, Super Metroid's logic, keysanity, the three thresholds, the medallion pads; New game; Undo, Redo; Autotracking, `Smz3Autotrack`; Export, Import; the stream view's address; the bar folds its labels through `useToolbarFit`), `Smz3AutotrackDialog` (the shared `GameAutotrackDialog`, `game="SMZ3"`, and "What I read": the seed's title, which game runs, a race seed's note, its settings and, outside races, what it asks for) and `Smz3ObsView` (the grid alone, fitted by `fitObsGrid` over the rows it shows). +- `games/sm/` — Super Metroid (Map Rando), its module loaded alone with its stylesheet (`sm.css`): `SmApp` (the shared header, `SmControls`, `SmGrid` and `SmObjectives`; Ctrl/Cmd+Z and Shift+Z or Ctrl+Y undo and redo), `SmGrid` (six columns, `smGrid` of `data.ts` laying the cells the seed's settings add; the shared item cell, `griditem` and `griditem--dimmed`, its icon a frame of Map Rando's item sheet set by `--frame`, `SPRITE_FRAME`; a click adds one, a right-click or "-" takes one away; packs and tanks show their count; one Tab stop through the shared `useGridNavigation`), `SmObjectives` (the seed's objectives as `aria-pressed` toggles, how many are done, and once all are, that the barriers before Mother Brain are down), `SmControls` (Mode: the game picker, the objectives by preset or one by one, the Wall Jump Boots and the split Speed Booster; New game; Undo, Redo; Autotracking; Export, Import; the stream view's address; the bar folds through the shared `useToolbarFit`, its labels only, having no More menu: the secondary actions sit in a `toolbar-actions` span so they lose theirs first), `SmAutotrack` (the shared `GameAutotrackControl` and `useGameAutotrack` over `smAutotrack`) opening the lazy `SmAutotrackDialog` (the shared `GameAutotrackDialog`, `game="Map Rando"`, and "What I read": the seed linked to maprando.com, its presets, its objectives with those done) and `SmObsView` (the grid alone, fitted by `fitObsGrid` over six columns). +- `games/smz3/` — SMZ3 (samus.link), its module loaded alone with its stylesheet (`smz3.css`): `Smz3App` (the shared header, `Smz3Controls`, the board (`Smz3Grid`, `Smz3Tokens`, and under keysanity `Smz3Keycards`) beside `Smz3Maps`; Ctrl/Cmd+Z and Shift+Z or Ctrl+Y undo and redo), `Smz3Grid` (seven columns, `gridOf` of `data.ts`: A Link to the Past's items (ALttPR's assets by `--sprite`, a leveled one's level drawn on its sprite, never as a number), under keysanity its dungeons' key cells laid as ALttPR's (`bigkey-*`, the short name top left, the big key and small keys diagonal so they keep the 24 px target spacing on a phone), Super Metroid's (Map Rando's sheet by `--frame`), the rewards; a click adds one, a right-click or "-" takes one away; one Tab stop through `useGridNavigation`), `Smz3Tokens` (the boss tokens as `aria-pressed` toggles, and how many Tourian asks for), `Smz3Keycards` (a `
`, an area a row and a level a column, its toggles one Tab stop through `useGridNavigation`), `Smz3Maps` (`Tabs` Hyrule | Zebes over one panel: Hyrule's two worlds as ALttPR's map draws them, with the smz3 pack's `HYRULE_MARKERS` (ALttPR's, by alias, as data: importing ALttPR's markers would download its world), Zebes VARIA's composed map (`assets/smz3/zebes.webp`, fetched only when its tab opens, the service worker leaving it on demand) with the pack's `ZEBES_MARKERS`; each marker a `mapspan` button named by its locations and state, its state from `logic.ts`'s `useMarkerState` (opened once all its locations are checked, else the alttpr pack's `markerStatus`, a module of its own, over the engine's answers), a click checking them all or none), `Smz3Controls` (Mode: the game picker, a preset, Super Metroid's logic, keysanity, the three thresholds, the medallion pads; New game; Undo, Redo; Autotracking, `Smz3Autotrack`; Export, Import; the stream view's address; the bar folds its labels through `useToolbarFit`, the secondary actions first, in a `toolbar-actions` span), `Smz3AutotrackDialog` (the shared `GameAutotrackDialog`, `game="SMZ3"`, and "What I read": the seed's title, which game runs, a race seed's note, its settings and, outside races, what it asks for) and `Smz3ObsView` (the grid alone, fitted by `fitObsGrid` over the rows it shows). - `autotrack/` — `AutotrackControl` (the toolbar's plug with its status dot and race-legal badge; `variant="menu"` for More), `AutotrackLiveRegion` (the polite `role="status"`, outside the foldable control, quiet while merely connecting; it adds autotracking's `news`, a switch to the seed's run type, after the status), `SeedPrompt` (the notice after `UpdatePrompt` asking to switch to the seed's run type, Switch or Keep mine, while a board with a run on it differs from the seed's), `AutotrackMoreBadge` (the dot on More once the control folds), and the lazy `AutotrackDialog` (status, the seed's run type (`autotrack-seed`, `seedLineText`), Connect/Disconnect/Retry now, Stop waiting while another tab autotracks, diagnostic report, Sync from game with what it changed and, in Race Mode, what stays the player's to mark (where entrances lead too, in a run that shuffles them), `SetupGuide` with `Tabs` per OS and the steps ticked from `setupProgress`, `Readout` "What I read" with the seed's settings, titled "from alttpr.com" when a race seed's came from its file, "from the ROM and alttpr.com" when the file adds what no ROM states, and what the board has no cell for). The dialog opens on a click only: autotracking never pops anything up during a run. Super Metroid's and SMZ3's control and dialog are the shared `GameAutotrackControl` (the button, its dot, the polite live region quiet while merely connecting; `useGameAutotrack`, the session's lifetime) and `GameAutotrackDialog` (status, actions, what the board follows, the setup guide, the game's readout as children), over a game's session from `apps/web/src/autotrack/gameSession.ts`. Each game's dialog is a chunk of its own, fetched as a pointer or focus nears its button through `components/autotrack/lazyDialog.tsx` (shared: the fetch once, the same-frame open once arrived, the Reload notice when it cannot be fetched). Its words live in `apps/web/src/autotrack/` (`status.ts` headlines, dot phases and the line under the headline, `detailOf`; `guide.ts` fixes; ALttPR's `readout.ts` names). No cell or marker says it was set by the game: once connected the game sets nearly all of them, so a mark would say little and cost every name a few words. - `games/alttpr/desktop/` — the desktop app's global shortcuts, imported behind an inline `import.meta.env.MODE === "desktop"` test so the site's build drops them (`docs/desktop.md`, "Global shortcuts"): `GlobalShortcutsPanel` (the Global tab, lazy: a switch, what the app's mechanism says and how it stands (`statusText`), the `ShortcutTable` over the global keymap with a note under each key (taken, not approved, the desktop's own trigger) and, on the command line mechanism, a Copy button per action), `globalKeymap.ts` (`globalRefusalOf`: two modifiers for a character key, never Ctrl+Alt on Windows, never a chord the window uses; the app checks a floor again), `globalShortcuts.ts` (`startGlobalShortcuts` from the module's `start`: listens for `global-step`, runs it as the window's key does, applies the bindings by the mechanism's policy, loads the layout's key labels on Linux) and `globalShortcutsStore.ts` (the choices under `alltp-tracker-global-shortcuts`, what the app reported apart). Super Metroid and SMZ3 never load it. - `desktop/` (`apps/web/src/desktop/`, the desktop build's own code, each piece behind an inline `import.meta.env.MODE === "desktop"` test and lazy) — besides the start-up (`start.ts`), the Linux AppImage's application menu entry (`docs/desktop.md`, "Application menu"): `appMenu.ts` (`useAppMenuStore`: what the app's `app_menu_state` says, `change` through `app_menu_set`, and whether the first-launch notice was answered, under `alltp-tracker-app-menu-asked`), `AppMenuPrompt` (the notice among `.notices`, rendered by each game's app: "Add Rando Atlas to your application menu?", **Add** / **Not now**, an empty live region until it has something to say) and `AppMenuItem` (the More menu's "Add to application menu" / "Remove from application menu", in a `.app-menu-entry` wrapper that `appMenu.css` uses to show More at every width; `TrackerControls` then leaves the bar's own Export, Import and Shortcuts out of the menu until the bar folds them). Super Metroid and SMZ3 have the notice only, having no More menu. -- The toolbar folds by measured fit, never by breakpoint: system fonts differ between macOS, Windows and Linux. `hooks/useToolbarFit.ts` lays a hidden copy of the bar out in each form (`FITS`, richest first) and writes the richest that holds — the full bar on one row, a folded one on two — to its `data-fit`, and what it folds to `data-fold` tokens, which `ui.css` and `styles/toolbar-menu.css` follow. The fold order: labels, then the secondary actions (Export, Import, Stream view, Shortcuts) into More, then the map layout (`menuitemradio`s in a group), then the key cells toggle (a `menuitemcheckbox`), then New game to an icon, and last Autotracking into More (the first item there, its dot copied onto the More button); Mode and Undo/Redo never fold. More reads `data-fold` as it opens to know which settings it holds. The hook re-checks on resize, content change and font load, moves up only with 24 px to spare, and never re-renders React. Never key toolbar CSS on a viewport width. `StreamViewControl` and `ShortcutsButton` take `variant="menu"` to render as a menu item with their own dialog. React bubbles a portalled dialog's events through its React parents, so a menu item's dialog still reaches the menu's handlers: they act only on events from the menu's own DOM. +- The toolbar folds by measured fit, never by breakpoint: system fonts differ between macOS, Windows and Linux. `hooks/useToolbarFit.ts` lays a hidden copy of the bar out in each form (`FITS`, richest first) and writes the richest that holds — the full bar on one row, a folded one on two — to its `data-fit`, and what it folds to `data-fold` tokens, which `ui.css` and `styles/toolbar-menu.css` follow. The fold order: the secondary actions (Export, Import, Share, Stream view, Shortcuts) lose their labels first (`secondary-icons`: Mode, New game, Autotracking, the map layout and the key cells toggle keep theirs, so a button added later costs this step a few pixels, not every label), then every button does (`icons`), then the secondary actions go into More, then the map layout (`menuitemradio`s in a group), then the key cells toggle (a `menuitemcheckbox`), then New game to an icon, and last Autotracking into More (the first item there, its dot copied onto the More button); Mode and Undo/Redo never fold. More reads `data-fold` as it opens to know which settings it holds. The hook re-checks on resize, content change and font load, moves up only with 24 px to spare, and never re-renders React. Never key toolbar CSS on a viewport width. `StreamViewControl` and `ShortcutsButton` take `variant="menu"` to render as a menu item with their own dialog. React bubbles a portalled dialog's events through its React parents, so a menu item's dialog still reaches the menu's handlers: they act only on events from the menu's own DOM. - Subscribe to the store with one selector per slice, never bare `useGameStore()`. ## Semantics and accessibility diff --git a/apps/web/src/games/sm/components/SmControls.tsx b/apps/web/src/games/sm/components/SmControls.tsx index 13256c72..81524534 100644 --- a/apps/web/src/games/sm/components/SmControls.tsx +++ b/apps/web/src/games/sm/components/SmControls.tsx @@ -239,47 +239,51 @@ export const SmControls = () => { Redo - - - - {/* OBS cannot load the desktop app's page nor follow its storage */} - {!isDesktopApp && ( + {/* The secondary actions lose their labels before the primary controls + do (ui.css); with no More menu they never move */} + - )} + + + {/* OBS cannot load the desktop app's page nor follow its storage */} + {!isDesktopApp && ( + + )} + { Redo - - - - {/* OBS cannot load the desktop app's page nor follow its storage */} - {!isDesktopApp && ( + {/* The secondary actions lose their labels before the primary controls + do (ui.css); with no More menu they never move */} + - )} + + + {/* OBS cannot load the desktop app's page nor follow its storage */} + {!isDesktopApp && ( + + )} + = { "more-autotrack": { gap: 0, padding: 0, widths: [50, 50, 50] }, "more-keys": { gap: 0, padding: 0, widths: [100, 100, 100, 100] }, "more-map": { gap: 8, padding: 0, widths: [150, 150, 150] }, + "secondary-icons": { gap: 8, padding: 0, widths: [400, 400] }, }; const layoutOf = (fit: ToolbarFit) => LAYOUTS[fit]; @@ -42,7 +43,9 @@ describe("chooseFit", () => { it.each([ [1100, "labels"], [1008, "labels"], - [1007, "icons"], + [1007, "secondary-icons"], + [808, "secondary-icons"], + [807, "icons"], [608, "icons"], [607, "more"], [408, "more"], @@ -71,13 +74,18 @@ describe("chooseFit", () => { chooseFit(700, asked); - expect(asked.mock.calls.map(([fit]) => fit)).toEqual(["labels", "icons"]); + expect(asked.mock.calls.map(([fit]) => fit)).toEqual([ + "labels", + "secondary-icons", + "icons", + ]); }); it("keeps a form while it fits, and falls back as soon as it no longer does", () => { expect(chooseFit(1008, layoutOf, "labels")).toBe("labels"); expect(chooseFit(408, layoutOf, "more")).toBe("more"); - expect(chooseFit(1007, layoutOf, "labels")).toBe("icons"); + expect(chooseFit(1007, layoutOf, "labels")).toBe("secondary-icons"); + expect(chooseFit(807, layoutOf, "secondary-icons")).toBe("icons"); expect(chooseFit(407, layoutOf, "more")).toBe("more-map"); expect(chooseFit(250, layoutOf, "labels")).toBe("more-keys"); }); @@ -87,10 +95,16 @@ describe("chooseFit", () => { "more-map", ); expect(chooseFit(408 + HYSTERESIS_PX, layoutOf, "more-map")).toBe("more"); - expect(chooseFit(1008 + HYSTERESIS_PX - 1, layoutOf, "icons")).toBe( - "icons", + expect( + chooseFit(1008 + HYSTERESIS_PX - 1, layoutOf, "secondary-icons"), + ).toBe("secondary-icons"); + expect(chooseFit(1008 + HYSTERESIS_PX, layoutOf, "secondary-icons")).toBe( + "labels", + ); + expect(chooseFit(808 + HYSTERESIS_PX - 1, layoutOf, "icons")).toBe("icons"); + expect(chooseFit(808 + HYSTERESIS_PX, layoutOf, "icons")).toBe( + "secondary-icons", ); - expect(chooseFit(1008 + HYSTERESIS_PX, layoutOf, "icons")).toBe("labels"); // Without the margin, a scrollbar's width could undo each switch expect(HYSTERESIS_PX).toBeGreaterThan(17); }); @@ -112,6 +126,7 @@ beforeEach(() => { "more-autotrack": 40, "more-keys": 80, "more-map": 100, + "secondary-icons": 300, }; seen.length = 0; vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation( @@ -236,7 +251,7 @@ describe("useToolbarFit", () => { render(); const folds: Record = {}; - for (const width of [700, 400, 250, 170, 130, 100]) { + for (const width of [900, 700, 400, 250, 170, 130, 100]) { resizeTo(width); folds[width] = [toolbar().dataset.fit, toolbar().dataset.fold]; } @@ -248,6 +263,7 @@ describe("useToolbarFit", () => { 250: ["more-map", "labels secondary map"], 400: ["more", "labels secondary"], 700: ["icons", "labels"], + 900: ["secondary-icons", "secondary-labels"], }); }); diff --git a/apps/web/src/hooks/useToolbarFit.ts b/apps/web/src/hooks/useToolbarFit.ts index 8c10503d..180dcce4 100644 --- a/apps/web/src/hooks/useToolbarFit.ts +++ b/apps/web/src/hooks/useToolbarFit.ts @@ -6,6 +6,10 @@ import { type RefObject, useLayoutEffect } from "react"; * during a run fold first: * * - `labels`: every action with its label, on one row. + * - `secondary-icons`: Export, Import, Share, Stream view and Shortcuts show + * as icons while the primary controls (Mode, New game, Autotracking, the + * map layout, the key cells toggle) keep their labels. A button added to + * the bar later costs this step its few pixels, never every label at once. * - `icons`: every action as an icon, on one row. * - `more`: Export, Import, Stream view and Shortcuts move into More. * - `more-map`: the map layout too, set once per device and the widest @@ -20,6 +24,7 @@ import { type RefObject, useLayoutEffect } from "react"; */ export const FITS = [ { fit: "labels", fold: [], rows: 1 }, + { fit: "secondary-icons", fold: ["secondary-labels"], rows: 1 }, { fit: "icons", fold: ["labels"], rows: 1 }, { fit: "more", fold: ["labels", "secondary"], rows: 2 }, { fit: "more-map", fold: ["labels", "secondary", "map"], rows: 2 }, diff --git a/apps/web/src/styles/toolbar-menu.css b/apps/web/src/styles/toolbar-menu.css index 52f8bcfc..020da45f 100644 --- a/apps/web/src/styles/toolbar-menu.css +++ b/apps/web/src/styles/toolbar-menu.css @@ -1,11 +1,15 @@ /* * How the toolbar folds, by the tokens useToolbarFit writes to its data-fold * from the widths it measures (see FITS there for the order): the secondary - * actions, then the map layout, then the key cells toggle move into the More - * menu, then New game shows as an icon, and last Autotracking moves into More + * actions lose their labels, then every button does (ui.css); then the + * secondary actions, the map layout and the key cells toggle move into the + * More menu, New game shows as an icon, and last Autotracking moves into More * too, so the bar holds on one or two rows down to the narrowest phone. + * .toolbar-actions groups the secondary actions of a bar with no More menu + * (Super Metroid, SMZ3): their labels fold as ALttPR's, they never move. */ .toolbar-secondary, +.toolbar-actions, .toolbar-autotrack { display: contents; } diff --git a/apps/web/src/styles/ui.css b/apps/web/src/styles/ui.css index 9d47a51a..7aab9e09 100644 --- a/apps/web/src/styles/ui.css +++ b/apps/web/src/styles/ui.css @@ -65,15 +65,23 @@ } /* Unless the labelled bar fits on one row (data-fold, set by useToolbarFit - from the widths it measures), its buttons are icons, square 40 px targets */ + from the widths it measures), its buttons are icons, square 40 px targets: + first the secondary actions only ("secondary-labels", inside + .toolbar-secondary or .toolbar-actions), then every button ("labels") */ .toolbar[data-fold~="labels"] .tracker-controls__button, -.toolbar[data-fold~="labels"] .bigkey-toggle { +.toolbar[data-fold~="labels"] .bigkey-toggle, +.toolbar[data-fold~="secondary-labels"] + :is(.toolbar-secondary, .toolbar-actions) + .tracker-controls__button { justify-content: center; min-width: 2.5rem; padding-inline: var(--spacing-sm); } .toolbar[data-fold~="labels"] .toolbar-label, +.toolbar[data-fold~="secondary-labels"] + :is(.toolbar-secondary, .toolbar-actions) + .toolbar-label, .toolbar[data-fold~="new-game"] .new-game__label { position: absolute; width: 1px; diff --git a/docs/screenshots/alttpr.webp b/docs/screenshots/alttpr.webp index 9059f954..a8ce46a6 100644 Binary files a/docs/screenshots/alttpr.webp and b/docs/screenshots/alttpr.webp differ diff --git a/docs/screenshots/sm.webp b/docs/screenshots/sm.webp index fab0f0ad..93e6828d 100644 Binary files a/docs/screenshots/sm.webp and b/docs/screenshots/sm.webp differ