Public Access
fix(layout): fit names on phones without sideways scroll
Display and game names shrink to 0.875rem, then end with an ellipsis (FitText + fit-text utility, CSS container units, full text in title). Rows and buttons wrap at large fonts, header points chip can shrink. Guard: scripts/check-overflow.js.
This commit is contained in:
1 parent
04387fcd0c
commit
d7810fbf94
37 files changed
+263
-79
No files matched your search
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Sideways scroll check for phones. Not part of the gate, run by hand on the local stack (see README, "Overflow check").
|
||||
* A Playwright function, run with the Playwright MCP tool browser_run_code_unsafe (filename = this file) or
|
||||
* `playwright-cli run-code`. Log in as an admin in that browser first. A logged-in browser redirects the logged-out
|
||||
* pages, so check those in a second run with LOGGED_OUT = true in a clean browser (no session).
|
||||
* A hit is: scrollWidth != clientWidth, an element sticking out on the left (never raises scrollWidth), or a
|
||||
* redirect away from the page.
|
||||
* Content clipped inside an overflow-hidden ancestor is not checked (same rule as scrollWidth).
|
||||
*/
|
||||
async (page) => {
|
||||
const BASE = 'http://localhost:5173';
|
||||
const WIDTHS = [360, 390, 412, 414];
|
||||
const FONT_PERCENT = 100; // set 130 for the large system font run
|
||||
const DARK = false; // set true for dark mode
|
||||
const SKIN = ''; // e.g. 'pastel' to check a skin
|
||||
const LOGGED_OUT = false; // true: only the logged-out pages, run in a browser without a session
|
||||
const GAME_ID = 620; // any appid in the library
|
||||
const PROFILE_NAME = 'Dev'; // display name of the logged-in user, the profile must be public
|
||||
const PAGES = LOGGED_OUT ? ['/', '/login', '/register', '/reset-password', '/impressum', `/u/${encodeURIComponent(PROFILE_NAME)}`] : [
|
||||
'/dashboard', '/library', `/library/${GAME_ID}`, '/wishlist', '/play-next', '/stats', '/import', '/points',
|
||||
'/settings', '/whats-new', '/welcome', '/audit/games', '/audit/links', '/audit/log', '/admin/users',
|
||||
'/admin/hints', '/admin/news', '/admin/broadcasts', `/u/${encodeURIComponent(PROFILE_NAME)}`,
|
||||
];
|
||||
|
||||
const hits = [];
|
||||
let checked = 0;
|
||||
for (const width of WIDTHS) {
|
||||
await page.setViewportSize({width, height: 800});
|
||||
for (const path of PAGES) {
|
||||
await page.goto(BASE + path); // no networkidle: some pages poll
|
||||
await page.waitForTimeout(1500);
|
||||
if (FONT_PERCENT !== 100) await page.addStyleTag({content: `html{font-size:${FONT_PERCENT}%}`});
|
||||
await page.evaluate(([dark, skin]) => {
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
if (skin) document.documentElement.dataset.skin = skin;
|
||||
}, [DARK, SKIN]);
|
||||
await page.waitForTimeout(300);
|
||||
const result = await page.evaluate(() => {
|
||||
const root = document.documentElement;
|
||||
const describe = (element) => `${element.tagName.toLowerCase()}.${String(element.className).slice(0, 60)}`;
|
||||
const all = [...document.querySelectorAll('body *')].map((element) => [element, element.getBoundingClientRect()]);
|
||||
const right = all.filter(([, box]) => box.right > root.clientWidth).sort((a, b) => b[1].right - a[1].right);
|
||||
// Clipped by an overflow:hidden/auto ancestor (an effect preview, a scroller): not visible, not a hit.
|
||||
const clipped = (element) => {
|
||||
for (let parent = element.parentElement; parent && parent !== document.body; parent = parent.parentElement) {
|
||||
if (getComputedStyle(parent).overflowX !== 'visible') return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const left = all.filter(([element, box]) => box.width > 0 && box.left < 0 && !clipped(element));
|
||||
if (root.scrollWidth === root.clientWidth && left.length === 0) return null;
|
||||
return {
|
||||
scrollWidth: root.scrollWidth, clientWidth: root.clientWidth,
|
||||
widest: right.slice(0, 5).map(([element]) => describe(element)),
|
||||
offLeft: left.slice(0, 5).map(([element]) => describe(element)),
|
||||
};
|
||||
});
|
||||
checked++;
|
||||
const landed = await page.evaluate(() => location.pathname);
|
||||
if (decodeURIComponent(landed) !== decodeURIComponent(path)) hits.push({path, width, redirectedTo: landed});
|
||||
if (result) hits.push({path, width, ...result});
|
||||
}
|
||||
}
|
||||
return {checked, hits};
|
||||
}
|
||||
Reference in new issue
Block a user