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 index f45d48d..2019bfc 100644 --- a/docs/superpowers/specs/2026-07-01-inner-page-layout-design.md +++ b/docs/superpowers/specs/2026-07-01-inner-page-layout-design.md @@ -2,7 +2,7 @@ ## 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. +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 @@ -13,13 +13,14 @@ Build the inner page layout shown in `notes/inner-page-mockup.png` so that every ## Architecture -The layout is a fixed three-part composition: +The layout is a fixed two-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. +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.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. +`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. @@ -36,62 +37,61 @@ No other ACF group changes. The existing `heading`, `intro`, and `call_to_action Changes: +- The wrapper `
` element with `text-lg lg:text-xl leading-relaxed` containing `wp_kses_post( $intro )`. +- Wrapper: `
` containing `wp_kses_post( $intro )`. +- The partial returns early when the `intro` field is empty, so no empty wrapper is rendered. -No new fields, no new CSS file — Tailwind utilities cover the layout. +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` 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' )` **as a sibling of** the `entry-content` div (not inside it). The intro must be a peer of the body in the layout flow, not a child, so the test's order assertion `introBox.y < contentBox.y` holds (when intro is a child of entry-content, the two bounding boxes share the same top y and the order check fails). -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. +`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.php` loads the page-intro partial (if intro is set) → `the_content` renders the body blocks. +- 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 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. +- `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; no change. +- 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`). @@ -107,11 +107,11 @@ The hero is still rendered by `header.php` before this template runs. No other b ## 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. + - 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. @@ -121,10 +121,10 @@ The hero is still rendered by `header.php` before this template runs. No other b ## 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). +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 text on the left remains readable at all viewports. +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.