119 lines
11 KiB
Markdown
119 lines
11 KiB
Markdown
# 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 the page title, an optional hero image at its natural size on the right with the bottom hanging over the hero's bottom edge, a decorative vector along the lower-left with breathing room from the bottom, an optional intro text block (the editor-supplied `intro` field) on the left below the H1, and the standard editor body below the hero. 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 `<div>` no longer carries the `bg-dark` or `bg-cover bg-no-repeat` Tailwind utilities. The wrapper inherits a blue-to-teal gradient from `.page-hero` (defined in `styles/blocks/page-hero.css`), the same gradient used by the homepage-hero block. When the editor sets a `background_color` ACF field, the inline `style` attribute on the wrapper overrides the gradient with that solid color.
|
|
- Read the two new fields (`hero_image`, `hero_vector`) and the existing `intro` field.
|
|
- Render the breadcrumbs, the H1, and the intro **inside** a single `.page-hero__content` container, **before** the image and vector markup. The H1 has a dedicated `.page-hero__heading` class for typography control. The intro, when non-empty, is a single `.page-hero__intro` div containing only the editor-supplied intro text (no hard-coded heading).
|
|
- Render `hero_image` as a foreground absolutely-positioned `<img>` (class `.page-hero__media`), anchored to the right of the hero with `bottom: -10%` so the image's bottom hangs over the bottom edge of the hero. The image is shown at its natural size (no `object-fit: cover`, no mask); it sits above the gradient backdrop and below the content layer.
|
|
- Render `hero_vector` as an absolutely-positioned `<img>` (class `.page-hero__vector`), anchored to the bottom-left of the hero with `bottom: clamp(1.5rem, 4vw, 3rem)` for spacing from the bottom edge. The vector sits above the image and below the content.
|
|
- When no `hero_image` is set, the gradient (or the override `background_color`) is the only background. When no `hero_vector` is set, the bottom of the hero is just the gradient.
|
|
|
|
Both new images are `loading="lazy"` and decorative. No hard-coded copy is rendered.
|
|
|
|
## Hero Partial CSS — `styles/blocks/page-hero.css` (new)
|
|
|
|
A dedicated file follows the project's existing pattern (`contact-block.css`, `homepage-hero.css`).
|
|
|
|
Rules:
|
|
|
|
- `.page-hero` — the wrapper. `position: relative; isolation: isolate; overflow: hidden; min-height: clamp(31rem, 38vw, 36rem);`. `background: linear-gradient(83.68deg, #032F46 3.13%, #006196 45.91%, #8FC9E6 96.27%);` — same gradient as the homepage-hero block.
|
|
- `.page-hero__content` — `position: relative; z-index: 10; padding-block: clamp(2rem, 6vw, 4rem);` so the heading, intro, and breadcrumbs always sit above the image and vector layers.
|
|
- `.page-hero__heading` — `font-family: var(--font-quincy, 'Quincy', serif); font-weight: 400; font-size: clamp(2.5rem, 6vw, 4.5rem); line-height: 1.05; max-width: 36rem;` so the H1 doesn't span the full hero width and stays aligned with the intro text.
|
|
- `.page-hero__intro` — `font-size: clamp(1rem, 1.5vw, 1.25rem); line-height: 1.5; max-width: 36rem; opacity: 0.95;` so the intro text sits alongside the H1.
|
|
- `.page-hero__media` — `position: absolute; right: clamp(1.5rem, 4vw, 4rem); bottom: -10%; width: clamp(18rem, 32vw, 28rem); height: auto; pointer-events: none; z-index: 1;`. The image is shown at its natural size.
|
|
- `.page-hero__vector` — `position: absolute; left: 0; bottom: clamp(1.5rem, 4vw, 3rem); width: clamp(28rem, 60vw, 56rem); height: auto; pointer-events: none; z-index: 5;`. The vector is sized to span the lower-left of the hero with breathing room from the bottom edge.
|
|
- Mobile (≤ 767px) breakpoint: hero min-height becomes `clamp(28rem, 130vw, 40rem)`; image shrinks to `60vw` and sits flush right (`right: 0`); vector shrinks to `80vw`.
|
|
|
|
These rules are scoped to `.page-hero` and only affect the page-hero partial. They do not affect the homepage-hero block, which uses a different root class.
|
|
|
|
## 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. 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 → the `.page-hero__intro` div is not rendered; the hero shows the H1 only.
|
|
- `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 H1 is 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) in the Quincy serif face, and — when supplied — a hero image at its natural size on the right with the bottom hanging over the hero's bottom edge, and a decorative vector along the lower-left with some spacing from the bottom edge.
|
|
3. When the `intro` field is set, a single text block appears inside the hero below the H1, in the same left column. When empty, the intro block is omitted entirely.
|
|
4. The body renders `the_content` as before, with the standard block editor.
|
|
5. No hard-coded copy is rendered by the partial — only the page title, the editor-supplied `intro` text, and the editor-supplied images.
|
|
6. The hero image and vector render at all viewports without clipping.
|
|
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.
|