# Inner Page Layout ## Goal Build the inner page layout shown in `notes/inner-page-mockup.png` so that every WordPress page renders the same structure: a blue-to-teal gradient hero with a background image and a decorative vector, the page title, an optional intro block (heading + body) inside the hero, and the standard editor body below it. Different pages vary only by hero image, vector, intro text, and body content. ## Scope - Applies to all `page` post-type views rendered by `page.php` (and through it, the `page-hero` partial that `header.php` already loads). - Does not change the front page, single posts, archives, search, or 404 templates. - Does not change the homepage-hero block. - Does not add a new contact block to the page body — the dark navy block with the orange rule in the mockup is the existing site footer, not a new CTA. ## Architecture The layout is a fixed two-part composition: 1. **Hero** — `views/partials/page-hero.php` (existing), extended with the blue-to-teal gradient backdrop, two new ACF fields, the hero image rendered on the right (faded to transparent on the left so the heading has a clear backdrop), a decorative vector along the bottom, the heading, and the intro block. 2. **Body** — `the_content`, unchanged. `page-hero.php` is the orchestrator: it renders the gradient backdrop, the optional hero image and vector, the heading, and the intro block all inside a single `.page-hero` wrapper. The `intro` ACF field, when set, renders as a two-column heading + body block inside the hero (not as a separate page section). The intro heading is a hard-coded "Our Work" label; the body is the editor-supplied `intro` text. `page.php` continues to render `the_content` after the hero. Sidebar behavior (already gated by `hasSidebar()` and the `Page Sidebar` field group) is preserved. `page.php` no longer loads the `page-intro` partial directly — the partial is loaded by `page-hero.php` instead. `hasPageHeader()` in `lib/extras.php` continues to gate the hero. The function's current behavior (hero shows when `hero_style !== 'none'`) is kept; no change to that filter is required. ## ACF Field Group Changes Add two fields to the existing `Page Heading` group (`acf/group_60bfb84ae973c.json`): - `hero_image` — type: `image`, return_format: `array`, library: `all`, preview_size: `medium`. Conditional: shown when `hero_style !== 'none'`. Optional. - `hero_vector` — type: `image`, return_format: `array`, library: `all`, preview_size: `medium`. Conditional: shown when `hero_style !== 'none'`. Optional. No other ACF group changes. The existing `heading`, `intro`, and `call_to_actions` fields are reused as-is. ## Hero Partial — `views/partials/page-hero.php` Changes: - The wrapper `
` containing `wp_kses_post( $intro )`. - The partial returns early when the `intro` field is empty, so no empty wrapper is rendered. The partial is loaded by `page-hero.php` (not `page.php`). The intro is **part of the hero composition** in the rendered page, not a separate page section. ## page.php Wiring `page.php` continues to render the article container, `the_content`, and the sidebar (when `hasSidebar()`). The hero is rendered by `header.php` before this template runs. The `page-intro` partial is no longer loaded directly by `page.php` — it is loaded by `page-hero.php` (inside the hero wrapper). No other behavior changes. ## Data Flow - Editor experience: on every page (and post) edit screen, the existing Page Heading meta box shows the existing fields plus two new ones (Hero Image, Vector). The editor uploads the assets, sets a background color if desired, writes the optional heading override, intro, and CTAs, then writes the body in the standard block editor. - Render: `header.php` loads the page-hero partial → `page-hero.php` renders the gradient + optional hero image + vector + heading + (when `intro` is set) the intro block, all inside `.page-hero` → `page.php` renders `the_content` for the body blocks. - Per-page variation: hero image, vector, intro text, and body content. Everything else is shared. ## Error Handling and Edge Cases - `hero_image` empty → hero uses the gradient (or the override `background_color`) as the only background. - `hero_vector` empty → no vector renders; the bottom of the hero is just the gradient. - `intro` empty → page-intro partial returns early; the hero renders without the intro block, and the page goes straight from hero to body. - `heading` empty → falls back to `getTheTitle()` (existing behavior, unchanged). - Background color set without a hero image → existing behavior; the inline `style` overrides the gradient with the solid color. - No sidebar field set → existing `hasSidebar()` filter returns false → no sidebar (existing behavior). - Lazy-loaded images must await `load`/`complete` before Playwright reads dimensions (existing project convention; see `tests/mobile-homepage.spec.js:641`). ## Out of Scope - New ACF blocks. This layout uses the existing Page Heading meta box, not the block editor. - Front page, single posts, archives, search, 404 changes. - Homepage-hero block changes. - The contact-block, pull-quote, our-work, or other existing blocks. - A new contact CTA block in the page body. The mockup's bottom dark block is the existing site footer. - Migration of existing pages' content. ## Verification - Add `tests/inner-page.spec.js` covering: - Desktop (1280px): hero, intro, body render with the intro block as a descendant of the hero. The heading and intro heading/body are all visible. The hero exposes the gradient backdrop. - Mobile (402px): intro is inside the hero and fits within the viewport. The vector is visible at the bottom of the hero. No clipped text. - 320px and 767px: nothing clips, hero and intro remain inside the viewport. - A page with no `intro` field renders the hero without a `.page-hero__intro` wrapper. - A page with no `hero_image` and no `hero_vector` renders the existing gradient text-only hero unchanged. - Add a test page fixture (or use an existing page) to drive the test. If no fixture exists, the test seeds a page in a `beforeAll` and tears it down in `afterAll`. - Run `npm run build` to regenerate `static/dist/`. - Run the full Playwright suite (`npx playwright test`) — including the existing `tests/mobile-homepage.spec.js` and `tests/site-a11y.spec.js` — to confirm no regressions. - Run `composer lint` to catch PHPCS issues. - Capture a 1280px and a 402px full-page screenshot of the test fixture for visual comparison against `notes/inner-page-mockup.png`. ## Acceptance Criteria 1. Every WordPress page renders the new layout when the page-hero partial is active. 2. The hero shows the blue-to-teal gradient backdrop, the page title (or heading override), and — when supplied — a background image on the right side and a decorative vector along the bottom. 3. When the `intro` field is set, a two-column heading + body block appears inside the hero below the H1. When empty, it is omitted entirely (no empty wrapper). 4. The body renders `the_content` as before, with the standard block editor. 5. The hero background image is masked so it is visible on the right and fades to transparent on the left, so the heading on the left has the gradient as a clean backdrop at all viewports. 6. The vector scales proportionally from 320px through desktop and is repositioned below 768px to fill the lower portion of the hero. 7. The page renders without console errors, axe violations, or PHPCS errors. 8. Existing single posts, archives, search, and 404 pages render the page-hero partial unchanged. 9. Existing tests (`mobile-homepage`, `site-a11y`) continue to pass.