From c9461fe84dbbc194765d52e36c6f8d3e025b9e65 Mon Sep 17 00:00:00 2001
From: ttrenty <154608953+ttrenty@users.noreply.github.com>
Date: Fri, 11 Sep 2026 07:03:38 -0400
Subject: [PATCH] fix: improve phone check to reduce false positives
---
assets/css/main.css | 60 +++++++++++++++++++++++++------
assets/js/site.js | 3 +-
layouts/partials/footer.html | 5 ++-
scripts/browser_check.cjs | 69 ++++++++++++++++++++++++++++++++++--
4 files changed, 122 insertions(+), 15 deletions(-)
diff --git a/assets/css/main.css b/assets/css/main.css
index c58d9e9..e79efbb 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -1296,6 +1296,13 @@ button:disabled { cursor: default; opacity: 0.6; }
gap: 0.8rem;
}
+.footer-bottom-links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem 1rem;
+ font-size: 0.875rem;
+}
+
.cookie-notice {
position: fixed;
right: 1rem;
@@ -1462,18 +1469,33 @@ button:disabled { cursor: default; opacity: 0.6; }
margin: 0.25rem 0;
}
-@media (max-width: 980px) {
- .content-grid,
- .feature-split,
- .page-hero-grid,
- .person-hero,
- .personal-grid,
- .personal-hero-grid,
- .news-layout,
- .footer-grid {
- grid-template-columns: 1fr;
+/* Keep the full navigation on smaller desktop viewports, including scaled
+ displays and browser windows narrowed by sidebars. Compact the branding
+ before switching to the phone menu; touch capability does not choose layout. */
+@media (max-width: 1100px) {
+ .site-header .site-shell {
+ gap: 1rem;
+ }
+
+ .brand-row {
+ flex-shrink: 0;
+ gap: 0.6rem;
}
+ .brand-wordmark {
+ height: 3.4rem;
+ }
+
+ .uoft-mark img {
+ height: 2.5rem;
+ }
+
+ .site-nav {
+ column-gap: 0.75rem;
+ }
+}
+
+@media (max-width: 980px) {
.card-grid,
.card-grid-wide {
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1482,9 +1504,25 @@ button:disabled { cursor: default; opacity: 0.6; }
.meta-card-soft {
grid-template-columns: 1fr;
}
+
+ .footer-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
}
-@media (max-width: 980px) {
+/* Keep this breakpoint in sync with mobileNav in assets/js/site.js. */
+@media (max-width: 760px) {
+ .content-grid,
+ .feature-split,
+ .page-hero-grid,
+ .person-hero,
+ .personal-grid,
+ .personal-hero-grid,
+ .news-layout,
+ .footer-grid {
+ grid-template-columns: 1fr;
+ }
+
:root {
--sticky-header-offset: 4.25rem;
}
diff --git a/assets/js/site.js b/assets/js/site.js
index f3f9d55..2b18fd4 100644
--- a/assets/js/site.js
+++ b/assets/js/site.js
@@ -3,7 +3,8 @@ document.addEventListener("DOMContentLoaded", () => {
const navPanel = document.querySelector("[data-nav-panel]");
if (navButton && navPanel) {
- const mobileNav = window.matchMedia("(max-width: 980px)");
+ // Match the phone navigation breakpoint in assets/css/main.css.
+ const mobileNav = window.matchMedia("(max-width: 760px)");
const setNavOpen = (open) => {
navButton.setAttribute("aria-expanded", String(open));
navPanel.classList.toggle("is-open", open);
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index 27087ff..b7b5fef 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -40,6 +40,9 @@
Contact
diff --git a/scripts/browser_check.cjs b/scripts/browser_check.cjs
index 8a3d7cf..caadf28 100644
--- a/scripts/browser_check.cjs
+++ b/scripts/browser_check.cjs
@@ -38,7 +38,8 @@ let browser;
}
report.baseURL = base;
browser = await ({ chromium, firefox, webkit }[engine]).launch(
- process.env.BROWSER_EXECUTABLE ? { executablePath: process.env.BROWSER_EXECUTABLE } : {},
+ process.env.BROWSER_EXECUTABLE ? { executablePath: process.env.BROWSER_EXECUTABLE } :
+ process.env.BROWSER_CHANNEL ? { channel: process.env.BROWSER_CHANNEL } : {},
);
const createContext = async (options = {}, dismissNotice = true) => {
const context = await browser.newContext(options);
@@ -49,6 +50,70 @@ let browser;
context.on('page', page => page.on('pageerror', error => report.errors.push(error.message)));
return context;
};
+
+ // Width is measured in CSS pixels: a scaled desktop can be below 980px.
+ // Pixel density and a touchscreen must not independently collapse its menu.
+ report.navigation = [];
+ for (const options of [
+ { deviceScaleFactor: 1 },
+ { deviceScaleFactor: 1.25 },
+ { deviceScaleFactor: 1.5 },
+ { deviceScaleFactor: 2, hasTouch: true },
+ { javaScriptEnabled: false },
+ ]) {
+ const responsiveContext = await createContext(options);
+ const responsive = await responsiveContext.newPage();
+ for (const width of [1440, 1100, 1024, 981, 980, 911, 853, 800, 768, 761, 760, 700, 390, 320]) {
+ await responsive.setViewportSize({ width, height: 900 });
+ await responsive.goto(base + '/');
+ const desktop = width > 760;
+ const scripted = options.javaScriptEnabled !== false;
+ const label = `${width}px ${JSON.stringify(options)}`;
+ assert.equal(await responsive.locator('[data-nav-toggle]').isVisible(), !desktop && scripted, `${label}: wrong menu layout`);
+ assert.equal(await responsive.locator('#site-nav').isVisible(), desktop || !scripted, `${label}: wrong navigation visibility`);
+ const layout = await responsive.evaluate(() => {
+ const header = document.querySelector('.site-header').getBoundingClientRect();
+ const brand = document.querySelector('.brand-row').getBoundingClientRect();
+ const links = [...document.querySelectorAll('#site-nav > .nav-item > a')].map(link => {
+ const rect = link.getBoundingClientRect();
+ return { top: rect.top, bottom: rect.bottom, left: rect.left, right: rect.right };
+ });
+ return {
+ overflow: document.documentElement.scrollWidth > innerWidth + 1,
+ columns: getComputedStyle(document.querySelector('.content-grid')).gridTemplateColumns.split(' ').length,
+ linksFit: links.every(rect => rect.left >= brand.right && rect.right <= innerWidth && rect.top >= header.top && rect.bottom <= header.bottom),
+ oneRow: links.every(rect => Math.abs(rect.top - links[0].top) < 1),
+ };
+ });
+ assert.equal(layout.overflow, false, `${label}: horizontal overflow`);
+ assert.equal(layout.columns, desktop ? 2 : 1, `${label}: wrong content layout`);
+ if (desktop) {
+ assert(layout.linksFit && layout.oneRow, `${label}: desktop links must fit beside the branding in one row`);
+ }
+ report.navigation.push({ width, ...options, desktop });
+ }
+ if (options.javaScriptEnabled !== false) {
+ // Crossing the breakpoint must reset state in both directions without a reload.
+ await responsive.locator('[data-nav-toggle]').click();
+ await responsive.setViewportSize({ width: 800, height: 900 });
+ await responsive.waitForFunction(() => document.querySelector('[data-nav-toggle]').getAttribute('aria-expanded') === 'false');
+ assert(await responsive.locator('#site-nav').isVisible(), 'Resizing to desktop must reveal navigation');
+ await responsive.setViewportSize({ width: 760, height: 900 });
+ assert.equal(await responsive.locator('#site-nav').isVisible(), false, 'Returning to mobile must close the menu');
+ await responsive.locator('[data-nav-toggle]').click();
+ assert(await responsive.locator('#site-nav').isVisible(), 'Mobile menu must still open after resizing');
+ await responsive.keyboard.press('Escape');
+ assert.equal(await responsive.locator('#site-nav').isVisible(), false, 'Escape must close the mobile menu');
+ }
+ await responsiveContext.close();
+ }
+ console.log(`${engine}: ${report.navigation.length} desktop/mobile layout configurations passed`);
+ if (process.env.RESPONSIVE_ONLY) {
+ assert.deepEqual(report.errors, [], 'JavaScript errors found');
+ fs.writeFileSync(path.join(output, 'report.json'), JSON.stringify(report, null, 2));
+ console.log(`Artifacts: ${output}`);
+ return;
+ }
for (const width of process.env.SKIP_PAGE_CRAWL ? [] : report.widths) {
const context = await createContext({ viewport: { width, height: 900 } });
const page = await context.newPage();
@@ -82,7 +147,7 @@ let browser;
await menu.click();
await page.locator('#site-nav a[href="/data-sets/"]').click();
await page.waitForURL('**/data-sets/');
- await page.setViewportSize({ width: 844, height: 390 });
+ await page.setViewportSize({ width: 740, height: 390 });
await menu.click();
assert(await page.locator('#site-nav').evaluate(element => element.getBoundingClientRect().bottom <= innerHeight + 1), 'Landscape menu must fit the viewport');
await page.locator('#site-nav a[href="/publications/"]').click();