From 455a005e585d87814b8dab4b3d6f0b2aab6753c0 Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Wed, 1 Jul 2026 08:36:37 -0500 Subject: [PATCH] Add inner page layout design spec Defines the inner page layout: dark hero with background image and decorative vector (driven by two new fields on the existing Page Heading ACF group), a centered narrow intro section underneath (driven by the existing Intro field), and the standard the_content body. The mockup's bottom dark block is the existing site footer, not a new CTA. No new ACF blocks are introduced; the layout reuses the existing Page Heading meta box and the page-hero partial that header.php already loads. Co-Authored-By: Claude --- .../2026-07-01-inner-page-layout-design.md | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-01-inner-page-layout-design.md diff --git a/docs/superpowers/specs/2026-07-01-inner-page-layout-design.md b/docs/superpowers/specs/2026-07-01-inner-page-layout-design.md new file mode 100644 index 0000000..130223a --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-inner-page-layout-design.md @@ -0,0 +1,131 @@ +# 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 dark hero with a background image and a decorative vector on the right, a centered narrow intro section underneath, 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 three-part composition: + +1. **Hero** — `views/partials/page-hero.php` (existing), extended with two new ACF fields and two new visual slots. +2. **Intro** — new `views/partials/page-intro.php`, rendered by `page.php` between the hero and `the_content` when the `intro` ACF field is non-empty. +3. **Body** — `the_content`, unchanged. + +`page.php` becomes the orchestrator: it decides whether to render the intro partial based on the `intro` ACF value, then renders the body. Sidebar behavior (already gated by `hasSidebar()` and the `Page Sidebar` field group) is preserved. + +`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: + +- Read the two new fields (`hero_image`, `hero_vector`). +- Render `hero_image` as an absolutely-positioned `` inside a new `.page-hero__media` container, masked with a left-to-right fade so the text on the left remains readable. +- Render `hero_vector` as an absolutely-positioned, right-anchored, bottom-anchored `` inside a new `.page-hero__vector` container, with `aria-hidden="true"` and `role="presentation"`. +- Stop rendering the `intro` paragraph inside the hero. The `intro` field is now rendered in the new page-intro partial. (The heading still renders inside the hero, so the page title and the optional heading override work as before.) +- Keep the `heading`, `breadcrumbs`, and `call_to_actions` rendering unchanged. +- When no `hero_image` is set, the existing `background_color` (or the default dark gradient) is the only background. + +Both new images are `loading="lazy"` and decorative. + +## Hero Partial CSS — `views/partials/page-hero.css` (new) + +The partial needs a small CSS file for the absolute positioning and mask treatment that Tailwind utilities do not express cleanly. Inline styles in the partial are acceptable as a fallback if a CSS file is undesirable, but a dedicated file follows the project's existing pattern (`contact-block.css`, `homepage-hero.css`). + +Rules: + +- `.page-hero` — the existing wrapper. Add `position: relative` and `isolation: isolate` so the new media layers stack predictably. +- `.page-hero__media` — `position: absolute; inset: 0; z-index: 0; mask-image: linear-gradient(to right, transparent 0%, black 30%);` (mirrored `-webkit-mask-image`). The image inside it is `width: 100%; height: 100%; object-fit: cover;`. +- `.page-hero__vector` — `position: absolute; right: 0; bottom: 0; width: clamp(18rem, 38vw, 32rem); height: auto; z-index: 1; pointer-events: none;`. The mobile breakpoint (≤ 767px) repositions the vector to fill the lower portion of the hero: `width: 100vw; right: auto; left: 0; bottom: 0;` and the `__media` mask switches to a top-to-bottom gradient so the text remains readable. +- `.page-hero__content` (or the existing `.content-wrapper` div inside the hero) — `position: relative; z-index: 10;` so text always sits above the media layers. + +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. + +## Intro Partial — `views/partials/page-intro.php` (new) + +A small PHP partial that renders a single centered column: + +- Wrapper: `
` +- Container: `
` +- Inner: a single `

` element with `text-lg lg:text-xl leading-relaxed` containing `wp_kses_post( $intro )`. + +No new fields, no new CSS file — Tailwind utilities cover the layout. + +## page.php Wiring + +`page.php` changes from rendering the body directly to: + +1. Output the article container (existing behavior). +2. If `get_field( 'intro' )` is non-empty, call `get_template_part( 'views/partials/page-intro' )`. +3. Render `the_content` (existing behavior). +4. Render the sidebar if `hasSidebar()` (existing behavior). + +The hero is still 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.php` loads the page-intro partial (if intro is set) → `the_content` renders 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 existing dark background color (or the default dark text-only style). +- `hero_vector` empty → no vector renders; the right side of the hero is empty space. +- `intro` empty → page-intro partial is not loaded; 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; no change. +- 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 in that order. When hero image is set, it is visible behind the hero. When vector is set, it sits on the right of the hero. + - Mobile (402px): same order. Vector fills the lower portion of the hero. Intro is centered and within the 48rem max-width. No clipped text. + - 320px and 767px: nothing clips, vector scales proportionally, intro remains readable. + - A page with no `intro` field renders hero + body with no empty intro wrapper. + - A page with no `hero_image` and no `hero_vector` renders the existing dark 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 page title (or heading override) and, when supplied, a background image and a decorative vector on the right. +3. When the `intro` field is set, a centered narrow column appears under the hero. 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 text on the left remains readable 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.