:root { --primary-color: #00ff9d; --secondary-color: #00cc8f; --background-color: #000000; --text-color: #c8ccd0; --error-color: #ca4754; --correct-color: #00ff9d; --neutral-color: #646669; --caret-color: #00ff9d; --sub-color: #8b8e92; /* Unified top offset below fixed header/toggle */ --header-offset: 3.5rem; /* App padding variable so we can compensate where needed */ --app-padding: 1rem; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } [data-theme="light"] { --primary-color: #067a4e; --secondary-color: #05663f; --background-color: #f6f6f4; --text-color: #2b2b2b; --error-color: #b3323f; --correct-color: #067a4e; --neutral-color: #8a8d90; --caret-color: #067a4e; --sub-color: #5c5f61; --record-color: #9a6b00; --warning-color: #a35b00; --player-2: #1667c2; --player-3: #b45309; --player-4: #7c3aed; --player-5: #be123c; } [data-theme="dark"] { --primary-color: #00ff9d; --secondary-color: #00cc8f; --background-color: #000000; --text-color: #c8ccd0; --error-color: #ca4754; --correct-color: #00ff9d; --neutral-color: #646669; --caret-color: #00ff9d; --sub-color: #8b8e92; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } [data-theme="dracula"] { --primary-color: #bd93f9; --secondary-color: #ff79c6; --background-color: #282a36; --text-color: #f8f8f2; --error-color: #ff5555; --correct-color: #bd93f9; --neutral-color: #6272a4; --caret-color: #bd93f9; --sub-color: #9aa5c4; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } [data-theme="nord"] { --primary-color: #88c0d0; --secondary-color: #81a1c1; --background-color: #2e3440; --text-color: #d8dee9; --error-color: #bf616a; --correct-color: #88c0d0; --neutral-color: #4c566a; --caret-color: #88c0d0; --sub-color: #93a1b8; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } [data-theme="solarized"] { --primary-color: #268bd2; --secondary-color: #2aa198; --background-color: #002b36; --text-color: #93a1a1; --error-color: #dc322f; --correct-color: #268bd2; --neutral-color: #586e75; --caret-color: #268bd2; --sub-color: #839496; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } [data-theme="gruvbox"] { --primary-color: #fabd2f; --secondary-color: #b8bb26; --background-color: #282828; --text-color: #ebdbb2; --error-color: #fb4934; --correct-color: #fabd2f; --neutral-color: #928374; --caret-color: #fabd2f; --sub-color: #bdae93; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } [data-theme="monokai"] { --primary-color: #a6e22e; --secondary-color: #66d9ef; --background-color: #272822; --text-color: #f8f8f2; --error-color: #f92672; --correct-color: #a6e22e; --neutral-color: #75715e; --caret-color: #a6e22e; --sub-color: #b0a98d; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; --player-3: #ff9f43; --player-4: #c678dd; --player-5: #ff6b8a; } * { margin: 0; padding: 0; box-sizing: border-box; } [hidden] { display: none !important; } body { margin: 0; padding: 0; font-family: 'JetBrains Mono', monospace; background-color: var(--background-color); color: var(--text-color); height: 100vh; /* Vertical scroll is allowed (and only appears when actually needed) -- a fixed "overflow: hidden" here silently clipped any screen whose content got taller than the viewport (a wide-but-short window, a longer passage, extra stat rows) with literally no way to reach the clipped part. Horizontal stays clipped since nothing should ever need to scroll sideways. */ overflow-x: hidden; overflow-y: auto; /* Cursor is set per-theme in JS (theme.js) - a hardcoded green crosshair here would look wrong on Dracula/Nord/etc. */ } .app { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; /* avoid vertical centering that pushes content down */ height: 100vh; padding: var(--app-padding); overflow: hidden; } .logo { position: fixed; top: 1rem; left: 1rem; font-size: 1.2rem; color: var(--primary-color); font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 2px; cursor: pointer; transition: color 0.2s ease; } .logo:hover { color: var(--secondary-color); } /* Main Menu */ .main-menu { display: flex; flex-direction: column; align-items: center; /* Pinned near the top rather than vertically centered - centered left barely more room below the Start button than the mode popover needs, so on anything but a tall viewport the list ran past the bottom edge. Fixed higher, there's always real room underneath it. */ justify-content: flex-start; padding-top: 6rem; height: 100vh; width: 100%; max-width: 700px; margin: 0 auto; padding-left: 2rem; padding-right: 2rem; padding-bottom: 2rem; overflow-y: auto; box-sizing: border-box; } .main-menu h1 { font-size: 4rem; color: var(--primary-color); margin-bottom: 2rem; text-transform: uppercase; letter-spacing: 4px; } .settings-row-group { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem; } .settings-row-group .menu-button.small { width: 230px; } .menu-stat .stat-label { font-size: 0.7rem; color: var(--sub-color); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 0.2rem; } .menu-stat .stat-value { font-size: 1.3rem; color: var(--primary-color); font-weight: bold; } /* Stats Screen */ .stats-screen { width: 100%; max-width: 700px; margin: 0 auto; padding: 2rem; margin-top: calc(var(--header-offset) - var(--app-padding)); min-height: calc(100vh - var(--header-offset)); box-sizing: border-box; display: flex; flex-direction: column; align-items: center; /* Vertically centred when the content is short - these screens were top-heavy, ending around y=510 on a 900px viewport with the rest of the page empty. "safe" keeps it flex-start once the content is taller than the viewport, so a long list is never cut off above the scroll origin; browsers that do not know the keyword ignore the declaration and keep the old flex-start behaviour. */ justify-content: safe center; overflow-y: auto; } .stats-screen h2 { font-size: 1.8rem; color: var(--primary-color); text-transform: uppercase; letter-spacing: 3px; margin: 1.5rem 0 1.5rem; } .stats-screen h3 { font-size: 1rem; color: var(--sub-color); text-transform: uppercase; letter-spacing: 2px; align-self: flex-start; margin: 2rem 0 0.75rem; } .stats-summary { width: 100%; display: flex; flex-wrap: wrap; justify-content: center; gap: 1.5rem; border: 1px solid var(--neutral-color); border-radius: 8px; padding: 1.25rem 1rem; text-align: center; } .stats-empty { color: var(--sub-color); font-size: 0.9rem; align-self: flex-start; } .stats-pb-list { width: 100%; display: flex; flex-direction: column; gap: 0.5rem; } .stats-pb-row { display: flex; align-items: center; gap: 1rem; border: 1px solid var(--neutral-color); border-radius: 6px; padding: 0.6rem 1rem; } .stats-pb-mode { flex: 1; text-align: left; } .stats-pb-wpm { color: var(--primary-color); font-weight: bold; } .stats-pb-date { color: var(--sub-color); font-size: 0.8rem; min-width: 90px; text-align: right; } .leaderboard-modes { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.4rem; margin-bottom: 0.5rem; } .leaderboard-list { width: 100%; display: flex; flex-direction: column; gap: 0.5rem; } .leaderboard-row { display: flex; align-items: center; gap: 1rem; border: 1px solid var(--neutral-color); border-radius: 6px; padding: 0.6rem 1rem; } .leaderboard-rank { color: var(--sub-color); min-width: 2.5rem; } .leaderboard-name { flex: 1; text-align: left; /* A