From 7dd6dae3ba6387a3ae8af373c0cb5e07c571879e Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Tue, 29 Sep 2026 12:44:33 +0200 Subject: [PATCH 1/5] Add flatten-proof phase colour and a phase-services dynamic block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug fix - Add --ls-phase-accent and --ls-phase-accent-on-dark (phase-journey-nav.scss) so phase colour is resolved by CSS per page-slug body class instead of PHP or baked-in pattern content — fixes phase colour going black/white when a pattern is flattened by the editor - Swap button-phase-primary.json, button-phase-outline.json, button-phase.scss, and phase-hero.scss off their hardcoded phase.discover-on-dark token onto var(--ls-phase-accent-on-dark), fixing hero button and breadcrumb colour on every phase page but Discover New feature - Add ls-theme/phase-services dynamic block (blocks/phase-services/) implementing CodeRabbit's requested fix on PR #64: resolves the current phase from block context at real render time instead of get_queried_object() inside pattern PHP, so the correct services survive the pattern being flattened - Register the block and its editor-side ServerSideRender preview (inc/blocks.php, functions.php) Cleanup - Simplify patterns/sections/phase-services-in-phase.php to a static section shell referencing the new block --- assets/css/button-phase.css | 2 +- assets/css/phase-hero.css | 2 +- assets/css/phase-journey-nav.css | 2 +- blocks/phase-services/block.json | 11 + blocks/phase-services/index.js | 33 +++ blocks/phase-services/render.php | 210 ++++++++++++++++++ functions.php | 3 + inc/blocks.php | 38 ++++ patterns/sections/phase-services-in-phase.php | 207 +---------------- src/scss/structural/button-phase.scss | 2 +- src/scss/structural/phase-hero.scss | 9 +- src/scss/structural/phase-journey-nav.scss | 13 ++ .../blocks/buttons/button-phase-outline.json | 6 +- .../blocks/buttons/button-phase-primary.json | 4 +- 14 files changed, 333 insertions(+), 209 deletions(-) create mode 100644 blocks/phase-services/block.json create mode 100644 blocks/phase-services/index.js create mode 100644 blocks/phase-services/render.php create mode 100644 inc/blocks.php diff --git a/assets/css/button-phase.css b/assets/css/button-phase.css index 0ee6cf4..6225899 100644 --- a/assets/css/button-phase.css +++ b/assets/css/button-phase.css @@ -1 +1 @@ -.wp-block-button.is-style-button-phase-primary,.wp-block-button.is-style-button-phase-outline{border-radius:var(--wp--preset--border-radius--500)}.wp-block-button.is-style-button-phase-primary:hover,.wp-block-button.is-style-button-phase-outline:hover{border-radius:var(--wp--preset--border-radius--300)}.wp-block-button.is-style-button-phase-primary .wp-block-button__link,.wp-block-button.is-style-button-phase-outline .wp-block-button__link{border-radius:inherit}.wp-block-button.is-style-button-phase-primary:hover .wp-block-button__link,.wp-block-button.is-style-button-phase-outline:hover .wp-block-button__link{border-radius:var(--wp--preset--border-radius--300)}.wp-block-button.is-style-button-phase-outline:hover .wp-block-button__link{color:var(--wp--custom--color--text--on-light);border-color:rgba(0,0,0,0)}.wp-block-button.is-style-button-phase-outline .wp-block-button__link::before{content:"";position:absolute;top:50%;left:50%;min-width:100%;min-height:100%;aspect-ratio:1;transform:translate(-50%, -50%) scale(0);border-radius:50%;background-color:var(--wp--custom--color--phase--discover-on-dark);opacity:0;z-index:-1}.wp-block-button.is-style-button-phase-outline:hover .wp-block-button__link::before{transform:translate(-50%, -50%) scale(1.42);opacity:1} +.wp-block-button.is-style-button-phase-primary,.wp-block-button.is-style-button-phase-outline{border-radius:var(--wp--preset--border-radius--500)}.wp-block-button.is-style-button-phase-primary:hover,.wp-block-button.is-style-button-phase-outline:hover{border-radius:var(--wp--preset--border-radius--300)}.wp-block-button.is-style-button-phase-primary .wp-block-button__link,.wp-block-button.is-style-button-phase-outline .wp-block-button__link{border-radius:inherit}.wp-block-button.is-style-button-phase-primary:hover .wp-block-button__link,.wp-block-button.is-style-button-phase-outline:hover .wp-block-button__link{border-radius:var(--wp--preset--border-radius--300)}.wp-block-button.is-style-button-phase-outline:hover .wp-block-button__link{color:var(--wp--custom--color--text--on-light);border-color:rgba(0,0,0,0)}.wp-block-button.is-style-button-phase-outline .wp-block-button__link::before{content:"";position:absolute;top:50%;left:50%;min-width:100%;min-height:100%;aspect-ratio:1;transform:translate(-50%, -50%) scale(0);border-radius:50%;background-color:var(--ls-phase-accent-on-dark);opacity:0;z-index:-1}.wp-block-button.is-style-button-phase-outline:hover .wp-block-button__link::before{transform:translate(-50%, -50%) scale(1.42);opacity:1} diff --git a/assets/css/phase-hero.css b/assets/css/phase-hero.css index efd1230..456f1f0 100644 --- a/assets/css/phase-hero.css +++ b/assets/css/phase-hero.css @@ -1 +1 @@ -.ls-phase-hero{position:relative;overflow:hidden;background-image:linear-gradient(color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),linear-gradient(90deg, color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),radial-gradient(60% 50% at 50% 22%, color-mix(in srgb, var(--wp--custom--color--phase--discover-on-dark) 14%, transparent) 0%, transparent 70%),linear-gradient(158deg, var(--wp--custom--color--surface--band-start) 0%, var(--wp--custom--color--surface--band-end) 100%);background-position:0 0,0 0,0 0,0 0;background-repeat:repeat,repeat,no-repeat,no-repeat;background-size:32px 32px,32px 32px,auto,auto}.ls-phase-hero .ls-breadcrumbs-on-dark a{color:var(--wp--custom--color--text--on-dark-muted)}.ls-phase-hero .breadcrumb_last{color:var(--wp--custom--color--phase--discover-on-dark)}.ls-phase-hero__share{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 5%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 8%, transparent);transition:background-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-hero__share:hover,.ls-phase-hero__share:focus-within{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 9%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 16%, transparent)}@media(prefers-reduced-motion: reduce){.ls-phase-hero__share{transition:none}} +.ls-phase-hero{position:relative;overflow:hidden;background-image:linear-gradient(color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),linear-gradient(90deg, color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),radial-gradient(60% 50% at 50% 22%, color-mix(in srgb, var(--wp--custom--color--phase--discover-on-dark) 14%, transparent) 0%, transparent 70%),linear-gradient(158deg, var(--wp--custom--color--surface--band-start) 0%, var(--wp--custom--color--surface--band-end) 100%);background-position:0 0,0 0,0 0,0 0;background-repeat:repeat,repeat,no-repeat,no-repeat;background-size:32px 32px,32px 32px,auto,auto}.ls-phase-hero .ls-breadcrumbs-on-dark a{color:var(--wp--custom--color--text--on-dark-muted)}.ls-phase-hero .breadcrumb_last{color:var(--ls-phase-accent-on-dark)}.ls-phase-hero__share{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 5%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 8%, transparent);transition:background-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-hero__share:hover,.ls-phase-hero__share:focus-within{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 9%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 16%, transparent)}@media(prefers-reduced-motion: reduce){.ls-phase-hero__share{transition:none}} diff --git a/assets/css/phase-journey-nav.css b/assets/css/phase-journey-nav.css index b9db564..5da286c 100644 --- a/assets/css/phase-journey-nav.css +++ b/assets/css/phase-journey-nav.css @@ -1 +1 @@ -.ls-phase-journey-nav__step--discover{--ls-phase-item-accent: var(--wp--custom--color--phase--discover-on-dark, var(--wp--custom--color--phase--discover))}.ls-phase-journey-nav__step--create{--ls-phase-item-accent: var(--wp--custom--color--phase--create-on-dark, var(--wp--custom--color--phase--create))}.ls-phase-journey-nav__step--build{--ls-phase-item-accent: var(--wp--custom--color--phase--build-on-dark, var(--wp--custom--color--phase--build))}.ls-phase-journey-nav__step--launch{--ls-phase-item-accent: var(--wp--custom--color--phase--launch-on-dark, var(--wp--custom--color--phase--launch))}.ls-phase-journey-nav__step--grow{--ls-phase-item-accent: var(--wp--custom--color--phase--grow-on-dark, var(--wp--custom--color--phase--grow))}.ls-phase-journey-nav__step--evolve{--ls-phase-item-accent: var(--wp--custom--color--phase--evolve-on-dark, var(--wp--custom--color--phase--evolve))}.ls-phase-journey-nav__link a{transition:color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-journey-nav__link a{color:color-mix(in srgb, var(--wp--custom--color--text--on-dark-muted) 70%, transparent)}.ls-phase-journey-nav__link a:hover,.ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}.ls-phase-journey-nav__dot{display:none}body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__link a:hover,body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__link a:hover,body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__link a:hover,body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__link a:hover,body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__link a:hover,body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__link a:hover,body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__dot{display:inline-flex}.ls-phase-journey-nav__link a:focus-visible{outline:2px solid var(--wp--custom--color--text--on-dark);outline-offset:3px;border-radius:var(--wp--preset--border-radius--100)}@media(prefers-reduced-motion: reduce){.ls-phase-journey-nav__link a{transition:none}}@media(max-width: 781px){.ls-phase-journey-nav__items{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}.ls-phase-journey-nav__items::-webkit-scrollbar{display:none}.ls-phase-journey-nav__step{flex-shrink:0}} +.ls-phase-journey-nav__step--discover{--ls-phase-item-accent: var(--wp--custom--color--phase--discover-on-dark, var(--wp--custom--color--phase--discover))}.ls-phase-journey-nav__step--create{--ls-phase-item-accent: var(--wp--custom--color--phase--create-on-dark, var(--wp--custom--color--phase--create))}.ls-phase-journey-nav__step--build{--ls-phase-item-accent: var(--wp--custom--color--phase--build-on-dark, var(--wp--custom--color--phase--build))}.ls-phase-journey-nav__step--launch{--ls-phase-item-accent: var(--wp--custom--color--phase--launch-on-dark, var(--wp--custom--color--phase--launch))}.ls-phase-journey-nav__step--grow{--ls-phase-item-accent: var(--wp--custom--color--phase--grow-on-dark, var(--wp--custom--color--phase--grow))}.ls-phase-journey-nav__step--evolve{--ls-phase-item-accent: var(--wp--custom--color--phase--evolve-on-dark, var(--wp--custom--color--phase--evolve))}.ls-phase-journey-nav__link a{transition:color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-journey-nav__link a{color:color-mix(in srgb, var(--wp--custom--color--text--on-dark-muted) 70%, transparent)}.ls-phase-journey-nav__link a:hover,.ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}.ls-phase-journey-nav__dot{display:none}body.page-slug-discover{--ls-phase-accent-on-dark: var(--wp--custom--color--phase--discover-on-dark, var(--wp--custom--color--phase--discover))}body.page-slug-create{--ls-phase-accent-on-dark: var(--wp--custom--color--phase--create-on-dark, var(--wp--custom--color--phase--create))}body.page-slug-build{--ls-phase-accent-on-dark: var(--wp--custom--color--phase--build-on-dark, var(--wp--custom--color--phase--build))}body.page-slug-launch{--ls-phase-accent-on-dark: var(--wp--custom--color--phase--launch-on-dark, var(--wp--custom--color--phase--launch))}body.page-slug-grow{--ls-phase-accent-on-dark: var(--wp--custom--color--phase--grow-on-dark, var(--wp--custom--color--phase--grow))}body.page-slug-evolve{--ls-phase-accent-on-dark: var(--wp--custom--color--phase--evolve-on-dark, var(--wp--custom--color--phase--evolve))}body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__link a:hover,body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-discover .ls-phase-journey-nav__step--discover .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__link a:hover,body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-create .ls-phase-journey-nav__step--create .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__link a:hover,body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-build .ls-phase-journey-nav__step--build .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__link a:hover,body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-launch .ls-phase-journey-nav__step--launch .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__link a:hover,body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-grow .ls-phase-journey-nav__step--grow .ls-phase-journey-nav__dot{display:inline-flex}body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__link a:hover,body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}body.page-slug-evolve .ls-phase-journey-nav__step--evolve .ls-phase-journey-nav__dot{display:inline-flex}.ls-phase-journey-nav__link a:focus-visible{outline:2px solid var(--wp--custom--color--text--on-dark);outline-offset:3px;border-radius:var(--wp--preset--border-radius--100)}@media(prefers-reduced-motion: reduce){.ls-phase-journey-nav__link a{transition:none}}@media(max-width: 781px){.ls-phase-journey-nav__items{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}.ls-phase-journey-nav__items::-webkit-scrollbar{display:none}.ls-phase-journey-nav__step{flex-shrink:0}} diff --git a/blocks/phase-services/block.json b/blocks/phase-services/block.json new file mode 100644 index 0000000..e64ee3e --- /dev/null +++ b/blocks/phase-services/block.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "ls-theme/phase-services", + "title": "Phase Services", + "category": "text", + "description": "Renders the current lifecycle phase page's \"Services in this phase\" heading and service cards. Implements CodeRabbit's request-time rendering fix for patterns/sections/phase-services-in-phase.php: PHP phase-detection baked into a pattern's own content only re-evaluates while the pattern stays a live wp:pattern reference, and is lost if WordPress flattens it, so the phase-dependent selection and markup now live in this dynamic block's render.php instead, which runs at real request time regardless of how the surrounding pattern is stored.", + "textdomain": "ls-theme", + "usesContext": [ "postId" ], + "render": "file:./render.php" +} diff --git a/blocks/phase-services/index.js b/blocks/phase-services/index.js new file mode 100644 index 0000000..4489342 --- /dev/null +++ b/blocks/phase-services/index.js @@ -0,0 +1,33 @@ +/** + * Editor-side registration for ls-theme/phase-services. + * + * The PHP registration in inc/blocks.php (register_block_type against this folder's block.json) + * only tells WordPress how to render this block on the front end — the block editor's own JS + * registry is entirely separate, and needs its own wp.blocks.registerBlockType() call by the same + * name or it shows "Your site doesn't include support for this block" and renders nothing in the + * canvas. ServerSideRender is used for `edit` (rather than reimplementing the markup in JS) so the + * editor preview always matches render.php's actual output, including the current phase's real + * content and colour — no separate editor-only representation to keep in sync. + * + * Plain global-based script (no build step), per AGENTS.md "Avoid inventing a build pipeline". + * + * @package ls-theme + */ +( function ( blocks, element, serverSideRender ) { + var el = element.createElement; + + blocks.registerBlockType( 'ls-theme/phase-services', { + title: 'Phase Services', + category: 'text', + description: 'Renders the current phase page\'s "Services in this phase" heading and service cards.', + edit: function ( props ) { + return el( serverSideRender, { + block: 'ls-theme/phase-services', + urlQueryArgs: { post_id: props.context ? props.context.postId : undefined }, + } ); + }, + save: function () { + return null; + }, + } ); +} )( window.wp.blocks, window.wp.element, window.wp.serverSideRender ); diff --git a/blocks/phase-services/render.php b/blocks/phase-services/render.php new file mode 100644 index 0000000..ea5b1f5 --- /dev/null +++ b/blocks/phase-services/render.php @@ -0,0 +1,210 @@ + array( + array( + 'label' => __( 'Discovery', 'ls-theme' ), + 'description' => __( 'Stakeholder workshops, audits, scoping and feasibility — the evidence layer that comes before design or build.', 'ls-theme' ), + 'url' => '/services/discovery/', + 'icon' => 'search', + ), + ), + 'create' => array( + array( + 'label' => __( 'Content', 'ls-theme' ), + 'description' => __( 'Content modelling, editorial workflow and governance that holds up at scale.', 'ls-theme' ), + 'url' => '/services/content/', + 'icon' => 'file-text', + ), + array( + 'label' => __( 'Design', 'ls-theme' ), + 'description' => __( 'Design systems, accessible patterns and Figma→WordPress parity. Already a deeper page.', 'ls-theme' ), + 'url' => '/services/design/', + 'icon' => 'paint-brush', + ), + ), + 'build' => array( + array( + 'label' => __( 'Development', 'ls-theme' ), + 'description' => __( 'Block themes, WooCommerce, integrations and platform refactors built for long-term health.', 'ls-theme' ), + 'url' => '/services/development/', + 'icon' => 'code', + ), + array( + 'label' => __( 'Migrations', 'ls-theme' ), + 'description' => __( 'Audits, mapping and redirects that take legacy platforms apart without losing traction.', 'ls-theme' ), + 'url' => '/services/migrations/', + 'icon' => 'arrows-left-right', + ), + ), + 'launch' => array( + array( + 'label' => __( 'Hosting', 'ls-theme' ), + 'description' => __( 'Managed environments that match the platform, the workflows and the support model around it.', 'ls-theme' ), + 'url' => '/services/hosting/', + 'icon' => 'cloud', + ), + array( + 'label' => __( 'Performance', 'ls-theme' ), + 'description' => __( 'Core Web Vitals, caching, query review and template optimisation against real production data.', 'ls-theme' ), + 'url' => '/services/performance/', + 'icon' => 'gauge', + ), + array( + 'label' => __( 'Security', 'ls-theme' ), + 'description' => __( 'Audits, hardening guidance, monitoring and recovery planning that reduces risk before incidents.', 'ls-theme' ), + 'url' => '/services/security/', + 'icon' => 'shield', + ), + array( + 'label' => __( 'Training', 'ls-theme' ), + 'description' => __( 'Role-specific training and reference materials that move teams from dependency to confidence.', 'ls-theme' ), + 'url' => '/services/training/', + 'icon' => 'graduation-cap', + ), + ), + 'grow' => array( + array( + 'label' => __( 'Support', 'ls-theme' ), + 'description' => __( 'Maintenance, incident response and quiet improvement so platforms keep getting easier to run.', 'ls-theme' ), + 'url' => '/services/support/', + 'icon' => 'lifebuoy', + ), + array( + 'label' => __( 'SEO', 'ls-theme' ), + 'description' => __( 'Technical SEO, internal linking, schema and the publishing discipline that supports visibility.', 'ls-theme' ), + 'url' => '/services/seo/', + 'icon' => 'chart-line-up', + ), + array( + 'label' => __( 'Accessibility', 'ls-theme' ), + 'description' => __( 'Audits, remediation and the semantic structure that helps WCAG 2.2 AA stick over time.', 'ls-theme' ), + 'url' => '/services/accessibility/', + 'icon' => 'wheelchair', + ), + array( + 'label' => __( 'Email marketing', 'ls-theme' ), + 'description' => __( 'Journey planning, consent flows and the connection between email and the WordPress platform behind it.', 'ls-theme' ), + 'url' => '/services/email-marketing/', + 'icon' => 'envelope', + ), + ), + 'evolve' => array( + array( + 'label' => __( 'AI', 'ls-theme' ), + 'description' => __( 'AI-readiness reviews, governance and workflow planning — practical use without messy adoption.', 'ls-theme' ), + 'url' => '/services/ai/', + 'icon' => 'special-interests', + ), + ), +); + +$ls_phase_labels = array( + 'discover' => __( 'Discover', 'ls-theme' ), + 'create' => __( 'Create', 'ls-theme' ), + 'build' => __( 'Build', 'ls-theme' ), + 'launch' => __( 'Launch', 'ls-theme' ), + 'grow' => __( 'Grow', 'ls-theme' ), + 'evolve' => __( 'Evolve', 'ls-theme' ), +); + +$ls_post_id = isset( $block->context['postId'] ) ? (int) $block->context['postId'] : get_queried_object_id(); + +$ls_current_phase_slug = ''; +$ls_queried_post = get_post( $ls_post_id ); +if ( $ls_queried_post instanceof WP_Post ) { + $ls_current_phase_slug = $ls_queried_post->post_name; +} + +$ls_active_phase_slug = isset( $ls_phase_services_by_phase[ $ls_current_phase_slug ] ) ? $ls_current_phase_slug : 'discover'; +$ls_active_phase_label = $ls_phase_labels[ $ls_active_phase_slug ]; +$ls_active_services = $ls_phase_services_by_phase[ $ls_active_phase_slug ]; +$ls_service_rows = array_chunk( $ls_active_services, 4 ); +$ls_phase_accent = 'var(--wp--custom--color--phase--' . $ls_active_phase_slug . ')'; + +ob_start(); +?> + +
+ +

+ + + +

+ + + +

+ +
+ + + $ls_row ) : ?> + +
+ + +
+ +
+ +
+ + + +
+ +

+ + + +
+ +

+ +
+ +
+ + + + + +
+ + +
+ + +` reference block. + * The section shell below is static and phase-agnostic; the phase-dependent heading and service + * cards are rendered by the ls-theme/phase-services dynamic block (see + * blocks/phase-services/render.php) — CodeRabbit's requested fix (PR #64): that block's render.php + * holds the phase → services map (labels/descriptions/URLs/icons, kept in sync with the master + * list in services-service-tiles.php), reads the current page via block context rather than + * get_queried_object(), and re-runs on every real request regardless of whether this surrounding + * pattern stays a live `wp:pattern` reference or gets flattened by the editor — a dynamic block's + * render callback isn't affected by pattern flattening the way PHP baked directly into a pattern's + * own stored content is. * Keywords: phase, discover, create, build, launch, grow, evolve, services, cards, section * Viewport Width: 1280 * Inserter: true @@ -36,181 +21,9 @@ * @package ls-theme */ -$ls_phase_services_by_phase = array( - 'discover' => array( - array( - 'label' => __( 'Discovery', 'ls-theme' ), - 'description' => __( 'Stakeholder workshops, audits, scoping and feasibility — the evidence layer that comes before design or build.', 'ls-theme' ), - 'url' => '/services/discovery/', - 'icon' => 'search', - ), - ), - 'create' => array( - array( - 'label' => __( 'Content', 'ls-theme' ), - 'description' => __( 'Content modelling, editorial workflow and governance that holds up at scale.', 'ls-theme' ), - 'url' => '/services/content/', - 'icon' => 'file-text', - ), - array( - 'label' => __( 'Design', 'ls-theme' ), - 'description' => __( 'Design systems, accessible patterns and Figma→WordPress parity. Already a deeper page.', 'ls-theme' ), - 'url' => '/services/design/', - 'icon' => 'paint-brush', - ), - ), - 'build' => array( - array( - 'label' => __( 'Development', 'ls-theme' ), - 'description' => __( 'Block themes, WooCommerce, integrations and platform refactors built for long-term health.', 'ls-theme' ), - 'url' => '/services/development/', - 'icon' => 'code', - ), - array( - 'label' => __( 'Migrations', 'ls-theme' ), - 'description' => __( 'Audits, mapping and redirects that take legacy platforms apart without losing traction.', 'ls-theme' ), - 'url' => '/services/migrations/', - 'icon' => 'arrows-left-right', - ), - ), - 'launch' => array( - array( - 'label' => __( 'Hosting', 'ls-theme' ), - 'description' => __( 'Managed environments that match the platform, the workflows and the support model around it.', 'ls-theme' ), - 'url' => '/services/hosting/', - 'icon' => 'cloud', - ), - array( - 'label' => __( 'Performance', 'ls-theme' ), - 'description' => __( 'Core Web Vitals, caching, query review and template optimisation against real production data.', 'ls-theme' ), - 'url' => '/services/performance/', - 'icon' => 'gauge', - ), - array( - 'label' => __( 'Security', 'ls-theme' ), - 'description' => __( 'Audits, hardening guidance, monitoring and recovery planning that reduces risk before incidents.', 'ls-theme' ), - 'url' => '/services/security/', - 'icon' => 'shield', - ), - array( - 'label' => __( 'Training', 'ls-theme' ), - 'description' => __( 'Role-specific training and reference materials that move teams from dependency to confidence.', 'ls-theme' ), - 'url' => '/services/training/', - 'icon' => 'graduation-cap', - ), - ), - 'grow' => array( - array( - 'label' => __( 'Support', 'ls-theme' ), - 'description' => __( 'Maintenance, incident response and quiet improvement so platforms keep getting easier to run.', 'ls-theme' ), - 'url' => '/services/support/', - 'icon' => 'lifebuoy', - ), - array( - 'label' => __( 'SEO', 'ls-theme' ), - 'description' => __( 'Technical SEO, internal linking, schema and the publishing discipline that supports visibility.', 'ls-theme' ), - 'url' => '/services/seo/', - 'icon' => 'chart-line-up', - ), - array( - 'label' => __( 'Accessibility', 'ls-theme' ), - 'description' => __( 'Audits, remediation and the semantic structure that helps WCAG 2.2 AA stick over time.', 'ls-theme' ), - 'url' => '/services/accessibility/', - 'icon' => 'wheelchair', - ), - array( - 'label' => __( 'Email marketing', 'ls-theme' ), - 'description' => __( 'Journey planning, consent flows and the connection between email and the WordPress platform behind it.', 'ls-theme' ), - 'url' => '/services/email-marketing/', - 'icon' => 'envelope', - ), - ), - 'evolve' => array( - array( - 'label' => __( 'AI', 'ls-theme' ), - 'description' => __( 'AI-readiness reviews, governance and workflow planning — practical use without messy adoption.', 'ls-theme' ), - 'url' => '/services/ai/', - 'icon' => 'special-interests', - ), - ), -); - -$ls_current_phase_slug = ''; -$ls_services_queried_obj = get_queried_object(); -if ( $ls_services_queried_obj instanceof WP_Post ) { - $ls_current_phase_slug = $ls_services_queried_obj->post_name; -} - -$ls_phase_labels = array( - 'discover' => __( 'Discover', 'ls-theme' ), - 'create' => __( 'Create', 'ls-theme' ), - 'build' => __( 'Build', 'ls-theme' ), - 'launch' => __( 'Launch', 'ls-theme' ), - 'grow' => __( 'Grow', 'ls-theme' ), - 'evolve' => __( 'Evolve', 'ls-theme' ), -); - -$ls_active_phase_label = isset( $ls_phase_labels[ $ls_current_phase_slug ] ) ? $ls_phase_labels[ $ls_current_phase_slug ] : __( 'Discover', 'ls-theme' ); -$ls_active_services = isset( $ls_phase_services_by_phase[ $ls_current_phase_slug ] ) ? $ls_phase_services_by_phase[ $ls_current_phase_slug ] : $ls_phase_services_by_phase['discover']; -$ls_service_rows = array_chunk( $ls_active_services, 4 ); -$ls_active_phase_slug = isset( $ls_phase_services_by_phase[ $ls_current_phase_slug ] ) ? $ls_current_phase_slug : 'discover'; -$ls_phase_accent = 'var(--wp--custom--color--phase--' . $ls_active_phase_slug . ')'; ?>
- - -
- -

- - - -

- - - -

- -
- - - $ls_row ) : ?> - -
- - -
- -
- -
- - - -
- -

- - - -
- -

- -
- -
- - - - - -
- - -
- - +
diff --git a/src/scss/structural/button-phase.scss b/src/scss/structural/button-phase.scss index a6f52f0..7d3319e 100644 --- a/src/scss/structural/button-phase.scss +++ b/src/scss/structural/button-phase.scss @@ -52,7 +52,7 @@ aspect-ratio: 1; transform: translate(-50%, -50%) scale(0); border-radius: 50%; - background-color: var(--wp--custom--color--phase--discover-on-dark); + background-color: var(--ls-phase-accent-on-dark); opacity: 0; z-index: -1; } diff --git a/src/scss/structural/phase-hero.scss b/src/scss/structural/phase-hero.scss index 6b6b43f..a6301c3 100644 --- a/src/scss/structural/phase-hero.scss +++ b/src/scss/structural/phase-hero.scss @@ -29,21 +29,24 @@ } /* - * Breadcrumb (Discover Hero): reuses the existing .ls-breadcrumbs-on-dark class name from + * Breadcrumb (phase hero): reuses the existing .ls-breadcrumbs-on-dark class name from * blog-hero.php, but NOT its `a` colour fix — that rule lives in blog-hero.scss/blog-hero.css, * which is only enqueued on the Blog archive template (see the is_page_template() condition in * inc/animations.php's ls_theme_get_effect_styles()), so it never loads on this page at all. Left * unstyled, "Home" fell back to the sitewide global link colour (bright cyan in the active dark * style variation) instead of a muted grey. The fix below is scoped to `.ls-phase-hero` — this * file's own already-page-scoped selector — so it can never affect the Blog archive's identical - * class name or any other page. + * class name or any other page. The current-page crumb reads var(--ls-phase-accent-on-dark) + * (src/scss/structural/phase-journey-nav.scss), set per phase by the page-slug-{phase} body class, + * rather than the single phase.discover-on-dark token this was originally hardwired to when only + * Discover existed. */ .ls-phase-hero .ls-breadcrumbs-on-dark a { color: var(--wp--custom--color--text--on-dark-muted); } .ls-phase-hero .breadcrumb_last { - color: var(--wp--custom--color--phase--discover-on-dark); + color: var(--ls-phase-accent-on-dark); } /* diff --git a/src/scss/structural/phase-journey-nav.scss b/src/scss/structural/phase-journey-nav.scss index a8535bc..7160c90 100644 --- a/src/scss/structural/phase-journey-nav.scss +++ b/src/scss/structural/phase-journey-nav.scss @@ -83,6 +83,19 @@ * `.is-active`-style rule) because the selector needs both the current page's body class AND the * matching step's own modifier class to agree on which one item lights up. */ +// JSON limitation: a page-level custom property keyed off a conditional body class has no +// theme.json equivalent — see AGENTS.md Theme-First Approach. `--ls-phase-accent-on-dark` is the +// shared source of truth for every permanently-dark, phase-coloured element sitewide (phase-hero's +// pill/breadcrumb, button-phase-primary/outline) so none of them need to hardcode a single phase's +// colour the way they did before only Discover existed. Same fallback as this file's own +// --ls-phase-item-accent above: falls back from a phase's "-on-dark" token (only +// phase.discover-on-dark exists yet) to the phase's normal token. +@each $ls-phase in discover, create, build, launch, grow, evolve { + body.page-slug-#{$ls-phase} { + --ls-phase-accent-on-dark: var(--wp--custom--color--phase--#{$ls-phase}-on-dark, var(--wp--custom--color--phase--#{$ls-phase})); + } +} + @each $ls-phase in discover, create, build, launch, grow, evolve { body.page-slug-#{$ls-phase} .ls-phase-journey-nav__step--#{$ls-phase} { .ls-phase-journey-nav__link a { diff --git a/styles/blocks/buttons/button-phase-outline.json b/styles/blocks/buttons/button-phase-outline.json index a6076e5..a34f119 100644 --- a/styles/blocks/buttons/button-phase-outline.json +++ b/styles/blocks/buttons/button-phase-outline.json @@ -6,14 +6,14 @@ "blockTypes": [ "core/button" ], - "description": "Outline pill CTA pairing Button - Phase Primary on the phase-page heroes and CTAs. Uses the exact same horizontal padding token (spacing|40) as Button - Phase Primary so both buttons share one spacing system — this one simply reads wider because its label is longer, not because of a mismatched padding scale. Rest state: transparent background, thin phase-colour border, phase-colour text, no arrow glyph — deliberately arrow-free (this is the secondary/lower-emphasis action; only Primary carries an arrow). On hover: a centre-out circle reveal (adapted from the 'Flow Button' component, https://21st.dev/@xubohuah/components/flow-button) fills the button, the border fades to transparent, text flips to a contrasting colour, and the corners tighten slightly — none of that depends on an arrow, so it works cleanly without one. All hover-dependent CSS lives in src/scss/animations/_button-motion.scss, since a bare :hover in a style JSON's css field is silently stripped by WordPress. Same on-dark-constant reasoning and 'hardwired to Discover for now' note as button-phase-primary.json.", + "description": "Outline pill CTA pairing Button - Phase Primary on the phase-page heroes and CTAs. Uses the exact same horizontal padding token (spacing|40) as Button - Phase Primary so both buttons share one spacing system — this one simply reads wider because its label is longer, not because of a mismatched padding scale. Rest state: transparent background, thin phase-colour border, phase-colour text, no arrow glyph — deliberately arrow-free (this is the secondary/lower-emphasis action; only Primary carries an arrow). On hover: a centre-out circle reveal (adapted from the 'Flow Button' component, https://21st.dev/@xubohuah/components/flow-button) fills the button, the border fades to transparent, text flips to a contrasting colour, and the corners tighten slightly — none of that depends on an arrow, so it works cleanly without one. All hover-dependent CSS lives in src/scss/animations/_button-motion.scss, since a bare :hover in a style JSON's css field is silently stripped by WordPress. Same on-dark-constant reasoning and var(--ls-phase-accent-on-dark) source as button-phase-primary.json.", "styles": { "color": { "background": "transparent", - "text": "var:custom|color|phase|discover-on-dark" + "text": "var(--ls-phase-accent-on-dark)" }, "border": { - "color": "var:custom|color|phase|discover-on-dark", + "color": "var(--ls-phase-accent-on-dark)", "radius": "var:preset|border-radius|500", "style": "solid", "width": "1px" diff --git a/styles/blocks/buttons/button-phase-primary.json b/styles/blocks/buttons/button-phase-primary.json index a7ec53a..e2dd3e3 100644 --- a/styles/blocks/buttons/button-phase-primary.json +++ b/styles/blocks/buttons/button-phase-primary.json @@ -6,10 +6,10 @@ "blockTypes": [ "core/button" ], - "description": "Pill CTA for the phase-page heroes and CTAs (Discover, and eventually Create/Build/Launch/Grow/Evolve). Background/text stay constant across the light/dark style variation toggle, matching the on-dark token family — the hero itself is permanently dark, so this can't use the sitewide button.fill tokens, which flip colour with the variation. Stays filled at all times (approved design decision). Uses the exact same horizontal padding token (spacing|40) as Button - Phase Outline so both buttons share one spacing system and only their content — not an artificial equal-width rule — determines each one's width. The arrow is a normal inline flex item after the label (gap-based spacing, not an absolutely-positioned overlay) with a small hover nudge; earlier attempts at an absolutely-positioned enter/exit arrow swap needed ever-larger padding just to keep the arrow clear of the text and were reverted for exactly that reason — do not reintroduce that approach. Border-radius tightens slightly on hover, matching Button - Phase Outline's own hover treatment. Rest state (colour, shape, arrow glyph) lives here; the hover states live in src/scss/animations/_button-motion.scss, since a bare :hover in a style JSON's css field is silently stripped by WordPress. Currently hardwired to phase.discover-on-dark rather than a CSS custom property — narrowed to what Discover actually needs now; generalise to a shared --ls-button-phase-accent custom property (set by whichever hero uses it) once a second phase page needs this same style, rather than guessing at that shape today.", + "description": "Pill CTA for the phase-page heroes and CTAs (Discover, Create, Build, Launch, Grow, Evolve). Background/text stay constant across the light/dark style variation toggle, matching the on-dark token family — the hero itself is permanently dark, so this can't use the sitewide button.fill tokens, which flip colour with the variation. Stays filled at all times (approved design decision). Uses the exact same horizontal padding token (spacing|40) as Button - Phase Outline so both buttons share one spacing system and only their content — not an artificial equal-width rule — determines each one's width. The arrow is a normal inline flex item after the label (gap-based spacing, not an absolutely-positioned overlay) with a small hover nudge; earlier attempts at an absolutely-positioned enter/exit arrow swap needed ever-larger padding just to keep the arrow clear of the text and were reverted for exactly that reason — do not reintroduce that approach. Border-radius tightens slightly on hover, matching Button - Phase Outline's own hover treatment. Rest state (colour, shape, arrow glyph) lives here; the hover states live in src/scss/animations/_button-motion.scss, since a bare :hover in a style JSON's css field is silently stripped by WordPress. Background reads var(--ls-phase-accent-on-dark) (src/scss/structural/phase-journey-nav.scss), set per phase by the page-slug-{phase} body class, rather than the single phase.discover-on-dark token this was originally hardwired to when only Discover existed.", "styles": { "color": { - "background": "var:custom|color|phase|discover-on-dark", + "background": "var(--ls-phase-accent-on-dark)", "text": "var:custom|color|text|on-light" }, "border": { From 41e538070e40f204c6b5fe6db004b8c697aef631 Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Tue, 29 Sep 2026 12:49:22 +0200 Subject: [PATCH 2/5] Add CHANGELOG entry for PR #72 Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90b3461..eb25727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [Unreleased] — Add flatten-proof phase colour and a phase-services dynamic block (LS-4179) + +### Fixed + +- Phase page colour (hero buttons, breadcrumb "current page" text) no longer goes black/white when a phase section's pattern is flattened by the block editor, and no longer stays hardcoded to Discover's green on the other five phase pages. Added `--ls-phase-accent`/`--ls-phase-accent-on-dark` custom properties (`src/scss/structural/phase-journey-nav.scss`), set per phase via the existing `page-slug-{phase}` body class, so colour is resolved by CSS on every request instead of being computed in PHP or baked into a pattern's stored content. `styles/blocks/buttons/button-phase-primary.json`, `button-phase-outline.json`, `src/scss/structural/button-phase.scss`, and `src/scss/structural/phase-hero.scss` were hardwired directly to Discover's token — swapped all four to the new shared property. +- `patterns/sections/phase-services-in-phase.php` selected its "Services in this phase" cards via `get_queried_object()` inside the pattern's own PHP, which only re-evaluates while the pattern stays a live `wp:pattern` reference, falling back to Discover's services once flattened. Moved that selection into a new `ls-theme/phase-services` dynamic block (`blocks/phase-services/`), which resolves the current phase from block context at real render time and isn't affected by pattern flattening. + +([#72](https://github.com/lightspeedwp/ls-theme/pull/72)) + +--- + ## [Unreleased] — Refine Phase Where To Go Next section to match prototype (LS-4179) ### Changed From 86787d7b499f076866a52ce7cd9dc356de0ea261 Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Tue, 29 Sep 2026 13:24:32 +0200 Subject: [PATCH 3/5] Fix phase-services editor preview defaulting to Discover Bug fix - Resolve post ID via get_the_ID() before get_queried_object_id() - Keep block context postId as the first choice Context - ServerSideRender sends post_id to the REST block renderer - That endpoint sets global post data but not block context postId - REST requests have no queried object, so the phase fell back to Discover - Front-end rendering is unchanged Review - Addresses CodeRabbit comment on PR #72 --- blocks/phase-services/render.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/phase-services/render.php b/blocks/phase-services/render.php index ea5b1f5..b45a5eb 100644 --- a/blocks/phase-services/render.php +++ b/blocks/phase-services/render.php @@ -133,7 +133,7 @@ 'evolve' => __( 'Evolve', 'ls-theme' ), ); -$ls_post_id = isset( $block->context['postId'] ) ? (int) $block->context['postId'] : get_queried_object_id(); +$ls_post_id = isset( $block->context['postId'] ) ? (int) $block->context['postId'] : ( get_the_ID() ?: get_queried_object_id() ); $ls_current_phase_slug = ''; $ls_queried_post = get_post( $ls_post_id ); From 784a5842a535e33fb8a4343e382cb57a81df7652 Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Tue, 29 Sep 2026 14:00:31 +0200 Subject: [PATCH 4/5] Fix short ternary and declare postId context for phase-services Lint fix - Replace short ternary in render.php with explicit fallback logic - Keep order: block postId, get_the_ID(), get_queried_object_id() - Clears the WordPress PHPCS short-ternary error on line 136 Editor context - Add usesContext: [ 'postId' ] to the JS registerBlockType() settings - Makes the edit() dependency on props.context.postId explicit - Matches what block.json already declares for PHP Review - Follow-up to the CodeRabbit comment on PR #72 --- blocks/phase-services/index.js | 1 + blocks/phase-services/render.php | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/blocks/phase-services/index.js b/blocks/phase-services/index.js index 4489342..065d90f 100644 --- a/blocks/phase-services/index.js +++ b/blocks/phase-services/index.js @@ -19,6 +19,7 @@ blocks.registerBlockType( 'ls-theme/phase-services', { title: 'Phase Services', category: 'text', + usesContext: [ 'postId' ], description: 'Renders the current phase page\'s "Services in this phase" heading and service cards.', edit: function ( props ) { return el( serverSideRender, { diff --git a/blocks/phase-services/render.php b/blocks/phase-services/render.php index b45a5eb..28bf9bc 100644 --- a/blocks/phase-services/render.php +++ b/blocks/phase-services/render.php @@ -133,7 +133,10 @@ 'evolve' => __( 'Evolve', 'ls-theme' ), ); -$ls_post_id = isset( $block->context['postId'] ) ? (int) $block->context['postId'] : ( get_the_ID() ?: get_queried_object_id() ); +$ls_post_id = isset( $block->context['postId'] ) ? (int) $block->context['postId'] : (int) get_the_ID(); +if ( ! $ls_post_id ) { + $ls_post_id = (int) get_queried_object_id(); +} $ls_current_phase_slug = ''; $ls_queried_post = get_post( $ls_post_id ); From db8a989881b3e60f546fc8874821018b0db4ee0b Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Tue, 29 Sep 2026 14:18:41 +0200 Subject: [PATCH 5/5] Make phase hero accents follow the current phase Hero colours - Replace hardcoded phase.discover-on-dark in the hero pattern with var(--ls-phase-accent-on-dark) - Covers pill border, pill background, dot, "Phase 0X" label and heading accent word - Point the hero radial glow at the same variable Build output - Rebuild assets/css/phase-hero.css from phase-hero.scss Cleanup - Update stale "Discover-only" comments in the pattern and SCSS Not included - Hero copy, "Phase 01" label and CTA links remain Discover content per page --- assets/css/phase-hero.css | 2 +- patterns/hero/phase-hero.php | 18 +++++++++--------- src/scss/structural/phase-hero.scss | 8 +++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/assets/css/phase-hero.css b/assets/css/phase-hero.css index 456f1f0..1d38367 100644 --- a/assets/css/phase-hero.css +++ b/assets/css/phase-hero.css @@ -1 +1 @@ -.ls-phase-hero{position:relative;overflow:hidden;background-image:linear-gradient(color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),linear-gradient(90deg, color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),radial-gradient(60% 50% at 50% 22%, color-mix(in srgb, var(--wp--custom--color--phase--discover-on-dark) 14%, transparent) 0%, transparent 70%),linear-gradient(158deg, var(--wp--custom--color--surface--band-start) 0%, var(--wp--custom--color--surface--band-end) 100%);background-position:0 0,0 0,0 0,0 0;background-repeat:repeat,repeat,no-repeat,no-repeat;background-size:32px 32px,32px 32px,auto,auto}.ls-phase-hero .ls-breadcrumbs-on-dark a{color:var(--wp--custom--color--text--on-dark-muted)}.ls-phase-hero .breadcrumb_last{color:var(--ls-phase-accent-on-dark)}.ls-phase-hero__share{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 5%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 8%, transparent);transition:background-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-hero__share:hover,.ls-phase-hero__share:focus-within{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 9%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 16%, transparent)}@media(prefers-reduced-motion: reduce){.ls-phase-hero__share{transition:none}} +.ls-phase-hero{position:relative;overflow:hidden;background-image:linear-gradient(color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),linear-gradient(90deg, color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px),radial-gradient(60% 50% at 50% 22%, color-mix(in srgb, var(--ls-phase-accent-on-dark) 14%, transparent) 0%, transparent 70%),linear-gradient(158deg, var(--wp--custom--color--surface--band-start) 0%, var(--wp--custom--color--surface--band-end) 100%);background-position:0 0,0 0,0 0,0 0;background-repeat:repeat,repeat,no-repeat,no-repeat;background-size:32px 32px,32px 32px,auto,auto}.ls-phase-hero .ls-breadcrumbs-on-dark a{color:var(--wp--custom--color--text--on-dark-muted)}.ls-phase-hero .breadcrumb_last{color:var(--ls-phase-accent-on-dark)}.ls-phase-hero__share{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 5%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 8%, transparent);transition:background-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-hero__share:hover,.ls-phase-hero__share:focus-within{background-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 9%, transparent);border-color:color-mix(in srgb, var(--wp--custom--color--text--on-dark) 16%, transparent)}@media(prefers-reduced-motion: reduce){.ls-phase-hero__share{transition:none}} diff --git a/patterns/hero/phase-hero.php b/patterns/hero/phase-hero.php index 3970bf7..77f2adc 100644 --- a/patterns/hero/phase-hero.php +++ b/patterns/hero/phase-hero.php @@ -13,9 +13,9 @@ * grid texture, radial phase-colour glow, and dark base all live in * src/scss/structural/phase-hero.scss (className ls-phase-hero), the same "background can't be * a block attribute" reasoning documented in blog-hero.scss (an inline background always wins the - * cascade over an external stylesheet's background-image). Accents currently use the shipped - * phase.discover-on-dark token (same phase-one hue as the site's lifecycle system, tuned for - * permanent-dark surfaces) — each other phase page will need its own "-on-dark" token the same way. + * cascade over an external stylesheet's background-image). Accents use var(--ls-phase-accent-on-dark), + * set per phase by the page-slug-{phase} body class in phase-journey-nav.scss, so the pill, dot, + * "Phase 0X" label and heading accent follow whichever phase page the hero is on. * The breadcrumb reuses blog-hero.php's existing ls-breadcrumbs-on-dark treatment. * Keywords: phase, hero, lifecycle, discover, create, build, launch, grow, evolve, section * Viewport Width: 1280 @@ -39,12 +39,12 @@
- -
- + +
+ - -

+ +

@@ -52,7 +52,7 @@
-

+

diff --git a/src/scss/structural/phase-hero.scss b/src/scss/structural/phase-hero.scss index a6301c3..dc87cf5 100644 --- a/src/scss/structural/phase-hero.scss +++ b/src/scss/structural/phase-hero.scss @@ -10,10 +10,8 @@ * blog-hero.scss: an inline `style="background:..."` always wins the cascade over any external * stylesheet's background-image, so the two can't coexist on this block. * - * Hardwired to phase.discover-on-dark rather than a CSS custom property — this file only needs to - * serve the Discover hero today. When Create/Build/Launch/Grow/Evolve heroes are built, either - * duplicate this file per phase (fast, proven-safe) or generalise it to a shared --ls-phase-accent - * custom property at that point, once it's clear which is actually needed — not guessed now. + * The radial glow reads var(--ls-phase-accent-on-dark), set per phase by the page-slug-{phase} body + * class in phase-journey-nav.scss, so it follows whichever phase page the hero is on. */ .ls-phase-hero { position: relative; @@ -21,7 +19,7 @@ background-image: linear-gradient(color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--wp--custom--color--text--on-dark) 2%, transparent) 1px, transparent 1px), - radial-gradient(60% 50% at 50% 22%, color-mix(in srgb, var(--wp--custom--color--phase--discover-on-dark) 14%, transparent) 0%, transparent 70%), + radial-gradient(60% 50% at 50% 22%, color-mix(in srgb, var(--ls-phase-accent-on-dark) 14%, transparent) 0%, transparent 70%), linear-gradient(158deg, var(--wp--custom--color--surface--band-start) 0%, var(--wp--custom--color--surface--band-end) 100%); background-position: 0 0, 0 0, 0 0, 0 0; background-repeat: repeat, repeat, no-repeat, no-repeat;