diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/src/screens/endScreen.js | 50 | ||||
| -rw-r--r-- | web/src/screens/mainMenu.js | 22 | ||||
| -rw-r--r-- | web/src/styles.css | 77 |
3 files changed, 101 insertions, 48 deletions
diff --git a/web/src/screens/endScreen.js b/web/src/screens/endScreen.js index b03c306..2787f76 100644 --- a/web/src/screens/endScreen.js +++ b/web/src/screens/endScreen.js @@ -165,46 +165,16 @@ export function renderEndScreen(root, { stats, text, attribution, explanation, s // passage happened to be on screen when it was measured. This claws the // room back in stages instead, each one only kicking in if the last // wasn't enough, so the page itself never needs to scroll to reach - // Play Again: shrink the graph down to a floor, then trim the button's - // top margin, and only as a last resort (a passage long enough that even - // both of those can't absorb it - realistically only reachable by - // pasting the full text-mode word buffer instead of actually typing it - // in the time given) let the passage preview itself scroll internally, - // which keeps every control below it reachable without the page - // scrolling. Runs once after layout settles. - requestAnimationFrame(() => { - const doc = document.documentElement; - // Re-measured after every stage rather than tracked as a running - // subtraction - margin-collapse and sub-pixel rounding meant an - // assumed "reduced height X means Y px less overflow" estimate - // drifted from the DOM's actual scrollHeight, consistently - // undershooting by a fixed amount however much was cut. - const overflowNow = () => doc.scrollHeight - doc.clientHeight; - if (overflowNow() <= 0) return; - - const graphBox = root.querySelector('.graph-container'); - const MIN_GRAPH_HEIGHT = 120; - const graphHeight = graphBox.getBoundingClientRect().height; - if (graphHeight > MIN_GRAPH_HEIGHT) { - const reduceBy = Math.min(overflowNow(), graphHeight - MIN_GRAPH_HEIGHT); - graphBox.style.height = `${graphHeight - reduceBy}px`; - redraw(); - } - if (overflowNow() <= 0) return; - - const buttons = root.querySelector('.end-screen-buttons'); - const buttonsMargin = parseFloat(getComputedStyle(buttons).marginTop); - if (buttonsMargin > 8) { - const reduceBy = Math.min(overflowNow(), buttonsMargin - 8); - buttons.style.marginTop = `${buttonsMargin - reduceBy}px`; - } - if (overflowNow() <= 0) return; - - const textBox = root.querySelector('.end-screen-text'); - const textHeight = textBox.getBoundingClientRect().height; - textBox.style.maxHeight = `${Math.max(80, textHeight - overflowNow() - 4)}px`; - textBox.style.overflowY = 'auto'; - }); + // There was an auto-fit routine here that forced this screen into one + // viewport: it shrank the graph to a 120px floor, then trimmed the Play + // Again margin, then capped the passage box at 80px with its own + // scrollbar. On an ordinary laptop window that left the two things the + // screen exists to show - the passage you just typed and the graph of + // how you typed it - both too small to read, and the passage clipped + // rather than scrolled to. + // + // A results screen is read, not acted on under time pressure, so the + // page scrolls instead. Nothing is hidden and nothing is squeezed. const tooltip = document.createElement('div'); tooltip.className = 'chart-tooltip'; diff --git a/web/src/screens/mainMenu.js b/web/src/screens/mainMenu.js index 1a3ce87..2d27468 100644 --- a/web/src/screens/mainMenu.js +++ b/web/src/screens/mainMenu.js @@ -240,12 +240,26 @@ export function renderMainMenu(root, props) { function position() { const rect = groupEl.getBoundingClientRect(); const gap = 8; - const spaceBelow = window.innerHeight - rect.bottom - gap; + // Clear of the bottom edge: the corner rails and the footer live + // down there, and a panel that runs to the last pixel of the + // window reads as cut off rather than as a menu. + const bottomInset = 88; + const spaceBelow = window.innerHeight - rect.bottom - gap - bottomInset; + const spaceAbove = rect.top - gap - 16; + popoverEl.style.left = `${rect.left}px`; popoverEl.style.width = `${rect.width}px`; - popoverEl.style.top = `${rect.bottom + gap}px`; - popoverEl.style.bottom = 'auto'; - popoverEl.style.maxHeight = `${Math.max(100, spaceBelow)}px`; + // Opens upward when there is more room there, which is what a + // short window leaves. + if (spaceBelow < 220 && spaceAbove > spaceBelow) { + popoverEl.style.top = 'auto'; + popoverEl.style.bottom = `${window.innerHeight - rect.top + gap}px`; + popoverEl.style.maxHeight = `${Math.max(140, spaceAbove)}px`; + } else { + popoverEl.style.top = `${rect.bottom + gap}px`; + popoverEl.style.bottom = 'auto'; + popoverEl.style.maxHeight = `${Math.max(140, spaceBelow)}px`; + } } chevronEl.addEventListener('click', e => { e.stopPropagation(); diff --git a/web/src/styles.css b/web/src/styles.css index c56ac27..3e4aea4 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -12,6 +12,9 @@ --header-offset: 3.5rem; /* App padding variable so we can compensate where needed */ --app-padding: 1rem; + /* Room at the foot of every screen for the fixed footer and the corner + rails, so normal-flow content never ends up underneath them. */ + --bottom-chrome: 5.5rem; --record-color: #ffd166; --warning-color: #ffb454; --player-2: #4ea3ff; @@ -266,6 +269,7 @@ body { margin-top: calc(var(--header-offset) - var(--app-padding)); min-height: calc(100vh - var(--header-offset)); box-sizing: border-box; + padding-bottom: var(--bottom-chrome); display: flex; flex-direction: column; align-items: center; @@ -1571,7 +1575,7 @@ body { away first if a genuinely tall passage would otherwise force a scrollbar, so it never fights that logic, it just guarantees normal breathing room in the common case. */ - padding-bottom: 2rem; + padding-bottom: var(--bottom-chrome); box-sizing: border-box; } @@ -2039,7 +2043,7 @@ body::-webkit-scrollbar, .app::-webkit-scrollbar, #root::-webkit-scrollbar { width: 100% !important; overflow-x: auto !important; margin: 0 auto 1rem auto !important; - height: 160px !important; + height: 220px !important; } .end-screen-buttons { position: static !important; @@ -2067,7 +2071,7 @@ body::-webkit-scrollbar, .app::-webkit-scrollbar, #root::-webkit-scrollbar { padding: 0 0.5rem !important; } .graph-container { - height: 180px !important; + height: 200px !important; padding: 0.5rem !important; } .endscreen-side-stat.wpm, .endscreen-side-stat.acc { @@ -2397,7 +2401,7 @@ body::-webkit-scrollbar, .app::-webkit-scrollbar, #root::-webkit-scrollbar { title and the buttons where it competed with them for attention. */ .menu-key-hint { position: fixed; - bottom: 1.25rem; + bottom: 3.25rem; left: 50%; transform: translateX(-50%); font-size: 0.75rem; @@ -3081,3 +3085,68 @@ body::-webkit-scrollbar, .app::-webkit-scrollbar, #root::-webkit-scrollbar { border-radius: 4px; padding: 0.1rem 0.35rem; } + + +/* The end screen is a flex column, so every child shrinks by default once the + content is taller than the viewport. That squeezed the two things the + screen exists to show: on a 650px window the passage came out 80px tall and + the graph 120px, both unreadable, and the passage was clipped rather than + scrolled to. They keep their size now and the page scrolls instead, which + is the correct trade for a screen you read rather than one you act on. */ +.end-screen-text, +.end-screen-graph-row, +.end-screen-stat-row, +.end-screen-attribution, +.code-explainer, +.mp-standings, +.end-screen-buttons { + flex-shrink: 0; +} + +/* Below this the chart stops carrying information. */ +.graph-container { + min-height: 180px; +} + +@media (max-height: 600px) { + .graph-container { + min-height: 150px; + } +} + + +/* Bottom chrome on a narrow screen. + The footer is centred and grows to the full width once the links wrap, so + at 375px it ran straight through both corner rails and covered Play Again, + which made the button unclickable. Stacked instead: rails on the bottom + line, footer above them, keyboard hint above that, and the reserved space + grows to match. */ +@media (max-width: 700px) { + :root { + --bottom-chrome: 12rem; + } + + .site-footer { + bottom: 4rem; + flex-wrap: wrap; + justify-content: center; + max-width: calc(100vw - 2rem); + row-gap: 0.2rem; + } + + /* Clears the footer even when its links wrap onto a second line, which + they do below about 400px. */ + .menu-key-hint { + bottom: 9.5rem; + } + + /* The narrow-width rule above sets this to 0.5rem, which is what left the + content underneath the chrome in the first place. */ + .end-screen { + padding-bottom: var(--bottom-chrome) !important; + } + + .stats-screen { + padding-bottom: var(--bottom-chrome); + } +} |