# Mobile Homepage Fidelity Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make the homepage match the supplied 402px mobile section crops and remain proportional from 320px through 767px without changing layouts at 768px and above.

**Architecture:** Add narrowly scoped mobile overrides to the block or component that owns each visual region. Use the existing WordPress markup and content, with Playwright geometry assertions written before each correction. Treat 402px as the exact baseline, then verify the lower, upper, and desktop boundary widths.

**Tech Stack:** WordPress/PHP, ACF blocks, Tailwind CSS v4 with nested CSS, Playwright, axe-core, PHPCS, Prettier.

---

## File Map

- `tests/mobile-homepage.spec.js`: mobile geometry, ordering, breakpoint, and accessibility regression coverage.
- `styles/components/site-header.css`: hamburger appearance only.
- `views/blocks/homepage-hero/homepage-hero.php`: decorative location-label hook shown in the approved crop.
- `views/blocks/homepage-hero/homepage-hero.css`: hero height, content, CTAs, media crop, and linework.
- `views/blocks/pull-quote/pull-quote.css`: mobile type, block height, and vector crop.
- `views/blocks/media-text/media-text.css`: mobile text-first order, CTA row, collage size, and block spacing.
- `views/blocks/contact-block/contact-block.css`: mobile card dimensions, photo treatment, text, CTA, and linework.
- `views/blocks/our-work/our-work.css`: mobile content, vector density, and contained overlapping images.
- `styles/components/site-footer.css`: mobile footer grid and alignment.
- `static/dist/theme.css`: generated by `npm run build`; do not hand-edit.

## Reference Material

- `notes/header-hero.png` — 402×874.
- `notes/pullquote.png` — 402×361.
- `notes/image-text.png` — 402×964.
- `notes/contact.png` — 343×502.
- `notes/our-work.png` — 402×888.
- `notes/footer.png` — 402×673.

---

### Task 1: Add reusable mobile geometry assertions

**Files:**
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add geometry helpers below the homepage constant**

```js
const getBox = async (locator) => {
    const box = await locator.boundingBox();
    expect(box).not.toBeNull();
    return box;
};

const expectNear = (actual, expected, tolerance = 3) => {
    expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance);
};
```

- [ ] **Step 2: Replace the current hamburger size test with the approved target-only test**

```js
test("mobile header preserves its layout and uses the designed hamburger", async ({
    page,
}) => {
    await page.goto(homepage);

    const header = await getBox(page.locator(".site-header"));
    const toggle = page.locator(".nav-main__toggle");
    const toggleBox = await getBox(toggle);

    expectNear(header.height, 70, 2);
    expectNear(toggleBox.width, 72, 2);
    expectNear(toggleBox.height, 45, 2);
    await expect(toggle).toHaveCSS(
        "background-color",
        "oklch(0.6353 0.1751 29.61)",
    );
});
```

- [ ] **Step 3: Run the test and verify it fails for the hamburger dimensions**

Run:

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "designed hamburger" --reporter=line
```

Expected: FAIL because the current button is 36×36 rather than approximately 72×45.

---

### Task 2: Correct the hamburger and homepage hero

**Files:**
- Modify: `styles/components/site-header.css:73-96`
- Modify: `views/blocks/homepage-hero/homepage-hero.php:62-70`
- Modify: `views/blocks/homepage-hero/homepage-hero.css:55-114`
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add a failing hero geometry test**

```js
test("mobile hero matches the 402px reference composition", async ({ page }) => {
    await page.goto(homepage);

    const hero = await getBox(page.locator(".homepage-hero"));
    const heading = await getBox(page.locator(".homepage-hero .intro h1"));
    const buttons = page.locator(".homepage-hero .reset .button");
    const firstButton = await getBox(buttons.nth(0));
    const secondButton = await getBox(buttons.nth(1));

    expectNear(hero.height, 752, 8);
    expectNear(heading.x, 32, 3);
    expect(heading.width).toBeGreaterThanOrEqual(330);
    expectNear(firstButton.y, secondButton.y, 2);
    expectNear(firstButton.width, 148, 4);
    expectNear(secondButton.width, 156, 4);
    await expect(page.locator(".homepage-hero__location")).toBeVisible();
});
```

- [ ] **Step 2: Run the hero test and verify the current short composition fails**

Run:

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "reference composition" --reporter=line
```

Expected: FAIL because the current hero is about 332px tall and the content/buttons are undersized.

- [ ] **Step 3: Change only the hamburger button declarations inside the existing mobile query**

```css
.site-header .nav-main__toggle {
    align-items: center;
    background: var(--color-secondary);
    border-radius: 1.4rem 0.35rem 1.4rem 0.35rem;
    display: inline-flex;
    height: 2.8125rem;
    justify-content: center;
    margin-right: 1rem;
    padding: 0.65rem 1rem;
    width: 4.5rem;
}

.site-header .nav-main__toggle svg {
    height: 1.5rem;
    width: 2rem;
}
```

Do not alter the header container, logo, padding, or height rules.

- [ ] **Step 4: Add the decorative location label after `.heroVector`**

```php
    <div class="homepage-hero__location" aria-hidden="true">
        <svg viewBox="0 0 24 24" focusable="false">
            <path d="M12 21s7-5.6 7-12a7 7 0 1 0-14 0c0 6.4 7 12 7 12Z" />
            <circle cx="12" cy="9" r="2.5" />
        </svg>
        <span><?php echo esc_html__( 'Location / Nation Information', 'cwc' ); ?></span>
    </div>
```

The label is marked decorative because it reproduces a visual annotation from the approved design rather than page navigation or editorial content.

- [ ] **Step 5: Replace the hero mobile query with a 402px-baseline composition**

```css
.homepage-hero__location {
    display: none;
}

@media (max-width: 767px) {
    .homepage-hero {
        --hgtHero: clamp(37.5rem, 187vw, 47rem);

        min-height: var(--hgtHero);
    }

    .homepage-hero .content-wrapper {
        padding-inline: clamp(1.25rem, 8vw, 2rem);
    }

    .homepage-hero .content-wrapper > div {
        max-width: 21.25rem;
        padding-top: clamp(3rem, 14vw, 3.75rem);
        text-align: left;
    }

    .homepage-hero .intro h1 {
        font-size: clamp(1.75rem, 8vw, 2rem);
        letter-spacing: 0;
        line-height: 1.24;
        margin-bottom: 1.75rem;
    }

    .homepage-hero .intro h1 strong {
        font-size: inherit;
        line-height: inherit;
    }

    .homepage-hero .intro p {
        font-size: var(--text-14px);
        line-height: 1.3;
        margin: 0;
        max-width: 18rem;
    }

    .homepage-hero .reset {
        flex-wrap: nowrap;
        gap: clamp(0.75rem, 5.5vw, 1.4rem);
        justify-content: flex-start;
        margin-top: 2rem;
    }

    .homepage-hero .button {
        font-size: var(--text-18px);
        min-height: 2.75rem;
        min-width: 9.25rem;
        padding: 0.65rem 1rem;
    }

    .homepage-hero .reset x-button:nth-child(2) .button {
        min-width: 9.75rem;
    }

    .homepage-hero .heroVector .vector {
        bottom: 6.25rem;
        left: 50%;
        transform: translateX(-50%);
        width: clamp(43rem, 190vw, 48rem);
    }

    .homepage-hero .heroMedia img,
    .homepage-hero .heroMedia video {
        object-position: 58% center;
    }

    .homepage-hero .homepage-hero__location {
        align-items: center;
        bottom: 5.5rem;
        color: var(--color-cwc-blue-03);
        display: flex;
        font-size: var(--text-14px);
        gap: 0.5rem;
        left: 2rem;
        position: absolute;
        z-index: 30;
    }

    .homepage-hero .homepage-hero__location svg {
        fill: none;
        height: 1.25rem;
        stroke: currentColor;
        stroke-width: 1.75;
        width: 1.25rem;
    }
}
```

- [ ] **Step 6: Build and run the hamburger/hero tests**

```powershell
npm run build
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "designed hamburger|reference composition" --reporter=line
```

Expected: 2 passed.

- [ ] **Step 7: Capture and inspect the 402px header/hero**

```powershell
npx playwright screenshot --viewport-size "402,874" --wait-for-timeout 1000 http://community-works-collaborative.test "$env:TEMP\cwc-header-hero.png"
```

Compare directly with `notes/header-hero.png`. Adjust only the numeric values in the mobile query until heading, buttons, media crop, and vector anchor match.

- [ ] **Step 8: Commit the header and hero correction**

```powershell
git add styles/components/site-header.css views/blocks/homepage-hero/homepage-hero.php views/blocks/homepage-hero/homepage-hero.css tests/mobile-homepage.spec.js
git commit -m "fix: match mobile header and hero design"
```

---

### Task 3: Correct the pull quote

**Files:**
- Modify: `views/blocks/pull-quote/pull-quote.css:47-60`
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add the failing pull-quote test**

```js
test("mobile pull quote matches the reference scale", async ({ page }) => {
    await page.goto(homepage);

    const quote = await getBox(page.locator(".pull-quote"));
    const text = await getBox(page.locator(".pull-quote__text"));

    expectNear(quote.height, 361, 6);
    expect(text.width).toBeGreaterThanOrEqual(330);
    expect(text.height).toBeGreaterThanOrEqual(235);
});
```

- [ ] **Step 2: Run the test and verify it fails**

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "pull quote" --reporter=line
```

Expected: FAIL because the current text and block are substantially shorter than the reference.

- [ ] **Step 3: Replace the mobile pull-quote overrides**

```css
@media (max-width: 767px) {
    .pull-quote {
        border-bottom-width: 0.75rem;
        min-height: clamp(18rem, 89.8vw, 22.5625rem);
    }

    .pull-quote .pull-quote__vector img {
        height: 118%;
        object-fit: cover;
        object-position: center 42%;
        width: 118%;
    }

    .pull-quote .pull-quote__content {
        align-items: center;
        display: flex;
        min-height: inherit;
        padding: 3.5rem 2rem;
    }

    .pull-quote .pull-quote__text {
        font-size: clamp(1.5rem, 6.75vw, 1.7rem);
        line-height: 1.18;
        width: 100%;
    }
}
```

- [ ] **Step 4: Build, test, and compare with the crop**

```powershell
npm run build
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "pull quote" --reporter=line
```

Expected: PASS. Use a 402px screenshot to compare the vector crop and text wrapping with `notes/pullquote.png`.

- [ ] **Step 5: Commit the pull-quote correction**

```powershell
git add views/blocks/pull-quote/pull-quote.css tests/mobile-homepage.spec.js
git commit -m "fix: match mobile pull quote design"
```

---

### Task 4: Reorder and resize the image/text block

**Files:**
- Modify: `views/blocks/media-text/media-text.css`
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add failing order and CTA-row tests**

```js
test("mobile media text places copy and CTAs before the complete collage", async ({
    page,
}) => {
    await page.goto(homepage);

    const block = page.locator(".media-cols").first();
    const content = await getBox(block.locator(".content-wrapper"));
    const media = await getBox(block.locator(":scope > div").first());
    const buttons = block.locator(".reset .button");
    const firstButton = await getBox(buttons.nth(0));
    const secondButton = await getBox(buttons.nth(1));

    expect(content.y).toBeLessThan(media.y);
    expectNear(firstButton.y, secondButton.y, 2);
    expect(media.width).toBeGreaterThanOrEqual(335);
});
```

- [ ] **Step 2: Run the test and verify it fails for media order**

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "complete collage" --reporter=line
```

Expected: FAIL because the current `order-first` utility places the collage above the copy.

- [ ] **Step 3: Populate the empty block stylesheet with scoped mobile rules**

```css
@media (max-width: 767px) {
    .media-cols {
        gap: 2.75rem;
        margin-bottom: clamp(12rem, 55vw, 14rem);
        padding-inline: clamp(1.25rem, 8vw, 2rem);
    }

    .media-cols > div:first-child {
        order: 2;
        width: 100%;
    }

    .media-cols > .content-wrapper {
        order: 1;
        padding-inline: 0;
    }

    .media-cols > div:first-child img {
        display: block;
        height: auto;
        width: 100%;
    }

    .media-cols .content-wrapper > .font-light {
        font-size: var(--text-16px);
        line-height: 1.45;
    }

    .media-cols .reset {
        flex-wrap: nowrap;
        gap: 1.4rem;
        justify-content: flex-start;
        margin-top: 2rem;
    }

    .media-cols .reset .button {
        font-size: var(--text-18px);
        min-height: 2.75rem;
        min-width: 0;
        padding: 0.65rem 2rem;
    }
}
```

- [ ] **Step 4: Build, test, and compare with the reference**

```powershell
npm run build
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "complete collage" --reporter=line
```

Expected: PASS. Compare the full block with `notes/image-text.png`; tune the mobile `margin-bottom` so the contact block starts at the reference distance rather than the current excessive gap.

- [ ] **Step 5: Commit the media-text correction**

```powershell
git add views/blocks/media-text/media-text.css tests/mobile-homepage.spec.js
git commit -m "fix: match mobile image text layout"
```

---

### Task 5: Correct the contact card composition

**Files:**
- Modify: `views/blocks/contact-block/contact-block.css:46-99`
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add a failing contact-card geometry test**

```js
test("mobile contact card matches the reference frame", async ({ page }) => {
    await page.goto(homepage);

    const card = await getBox(page.locator(".contact-block.home-contact"));
    const content = await getBox(
        page.locator(".contact-block.home-contact .contact-block__content"),
    );

    expectNear(card.width, 343, 4);
    expectNear(card.height, 502, 8);
    expectNear(content.x - card.x, 32, 4);
});
```

- [ ] **Step 2: Run the test and verify it fails**

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "contact card" --reporter=line
```

Expected: FAIL because the current card is wider, shorter, and top-pads the content by seven rem.

- [ ] **Step 3: Replace the contact block mobile overrides**

```css
@media (max-width: 767px) {
    .contact-block {
        border-bottom-width: 0.75rem;
        border-radius: 1rem 1rem 0 0;
        min-height: clamp(25rem, 124.9vw, 31.375rem);
    }

    .contact-block .contact-block__media {
        inset: 0;
        mask-image: linear-gradient(to right, rgb(0 0 0 / 0.2) 0%, black 68%);
        -webkit-mask-image: linear-gradient(to right, rgb(0 0 0 / 0.2) 0%, black 68%);
        width: 100%;
    }

    .contact-block .contact-block__bg-image {
        object-fit: cover;
        object-position: 70% center;
    }

    .contact-block .contact-block__linework {
        bottom: 0;
        height: auto;
        left: 0;
        max-width: none;
        right: auto;
        top: auto;
        width: 112%;
    }

    .contact-block .contact-block__content {
        min-height: clamp(25rem, 124.9vw, 31.375rem);
        padding: 2rem 2rem 2.75rem !important;
        position: relative;
        z-index: 2;
    }

    .contact-block .contact-block__text {
        font-size: clamp(1.65rem, 7vw, 1.8rem);
        line-height: 1.08;
        max-width: 17rem !important;
    }

    .contact-block.home-contact {
        bottom: auto;
        left: 50%;
        margin: 2.5rem auto -3.75rem;
        max-width: 21.4375rem;
        position: relative;
        transform: translateX(-50%);
        width: min(85.3vw, 21.4375rem);
    }
}
```

- [ ] **Step 4: Build, test, and visually tune photo/vector anchors**

```powershell
npm run build
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "contact card" --reporter=line
```

Expected: PASS. Compare with `notes/contact.png`; adjust only `object-position`, mask stop, and linework width/offset until the photo and lower vector match.

- [ ] **Step 5: Commit the contact card correction**

```powershell
git add views/blocks/contact-block/contact-block.css tests/mobile-homepage.spec.js
git commit -m "fix: match mobile contact card design"
```

---

### Task 6: Correct Our Work vector and image containment

**Files:**
- Modify: `views/blocks/our-work/our-work.css:193-261`
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add failing block and image-containment tests**

```js
test("mobile Our Work contains both reference images", async ({ page }) => {
    await page.goto(homepage);

    const block = await getBox(page.locator(".our-work"));
    const left = await getBox(page.locator(".our-work__media--left"));
    const bottom = await getBox(page.locator(".our-work__media--bottom"));

    expectNear(block.height, 888, 12);
    expectNear(left.width, 189, 5);
    expect(bottom.width).toBeGreaterThanOrEqual(335);
    expect(bottom.x).toBeGreaterThanOrEqual(60);
});
```

- [ ] **Step 2: Run the test and verify it fails**

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "contains both" --reporter=line
```

Expected: FAIL because the existing left image is about 152px wide and the bottom image is undersized/clipped.

- [ ] **Step 3: Change the mobile breakpoint to the approved 767px scope and replace its rules**

```css
@media (max-width: 767px) {
    .our-work {
        margin-bottom: 2rem;
        min-height: clamp(44rem, 220.9vw, 55.5rem);
        padding-top: 4.25rem;
    }

    .our-work__vector img {
        height: 118%;
        object-fit: cover;
        object-position: 56% center;
        width: 118%;
    }

    .our-work__inner {
        display: block;
        padding-inline: 2rem;
    }

    .our-work__content {
        margin-inline: 0;
        max-width: 21rem;
        text-align: left;
    }

    .our-work__content .acf-innerblocks-container {
        gap: 1.75rem;
    }

    .our-work__content h1,
    .our-work__content h2,
    .our-work__content h3,
    .our-work__content h4,
    .our-work__content h5,
    .our-work__content h6 {
        font-size: clamp(2.5rem, 11vw, 2.75rem);
    }

    .our-work__content p {
        font-size: var(--text-16px);
        line-height: 1.45;
    }

    .our-work__content .wp-block-acf-buttons > div {
        flex-wrap: nowrap;
        gap: 2rem;
        justify-content: flex-start;
    }

    .our-work__content .button {
        font-size: var(--text-18px);
        min-height: 2.75rem;
        min-width: 9.75rem;
        padding: 0.65rem 1rem;
    }

    .our-work__media--left {
        margin: 4rem 0 0;
        max-width: 11.8125rem;
        position: relative;
        width: 55%;
        z-index: 2;
    }

    .our-work__media--bottom {
        margin: -3.5rem -2rem 0 auto;
        max-width: none;
        position: relative;
        width: 100%;
        z-index: 1;
    }

    .our-work__image {
        object-fit: contain;
    }
}
```

- [ ] **Step 4: Build, test, and compare the overlap**

```powershell
npm run build
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "contains both" --reporter=line
```

Expected: PASS. Compare with `notes/our-work.png`; tune vector `object-position` and the two media margins without changing image containment.

- [ ] **Step 5: Commit the Our Work correction**

```powershell
git add views/blocks/our-work/our-work.css tests/mobile-homepage.spec.js
git commit -m "fix: match mobile Our Work design"
```

---

### Task 7: Rebuild the mobile footer hierarchy

**Files:**
- Modify: `styles/components/site-footer.css:50-80`
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add a failing footer geometry test**

```js
test("mobile footer matches the reference hierarchy", async ({ page }) => {
    await page.goto(homepage);

    const footer = page.locator(".site-footer");
    const footerBox = await getBox(footer);
    const logo = await getBox(footer.locator(".site-footer__logo-image"));
    const backToTop = await getBox(footer.locator("#backToTopBtn"));
    const nav = await getBox(footer.locator("#footer-nav"));
    const contact = await getBox(footer.locator(".contact"));

    expectNear(footerBox.height, 673, 12);
    expectNear(logo.y, backToTop.y, 8);
    expect(nav.y).toBeGreaterThan(logo.y + logo.height);
    expect(contact.y).toBeGreaterThan(nav.y + nav.height);
});
```

- [ ] **Step 2: Run the test and verify the current four-cell grid fails**

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "footer matches" --reporter=line
```

Expected: FAIL because the back-to-top, nav, contact details, social links, and copyright do not occupy the reference rows.

- [ ] **Step 3: Replace the mobile footer query**

```css
@media (max-width: 767px) {
    .site-footer {
        min-height: clamp(34rem, 167.4vw, 42.0625rem);
        padding-block: 4rem 2rem;
    }

    .site-footer > .container {
        display: grid;
        gap: 1.75rem 1rem;
        grid-template-columns: minmax(0, 1fr) auto;
        padding-inline: 2rem;
    }

    .site-footer > .container > div:first-child {
        grid-column: 1;
        grid-row: 1;
    }

    .site-footer #footer-header {
        margin: 0;
        max-width: 12.25rem;
    }

    .site-footer #footer-nav {
        grid-column: 1 / -1;
        grid-row: 2;
    }

    .site-footer .site-footer__nav-list {
        display: grid;
        gap: 0.75rem 2rem;
        grid-auto-flow: column;
        grid-template-rows: repeat(3, auto);
    }

    .site-footer #footer-nav + div {
        grid-column: 1 / -1;
        grid-row: 3;
    }

    .site-footer #footer-description {
        font-size: var(--text-20px);
        max-width: 21rem;
    }

    .site-footer .contact__item {
        font-size: var(--text-16px);
    }

    .site-footer .text-right {
        display: contents;
    }

    .site-footer x-back-to-top {
        align-self: start;
        grid-column: 2;
        grid-row: 1;
    }

    .site-footer .social-links {
        grid-column: 1 / -1;
        grid-row: 4;
        justify-content: flex-end;
        margin-top: 0;
    }

    .site-footer .copyright {
        grid-column: 1 / -1;
        grid-row: 5;
        padding-block: 0;
        text-align: center;
    }
}
```

- [ ] **Step 4: Build, test, and compare against the footer crop**

```powershell
npm run build
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "footer matches" --reporter=line
```

Expected: PASS. Compare with `notes/footer.png`; tune only gaps, logo width, and row padding while retaining WordPress-driven content and link order.

- [ ] **Step 5: Commit the footer correction**

```powershell
git add styles/components/site-footer.css tests/mobile-homepage.spec.js
git commit -m "fix: match mobile footer design"
```

---

### Task 8: Verify responsive boundaries and desktop isolation

**Files:**
- Modify: `tests/mobile-homepage.spec.js`

- [ ] **Step 1: Add a parameterized proportional-layout test**

```js
for (const width of [320, 402, 767]) {
    test(`homepage remains contained at ${width}px`, async ({ page }) => {
        await page.setViewportSize({ width, height: 900 });
        await page.goto(homepage);

        const overflow = await page.evaluate(
            () => document.documentElement.scrollWidth - window.innerWidth,
        );

        expect(overflow).toBeLessThanOrEqual(1);
        await expect(page.locator(".homepage-hero .reset")).toBeVisible();
        await expect(page.locator(".contact-block.home-contact")).toBeVisible();
        await expect(page.locator(".our-work__media--bottom")).toBeVisible();
    });
}
```

- [ ] **Step 2: Add the desktop-isolation assertion**

```js
test("mobile overrides stop below the 768px desktop boundary", async ({
    page,
}) => {
    await page.setViewportSize({ width: 768, height: 900 });
    await page.goto(homepage);

    const media = page.locator(".media-cols").first();
    const content = await getBox(media.locator(".content-wrapper"));
    const image = await getBox(media.locator(":scope > div").first());

    expect(image.y).toBeLessThanOrEqual(content.y);
    await expect(page.locator(".site-footer > .container")).toHaveCSS(
        "grid-template-columns",
        /.+ .+ .+ .+/,
    );
});
```

- [ ] **Step 3: Run the new boundary tests**

```powershell
npx playwright test tests/mobile-homepage.spec.js --project=chromium --grep "remains contained|desktop boundary" --reporter=line
```

Expected: 4 passed and no horizontal overflow.

- [ ] **Step 4: Run the full production and quality gate**

```powershell
npm run build
npx playwright test --project=chromium --reporter=line
npx --yes prettier@3.6.2 --check static/js/modules/RecentPostsCarousel.js tests/mobile-homepage.spec.js tests/site-a11y.spec.js
composer validate --no-check-publish
vendor\bin\phpcs.bat --standard=.phpcs.xml footer.php views/blocks/recent-posts/recent-posts.php views/icons/facebook.php views/icons/linkedin.php views/icons/youtube.php
php -l footer.php
php -l views/blocks/recent-posts/recent-posts.php
git diff --check
```

Expected: build succeeds; all Playwright/axe tests pass; Prettier, Composer, PHPCS, PHP syntax, and whitespace checks exit 0.

- [ ] **Step 5: Capture final comparison screenshots**

```powershell
npx playwright screenshot --viewport-size "402,874" --full-page --wait-for-timeout 1500 http://community-works-collaborative.test "$env:TEMP\cwc-homepage-402-final.png"
npx playwright screenshot --viewport-size "320,800" --full-page --wait-for-timeout 1500 http://community-works-collaborative.test "$env:TEMP\cwc-homepage-320-final.png"
npx playwright screenshot --viewport-size "767,900" --full-page --wait-for-timeout 1500 http://community-works-collaborative.test "$env:TEMP\cwc-homepage-767-final.png"
```

Inspect the 402px capture section by section against all six reference crops. Inspect 320px and 767px for clipping, awkward line wrapping, and unintended overlap.

- [ ] **Step 6: Commit the boundary tests and final tuning**

```powershell
git add tests/mobile-homepage.spec.js styles/components/site-header.css styles/components/site-footer.css views/blocks/homepage-hero/homepage-hero.css views/blocks/pull-quote/pull-quote.css views/blocks/media-text/media-text.css views/blocks/contact-block/contact-block.css views/blocks/our-work/our-work.css
git commit -m "test: verify mobile homepage responsive fidelity"
```
