Files
CWC/docs/superpowers/specs/2026-07-01-inner-page-layout-design.md
T
Keith SolomonandClaude ec87cae6c5 Update inner page layout spec for hero+intro refactor
- Goal: hero is now a blue-to-teal gradient (not a flat dark color) and the
  intro block lives inside the hero composition (not as a separate page
  section).
- Architecture: reduced from 3-part (hero / intro / body) to 2-part
  (hero / body) since the intro is now embedded in the hero.
- Hero Partial: dropped the bg-dark utility (gradient is set in CSS), the
  hero image is anchored to the right with a left-to-right fade, and the
  vector is full-width anchored to the bottom at z-index 2.
- Hero Partial CSS: the file is at styles/blocks/page-hero.css (not
  views/partials/page-hero.css). Updated the rules to match the new
  gradient + image right-side + vector bottom-center + intro grid layout.
- Intro Partial: rewritten as a fragment for embedding inside the hero
  (two-column heading + body grid) rather than a centered standalone
  section. Partial is loaded by page-hero.php, not page.php.
- page.php Wiring: no longer loads the page-intro partial directly; the
  partial is loaded by page-hero.php.
- Error Handling, Verification, and Acceptance Criteria: updated to
  match the new structure (no empty .page-intro wrapper check; intro is
  a descendant of the hero; gradient + image-right + vector-bottom-center
  expectations).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-01 15:26:12 -05:00

11 KiB

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. Heroviews/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. Bodythe_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).
  • Render hero_image as an absolutely-positioned <img> inside a new .page-hero__media container. The image is anchored to the right (object-position: right center) and masked with a left-to-right fade (transparent on the left, visible on the right) so the heading on the left has the gradient as a clean backdrop.
  • Render hero_vector as an absolutely-positioned, full-width <img> anchored to the bottom of the hero, with aria-hidden="true" and role="presentation". The vector sits above the media layer (z-index: 2) and below the content (z-index: 10), so it is visible across the bottom of the hero.
  • Render the heading (h1) inside the new .page-hero__content container, above the media layers.
  • Load the page-intro partial inside .page-hero__content, below the heading. The intro block only renders when the intro ACF field is non-empty.
  • Keep the breadcrumb rendering unchanged.
  • When no hero_image is set, the gradient (or the override background_color) is the only background.

Both new images are loading="lazy" and decorative.

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; so the new media layers stack predictably. background: linear-gradient(83.68deg, #032F46 3.13%, #006196 45.91%, #8FC9E6 96.27%); — same gradient as the homepage-hero block.
  • .page-hero__mediaposition: absolute; inset: 0; z-index: 1; -webkit-mask-image: linear-gradient(to left, transparent 0%, black 50%); mask-image: linear-gradient(to left, transparent 0%, black 50%);. The image inside it is width: 100%; height: 100%; object-fit: cover; object-position: right center;. The mask fades the image from invisible on the left to fully visible on the right.
  • .page-hero__vectorposition: absolute; left: 0; bottom: 0; width: 100%; height: auto; z-index: 2; pointer-events: none;. The mobile breakpoint (≤ 767px) repositions the vector to fill the lower portion of the hero: width: 100vw; right: auto; and the __media mask switches to a top-to-bottom gradient.
  • .page-hero__contentposition: relative; z-index: 10; so the heading and intro block always sit above the media and vector layers.
  • .page-hero__intromargin-top: clamp(2rem, 6vw, 4rem); provides spacing between the H1 and the intro block.
  • .page-hero__intro-innerdisplay: grid; gap: clamp(1.5rem, 4vw, 3rem); grid-template-columns: 1fr; on mobile, grid-template-columns: 1fr 2fr; on desktop, so the heading and body form a two-column layout matching the mockup.

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 two-column heading + body block inside the page hero:

  • Wrapper: <div class="page-hero__intro text-light">
  • Inner: <div class="page-hero__intro-inner"> — a CSS grid (1 column on mobile, 1fr / 2fr on desktop) holding:
    • <h2 class="page-hero__intro-heading text-2xl lg:text-3xl font-quincy font-normal leading-tight"> containing the hard-coded label "Our Work".
    • <p class="page-hero__intro-text text-base lg:text-lg leading-relaxed text-light/90"> 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-heropage.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.