srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/screens
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-02-25 09:23:00 +0200
committersrdusr <[email protected]>2026-02-25 09:23:00 +0200
commitd7c1c7b35e9d0995eed41d437404d5255529cf17 (patch)
treeda9fe82110da644d8150ae520ca70cfda23d1c6f /web/src/screens
parent4933f669f8338ca5e4c64cf0294818c5d15ed122 (diff)
downloadtyperpunk-d7c1c7b35e9d0995eed41d437404d5255529cf17.tar.gz
typerpunk-d7c1c7b35e9d0995eed41d437404d5255529cf17.zip
Give TyperPunk a mark, and fix the settings and custom text dialogs
Branding. The name was set in the body typeface and that was the whole of it. There is a mark now: a chevron, a baseline, and a block cursor resting on it, which reads as text being entered at a prompt and stays legible at 16 pixels. The chevron is drawn twice, the lower copy offset and in the secondary colour, which is the one stylistic note. The wordmark is split so the accent falls on the second half. Everything is drawn in the theme's own variables, so the mark follows the palette rather than carrying its own, and the favicon is the same drawing. The menu carried the name twice, as its own title and as the fixed corner mark. The corner mark exists for screens with no title of their own, so the menu keeps only the title, now the full lockup at size. The settings panel. Measured at 1280x900 it opened at y=506 with a height of 437, so it ran 140px past the bottom of a 900px window, and .app is overflow:hidden so there was nothing to scroll. It had no background either, so the fixed footer links rendered through the middle of it and, sitting higher in the stacking order, took the clicks: Playwright could not press the panel's own Close button because the footer intercepted every attempt. Both it and the custom text panel are dialogs now. Centred, opaque, above the fixed chrome, scrolling internally, with the page dimmed behind them. Their controls are buttons, but .quiet removes a button's border, so a panel of them read as a list of plain text labels with nothing to suggest any of it could be clicked. They keep an outline inside these panels. The checkbox was the browser's default control in a panel where nothing else was, and its border was too dark to see against the panel. Escape did not close either of them. The handler looked for .rail-settings-panel, which is a different panel on a different screen. A Dev section with a button that jumps straight to the results screen with invented figures was shipping to every visitor. It is limited to localhost. The top rail. The account control has moved into the icon row, right of Friends, and is the account's own picture once signed in: there is no avatar upload, so it is the initial over a colour hashed from the whole username, which gives every account a stable and distinct mark. Sign In and Sign Up sit on their own row beneath. The friends count is a badge under its icon rather than the words "2 online" beside it. That took the rail from 307px wide to 189px, which is what had been overlapping the wordmark by 75px at 390px. But it also made the rail taller, which put the close button back underneath it, so the rail now publishes its measured height and the content column starts below whatever that is. The rail is one row signed in and two signed out, so this is not a constant the stylesheet could hold. The wordmark was inline-flex, which blockified its two halves, so the name read as two lines to innerText: a screen reader, a copy-paste, or a test. The advertising slot has moved from inside the results screen to a banner across the top of the page, where a navigation bar usually goes. The header moves down to make room rather than being covered by it. It stays off the typing screen, the passive reader and the race lobby. Custom text: the panel had no title, and its hint was centred at 340px inside a 480px panel, which broke one sentence into three ragged lines. 3 browser tests pass. The store test now asserts the avatar replaces the sign-in links rather than expecting a button that has moved.
Diffstat (limited to 'web/src/screens')
-rw-r--r--web/src/screens/accountScreen.js3
-rw-r--r--web/src/screens/contributeScreen.js3
-rw-r--r--web/src/screens/endScreen.js3
-rw-r--r--web/src/screens/friendsScreen.js3
-rw-r--r--web/src/screens/leaderboardScreen.js3
-rw-r--r--web/src/screens/lyricsScreen.js3
-rw-r--r--web/src/screens/mainMenu.js22
-rw-r--r--web/src/screens/multiplayerScreen.js5
-rw-r--r--web/src/screens/passiveMode.js3
-rw-r--r--web/src/screens/placeholderScreen.js3
-rw-r--r--web/src/screens/publicProfileScreen.js3
-rw-r--r--web/src/screens/statsScreen.js3
-rw-r--r--web/src/screens/storeScreen.js3
-rw-r--r--web/src/screens/typingGame.js3
14 files changed, 42 insertions, 21 deletions
diff --git a/web/src/screens/accountScreen.js b/web/src/screens/accountScreen.js
index 5f3bdfe..b65e2d6 100644
--- a/web/src/screens/accountScreen.js
+++ b/web/src/screens/accountScreen.js
@@ -1,4 +1,5 @@
import { CLOSE_ICON } from './icons.js';
+import { logoLockup } from '../logo.js';
import { escapeHtml } from '../util.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
@@ -61,7 +62,7 @@ export function renderAccountScreen(root, { onBack, onShowAccount, onShowStats,
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Account</h2>
<div class="account-panel">${formMarkup(user)}</div>
</div>
diff --git a/web/src/screens/contributeScreen.js b/web/src/screens/contributeScreen.js
index 4c47852..b84ee40 100644
--- a/web/src/screens/contributeScreen.js
+++ b/web/src/screens/contributeScreen.js
@@ -1,4 +1,5 @@
import { escapeHtml } from '../util.js';
+import { logoLockup } from '../logo.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
import { renderTopRail } from '../topRail.js';
@@ -72,7 +73,7 @@ export function renderContributeScreen(root, { onBack, onShowStats, onShowPlaceh
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Contribute a passage</h2>
${!user ? `
<div class="stats-empty">Sign in to submit a passage. Everything submitted is reviewed before anyone types it.</div>
diff --git a/web/src/screens/endScreen.js b/web/src/screens/endScreen.js
index fa3b188..d9dc6fc 100644
--- a/web/src/screens/endScreen.js
+++ b/web/src/screens/endScreen.js
@@ -1,4 +1,5 @@
import { buildGraphPoints, buildErrorPoints, drawChart, hitTestError, hitTestLine, calculateConsistency } from '../chart.js';
+import { logoLockup } from '../logo.js';
import { escapeHtml } from '../util.js';
import { attachTooltips } from '../tooltip.js';
import { onThemeChange } from '../theme.js';
@@ -59,7 +60,7 @@ export function renderEndScreen(root, { stats, text, attribution, category, expl
root.innerHTML = `
<div class="end-screen">
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<div class="end-screen-text"><div class="text-display"></div></div>
<div class="attribution end-screen-attribution">&mdash; ${escapeHtml(attribution || 'Unknown')}${category ? `<span class="attribution-pack">${escapeHtml(category)}</span>` : ''}</div>
${explanation ? `<div class="code-explainer"><span class="code-explainer-label">What this does</span>${escapeHtml(explanation)}</div>` : ''}
diff --git a/web/src/screens/friendsScreen.js b/web/src/screens/friendsScreen.js
index e121f4c..c19809e 100644
--- a/web/src/screens/friendsScreen.js
+++ b/web/src/screens/friendsScreen.js
@@ -1,4 +1,5 @@
import { CLOSE_ICON } from './icons.js';
+import { logoLockup } from '../logo.js';
import { escapeHtml } from '../util.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
@@ -80,7 +81,7 @@ export function renderFriendsScreen(root, { onBack, onShowFriends, onShowStats,
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Friends</h2>
${user ? signedInMarkup() : signedOutMarkup()}
</div>
diff --git a/web/src/screens/leaderboardScreen.js b/web/src/screens/leaderboardScreen.js
index 8aeda98..4eb0564 100644
--- a/web/src/screens/leaderboardScreen.js
+++ b/web/src/screens/leaderboardScreen.js
@@ -1,4 +1,5 @@
import { escapeHtml } from '../util.js';
+import { logoLockup } from '../logo.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
import { renderTopRail } from '../topRail.js';
@@ -49,7 +50,7 @@ export function renderLeaderboardScreen(root, { onBack, onShowPublicProfile, onS
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Leaderboard</h2>
<div class="leaderboard-modes">
${MODES.map(m => `<button class="menu-button small quiet${m.key === selectedMode ? ' active' : ''}" data-mode="${m.key}">${escapeHtml(m.label)}</button>`).join('')}
diff --git a/web/src/screens/lyricsScreen.js b/web/src/screens/lyricsScreen.js
index 95af6b2..b669891 100644
--- a/web/src/screens/lyricsScreen.js
+++ b/web/src/screens/lyricsScreen.js
@@ -1,4 +1,5 @@
import { CLOSE_ICON } from './icons.js';
+import { logoLockup } from '../logo.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
import { renderTopRail } from '../topRail.js';
@@ -32,7 +33,7 @@ export function renderLyricsScreen(root, { onBack, onLyricsReady, onShowStats, o
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Lyrics</h2>
<div class="stats-placeholder">${escapeHtml(bodyText())}</div>
${status === 'signed-out' ? `<button class="menu-button" data-action="go-account">Sign In</button>` : ''}
diff --git a/web/src/screens/mainMenu.js b/web/src/screens/mainMenu.js
index f812509..743f4b4 100644
--- a/web/src/screens/mainMenu.js
+++ b/web/src/screens/mainMenu.js
@@ -1,4 +1,5 @@
import { STATS_ICON, SETTINGS_ICON, CHEVRON_DOWN_ICON, LEADERBOARD_ICON, STORE_ICON } from './icons.js';
+import { logoLockup } from '../logo.js';
import { parseCustomContent } from '../customText.js';
import { escapeHtml } from '../util.js';
import { getSettings, updateSettings } from '../settings.js';
@@ -8,6 +9,13 @@ import { wordListTiers } from '../wordGenerator.js';
import { renderTopRail } from '../topRail.js';
import { renderSiteFooter } from '../siteFooter.js';
+/// Whether this is a development machine. The Dev controls below are shown
+/// only here: on a deployed site they are a way to fake a result.
+function isLocalDev() {
+ const h = window.location.hostname;
+ return h === 'localhost' || h === '127.0.0.1' || h === '[::1]' || h.endsWith('.local');
+}
+
function label(m) {
if (m === 'random') return 'Random';
if (m === 'words') return 'Words';
@@ -111,7 +119,7 @@ export function renderMainMenu(root, props) {
root.innerHTML = `
<div class="main-menu">
- <h1>TyperPunk</h1>
+ ${logoLockup({ tag: 'h1', className: 'logo logo-hero', action: null })}
<div class="menu-options">
<div class="start-group sp-group">
<button class="menu-button start-main" data-action="pick-mode">Single Player</button>
@@ -166,14 +174,16 @@ export function renderMainMenu(root, props) {
</div>
</div>
</div>
+ ${isLocalDev() ? `
<div class="settings-hint">Dev</div>
- <button class="menu-button small" data-action="simulate-test" data-tooltip="Jump straight to the end screen with fake results, no typing required.">Simulate Test &rarr; End Screen</button>
+ <button class="menu-button small" data-action="simulate-test" data-tooltip="Jump straight to the end screen with fake results, no typing required.">Simulate Test &rarr; End Screen</button>` : ''}
<button class="menu-button small quiet" data-action="close-settings">Close</button>
</div>
<div class="custom-panel" hidden>
- <div class="custom-hint">Paste text, notes, code, subtitles (.srt/.vtt), or lyrics (.lrc) - typed in order, not randomized. Drag a file in, or:</div>
- <textarea class="custom-textarea" placeholder="Paste text, notes, code, or lyrics here... (or drop a file anywhere in this box)"></textarea>
+ <div class="panel-title">Custom text</div>
+ <div class="custom-hint">Typed in the order you give it, not shuffled. Notes, code, subtitles (.srt, .vtt) and lyrics (.lrc) all work. Long documents are split into segments and keep your place between visits.</div>
+ <textarea class="custom-textarea" placeholder="Paste here, or drop a file anywhere in this box"></textarea>
<div class="custom-stats" hidden></div>
<div class="custom-panel-row">
<label class="menu-button small file-label">
@@ -202,8 +212,6 @@ export function renderMainMenu(root, props) {
<div class="custom-error"></div>
</div>
- <div class="logo">TyperPunk</div>
-
<div class="menu-key-hint">Enter to start &middot; Esc for the menu</div>
<div class="corner-rail-left">
@@ -462,7 +470,7 @@ export function renderMainMenu(root, props) {
soundThemeBtn.textContent = `Sound: ${soundThemeLabel(next)}`;
});
- root.querySelector('[data-action="simulate-test"]').addEventListener('click', onSimulateTest);
+ root.querySelector('[data-action="simulate-test"]')?.addEventListener('click', onSimulateTest);
const passiveBtn = root.querySelector('[data-action="start-passive"]');
if (passiveBtn) passiveBtn.addEventListener('click', onStartPassive);
diff --git a/web/src/screens/multiplayerScreen.js b/web/src/screens/multiplayerScreen.js
index a69080d..878f659 100644
--- a/web/src/screens/multiplayerScreen.js
+++ b/web/src/screens/multiplayerScreen.js
@@ -1,4 +1,5 @@
import { escapeHtml } from '../util.js';
+import { logoLockup } from '../logo.js';
import { CLOSE_ICON, RACER_SPRITES, RACER_SPRITE_IDS } from './icons.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
@@ -84,7 +85,7 @@ export function renderMultiplayerScreen(root, { onBack, onFinish, onShowStats, o
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Multiplayer</h2>
<div class="account-panel">
<input class="account-input" type="text" id="mp-name" placeholder="Your name" value="${escapeHtml(getUser()?.username || '')}">
@@ -172,7 +173,7 @@ export function renderMultiplayerScreen(root, { onBack, onFinish, onShowStats, o
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>${auto ? 'Finding a race' : `Room ${escapeHtml(roomCode)}`}</h2>
<div class="settings-hint">${auto
? 'Racing whoever else is looking right now.'
diff --git a/web/src/screens/passiveMode.js b/web/src/screens/passiveMode.js
index e690d3c..d18334c 100644
--- a/web/src/screens/passiveMode.js
+++ b/web/src/screens/passiveMode.js
@@ -1,4 +1,5 @@
import { getTheme, toggleTheme, onThemeChange } from '../theme.js';
+import { logoLockup } from '../logo.js';
import { themeIcon } from './icons.js';
import { escapeHtml } from '../util.js';
@@ -44,7 +45,7 @@ export function renderPassiveMode(root, { name, chunks, onExit }) {
root.innerHTML = `
<div class="passive-mode">
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<div class="passive-source">${escapeHtml(name)} ยท passive mode</div>
<div class="passive-lines">
<div class="passive-line prev"></div>
diff --git a/web/src/screens/placeholderScreen.js b/web/src/screens/placeholderScreen.js
index e03dabf..d632adb 100644
--- a/web/src/screens/placeholderScreen.js
+++ b/web/src/screens/placeholderScreen.js
@@ -1,4 +1,5 @@
import { CLOSE_ICON } from './icons.js';
+import { logoLockup } from '../logo.js';
import { escapeHtml } from '../util.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
@@ -13,7 +14,7 @@ export function renderPlaceholderScreen(root, { title, description, onBack, onSh
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>${escapeHtml(title)}</h2>
<div class="stats-placeholder prose">${escapeHtml(description)}</div>
</div>
diff --git a/web/src/screens/publicProfileScreen.js b/web/src/screens/publicProfileScreen.js
index 1479df6..da8b3fd 100644
--- a/web/src/screens/publicProfileScreen.js
+++ b/web/src/screens/publicProfileScreen.js
@@ -1,4 +1,5 @@
import { escapeHtml } from '../util.js';
+import { logoLockup } from '../logo.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
import { renderTopRail } from '../topRail.js';
@@ -53,7 +54,7 @@ export function renderPublicProfileScreen(root, { username, onBack, onShowStats,
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2 class="public-profile-heading">${escapeHtml(username)}</h2>
<div class="public-profile-body"></div>
</div>
diff --git a/web/src/screens/statsScreen.js b/web/src/screens/statsScreen.js
index 9b00181..b15bf51 100644
--- a/web/src/screens/statsScreen.js
+++ b/web/src/screens/statsScreen.js
@@ -1,4 +1,5 @@
import { CLOSE_ICON } from './icons.js';
+import { logoLockup } from '../logo.js';
import { getProfileStats } from '../profileStats.js';
import { getAllPersonalBests } from '../pb.js';
import { getWeakCharDetails } from '../keyStats.js';
@@ -42,7 +43,7 @@ export function renderStatsScreen(root, { onBack, onShowPlaceholder, onShowAccou
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Stats</h2>
${profile.testsCompleted > 0 ? `
diff --git a/web/src/screens/storeScreen.js b/web/src/screens/storeScreen.js
index 7c9a047..5299582 100644
--- a/web/src/screens/storeScreen.js
+++ b/web/src/screens/storeScreen.js
@@ -1,4 +1,5 @@
import { escapeHtml } from '../util.js';
+import { logoLockup } from '../logo.js';
import { renderCornerRail } from '../cornerRail.js';
import { attachTooltips } from '../tooltip.js';
import { renderTopRail } from '../topRail.js';
@@ -168,7 +169,7 @@ export function renderStoreScreen(root, { onBack, onShowStats, onShowPlaceholder
root.innerHTML = `
<div class="stats-screen">
<button class="screen-close" data-action="menu" aria-label="Close" data-tooltip="Close (Esc)">${CLOSE_ICON}</button>
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
<h2>Store</h2>
${bodyMarkup()}
</div>
diff --git a/web/src/screens/typingGame.js b/web/src/screens/typingGame.js
index 6e2ac66..b58e11f 100644
--- a/web/src/screens/typingGame.js
+++ b/web/src/screens/typingGame.js
@@ -1,4 +1,5 @@
import { calculateStats, emptyStats } from '../stats.js';
+import { logoLockup } from '../logo.js';
import { escapeHtml } from '../util.js';
import { highlightClasses } from '../customText.js';
import { getSettings } from '../settings.js';
@@ -15,7 +16,7 @@ export function renderTypingGame(root, { game, text, attribution, category, expl
const { hideLiveStats, caretBlink, blindMode } = getSettings();
root.innerHTML = `
<div class="typing-game${hideLiveStats ? ' hide-live-stats' : ''}${caretBlink ? '' : ' no-caret-blink'}">
- <div class="logo" data-action="menu">TyperPunk</div>
+ ${logoLockup()}
${progress ? `<div class="segment-progress">${escapeHtml(progress)}</div>` : ''}
<div class="text-container">
<div class="text-display${language ? ' code' : ''}"></div>