Hollis Creek Family Dental · Repair report

Exhibit A-1 Evidence: commit c4c8ced

Committed · c4c8cedbe2276d3e1160a4379f9cfc092c518e1e

The booking button was off the screen on phones

The repository itself is private; this page carries the commit as it is recorded there.

Commit message

Fix A-1: fluid header so the booking button fits a 390px phone

Root cause: a 2019 child-theme rule fixed .header-inner at 1180px, stopped
the menu from wrapping and hid the small-screen menu toggle, so on a 390px
phone the page scrolled sideways and "Request an appointment" sat at x>390.

Fix: drop the override and let ClinicPress's fluid header work again: the
menu collapses behind its toggle under 900px and the booking button takes
its own full-width row.

Proof: e2e/tests/a1-mobile-layout.spec.ts (390px, both sites).

Exhibit: A-1

The change

Text files only, as recorded in git. Lines starting with + were added, lines with − removed.

Files changed

---
 e2e/tests/a1-mobile-layout.spec.ts            | 43 +++++++++++++++++++++++++++++++++++++++++++
 site/wp-content/themes/hollis-child/style.css | 20 --------------------
 2 files changed, 43 insertions(+), 20 deletions(-)
diff --git a/e2e/tests/a1-mobile-layout.spec.ts b/e2e/tests/a1-mobile-layout.spec.ts
new file mode 100644
index 0000000..e2ad17d
--- /dev/null
+++ b/e2e/tests/a1-mobile-layout.spec.ts
@@ -0,0 +1,43 @@
+import { test, expect } from '@playwright/test';
+import { BEFORE, AFTER } from './helpers';
+
+// Exhibit A-1. On a 390px phone the BEFORE page scrolls sideways and the booking button sits off screen;
+// the AFTER page fits the screen, the button is in view and the menu opens behind its toggle.
+test.use({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });
+
+async function layout(page: import('@playwright/test').Page) {
+  const cta = page.locator('.site-header .header-cta');
+  const box = await cta.boundingBox();
+  const width = await page.evaluate(() => document.documentElement.scrollWidth);
+  return { width, box };
+}
+
+test('A-1 before: the page is wider than the phone and the booking button is off screen', async ({ page }) => {
+  await page.goto(BEFORE + '/');
+  const { width, box } = await layout(page);
+  expect(width).toBeGreaterThan(390);
+  expect(box).not.toBeNull();
+  expect(box!.x + box!.width).toBeGreaterThan(390);
+});
+
+test('A-1 after: the page fits 390px, the booking button is in view and leads to the form', async ({ page }) => {
+  await page.goto(AFTER + '/');
+  const { width, box } = await layout(page);
+  expect(width).toBeLessThanOrEqual(390);
+  expect(box!.x).toBeGreaterThanOrEqual(0);
+  expect(box!.x + box!.width).toBeLessThanOrEqual(390);
+  await page.locator('.site-header .header-cta').click();
+  await expect(page.getByRole('heading', { level: 1, name: 'Request an appointment' })).toBeVisible();
+});
+
+test('A-1 after: the menu opens from its toggle and reaches the team page', async ({ page }) => {
+  await page.goto(AFTER + '/');
+  const toggle = page.getByRole('button', { name: 'Menu' });
+  await expect(toggle).toBeVisible();
+  await expect(page.getByRole('navigation', { name: 'Primary' }).getByRole('link', { name: 'Our team' })).toBeHidden();
+  await toggle.click();
+  await expect(toggle).toHaveAttribute('aria-expanded', 'true');
+  await page.getByRole('navigation', { name: 'Primary' }).getByRole('link', { name: 'Our team' }).click();
+  await expect(page.getByRole('heading', { level: 3, name: 'Grace Han' })).toBeVisible();
+  expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
+});
diff --git a/site/wp-content/themes/hollis-child/style.css b/site/wp-content/themes/hollis-child/style.css
index 817d48d..b4fe857 100644
--- a/site/wp-content/themes/hollis-child/style.css
+++ b/site/wp-content/themes/hollis-child/style.css
@@ -10,26 +10,6 @@ Text Domain: hollis-child
 
 @import url("../clinicpress/style.css");
 
-/* 2019: the header was widened to fit the new menu and the booking button on one line. */
-.site-header .header-inner {
-	width: 1180px;
-	max-width: none;
-	flex-wrap: nowrap;
-}
-
-.main-nav ul {
-	flex-wrap: nowrap;
-	white-space: nowrap;
-}
-
-.main-nav.is-collapsible {
-	display: block !important;
-}
-
-.menu-toggle {
-	display: none !important;
-}
-
 .main-nav a {
 	color: #8aa7a2;
 	font-weight: 400;

The proving test

e2e/tests/a1-mobile-layout.spec.ts

e2e/tests/a1-mobile-layout.spec.ts, as committed.

import { test, expect } from '@playwright/test';
import { BEFORE, AFTER } from './helpers';

// Exhibit A-1. On a 390px phone the BEFORE page scrolls sideways and the booking button sits off screen;
// the AFTER page fits the screen, the button is in view and the menu opens behind its toggle.
test.use({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });

async function layout(page: import('@playwright/test').Page) {
  const cta = page.locator('.site-header .header-cta');
  const box = await cta.boundingBox();
  const width = await page.evaluate(() => document.documentElement.scrollWidth);
  return { width, box };
}

test('A-1 before: the page is wider than the phone and the booking button is off screen', async ({ page }) => {
  await page.goto(BEFORE + '/');
  const { width, box } = await layout(page);
  expect(width).toBeGreaterThan(390);
  expect(box).not.toBeNull();
  expect(box!.x + box!.width).toBeGreaterThan(390);
});

test('A-1 after: the page fits 390px, the booking button is in view and leads to the form', async ({ page }) => {
  await page.goto(AFTER + '/');
  const { width, box } = await layout(page);
  expect(width).toBeLessThanOrEqual(390);
  expect(box!.x).toBeGreaterThanOrEqual(0);
  expect(box!.x + box!.width).toBeLessThanOrEqual(390);
  await page.locator('.site-header .header-cta').click();
  await expect(page.getByRole('heading', { level: 1, name: 'Request an appointment' })).toBeVisible();
});

test('A-1 after: the menu opens from its toggle and reaches the team page', async ({ page }) => {
  await page.goto(AFTER + '/');
  const toggle = page.getByRole('button', { name: 'Menu' });
  await expect(toggle).toBeVisible();
  await expect(page.getByRole('navigation', { name: 'Primary' }).getByRole('link', { name: 'Our team' })).toBeHidden();
  await toggle.click();
  await expect(toggle).toHaveAttribute('aria-expanded', 'true');
  await page.getByRole('navigation', { name: 'Primary' }).getByRole('link', { name: 'Our team' }).click();
  await expect(page.getByRole('heading', { level: 3, name: 'Grace Han' })).toBeVisible();
  expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
});

test('A-1 after: the header does not shift when the page finishes loading (CLS)', async ({ page }) => {
  await page.addInitScript(() => {
    (window as any).__cls = 0;
    new PerformanceObserver((list) => {
      for (const e of list.getEntries() as any[]) if (!e.hadRecentInput) (window as any).__cls += e.value;
    }).observe({ type: 'layout-shift', buffered: true });
  });
  await page.goto(AFTER + '/', { waitUntil: 'networkidle' });
  await page.waitForTimeout(500);
  expect(await page.evaluate(() => (window as any).__cls)).toBeLessThan(0.01);
});

Back to Exhibit A-1