diff options
| author | srdusr <[email protected]> | 2025-09-12 01:11:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-09-12 01:11:00 +0200 |
| commit | 29992fe40703cf567177d70f152811bfedb0caad (patch) | |
| tree | 244298c668a9c1b44204dd8ea557ddd06991f0f4 /web/src/screens | |
| parent | a726d9f5fb56e1fd7983c5ac806d408ad78daa86 (diff) | |
| download | typerpunk-29992fe40703cf567177d70f152811bfedb0caad.tar.gz typerpunk-29992fe40703cf567177d70f152811bfedb0caad.zip | |
Rework the corner layout and move mode selection out of the button stack
- Settings and Store move onto the wordmark's own line. Stacked beneath it
they pushed into the space the passage needs and read as a second,
unrelated column.
- The live player count moves to the bottom-left beside a Multiplayer icon,
as readable text ("5 racing") rather than a corner badge. As a badge it was
a bare digit with nothing saying what it counted, sharing a corner with two
other buttons.
- Bottom-right is now just Stats and Leaderboard.
- Mode selection becomes a row of chips below both start buttons. It has been
three things before this: a chevron notched into the Single Player button,
a caption wedged between the two buttons, and a pill sitting on top of them.
As its own row it reads as a setting for the run rather than part of the
button stack, and every common mode is one click instead of a dropdown
away. The eleven topic packs stay behind a "Text" chip.
- "Multiplayer" becomes "Multi-Player", so the two buttons read as a pair.
Diffstat (limited to 'web/src/screens')
| -rw-r--r-- | web/src/screens/mainMenu.js | 72 |
1 files changed, 44 insertions, 28 deletions
diff --git a/web/src/screens/mainMenu.js b/web/src/screens/mainMenu.js index 909740d..a5e9df1 100644 --- a/web/src/screens/mainMenu.js +++ b/web/src/screens/mainMenu.js @@ -7,6 +7,7 @@ import { attachTooltips } from '../tooltip.js'; import { wordListTiers } from '../wordGenerator.js'; import { renderTopRail } from '../topRail.js'; import { onCountsChange, getCounts } from '../counts.js'; +import { MULTIPLAYER_ICON } from './icons.js'; function label(m) { if (m === 'random') return 'Random'; @@ -78,6 +79,7 @@ export function renderMainMenu(root, props) { const gameModes = [...GAME_MODES.filter(m => modes.includes(m)), 'custom']; const textCategories = modes.filter(m => !gameModes.includes(m)); const modeItem = m => `<button class="mode-popover-item${m === currentMode ? ' active' : ''}" data-mode="${escapeHtml(m)}" data-tooltip="${escapeHtml(modeDescription(m))}">${escapeHtml(label(m))}</button>`; + const modeChip = m => `<button class="mode-chip${m === currentMode ? ' active' : ''}" data-mode="${escapeHtml(m)}" data-tooltip="${escapeHtml(modeDescription(m))}">${escapeHtml(label(m))}</button>`; const currentIndex = Math.max(0, modes.indexOf(selectedCategory || 'random')); const currentMode = modes[currentIndex] || 'random'; const isCustom = currentMode === 'custom'; @@ -113,28 +115,29 @@ export function renderMainMenu(root, props) { <div class="main-menu"> <h1>TyperPunk</h1> <div class="menu-options"> - <button class="mode-pill sp-chevron" data-tooltip="Choose what to type"> - <span class="mode-pill-label">${escapeHtml(startLabel)}${rulesSummary ? ` · ${escapeHtml(rulesSummary)}` : ''}</span> - ${CHEVRON_DOWN_ICON} - </button> <div class="start-group sp-group"> <button class="menu-button start-main" data-action="start">Single Player</button> - <div class="mode-popover sp-popover" hidden> - <div class="mode-popover-group"> - <div class="mode-popover-heading">Modes</div> - ${gameModes.map(modeItem).join('')} - <button class="mode-popover-item" data-action="mode-lyrics" data-tooltip="Connect Spotify and type along to whatever's playing.">Lyrics</button> - </div> - <div class="mode-popover-group"> - <div class="mode-popover-heading">Text</div> - ${textCategories.map(modeItem).join('')} - </div> - </div> </div> <div class="start-group mp-group"> - <button class="menu-button start-main" data-action="mode-multiplayer" data-tooltip="Race other typists live.">Multiplayer</button> + <button class="menu-button start-main" data-action="mode-multiplayer" data-tooltip="Race other typists live.">Multi-Player</button> </div> - <div class="start-caption mp-caption" hidden></div> + + <div class="mode-bar"> + ${gameModes.map(modeChip).join('')} + <button class="mode-chip" data-action="mode-lyrics" data-tooltip="Connect Spotify and type along to whatever's playing.">Lyrics</button> + <div class="mode-chip-group"> + <button class="mode-chip sp-chevron${textCategories.includes(currentMode) ? ' active' : ''}" data-tooltip="Passages from a topic"> + ${textCategories.includes(currentMode) ? escapeHtml(label(currentMode)) : 'Text'} ${CHEVRON_DOWN_ICON} + </button> + <div class="mode-popover sp-popover" hidden> + <div class="mode-popover-group"> + <div class="mode-popover-heading">Text</div> + ${textCategories.map(modeItem).join('')} + </div> + </div> + </div> + </div> + ${rulesSummary ? `<div class="start-caption sp-caption">${escapeHtml(rulesSummary)}</div>` : ''} ${isCustom && customText && customText.timed ? `<button class="menu-button" data-action="start-passive">Passive Mode</button>` : ''} ${isCustom && customText ? `<button class="menu-button" data-action="clear-custom">Clear Custom Text</button>` : ''} </div> @@ -201,6 +204,11 @@ export function renderMainMenu(root, props) { <button class="corner-icon-button" data-action="store" aria-label="Store" data-tooltip="Store">${STORE_ICON}</button> </div> + <div class="corner-rail-bottom-left"> + <button class="corner-icon-button" data-action="mode-multiplayer" aria-label="Multiplayer" data-tooltip="Multiplayer">${MULTIPLAYER_ICON}</button> + <span class="online-count" data-badge="online" hidden></span> + </div> + <div class="corner-rail-right"> ${profile.testsCompleted > 0 ? `<div class="stats-corner-figures" data-tooltip="Your average WPM across every test, and your best single result."> <div class="stats-corner-avg">avg ${profile.averageWpm}</div> @@ -216,13 +224,19 @@ export function renderMainMenu(root, props) { attachTooltips(root); const cleanupTheme = renderTopRail(root, { onShowAccount, onShowFriends }); - // Live count under the Multiplayer button. The Friends badge is the top - // rail's, painted there. - const mpCaptionEl = root.querySelector('.mp-caption'); + // The Friends badge belongs to the top rail. The player count is this + // screen's own, since the menu builds its corner clusters inline. + const onlineEl = root.querySelector('.corner-rail-bottom-left [data-badge="online"]'); + const mpIconBtn = root.querySelector('.corner-rail-bottom-left [data-action="mode-multiplayer"]'); function paintCounts({ online }) { - if (mpCaptionEl) { - mpCaptionEl.hidden = !online; - mpCaptionEl.textContent = online === 1 ? '1 racing now' : `${online} racing now`; + if (onlineEl) { + onlineEl.hidden = !online; + onlineEl.textContent = online === 1 ? '1 racing' : `${online} racing`; + } + if (mpIconBtn) { + mpIconBtn.dataset.tooltip = !online + ? 'Multiplayer' + : (online === 1 ? 'Multiplayer - 1 player racing now' : `Multiplayer - ${online} players racing now`); } } paintCounts(getCounts()); @@ -272,12 +286,14 @@ export function renderMainMenu(root, props) { root.querySelector('.sp-popover'), null, ); - root.querySelectorAll('.sp-popover .mode-popover-item[data-mode]').forEach(item => { + root.querySelectorAll('.sp-popover .mode-popover-item[data-mode], .mode-bar .mode-chip[data-mode]').forEach(item => { item.addEventListener('click', () => onSelectCategory(item.dataset.mode)); }); root.querySelector('[data-action="mode-lyrics"]').addEventListener('click', onShowLyrics); - root.querySelector('[data-action="mode-multiplayer"]').addEventListener('click', onShowMultiplayer); + // Two of these now: the big button and the bottom-left icon beside the + // live player count. + root.querySelectorAll('[data-action="mode-multiplayer"]').forEach(el => el.addEventListener('click', onShowMultiplayer)); const startBtn = root.querySelector('.sp-group .start-main'); const panel = root.querySelector('.custom-panel'); @@ -342,15 +358,15 @@ export function renderMainMenu(root, props) { // them change the selected mode, and a full re-render was resetting // .settings-panel back to hidden on every single click, forcing you to // reopen Settings after each change. - // The mode pill above the buttons carries the mode name and its rules. - const startSubEl = root.querySelector('.mode-pill-label'); + // The rules line under the mode bar, for the modes that have rules. + const startSubEl = root.querySelector('.sp-caption'); function refreshRulesSummary() { if (!startSubEl) return; const s = getSettings(); const parts = [isWords ? `${liveWordCount} words` : `${liveTimeDuration}s`]; if (s.wordsPunctuation) parts.push('punctuation'); if (s.wordsNumbers) parts.push('numbers'); - startSubEl.textContent = `${startLabel} · ${parts.join(' · ')}`; + startSubEl.textContent = parts.join(' · '); } let liveWordCount = wordCount; |